mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abea3024b4 | ||
|
|
07dafeb6cd | ||
|
|
a726d42ae3 | ||
|
|
d02d186a2f | ||
|
|
a6be66949d | ||
|
|
0dfd3b7443 | ||
|
|
6f90e9a76e | ||
|
|
9246b88560 | ||
|
|
61af9db362 | ||
|
|
5aa950e7f7 | ||
|
|
b8903af52f | ||
|
|
5b35c4e82b | ||
|
|
06db80780c | ||
|
|
e5fd4f5700 | ||
|
|
e1a199e060 | ||
|
|
22ac34c7a1 | ||
|
|
04fa6901b1 | ||
|
|
8f7d4211e3 | ||
|
|
1147d6fd9a | ||
|
|
e4449c4901 | ||
|
|
dc8963faa5 | ||
|
|
f9cf9a8fd4 | ||
|
|
393bdc04bc | ||
|
|
db4d787b49 | ||
|
|
cd802d6a66 | ||
|
|
be2281a5b3 | ||
|
|
e717396b33 | ||
|
|
db0e49f5dd | ||
|
|
d3b94aa6af | ||
|
|
713f702064 | ||
|
|
d0b6a9b28c | ||
|
|
4003781a1b | ||
|
|
40586a8f0e | ||
|
|
f4d427f5c5 | ||
|
|
ce2a4282f3 | ||
|
|
0cc78c1402 | ||
|
|
fa0d4da6d1 | ||
|
|
33334d6f5c | ||
|
|
55aba93faf | ||
|
|
4f49296a94 | ||
|
|
65357ee8da | ||
|
|
bfcad4001b | ||
|
|
5a3000febb | ||
|
|
2d236670ab | ||
|
|
796f1480a8 | ||
|
|
a8ee7be844 | ||
|
|
60b506cb2a | ||
|
|
66117e35ba | ||
|
|
22cfee4f01 | ||
|
|
d9355e576f | ||
|
|
e54e33edb0 | ||
|
|
25881ce343 | ||
|
|
f64197c189 | ||
|
|
a7ec907f1d | ||
|
|
81272b0bc8 | ||
|
|
25c1c6ef91 | ||
|
|
d1a83134e3 | ||
|
|
71c2993d20 | ||
|
|
14fa616723 | ||
|
|
d46ea9c9ba | ||
|
|
ea00ccb34f | ||
|
|
8e416bb3cb | ||
|
|
8e0a26073d | ||
|
|
9fa24948ea | ||
|
|
b9a9cc4b0f | ||
|
|
a67345f9bf | ||
|
|
2a022f39bd | ||
|
|
a3ba969589 |
2
.github/workflows/test-content.yml
vendored
2
.github/workflows/test-content.yml
vendored
@@ -38,4 +38,4 @@ jobs:
|
||||
- name: Content.Tests
|
||||
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
|
||||
- name: Content.IntegrationTests
|
||||
run: dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -13,3 +13,6 @@
|
||||
[submodule "ManagedHttpListener"]
|
||||
path = ManagedHttpListener
|
||||
url = https://github.com/space-wizards/ManagedHttpListener.git
|
||||
[submodule "Linguini"]
|
||||
path = Linguini
|
||||
url = https://github.com/space-wizards/Linguini
|
||||
|
||||
1
Linguini
Submodule
1
Linguini
Submodule
Submodule Linguini added at 62b0e75b91
@@ -23,12 +23,12 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return NativeLibrary.Load("libglfw.so.3", assembly, path);
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return NativeLibrary.Load("libglfw.3.dylib", assembly, path);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,48 @@
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
- name: Box2D
|
||||
license:
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Erin Catto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
- name: Bullet Physics SDK
|
||||
license:
|
||||
The files in this repository are licensed under the zlib license, except for the files under 'Extras' and examples/ThirdPartyLibs.
|
||||
|
||||
Bullet Continuous Collision Detection and Physics Library
|
||||
http://bulletphysics.org
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it freely,
|
||||
subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
|
||||
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
- name: Castle Core
|
||||
license: |
|
||||
Copyright 2004-2016 Castle Project - http://www.castleproject.org/
|
||||
@@ -317,6 +359,43 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
- name: Farseer Physics Engine
|
||||
license:
|
||||
Microsoft Permissive License (Ms-PL)
|
||||
|
||||
This license governs use of the accompanying software.
|
||||
If you use the software, you accept this license.
|
||||
If you do not accept the license, do not use the software.
|
||||
|
||||
1. Definitions
|
||||
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
|
||||
A "contribution" is the original software, or any additions or changes to the software.
|
||||
A "contributor" is any person that distributes its contribution under this license.
|
||||
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
|
||||
prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
|
||||
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
|
||||
derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
|
||||
your patent license from such contributor to the software ends automatically.
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
|
||||
and attribution notices that are present in the software.
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
|
||||
including a complete copy of this license with your distribution. If you distribute any portion of the software in
|
||||
compiled or object code form, you may only do so under a license that complies with this license.
|
||||
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
|
||||
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
|
||||
To the extent permitted under your local laws, the contributors exclude the implied warranties of
|
||||
merchantability, fitness for a particular purpose and non-infringement.
|
||||
|
||||
- name: Mono.Cecil
|
||||
license: |
|
||||
Copyright (c) 2008 - 2015 Jb Evain
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
console-line-edit-placeholder = Command Here
|
||||
7
Resources/Locale/en-US/custom-controls.ftl
Normal file
7
Resources/Locale/en-US/custom-controls.ftl
Normal file
@@ -0,0 +1,7 @@
|
||||
## EntitySpawnWindow
|
||||
|
||||
entity-spawn-window-title = Entity Spawn Panel
|
||||
entity-spawn-window-search-bar-placeholder = search
|
||||
entity-spawn-window-clear-button = Clear
|
||||
entity-spawn-window-erase-button-text = Erase Mode
|
||||
entity-spawn-window-override-menu-tooltip = Override placement
|
||||
1
Resources/Locale/en-US/tab-container.ftl
Normal file
1
Resources/Locale/en-US/tab-container.ftl
Normal file
@@ -0,0 +1 @@
|
||||
tab-container-not-tab-title-provided = No title
|
||||
11
Resources/Locale/en-US/view-variables.ftl
Normal file
11
Resources/Locale/en-US/view-variables.ftl
Normal file
@@ -0,0 +1,11 @@
|
||||
## ViewVariablesInstanceEntity
|
||||
|
||||
view-variable-instance-entity-server-components-add-component-button-placeholder = Add Component
|
||||
view-variable-instance-entity-client-variables-tab-title = Client Variables
|
||||
view-variable-instance-entity-client-components-tab-title = Client Components
|
||||
view-variable-instance-entity-server-variables-tab-title = Server Variables
|
||||
view-variable-instance-entity-server-components-tab-title = Server Components
|
||||
view-variable-instance-entity-client-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-server-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-add-window-server-components = Add Component [S]
|
||||
view-variable-instance-entity-add-window-client-components = Add Component [C]
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@@ -147,9 +148,10 @@ namespace {nameSpace}
|
||||
foreach (var typeSymbol in symbols)
|
||||
{
|
||||
var xamlFileName = $"{typeSymbol.Name}.xaml";
|
||||
var relevantXamlFile = context.AdditionalFiles.FirstOrDefault(t => t.Path.EndsWith(xamlFileName));
|
||||
var xamlFileNameSep = $"{Path.DirectorySeparatorChar}{xamlFileName}";
|
||||
var relevantXamlFiles = context.AdditionalFiles.Where(t => t.Path.EndsWith(xamlFileNameSep)).ToArray();
|
||||
|
||||
if (relevantXamlFile == null)
|
||||
if (relevantXamlFiles.Length == 0)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
@@ -165,13 +167,28 @@ namespace {nameSpace}
|
||||
continue;
|
||||
}
|
||||
|
||||
var txt = relevantXamlFile.GetText()?.ToString();
|
||||
if (txt == null)
|
||||
if (relevantXamlFiles.Length > 1)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0002",
|
||||
$"Found multiple candidate XAML files for {typeSymbol}",
|
||||
$"Multiple files exist with name {xamlFileName}",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
typeSymbol.Locations[0]));
|
||||
continue;
|
||||
}
|
||||
|
||||
var txt = relevantXamlFiles[0].GetText()?.ToString();
|
||||
if (txt == null)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0004",
|
||||
$"Unexpected empty Xaml-File was found at {xamlFileName}",
|
||||
"Expected Content due to a Class with the same name being annotated with [GenerateTypedNameReferences].",
|
||||
"Usage",
|
||||
@@ -192,7 +209,7 @@ namespace {nameSpace}
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"AXN0003",
|
||||
"RXN0003",
|
||||
"Unhandled exception occured while generating typed Name references.",
|
||||
$"Unhandled exception occured while generating typed Name references: {e}",
|
||||
"Usage",
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace Robust.Client.Audio.Midi
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
renderer.LoadSoundfont(FallbackSoundfont);
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
foreach (var filepath in LinuxSoundfonts)
|
||||
{
|
||||
@@ -243,12 +243,12 @@ namespace Robust.Client.Audio.Midi
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
|
||||
renderer.LoadSoundfont(OsxSoundfont, true);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
|
||||
renderer.LoadSoundfont(WindowsSoundfont, true);
|
||||
|
||||
@@ -628,10 +628,15 @@ namespace Robust.Client.Audio.Midi
|
||||
void IMidiRenderer.InternalDispose()
|
||||
{
|
||||
Source?.Dispose();
|
||||
_driver?.Dispose();
|
||||
|
||||
// Do NOT dispose of the sequencer after the synth or it'll cause a segfault for some fucking reason.
|
||||
_sequencer?.UnregisterClient(_debugRegister);
|
||||
_sequencer?.UnregisterClient(_synthRegister);
|
||||
_sequencer?.Dispose();
|
||||
|
||||
_synth?.Dispose();
|
||||
_player?.Dispose();
|
||||
_driver?.Dispose();
|
||||
_sequencer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
Robust.Client/Console/Commands/LightBBCommand.cs
Normal file
19
Robust.Client/Console/Commands/LightBBCommand.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
#if DEBUG
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
internal sealed class LightDebugCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "lightbb";
|
||||
public string Description => "Toggles whether to show light bounding boxes";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,6 +1,5 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -41,7 +40,7 @@ namespace Robust.Client.Console.Commands
|
||||
var mgr = IoCManager.Resolve<IScriptClient>();
|
||||
if (!mgr.CanScript)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("You do not have server side scripting permission."));
|
||||
shell.WriteError("You do not have server side scripting permission.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#if CLIENT_SCRIPTING
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -14,7 +13,6 @@ using Microsoft.CodeAnalysis.Text;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
@@ -43,14 +41,14 @@ namespace Robust.Client.Console
|
||||
|
||||
public ScriptConsoleClient()
|
||||
{
|
||||
Title = Loc.GetString("Robust C# Interactive (CLIENT)");
|
||||
Title = "Robust C# Interactive (CLIENT)";
|
||||
ScriptInstanceShared.InitDummy();
|
||||
|
||||
_globals = new ScriptGlobalsImpl(this);
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
OutputPanel.AddText(Loc.GetString(@"Robust C# interactive console (CLIENT)."));
|
||||
OutputPanel.AddText("Robust C# interactive console (CLIENT).");
|
||||
OutputPanel.AddText(">");
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -31,7 +29,7 @@ namespace Robust.Client.Console
|
||||
|
||||
ScriptInstanceShared.InitDummy();
|
||||
|
||||
Title = Loc.GetString("Watch Window");
|
||||
Title = "Watch Window";
|
||||
|
||||
var mainVBox = new VBoxContainer
|
||||
{
|
||||
@@ -49,11 +47,11 @@ namespace Robust.Client.Console
|
||||
(_addWatchEdit = new HistoryLineEdit
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
PlaceHolder = Loc.GetString("Add watch (C# interactive)")
|
||||
PlaceHolder = "Add watch (C# interactive)"
|
||||
}),
|
||||
(_addWatchButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Add")
|
||||
Text = "Add"
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -118,7 +116,7 @@ namespace Robust.Client.Console
|
||||
}),
|
||||
(delButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Remove")
|
||||
Text = "Remove"
|
||||
}),
|
||||
}
|
||||
});
|
||||
@@ -178,7 +176,7 @@ namespace Robust.Client.Console
|
||||
ClipText = true,
|
||||
HorizontalExpand = true
|
||||
},
|
||||
(delButton = new Button {Text = Loc.GetString("Remove")})
|
||||
(delButton = new Button {Text = "Remove"})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
@@ -130,17 +130,17 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
if (body != _hoverBodies[0])
|
||||
{
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), "------");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
|
||||
row++;
|
||||
}
|
||||
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
|
||||
row++;
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
row++;
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
row++;
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
row++;
|
||||
}
|
||||
|
||||
@@ -199,17 +199,6 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
|
||||
{
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
|
||||
foreach (var rune in str.EnumerateRunes())
|
||||
{
|
||||
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private class PhysDrawingAdapter : DebugDrawingHandle
|
||||
{
|
||||
private readonly DrawingHandleWorld _handle;
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Robust.Client
|
||||
_commandLineArgs = args;
|
||||
}
|
||||
|
||||
private bool StartupContinue(DisplayMode displayMode)
|
||||
internal bool StartupContinue(DisplayMode displayMode)
|
||||
{
|
||||
_clyde.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
|
||||
@@ -196,7 +196,7 @@ namespace Robust.Client
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
|
||||
internal bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
|
||||
{
|
||||
ReadInitialLaunchState();
|
||||
|
||||
@@ -222,6 +222,8 @@ namespace Robust.Client
|
||||
}
|
||||
}
|
||||
|
||||
ProgramShared.PrintRuntimeInfo(_logManager.RootSawmill);
|
||||
|
||||
// Figure out user data directory.
|
||||
var userDataDir = GetUserDataDir();
|
||||
|
||||
@@ -469,7 +471,7 @@ namespace Robust.Client
|
||||
Clyde,
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
internal void Cleanup()
|
||||
{
|
||||
_networkManager.Shutdown("Client shutting down");
|
||||
_midiManager.Shutdown();
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Prometheus;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
@@ -16,6 +17,7 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IClientNetManager _networkManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
@@ -143,7 +145,11 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
|
||||
case EntityMessageType.SystemMessage:
|
||||
ReceivedSystemMessage?.Invoke(this, message.SystemMessage);
|
||||
var msg = message.SystemMessage;
|
||||
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalPlayer!.Session), msg)!;
|
||||
ReceivedSystemMessage?.Invoke(this, msg);
|
||||
ReceivedSystemMessage?.Invoke(this, sessionMsg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Robust.Client.GameObjects
|
||||
_appearanceDirty = false;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Robust.Client.GameObjects
|
||||
public MapCoordinates? Position => _eye?.Position;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace Robust.Client.GameObjects
|
||||
VisibilityMask = state.VisibilityMask;
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
SnapGridOnPositionChanged();
|
||||
AnchorStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void SnapGridOnPositionChanged()
|
||||
public void AnchorStateChanged()
|
||||
{
|
||||
SendDirty();
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -44,11 +46,29 @@ namespace Robust.Client.GameObjects
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set => _enabled = value;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ContainerOccluded { get; set; }
|
||||
public bool ContainerOccluded
|
||||
{
|
||||
get => _containerOccluded;
|
||||
set
|
||||
{
|
||||
if (_containerOccluded == value) return;
|
||||
|
||||
_containerOccluded = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private bool _containerOccluded;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the light mask should automatically rotate with the entity. (like a flashlight)
|
||||
@@ -117,30 +137,13 @@ namespace Robust.Client.GameObjects
|
||||
public bool VisibleNested
|
||||
{
|
||||
get => _visibleNested;
|
||||
set
|
||||
{
|
||||
if (_visibleNested == value) return;
|
||||
_visibleNested = value;
|
||||
if (value)
|
||||
{
|
||||
if (Owner.Transform.Parent == null) return;
|
||||
|
||||
_lightOnParent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_lightOnParent) return;
|
||||
|
||||
_lightOnParent = false;
|
||||
}
|
||||
}
|
||||
set => _visibleNested = value;
|
||||
}
|
||||
|
||||
[DataField("radius")]
|
||||
private float _radius = 5f;
|
||||
[DataField("nestedvisible")]
|
||||
private bool _visibleNested = true;
|
||||
private bool _lightOnParent;
|
||||
[DataField("color")]
|
||||
private Color _color = Color.White;
|
||||
[DataField("offset")]
|
||||
@@ -167,6 +170,8 @@ namespace Robust.Client.GameObjects
|
||||
get => _radius;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseTo(value, _radius)) return;
|
||||
|
||||
_radius = MathF.Max(value, 0.01f); // setting radius to 0 causes exceptions, so just use a value close enough to zero that it's unnoticeable.
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
|
||||
}
|
||||
@@ -180,17 +185,8 @@ namespace Robust.Client.GameObjects
|
||||
Mask = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What MapId we are intersecting for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
|
||||
|
||||
/// <summary>
|
||||
/// What grids we're on for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal List<GridId> IntersectingGrids = new();
|
||||
internal RenderingTreeComponent? RenderTree { get; set; }
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
@@ -200,42 +196,13 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdateMask();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
if (message is ParentChangedMessage msg)
|
||||
{
|
||||
HandleTransformParentChanged(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTransformParentChanged(ParentChangedMessage obj)
|
||||
{
|
||||
// TODO: this does not work for things nested multiply layers deep.
|
||||
if (!VisibleNested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.NewParent != null && obj.NewParent.IsValid())
|
||||
{
|
||||
_lightOnParent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lightOnParent = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
@@ -270,4 +237,9 @@ namespace Robust.Client.GameObjects
|
||||
PointLightComponent = pointLightComponent;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PointLightUpdateEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,13 @@ namespace Robust.Client.GameObjects
|
||||
public override bool Visible
|
||||
{
|
||||
get => _visible;
|
||||
set => _visible = value;
|
||||
set
|
||||
{
|
||||
if (_visible == value) return;
|
||||
_visible = value;
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
|
||||
@@ -125,17 +131,8 @@ namespace Robust.Client.GameObjects
|
||||
[DataField("directional")]
|
||||
private bool _directional = true;
|
||||
|
||||
/// <summary>
|
||||
/// What MapId we are intersecting for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
|
||||
|
||||
/// <summary>
|
||||
/// What grids we're on for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal List<GridId> IntersectingGrids { get; } = new();
|
||||
internal RenderingTreeComponent? RenderTree { get; set; } = null;
|
||||
|
||||
[DataField("layerDatums")]
|
||||
private List<PrototypeLayerData> LayerDatums
|
||||
@@ -152,7 +149,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
set
|
||||
{
|
||||
if(value == null) return;
|
||||
if (value == null) return;
|
||||
|
||||
Layers.Clear();
|
||||
foreach (var layerDatum in value)
|
||||
@@ -162,11 +159,12 @@ namespace Robust.Client.GameObjects
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
|
||||
{
|
||||
var path = TextureRoot / layerDatum.RsiPath;
|
||||
try
|
||||
|
||||
if (IoCManager.Resolve<IResourceCache>().TryGetResource(path, out RSIResource? resource))
|
||||
{
|
||||
layer.RSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(path).RSI;
|
||||
layer.RSI = resource.RSI;
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
|
||||
}
|
||||
@@ -265,7 +263,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
UpdateIsInert();
|
||||
QueueUpdateIsInert();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,17 +306,30 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
[DataField("sprite", readOnly: true)] private string? rsi;
|
||||
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new ();
|
||||
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new();
|
||||
|
||||
[DataField("state", readOnly: true)] private string? state;
|
||||
[DataField("texture", readOnly: true)] private string? texture;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ContainerOccluded { get; set; }
|
||||
public bool ContainerOccluded
|
||||
{
|
||||
get => _containerOccluded;
|
||||
set
|
||||
{
|
||||
if (_containerOccluded == value) return;
|
||||
_containerOccluded = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private bool _containerOccluded;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool TreeUpdateQueued { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _inertUpdateQueued;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ShaderInstance? PostShader { get; set; }
|
||||
|
||||
@@ -348,13 +359,13 @@ namespace Robust.Client.GameObjects
|
||||
if (!string.IsNullOrWhiteSpace(rsi))
|
||||
{
|
||||
var rsiPath = TextureRoot / rsi;
|
||||
try
|
||||
if(IoCManager.Resolve<IResourceCache>().TryGetResource(rsiPath, out RSIResource? resource))
|
||||
{
|
||||
BaseRSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(rsiPath).RSI;
|
||||
BaseRSI = resource.RSI;
|
||||
}
|
||||
catch (Exception e)
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(SpriteComponent.LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,7 +493,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public int AddBlankLayer(int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) {Visible = false};
|
||||
var layer = new Layer(this) { Visible = false };
|
||||
return AddLayer(layer, newIndex);
|
||||
}
|
||||
|
||||
@@ -511,13 +522,13 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public int AddLayer(Texture? texture, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) {Texture = texture};
|
||||
var layer = new Layer(this) { Texture = texture };
|
||||
return AddLayer(layer, newIndex);
|
||||
}
|
||||
|
||||
public int AddLayer(RSI.StateId stateId, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) {State = stateId};
|
||||
var layer = new Layer(this) { State = stateId };
|
||||
if (BaseRSI != null && BaseRSI.TryGetState(stateId, out var state))
|
||||
{
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
@@ -563,7 +574,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public int AddLayer(RSI.StateId stateId, RSI? rsi, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) {State = stateId, RSI = rsi};
|
||||
var layer = new Layer(this) { State = stateId, RSI = rsi };
|
||||
if (rsi != null && rsi.TryGetState(stateId, out var state))
|
||||
{
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
@@ -619,7 +630,7 @@ namespace Robust.Client.GameObjects
|
||||
index = Layers.Count - 1;
|
||||
}
|
||||
|
||||
UpdateIsInert();
|
||||
QueueUpdateIsInert();
|
||||
return index;
|
||||
}
|
||||
|
||||
@@ -646,7 +657,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
UpdateIsInert();
|
||||
QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void RemoveLayer(object layerKey)
|
||||
@@ -1288,8 +1299,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var layerColor = color * layer.Color;
|
||||
|
||||
var position = -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
|
||||
var textureSize = texture.Size / (float) EyeManager.PixelsPerMeter;
|
||||
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(position, textureSize);
|
||||
|
||||
// TODO: Implement layer-specific rotation and scale.
|
||||
@@ -1309,7 +1320,7 @@ namespace Robust.Client.GameObjects
|
||||
public static Angle CalcRectWorldAngle(Angle worldAngle, int numDirections)
|
||||
{
|
||||
var theta = worldAngle.Theta;
|
||||
var segSize = (MathF.PI*2) / (numDirections * 2);
|
||||
var segSize = (MathF.PI * 2) / (numDirections * 2);
|
||||
var segments = (int)(theta / segSize);
|
||||
var odd = segments % 2;
|
||||
var result = theta - (segments * segSize) - (odd * segSize);
|
||||
@@ -1409,7 +1420,7 @@ namespace Robust.Client.GameObjects
|
||||
if (curState == null)
|
||||
return;
|
||||
|
||||
var thestate = (SpriteComponentState) curState;
|
||||
var thestate = (SpriteComponentState)curState;
|
||||
|
||||
Visible = thestate.Visible;
|
||||
DrawDepth = thestate.DrawDepth;
|
||||
@@ -1497,8 +1508,20 @@ namespace Robust.Client.GameObjects
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateIsInert()
|
||||
private void QueueUpdateIsInert()
|
||||
{
|
||||
if (_inertUpdateQueued)
|
||||
return;
|
||||
|
||||
_inertUpdateQueued = true;
|
||||
// Yes that null check is valid because of that stupid fucking dummy IEntity.
|
||||
// Who thought that was a good idea.
|
||||
Owner?.EntityManager?.EventBus?.RaiseEvent(EventSource.Local, new SpriteUpdateInertEvent {Sprite = this});
|
||||
}
|
||||
|
||||
internal void DoUpdateIsInert()
|
||||
{
|
||||
_inertUpdateQueued = false;
|
||||
IsInert = true;
|
||||
|
||||
foreach (var layer in Layers)
|
||||
@@ -1848,14 +1871,14 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
AutoAnimated = value;
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetVisible(bool value)
|
||||
{
|
||||
Visible = value;
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetRsi(RSI? rsi)
|
||||
@@ -1887,7 +1910,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetState(RSI.StateId stateId)
|
||||
@@ -1919,7 +1942,7 @@ namespace Robust.Client.GameObjects
|
||||
AnimationTime = 0;
|
||||
AnimationTimeLeft = state.GetDelay(0);
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetTexture(Texture? texture)
|
||||
@@ -1927,7 +1950,7 @@ namespace Robust.Client.GameObjects
|
||||
State = default;
|
||||
Texture = texture;
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetOffset(Vector2 offset)
|
||||
@@ -1979,13 +2002,13 @@ namespace Robust.Client.GameObjects
|
||||
switch (layerProp)
|
||||
{
|
||||
case "texture":
|
||||
LayerSetTexture(index, (string) value);
|
||||
LayerSetTexture(index, (string)value);
|
||||
return;
|
||||
case "state":
|
||||
LayerSetState(index, (string) value);
|
||||
LayerSetState(index, (string)value);
|
||||
return;
|
||||
case "color":
|
||||
LayerSetColor(index, (Color) value);
|
||||
LayerSetColor(index, (Color)value);
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown layer property '{layerProp}'");
|
||||
@@ -2030,7 +2053,7 @@ namespace Robust.Client.GameObjects
|
||||
yield break;
|
||||
}
|
||||
|
||||
var dummy = new DummyIconEntity {Prototype = prototype};
|
||||
var dummy = new DummyIconEntity { Prototype = prototype };
|
||||
var spriteComponent = dummy.AddComponent<SpriteComponent>();
|
||||
|
||||
if (prototype.Components.TryGetValue("Appearance", out _))
|
||||
@@ -2074,7 +2097,7 @@ namespace Robust.Client.GameObjects
|
||||
return GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
var dummy = new DummyIconEntity {Prototype = prototype};
|
||||
var dummy = new DummyIconEntity { Prototype = prototype };
|
||||
var spriteComponent = dummy.AddComponent<SpriteComponent>();
|
||||
dummy.Delete();
|
||||
|
||||
@@ -2111,7 +2134,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
|
||||
var serializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
var comp = (T) typeFactory.CreateInstanceUnchecked(typeof(T));
|
||||
var comp = (T)typeFactory.CreateInstanceUnchecked(typeof(T));
|
||||
_components[typeof(T)] = comp;
|
||||
comp.Owner = this;
|
||||
|
||||
@@ -2145,7 +2168,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public T GetComponent<T>()
|
||||
{
|
||||
return (T) _components[typeof(T)];
|
||||
return (T)_components[typeof(T)];
|
||||
}
|
||||
|
||||
public IComponent GetComponent(Type type)
|
||||
@@ -2157,7 +2180,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
component = null;
|
||||
if (!_components.TryGetValue(typeof(T), out var value)) return false;
|
||||
component = (T) value;
|
||||
component = (T)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2213,4 +2236,14 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal sealed class SpriteUpdateEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal struct SpriteUpdateInertEvent
|
||||
{
|
||||
public SpriteComponent Sprite;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class RenderingTreeComponent : Component
|
||||
{
|
||||
public override string Name => "RenderingTree";
|
||||
|
||||
internal DynamicTree<SpriteComponent> SpriteTree { get; private set; } = new(SpriteAabbFunc);
|
||||
internal DynamicTree<PointLightComponent> LightTree { get; private set; } = new(LightAabbFunc);
|
||||
|
||||
private static Box2 SpriteAabbFunc(in SpriteComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
return new Box2(worldPos, worldPos);
|
||||
}
|
||||
|
||||
private static Box2 LightAabbFunc(in PointLightComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
|
||||
var boxSize = value.Radius * 2;
|
||||
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
|
||||
}
|
||||
|
||||
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
|
||||
{
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
|
||||
return new Box2(worldPos.Value, worldPos.Value);
|
||||
}
|
||||
|
||||
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
|
||||
{
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
var boxSize = value.Radius * 2;
|
||||
return Box2.CenteredAround(worldPos.Value, (boxSize, boxSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
SubscribeLocalEvent<OccluderDirtyEvent>(HandleDirtyEvent);
|
||||
|
||||
SubscribeLocalEvent<ClientOccluderComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
|
||||
SubscribeLocalEvent<ClientOccluderComponent, AnchorStateChangedEvent>(HandleAnchorChanged);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
@@ -62,9 +62,9 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleSnapGridMove(EntityUid uid, ClientOccluderComponent component, SnapGridPositionChangedEvent args)
|
||||
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, AnchorStateChangedEvent args)
|
||||
{
|
||||
component.SnapGridOnPositionChanged();
|
||||
component.AnchorStateChanged();
|
||||
}
|
||||
|
||||
private void HandleDirtyEvent(OccluderDirtyEvent ev)
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#if DEBUG
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal sealed class DebugLightTreeSystem : EntitySystem
|
||||
{
|
||||
private DebugLightOverlay? _lightOverlay;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
_lightOverlay = new DebugLightOverlay(
|
||||
IoCManager.Resolve<IEntityLookup>(),
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IMapManager>(),
|
||||
Get<RenderingTreeSystem>());
|
||||
|
||||
overlayManager.AddOverlay(_lightOverlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
overlayManager.RemoveOverlay(_lightOverlay!);
|
||||
_lightOverlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
|
||||
private sealed class DebugLightOverlay : Overlay
|
||||
{
|
||||
private IEntityLookup _lookup;
|
||||
private IEyeManager _eyeManager;
|
||||
private IMapManager _mapManager;
|
||||
|
||||
private RenderingTreeSystem _tree;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_eyeManager = eyeManager;
|
||||
_mapManager = mapManager;
|
||||
_tree = tree;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var map = _eyeManager.CurrentMap;
|
||||
if (map == MapId.Nullspace) return;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var tree in _tree.GetLightTrees(map, viewport))
|
||||
{
|
||||
foreach (var light in tree)
|
||||
{
|
||||
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
|
||||
if (!aabb.Intersects(viewport)) continue;
|
||||
|
||||
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -173,4 +173,24 @@ namespace Robust.Client.GameObjects
|
||||
AttachedEntity = attachedEntity;
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayerAttachedEvent : EntityEventArgs
|
||||
{
|
||||
public PlayerAttachedEvent(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
public IEntity Entity { get; }
|
||||
}
|
||||
|
||||
public class PlayerDetachedEvent : EntityEventArgs
|
||||
{
|
||||
public PlayerDetachedEvent(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
public IEntity Entity { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@ using System.Drawing;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Physics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -18,23 +21,58 @@ namespace Robust.Client.GameObjects
|
||||
[UsedImplicitly]
|
||||
public sealed class RenderingTreeSystem : EntitySystem
|
||||
{
|
||||
internal const string LoggerSawmill = "rendertree";
|
||||
|
||||
// Nullspace is not indexed. Keep that in mind.
|
||||
|
||||
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
|
||||
private readonly Dictionary<MapId, Dictionary<GridId, MapTrees>> _gridTrees = new();
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private readonly List<SpriteComponent> _spriteQueue = new();
|
||||
private readonly List<PointLightComponent> _lightQueue = new();
|
||||
|
||||
internal DynamicTree<SpriteComponent> GetSpriteTreeForMap(MapId map, GridId grid)
|
||||
private HashSet<EntityUid> _checkedChildren = new();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CVars.MaxLightRadius"/>
|
||||
/// </summary>
|
||||
public float MaxLightRadius { get; private set; }
|
||||
|
||||
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
return _gridTrees[map][grid].SpriteTree;
|
||||
if (mapId == MapId.Nullspace) yield break;
|
||||
|
||||
var enclosed = false;
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
|
||||
|
||||
// if we're enclosed then we know no other grids relevant + don't need the map's rendertree
|
||||
if (grid.WorldBounds.Encloses(in worldAABB))
|
||||
{
|
||||
enclosed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!enclosed)
|
||||
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
internal DynamicTree<PointLightComponent> GetLightTreeForMap(MapId map, GridId grid)
|
||||
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
return _gridTrees[map][grid].LightTree;
|
||||
foreach (var comp in GetRenderTrees(mapId, worldAABB))
|
||||
{
|
||||
yield return comp.SpriteTree;
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<DynamicTree<PointLightComponent>> GetLightTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
foreach (var comp in GetRenderTrees(mapId, worldAABB))
|
||||
{
|
||||
yield return comp.LightTree;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -46,23 +84,38 @@ namespace Robust.Client.GameObjects
|
||||
UpdatesAfter.Add(typeof(PhysicsSystem));
|
||||
|
||||
_mapManager.MapCreated += MapManagerOnMapCreated;
|
||||
_mapManager.MapDestroyed += MapManagerOnMapDestroyed;
|
||||
_mapManager.OnGridCreated += MapManagerOnGridCreated;
|
||||
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
|
||||
|
||||
// Due to how recursion works, this must be done.
|
||||
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
|
||||
|
||||
SubscribeLocalEvent<SpriteComponent, EntMapIdChangedMessage>(SpriteMapChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, MoveEvent>(SpriteMoved);
|
||||
SubscribeLocalEvent<SpriteComponent, EntParentChangedMessage>(SpriteParentChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, ComponentRemove>(RemoveSprite);
|
||||
SubscribeLocalEvent<SpriteComponent, SpriteUpdateEvent>(HandleSpriteUpdate);
|
||||
|
||||
SubscribeLocalEvent<PointLightComponent, EntMapIdChangedMessage>(LightMapChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, MoveEvent>(LightMoved);
|
||||
SubscribeLocalEvent<PointLightComponent, EntParentChangedMessage>(LightParentChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, RenderTreeRemoveLightEvent>(RemoveLight);
|
||||
SubscribeLocalEvent<PointLightComponent, PointLightUpdateEvent>(HandleLightUpdate);
|
||||
|
||||
SubscribeLocalEvent<RenderingTreeComponent, ComponentRemove>(HandleTreeRemove);
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.MaxLightRadius, value => MaxLightRadius = value, true);
|
||||
}
|
||||
|
||||
private void HandleLightUpdate(EntityUid uid, PointLightComponent component, PointLightUpdateEvent args)
|
||||
{
|
||||
if (component.TreeUpdateQueued) return;
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void HandleSpriteUpdate(EntityUid uid, SpriteComponent component, SpriteUpdateEvent args)
|
||||
{
|
||||
if (component.TreeUpdateQueued) return;
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void AnythingMoved(MoveEvent args)
|
||||
@@ -72,13 +125,20 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void AnythingMovedSubHandler(ITransformComponent sender)
|
||||
{
|
||||
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
|
||||
if (!_checkedChildren.Add(sender.Owner.Uid) ||
|
||||
sender.Owner.HasComponent<RenderingTreeComponent>()) return;
|
||||
|
||||
// This recursive search is needed, as MoveEvent is defined to not care about indirect events like children.
|
||||
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
|
||||
// (Struct-based events ok though)
|
||||
// Ironically this was lagging the GC lolz
|
||||
if (sender.Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
QueueSpriteUpdate(sprite);
|
||||
|
||||
if (sender.Owner.TryGetComponent(out PointLightComponent? light))
|
||||
QueueLightUpdate(light);
|
||||
|
||||
foreach (ITransformComponent child in sender.Children)
|
||||
{
|
||||
AnythingMovedSubHandler(child);
|
||||
@@ -96,11 +156,6 @@ namespace Robust.Client.GameObjects
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void SpriteMoved(EntityUid uid, SpriteComponent component, MoveEvent args)
|
||||
{
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, EntParentChangedMessage args)
|
||||
{
|
||||
QueueSpriteUpdate(component);
|
||||
@@ -113,16 +168,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void ClearSprite(SpriteComponent component)
|
||||
{
|
||||
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
|
||||
{
|
||||
foreach (var gridId in component.IntersectingGrids)
|
||||
{
|
||||
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
|
||||
tree.SpriteTree.Remove(component);
|
||||
}
|
||||
}
|
||||
if (component.RenderTree == null) return;
|
||||
|
||||
component.IntersectingGrids.Clear();
|
||||
component.RenderTree.SpriteTree.Remove(component);
|
||||
component.RenderTree = null;
|
||||
}
|
||||
|
||||
private void QueueSpriteUpdate(SpriteComponent component)
|
||||
@@ -131,22 +180,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
component.TreeUpdateQueued = true;
|
||||
_spriteQueue.Add(component);
|
||||
|
||||
foreach (var child in component.Owner.Transform.Children)
|
||||
{
|
||||
QueueSpriteUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueSpriteUpdate(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out SpriteComponent? spriteComponent)) return;
|
||||
QueueSpriteUpdate(spriteComponent);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
QueueSpriteUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -156,11 +189,6 @@ namespace Robust.Client.GameObjects
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void LightMoved(EntityUid uid, PointLightComponent component, MoveEvent args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void LightParentChanged(EntityUid uid, PointLightComponent component, EntParentChangedMessage args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
@@ -178,16 +206,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void ClearLight(PointLightComponent component)
|
||||
{
|
||||
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
|
||||
{
|
||||
foreach (var gridId in component.IntersectingGrids)
|
||||
{
|
||||
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
|
||||
tree.LightTree.Remove(component);
|
||||
}
|
||||
}
|
||||
if (component.RenderTree == null) return;
|
||||
|
||||
component.IntersectingGrids.Clear();
|
||||
component.RenderTree.LightTree.Remove(component);
|
||||
component.RenderTree = null;
|
||||
}
|
||||
|
||||
private void QueueLightUpdate(PointLightComponent component)
|
||||
@@ -196,22 +218,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
component.TreeUpdateQueued = true;
|
||||
_lightQueue.Add(component);
|
||||
|
||||
foreach (var child in component.Owner.Transform.Children)
|
||||
{
|
||||
QueueLightUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueLightUpdate(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out PointLightComponent? lightComponent)) return;
|
||||
QueueLightUpdate(lightComponent);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
QueueLightUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -219,31 +225,23 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
base.Shutdown();
|
||||
_mapManager.MapCreated -= MapManagerOnMapCreated;
|
||||
_mapManager.MapDestroyed -= MapManagerOnMapDestroyed;
|
||||
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
|
||||
_mapManager.OnGridRemoved -= MapManagerOnGridRemoved;
|
||||
}
|
||||
|
||||
private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e)
|
||||
private void HandleTreeRemove(EntityUid uid, RenderingTreeComponent component, ComponentRemove args)
|
||||
{
|
||||
foreach (var (_, gridTree) in _gridTrees[e.Map])
|
||||
foreach (var sprite in component.SpriteTree)
|
||||
{
|
||||
foreach (var comp in gridTree.LightTree)
|
||||
{
|
||||
comp.IntersectingGrids.Clear();
|
||||
}
|
||||
|
||||
foreach (var comp in gridTree.SpriteTree)
|
||||
{
|
||||
comp.IntersectingGrids.Clear();
|
||||
}
|
||||
|
||||
// Just in case?
|
||||
gridTree.LightTree.Clear();
|
||||
gridTree.SpriteTree.Clear();
|
||||
sprite.RenderTree = null;
|
||||
}
|
||||
|
||||
_gridTrees.Remove(e.Map);
|
||||
foreach (var light in component.LightTree)
|
||||
{
|
||||
light.RenderTree = null;
|
||||
}
|
||||
|
||||
component.SpriteTree.Clear();
|
||||
component.LightTree.Clear();
|
||||
}
|
||||
|
||||
private void MapManagerOnMapCreated(object? sender, MapEventArgs e)
|
||||
@@ -253,154 +251,122 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
_gridTrees.Add(e.Map, new Dictionary<GridId, MapTrees>
|
||||
{
|
||||
{GridId.Invalid, new MapTrees()}
|
||||
});
|
||||
_mapManager.GetMapEntity(e.Map).EnsureComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
private void MapManagerOnGridCreated(MapId mapId, GridId gridId)
|
||||
{
|
||||
_gridTrees[mapId].Add(gridId, new MapTrees());
|
||||
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
private void MapManagerOnGridRemoved(MapId mapId, GridId gridId)
|
||||
private RenderingTreeComponent? GetRenderTree(IEntity entity)
|
||||
{
|
||||
var gridTree = _gridTrees[mapId][gridId];
|
||||
|
||||
foreach (var sprite in gridTree.SpriteTree)
|
||||
if (entity.Transform.MapID == MapId.Nullspace ||
|
||||
entity.HasComponent<RenderingTreeComponent>()) return null;
|
||||
|
||||
var parent = entity.Transform.Parent?.Owner;
|
||||
|
||||
while (true)
|
||||
{
|
||||
sprite.IntersectingGrids.Remove(gridId);
|
||||
if (parent == null) break;
|
||||
|
||||
if (parent.TryGetComponent(out RenderingTreeComponent? comp)) return comp;
|
||||
parent = parent.Transform.Parent?.Owner;
|
||||
}
|
||||
|
||||
foreach (var light in gridTree.LightTree)
|
||||
{
|
||||
light.IntersectingGrids.Remove(gridId);
|
||||
}
|
||||
|
||||
// Clear in case
|
||||
gridTree.LightTree.Clear();
|
||||
gridTree.SpriteTree.Clear();
|
||||
_gridTrees[mapId].Remove(gridId);
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
_checkedChildren.Clear();
|
||||
|
||||
foreach (var sprite in _spriteQueue)
|
||||
{
|
||||
var mapId = sprite.Owner.Transform.MapID;
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (sprite.IntersectingMapId != mapId)
|
||||
sprite.TreeUpdateQueued = false;
|
||||
if (!sprite.Visible || sprite.ContainerOccluded)
|
||||
{
|
||||
ClearSprite(sprite);
|
||||
continue;
|
||||
}
|
||||
|
||||
sprite.IntersectingMapId = mapId;
|
||||
var oldMapTree = sprite.RenderTree;
|
||||
var newMapTree = GetRenderTree(sprite.Owner);
|
||||
// TODO: Temp PVS guard
|
||||
var worldPos = sprite.Owner.Transform.WorldPosition;
|
||||
|
||||
if (mapId == MapId.Nullspace) continue;
|
||||
|
||||
var mapTree = _gridTrees[mapId];
|
||||
var aabb = MapTrees.SpriteAabbFunc(sprite);
|
||||
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
|
||||
|
||||
// Remove from old
|
||||
foreach (var gridId in sprite.IntersectingGrids)
|
||||
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
|
||||
{
|
||||
if (intersectingGrids.Contains(gridId)) continue;
|
||||
mapTree[gridId].SpriteTree.Remove(sprite);
|
||||
ClearSprite(sprite);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rebuild in the update below
|
||||
sprite.IntersectingGrids.Clear();
|
||||
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos).Translated(-treePos);
|
||||
|
||||
// Update / add to new
|
||||
foreach (var gridId in intersectingGrids)
|
||||
// If we're on a new map then clear the old one.
|
||||
if (oldMapTree != newMapTree)
|
||||
{
|
||||
var translated = aabb.Translated(gridId == GridId.Invalid
|
||||
? Vector2.Zero
|
||||
: -_mapManager.GetGrid(gridId).WorldPosition);
|
||||
|
||||
mapTree[gridId].SpriteTree.AddOrUpdate(sprite, translated);
|
||||
|
||||
sprite.IntersectingGrids.Add(gridId);
|
||||
ClearSprite(sprite);
|
||||
newMapTree?.SpriteTree.Add(sprite, aabb);
|
||||
}
|
||||
else
|
||||
{
|
||||
newMapTree?.SpriteTree.Update(sprite, aabb);
|
||||
}
|
||||
|
||||
sprite.TreeUpdateQueued = false;
|
||||
sprite.RenderTree = newMapTree;
|
||||
}
|
||||
|
||||
foreach (var light in _lightQueue)
|
||||
{
|
||||
var mapId = light.Owner.Transform.MapID;
|
||||
light.TreeUpdateQueued = false;
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (light.IntersectingMapId != mapId)
|
||||
if (!light.Enabled || light.ContainerOccluded)
|
||||
{
|
||||
ClearLight(light);
|
||||
continue;
|
||||
}
|
||||
|
||||
light.IntersectingMapId = mapId;
|
||||
var oldMapTree = light.RenderTree;
|
||||
var newMapTree = GetRenderTree(light.Owner);
|
||||
// TODO: Temp PVS guard
|
||||
var worldPos = light.Owner.Transform.WorldPosition;
|
||||
|
||||
if (mapId == MapId.Nullspace) continue;
|
||||
|
||||
var mapTree = _gridTrees[mapId];
|
||||
var aabb = MapTrees.LightAabbFunc(light);
|
||||
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
|
||||
|
||||
// Remove from old
|
||||
foreach (var gridId in intersectingGrids)
|
||||
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
|
||||
{
|
||||
if (intersectingGrids.Contains(gridId)) continue;
|
||||
mapTree[gridId].LightTree.Remove(light);
|
||||
ClearLight(light);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rebuild in the update below
|
||||
light.IntersectingGrids.Clear();
|
||||
|
||||
// Update / add to new
|
||||
foreach (var gridId in intersectingGrids)
|
||||
// TODO: Events need a bit of cleanup so we only validate this on initialize and radius changed events
|
||||
// this is fine for now IMO as it's 1 float check for every light that moves
|
||||
if (light.Radius > MaxLightRadius)
|
||||
{
|
||||
var translated = aabb.Translated(gridId == GridId.Invalid
|
||||
? Vector2.Zero
|
||||
: -_mapManager.GetGrid(gridId).WorldPosition);
|
||||
|
||||
mapTree[gridId].LightTree.AddOrUpdate(light, translated);
|
||||
light.IntersectingGrids.Add(gridId);
|
||||
Logger.WarningS(LoggerSawmill, $"Light radius for {light.Owner} set above max radius of {MaxLightRadius}. This may lead to pop-in.");
|
||||
}
|
||||
|
||||
light.TreeUpdateQueued = false;
|
||||
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos).Translated(-treePos);
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (oldMapTree != newMapTree)
|
||||
{
|
||||
ClearLight(light);
|
||||
newMapTree?.LightTree.Add(light, aabb);
|
||||
}
|
||||
else
|
||||
{
|
||||
newMapTree?.LightTree.Update(light, aabb);
|
||||
}
|
||||
|
||||
light.RenderTree = newMapTree;
|
||||
}
|
||||
|
||||
_spriteQueue.Clear();
|
||||
_lightQueue.Clear();
|
||||
}
|
||||
|
||||
private sealed class MapTrees
|
||||
{
|
||||
public readonly DynamicTree<SpriteComponent> SpriteTree;
|
||||
public readonly DynamicTree<PointLightComponent> LightTree;
|
||||
|
||||
public MapTrees()
|
||||
{
|
||||
SpriteTree = new DynamicTree<SpriteComponent>(SpriteAabbFunc);
|
||||
LightTree = new DynamicTree<PointLightComponent>(LightAabbFunc);
|
||||
}
|
||||
|
||||
internal static Box2 SpriteAabbFunc(in SpriteComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
|
||||
return new Box2(worldPos, worldPos);
|
||||
}
|
||||
|
||||
internal static Box2 LightAabbFunc(in PointLightComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
|
||||
var boxSize = value.Radius * 2;
|
||||
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class RenderTreeRemoveLightEvent : EntityEventArgs
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using JetBrains.Annotations;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -17,16 +17,29 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private RenderingTreeSystem _treeSystem = default!;
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_treeSystem = Get<RenderingTreeSystem>();
|
||||
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
}
|
||||
|
||||
private void QueueUpdateInert(SpriteUpdateInertEvent ev)
|
||||
{
|
||||
_inertUpdateQueue.Enqueue(ev.Sprite);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
while (_inertUpdateQueue.TryDequeue(out var sprite))
|
||||
{
|
||||
sprite.DoUpdateIsInert();
|
||||
}
|
||||
|
||||
// So we could calculate the correct size of the entities based on the contents of their sprite...
|
||||
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
|
||||
var pvsBounds = _eyeManager.GetWorldViewport().Enlarged(5);
|
||||
@@ -37,13 +50,11 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true))
|
||||
foreach (var comp in _treeSystem.GetRenderTrees(currentMap, pvsBounds))
|
||||
{
|
||||
var gridBounds = gridId == GridId.Invalid ? pvsBounds : pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
var bounds = pvsBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
|
||||
var mapTree = _treeSystem.GetSpriteTreeForMap(currentMap, gridId);
|
||||
|
||||
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
|
||||
comp.SpriteTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
|
||||
{
|
||||
if (value.IsInert)
|
||||
{
|
||||
@@ -52,7 +63,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
value.FrameUpdate(state);
|
||||
return true;
|
||||
}, gridBounds, approx: true);
|
||||
}, bounds, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class PlayerAttachedMsg : ComponentMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class PlayerDetachedMsg : ComponentMessage
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -210,6 +210,11 @@ namespace Robust.Client.GameStates
|
||||
ResetPredictedEntities(_timing.CurTick);
|
||||
}
|
||||
|
||||
if (!curState.Extrapolated)
|
||||
{
|
||||
_processor.UpdateFullRep(curState);
|
||||
}
|
||||
|
||||
// Store last tick we got from the GameStateProcessor.
|
||||
_lastProcessedTick = _timing.CurTick;
|
||||
|
||||
@@ -319,6 +324,8 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void ResetPredictedEntities(GameTick curTick)
|
||||
{
|
||||
var bus = (EntityEventBus) _entities.EventBus;
|
||||
|
||||
foreach (var entity in _entities.GetEntities())
|
||||
{
|
||||
// TODO: 99% there's an off-by-one here.
|
||||
@@ -347,6 +354,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
Logger.DebugS(CVars.NetPredict.Name, $" And also its component {comp.Name}");
|
||||
// TODO: Handle interpolation.
|
||||
bus.RaiseComponentEvent(comp, new ComponentHandleState(compState, null));
|
||||
comp.HandleComponentState(compState, null);
|
||||
}
|
||||
}
|
||||
@@ -394,7 +402,7 @@ namespace Robust.Client.GameStates
|
||||
private List<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
|
||||
{
|
||||
_config.TickProcessMessages();
|
||||
_mapManager.ApplyGameStatePre(curState.MapData);
|
||||
_mapManager.ApplyGameStatePre(curState.MapData, curState.EntityStates);
|
||||
var createdEntities = ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
|
||||
nextState?.EntityStates);
|
||||
_players.ApplyPlayerStates(curState.PlayerStates);
|
||||
@@ -419,6 +427,7 @@ namespace Robust.Client.GameStates
|
||||
//Known entities
|
||||
if (_entities.TryGetEntity(es.Uid, out var entity))
|
||||
{
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] MOD {es.Uid}");
|
||||
toApply.Add(entity, (es, null));
|
||||
}
|
||||
else //Unknown entities
|
||||
@@ -429,6 +438,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
|
||||
}
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] CREATE {es.Uid} {metaState.PrototypeId}");
|
||||
var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid);
|
||||
toApply.Add(newEntity, (es, null));
|
||||
toInitialize.Add(newEntity);
|
||||
@@ -455,17 +465,20 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
var bus = (EntityEventBus) _entities.EventBus;
|
||||
|
||||
// Make sure this is done after all entities have been instantiated.
|
||||
foreach (var kvStates in toApply)
|
||||
{
|
||||
var ent = kvStates.Key;
|
||||
var entity = (Entity) ent;
|
||||
HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1,
|
||||
HandleEntityState(entity.EntityManager.ComponentManager, entity, bus, kvStates.Value.Item1,
|
||||
kvStates.Value.Item2);
|
||||
}
|
||||
|
||||
foreach (var id in deletions)
|
||||
{
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] DELETE {id}");
|
||||
_entities.DeleteEntity(id);
|
||||
}
|
||||
|
||||
@@ -527,7 +540,7 @@ namespace Robust.Client.GameStates
|
||||
return created;
|
||||
}
|
||||
|
||||
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState? curState,
|
||||
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityEventBus bus, EntityState? curState,
|
||||
EntityState? nextState)
|
||||
{
|
||||
var compStateWork = new Dictionary<uint, (ComponentState? curState, ComponentState? nextState)>();
|
||||
@@ -585,6 +598,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
try
|
||||
{
|
||||
bus.RaiseComponentEvent(component, new ComponentHandleState(cur, next));
|
||||
component.HandleComponentState(cur, next);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -149,11 +149,6 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
Logger.DebugS("net.state", $"Applying State: ext={curState!.Extrapolated}, cTick={_timing.CurTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
|
||||
}
|
||||
|
||||
if (!curState!.Extrapolated)
|
||||
{
|
||||
UpdateFullRep(curState);
|
||||
}
|
||||
}
|
||||
|
||||
var cState = curState!;
|
||||
@@ -162,8 +157,10 @@ namespace Robust.Client.GameStates
|
||||
return applyNextState;
|
||||
}
|
||||
|
||||
private void UpdateFullRep(GameState state)
|
||||
public void UpdateFullRep(GameState state)
|
||||
{
|
||||
// Logger.Debug($"UPDATE FULL REP: {string.Join(", ", state.EntityStates?.Select(e => e.Uid) ?? Enumerable.Empty<EntityUid>())}");
|
||||
|
||||
if (state.FromSequence == GameTick.Zero)
|
||||
{
|
||||
// Full state.
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace Robust.Client.GameStates
|
||||
var yPos = 10 + _lineHeight * i;
|
||||
var name = $"({netEnt.Id}) {ent.Prototype?.ID}";
|
||||
var color = CalcTextColor(ref netEnt);
|
||||
DrawString(screenHandle, _font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
||||
screenHandle.DrawString(_font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
||||
DrawTrafficBox(screenHandle, ref netEnt, xPos, yPos);
|
||||
}
|
||||
}
|
||||
@@ -223,17 +223,6 @@ namespace Robust.Client.GameStates
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
|
||||
{
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
|
||||
foreach (var rune in str.EnumerateRunes())
|
||||
{
|
||||
var advance = font.DrawChar(handle, rune, baseLine, 1, textColor);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private struct NetEntity
|
||||
{
|
||||
public GameTick LastUpdate;
|
||||
|
||||
@@ -184,7 +184,7 @@ namespace Robust.Client.GameStates
|
||||
// Draw size if above average
|
||||
if (drawSizeThreshold * 1.5 < state.Payload)
|
||||
{
|
||||
DrawString(handle, _font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
|
||||
handle.DrawString(_font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
|
||||
}
|
||||
|
||||
// second tick marks
|
||||
@@ -224,14 +224,14 @@ namespace Robust.Client.GameStates
|
||||
handle.DrawLine(new Vector2(leftMargin, midYoff), new Vector2(leftMargin + width, midYoff), Color.DarkGray.WithAlpha(0.8f));
|
||||
|
||||
// payload text
|
||||
DrawString(handle, _font, new Vector2(leftMargin + width, warnYoff), "56K");
|
||||
DrawString(handle, _font, new Vector2(leftMargin + width, midYoff), "33.6K");
|
||||
handle.DrawString(_font, new Vector2(leftMargin + width, warnYoff), "56K");
|
||||
handle.DrawString(_font, new Vector2(leftMargin + width, midYoff), "33.6K");
|
||||
|
||||
// interp text info
|
||||
if(lastLagY != -1)
|
||||
DrawString(handle, _font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
|
||||
handle.DrawString(_font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
|
||||
|
||||
DrawString(handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
|
||||
handle.DrawString(_font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
@@ -241,17 +241,6 @@ namespace Robust.Client.GameStates
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
|
||||
{
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
|
||||
foreach (var rune in str.EnumerateRunes())
|
||||
{
|
||||
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private class NetShowGraphCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "net_graph";
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
static Clyde()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
||||
if (OperatingSystem.IsWindows() &&
|
||||
RuntimeInformation.ProcessArchitecture == Architecture.X64 &&
|
||||
Environment.GetEnvironmentVariable("ROBUST_INTEGRATED_GPU") != "1")
|
||||
{
|
||||
@@ -28,20 +28,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeLibrary.SetDllImportResolver(typeof(GL).Assembly, (name, assembly, path) =>
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|
||||
if (OperatingSystem.IsLinux()
|
||||
&& _dllMapLinux.TryGetValue(name, out var mappedName))
|
||||
{
|
||||
return NativeLibrary.Load(mappedName);
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
|
||||
if (OperatingSystem.IsMacOS()
|
||||
&& _dllMapMacOS.TryGetValue(name, out mappedName))
|
||||
{
|
||||
return NativeLibrary.Load(mappedName);
|
||||
|
||||
@@ -366,33 +366,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void ProcessSpriteEntities(MapId map, Box2 worldBounds,
|
||||
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
|
||||
{
|
||||
var spriteSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
|
||||
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = worldBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
|
||||
var tree = spriteSystem.GetSpriteTreeForMap(map, gridId);
|
||||
|
||||
tree.QueryAabb(ref list, ((
|
||||
comp.SpriteTree.QueryAabb(ref list, ((
|
||||
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
|
||||
in SpriteComponent value) =>
|
||||
{
|
||||
// TODO: Probably value in storing this as its own DynamicTree
|
||||
if (value.ContainerOccluded || !value.Visible)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var entity = value.Owner;
|
||||
var transform = entity.Transform;
|
||||
|
||||
@@ -404,7 +385,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
entry.yWorldPos = worldPos.Y;
|
||||
return true;
|
||||
|
||||
}), gridBounds, approx: true);
|
||||
}), bounds, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,16 +149,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// FOV FBO.
|
||||
_fovRenderTarget = CreateRenderTarget((FovMapSize, 2),
|
||||
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
|
||||
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat},
|
||||
new TextureSampleParameters { WrapMode = TextureWrapMode.Repeat },
|
||||
nameof(_fovRenderTarget));
|
||||
|
||||
if (_hasGLSamplerObjects)
|
||||
{
|
||||
_fovFilterSampler = new GLHandle(GL.GenSampler());
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMagFilter, (int) All.Linear);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMinFilter, (int) All.Linear);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapS, (int) All.Repeat);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapT, (int) All.Repeat);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMagFilter, (int)All.Linear);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMinFilter, (int)All.Linear);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapS, (int)All.Repeat);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapT, (int)All.Repeat);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
@@ -180,20 +180,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_fovCalculationProgram = _compileProgram(depthVert, depthFrag, attribLocations, "Shadow Depth Program");
|
||||
|
||||
var debugShader = _resourceCache.GetResource<ShaderSourceResource>("/Shaders/Internal/depth-debug.swsl");
|
||||
_fovDebugShaderInstance = (ClydeShaderInstance) InstanceShader(debugShader.ClydeHandle);
|
||||
_fovDebugShaderInstance = (ClydeShaderInstance)InstanceShader(debugShader.ClydeHandle);
|
||||
|
||||
ClydeHandle LoadShaderHandle(string path)
|
||||
{
|
||||
try
|
||||
if (_resourceCache.TryGetResource(path, out ShaderSourceResource? resource))
|
||||
{
|
||||
var shaderSource = _resourceCache.GetResource<ShaderSourceResource>(path);
|
||||
return shaderSource.ClydeHandle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"Can't load shader {path}\n{ex.GetType().Name}: {ex.Message}");
|
||||
return default;
|
||||
return resource.ClydeHandle;
|
||||
}
|
||||
|
||||
Logger.Warning($"Can't load shader {path}\n");
|
||||
return default;
|
||||
}
|
||||
|
||||
_lightSoftShaderHandle = LoadShaderHandle("/Shaders/Internal/light-soft.swsl");
|
||||
@@ -214,7 +211,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Calculate maximum distance for the projection based on screen size.
|
||||
var screenSizeCut = viewport.Size / EyeManager.PixelsPerMeter;
|
||||
var maxDist = (float) Math.Max(screenSizeCut.X, screenSizeCut.Y);
|
||||
var maxDist = (float)Math.Max(screenSizeCut.X, screenSizeCut.Y);
|
||||
|
||||
// FOV is rendered twice.
|
||||
// Once with back face culling like regular lighting.
|
||||
@@ -497,24 +494,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GetLightsToRender(MapId map, in Box2 worldBounds)
|
||||
{
|
||||
var renderingTreeSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
|
||||
var enlargedBounds = worldBounds.Enlarged(renderingTreeSystem.MaxLightRadius);
|
||||
|
||||
// Use worldbounds for this one as we only care if the light intersects our actual bounds
|
||||
var state = (this, worldBounds, count: 0);
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
|
||||
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = worldBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
|
||||
var lightTree = renderingTreeSystem.GetLightTreeForMap(map, gridId);
|
||||
|
||||
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
|
||||
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
|
||||
{
|
||||
var transform = light.Owner.Transform;
|
||||
|
||||
@@ -524,12 +513,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Don't insert into trees for these, same as sprites.
|
||||
if (!light.Enabled || light.ContainerOccluded)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var lightPos = transform.WorldMatrix.Transform(light.Offset);
|
||||
|
||||
var circle = new Circle(lightPos, light.Radius);
|
||||
@@ -544,7 +527,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
|
||||
|
||||
return true;
|
||||
}, gridBounds);
|
||||
}, bounds);
|
||||
}
|
||||
|
||||
if (state.count > _maxLightsPerScene)
|
||||
@@ -590,7 +573,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
SetupGlobalUniformsImmediate(shader, viewport.LightRenderTarget.Texture);
|
||||
|
||||
shader.SetUniformMaybe("size", (Vector2) viewport.WallBleedIntermediateRenderTarget1.Size);
|
||||
shader.SetUniformMaybe("size", (Vector2)viewport.WallBleedIntermediateRenderTarget1.Size);
|
||||
shader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
||||
|
||||
var size = viewport.WallBleedIntermediateRenderTarget1.Size;
|
||||
@@ -809,7 +792,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
occluderTree.QueryAabb((in OccluderComponent sOccluder) =>
|
||||
{
|
||||
var occluder = (ClientOccluderComponent) sOccluder;
|
||||
var occluder = (ClientOccluderComponent)sOccluder;
|
||||
var transform = occluder.Owner.Transform;
|
||||
if (!occluder.Enabled)
|
||||
{
|
||||
@@ -893,11 +876,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// DddD
|
||||
// HHhh
|
||||
// deflection
|
||||
arrayVIBuffer[avi++] = (byte) ((((vi + 1) & 2) != 0) ? 0 : 255);
|
||||
arrayVIBuffer[avi++] = (byte)((((vi + 1) & 2) != 0) ? 0 : 255);
|
||||
// height
|
||||
arrayVIBuffer[avi++] = (byte) (((vi & 2) != 0) ? 0 : 255);
|
||||
arrayVIBuffer[avi++] = (byte)(((vi & 2) != 0) ? 0 : 255);
|
||||
}
|
||||
QuadBatchIndexWrite(indexBuffer, ref ii, (ushort) aiBase);
|
||||
QuadBatchIndexWrite(indexBuffer, ref ii, (ushort)aiBase);
|
||||
}
|
||||
|
||||
// North face (TL/TR)
|
||||
@@ -931,7 +914,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
arrayMaskBuffer[ami + 3] = new Vector2(blX, blY);
|
||||
|
||||
// Generate mask indices.
|
||||
QuadBatchIndexWrite(indexMaskBuffer, ref imi, (ushort) ami);
|
||||
QuadBatchIndexWrite(indexMaskBuffer, ref imi, (ushort)ami);
|
||||
|
||||
ami += 4;
|
||||
|
||||
@@ -973,7 +956,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var lightMapSize = GetLightMapSize(viewport.Size);
|
||||
var lightMapSizeQuart = GetLightMapSize(viewport.Size, true);
|
||||
var lightMapColorFormat = _hasGLFloatFramebuffers ? RenderTargetColorFormat.R11FG11FB10F : RenderTargetColorFormat.Rgba8;
|
||||
var lightMapSampleParameters = new TextureSampleParameters {Filter = true};
|
||||
var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
|
||||
|
||||
viewport.LightRenderTarget?.Dispose();
|
||||
viewport.WallMaskRenderTarget?.Dispose();
|
||||
@@ -1010,14 +993,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private Vector2i GetLightMapSize(Vector2i screenSize, bool furtherDivide = false)
|
||||
{
|
||||
var divider = (float) _lightmapDivider;
|
||||
var divider = (float)_lightmapDivider;
|
||||
if (furtherDivide)
|
||||
{
|
||||
divider *= 2;
|
||||
}
|
||||
|
||||
var w = (int) Math.Ceiling(screenSize.X / divider);
|
||||
var h = (int) Math.Ceiling(screenSize.Y / divider);
|
||||
var w = (int)Math.Ceiling(screenSize.X / divider);
|
||||
var h = (int)Math.Ceiling(screenSize.Y / divider);
|
||||
|
||||
return (w, h);
|
||||
}
|
||||
@@ -1043,7 +1026,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Shadow FBO.
|
||||
_shadowRenderTarget = CreateRenderTarget((ShadowMapSize, _maxLightsPerScene),
|
||||
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
|
||||
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat, Filter = true},
|
||||
new TextureSampleParameters { WrapMode = TextureWrapMode.Repeat, Filter = true },
|
||||
nameof(_shadowRenderTarget));
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
if (!succeeded)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
var msgBoxContent = "Failed to create the game window. " +
|
||||
"This probably means your GPU is too old to play the game. " +
|
||||
@@ -164,7 +164,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private IEnumerable<Image<Rgba32>> LoadWindowIcons()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
// Does nothing on macOS so don't bother.
|
||||
yield break;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
@@ -13,11 +13,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
private sealed partial class GlfwWindowingImpl
|
||||
{
|
||||
// glfwPostEmptyEvent is broken on macOS and crashes when not called from the main thread
|
||||
// (despite what the docs claim, and yes this makes it useless).
|
||||
// Because of this, we just forego it and use glfwWaitEventsTimeout on macOS instead.
|
||||
private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
private bool _windowingRunning;
|
||||
private ChannelWriter<CmdBase> _cmdWriter = default!;
|
||||
private ChannelReader<CmdBase> _cmdReader = default!;
|
||||
@@ -53,7 +48,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
while (_windowingRunning)
|
||||
{
|
||||
if (IsMacOS)
|
||||
// glfwPostEmptyEvent is broken on macOS and crashes when not called from the main thread
|
||||
// (despite what the docs claim, and yes this makes it useless).
|
||||
// Because of this, we just forego it and use glfwWaitEventsTimeout on macOS instead.
|
||||
if (OperatingSystem.IsMacOS())
|
||||
GLFW.WaitEventsTimeout(0.008);
|
||||
else
|
||||
GLFW.WaitEvents();
|
||||
@@ -157,7 +155,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_cmdWriter.TryWrite(cmd);
|
||||
|
||||
// Post empty event to unstuck WaitEvents if necessary.
|
||||
if (!IsMacOS)
|
||||
if (!OperatingSystem.IsMacOS())
|
||||
GLFW.PostEmptyEvent();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
using System.Threading.Tasks;
|
||||
using OpenToolkit;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
@@ -33,6 +34,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private GlfwWindowReg? _mainWindow;
|
||||
private GlfwBindingsContext _mainGraphicsContext = default!;
|
||||
private int _nextWindowId = 1;
|
||||
private static bool _eglLoaded;
|
||||
|
||||
public async Task<WindowHandle> WindowCreate(WindowCreateParameters parameters)
|
||||
{
|
||||
@@ -474,6 +476,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.EglContextApi);
|
||||
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
|
||||
GLFW.WindowHint(WindowHintBool.SrgbCapable, false);
|
||||
|
||||
if (!_eglLoaded && OperatingSystem.IsWindows())
|
||||
{
|
||||
// On non-published builds (so, development), GLFW can't find libEGL.dll
|
||||
// because it'll be in runtimes/<rid>/native/ instead of next to the actual executable.
|
||||
// We manually preload the library here so that GLFW will find it when it does its thing.
|
||||
NativeLibrary.TryLoad(
|
||||
"libEGL.dll",
|
||||
typeof(Clyde).Assembly,
|
||||
DllImportSearchPath.SafeDirectories,
|
||||
out _);
|
||||
|
||||
_eglLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
@@ -39,5 +40,57 @@ namespace Robust.Client.Graphics
|
||||
DrawPrimitiveTopology type = filled ? DrawPrimitiveTopology.TriangleFan : DrawPrimitiveTopology.LineStrip;
|
||||
DrawPrimitives(type, buffer, color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a simple string to the screen at the specified position.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is primarily intended for debug purposes and does not handle things like UI scaling.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// The space taken up (horizontal and vertical) by the text.
|
||||
/// </returns>
|
||||
/// <param name="font">The font to render with.</param>
|
||||
/// <param name="pos">The top-left corner to start drawing text at.</param>
|
||||
/// <param name="str">The text to draw.</param>
|
||||
/// <param name="color">The color of text to draw.</param>
|
||||
public Vector2 DrawString(Font font, Vector2 pos, string str, Color color)
|
||||
{
|
||||
var advanceTotal = Vector2.Zero;
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
var lineHeight = font.GetLineHeight(1);
|
||||
|
||||
foreach (var rune in str.EnumerateRunes())
|
||||
{
|
||||
if (rune == new Rune('\n'))
|
||||
{
|
||||
baseLine.X = pos.X;
|
||||
baseLine.Y += lineHeight;
|
||||
advanceTotal.Y += lineHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
var advance = font.DrawChar(this, rune, baseLine, 1, color);
|
||||
advanceTotal.X += advance;
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
|
||||
return advanceTotal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a simple string to the screen at the specified position.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is primarily intended for debug purposes and does not handle things like UI scaling.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// The space taken up (horizontal and vertical) by the text.
|
||||
/// </returns>
|
||||
/// <param name="font">The font to render with.</param>
|
||||
/// <param name="pos">The top-left corner to start drawing text at.</param>
|
||||
/// <param name="str">The text to draw.</param>
|
||||
public Vector2 DrawString(Font font, Vector2 pos, string str)
|
||||
=> DrawString(font, pos, str, Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Client.Input
|
||||
{
|
||||
@@ -179,9 +178,9 @@ namespace Robust.Client.Input
|
||||
var locId = $"input-key-{key}";
|
||||
if (key == Key.LSystem || key == Key.RSystem)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (OperatingSystem.IsWindows())
|
||||
locId += "-win";
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
locId += "-mac";
|
||||
else
|
||||
locId += "-linux";
|
||||
|
||||
@@ -1,72 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Map
|
||||
{
|
||||
internal class ClientMapManager : MapManager, IClientMapManager
|
||||
{
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
|
||||
public void ApplyGameStatePre(GameStateMapData? data)
|
||||
public void ApplyGameStatePre(GameStateMapData? data, EntityState[]? entityStates)
|
||||
{
|
||||
// There was no map data this tick, so nothing to do.
|
||||
if(data == null)
|
||||
return;
|
||||
|
||||
var createdGrids = data.CreatedGrids != null
|
||||
? new Dictionary<GridId, GameStateMapData.GridCreationDatum>(data.CreatedGrids)
|
||||
: null;
|
||||
|
||||
// First we need to figure out all the NEW MAPS.
|
||||
if(data.CreatedMaps != null)
|
||||
{
|
||||
DebugTools.Assert(entityStates is not null, "Received new maps, but no entity state.");
|
||||
|
||||
foreach (var mapId in data.CreatedMaps)
|
||||
{
|
||||
// map already exists from a previous state.
|
||||
if (_maps.Contains(mapId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CreateMap(mapId);
|
||||
EntityUid mapEuid = default;
|
||||
|
||||
//get shared euid of map comp entity
|
||||
foreach (var entityState in entityStates!)
|
||||
{
|
||||
if(entityState.ComponentStates is null)
|
||||
continue;
|
||||
|
||||
foreach (var compState in entityState.ComponentStates)
|
||||
{
|
||||
if (compState is not MapComponentState mapCompState || mapCompState.MapId != mapId)
|
||||
continue;
|
||||
|
||||
mapEuid = entityState.Uid;
|
||||
goto BreakMapEntSearch;
|
||||
}
|
||||
}
|
||||
BreakMapEntSearch:
|
||||
|
||||
DebugTools.Assert(mapEuid != default, $"Could not find corresponding entity state for new map {mapId}.");
|
||||
|
||||
CreateMap(mapId, mapEuid);
|
||||
}
|
||||
}
|
||||
|
||||
// Then make all the grids.
|
||||
if(data.CreatedGrids != null)
|
||||
{
|
||||
var gridData = data.GridData != null
|
||||
? new Dictionary<GridId, GameStateMapData.GridDatum>(data.GridData)
|
||||
: null;
|
||||
|
||||
DebugTools.AssertNotNull(createdGrids);
|
||||
DebugTools.Assert(data.GridData is not null, "Received new grids, but GridData was null.");
|
||||
|
||||
foreach (var (gridId, creationDatum) in data.CreatedGrids)
|
||||
{
|
||||
if (_grids.ContainsKey(gridId))
|
||||
{
|
||||
continue;
|
||||
|
||||
EntityUid gridEuid = default;
|
||||
|
||||
//get shared euid of map comp entity
|
||||
foreach (var entityState in entityStates!)
|
||||
{
|
||||
if(entityState.ComponentStates is null)
|
||||
continue;
|
||||
|
||||
foreach (var compState in entityState.ComponentStates)
|
||||
{
|
||||
if (compState is not MapGridComponentState gridCompState || gridCompState.GridIndex != gridId)
|
||||
continue;
|
||||
|
||||
gridEuid = entityState.Uid;
|
||||
goto BreakGridEntSearch;
|
||||
}
|
||||
}
|
||||
BreakGridEntSearch:
|
||||
|
||||
DebugTools.Assert(gridEuid != default, $"Could not find corresponding entity state for new grid {gridId}.");
|
||||
|
||||
MapId gridMapId = default;
|
||||
foreach (var kvData in data.GridData!)
|
||||
{
|
||||
if (kvData.Key != gridId)
|
||||
continue;
|
||||
|
||||
gridMapId = kvData.Value.Coordinates.MapId;
|
||||
break;
|
||||
}
|
||||
|
||||
CreateGrid(gridData![gridId].Coordinates.MapId, gridId, creationDatum.ChunkSize);
|
||||
DebugTools.Assert(gridMapId != default, $"Could not find corresponding gridData for new grid {gridId}.");
|
||||
|
||||
CreateGrid(gridMapId, gridId, creationDatum.ChunkSize, gridEuid);
|
||||
}
|
||||
}
|
||||
|
||||
// Process all grid updates.
|
||||
if(data.GridData != null)
|
||||
{
|
||||
SuppressOnTileChanged = true;
|
||||
// Ok good all the grids and maps exist now.
|
||||
foreach (var (gridId, gridDatum) in data.GridData)
|
||||
{
|
||||
|
||||
var grid = _grids[gridId];
|
||||
if (grid.ParentMapId != gridDatum.Coordinates.MapId)
|
||||
{
|
||||
@@ -112,99 +151,9 @@ namespace Robust.Client.Map
|
||||
|
||||
public void ApplyGameStatePost(GameStateMapData? data)
|
||||
{
|
||||
DebugTools.Assert(_netManager.IsClient, "Only the client should call this.");
|
||||
|
||||
if(data == null) // if there is no data, there is nothing to do!
|
||||
return;
|
||||
|
||||
// maps created on the client in pre-state are linked to client entities
|
||||
// resolve new maps with their shared component that the server just gave us
|
||||
// and delete the client entities
|
||||
if (data.CreatedMaps != null)
|
||||
{
|
||||
foreach (var mapId in data.CreatedMaps)
|
||||
{
|
||||
// CreateMap should have set this
|
||||
DebugTools.Assert(_mapEntities.ContainsKey(mapId));
|
||||
|
||||
// this was already linked in a previous state.
|
||||
if(!_mapEntities[mapId].IsClientSide())
|
||||
continue;
|
||||
|
||||
// get the existing client entity for the map.
|
||||
var cEntity = EntityManager.GetEntity(_mapEntities[mapId]);
|
||||
|
||||
// locate the entity that represents this map that was just sent to us
|
||||
IEntity? sharedMapEntity = null;
|
||||
var mapComps = EntityManager.ComponentManager.EntityQuery<IMapComponent>(true);
|
||||
foreach (var mapComp in mapComps)
|
||||
{
|
||||
if (!mapComp.Owner.Uid.IsClientSide() && mapComp.WorldMap == mapId)
|
||||
{
|
||||
sharedMapEntity = mapComp.Owner;
|
||||
_mapEntities[mapId] = mapComp.Owner.Uid;
|
||||
Logger.DebugS("map", $"Map {mapId} pivoted bound entity from {cEntity.Uid} to {mapComp.Owner.Uid}.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// verify shared entity was found (the server sent us one)
|
||||
DebugTools.AssertNotNull(sharedMapEntity);
|
||||
DebugTools.Assert(!_mapEntities[mapId].IsClientSide());
|
||||
|
||||
// Transfer client child grids made in GameStatePre to the shared component
|
||||
// so they are not deleted
|
||||
foreach (var childGridTrans in cEntity.Transform.Children.ToList())
|
||||
{
|
||||
childGridTrans.AttachParent(sharedMapEntity!);
|
||||
}
|
||||
|
||||
// remove client entity
|
||||
var cGridComp = cEntity.GetComponent<IMapComponent>();
|
||||
cGridComp.ClearMapId();
|
||||
cEntity.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// grids created on the client in pre-state are linked to client entities
|
||||
// resolve new grids with their shared component that the server just gave us
|
||||
// and delete the client entities
|
||||
if (data.CreatedGrids != null)
|
||||
{
|
||||
foreach (var kvNewGrid in data.CreatedGrids)
|
||||
{
|
||||
var grid = _grids[kvNewGrid.Key];
|
||||
|
||||
// this was already linked in a previous state.
|
||||
if(!grid.GridEntityId.IsClientSide())
|
||||
continue;
|
||||
|
||||
// remove the existing client entity.
|
||||
var cEntity = EntityManager.GetEntity(grid.GridEntityId);
|
||||
var cGridComp = cEntity.GetComponent<IMapGridComponent>();
|
||||
|
||||
// prevents us from deleting the grid when deleting the grid entity
|
||||
if(cEntity.Uid.IsClientSide())
|
||||
cGridComp.ClearGridId();
|
||||
|
||||
cEntity.Delete(); // normal entities are already parented to the shared comp, client comp has no children
|
||||
|
||||
var gridComps = EntityManager.ComponentManager.EntityQuery<IMapGridComponent>(true);
|
||||
foreach (var gridComp in gridComps)
|
||||
{
|
||||
if (gridComp.GridIndex == kvNewGrid.Key)
|
||||
{
|
||||
grid.GridEntityId = gridComp.Owner.Uid;
|
||||
Logger.DebugS("map", $"Grid {grid.Index} pivoted bound entity from {cEntity.Uid} to {grid.GridEntityId}.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DebugTools.Assert(!grid.GridEntityId.IsClientSide());
|
||||
}
|
||||
}
|
||||
|
||||
if(data.DeletedGrids != null)
|
||||
{
|
||||
foreach (var grid in data.DeletedGrids)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
@@ -7,7 +8,7 @@ namespace Robust.Client.Map
|
||||
{
|
||||
// Two methods here, so that new grids etc can be made BEFORE entities get states applied,
|
||||
// but old ones can be deleted after.
|
||||
void ApplyGameStatePre(GameStateMapData? data);
|
||||
void ApplyGameStatePre(GameStateMapData? data, EntityState[]? entityStates);
|
||||
void ApplyGameStatePost(GameStateMapData? data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -25,7 +25,6 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
public partial class PlacementManager : IPlacementManager, IDisposable
|
||||
{
|
||||
[Dependency] public readonly IPhysicsManager PhysicsManager = default!;
|
||||
[Dependency] private readonly IClientNetManager NetworkManager = default!;
|
||||
[Dependency] public readonly IPlayerManager PlayerManager = default!;
|
||||
[Dependency] public readonly IResourceCache ResourceCache = default!;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
@@ -71,10 +70,10 @@ namespace Robust.Client.Player
|
||||
eye.Current = true;
|
||||
|
||||
EntityAttached?.Invoke(new EntityAttachedEventArgs(entity));
|
||||
entity.SendMessage(null, new PlayerAttachedMsg());
|
||||
|
||||
// notify ECS Systems
|
||||
ControlledEntity.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerAttachSysMessage(ControlledEntity));
|
||||
ControlledEntity.EntityManager.EventBus.RaiseLocalEvent(ControlledEntity.Uid, new PlayerAttachedEvent(ControlledEntity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -83,13 +82,13 @@ namespace Robust.Client.Player
|
||||
public void DetachEntity()
|
||||
{
|
||||
var previous = ControlledEntity;
|
||||
if (previous != null && previous.Initialized && !previous.Deleted)
|
||||
if (previous is {Initialized: true, Deleted: false})
|
||||
{
|
||||
previous.GetComponent<EyeComponent>().Current = false;
|
||||
previous.SendMessage(null, new PlayerDetachedMsg());
|
||||
|
||||
// notify ECS Systems
|
||||
previous.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerAttachSysMessage(null));
|
||||
previous.EntityManager.EventBus.RaiseLocalEvent(previous.Uid, new PlayerDetachedEvent(previous));
|
||||
}
|
||||
|
||||
ControlledEntity = null;
|
||||
|
||||
@@ -318,6 +318,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
var sizeBox = PixelSizeBox;
|
||||
|
||||
var font = ActualFont;
|
||||
var listBg = ActualBackground;
|
||||
var iconBg = ActualItemBackground;
|
||||
@@ -346,31 +348,38 @@ namespace Robust.Client.UserInterface.Controls
|
||||
itemHeight = item.IconSize.Y;
|
||||
}
|
||||
|
||||
itemHeight = Math.Max(itemHeight, ActualFont.GetHeight(UIScale));
|
||||
itemHeight = Math.Max(itemHeight, font.GetHeight(UIScale));
|
||||
itemHeight += ActualItemBackground.MinimumSize.Y;
|
||||
|
||||
item.Region = UIBox2.FromDimensions(0, offset, PixelWidth, itemHeight);
|
||||
var region = UIBox2.FromDimensions(0, offset, PixelWidth, itemHeight);
|
||||
item.Region = region;
|
||||
|
||||
bg.Draw(handle, item.Region.Value);
|
||||
|
||||
var contentBox = bg.GetContentBox(item.Region.Value);
|
||||
var drawOffset = contentBox.TopLeft;
|
||||
if (item.Icon != null)
|
||||
if (region.Intersects(sizeBox))
|
||||
{
|
||||
if (item.IconRegion.Size == Vector2.Zero)
|
||||
{
|
||||
handle.DrawTextureRect(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size), item.IconModulate);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.DrawTextureRectRegion(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size), item.IconRegion, item.IconModulate);
|
||||
}
|
||||
}
|
||||
bg.Draw(handle, item.Region.Value);
|
||||
|
||||
if (item.Text != null)
|
||||
{
|
||||
var textBox = new UIBox2(contentBox.Left + item.IconSize.X, contentBox.Top, contentBox.Right, contentBox.Bottom);
|
||||
DrawTextInternal(handle, item.Text, textBox);
|
||||
var contentBox = bg.GetContentBox(item.Region.Value);
|
||||
var drawOffset = contentBox.TopLeft;
|
||||
if (item.Icon != null)
|
||||
{
|
||||
if (item.IconRegion.Size == Vector2.Zero)
|
||||
{
|
||||
handle.DrawTextureRect(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size),
|
||||
item.IconModulate);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.DrawTextureRectRegion(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size),
|
||||
item.IconRegion, item.IconModulate);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.Text != null)
|
||||
{
|
||||
var textBox = new UIBox2(contentBox.Left + item.IconSize.X, contentBox.Top, contentBox.Right,
|
||||
contentBox.Bottom);
|
||||
DrawTextInternal(handle, item.Text, textBox);
|
||||
}
|
||||
}
|
||||
|
||||
offset += itemHeight;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
renderHandle.DrawEntity(Sprite.Owner, GlobalPixelPosition + PixelSize / 2, Scale, OverrideDirection);
|
||||
renderHandle.DrawEntity(Sprite.Owner, GlobalPixelPosition + PixelSize / 2, Scale * UIScale, OverrideDirection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Localization;
|
||||
@@ -75,7 +75,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
var control = GetChild(tab);
|
||||
var title = control.GetValue(TabTitleProperty);
|
||||
|
||||
return title ?? control.Name ?? Loc.GetString("No title");
|
||||
return title ?? control.Name ?? Loc.GetString("tab-container-not-tab-title-provided");
|
||||
}
|
||||
|
||||
public static string? GetTabTitle(Control control)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -165,6 +165,8 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
protected internal override void MouseExited()
|
||||
{
|
||||
base.MouseExited();
|
||||
|
||||
if (Resizable && CurrentDrag == DragMode.None)
|
||||
{
|
||||
DefaultCursorShape = CursorShape.Arrow;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
this.prototypeManager = prototypeManager;
|
||||
this.resourceCache = resourceCache;
|
||||
|
||||
Title = Loc.GetString("Entity Spawn Panel");
|
||||
Title = Loc.GetString("entity-spawn-window-title");
|
||||
|
||||
|
||||
SetSize = (250, 300);
|
||||
@@ -83,13 +83,13 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
(SearchBar = new LineEdit
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
PlaceHolder = Loc.GetString("Search")
|
||||
PlaceHolder = Loc.GetString("entity-spawn-window-search-bar-placeholder")
|
||||
}),
|
||||
|
||||
(ClearButton = new Button
|
||||
{
|
||||
Disabled = true,
|
||||
Text = Loc.GetString("Clear"),
|
||||
Text = Loc.GetString("entity-spawn-window-clear-button"),
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -109,13 +109,13 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
(EraseButton = new Button
|
||||
{
|
||||
ToggleMode = true,
|
||||
Text = Loc.GetString("Erase Mode")
|
||||
Text = Loc.GetString("entity-spawn-window-erase-button-text")
|
||||
}),
|
||||
|
||||
(OverrideMenu = new OptionButton
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
ToolTip = Loc.GetString("Override placement")
|
||||
ToolTip = Loc.GetString("entity-spawn-window-override-menu-tooltip")
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls
|
||||
@@ -37,9 +36,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
(InputBar = new HistoryLineEdit
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
PlaceHolder = Loc.GetString("Your C# code here.")
|
||||
PlaceHolder = "Your C# code here."
|
||||
}),
|
||||
(RunButton = new Button {Text = Loc.GetString("Run")})
|
||||
(RunButton = new Button {Text = "Run"})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Robust.Client.UserInterface
|
||||
// ReSharper disable once MemberCanBeMadeStatic.Local
|
||||
private Task<string?> RunAsyncMaybe(Func<string?> action)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
// macOS seems pretty annoying about having the file dialog opened from the main thread.
|
||||
// So we are forced to execute this synchronously on the main thread.
|
||||
@@ -326,7 +326,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
private async Task<bool> IsKDialogAvailable()
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
if (!OperatingSystem.IsLinux())
|
||||
return false;
|
||||
|
||||
if (!_checkedKDialogAvailable)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -120,7 +120,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
var clientVBox = new VBoxContainer {SeparationOverride = 0};
|
||||
_tabs.AddChild(clientVBox);
|
||||
_tabs.SetTabTitle(TabClientVars, "Client Variables");
|
||||
_tabs.SetTabTitle(TabClientVars, Loc.GetString("view-variable-instance-entity-client-variables-tab-title"));
|
||||
|
||||
var first = true;
|
||||
foreach (var group in LocalPropertyList(obj, ViewVariablesManager, _robustSerializer))
|
||||
@@ -138,7 +138,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
_clientComponents = new VBoxContainer {SeparationOverride = 0};
|
||||
_tabs.AddChild(_clientComponents);
|
||||
_tabs.SetTabTitle(TabClientComponents, "Client Components");
|
||||
_tabs.SetTabTitle(TabClientComponents, Loc.GetString("view-variable-instance-entity-client-components-tab-title"));
|
||||
|
||||
PopulateClientComponents();
|
||||
|
||||
@@ -146,11 +146,11 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
{
|
||||
_serverVariables = new VBoxContainer {SeparationOverride = 0};
|
||||
_tabs.AddChild(_serverVariables);
|
||||
_tabs.SetTabTitle(TabServerVars, "Server Variables");
|
||||
_tabs.SetTabTitle(TabServerVars, Loc.GetString("view-variable-instance-entity-server-variables-tab-title"));
|
||||
|
||||
_serverComponents = new VBoxContainer {SeparationOverride = 0};
|
||||
_tabs.AddChild(_serverComponents);
|
||||
_tabs.SetTabTitle(TabServerComponents, "Server Components");
|
||||
_tabs.SetTabTitle(TabServerComponents, Loc.GetString("view-variable-instance-entity-server-components-tab-title"));
|
||||
|
||||
PopulateServerComponents(false);
|
||||
}
|
||||
@@ -162,13 +162,13 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
_clientComponents.AddChild(_clientComponentsSearchBar = new LineEdit
|
||||
{
|
||||
PlaceHolder = Loc.GetString("Search"),
|
||||
PlaceHolder = Loc.GetString("view-variable-instance-entity-client-components-search-bar-placeholder"),
|
||||
HorizontalExpand = true,
|
||||
});
|
||||
|
||||
_clientComponents.AddChild(_clientComponentsAddButton = new Button()
|
||||
{
|
||||
Text = Loc.GetString("Add Component"),
|
||||
Text = Loc.GetString("view-variable-instance-entity-server-components-add-component-button-placeholder"),
|
||||
HorizontalExpand = true,
|
||||
});
|
||||
|
||||
@@ -198,13 +198,13 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
_serverComponents.AddChild(_serverComponentsSearchBar = new LineEdit
|
||||
{
|
||||
PlaceHolder = Loc.GetString("Search"),
|
||||
PlaceHolder = Loc.GetString("view-variable-instance-entity-server-components-search-bar-placeholder"),
|
||||
HorizontalExpand = true,
|
||||
});
|
||||
|
||||
_serverComponents.AddChild(_serverComponentsAddButton = new Button()
|
||||
{
|
||||
Text = Loc.GetString("Add Component"),
|
||||
Text = Loc.GetString("view-variable-instance-entity-server-components-add-component-button-placeholder"),
|
||||
HorizontalExpand = true,
|
||||
});
|
||||
|
||||
@@ -336,7 +336,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
{
|
||||
_addComponentWindow?.Dispose();
|
||||
|
||||
_addComponentWindow = new ViewVariablesAddWindow(GetValidComponentsForAdding(), "Add Component [C]");
|
||||
_addComponentWindow = new ViewVariablesAddWindow(GetValidComponentsForAdding(), Loc.GetString("view-variable-instance-entity-add-window-client-components"));
|
||||
_addComponentWindow.AddButtonPressed += TryAdd;
|
||||
_addComponentServer = false;
|
||||
|
||||
@@ -349,7 +349,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
if (_entitySession == null) return;
|
||||
|
||||
_addComponentWindow = new ViewVariablesAddWindow(await GetValidServerComponentsForAdding(), "Add Component [S]");
|
||||
_addComponentWindow = new ViewVariablesAddWindow(await GetValidServerComponentsForAdding(), Loc.GetString("view-variable-instance-entity-add-window-server-components"));
|
||||
_addComponentWindow.AddButtonPressed += TryAdd;
|
||||
_addComponentServer = true;
|
||||
|
||||
|
||||
@@ -237,6 +237,8 @@ namespace Robust.Server
|
||||
}
|
||||
}
|
||||
|
||||
ProgramShared.PrintRuntimeInfo(_log.RootSawmill);
|
||||
|
||||
SelfLog.Enable(s => { System.Console.WriteLine("SERILOG ERROR: {0}", s); });
|
||||
|
||||
if (!SetupLoki())
|
||||
@@ -353,7 +355,7 @@ namespace Robust.Server
|
||||
AddFinalStringsToSerializer();
|
||||
_stringSerializer.LockStrings();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
|
||||
if (OperatingSystem.IsWindows() && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
|
||||
{
|
||||
WindowsTickPeriod.TimeBeginPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
|
||||
}
|
||||
@@ -371,6 +373,19 @@ namespace Robust.Server
|
||||
var reg = factory.GetRegistration(regType);
|
||||
_stringSerializer.AddString(reg.Name);
|
||||
}
|
||||
|
||||
using var extraMappedStrings = typeof(BaseServer).Assembly
|
||||
.GetManifestResourceStream("Robust.Server.ExtraMappedSerializerStrings.txt");
|
||||
|
||||
if (extraMappedStrings != null)
|
||||
{
|
||||
using var sr = new StreamReader(extraMappedStrings);
|
||||
string? line;
|
||||
while ((line = sr.ReadLine()) != null)
|
||||
{
|
||||
_stringSerializer.AddString(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool SetupLoki()
|
||||
@@ -434,8 +449,7 @@ namespace Robust.Server
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MainLoop()
|
||||
internal void SetupMainLoop()
|
||||
{
|
||||
if (_mainLoop == null)
|
||||
{
|
||||
@@ -457,14 +471,26 @@ namespace Robust.Server
|
||||
|
||||
// set GameLoop.Running to false to return from this function.
|
||||
_time.Paused = false;
|
||||
_mainLoop.Run();
|
||||
}
|
||||
|
||||
internal void FinishMainLoop()
|
||||
{
|
||||
_time.InSimulation = true;
|
||||
Cleanup();
|
||||
|
||||
_shutdownEvent.Set();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MainLoop()
|
||||
{
|
||||
SetupMainLoop();
|
||||
|
||||
_mainLoop.Run();
|
||||
|
||||
FinishMainLoop();
|
||||
}
|
||||
|
||||
public bool ContentStart { get; set; }
|
||||
public bool LoadConfigAndUserData { private get; set; } = true;
|
||||
|
||||
@@ -545,7 +571,7 @@ namespace Robust.Server
|
||||
|
||||
//TODO: This should prob shutdown all managers in a loop.
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
|
||||
if (OperatingSystem.IsWindows() && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
|
||||
{
|
||||
WindowsTickPeriod.TimeEndPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
|
||||
}
|
||||
|
||||
1
Robust.Server/ExtraMappedSerializerStrings.txt
Normal file
1
Robust.Server/ExtraMappedSerializerStrings.txt
Normal file
@@ -0,0 +1 @@
|
||||
fixture-0
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -12,34 +11,4 @@ namespace Robust.Server.GameObjects
|
||||
[ViewVariables]
|
||||
public IPlayerSession PlayerSession { get; internal set; } = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity whenever a player attaches to this entity.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class PlayerAttachedMsg : ComponentMessage
|
||||
{
|
||||
public IPlayerSession NewPlayer { get; }
|
||||
|
||||
public PlayerAttachedMsg(IPlayerSession newPlayer)
|
||||
{
|
||||
NewPlayer = newPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity whenever a player detaches from this entity.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class PlayerDetachedMsg : ComponentMessage
|
||||
{
|
||||
public IPlayerSession OldPlayer { get; }
|
||||
|
||||
public PlayerDetachedMsg(IPlayerSession oldPlayer)
|
||||
{
|
||||
OldPlayer = oldPlayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -9,7 +8,7 @@ namespace Robust.Server.GameObjects
|
||||
/// System that handles players being attached/detached from entities.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ActorSystem : EntitySystem
|
||||
public sealed class ActorSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -22,27 +21,54 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
private void OnActorPlayerAttach(AttachPlayerEvent args)
|
||||
{
|
||||
args.Result = Attach(args.Entity, args.Player, args.Force, out var forceKicked);
|
||||
args.ForceKicked = forceKicked;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a player session to an entity, optionally kicking any sessions already attached to it.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to attach the player to</param>
|
||||
/// <param name="player">The player to attach to the entity</param>
|
||||
/// <param name="force">Whether to kick any existing players from the entity</param>
|
||||
/// <returns>Whether the attach succeeded, or not.</returns>
|
||||
public bool Attach(IEntity entity, IPlayerSession player, bool force = false)
|
||||
{
|
||||
return Attach(entity, player, false, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a player session to an entity, optionally kicking any sessions already attached to it.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to attach the player to</param>
|
||||
/// <param name="player">The player to attach to the entity</param>
|
||||
/// <param name="force">Whether to kick any existing players from the entity</param>
|
||||
/// <param name="forceKicked">The player that was forcefully kicked, or null.</param>
|
||||
/// <returns>Whether the attach succeeded, or not.</returns>
|
||||
public bool Attach(IEntity entity, IPlayerSession player, bool force, out IPlayerSession? forceKicked)
|
||||
{
|
||||
// Null by default.
|
||||
forceKicked = null;
|
||||
|
||||
// Cannot attach to a deleted entity.
|
||||
if (args.Entity.Deleted)
|
||||
if (entity.Deleted)
|
||||
{
|
||||
args.Result = false;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var uid = args.Entity.Uid;
|
||||
var uid = entity.Uid;
|
||||
|
||||
// Check if there was a player attached to the entity already...
|
||||
if (ComponentManager.TryGetComponent(uid, out ActorComponent actor))
|
||||
{
|
||||
// If we're not forcing the attach, this fails.
|
||||
if (!args.Force)
|
||||
if (!force)
|
||||
{
|
||||
args.Result = false;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the event's force-kicked session before detaching it.
|
||||
args.ForceKicked = actor.PlayerSession;
|
||||
forceKicked = actor.PlayerSession;
|
||||
|
||||
// This detach cannot fail, as a player is attached to this entity.
|
||||
// It's important to note that detaching the player removes the component.
|
||||
@@ -50,18 +76,16 @@ namespace Robust.Server.GameObjects
|
||||
}
|
||||
|
||||
// We add the actor component.
|
||||
actor = ComponentManager.AddComponent<ActorComponent>(args.Entity);
|
||||
actor.PlayerSession = args.Player;
|
||||
args.Player.SetAttachedEntity(args.Entity);
|
||||
args.Result = true;
|
||||
|
||||
// TODO: Remove component message.
|
||||
args.Entity.SendMessage(actor, new PlayerAttachedMsg(args.Player));
|
||||
actor = ComponentManager.AddComponent<ActorComponent>(entity);
|
||||
actor.PlayerSession = player;
|
||||
player.SetAttachedEntity(entity);
|
||||
|
||||
// The player is fully attached now, raise an event!
|
||||
RaiseLocalEvent(uid, new PlayerAttachedEvent(args.Entity, args.Player));
|
||||
RaiseLocalEvent(uid, new PlayerAttachedEvent(entity, player, forceKicked));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not gonna make this method call Detach as all we have to do is remove a component...
|
||||
private void OnActorPlayerDetach(EntityUid uid, ActorComponent component, DetachPlayerEvent args)
|
||||
{
|
||||
// Removing the component will call shutdown, and our subscription will handle the rest of the detach logic.
|
||||
@@ -69,15 +93,39 @@ namespace Robust.Server.GameObjects
|
||||
args.Result = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detaches an attached session from the entity, if any.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity player sessions will be detached from.</param>
|
||||
/// <returns>Whether any player session was detached.</returns>
|
||||
public bool Detach(IEntity entity)
|
||||
{
|
||||
if (!entity.HasComponent<ActorComponent>())
|
||||
return false;
|
||||
|
||||
// Removing the component will call shutdown, and our subscription will handle the rest of the detach logic.
|
||||
entity.RemoveComponent<ActorComponent>();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detaches this player from its attached entity, if any.
|
||||
/// </summary>
|
||||
/// <param name="player">The player session that will be detached from any attached entities.</param>
|
||||
/// <returns>Whether the player is now detached from any entities.
|
||||
/// This returns true if the player wasn't attached to any entity.</returns>
|
||||
public bool Detach(IPlayerSession player)
|
||||
{
|
||||
var entity = player.AttachedEntity;
|
||||
return entity == null || Detach(entity);
|
||||
}
|
||||
|
||||
private void OnActorShutdown(EntityUid uid, ActorComponent component, ComponentShutdown args)
|
||||
{
|
||||
component.PlayerSession.SetAttachedEntity(null);
|
||||
|
||||
var entity = EntityManager.GetEntity(uid);
|
||||
|
||||
// TODO: Remove component message.
|
||||
entity.SendMessage(component, new PlayerDetachedMsg(component.PlayerSession));
|
||||
|
||||
// The player is fully detached now that the component has shut down.
|
||||
RaiseLocalEvent(uid, new PlayerDetachedEvent(entity, component.PlayerSession));
|
||||
}
|
||||
@@ -86,7 +134,7 @@ namespace Robust.Server.GameObjects
|
||||
/// <summary>
|
||||
/// Raise this broadcast event to attach a player to an entity, optionally detaching the player attached to it.
|
||||
/// </summary>
|
||||
public class AttachPlayerEvent : EntityEventArgs
|
||||
public sealed class AttachPlayerEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Player to attach to the entity.
|
||||
@@ -131,7 +179,7 @@ namespace Robust.Server.GameObjects
|
||||
/// <summary>
|
||||
/// Raise this directed event to detach a player from an entity.
|
||||
/// </summary>
|
||||
public class DetachPlayerEvent : EntityEventArgs
|
||||
public sealed class DetachPlayerEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the player was detached correctly.
|
||||
@@ -144,22 +192,28 @@ namespace Robust.Server.GameObjects
|
||||
/// <summary>
|
||||
/// Event for when a player has been attached to an entity.
|
||||
/// </summary>
|
||||
public class PlayerAttachedEvent : EntityEventArgs
|
||||
public sealed class PlayerAttachedEvent : EntityEventArgs
|
||||
{
|
||||
public IEntity Entity { get; }
|
||||
public IPlayerSession Player { get; }
|
||||
|
||||
public PlayerAttachedEvent(IEntity entity, IPlayerSession player)
|
||||
/// <summary>
|
||||
/// The player session that was forcefully kicked from the entity, if any.
|
||||
/// </summary>
|
||||
public IPlayerSession? Kicked { get; }
|
||||
|
||||
public PlayerAttachedEvent(IEntity entity, IPlayerSession player, IPlayerSession? kicked = null)
|
||||
{
|
||||
Entity = entity;
|
||||
Player = player;
|
||||
Kicked = kicked;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event for when a player has been detached from an entity.
|
||||
/// </summary>
|
||||
public class PlayerDetachedEvent : EntityEventArgs
|
||||
public sealed class PlayerDetachedEvent : EntityEventArgs
|
||||
{
|
||||
public IEntity Entity { get; }
|
||||
public IPlayerSession Player { get; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -7,7 +8,7 @@ namespace Robust.Server.GameObjects
|
||||
internal sealed class GridTileLookupNode
|
||||
{
|
||||
internal GridTileLookupChunk ParentChunk { get; }
|
||||
|
||||
|
||||
internal Vector2i Indices { get; }
|
||||
|
||||
internal IEnumerable<IEntity> Entities
|
||||
@@ -17,7 +18,18 @@ namespace Robust.Server.GameObjects
|
||||
foreach (var entity in _entities)
|
||||
{
|
||||
if (!entity.Deleted)
|
||||
{
|
||||
yield return entity;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager)) continue;
|
||||
foreach (var container in containerManager.GetAllContainers())
|
||||
{
|
||||
foreach (var child in container.ContainedEntities)
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +41,7 @@ namespace Robust.Server.GameObjects
|
||||
ParentChunk = parentChunk;
|
||||
Indices = indices;
|
||||
}
|
||||
|
||||
|
||||
internal void AddEntity(IEntity entity)
|
||||
{
|
||||
_entities.Add(entity);
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -200,16 +201,20 @@ namespace Robust.Server.GameObjects
|
||||
private Box2 GetEntityBox(IEntity entity)
|
||||
{
|
||||
var aabb = _lookup.GetWorldAabbFromEntity(entity);
|
||||
return aabb.IsEmpty() ? aabb : aabb.Enlarged(-0.1f);
|
||||
|
||||
// Need to clip the aabb as anything with an edge intersecting another tile might be picked up, such as walls.
|
||||
return aabb.Scale(0.98f);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
#if DEBUG
|
||||
#if DEBUG
|
||||
SubscribeNetworkEvent<RequestGridTileLookupMessage>(HandleRequest);
|
||||
#endif
|
||||
#endif
|
||||
SubscribeLocalEvent<MoveEvent>(HandleEntityMove);
|
||||
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(HandleContainerInsert);
|
||||
SubscribeLocalEvent<EntRemovedFromContainerMessage>(HandleContainerRemove);
|
||||
SubscribeLocalEvent<EntityInitializedMessage>(HandleEntityInitialized);
|
||||
SubscribeLocalEvent<EntityDeletedMessage>(HandleEntityDeleted);
|
||||
_mapManager.OnGridCreated += HandleGridCreated;
|
||||
@@ -217,6 +222,16 @@ namespace Robust.Server.GameObjects
|
||||
_mapManager.TileChanged += HandleTileChanged;
|
||||
}
|
||||
|
||||
private void HandleContainerRemove(EntRemovedFromContainerMessage ev)
|
||||
{
|
||||
HandleEntityAdd(ev.Entity);
|
||||
}
|
||||
|
||||
private void HandleContainerInsert(EntInsertedIntoContainerMessage ev)
|
||||
{
|
||||
HandleEntityRemove(ev.Entity);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
@@ -236,6 +251,11 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
private void HandleEntityInitialized(EntityInitializedMessage message)
|
||||
{
|
||||
if (message.Entity.IsInContainer())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandleEntityAdd(message.Entity);
|
||||
}
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ namespace Robust.Server.GameObjects
|
||||
var msg = message.SystemMessage;
|
||||
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(player), msg)!;
|
||||
ReceivedSystemMessage?.Invoke(this, msg);
|
||||
ReceivedSystemMessage?.Invoke(this, sessionMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -213,7 +213,8 @@ namespace Robust.Server.GameStates
|
||||
new TransformComponent.TransformComponentState(Vector2NaN,
|
||||
oldState.Rotation,
|
||||
oldState.ParentID,
|
||||
oldState.NoLocalRotation)
|
||||
oldState.NoLocalRotation,
|
||||
oldState.Anchored)
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -183,6 +183,8 @@ namespace Robust.Server.GameStates
|
||||
oldestAck = lastAck;
|
||||
}
|
||||
|
||||
DebugTools.Assert(state.MapData?.CreatedMaps is null || (state.MapData?.CreatedMaps is not null && state.EntityStates is not null), "Sending new maps, but no entity state.");
|
||||
|
||||
// actually send the state
|
||||
var stateUpdateMessage = _networkManager.CreateNetMessage<MsgState>();
|
||||
stateUpdateMessage.State = state;
|
||||
@@ -296,10 +298,8 @@ namespace Robust.Server.GameStates
|
||||
|
||||
DebugTools.Assert(entity.Initialized);
|
||||
|
||||
if (entity.LastModifiedTick <= fromTick)
|
||||
continue;
|
||||
|
||||
stateEntities.Add(GetEntityState(entityMan.ComponentManager, player, entity.Uid, fromTick));
|
||||
if (entity.LastModifiedTick >= fromTick)
|
||||
stateEntities.Add(GetEntityState(entityMan.ComponentManager, player, entity.Uid, fromTick));
|
||||
}
|
||||
|
||||
// no point sending an empty collection
|
||||
|
||||
@@ -117,11 +117,7 @@ namespace Robust.Server.Player
|
||||
if (entity == null)
|
||||
return;
|
||||
|
||||
// This event needs to be broadcast.
|
||||
var attachPlayer = new AttachPlayerEvent(entity, this);
|
||||
entity.EntityManager.EventBus.RaiseLocalEvent(entity.Uid, attachPlayer);
|
||||
|
||||
if (!attachPlayer.Result)
|
||||
if (!EntitySystem.Get<ActorSystem>().Attach(entity, this))
|
||||
{
|
||||
Logger.Warning($"Couldn't attach player \"{this}\" to entity \"{entity}\"! Did it have a player already attached to it?");
|
||||
}
|
||||
@@ -133,10 +129,19 @@ namespace Robust.Server.Player
|
||||
if (AttachedEntity == null)
|
||||
return;
|
||||
|
||||
var detachPlayer = new DetachPlayerEvent();
|
||||
AttachedEntity.EntityManager.EventBus.RaiseLocalEvent(AttachedEntity.Uid, detachPlayer, false);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (AttachedEntity.Deleted)
|
||||
{
|
||||
Logger.Warning($"Player \"{this}\" was attached to an entity that was deleted. THIS SHOULD NEVER HAPPEN, BUT DOES.");
|
||||
// We can't contact ActorSystem because trying to fire an entity event would crash.
|
||||
// Work around it.
|
||||
AttachedEntity = null;
|
||||
UpdatePlayerState();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!detachPlayer.Result)
|
||||
if (!EntitySystem.Get<ActorSystem>().Detach(AttachedEntity))
|
||||
{
|
||||
Logger.Warning($"Couldn't detach player \"{this}\" to entity \"{AttachedEntity}\"! Is it missing an ActorComponent?");
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Robust.Server
|
||||
|
||||
internal static void SetupLogging()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
#if WINDOWS_USE_UTF8_CONSOLE
|
||||
System.Console.OutputEncoding = Encoding.UTF8;
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
<Content Include="server_config.toml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<EmbeddedResource Include="ExtraMappedSerializerStrings.txt">
|
||||
<LogicalName>Robust.Server.ExtraMappedSerializerStrings.txt</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace Robust.Server
|
||||
|
||||
IoCManager.Register<IBaseServer, BaseServer>();
|
||||
IoCManager.Register<IBaseServerInternal, BaseServer>();
|
||||
IoCManager.Register<BaseServer, BaseServer>();
|
||||
IoCManager.Register<IGameTiming, GameTiming>();
|
||||
IoCManager.Register<IComponentFactory, ServerComponentFactory>();
|
||||
IoCManager.Register<IConGroupController, ConGroupController>();
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Server.Utility
|
||||
|
||||
public static void TimeBeginPeriod(uint period)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (!OperatingSystem.IsWindows())
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var ret = timeBeginPeriod(period);
|
||||
@@ -21,7 +21,7 @@ namespace Robust.Server.Utility
|
||||
|
||||
public static void TimeEndPeriod(uint period)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (!OperatingSystem.IsWindows())
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var ret = timeEndPeriod(period);
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
{
|
||||
[Obsolete("Use MathHelper instead.")]
|
||||
public static class FloatMath
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Clamp01(float val)
|
||||
{
|
||||
return MathHelper.Clamp01(val);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T Clamp<T>(T val, T min, T max) where T : IComparable<T>
|
||||
{
|
||||
return MathHelper.Clamp(val, min, max);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Clamp(float val, float min, float max)
|
||||
{
|
||||
return MathHelper.Clamp(val, min, max);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static double Clamp(double val, double min, double max)
|
||||
{
|
||||
return MathHelper.Clamp(val, min, max);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool CloseTo(float a, float b, double tolerance = .00001)
|
||||
{
|
||||
return MathHelper.CloseTo(a, b, tolerance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Lerp(float a, float b, float blend)
|
||||
{
|
||||
return MathHelper.Lerp(a, b, blend);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,11 @@ namespace Robust.Shared.Asynchronous
|
||||
public void Initialize()
|
||||
{
|
||||
_mainThreadContext = new RobustSynchronizationContext(_runtimeLog);
|
||||
ResetSynchronizationContext();
|
||||
}
|
||||
|
||||
public void ResetSynchronizationContext()
|
||||
{
|
||||
SynchronizationContext.SetSynchronizationContext(_mainThreadContext);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Robust.Shared
|
||||
// just has the Lidgren thread go absolute brr polling.
|
||||
public static readonly CVarDef<float> NetPredictLagBias = CVarDef.Create(
|
||||
"net.predict_lag_bias",
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 0.016f : 0,
|
||||
OperatingSystem.IsWindows() ? 0.016f : 0,
|
||||
CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<int> NetStateBufMergeThreshold =
|
||||
@@ -211,6 +211,21 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<bool> LogRuntimeLog =
|
||||
CVarDef.Create("log.runtimelog", true, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* Light
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// This is the maximum the viewport is enlarged to check for any intersecting render-trees for lights.
|
||||
/// This should be set to your maximum light radius.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this value is too small it just means there may be pop-in where a light is located on a render-tree
|
||||
/// outside of our viewport.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<float> MaxLightRadius =
|
||||
CVarDef.Create("light.max_radius", 20.0f, CVar.CLIENTONLY);
|
||||
|
||||
/*
|
||||
* LOKI
|
||||
*/
|
||||
|
||||
@@ -68,6 +68,10 @@ namespace Robust.Shared.Containers
|
||||
InternalInsert(toinsert);
|
||||
transform.AttachParent(Owner.Transform);
|
||||
|
||||
// This is an edge case where the parent grid is the container being inserted into, so AttachParent would not unanchor.
|
||||
if (transform.Anchored)
|
||||
transform.Anchored = false;
|
||||
|
||||
// spatially move the object to the location of the container. If you don't want this functionality, the
|
||||
// calling code can save the local position before calling this function, and apply it afterwords.
|
||||
transform.LocalPosition = Vector2.Zero;
|
||||
@@ -145,9 +149,8 @@ namespace Robust.Shared.Containers
|
||||
{
|
||||
DebugTools.Assert(!Deleted);
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntInsertedIntoContainerMessage(toinsert, this));
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new EntInsertedIntoContainerMessage(toinsert, this));
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(toinsert));
|
||||
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toinsert, false));
|
||||
Manager.Dirty();
|
||||
}
|
||||
|
||||
@@ -162,9 +165,8 @@ namespace Robust.Shared.Containers
|
||||
DebugTools.AssertNotNull(toremove);
|
||||
DebugTools.Assert(toremove.IsValid());
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntRemovedFromContainerMessage(toremove, this));
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new EntRemovedFromContainerMessage(toremove, this));
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(toremove));
|
||||
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toremove, true));
|
||||
Manager.Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// The contents of this container have been changed.
|
||||
/// </summary>
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class ContainerContentsModifiedMessage : ComponentMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Container whose contents were modified.
|
||||
/// </summary>
|
||||
public IContainer Container { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Entity that was added or removed from the container.
|
||||
/// </summary>
|
||||
public IEntity Entity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the entity was removed. If false, it was added to the container.
|
||||
/// </summary>
|
||||
public bool Removed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="ContainerContentsModifiedMessage"/>.
|
||||
/// </summary>
|
||||
/// <param name="container">Container whose contents were modified.</param>
|
||||
/// <param name="entity">Entity that was added or removed in the container.</param>
|
||||
/// <param name="removed">If true, the entity was removed. If false, it was added to the container.</param>
|
||||
public ContainerContentsModifiedMessage(IContainer container, IEntity entity, bool removed)
|
||||
{
|
||||
Container = container;
|
||||
Entity = entity;
|
||||
Removed = removed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace Robust.Shared.Containers
|
||||
public sealed override uint? NetID => NetIDs.CONTAINER_MANAGER;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnRemove()
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Shared.Containers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
|
||||
@@ -106,8 +106,16 @@ namespace Robust.Shared.ContentPack
|
||||
Logger.DebugS("res.mod", $"Verified assemblies in {checkerSw.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
var nodes = TopologicalSort.FromBeforeAfter(
|
||||
files,
|
||||
kv => kv.Key,
|
||||
kv => kv.Value.Path,
|
||||
_ => Array.Empty<string>(),
|
||||
kv => kv.Value.references,
|
||||
allowMissing: true); // missing refs would be non-content assemblies so allow that.
|
||||
|
||||
// Actually load them in the order they depend on each other.
|
||||
foreach (var path in TopologicalSortModules(files))
|
||||
foreach (var path in TopologicalSort.Sort(nodes))
|
||||
{
|
||||
Logger.DebugS("res.mod", $"Loading module: '{path}'");
|
||||
try
|
||||
@@ -136,38 +144,6 @@ namespace Robust.Shared.ContentPack
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IEnumerable<ResourcePath> TopologicalSortModules(
|
||||
IEnumerable<KeyValuePair<string, (ResourcePath Path, string[] references)>> modules)
|
||||
{
|
||||
var elems = modules.ToDictionary(
|
||||
node => node.Key,
|
||||
node => (node.Value.Path, refs: new HashSet<string>(node.Value.references)));
|
||||
|
||||
// Remove assembly references we aren't sorting for.
|
||||
foreach (var (_, set) in elems.Values)
|
||||
{
|
||||
set.RemoveWhere(r => !elems.ContainsKey(r));
|
||||
}
|
||||
|
||||
while (elems.Count > 0)
|
||||
{
|
||||
var elem = elems.FirstOrNull(x => x.Value.refs.Count == 0);
|
||||
if (elem == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Found circular dependency in assembly dependency graph");
|
||||
}
|
||||
|
||||
elems.Remove(elem.Value.Key);
|
||||
foreach (var sElem in elems)
|
||||
{
|
||||
sElem.Value.refs.Remove(elem.Value.Key);
|
||||
}
|
||||
|
||||
yield return elem.Value.Value.Path;
|
||||
}
|
||||
}
|
||||
|
||||
private static (string[] refs, string name) GetAssemblyReferenceData(Stream stream)
|
||||
{
|
||||
using var reader = new PEReader(stream);
|
||||
|
||||
@@ -60,8 +60,8 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
// TODO: gaf
|
||||
public static bool IsFileSystemCaseSensitive() =>
|
||||
!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
&& !RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
!OperatingSystem.IsWindows()
|
||||
&& !OperatingSystem.IsMacOS();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +33,12 @@ namespace Robust.Shared
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return NativeLibrary.Load(linuxName, assembly, path);
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return NativeLibrary.Load(macName, assembly, path);
|
||||
}
|
||||
|
||||
@@ -41,31 +41,94 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public bool Initialized { get; private set; }
|
||||
public ComponentLifeStage LifeStage { get; private set; } = ComponentLifeStage.PreAdd;
|
||||
|
||||
private bool _running;
|
||||
/// <inheritdoc />
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Running
|
||||
/// <summary>
|
||||
/// Increases the life stage from <see cref="ComponentLifeStage.PreAdd" /> to <see cref="ComponentLifeStage.Added" />,
|
||||
/// calling <see cref="OnAdd" />.
|
||||
/// </summary>
|
||||
internal void LifeAddToEntity()
|
||||
{
|
||||
get => _running;
|
||||
set
|
||||
{
|
||||
if(_running == value)
|
||||
return;
|
||||
DebugTools.Assert(LifeStage == ComponentLifeStage.PreAdd);
|
||||
|
||||
if(value)
|
||||
Startup();
|
||||
else
|
||||
Shutdown();
|
||||
LifeStage = ComponentLifeStage.Adding;
|
||||
OnAdd();
|
||||
|
||||
_running = value;
|
||||
}
|
||||
DebugTools.Assert(LifeStage == ComponentLifeStage.Added, $"Component {this.GetType().Name} did not call base {nameof(OnAdd)} in derived method.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the life stage from <see cref="ComponentLifeStage.Added" /> to <see cref="ComponentLifeStage.Initialized" />,
|
||||
/// calling <see cref="Initialize" />.
|
||||
/// </summary>
|
||||
internal void LifeInitialize()
|
||||
{
|
||||
DebugTools.Assert(LifeStage == ComponentLifeStage.Added);
|
||||
|
||||
LifeStage = ComponentLifeStage.Initializing;
|
||||
Initialize();
|
||||
|
||||
DebugTools.Assert(LifeStage == ComponentLifeStage.Initialized, $"Component {this.GetType().Name} did not call base {nameof(Initialize)} in derived method.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the life stage from <see cref="ComponentLifeStage.Initialized" /> to
|
||||
/// <see cref="ComponentLifeStage.Running" />, calling <see cref="Startup" />.
|
||||
/// </summary>
|
||||
internal void LifeStartup()
|
||||
{
|
||||
DebugTools.Assert(LifeStage == ComponentLifeStage.Initialized);
|
||||
|
||||
LifeStage = ComponentLifeStage.Starting;
|
||||
Startup();
|
||||
|
||||
DebugTools.Assert(LifeStage == ComponentLifeStage.Running, $"Component {this.GetType().Name} did not call base {nameof(Startup)} in derived method.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the life stage from <see cref="ComponentLifeStage.Running" /> to <see cref="ComponentLifeStage.Stopped" />,
|
||||
/// calling <see cref="Shutdown" />.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Components are allowed to remove themselves in their own Startup function.
|
||||
/// </remarks>
|
||||
internal void LifeShutdown()
|
||||
{
|
||||
// Starting allows a component to remove itself in it's own Startup function.
|
||||
DebugTools.Assert(LifeStage == ComponentLifeStage.Starting || LifeStage == ComponentLifeStage.Running);
|
||||
|
||||
LifeStage = ComponentLifeStage.Stopping;
|
||||
Shutdown();
|
||||
|
||||
DebugTools.Assert(LifeStage == ComponentLifeStage.Stopped, $"Component {this.GetType().Name} did not call base {nameof(Shutdown)} in derived method.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the life stage from <see cref="ComponentLifeStage.Stopped" /> to <see cref="ComponentLifeStage.Deleted" />,
|
||||
/// calling <see cref="OnRemove" />.
|
||||
/// </summary>
|
||||
internal void LifeRemoveFromEntity()
|
||||
{
|
||||
// can be called at any time after PreAdd, including inside other life stage events.
|
||||
DebugTools.Assert(LifeStage != ComponentLifeStage.PreAdd);
|
||||
|
||||
LifeStage = ComponentLifeStage.Removing;
|
||||
OnRemove();
|
||||
|
||||
DebugTools.Assert(LifeStage == ComponentLifeStage.Deleted, $"Component {this.GetType().Name} did not call base {nameof(OnRemove)} in derived method.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public bool Deleted { get; private set; }
|
||||
public bool Initialized => LifeStage >= ComponentLifeStage.Initializing;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public bool Running => ComponentLifeStage.Starting <= LifeStage && LifeStage <= ComponentLifeStage.Stopping;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public bool Deleted => LifeStage >= ComponentLifeStage.Removing;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
@@ -91,87 +154,56 @@ namespace Robust.Shared.GameObjects
|
||||
return (EntityEventBus) Owner.EntityManager.EventBus;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void OnRemove()
|
||||
/// <summary>
|
||||
/// Called when the component gets added to an entity.
|
||||
/// </summary>
|
||||
protected virtual void OnAdd()
|
||||
{
|
||||
if (Running)
|
||||
throw new InvalidOperationException("Cannot Remove a running entity!");
|
||||
|
||||
// We have been marked for deletion by the Component Manager.
|
||||
Deleted = true;
|
||||
GetBus().RaiseComponentEvent(this, CompRemoveInstance);
|
||||
CreationTick = Owner.EntityManager.CurrentTick;
|
||||
GetBus().RaiseComponentEvent(this, CompAddInstance);
|
||||
LifeStage = ComponentLifeStage.Added;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the component gets added to an entity.
|
||||
/// Called when all of the entity's other components have been added and are available,
|
||||
/// But are not necessarily initialized yet. DO NOT depend on the values of other components to be correct.
|
||||
/// </summary>
|
||||
public virtual void OnAdd()
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
if (Initialized)
|
||||
throw new InvalidOperationException("Cannot Add an Initialized component!");
|
||||
|
||||
if (Running)
|
||||
throw new InvalidOperationException("Cannot Add a running component!");
|
||||
|
||||
if (Deleted)
|
||||
throw new InvalidOperationException("Cannot Add a Deleted component!");
|
||||
|
||||
CreationTick = Owner.EntityManager.CurrentTick;
|
||||
GetBus().RaiseComponentEvent(this, CompAddInstance);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Initialize()
|
||||
{
|
||||
if (Initialized)
|
||||
throw new InvalidOperationException("Component already Initialized!");
|
||||
|
||||
if (Running)
|
||||
throw new InvalidOperationException("Cannot Initialize a running component!");
|
||||
|
||||
if (Deleted)
|
||||
throw new InvalidOperationException("Cannot Initialize a Deleted component!");
|
||||
|
||||
Initialized = true;
|
||||
GetBus().RaiseComponentEvent(this, CompInitInstance);
|
||||
LifeStage = ComponentLifeStage.Initialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts up a component. This is called automatically after all components are Initialized and the entity is Initialized.
|
||||
/// This can be called multiple times during the component's life, and at any time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Components are allowed to remove themselves in their own Startup function.
|
||||
/// </remarks>
|
||||
protected virtual void Startup()
|
||||
{
|
||||
if (!Initialized)
|
||||
throw new InvalidOperationException("Cannot Start an uninitialized component!");
|
||||
|
||||
if (Running)
|
||||
throw new InvalidOperationException("Cannot Startup a running component!");
|
||||
|
||||
if (Deleted)
|
||||
throw new InvalidOperationException("Cannot Start a Deleted component!");
|
||||
|
||||
_running = true;
|
||||
GetBus().RaiseComponentEvent(this, CompStartupInstance);
|
||||
LifeStage = ComponentLifeStage.Running;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuts down the component. The is called Automatically by OnRemove. This can be called multiple times during
|
||||
/// the component's life, and at any time.
|
||||
/// Shuts down the component. The is called Automatically by OnRemove.
|
||||
/// </summary>
|
||||
protected virtual void Shutdown()
|
||||
{
|
||||
if (!Initialized)
|
||||
throw new InvalidOperationException("Cannot Shutdown an uninitialized component!");
|
||||
|
||||
if (!Running)
|
||||
throw new InvalidOperationException("Cannot Shutdown an unstarted component!");
|
||||
|
||||
if (Deleted)
|
||||
throw new InvalidOperationException("Cannot Shutdown a Deleted component!");
|
||||
|
||||
_running = false;
|
||||
GetBus().RaiseComponentEvent(this, CompShutdownInstance);
|
||||
LifeStage = ComponentLifeStage.Stopped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the component is removed from an entity.
|
||||
/// Shuts down the component.
|
||||
/// The component has already been marked as deleted in the component manager.
|
||||
/// </summary>
|
||||
protected virtual void OnRemove()
|
||||
{
|
||||
GetBus().RaiseComponentEvent(this, CompRemoveInstance);
|
||||
LifeStage = ComponentLifeStage.Deleted;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -244,6 +276,67 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The life stages of an ECS component.
|
||||
/// </summary>
|
||||
public enum ComponentLifeStage
|
||||
{
|
||||
/// <summary>
|
||||
/// The component has just been allocated.
|
||||
/// </summary>
|
||||
PreAdd = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Currently being added to an entity.
|
||||
/// </summary>
|
||||
Adding,
|
||||
|
||||
/// <summary>
|
||||
/// Has been added to an entity.
|
||||
/// </summary>
|
||||
Added,
|
||||
|
||||
/// <summary>
|
||||
/// Currently being initialized.
|
||||
/// </summary>
|
||||
Initializing,
|
||||
|
||||
/// <summary>
|
||||
/// Has been initialized.
|
||||
/// </summary>
|
||||
Initialized,
|
||||
|
||||
/// <summary>
|
||||
/// Currently being started up.
|
||||
/// </summary>
|
||||
Starting,
|
||||
|
||||
/// <summary>
|
||||
/// Has started up.
|
||||
/// </summary>
|
||||
Running,
|
||||
|
||||
/// <summary>
|
||||
/// Currently shutting down.
|
||||
/// </summary>
|
||||
Stopping,
|
||||
|
||||
/// <summary>
|
||||
/// Has been shut down.
|
||||
/// </summary>
|
||||
Stopped,
|
||||
|
||||
/// <summary>
|
||||
/// Currently being removed from it's entity.
|
||||
/// </summary>
|
||||
Removing,
|
||||
|
||||
/// <summary>
|
||||
/// Removed from it's entity, and is deleted.
|
||||
/// </summary>
|
||||
Deleted,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The component has been added to the entity. This is the first function
|
||||
/// to be called after the component has been allocated and (optionally) deserialized.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
@@ -93,5 +94,21 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
return entity.AddComponent<T>();
|
||||
}
|
||||
|
||||
public static IComponent SetAndDirtyIfChanged<TValue>(
|
||||
this IComponent comp,
|
||||
ref TValue backingField,
|
||||
TValue value)
|
||||
{
|
||||
if (EqualityComparer<TValue>.Default.Equals(backingField, value))
|
||||
{
|
||||
return comp;
|
||||
}
|
||||
|
||||
backingField = value;
|
||||
comp.Dirty();
|
||||
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,19 +164,15 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
_componentDependencyManager.OnComponentAdd(entity.Uid, component);
|
||||
|
||||
component.OnAdd();
|
||||
component.LifeAddToEntity();
|
||||
|
||||
if (!entity.Initialized && !entity.Initializing) return;
|
||||
if (!entity.Initialized && !entity.Initializing)
|
||||
return;
|
||||
|
||||
component.Initialize();
|
||||
|
||||
DebugTools.Assert(component.Initialized, "Component is not initialized after calling Initialize(). "
|
||||
+ "Did you forget to call base.Initialize() in an override?");
|
||||
component.LifeInitialize();
|
||||
|
||||
if (entity.Initialized)
|
||||
{
|
||||
component.Running = true;
|
||||
}
|
||||
component.LifeStartup();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -190,14 +186,14 @@ namespace Robust.Shared.GameObjects
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RemoveComponent(EntityUid uid, Type type)
|
||||
{
|
||||
RemoveComponentDeferred((Component) GetComponent(uid, type), uid, false);
|
||||
RemoveComponentDeferred((Component)GetComponent(uid, type), uid, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RemoveComponent(EntityUid uid, uint netId)
|
||||
{
|
||||
RemoveComponentDeferred((Component) GetComponent(uid, netId), uid, false);
|
||||
RemoveComponentDeferred((Component)GetComponent(uid, netId), uid, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -209,7 +205,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (component.Owner == null || component.Owner.Uid != uid)
|
||||
throw new InvalidOperationException("Component is not owned by entity.");
|
||||
|
||||
RemoveComponentDeferred((Component) component, uid, false);
|
||||
RemoveComponentDeferred((Component)component, uid, false);
|
||||
}
|
||||
|
||||
private static IEnumerable<Component> InSafeOrder(IEnumerable<Component> comps, bool forCreation = false)
|
||||
@@ -260,23 +256,26 @@ namespace Robust.Shared.GameObjects
|
||||
try
|
||||
{
|
||||
#endif
|
||||
// these two components are required on all entities and cannot be removed normally.
|
||||
if (!removeProtected && (component is ITransformComponent || component is IMetaDataComponent))
|
||||
{
|
||||
DebugTools.Assert("Tried to remove a protected component.");
|
||||
return;
|
||||
}
|
||||
// these two components are required on all entities and cannot be removed normally.
|
||||
if (!removeProtected && (component is ITransformComponent || component is IMetaDataComponent))
|
||||
{
|
||||
DebugTools.Assert("Tried to remove a protected component.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_deleteSet.Add(component))
|
||||
{
|
||||
// already deferred deletion
|
||||
return;
|
||||
}
|
||||
if (!_deleteSet.Add(component))
|
||||
{
|
||||
// already deferred deletion
|
||||
return;
|
||||
}
|
||||
|
||||
component.Running = false;
|
||||
component.OnRemove();
|
||||
_componentDependencyManager.OnComponentRemove(uid, component);
|
||||
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, uid));
|
||||
if (component.Running)
|
||||
component.LifeShutdown();
|
||||
|
||||
if (component.LifeStage != ComponentLifeStage.PreAdd)
|
||||
component.LifeRemoveFromEntity();
|
||||
_componentDependencyManager.OnComponentRemove(uid, component);
|
||||
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, uid));
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -300,8 +299,12 @@ namespace Robust.Shared.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
component.Running = false;
|
||||
component.OnRemove(); // Sets delete
|
||||
if (component.Running)
|
||||
component.LifeShutdown();
|
||||
|
||||
if (component.LifeStage != ComponentLifeStage.PreAdd)
|
||||
component.LifeRemoveFromEntity(); // Sets delete
|
||||
|
||||
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, component.Owner.Uid));
|
||||
|
||||
}
|
||||
@@ -374,7 +377,7 @@ namespace Robust.Shared.GameObjects
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetComponent<T>(EntityUid uid)
|
||||
{
|
||||
return (T) GetComponent(uid, typeof(T));
|
||||
return (T)GetComponent(uid, typeof(T));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -406,7 +409,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
if (!comp.Deleted)
|
||||
{
|
||||
component = (T) comp;
|
||||
component = (T)comp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -486,7 +489,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
if (comp.Deleted || !includePaused && comp.Paused) continue;
|
||||
|
||||
yield return (T) (object) comp;
|
||||
yield return (T)(object)comp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,7 +510,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (!trait2.TryGetValue(uid, out var t2Comp) || t2Comp.Deleted || !includePaused && kvComp.Value.Paused)
|
||||
continue;
|
||||
|
||||
yield return ((TComp1) (object) kvComp.Value, (TComp2) (object) t2Comp);
|
||||
yield return ((TComp1)(object)kvComp.Value, (TComp2)(object)t2Comp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,9 +534,9 @@ namespace Robust.Shared.GameObjects
|
||||
if (!trait3.TryGetValue(uid, out var t3Comp) || t3Comp.Deleted)
|
||||
continue;
|
||||
|
||||
yield return ((TComp1) (object) kvComp.Value,
|
||||
(TComp2) (object) t2Comp,
|
||||
(TComp3) (object) t3Comp);
|
||||
yield return ((TComp1)(object)kvComp.Value,
|
||||
(TComp2)(object)t2Comp,
|
||||
(TComp3)(object)t3Comp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,10 +565,10 @@ namespace Robust.Shared.GameObjects
|
||||
if (!trait4.TryGetValue(uid, out var t4Comp) || t4Comp.Deleted)
|
||||
continue;
|
||||
|
||||
yield return ((TComp1) (object) kvComp.Value,
|
||||
(TComp2) (object) t2Comp,
|
||||
(TComp3) (object) t3Comp,
|
||||
(TComp4) (object) t4Comp);
|
||||
yield return ((TComp1)(object)kvComp.Value,
|
||||
(TComp2)(object)t2Comp,
|
||||
(TComp3)(object)t3Comp,
|
||||
(TComp4)(object)t4Comp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class RelayMovementEntityMessage : ComponentMessage
|
||||
{
|
||||
[PublicAPI]
|
||||
public readonly IEntity Entity;
|
||||
|
||||
public RelayMovementEntityMessage(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The entity transform parent has been changed.
|
||||
/// </summary>
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class ParentChangedMessage : ComponentMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The new parent of the transform.
|
||||
/// </summary>
|
||||
public IEntity? NewParent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The old parent of the transform.
|
||||
/// </summary>
|
||||
public IEntity? OldParent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="ParentChangedMessage"/>.
|
||||
/// </summary>
|
||||
/// <param name="newParent">The new parent of the transform.</param>
|
||||
/// <param name="oldParent">The old parent of the transform.</param>
|
||||
public ParentChangedMessage(IEntity? newParent, IEntity? oldParent)
|
||||
{
|
||||
NewParent = newParent;
|
||||
OldParent = oldParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,9 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <summary>
|
||||
/// Store the body's index within the island so we can lookup its data.
|
||||
/// Key is Island's ID and value is our index.
|
||||
/// </summary>
|
||||
public int IslandIndex { get; set; }
|
||||
public Dictionary<int, int> IslandIndex { get; set; } = new();
|
||||
|
||||
// TODO: Actually implement after the initial pr dummy
|
||||
/// <summary>
|
||||
@@ -283,7 +284,7 @@ namespace Robust.Shared.GameObjects
|
||||
joints.Add(je.Joint);
|
||||
}
|
||||
|
||||
return new PhysicsComponentState(_canCollide, _sleepingAllowed, _fixedRotation, _bodyStatus, _fixtures, joints, _mass, LinearVelocity, AngularVelocity, BodyType);
|
||||
return new PhysicsComponentState(_canCollide, _sleepingAllowed, _fixedRotation, _bodyStatus, _fixtures, joints, LinearVelocity, AngularVelocity, BodyType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -514,15 +515,20 @@ namespace Robust.Shared.GameObjects
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public Box2 GetWorldAABB(Vector2? worldPosition = null, Angle? worldRotation = null)
|
||||
public Box2 GetWorldAABB(Vector2? worldPos = null, Angle? worldRot = null)
|
||||
{
|
||||
worldRotation ??= Owner.Transform.WorldRotation;
|
||||
worldPosition ??= Owner.Transform.WorldPosition;
|
||||
var bounds = new Box2(worldPosition.Value, worldPosition.Value);
|
||||
worldPos ??= Owner.Transform.WorldPosition;
|
||||
worldRot ??= Owner.Transform.WorldRotation;
|
||||
|
||||
var worldPosValue = worldPos.Value;
|
||||
var worldRotValue = worldRot.Value;
|
||||
|
||||
var bounds = new Box2(worldPosValue, worldPosValue);
|
||||
|
||||
foreach (var fixture in _fixtures)
|
||||
{
|
||||
bounds = bounds.Union(fixture.Shape.CalculateLocalBounds(worldRotation.Value).Translated(worldPosition.Value));
|
||||
var boundy = fixture.Shape.CalculateLocalBounds(worldRotValue);
|
||||
bounds = bounds.Union(boundy.Translated(worldPosValue));
|
||||
}
|
||||
|
||||
return bounds;
|
||||
@@ -1143,7 +1149,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
@@ -1248,7 +1254,7 @@ namespace Robust.Shared.GameObjects
|
||||
Logger.DebugS("physics", $"Removed joint id: {joint.ID} type: {joint.GetType().Name} from {Owner}");
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
// Need to do these immediately in case collision behaviors deleted the body
|
||||
|
||||
@@ -18,10 +18,6 @@ namespace Robust.Shared.GameObjects
|
||||
public readonly List<Fixture> Fixtures;
|
||||
public readonly List<Joint> Joints;
|
||||
|
||||
/// <summary>
|
||||
/// Current mass of the entity, stored in grams.
|
||||
/// </summary>
|
||||
public readonly int Mass;
|
||||
public readonly Vector2 LinearVelocity;
|
||||
public readonly float AngularVelocity;
|
||||
public readonly BodyType BodyType;
|
||||
@@ -35,7 +31,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="status"></param>
|
||||
/// <param name="fixtures"></param>
|
||||
/// <param name="joints"></param>
|
||||
/// <param name="mass">Current Mass of the entity.</param>
|
||||
/// <param name="linearVelocity">Current linear velocity of the entity in meters per second.</param>
|
||||
/// <param name="angularVelocity">Current angular velocity of the entity in radians per sec.</param>
|
||||
/// <param name="bodyType"></param>
|
||||
@@ -46,7 +41,6 @@ namespace Robust.Shared.GameObjects
|
||||
BodyStatus status,
|
||||
List<Fixture> fixtures,
|
||||
List<Joint> joints,
|
||||
float mass,
|
||||
Vector2 linearVelocity,
|
||||
float angularVelocity,
|
||||
BodyType bodyType)
|
||||
@@ -61,7 +55,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
LinearVelocity = linearVelocity;
|
||||
AngularVelocity = angularVelocity;
|
||||
Mass = (int) Math.Round(mass * 1000); // rounds kg to nearest gram
|
||||
BodyType = bodyType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -32,12 +33,12 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseStateChange()
|
||||
internal void RaiseStateChange()
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new CollisionWakeStateMessage(), false);
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
if (Owner.TryGetComponent(out IPhysBody? body))
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public override string Name => "DebugExceptionOnAdd";
|
||||
|
||||
public override void OnAdd() => throw new NotSupportedException();
|
||||
protected override void OnAdd() => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -27,7 +27,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public override string Name => "DebugExceptionInitialize";
|
||||
|
||||
public override void Initialize() => throw new NotSupportedException();
|
||||
protected override void Initialize() => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,13 +8,13 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public override string Name => "IgnorePause";
|
||||
|
||||
public override void OnAdd()
|
||||
protected override void OnAdd()
|
||||
{
|
||||
base.OnAdd();
|
||||
Owner.Paused = false;
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
if (IoCManager.Resolve<IPauseManager>().IsMapPaused(Owner.Transform.MapID))
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -18,6 +18,9 @@ namespace Robust.Shared.GameObjects
|
||||
GridId GridIndex { get; }
|
||||
IMapGrid Grid { get; }
|
||||
void ClearGridId();
|
||||
|
||||
bool AnchorEntity(ITransformComponent transform);
|
||||
void UnanchorEntity(ITransformComponent transform);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IMapGridComponent"/>
|
||||
@@ -47,12 +50,13 @@ namespace Robust.Shared.GameObjects
|
||||
[ViewVariables]
|
||||
public IMapGrid Grid => _mapManager.GetGrid(_gridIndex);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ClearGridId()
|
||||
{
|
||||
_gridIndex = GridId.Invalid;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
var mapId = Owner.Transform.MapID;
|
||||
@@ -63,11 +67,54 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool AnchorEntity(ITransformComponent transform)
|
||||
{
|
||||
var xform = (TransformComponent) transform;
|
||||
var tileIndices = Grid.TileIndicesFor(transform.Coordinates);
|
||||
var result = Grid.AddToSnapGridCell(tileIndices, transform.Owner.Uid);
|
||||
|
||||
if (result)
|
||||
{
|
||||
xform.Parent = Owner.Transform;
|
||||
|
||||
// anchor snapping
|
||||
xform.LocalPosition = Grid.GridTileToLocal(Grid.TileIndicesFor(xform.LocalPosition)).Position;
|
||||
|
||||
xform.SetAnchored(result);
|
||||
|
||||
if (xform.Owner.TryGetComponent<PhysicsComponent>(out var physicsComponent))
|
||||
{
|
||||
physicsComponent.BodyType = BodyType.Static;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UnanchorEntity(ITransformComponent transform)
|
||||
{
|
||||
//HACK: Client grid pivot causes this.
|
||||
//TODO: make grid components the actual grid
|
||||
if(GridIndex == GridId.Invalid)
|
||||
return;
|
||||
|
||||
var xform = (TransformComponent)transform;
|
||||
var tileIndices = Grid.TileIndicesFor(transform.Coordinates);
|
||||
Grid.RemoveFromSnapGridCell(tileIndices, transform.Owner.Uid);
|
||||
xform.SetAnchored(false);
|
||||
if (xform.Owner.TryGetComponent<PhysicsComponent>(out var physicsComponent))
|
||||
{
|
||||
physicsComponent.BodyType = BodyType.Dynamic;
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="player"></param>
|
||||
/// <inheritdoc />
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
return new MapGridComponentState(_gridIndex, Grid.HasGravity);
|
||||
return new MapGridComponentState(_gridIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -75,11 +122,10 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (!(curState is MapGridComponentState state))
|
||||
if (curState is not MapGridComponentState state)
|
||||
return;
|
||||
|
||||
_gridIndex = state.GridIndex;
|
||||
Grid.HasGravity = state.HasGravity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,17 +140,14 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public GridId GridIndex { get; }
|
||||
|
||||
public bool HasGravity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="MapGridComponentState"/>.
|
||||
/// </summary>
|
||||
/// <param name="gridIndex">Index of the grid this component is linked to.</param>
|
||||
public MapGridComponentState(GridId gridIndex, bool hasGravity)
|
||||
public MapGridComponentState(GridId gridIndex)
|
||||
: base(NetIDs.MAP_GRID)
|
||||
{
|
||||
GridIndex = gridIndex;
|
||||
HasGravity = hasGravity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes it possible to look this entity up with the snap grid.
|
||||
/// </summary>
|
||||
[Obsolete("Use Transform.Anchored instead of this flag component.")]
|
||||
internal class SnapGridComponent : Component
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed override string Name => "SnapGrid";
|
||||
|
||||
/// <summary>
|
||||
/// GridId the last time this component was moved.
|
||||
/// </summary>
|
||||
internal GridId LastGrid = GridId.Invalid;
|
||||
|
||||
/// <summary>
|
||||
/// TileIndices the last time this component was moved.
|
||||
/// </summary>
|
||||
internal Vector2i LastTileIndices;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace Robust.Shared.GameObjects
|
||||
private Angle _localRotation; // local rotation
|
||||
[DataField("noRot")]
|
||||
private bool _noLocalRotation;
|
||||
[DataField("anchored")]
|
||||
private bool _anchored;
|
||||
|
||||
private Matrix3 _localMatrix = Matrix3.Identity;
|
||||
private Matrix3 _invLocalMatrix = Matrix3.Identity;
|
||||
@@ -160,6 +162,11 @@ namespace Robust.Shared.GameObjects
|
||||
get => !_parent.IsValid() ? null : Owner.EntityManager.GetEntity(_parent).Transform;
|
||||
set
|
||||
{
|
||||
if (_anchored && value?.Owner.Uid != _parent)
|
||||
{
|
||||
Anchored = false;
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
AttachParent(value);
|
||||
@@ -212,6 +219,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use ContainerHelper to check if this entity is inside a container.")]
|
||||
public bool IsMapTransform => !Owner.IsInContainer();
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -261,6 +269,10 @@ namespace Robust.Shared.GameObjects
|
||||
// NOTE: This setter must be callable from before initialize (inheriting from AttachParent's note)
|
||||
set
|
||||
{
|
||||
// unless the parent is changing, nothing to do here
|
||||
if(value.EntityId == _parent && _anchored)
|
||||
return;
|
||||
|
||||
var oldPosition = Coordinates;
|
||||
_localPosition = value.Position;
|
||||
|
||||
@@ -268,6 +280,9 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (value.EntityId != _parent)
|
||||
{
|
||||
if(_parent != EntityUid.Invalid) // Allow setting Transform.Parent in Prototypes
|
||||
Anchored = false; // changing the parent un-anchors the entity
|
||||
|
||||
changedParent = true;
|
||||
var newParentEnt = Owner.EntityManager.GetEntity(value.EntityId);
|
||||
var newParent = newParentEnt.Transform;
|
||||
@@ -284,16 +299,11 @@ namespace Robust.Shared.GameObjects
|
||||
var newConcrete = (TransformComponent) newParent;
|
||||
newConcrete._children.Add(uid);
|
||||
|
||||
var oldParentOwner = oldParent?.Owner;
|
||||
var entMessage = new EntParentChangedMessage(Owner, oldParentOwner);
|
||||
var compMessage = new ParentChangedMessage(newParentEnt, oldParentOwner);
|
||||
|
||||
// offset position from world to parent
|
||||
_parent = newParentEnt.Uid;
|
||||
ChangeMapId(newConcrete.MapID);
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, entMessage);
|
||||
Owner.SendMessage(this, compMessage);
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new EntParentChangedMessage(Owner, oldParent?.Owner));
|
||||
|
||||
GridID = GetGridIndex();
|
||||
}
|
||||
@@ -310,7 +320,8 @@ namespace Robust.Shared.GameObjects
|
||||
//TODO: This is a hack, look into WHY we can't call GridPosition before the comp is Running
|
||||
if (Running)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new MoveEvent(Owner, oldPosition, Coordinates));
|
||||
if(!oldPosition.Position.Equals(Coordinates.Position))
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new MoveEvent(Owner, oldPosition, Coordinates));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -330,6 +341,9 @@ namespace Robust.Shared.GameObjects
|
||||
get => _localPosition;
|
||||
set
|
||||
{
|
||||
if(Anchored)
|
||||
return;
|
||||
|
||||
if (_localPosition.EqualsApprox(value, 0.00001))
|
||||
return;
|
||||
|
||||
@@ -353,17 +367,38 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Anchored
|
||||
{
|
||||
get => Owner.HasComponent<SnapGridComponent>();
|
||||
get => _anchored;
|
||||
set
|
||||
{
|
||||
if(value && !Owner.HasComponent<SnapGridComponent>())
|
||||
Owner.AddComponent<SnapGridComponent>();
|
||||
|
||||
else if(!value && Owner.HasComponent<SnapGridComponent>())
|
||||
Owner.RemoveComponent<SnapGridComponent>();
|
||||
// This will be set again when the transform starts, actually anchoring it.
|
||||
if (LifeStage < ComponentLifeStage.Starting)
|
||||
{
|
||||
_anchored = value;
|
||||
}
|
||||
else if (LifeStage == ComponentLifeStage.Starting)
|
||||
{
|
||||
if (value && _mapManager.TryFindGridAt(MapPosition, out var grid))
|
||||
{
|
||||
_anchored = Owner.EntityManager.GetEntity(grid.GridEntityId).GetComponent<IMapGridComponent>().AnchorEntity(this);
|
||||
}
|
||||
}
|
||||
else if (value && !_anchored && _mapManager.TryFindGridAt(MapPosition, out var grid))
|
||||
{
|
||||
_anchored = Owner.EntityManager.GetEntity(grid.GridEntityId).GetComponent<IMapGridComponent>().AnchorEntity(this);
|
||||
}
|
||||
else if (!value && _anchored)
|
||||
{
|
||||
// An anchored entity is always parented to the grid.
|
||||
// If Transform.Anchored is true in the prototype but the entity was not spawned with a grid as the parent,
|
||||
// then this will be false.
|
||||
if (Owner.EntityManager.ComponentManager.TryGetComponent<IMapGridComponent>(ParentUid, out var gridComp))
|
||||
gridComp.UnanchorEntity(this);
|
||||
else
|
||||
SetAnchored(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,7 +439,7 @@ namespace Robust.Shared.GameObjects
|
||||
[ViewVariables] public EntityUid LerpParent { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
@@ -482,6 +517,10 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
protected override void Startup()
|
||||
{
|
||||
// Re-Anchor the entity if needed.
|
||||
if (_anchored)
|
||||
Anchored = true;
|
||||
|
||||
base.Startup();
|
||||
|
||||
// Keep the cached matrices in sync with the fields.
|
||||
@@ -568,17 +607,14 @@ namespace Robust.Shared.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
Anchored = false;
|
||||
|
||||
var oldConcrete = (TransformComponent) oldParent;
|
||||
var uid = Owner.Uid;
|
||||
oldConcrete._children.Remove(uid);
|
||||
|
||||
var oldParentOwner = oldParent?.Owner;
|
||||
|
||||
var entMessage = new EntParentChangedMessage(Owner, oldParentOwner);
|
||||
var compMessage = new ParentChangedMessage(null, oldParentOwner);
|
||||
_parent = EntityUid.Invalid;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, entMessage);
|
||||
Owner.SendMessage(this, compMessage);
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new EntParentChangedMessage(Owner, oldParent?.Owner));
|
||||
var oldMapId = MapID;
|
||||
MapID = MapId.Nullspace;
|
||||
|
||||
@@ -687,7 +723,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
return new TransformComponentState(_localPosition, LocalRotation, _parent, _noLocalRotation);
|
||||
return new TransformComponentState(_localPosition, LocalRotation, _parent, _noLocalRotation, _anchored);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -737,6 +773,16 @@ namespace Robust.Shared.GameObjects
|
||||
_prevPosition = newState.LocalPosition;
|
||||
_prevRotation = newState.Rotation;
|
||||
|
||||
Anchored = newState.Anchored;
|
||||
|
||||
// This is not possible, because client entities don't exist on the server, so the parent HAS to be a shared entity.
|
||||
// If this assert fails, the code above that sets the parent is broken.
|
||||
DebugTools.Assert(!_parent.IsClientSide(), "Transform received a state, but is still parented to a client entity.");
|
||||
|
||||
// Whatever happened on the client, these should still be correct
|
||||
DebugTools.Assert(ParentUid == newState.ParentID);
|
||||
DebugTools.Assert(Anchored == newState.Anchored);
|
||||
|
||||
if (rebuildMatrices)
|
||||
{
|
||||
RebuildMatrices();
|
||||
@@ -837,6 +883,11 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public readonly bool NoLocalRotation;
|
||||
|
||||
/// <summary>
|
||||
/// True if the transform is anchored to a tile.
|
||||
/// </summary>
|
||||
public readonly bool Anchored;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new state snapshot of a TransformComponent.
|
||||
/// </summary>
|
||||
@@ -844,15 +895,24 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="rotation">Current direction offset of this entity.</param>
|
||||
/// <param name="parentId">Current parent transform of this entity.</param>
|
||||
/// <param name="noLocalRotation"></param>
|
||||
public TransformComponentState(Vector2 localPosition, Angle rotation, EntityUid parentId, bool noLocalRotation)
|
||||
public TransformComponentState(Vector2 localPosition, Angle rotation, EntityUid parentId, bool noLocalRotation, bool anchored)
|
||||
: base(NetIDs.TRANSFORM)
|
||||
{
|
||||
LocalPosition = localPosition;
|
||||
Rotation = rotation;
|
||||
ParentID = parentId;
|
||||
NoLocalRotation = noLocalRotation;
|
||||
Anchored = anchored;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAnchored(bool value)
|
||||
{
|
||||
_anchored = value;
|
||||
Dirty();
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new AnchorStateChangedEvent());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -900,4 +960,9 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public Box2? WorldAABB { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the Anchor state of the transform is changed.
|
||||
/// </summary>
|
||||
public class AnchorStateChangedEvent : EntityEventArgs { }
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace Robust.Shared.GameObjects
|
||||
return;
|
||||
|
||||
_paused = value;
|
||||
EntityManager.EventBus.RaiseLocalEvent(Uid, new EntityPausedEvent(Uid, value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,13 +135,13 @@ namespace Robust.Shared.GameObjects
|
||||
_ => int.MaxValue
|
||||
});
|
||||
|
||||
foreach (var comp in components)
|
||||
foreach (var component in components)
|
||||
{
|
||||
if (comp == null || comp.Initialized) continue;
|
||||
var comp = (Component) component;
|
||||
if (comp.Initialized)
|
||||
continue;
|
||||
|
||||
comp.Initialize();
|
||||
|
||||
DebugTools.Assert(comp.Initialized, $"Component {comp.Name} did not call base {nameof(comp.Initialize)} in derived method.");
|
||||
comp.LifeInitialize();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@@ -173,11 +174,12 @@ namespace Robust.Shared.GameObjects
|
||||
_ => int.MaxValue
|
||||
});
|
||||
|
||||
foreach (var comp in comps)
|
||||
foreach (var component in comps)
|
||||
{
|
||||
if (comp != null && comp.Initialized && !comp.Deleted)
|
||||
var comp = (Component) component;
|
||||
if (comp.LifeStage == ComponentLifeStage.Initialized)
|
||||
{
|
||||
comp.Running = true;
|
||||
comp.LifeStartup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,15 @@ namespace Robust.Shared.GameObjects
|
||||
void SubscribeEvent<T>(EventSource source, IEntityEventSubscriber subscriber,
|
||||
EntityEventHandler<T> eventHandler) where T : notnull;
|
||||
|
||||
void SubscribeEvent<T>(
|
||||
EventSource source,
|
||||
IEntityEventSubscriber subscriber,
|
||||
EntityEventHandler<T> eventHandler,
|
||||
Type orderType,
|
||||
Type[]? before=null,
|
||||
Type[]? after=null)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes all event handlers of a given type.
|
||||
/// </summary>
|
||||
@@ -137,7 +146,7 @@ namespace Robust.Shared.GameObjects
|
||||
return;
|
||||
|
||||
// UnsubscribeEvent modifies _inverseEventSubscriptions, requires val to be cached
|
||||
foreach (var (type, (source, originalHandler, handler)) in val.ToList())
|
||||
foreach (var (type, (source, originalHandler, handler, _)) in val.ToList())
|
||||
{
|
||||
UnsubscribeEvent(source, type, originalHandler, handler, subscriber);
|
||||
}
|
||||
@@ -154,7 +163,36 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SubscribeEvent<T>(EventSource source, IEntityEventSubscriber subscriber, EntityEventHandler<T> eventHandler) where T : notnull
|
||||
public void SubscribeEvent<T>(
|
||||
EventSource source,
|
||||
IEntityEventSubscriber subscriber,
|
||||
EntityEventHandler<T> eventHandler)
|
||||
where T : notnull
|
||||
{
|
||||
SubscribeEventCommon(source, subscriber, eventHandler, null);
|
||||
}
|
||||
|
||||
public void SubscribeEvent<T>(
|
||||
EventSource source,
|
||||
IEntityEventSubscriber subscriber,
|
||||
EntityEventHandler<T> eventHandler,
|
||||
Type orderType,
|
||||
Type[]? before=null,
|
||||
Type[]? after=null)
|
||||
where T : notnull
|
||||
{
|
||||
var order = new OrderingData(orderType, before, after);
|
||||
|
||||
SubscribeEventCommon(source, subscriber, eventHandler, order);
|
||||
HandleOrderRegistration(typeof(T), order);
|
||||
}
|
||||
|
||||
private void SubscribeEventCommon<T>(
|
||||
EventSource source,
|
||||
IEntityEventSubscriber subscriber,
|
||||
EntityEventHandler<T> eventHandler,
|
||||
OrderingData? order)
|
||||
where T : notnull
|
||||
{
|
||||
if (source == EventSource.None)
|
||||
throw new ArgumentOutOfRangeException(nameof(source));
|
||||
@@ -166,7 +204,7 @@ namespace Robust.Shared.GameObjects
|
||||
throw new ArgumentNullException(nameof(subscriber));
|
||||
|
||||
var eventType = typeof(T);
|
||||
var subscriptionTuple = new Registration(source, eventHandler, ev => eventHandler((T) ev), eventHandler);
|
||||
var subscriptionTuple = new Registration(source, eventHandler, ev => eventHandler((T) ev), eventHandler, order);
|
||||
if (!_eventSubscriptions.TryGetValue(eventType, out var subscriptions))
|
||||
_eventSubscriptions.Add(eventType, new List<Registration> {subscriptionTuple});
|
||||
else if (!subscriptions.Any(p => p.Mask == source && p.Original == (Delegate) eventHandler))
|
||||
@@ -184,7 +222,6 @@ namespace Robust.Shared.GameObjects
|
||||
inverseSubscription
|
||||
);
|
||||
}
|
||||
|
||||
else if (!inverseSubscription.ContainsKey(eventType))
|
||||
{
|
||||
inverseSubscription.Add(eventType, subscriptionTuple);
|
||||
@@ -290,15 +327,13 @@ namespace Robust.Shared.GameObjects
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
_awaitingMessages.Add(type, (source, reg, tcs));
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private void UnsubscribeEvent(EventSource source, Type eventType, Delegate originalHandler, EventHandler handler, IEntityEventSubscriber subscriber)
|
||||
{
|
||||
var tuple = new Registration(source, originalHandler, handler, originalHandler);
|
||||
var tuple = new Registration(source, originalHandler, handler, originalHandler, null);
|
||||
if (_eventSubscriptions.TryGetValue(eventType, out var subscriptions) && subscriptions.Contains(tuple))
|
||||
subscriptions.Remove(tuple);
|
||||
|
||||
@@ -310,7 +345,11 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var eventType = eventArgs.GetType();
|
||||
|
||||
if (_eventSubscriptions.TryGetValue(eventType, out var subs))
|
||||
if (_orderedEvents.Contains(eventType))
|
||||
{
|
||||
ProcessSingleEventOrdered(source, eventArgs, eventType);
|
||||
}
|
||||
else if (_eventSubscriptions.TryGetValue(eventType, out var subs))
|
||||
{
|
||||
foreach (var handler in subs)
|
||||
{
|
||||
@@ -319,25 +358,20 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
if (_awaitingMessages.TryGetValue(eventType, out var awaiting))
|
||||
{
|
||||
var (mask, _, tcs) = awaiting;
|
||||
|
||||
if ((source & mask) != 0)
|
||||
{
|
||||
tcs.TrySetResult(eventArgs);
|
||||
_awaitingMessages.Remove(eventType);
|
||||
}
|
||||
}
|
||||
ProcessAwaitingMessages(source, eventArgs, eventType);
|
||||
}
|
||||
|
||||
private void ProcessSingleEvent<T>(EventSource source, T eventArgs) where T : notnull
|
||||
{
|
||||
var eventType = typeof(T);
|
||||
|
||||
if (_eventSubscriptions.TryGetValue(eventType, out var subs))
|
||||
if (_orderedEvents.Contains(eventType))
|
||||
{
|
||||
foreach (var (mask, originalHandler, _) in subs)
|
||||
ProcessSingleEventOrdered(source, eventArgs, eventType);
|
||||
}
|
||||
else if (_eventSubscriptions.TryGetValue(eventType, out var subs))
|
||||
{
|
||||
foreach (var (mask, originalHandler, _, _) in subs)
|
||||
{
|
||||
if ((mask & source) != 0)
|
||||
{
|
||||
@@ -347,6 +381,13 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
ProcessAwaitingMessages(source, eventArgs, eventType);
|
||||
}
|
||||
|
||||
// Generic here so we can avoid boxing alloc unless actually awaiting.
|
||||
private void ProcessAwaitingMessages<T>(EventSource source, T eventArgs, Type eventType)
|
||||
where T : notnull
|
||||
{
|
||||
if (_awaitingMessages.TryGetValue(eventType, out var awaiting))
|
||||
{
|
||||
var (mask1, _, tcs) = awaiting;
|
||||
@@ -366,13 +407,20 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public readonly Delegate Original;
|
||||
public readonly EventHandler Handler;
|
||||
public readonly OrderingData? Ordering;
|
||||
|
||||
public Registration(EventSource mask, Delegate original, EventHandler handler, object equalityToken)
|
||||
public Registration(
|
||||
EventSource mask,
|
||||
Delegate original,
|
||||
EventHandler handler,
|
||||
object equalityToken,
|
||||
OrderingData? ordering)
|
||||
{
|
||||
Mask = mask;
|
||||
Original = original;
|
||||
Handler = handler;
|
||||
EqualityToken = equalityToken;
|
||||
Ordering = ordering;
|
||||
}
|
||||
|
||||
public bool Equals(Registration other)
|
||||
@@ -403,11 +451,16 @@ namespace Robust.Shared.GameObjects
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
public void Deconstruct(out EventSource mask, out Delegate originalHandler, out EventHandler handler)
|
||||
public void Deconstruct(
|
||||
out EventSource mask,
|
||||
out Delegate originalHandler,
|
||||
out EventHandler handler,
|
||||
out OrderingData? order)
|
||||
{
|
||||
mask = Mask;
|
||||
originalHandler = Original;
|
||||
handler = Handler;
|
||||
order = Ordering;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
@@ -14,6 +16,12 @@ namespace Robust.Shared.GameObjects
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs;
|
||||
|
||||
void SubscribeLocalEvent<TComp, TEvent>(
|
||||
ComponentEventHandler<TComp, TEvent> handler,
|
||||
Type orderType, Type[]? before=null, Type[]? after=null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs;
|
||||
|
||||
[Obsolete("Use the overload without the handler argument.")]
|
||||
void UnsubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
|
||||
where TComp : IComponent
|
||||
@@ -61,6 +69,12 @@ namespace Robust.Shared.GameObjects
|
||||
public void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = true)
|
||||
where TEvent : EntityEventArgs
|
||||
{
|
||||
if (_orderedEvents.Contains(typeof(TEvent)))
|
||||
{
|
||||
RaiseLocalOrdered(uid, args, broadcast);
|
||||
return;
|
||||
}
|
||||
|
||||
_eventTables.Dispatch(uid, typeof(TEvent), args);
|
||||
|
||||
// we also broadcast it so the call site does not have to.
|
||||
@@ -76,7 +90,24 @@ namespace Robust.Shared.GameObjects
|
||||
void EventHandler(EntityUid uid, IComponent comp, EntityEventArgs args)
|
||||
=> handler(uid, (TComp) comp, (TEvent) args);
|
||||
|
||||
_eventTables.Subscribe(typeof(TComp), typeof(TEvent), EventHandler);
|
||||
_eventTables.Subscribe(typeof(TComp), typeof(TEvent), EventHandler, null);
|
||||
}
|
||||
|
||||
public void SubscribeLocalEvent<TComp, TEvent>(
|
||||
ComponentEventHandler<TComp, TEvent> handler,
|
||||
Type orderType,
|
||||
Type[]? before=null,
|
||||
Type[]? after=null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs
|
||||
{
|
||||
void EventHandler(EntityUid uid, IComponent comp, EntityEventArgs args)
|
||||
=> handler(uid, (TComp) comp, (TEvent) args);
|
||||
|
||||
var orderData = new OrderingData(orderType, before, after);
|
||||
|
||||
_eventTables.Subscribe(typeof(TComp), typeof(TEvent), EventHandler, orderData);
|
||||
HandleOrderRegistration(typeof(TEvent), orderData);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -105,7 +136,7 @@ namespace Robust.Shared.GameObjects
|
||||
private Dictionary<EntityUid, Dictionary<Type, HashSet<Type>>> _eventTables;
|
||||
|
||||
// EventType -> CompType -> Handler
|
||||
private Dictionary<Type, Dictionary<Type, DirectedEventHandler>> _subscriptions;
|
||||
private Dictionary<Type, Dictionary<Type, (DirectedEventHandler handler, OrderingData? ordering)>> _subscriptions;
|
||||
|
||||
// prevents shitcode, get your subscriptions figured out before you start spawning entities
|
||||
private bool _subscriptionLock;
|
||||
@@ -148,25 +179,21 @@ namespace Robust.Shared.GameObjects
|
||||
RemoveComponent(e.OwnerUid, e.Component.GetType());
|
||||
}
|
||||
|
||||
public void Subscribe(Type compType, Type eventType, DirectedEventHandler handler)
|
||||
public void Subscribe(Type compType, Type eventType, DirectedEventHandler handler, OrderingData? order)
|
||||
{
|
||||
if (_subscriptionLock)
|
||||
throw new InvalidOperationException("Subscription locked.");
|
||||
|
||||
if (!_subscriptions.TryGetValue(compType, out var compSubs))
|
||||
{
|
||||
compSubs = new Dictionary<Type, DirectedEventHandler>();
|
||||
compSubs = new Dictionary<Type, (DirectedEventHandler, OrderingData?)>();
|
||||
_subscriptions.Add(compType, compSubs);
|
||||
|
||||
compSubs.Add(eventType, handler);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (compSubs.ContainsKey(eventType))
|
||||
throw new InvalidOperationException($"Duplicate Subscriptions for comp={compType.Name}, event={eventType.Name}");
|
||||
|
||||
compSubs.Add(eventType, handler);
|
||||
}
|
||||
if (compSubs.ContainsKey(eventType))
|
||||
throw new InvalidOperationException($"Duplicate Subscriptions for comp={compType.Name}, event={eventType.Name}");
|
||||
|
||||
compSubs.Add(eventType, (handler, order));
|
||||
}
|
||||
|
||||
public void Unsubscribe(Type compType, Type eventType)
|
||||
@@ -196,7 +223,8 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var eventTable = _eventTables[euid];
|
||||
|
||||
foreach (var type in GetReferences(compType))
|
||||
var enumerator = GetReferences(compType);
|
||||
while (enumerator.MoveNext(out var type))
|
||||
{
|
||||
if (!_subscriptions.TryGetValue(type, out var compSubs))
|
||||
continue;
|
||||
@@ -218,7 +246,8 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var eventTable = _eventTables[euid];
|
||||
|
||||
foreach (var type in GetReferences(compType))
|
||||
var enumerator = GetReferences(compType);
|
||||
while (enumerator.MoveNext(out var type))
|
||||
{
|
||||
if (!_subscriptions.TryGetValue(type, out var compSubs))
|
||||
continue;
|
||||
@@ -245,25 +274,53 @@ namespace Robust.Shared.GameObjects
|
||||
if(!_subscriptions.TryGetValue(compType, out var compSubs))
|
||||
return;
|
||||
|
||||
if(!compSubs.TryGetValue(eventType, out var handler))
|
||||
if(!compSubs.TryGetValue(eventType, out var sub))
|
||||
return;
|
||||
|
||||
var (handler, _) = sub;
|
||||
var component = _entMan.ComponentManager.GetComponent(euid, compType);
|
||||
|
||||
handler(euid, component, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void CollectOrdered(
|
||||
EntityUid euid,
|
||||
Type eventType,
|
||||
List<(EventHandler, OrderingData?)> found)
|
||||
{
|
||||
var eventTable = _eventTables[euid];
|
||||
|
||||
if(!eventTable.TryGetValue(eventType, out var subscribedComps))
|
||||
return;
|
||||
|
||||
foreach (var compType in subscribedComps)
|
||||
{
|
||||
if(!_subscriptions.TryGetValue(compType, out var compSubs))
|
||||
return;
|
||||
|
||||
if(!compSubs.TryGetValue(eventType, out var sub))
|
||||
return;
|
||||
|
||||
var (handler, order) = sub;
|
||||
var component = _entMan.ComponentManager.GetComponent(euid, compType);
|
||||
|
||||
found.Add((ev => handler(euid, component, (EntityEventArgs) ev), order));
|
||||
}
|
||||
}
|
||||
|
||||
public void DispatchComponent(EntityUid euid, IComponent component, Type eventType, EntityEventArgs args)
|
||||
{
|
||||
foreach (var type in GetReferences(component.GetType()))
|
||||
var enumerator = GetReferences(component.GetType());
|
||||
while (enumerator.MoveNext(out var type))
|
||||
{
|
||||
if (!_subscriptions.TryGetValue(type, out var compSubs))
|
||||
continue;
|
||||
|
||||
if (!compSubs.TryGetValue(eventType, out var handler))
|
||||
if (!compSubs.TryGetValue(eventType, out var sub))
|
||||
continue;
|
||||
|
||||
var (handler, _) = sub;
|
||||
handler(euid, component, args);
|
||||
}
|
||||
}
|
||||
@@ -294,9 +351,49 @@ namespace Robust.Shared.GameObjects
|
||||
_subscriptions = null!;
|
||||
}
|
||||
|
||||
private IEnumerable<Type> GetReferences(Type type)
|
||||
/// <summary>
|
||||
/// Enumerates the type's component references, returning the type itself last.
|
||||
/// </summary>
|
||||
private ReferencesEnumerator GetReferences(Type type)
|
||||
{
|
||||
return _comFac.GetRegistration(type).References;
|
||||
return new(type, _comFac.GetRegistration(type).References);
|
||||
}
|
||||
|
||||
private struct ReferencesEnumerator
|
||||
{
|
||||
private readonly Type _baseType;
|
||||
private readonly IReadOnlyList<Type> _list;
|
||||
private readonly int _totalLength;
|
||||
private int _idx;
|
||||
|
||||
public ReferencesEnumerator(Type baseType, IReadOnlyList<Type> list)
|
||||
{
|
||||
_baseType = baseType;
|
||||
_list = list;
|
||||
_totalLength = list.Count;
|
||||
_idx = 0;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out Type? type)
|
||||
{
|
||||
if (_idx >= _totalLength)
|
||||
{
|
||||
if (_idx++ == _totalLength)
|
||||
{
|
||||
type = _baseType;
|
||||
return true;
|
||||
}
|
||||
|
||||
type = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
type = _list[_idx++];
|
||||
if (type == _baseType)
|
||||
return MoveNext(out type);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
95
Robust.Shared/GameObjects/EntityEventBus.Ordering.cs
Normal file
95
Robust.Shared/GameObjects/EntityEventBus.Ordering.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
internal partial class EntityEventBus
|
||||
{
|
||||
// TODO: Topological sort is currently done every time an event is emitted.
|
||||
// This should be fine for low-volume stuff like interactions, but definitely not for anything high volume.
|
||||
// Not sure if we could pre-cache the topological sort, here.
|
||||
|
||||
// Ordered event raising is slow so if this event has any ordering dependencies we use a slower path.
|
||||
private readonly HashSet<Type> _orderedEvents = new();
|
||||
|
||||
private void ProcessSingleEventOrdered(EventSource source, object eventArgs, Type eventType)
|
||||
{
|
||||
var found = new List<(EventHandler, OrderingData?)>();
|
||||
|
||||
CollectBroadcastOrdered(source, eventType, found);
|
||||
|
||||
DispatchOrderedEvents(eventArgs, found);
|
||||
}
|
||||
|
||||
private void CollectBroadcastOrdered(
|
||||
EventSource source,
|
||||
Type eventType,
|
||||
List<(EventHandler, OrderingData?)> found)
|
||||
{
|
||||
if (!_eventSubscriptions.TryGetValue(eventType, out var subs))
|
||||
return;
|
||||
|
||||
foreach (var handler in subs)
|
||||
{
|
||||
if ((handler.Mask & source) != 0)
|
||||
found.Add((handler.Handler, handler.Ordering));
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseLocalOrdered<TEvent>(
|
||||
EntityUid uid,
|
||||
TEvent args,
|
||||
bool broadcast)
|
||||
where TEvent : EntityEventArgs
|
||||
{
|
||||
var found = new List<(EventHandler, OrderingData?)>();
|
||||
|
||||
if (broadcast)
|
||||
CollectBroadcastOrdered(EventSource.Local, typeof(TEvent), found);
|
||||
|
||||
_eventTables.CollectOrdered(uid, typeof(TEvent), found);
|
||||
|
||||
DispatchOrderedEvents(args, found);
|
||||
|
||||
if (broadcast)
|
||||
ProcessAwaitingMessages(EventSource.Local, args, typeof(TEvent));
|
||||
}
|
||||
|
||||
private static void DispatchOrderedEvents(object eventArgs, List<(EventHandler, OrderingData?)> found)
|
||||
{
|
||||
var nodes = TopologicalSort.FromBeforeAfter(
|
||||
found.Where(f => f.Item2 != null),
|
||||
n => n.Item2!.OrderType,
|
||||
n => n.Item1!,
|
||||
n => n.Item2!.Before ?? Array.Empty<Type>(),
|
||||
n => n.Item2!.After ?? Array.Empty<Type>(),
|
||||
allowMissing: true);
|
||||
|
||||
foreach (var handler in TopologicalSort.Sort(nodes))
|
||||
{
|
||||
handler(eventArgs);
|
||||
}
|
||||
|
||||
// Go over all handlers that don't have ordering so weren't included in the sort.
|
||||
foreach (var (handler, orderData) in found)
|
||||
{
|
||||
if (orderData == null)
|
||||
handler(eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOrderRegistration(Type eventType, OrderingData? data)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
if (data.Before != null || data.After != null)
|
||||
_orderedEvents.Add(eventType);
|
||||
}
|
||||
|
||||
private sealed record OrderingData(Type OrderType, Type[]? Before, Type[]? After);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// The life stages of an ECS Entity.
|
||||
/// </summary>
|
||||
public enum EntityLifeStage
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Prometheus;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -79,7 +76,6 @@ namespace Robust.Shared.GameObjects
|
||||
_eventBus = new EntityEventBus(this);
|
||||
|
||||
ComponentManager.Initialize();
|
||||
_componentManager.ComponentRemoved += (sender, args) => _eventBus.UnsubscribeEvents(args.Component);
|
||||
}
|
||||
|
||||
public virtual void Startup()
|
||||
@@ -142,6 +138,11 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
#region Entity Management
|
||||
|
||||
public IEntity CreateEntityUninitialized(string? prototypeName, EntityUid? euid)
|
||||
{
|
||||
return CreateEntity(prototypeName, euid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEntity CreateEntityUninitialized(string? prototypeName)
|
||||
{
|
||||
@@ -153,9 +154,8 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var newEntity = CreateEntity(prototypeName);
|
||||
|
||||
if (TryGetEntity(coordinates.EntityId, out var entity))
|
||||
if (coordinates.IsValid(this))
|
||||
{
|
||||
newEntity.Transform.AttachParent(entity);
|
||||
newEntity.Transform.Coordinates = coordinates;
|
||||
}
|
||||
|
||||
@@ -224,12 +224,7 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
}
|
||||
|
||||
[Obsolete("IEntityQuery is obsolete")]
|
||||
public IEnumerable<IEntity> GetEntities(IEntityQuery query)
|
||||
{
|
||||
return query.Match(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntities()
|
||||
{
|
||||
// Need to do an iterator loop to avoid issues with concurrent access.
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// An entity query that will let all entities pass.
|
||||
/// This is the same as matching <c>ITransformComponent</c>, but faster.
|
||||
/// </summary>
|
||||
[PublicAPI, Obsolete]
|
||||
public class AllEntityQuery : IEntityQuery
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Match(IEntity entity) => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> Match(IEntityManager entityMan)
|
||||
{
|
||||
return entityMan.GetEntities();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity query which will match entities based on a predicate.
|
||||
/// If you only want a single type of Component, use <c>TypeEntityQuery</c>.
|
||||
/// </summary>
|
||||
[PublicAPI, Obsolete]
|
||||
public class PredicateEntityQuery : IEntityQuery
|
||||
{
|
||||
private readonly Predicate<IEntity> Predicate;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <c>PredicateEntityQuery</c>.
|
||||
/// </summary>
|
||||
/// <param name="predicate"></param>
|
||||
public PredicateEntityQuery(Predicate<IEntity> predicate)
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException(nameof(predicate));
|
||||
|
||||
Predicate = predicate;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(IEntity entity) => Predicate(entity);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> Match(IEntityManager entityMan)
|
||||
{
|
||||
return entityMan.GetEntities().Where(entity => Predicate(entity));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity query that will match one type of component.
|
||||
/// This the fastest and most common query, and should be the default choice.
|
||||
/// </summary>
|
||||
[PublicAPI, Obsolete]
|
||||
public class TypeEntityQuery : IEntityQuery
|
||||
{
|
||||
private readonly Type ComponentType;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <c>TypeEntityQuery</c>.
|
||||
/// </summary>
|
||||
/// <param name="componentType">Type of the component to match.</param>
|
||||
public TypeEntityQuery(Type componentType)
|
||||
{
|
||||
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(componentType), "componentType must inherit IComponent");
|
||||
|
||||
ComponentType = componentType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(IEntity entity) => entity.HasComponent(ComponentType);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> Match(IEntityManager entityMan)
|
||||
{
|
||||
return entityMan.ComponentManager.GetAllComponents(ComponentType, true).Select(component => component.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity query that will match one type of component.
|
||||
/// This the fastest and most common query, and should be the default choice.
|
||||
/// </summary>
|
||||
/// <typeparamref name="T">Type of component to match.</typeparamref>
|
||||
[PublicAPI, Obsolete]
|
||||
public class TypeEntityQuery<T> : IEntityQuery where T : IComponent
|
||||
{
|
||||
public bool Match(IEntity entity) => entity.HasComponent<T>();
|
||||
|
||||
public IEnumerable<IEntity> Match(IEntityManager entityMan)
|
||||
{
|
||||
return entityMan.ComponentManager.EntityQuery<T>(true).Select(component => component.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity query that will match all entities that intersect with the argument entity.
|
||||
/// </summary>
|
||||
[PublicAPI, Obsolete]
|
||||
public class IntersectingEntityQuery : IEntityQuery
|
||||
{
|
||||
private readonly IEntity Entity;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <c>TypeEntityQuery</c>.
|
||||
/// </summary>
|
||||
/// <param name="componentType">Type of the component to match.</param>
|
||||
public IntersectingEntityQuery(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(IEntity entity)
|
||||
{
|
||||
if(Entity.TryGetComponent<IPhysBody>(out var physics))
|
||||
{
|
||||
return physics.Owner.Transform.MapID == entity.Transform.MapID && physics.GetWorldAABB().Contains(entity.Transform.WorldPosition);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<IEntity> Match(IEntityManager entityMan)
|
||||
{
|
||||
return entityMan.GetEntities().Where(entity => Match(entity));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity query that will match entities that have all of the provided components.
|
||||
/// </summary>
|
||||
[PublicAPI, Obsolete]
|
||||
public class MultipleTypeEntityQuery : IEntityQuery
|
||||
{
|
||||
private readonly List<Type> ComponentTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <c>MultipleTypeEntityQuery</c>.
|
||||
/// </summary>
|
||||
/// <param name="componentTypes">List of component types to match.</param>
|
||||
public MultipleTypeEntityQuery(List<Type> componentTypes)
|
||||
{
|
||||
foreach (var componentType in componentTypes)
|
||||
{
|
||||
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(componentType), "componentType must inherit IComponent");
|
||||
}
|
||||
|
||||
ComponentTypes = componentTypes;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(IEntity entity)
|
||||
{
|
||||
foreach (var componentType in ComponentTypes)
|
||||
{
|
||||
if (!entity.HasComponent(componentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> Match(IEntityManager entityMan)
|
||||
{
|
||||
return entityMan.GetEntities(new TypeEntityQuery(ComponentTypes.First())).Where(entity => Match(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,22 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
private List<SubBase>? _subscriptions;
|
||||
|
||||
protected void SubscribeNetworkEvent<T>(EntityEventHandler<T> handler)
|
||||
// NOTE: EntityEventHandler<T> and EntitySessionEventHandler<T> CANNOT BE ORDERED BETWEEN EACH OTHER.
|
||||
|
||||
protected void SubscribeNetworkEvent<T>(
|
||||
EntityEventHandler<T> handler,
|
||||
Type[]? before=null, Type[]? after=null)
|
||||
where T : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeEvent(EventSource.Network, this, handler);
|
||||
EntityManager.EventBus.SubscribeEvent(EventSource.Network, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<T>(EventSource.Network));
|
||||
}
|
||||
|
||||
protected void SubscribeNetworkEvent<T>(EntitySessionEventHandler<T> handler)
|
||||
protected void SubscribeNetworkEvent<T>(
|
||||
EntitySessionEventHandler<T> handler,
|
||||
Type[]? before=null, Type[]? after=null)
|
||||
where T : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeSessionEvent(EventSource.Network, this, handler);
|
||||
@@ -25,16 +31,20 @@ namespace Robust.Shared.GameObjects
|
||||
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(EventSource.Network));
|
||||
}
|
||||
|
||||
protected void SubscribeLocalEvent<T>(EntityEventHandler<T> handler)
|
||||
protected void SubscribeLocalEvent<T>(
|
||||
EntityEventHandler<T> handler,
|
||||
Type[]? before=null, Type[]? after=null)
|
||||
where T : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, handler);
|
||||
EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<T>(EventSource.Local));
|
||||
}
|
||||
|
||||
protected void SubscribeLocalEvent<T>(EntitySessionEventHandler<T> handler)
|
||||
protected void SubscribeLocalEvent<T>(
|
||||
EntitySessionEventHandler<T> handler,
|
||||
Type[]? before=null, Type[]? after=null)
|
||||
where T : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeSessionEvent(EventSource.Local, this, handler);
|
||||
@@ -57,8 +67,9 @@ namespace Robust.Shared.GameObjects
|
||||
EntityManager.EventBus.UnsubscribeEvent<T>(EventSource.Local, this);
|
||||
}
|
||||
|
||||
|
||||
protected void SubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
|
||||
protected void SubscribeLocalEvent<TComp, TEvent>(
|
||||
ComponentEventHandler<TComp, TEvent> handler,
|
||||
Type[]? before=null, Type[]? after=null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
Logger.DebugS("go.sys", "Initializing entity system {0}", type);
|
||||
// Force IoC inject of all systems
|
||||
var instance = _typeFactory.CreateInstanceUnchecked<IEntitySystem>(type);
|
||||
var instance = _typeFactory.CreateInstanceUnchecked<IEntitySystem>(type, oneOff: true);
|
||||
|
||||
_systems.Add(type, instance);
|
||||
|
||||
@@ -166,15 +166,15 @@ namespace Robust.Shared.GameObjects
|
||||
Dictionary<Type, IEntitySystem>.ValueCollection systems,
|
||||
Dictionary<Type, IEntitySystem> supertypeSystems)
|
||||
{
|
||||
var allNodes = new List<GraphNode<IEntitySystem>>();
|
||||
var typeToNode = new Dictionary<Type, GraphNode<IEntitySystem>>();
|
||||
var allNodes = new List<TopologicalSort.GraphNode<IEntitySystem>>();
|
||||
var typeToNode = new Dictionary<Type, TopologicalSort.GraphNode<IEntitySystem>>();
|
||||
|
||||
foreach (var system in systems)
|
||||
{
|
||||
var node = new GraphNode<IEntitySystem>(system);
|
||||
var node = new TopologicalSort.GraphNode<IEntitySystem>(system);
|
||||
|
||||
allNodes.Add(node);
|
||||
typeToNode.Add(system.GetType(), node);
|
||||
allNodes.Add(node);
|
||||
}
|
||||
|
||||
foreach (var (type, system) in supertypeSystems)
|
||||
@@ -183,52 +183,30 @@ namespace Robust.Shared.GameObjects
|
||||
typeToNode[type] = node;
|
||||
}
|
||||
|
||||
foreach (var node in allNodes)
|
||||
foreach (var node in typeToNode.Values)
|
||||
{
|
||||
foreach (var after in node.System.UpdatesAfter)
|
||||
foreach (var after in node.Value.UpdatesAfter)
|
||||
{
|
||||
var system = typeToNode[after];
|
||||
|
||||
node.DependsOn.Add(system);
|
||||
system.Dependant.Add(node);
|
||||
}
|
||||
|
||||
foreach (var before in node.System.UpdatesBefore)
|
||||
foreach (var before in node.Value.UpdatesBefore)
|
||||
{
|
||||
var system = typeToNode[before];
|
||||
|
||||
system.DependsOn.Add(node);
|
||||
node.Dependant.Add(system);
|
||||
}
|
||||
}
|
||||
|
||||
var order = TopologicalSort(allNodes).Select(p => p.System).ToArray();
|
||||
var order = TopologicalSort.Sort(allNodes).ToArray();
|
||||
var frameUpdate = order.Where(p => NeedsFrameUpdate(p.GetType()));
|
||||
var update = order.Where(p => NeedsUpdate(p.GetType()));
|
||||
|
||||
return (frameUpdate, update);
|
||||
}
|
||||
|
||||
internal static IEnumerable<GraphNode<T>> TopologicalSort<T>(IEnumerable<GraphNode<T>> nodes)
|
||||
{
|
||||
var elems = nodes.ToDictionary(node => node,
|
||||
node => new HashSet<GraphNode<T>>(node.DependsOn));
|
||||
while (elems.Count > 0)
|
||||
{
|
||||
var elem =
|
||||
elems.FirstOrDefault(x => x.Value.Count == 0);
|
||||
if (elem.Key == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Found circular dependency when resolving entity system update dependency graph");
|
||||
}
|
||||
elems.Remove(elem.Key);
|
||||
foreach (var selem in elems)
|
||||
{
|
||||
selem.Value.Remove(elem.Key);
|
||||
}
|
||||
yield return elem.Key;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetBaseTypes(Type type) {
|
||||
if(type.BaseType == null) return type.GetInterfaces();
|
||||
|
||||
@@ -344,18 +322,6 @@ namespace Robust.Shared.GameObjects
|
||||
return mFrameUpdate!.DeclaringType != typeof(EntitySystem);
|
||||
}
|
||||
|
||||
[DebuggerDisplay("GraphNode: {" + nameof(System) + "}")]
|
||||
internal sealed class GraphNode<T>
|
||||
{
|
||||
public readonly T System;
|
||||
public readonly List<GraphNode<T>> DependsOn = new();
|
||||
|
||||
public GraphNode(T system)
|
||||
{
|
||||
System = system;
|
||||
}
|
||||
}
|
||||
|
||||
private struct UpdateReg
|
||||
{
|
||||
[ViewVariables] public IEntitySystem System;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user