mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95ba58f0a4 | ||
|
|
f780f04784 | ||
|
|
85782bda92 | ||
|
|
14a01df5b1 | ||
|
|
644da60bfc | ||
|
|
8c83999ad2 | ||
|
|
24b9fc9eec | ||
|
|
ba40185179 | ||
|
|
8b013cb424 | ||
|
|
b67d24efee | ||
|
|
d992e47f30 | ||
|
|
dadd7b4cc3 | ||
|
|
baef2bc7f8 | ||
|
|
e0b1a7d64a | ||
|
|
aea5f83002 | ||
|
|
7df2d1f430 | ||
|
|
d216c3a1f6 | ||
|
|
986ec3ef06 | ||
|
|
60cec9cb84 | ||
|
|
c06707d519 | ||
|
|
63128324ab | ||
|
|
abea3024b4 | ||
|
|
07dafeb6cd | ||
|
|
a726d42ae3 | ||
|
|
d02d186a2f |
@@ -4,4 +4,8 @@ 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
|
||||
entity-spawn-window-override-menu-tooltip = Override placement
|
||||
|
||||
## Console
|
||||
|
||||
console-line-edit-placeholder = Command Here
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
@@ -46,13 +46,15 @@ namespace Robust.Client
|
||||
IoCManager.Register<IClientMapManager, ClientMapManager>();
|
||||
IoCManager.Register<IEntityManager, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityLookup, EntityLookup>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
|
||||
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
IoCManager.Register<GameController, GameController>();
|
||||
IoCManager.Register<IGameController, GameController>();
|
||||
IoCManager.Register<IGameControllerInternal, GameController>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
IoCManager.Register<IResourceManager, ResourceCache>();
|
||||
IoCManager.Register<IResourceManagerInternal, ResourceCache>();
|
||||
IoCManager.Register<IResourceCache, ResourceCache>();
|
||||
@@ -72,8 +74,6 @@ namespace Robust.Client
|
||||
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
|
||||
IoCManager.Register<ILightManager, LightManager>();
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
switch (mode)
|
||||
|
||||
@@ -49,7 +49,11 @@ namespace Robust.Client.Console
|
||||
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
|
||||
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
|
||||
|
||||
Reset();
|
||||
_requestedCommands = false;
|
||||
NetManager.Connected += OnNetworkConnected;
|
||||
|
||||
LoadConsoleCommands();
|
||||
SendServerCommandRequest();
|
||||
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
|
||||
}
|
||||
|
||||
@@ -61,17 +65,6 @@ namespace Robust.Client.Console
|
||||
ExecuteCommand(null, text);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
AvailableCommands.Clear();
|
||||
_requestedCommands = false;
|
||||
NetManager.Connected += OnNetworkConnected;
|
||||
|
||||
LoadConsoleCommands();
|
||||
SendServerCommandRequest();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<AddStringArgs>? AddString;
|
||||
|
||||
@@ -97,7 +90,7 @@ namespace Robust.Client.Console
|
||||
return;
|
||||
|
||||
// echo the command locally
|
||||
WriteError(null, "> " + command);
|
||||
WriteLine(null, "> " + command);
|
||||
|
||||
//Commands are processed locally and then sent to the server to be processed there again.
|
||||
var args = new List<string>();
|
||||
@@ -142,6 +135,9 @@ namespace Robust.Client.Console
|
||||
private void OutputText(string text, bool local, bool error)
|
||||
{
|
||||
AddString?.Invoke(this, new AddStringArgs(text, local, error));
|
||||
|
||||
var level = error ? LogLevel.Warning : LogLevel.Info;
|
||||
Logger.LogS(level, "CON", text);
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Robust.Client.Console.Commands
|
||||
message.Append($"net ID: {registration.NetID}");
|
||||
}
|
||||
|
||||
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
|
||||
message.Append($", References:");
|
||||
|
||||
shell.WriteLine(message.ToString());
|
||||
|
||||
|
||||
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
|
||||
@@ -11,11 +11,6 @@ namespace Robust.Client.Console
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the console to a post-initialized state.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
event EventHandler<AddStringArgs> AddString;
|
||||
event EventHandler<AddFormattedMessageArgs> AddFormatted;
|
||||
|
||||
|
||||
@@ -13,10 +13,11 @@ using Microsoft.CodeAnalysis.Text;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@@ -116,7 +117,7 @@ namespace Robust.Client.Console
|
||||
}
|
||||
else
|
||||
{
|
||||
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
|
||||
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager).AddReferences(typeof(Image).Assembly);
|
||||
newScript = CSharpScript.Create(code, options, typeof(ScriptGlobals));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace Robust.Client
|
||||
#if FULL_RELEASE
|
||||
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
|
||||
#else
|
||||
GameController.Start(args, true);
|
||||
GameController.Start(args, new GameControllerOptions(), true);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void StartLibrary(string[] args, GameControllerOptions options)
|
||||
{
|
||||
GameController.Start(args, true, null, options);
|
||||
GameController.Start(args, options, true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,8 +177,9 @@ namespace Robust.Client.Debugging
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
var shape = fixture.Shape;
|
||||
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
|
||||
var sleepPercent = physBody.Awake ? 0.0f : 1.0f;
|
||||
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
}
|
||||
|
||||
foreach (var joint in physBody.Joints)
|
||||
@@ -187,6 +188,7 @@ namespace Robust.Client.Debugging
|
||||
drawnJoints.Add(joint);
|
||||
|
||||
joint.DebugDraw(drawing, in viewport);
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
}
|
||||
|
||||
if (worldBox.Contains(mouseWorldPos))
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client
|
||||
{
|
||||
public void Main(IMainArgs args)
|
||||
{
|
||||
Start(args.Args, contentStart: false, args);
|
||||
Start(args.Args, new GameControllerOptions(), contentStart: false, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace Robust.Client
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Start(args);
|
||||
Start(args, new GameControllerOptions());
|
||||
}
|
||||
|
||||
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null, GameControllerOptions? options = null)
|
||||
public static void Start(string[] args, GameControllerOptions options, bool contentStart = false, IMainArgs? loaderArgs=null)
|
||||
{
|
||||
if (_hasStarted)
|
||||
{
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions? options)
|
||||
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions options)
|
||||
{
|
||||
IoCManager.InitThread();
|
||||
|
||||
@@ -51,15 +51,13 @@ namespace Robust.Client
|
||||
var gc = IoCManager.Resolve<GameController>();
|
||||
gc.SetCommandLineArgs(args);
|
||||
gc._loaderArgs = loaderArgs;
|
||||
if(options != null)
|
||||
gc.Options = options;
|
||||
|
||||
// When the game is ran with the startup executable being content,
|
||||
// we have to disable the separate load context.
|
||||
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
|
||||
gc.ContentStart = contentStart;
|
||||
|
||||
gc.Run(mode);
|
||||
gc.Run(mode, options);
|
||||
}
|
||||
|
||||
public void OverrideMainLoop(IGameLoop gameLoop)
|
||||
@@ -67,9 +65,9 @@ namespace Robust.Client
|
||||
_mainLoop = gameLoop;
|
||||
}
|
||||
|
||||
public void Run(DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null)
|
||||
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
|
||||
{
|
||||
if (!StartupSystemSplash(logHandlerFactory))
|
||||
if (!StartupSystemSplash(options, logHandlerFactory))
|
||||
{
|
||||
Logger.Fatal("Failed to start game controller!");
|
||||
return;
|
||||
|
||||
@@ -75,8 +75,6 @@ namespace Robust.Client
|
||||
public GameControllerOptions Options { get; private set; } = new();
|
||||
public InitialLaunchState LaunchState { get; private set; } = default!;
|
||||
|
||||
public bool LoadConfigAndUserData { get; set; } = true;
|
||||
|
||||
public void SetCommandLineArgs(CommandLineArgs args)
|
||||
{
|
||||
_commandLineArgs = args;
|
||||
@@ -95,7 +93,7 @@ namespace Robust.Client
|
||||
_modLoader.SetUseLoadContext(!ContentStart);
|
||||
_modLoader.SetEnableSandboxing(Options.Sandboxing);
|
||||
|
||||
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), Options.ContentModulePrefix))
|
||||
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, Options.ContentModulePrefix))
|
||||
{
|
||||
Logger.Fatal("Errors while loading content assemblies.");
|
||||
return false;
|
||||
@@ -196,8 +194,9 @@ namespace Robust.Client
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
|
||||
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
|
||||
{
|
||||
Options = options;
|
||||
ReadInitialLaunchState();
|
||||
|
||||
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()));
|
||||
@@ -234,7 +233,7 @@ namespace Robust.Client
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
|
||||
|
||||
if (LoadConfigAndUserData)
|
||||
if (Options.LoadConfigAndUserData)
|
||||
{
|
||||
var configFile = Path.Combine(userDataDir, Options.ConfigFileName);
|
||||
if (File.Exists(configFile))
|
||||
@@ -258,13 +257,13 @@ namespace Robust.Client
|
||||
|
||||
ProfileOptSetup.Setup(_configurationManager);
|
||||
|
||||
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
|
||||
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
|
||||
_loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
|
||||
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
|
||||
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
|
||||
|
||||
if (_loaderArgs != null)
|
||||
{
|
||||
|
||||
@@ -42,6 +42,11 @@ namespace Robust.Client
|
||||
/// </summary>
|
||||
public string ContentBuildDirectory { get; init; } = "Content.Client";
|
||||
|
||||
/// <summary>
|
||||
/// Directory to load all assemblies from.
|
||||
/// </summary>
|
||||
public ResourcePath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
|
||||
|
||||
/// <summary>
|
||||
/// Directory to load all prototypes from.
|
||||
/// </summary>
|
||||
@@ -52,6 +57,16 @@ namespace Robust.Client
|
||||
/// </summary>
|
||||
public bool ResourceMountDisabled { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to mount content resources when not on FULL_RELEASE.
|
||||
/// </summary>
|
||||
public bool LoadContentResources { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to load config and user data.
|
||||
/// </summary>
|
||||
public bool LoadConfigAndUserData { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to disable command line args server auto-connecting.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class ClientComponentFactory : ComponentFactory
|
||||
internal class ClientComponentFactory : ComponentFactory
|
||||
{
|
||||
public ClientComponentFactory()
|
||||
public ClientComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
|
||||
: base(typeFactory, reflectionManager, conHost)
|
||||
{
|
||||
// Required for the engine to work
|
||||
RegisterIgnore("KeyBindingInput");
|
||||
|
||||
@@ -109,13 +109,15 @@ namespace Robust.Client.GameObjects
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
|
||||
{
|
||||
if (!component.NetID.HasValue)
|
||||
var netId = ComponentFactory.GetRegistration(component.GetType()).NetID;
|
||||
|
||||
if (!netId.HasValue)
|
||||
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
|
||||
|
||||
var msg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
msg.Type = EntityMessageType.ComponentMessage;
|
||||
msg.EntityUid = entity.Uid;
|
||||
msg.NetId = component.NetID.Value;
|
||||
msg.NetId = netId.Value;
|
||||
msg.ComponentMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@ 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.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -15,12 +18,12 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IPointLightComponent))]
|
||||
[NetworkedComponent()]
|
||||
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
public override string Name => "PointLight";
|
||||
public override uint? NetID => NetIDs.POINT_LIGHT;
|
||||
|
||||
internal bool TreeUpdateQueued { get; set; }
|
||||
|
||||
@@ -168,6 +171,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));
|
||||
}
|
||||
@@ -181,17 +186,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()
|
||||
{
|
||||
|
||||
@@ -131,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
|
||||
@@ -374,7 +365,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath);
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
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;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
|
||||
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return new Box2(pos, pos);
|
||||
}
|
||||
|
||||
private static Box2 LightAabbFunc(in PointLightComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
var boxSize = value.Radius * 2;
|
||||
|
||||
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return Box2.CenteredAround(pos, (boxSize, boxSize));
|
||||
}
|
||||
|
||||
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
|
||||
{
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
|
||||
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return new Box2(pos, pos);
|
||||
}
|
||||
|
||||
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
|
||||
{
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
var boxSize = value.Radius * 2;
|
||||
|
||||
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return Box2.CenteredAround(pos, (boxSize, boxSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,25 +21,48 @@ 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();
|
||||
|
||||
private HashSet<EntityUid> _checkedChildren = new();
|
||||
|
||||
internal DynamicTree<SpriteComponent> GetSpriteTreeForMap(MapId map, GridId grid)
|
||||
/// <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;
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -48,9 +74,7 @@ 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);
|
||||
@@ -65,6 +89,11 @@ namespace Robust.Client.GameObjects
|
||||
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)
|
||||
@@ -88,12 +117,12 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
// 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<MapGridComponent>() ||
|
||||
sender.Owner.HasComponent<MapComponent>()) return;
|
||||
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);
|
||||
|
||||
@@ -129,16 +158,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)
|
||||
@@ -173,16 +196,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)
|
||||
@@ -198,31 +215,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)
|
||||
@@ -232,35 +241,30 @@ 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)
|
||||
internal static RenderingTreeComponent? GetRenderTree(IEntity entity)
|
||||
{
|
||||
var gridTree = _gridTrees[mapId][gridId];
|
||||
if (entity.Transform.MapID == MapId.Nullspace ||
|
||||
entity.HasComponent<RenderingTreeComponent>()) return null;
|
||||
|
||||
foreach (var sprite in gridTree.SpriteTree)
|
||||
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)
|
||||
@@ -270,55 +274,42 @@ namespace Robust.Client.GameObjects
|
||||
foreach (var sprite in _spriteQueue)
|
||||
{
|
||||
sprite.TreeUpdateQueued = false;
|
||||
var mapId = sprite.Owner.Transform.MapID;
|
||||
|
||||
if (!sprite.Visible || sprite.ContainerOccluded)
|
||||
{
|
||||
ClearSprite(sprite);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (sprite.IntersectingMapId != mapId)
|
||||
var oldMapTree = sprite.RenderTree;
|
||||
var newMapTree = GetRenderTree(sprite.Owner);
|
||||
// TODO: Temp PVS guard
|
||||
var worldPos = sprite.Owner.Transform.WorldPosition;
|
||||
|
||||
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
|
||||
{
|
||||
ClearSprite(sprite);
|
||||
continue;
|
||||
}
|
||||
|
||||
sprite.IntersectingMapId = mapId;
|
||||
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos);
|
||||
|
||||
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 we're on a new map then clear the old one.
|
||||
if (oldMapTree != newMapTree)
|
||||
{
|
||||
if (intersectingGrids.Contains(gridId)) continue;
|
||||
mapTree[gridId].SpriteTree.Remove(sprite);
|
||||
ClearSprite(sprite);
|
||||
newMapTree?.SpriteTree.Add(sprite, aabb);
|
||||
}
|
||||
|
||||
// Rebuild in the update below
|
||||
sprite.IntersectingGrids.Clear();
|
||||
|
||||
// Update / add to new
|
||||
foreach (var gridId in intersectingGrids)
|
||||
else
|
||||
{
|
||||
var translated = aabb.Translated(gridId == GridId.Invalid
|
||||
? Vector2.Zero
|
||||
: -_mapManager.GetGrid(gridId).WorldPosition);
|
||||
|
||||
mapTree[gridId].SpriteTree.AddOrUpdate(sprite, translated);
|
||||
|
||||
sprite.IntersectingGrids.Add(gridId);
|
||||
newMapTree?.SpriteTree.Update(sprite, aabb);
|
||||
}
|
||||
|
||||
sprite.RenderTree = newMapTree;
|
||||
}
|
||||
|
||||
foreach (var light in _lightQueue)
|
||||
{
|
||||
light.TreeUpdateQueued = false;
|
||||
var mapId = light.Owner.Transform.MapID;
|
||||
|
||||
if (!light.Enabled || light.ContainerOccluded)
|
||||
{
|
||||
@@ -326,72 +317,44 @@ namespace Robust.Client.GameObjects
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (light.IntersectingMapId != mapId)
|
||||
var oldMapTree = light.RenderTree;
|
||||
var newMapTree = GetRenderTree(light.Owner);
|
||||
// TODO: Temp PVS guard
|
||||
var worldPos = light.Owner.Transform.WorldPosition;
|
||||
|
||||
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
|
||||
{
|
||||
ClearLight(light);
|
||||
continue;
|
||||
}
|
||||
|
||||
light.IntersectingMapId = mapId;
|
||||
|
||||
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 light.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)
|
||||
{
|
||||
if (intersectingGrids.Contains(gridId)) continue;
|
||||
mapTree[gridId].LightTree.Remove(light);
|
||||
Logger.WarningS(LoggerSawmill, $"Light radius for {light.Owner} set above max radius of {MaxLightRadius}. This may lead to pop-in.");
|
||||
}
|
||||
|
||||
// Rebuild in the update below
|
||||
light.IntersectingGrids.Clear();
|
||||
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos);
|
||||
|
||||
// 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].LightTree.AddOrUpdate(light, translated);
|
||||
light.IntersectingGrids.Add(gridId);
|
||||
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
|
||||
|
||||
@@ -50,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)
|
||||
{
|
||||
@@ -65,7 +63,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
value.FrameUpdate(state);
|
||||
return true;
|
||||
}, gridBounds, approx: true);
|
||||
}, bounds, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
@@ -25,6 +26,7 @@ using Robust.Shared.Utility;
|
||||
namespace Robust.Client.GameStates
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[UsedImplicitly]
|
||||
public class ClientGameStateManager : IClientGameStateManager
|
||||
{
|
||||
private GameStateProcessor _processor = default!;
|
||||
@@ -36,6 +38,8 @@ namespace Robust.Client.GameStates
|
||||
_pendingSystemMessages
|
||||
= new();
|
||||
|
||||
private uint _metaCompNetId;
|
||||
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
@@ -99,6 +103,12 @@ namespace Robust.Client.GameStates
|
||||
Predicting = _config.GetCVar(CVars.NetPredict);
|
||||
PredictTickBias = _config.GetCVar(CVars.NetPredictTickBias);
|
||||
PredictLagBias = _config.GetCVar(CVars.NetPredictLagBias);
|
||||
|
||||
var metaId = _compFactory.GetRegistration(typeof(MetaDataComponent)).NetID;
|
||||
if (!metaId.HasValue)
|
||||
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
|
||||
|
||||
_metaCompNetId = metaId.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -210,6 +220,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;
|
||||
|
||||
@@ -338,11 +353,11 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
// TODO: handle component deletions/creations.
|
||||
foreach (var comp in _componentManager.GetNetComponents(entity.Uid))
|
||||
foreach (var (netId, comp) in _componentManager.GetNetComponents(entity.Uid))
|
||||
{
|
||||
DebugTools.AssertNotNull(comp.NetID);
|
||||
DebugTools.AssertNotNull(netId);
|
||||
|
||||
if (comp.LastModifiedTick < curTick || !last.TryGetValue(comp.NetID!.Value, out var compState))
|
||||
if (comp.LastModifiedTick < curTick || !last.TryGetValue(netId, out var compState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -363,24 +378,22 @@ namespace Robust.Client.GameStates
|
||||
// so that we can later roll back to it (if necessary).
|
||||
var outputData = new Dictionary<EntityUid, Dictionary<uint, ComponentState>>();
|
||||
|
||||
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
|
||||
var player = _players.LocalPlayer.Session;
|
||||
|
||||
foreach (var createdEntity in createdEntities)
|
||||
{
|
||||
var compData = new Dictionary<uint, ComponentState>();
|
||||
outputData.Add(createdEntity, compData);
|
||||
|
||||
foreach (var component in _componentManager.GetNetComponents(createdEntity))
|
||||
foreach (var (netId, component) in _componentManager.GetNetComponents(createdEntity))
|
||||
{
|
||||
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
|
||||
|
||||
var player = _players.LocalPlayer.Session;
|
||||
var state = component.GetComponentState(player);
|
||||
|
||||
if (state.GetType() == typeof(ComponentState))
|
||||
{
|
||||
if(state.GetType() == typeof(ComponentState))
|
||||
continue;
|
||||
}
|
||||
|
||||
compData.Add(state.NetID, state);
|
||||
compData.Add(netId, state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,16 +435,17 @@ 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
|
||||
{
|
||||
var metaState = (MetaDataComponentState?) es.ComponentStates
|
||||
?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA);
|
||||
var metaState = (MetaDataComponentState?) es.ComponentChanges?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
|
||||
if (metaState == null)
|
||||
{
|
||||
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);
|
||||
@@ -471,6 +485,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var id in deletions)
|
||||
{
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] DELETE {id}");
|
||||
_entities.DeleteEntity(id);
|
||||
}
|
||||
|
||||
@@ -535,7 +550,7 @@ namespace Robust.Client.GameStates
|
||||
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityEventBus bus, EntityState? curState,
|
||||
EntityState? nextState)
|
||||
{
|
||||
var compStateWork = new Dictionary<uint, (ComponentState? curState, ComponentState? nextState)>();
|
||||
var compStateWork = new Dictionary<ushort, (ComponentState? curState, ComponentState? nextState)>();
|
||||
var entityUid = entity.Uid;
|
||||
|
||||
if (curState?.ComponentChanges != null)
|
||||
@@ -551,42 +566,46 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
else
|
||||
{
|
||||
//Right now we just assume every state from an unseen entity is added
|
||||
|
||||
if (compMan.HasComponent(entityUid, compChange.NetID))
|
||||
continue;
|
||||
|
||||
var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!);
|
||||
var newComp = (Component) _compFactory.GetComponent(compChange.NetID);
|
||||
newComp.Owner = entity;
|
||||
compMan.AddComponent(entity, newComp, true);
|
||||
|
||||
compStateWork[compChange.NetID] = (compChange.State, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (curState?.ComponentStates != null)
|
||||
if (curState?.ComponentChanges != null)
|
||||
{
|
||||
foreach (var compState in curState.ComponentStates)
|
||||
foreach (var compChange in curState.ComponentChanges)
|
||||
{
|
||||
compStateWork[compState.NetID] = (compState, null);
|
||||
compStateWork[compChange.NetID] = (compChange.State, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextState?.ComponentStates != null)
|
||||
if (nextState?.ComponentChanges != null)
|
||||
{
|
||||
foreach (var compState in nextState.ComponentStates)
|
||||
foreach (var compState in nextState.ComponentChanges)
|
||||
{
|
||||
if (compStateWork.TryGetValue(compState.NetID, out var state))
|
||||
{
|
||||
compStateWork[compState.NetID] = (state.curState, compState);
|
||||
compStateWork[compState.NetID] = (state.curState, compState.State);
|
||||
}
|
||||
else
|
||||
{
|
||||
compStateWork[compState.NetID] = (null, compState);
|
||||
compStateWork[compState.NetID] = (null, compState.State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (netId, (cur, next)) in compStateWork)
|
||||
{
|
||||
if (compMan.TryGetComponent(entityUid, netId, out var component))
|
||||
if (compMan.TryGetComponent(entityUid, (ushort) netId, out var component))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -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.
|
||||
@@ -198,14 +195,10 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
compData.Remove(change.NetID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entityState.ComponentStates != null)
|
||||
{
|
||||
foreach (var compState in entityState.ComponentStates)
|
||||
{
|
||||
compData[compState.NetID] = compState;
|
||||
else if (change.State is not null)
|
||||
{
|
||||
compData[change.NetID] = change.State;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
|
||||
private const int HistorySize = 60 * 3; // number of ticks to keep in history.
|
||||
private const int TargetPayloadBps = 56000 / 8; // Target Payload size in Bytes per second. A mind-numbing fifty-six thousand bits per second, who would ever need more?
|
||||
@@ -90,17 +91,14 @@ namespace Robust.Client.GameStates
|
||||
sb.Append($"\n Changes:");
|
||||
foreach (var compChange in entState.ComponentChanges)
|
||||
{
|
||||
var del = compChange.Deleted ? 'D' : 'C';
|
||||
sb.Append($"\n [{del}]{compChange.NetID}:{compChange.ComponentName}");
|
||||
}
|
||||
}
|
||||
var registration = _componentFactory.GetRegistration(compChange.NetID);
|
||||
var create = compChange.Created ? 'C' : '\0';
|
||||
var mod = !(compChange.Created || compChange.Created) ? 'M' : '\0';
|
||||
var del = compChange.Deleted ? 'D' : '\0';
|
||||
sb.Append($"\n [{create}{mod}{del}]{compChange.NetID}:{registration.Name}");
|
||||
|
||||
if (entState.ComponentStates is not null)
|
||||
{
|
||||
sb.Append($"\n States:");
|
||||
foreach (var compState in entState.ComponentStates)
|
||||
{
|
||||
sb.Append($"\n {compState.NetID}:{compState.GetType().Name}");
|
||||
if(compChange.State is not null)
|
||||
sb.Append($"\n STATE:{compChange.State.GetType().Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,24 +366,11 @@ 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) =>
|
||||
{
|
||||
@@ -398,7 +385,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
entry.yWorldPos = worldPos.Y;
|
||||
return true;
|
||||
|
||||
}), gridBounds, approx: true);
|
||||
}), bounds, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -494,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;
|
||||
|
||||
@@ -535,7 +527,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
|
||||
|
||||
return true;
|
||||
}, gridBounds);
|
||||
}, bounds);
|
||||
}
|
||||
|
||||
if (state.count > _maxLightsPerScene)
|
||||
|
||||
@@ -210,11 +210,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_windowing!.WindowRequestAttention(_windowing.MainWindow!);
|
||||
}
|
||||
|
||||
public async Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
|
||||
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
|
||||
return await _windowing!.WindowCreate(parameters);
|
||||
return _windowing!.WindowCreate(parameters);
|
||||
}
|
||||
|
||||
private void DoDestroyWindow(WindowReg reg)
|
||||
|
||||
@@ -210,7 +210,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
yield break;
|
||||
}
|
||||
|
||||
public Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
|
||||
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
|
||||
{
|
||||
var window = new DummyWindow(CreateRenderTarget((123, 123), default))
|
||||
{
|
||||
@@ -218,7 +218,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
};
|
||||
_windows.Add(window);
|
||||
|
||||
return Task.FromResult<IClydeWindow>(window);
|
||||
return window;
|
||||
}
|
||||
|
||||
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)
|
||||
|
||||
@@ -22,8 +22,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
// Wait for it.
|
||||
private sealed partial class GlfwWindowingImpl
|
||||
private unsafe sealed partial class GlfwWindowingImpl
|
||||
{
|
||||
private readonly List<GlfwWindowReg> _windows = new();
|
||||
|
||||
@@ -36,45 +35,23 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private int _nextWindowId = 1;
|
||||
private static bool _eglLoaded;
|
||||
|
||||
public async Task<WindowHandle> WindowCreate(WindowCreateParameters parameters)
|
||||
public WindowHandle WindowCreate(WindowCreateParameters parameters)
|
||||
{
|
||||
// tfw await not allowed in unsafe contexts
|
||||
|
||||
// GL APIs don't take kindly to making a new window without unbinding the main context. Great.
|
||||
// Leaving code for async path in, in case it works on like GLX.
|
||||
var unbindContextAndBlock = true;
|
||||
|
||||
DebugTools.AssertNotNull(_mainWindow);
|
||||
|
||||
Task<GlfwWindowCreateResult> task;
|
||||
unsafe
|
||||
{
|
||||
if (unbindContextAndBlock)
|
||||
GLFW.MakeContextCurrent(null);
|
||||
GLFW.MakeContextCurrent(null);
|
||||
|
||||
task = SharedWindowCreate(
|
||||
_clyde._chosenRenderer,
|
||||
parameters,
|
||||
_mainWindow!.GlfwWindow);
|
||||
}
|
||||
var task = SharedWindowCreate(
|
||||
_clyde._chosenRenderer,
|
||||
parameters,
|
||||
_mainWindow!.GlfwWindow);
|
||||
|
||||
if (unbindContextAndBlock)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
// Block the main thread (to avoid stuff like texture uploads being problematic).
|
||||
WaitWindowCreate(task);
|
||||
// Block the main thread (to avoid stuff like texture uploads being problematic).
|
||||
WaitWindowCreate(task);
|
||||
|
||||
if (unbindContextAndBlock)
|
||||
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await task;
|
||||
}
|
||||
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
|
||||
|
||||
var (reg, error) = await task;
|
||||
var (reg, error) = task.Result;
|
||||
|
||||
if (reg == null)
|
||||
{
|
||||
@@ -85,18 +62,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.CreateWindowRenderTexture(reg);
|
||||
_clyde.InitWindowBlitThread(reg);
|
||||
|
||||
unsafe
|
||||
{
|
||||
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
|
||||
}
|
||||
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
|
||||
|
||||
return reg.Handle;
|
||||
}
|
||||
}
|
||||
|
||||
// Yes, you read that right.
|
||||
private sealed unsafe partial class GlfwWindowingImpl
|
||||
{
|
||||
public bool TryInitMainWindow(Renderer renderer, [NotNullWhen(false)] out string? error)
|
||||
{
|
||||
var width = _cfg.GetCVar(CVars.DisplayWidth);
|
||||
@@ -167,6 +137,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
WindowCreateParameters parameters,
|
||||
Window* share)
|
||||
{
|
||||
//
|
||||
// IF YOU'RE WONDERING WHY THIS IS TASK-BASED:
|
||||
// I originally wanted this to be async so we could avoid blocking the main thread
|
||||
// while the OS takes its stupid 100~ms just to initialize a fucking GL context.
|
||||
// This doesn't *work* because
|
||||
// we have to release the GL context while the shared context is being created.
|
||||
// (at least on WGL, I didn't test other platforms and I don't care to.)
|
||||
// Not worth it to avoid a main thread blockage by allowing Clyde to temporarily release the GL context,
|
||||
// because rendering would be locked up *anyways*.
|
||||
//
|
||||
// Basically what I'm saying is that everything about OpenGL is a fucking mistake
|
||||
// and I should get on either Veldrid or Vulkan some time.
|
||||
// Probably Veldrid tbh.
|
||||
//
|
||||
|
||||
// Yes we ping-pong this TCS through the window thread and back, deal with it.
|
||||
var tcs = new TaskCompletionSource<GlfwWindowCreateResult>();
|
||||
SendCmd(new CmdWinCreate(
|
||||
@@ -525,7 +510,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
void WindowRequestAttention(WindowReg window);
|
||||
void WindowSwapBuffers(WindowReg window);
|
||||
uint? WindowGetX11Id(WindowReg window);
|
||||
Task<WindowHandle> WindowCreate(WindowCreateParameters parameters);
|
||||
WindowHandle WindowCreate(WindowCreateParameters parameters);
|
||||
void WindowDestroy(WindowReg reg);
|
||||
|
||||
string KeyGetName(Keyboard.Key key);
|
||||
|
||||
@@ -130,6 +130,6 @@ namespace Robust.Client.Graphics
|
||||
|
||||
IEnumerable<IClydeMonitor> EnumerateMonitors();
|
||||
|
||||
Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters);
|
||||
IClydeWindow CreateWindow(WindowCreateParameters parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,7 @@ namespace Robust.Client
|
||||
GameControllerOptions Options { get; }
|
||||
bool ContentStart { get; set; }
|
||||
void SetCommandLineArgs(CommandLineArgs args);
|
||||
bool LoadConfigAndUserData { get; set; }
|
||||
void Run(GameController.DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null);
|
||||
void Run(GameController.DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null);
|
||||
void KeyDown(KeyEventArgs keyEvent);
|
||||
void KeyUp(KeyEventArgs keyEvent);
|
||||
void TextEntered(TextEventArgs textEvent);
|
||||
|
||||
@@ -32,12 +32,12 @@ namespace Robust.Client.Map
|
||||
//get shared euid of map comp entity
|
||||
foreach (var entityState in entityStates!)
|
||||
{
|
||||
if(entityState.ComponentStates is null)
|
||||
if(entityState.ComponentChanges is null)
|
||||
continue;
|
||||
|
||||
foreach (var compState in entityState.ComponentStates)
|
||||
foreach (var compChange in entityState.ComponentChanges)
|
||||
{
|
||||
if (compState is not MapComponentState mapCompState || mapCompState.MapId != mapId)
|
||||
if (compChange.State is not MapComponentState mapCompState || mapCompState.MapId != mapId)
|
||||
continue;
|
||||
|
||||
mapEuid = entityState.Uid;
|
||||
@@ -67,12 +67,12 @@ namespace Robust.Client.Map
|
||||
//get shared euid of map comp entity
|
||||
foreach (var entityState in entityStates!)
|
||||
{
|
||||
if(entityState.ComponentStates is null)
|
||||
if (entityState.ComponentChanges is null)
|
||||
continue;
|
||||
|
||||
foreach (var compState in entityState.ComponentStates)
|
||||
foreach (var compState in entityState.ComponentChanges)
|
||||
{
|
||||
if (compState is not MapGridComponentState gridCompState || gridCompState.GridIndex != gridId)
|
||||
if (compState.State is not MapGridComponentState gridCompState || gridCompState.GridIndex != gridId)
|
||||
continue;
|
||||
|
||||
gridEuid = entityState.Uid;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
@@ -17,7 +18,7 @@ namespace Robust.Client.Map
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
private Texture? _tileTextureAtlas;
|
||||
|
||||
|
||||
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
|
||||
|
||||
private readonly Dictionary<ushort, Box2> _tileRegions = new();
|
||||
@@ -61,7 +62,7 @@ namespace Robust.Client.Map
|
||||
var row = i / dimensionX;
|
||||
|
||||
Image<Rgba32> image;
|
||||
using (var stream = _resourceCache.ContentFileRead($"/Textures/Constructible/Tiles/{def.SpriteName}.png"))
|
||||
using (var stream = _resourceCache.ContentFileRead(Path.Join(def.Path, $"{def.SpriteName}.png")))
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
|
||||
@@ -6,20 +6,31 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// A container that lays out its children sequentially.
|
||||
/// Use <see cref="VBoxContainer"/> or <see cref="HBoxContainer"/> for an implementation.
|
||||
/// </summary>
|
||||
public abstract class BoxContainer : Container
|
||||
public class BoxContainer : Container
|
||||
{
|
||||
private LayoutOrientation _orientation;
|
||||
public const string StylePropertySeparation = "separation";
|
||||
|
||||
private const int DefaultSeparation = 0;
|
||||
private protected abstract bool Vertical { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies "where" the controls should be laid out.
|
||||
/// </summary>
|
||||
public AlignMode Align { get; set; }
|
||||
|
||||
private bool Vertical => Orientation == LayoutOrientation.Vertical;
|
||||
|
||||
public LayoutOrientation Orientation
|
||||
{
|
||||
get => _orientation;
|
||||
set
|
||||
{
|
||||
_orientation = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
private int ActualSeparation
|
||||
{
|
||||
get
|
||||
@@ -237,5 +248,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
End = 2
|
||||
}
|
||||
|
||||
public enum LayoutOrientation : byte
|
||||
{
|
||||
Horizontal,
|
||||
Vertical
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
public Label Label { get; }
|
||||
|
||||
public Button() : base()
|
||||
public Button()
|
||||
{
|
||||
AddStyleClass(StyleClassButton);
|
||||
Label = new Label
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public Label Label { get; }
|
||||
public TextureRect TextureRect { get; }
|
||||
|
||||
public CheckBox() : base()
|
||||
public CheckBox()
|
||||
{
|
||||
ToggleMode = true;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public const string StylePseudoClassHover = "hover";
|
||||
public const string StylePseudoClassDisabled = "disabled";
|
||||
|
||||
public ContainerButton() : base()
|
||||
public ContainerButton()
|
||||
{
|
||||
DrawModeChanged();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Container that lays its children out horizontally: from left to right.
|
||||
/// </summary>
|
||||
[Obsolete("Use BoxContainer and set Orientation instead")]
|
||||
public class HBoxContainer : BoxContainer
|
||||
{
|
||||
private protected override bool Vertical => false;
|
||||
public HBoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
[Obsolete("Use SplitContainer directly and set Orientation")]
|
||||
public class HSplitContainer : SplitContainer
|
||||
{
|
||||
private protected sealed override bool Vertical => false;
|
||||
public HSplitContainer()
|
||||
{
|
||||
Orientation = SplitOrientation.Horizontal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public event EventHandler<ValueChangedEventArgs>? ValueChanged;
|
||||
|
||||
public SpinBox() : base()
|
||||
public SpinBox()
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
public abstract class SplitContainer : Container
|
||||
public class SplitContainer : Container
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines how user-initiated moving of the split should work. See documentation
|
||||
@@ -22,11 +22,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
public float SplitEdgeSeparation { get; set; }
|
||||
|
||||
private protected abstract bool Vertical { get; }
|
||||
|
||||
private float _splitCenter;
|
||||
private SplitState _splitState;
|
||||
private bool _dragging;
|
||||
private SplitOrientation _orientation;
|
||||
|
||||
// min / max x and y extents in relative virtual pixels of where the split can go regardless
|
||||
// of anything else.
|
||||
@@ -35,6 +34,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private float SplitMax =>
|
||||
Vertical ? Height - (SplitWidth + SplitEdgeSeparation) : Width - (SplitWidth + SplitEdgeSeparation);
|
||||
|
||||
private bool Vertical => Orientation == SplitOrientation.Vertical;
|
||||
|
||||
public SplitOrientation Orientation
|
||||
{
|
||||
get => _orientation;
|
||||
set
|
||||
{
|
||||
_orientation = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
public SplitContainer()
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
@@ -264,5 +275,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
Manual = 1
|
||||
}
|
||||
|
||||
public enum SplitOrientation : byte
|
||||
{
|
||||
Horizontal,
|
||||
Vertical
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Container that lays its children out vertically: from top to bottom.
|
||||
/// </summary>
|
||||
[Obsolete("Use BoxContainer and set Orientation instead")]
|
||||
public class VBoxContainer : BoxContainer
|
||||
{
|
||||
private protected override bool Vertical => true;
|
||||
public VBoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
[Obsolete("Use SplitContainer directly and set Orientation")]
|
||||
public class VSplitContainer : SplitContainer
|
||||
{
|
||||
private protected sealed override bool Vertical => true;
|
||||
public VSplitContainer()
|
||||
{
|
||||
Orientation = SplitOrientation.Vertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Robust.Client.UserInterface
|
||||
monitor = clyde.EnumerateMonitors().Single(m => m.Id == id);
|
||||
}
|
||||
|
||||
var window = await clyde.CreateWindow(new WindowCreateParameters
|
||||
var window = clyde.CreateWindow(new WindowCreateParameters
|
||||
{
|
||||
Maximized = true,
|
||||
Title = "SS14 Debug Window",
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace Robust.Client.ViewVariables
|
||||
var valArg = args[0];
|
||||
if (valArg.StartsWith("SI"))
|
||||
{
|
||||
if (valArg.StartsWith("SIoC"))
|
||||
valArg = valArg.Substring(4);
|
||||
|
||||
// Server-side IoC selector.
|
||||
var selector = new ViewVariablesIoCSelector(valArg.Substring(1));
|
||||
vvm.OpenVV(selector);
|
||||
@@ -41,6 +44,9 @@ namespace Robust.Client.ViewVariables
|
||||
|
||||
if (valArg.StartsWith("I"))
|
||||
{
|
||||
if (valArg.StartsWith("IoC"))
|
||||
valArg = valArg.Substring(3);
|
||||
|
||||
// Client-side IoC selector.
|
||||
var reflection = IoCManager.Resolve<IReflectionManager>();
|
||||
if (!reflection.TryLooseGetType(valArg, out var type))
|
||||
|
||||
@@ -102,6 +102,8 @@ namespace Robust.Server
|
||||
|
||||
private readonly ManualResetEventSlim _shutdownEvent = new(false);
|
||||
|
||||
public ServerOptions Options { get; private set; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public int MaxPlayers => _config.GetCVar(CVars.GameMaxPlayers);
|
||||
|
||||
@@ -114,7 +116,7 @@ namespace Robust.Server
|
||||
Logger.InfoS("srv", "Restarting Server...");
|
||||
|
||||
Cleanup();
|
||||
Start(_logHandlerFactory);
|
||||
Start(Options, _logHandlerFactory);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -141,15 +143,16 @@ namespace Robust.Server
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Start(Func<ILogHandler>? logHandlerFactory = null)
|
||||
public bool Start(ServerOptions options, Func<ILogHandler>? logHandlerFactory = null)
|
||||
{
|
||||
Options = options;
|
||||
var profilePath = Path.Join(Environment.CurrentDirectory, "AAAAAAAA");
|
||||
ProfileOptimization.SetProfileRoot(profilePath);
|
||||
ProfileOptimization.StartProfile("AAAAAAAAAA");
|
||||
|
||||
_config.Initialize(true);
|
||||
|
||||
if (LoadConfigAndUserData)
|
||||
if (Options.LoadConfigAndUserData)
|
||||
{
|
||||
// Sets up the configMgr
|
||||
// If a config file path was passed, use it literally.
|
||||
@@ -269,22 +272,26 @@ namespace Robust.Server
|
||||
return true;
|
||||
}
|
||||
|
||||
var dataDir = LoadConfigAndUserData
|
||||
var dataDir = Options.LoadConfigAndUserData
|
||||
? _commandLineArgs?.DataDir ?? PathHelpers.ExecutableRelativeFile("data")
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir);
|
||||
|
||||
ProgramShared.DoMounts(_resources, _commandLineArgs?.MountOptions, "Content.Server", contentStart:ContentStart);
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
ProgramShared.DoMounts(_resources, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
|
||||
Options.LoadContentResources, Options.ResourceMountDisabled, ContentStart);
|
||||
|
||||
// When the game is ran with the startup executable being content,
|
||||
// we have to disable the separate load context.
|
||||
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
|
||||
_modLoader.SetUseLoadContext(!ContentStart);
|
||||
_modLoader.SetEnableSandboxing(false);
|
||||
_modLoader.SetEnableSandboxing(Options.Sandboxing);
|
||||
|
||||
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
|
||||
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, Options.ContentModulePrefix))
|
||||
{
|
||||
Logger.Fatal("Errors while loading content assemblies.");
|
||||
return true;
|
||||
@@ -335,13 +342,23 @@ namespace Robust.Server
|
||||
// otherwise the prototypes will be cleared
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
prototypeManager.Initialize();
|
||||
prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes"));
|
||||
prototypeManager.LoadDirectory(Options.PrototypeDirectory);
|
||||
prototypeManager.Resync();
|
||||
|
||||
IoCManager.Resolve<IServerConsoleHost>().Initialize();
|
||||
_entityManager.Startup();
|
||||
IoCManager.Resolve<IEntityLookup>().Startup();
|
||||
_stateManager.Initialize();
|
||||
|
||||
// sometime after content init
|
||||
{
|
||||
var reg = _entityManager.ComponentFactory.GetRegistration<TransformComponent>();
|
||||
if (!reg.NetID.HasValue)
|
||||
throw new InvalidOperationException("TransformComponent does not have a NetId.");
|
||||
|
||||
_stateManager.SetTransformNetId(reg.NetID.Value);
|
||||
}
|
||||
|
||||
_scriptHost.Initialize();
|
||||
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
|
||||
@@ -492,7 +509,6 @@ namespace Robust.Server
|
||||
}
|
||||
|
||||
public bool ContentStart { get; set; }
|
||||
public bool LoadConfigAndUserData { private get; set; } = true;
|
||||
|
||||
public void OverrideMainLoop(IGameLoop gameLoop)
|
||||
{
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace Robust.Server
|
||||
#if FULL_RELEASE
|
||||
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
|
||||
#else
|
||||
Program.Start(args, true);
|
||||
Program.Start(args, new ServerOptions(), true);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void StartLibrary(string[] args)
|
||||
public static void StartLibrary(string[] args, ServerOptions options)
|
||||
{
|
||||
Program.Start(args, true);
|
||||
Program.Start(args, options, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Players;
|
||||
@@ -10,6 +11,7 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IPointLightComponent))]
|
||||
[NetworkedComponent()]
|
||||
public class PointLightComponent : Component, IPointLightComponent
|
||||
{
|
||||
[DataField("color")]
|
||||
@@ -22,7 +24,6 @@ namespace Robust.Server.GameObjects
|
||||
private Vector2 _offset = Vector2.Zero;
|
||||
|
||||
public override string Name => "PointLight";
|
||||
public override uint? NetID => NetIDs.POINT_LIGHT;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Color Color
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
using JetBrains.Annotations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class PhysicsSystem : SharedPhysicsSystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_mapManager.OnGridCreated += HandleGridCreated;
|
||||
SubscribeLocalEvent<GridInitializeEvent>(HandleGridInit);
|
||||
LoadMetricCVar();
|
||||
_configurationManager.OnValueChanged(CVars.MetricsEnabled, _ => LoadMetricCVar());
|
||||
}
|
||||
@@ -28,19 +26,14 @@ namespace Robust.Server.GameObjects
|
||||
MetricsEnabled = _configurationManager.GetCVar(CVars.MetricsEnabled);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
private void HandleGridInit(GridInitializeEvent ev)
|
||||
{
|
||||
base.Shutdown();
|
||||
_mapManager.OnGridCreated -= HandleGridCreated;
|
||||
}
|
||||
var guid = ev.EntityUid;
|
||||
|
||||
private void HandleGridCreated(MapId mapId, GridId gridId)
|
||||
{
|
||||
if (!EntityManager.TryGetEntity(_mapManager.GetGrid(gridId).GridEntityId, out var gridEntity)) return;
|
||||
var grid = _mapManager.GetGrid(gridId);
|
||||
var collideComp = gridEntity.AddComponent<PhysicsComponent>();
|
||||
if (!EntityManager.TryGetEntity(guid, out var gridEntity)) return;
|
||||
var collideComp = gridEntity.EnsureComponent<PhysicsComponent>();
|
||||
collideComp.CanCollide = true;
|
||||
collideComp.AddFixture(new Fixture(collideComp, new PhysShapeGrid(grid)) {CollisionMask = MapGridHelpers.CollisionGroup, CollisionLayer = MapGridHelpers.CollisionGroup});
|
||||
collideComp.BodyType = BodyType.Static;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
public class ServerComponentFactory : ComponentFactory
|
||||
internal class ServerComponentFactory : ComponentFactory
|
||||
{
|
||||
public ServerComponentFactory()
|
||||
public ServerComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
|
||||
: base(typeFactory, reflectionManager, conHost)
|
||||
{
|
||||
RegisterIgnore("Input");
|
||||
RegisterIgnore("AnimationPlayer");
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Robust.Server.GameObjects
|
||||
// As such, we can reset the modified ticks to Zero,
|
||||
// which indicates "not different from client's own deserialization".
|
||||
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
|
||||
foreach (var component in ComponentManager.GetNetComponents(entity.Uid))
|
||||
foreach (var (netId, component) in ComponentManager.GetNetComponents(entity.Uid))
|
||||
{
|
||||
// Make sure to ONLY get components that are defined in the prototype.
|
||||
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
|
||||
@@ -143,13 +143,15 @@ namespace Robust.Server.GameObjects
|
||||
if (_networkManager.IsClient)
|
||||
return;
|
||||
|
||||
if (!component.NetID.HasValue)
|
||||
var netId = ComponentFactory.GetRegistration(component.GetType()).NetID;
|
||||
|
||||
if (!netId.HasValue)
|
||||
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
|
||||
|
||||
var msg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
msg.Type = EntityMessageType.ComponentMessage;
|
||||
msg.EntityUid = entity.Uid;
|
||||
msg.NetId = component.NetID.Value;
|
||||
msg.NetId = netId.Value;
|
||||
msg.ComponentMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ namespace Robust.Server.GameStates
|
||||
private readonly ObjectPool<HashSet<EntityUid>> _viewerEntsPool
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new DefaultPooledObjectPolicy<HashSet<EntityUid>>(), MaxVisPoolSize);
|
||||
|
||||
private ushort _transformNetId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Is view culling enabled, or will we send the whole map?
|
||||
/// </summary>
|
||||
@@ -58,6 +60,11 @@ namespace Robust.Server.GameStates
|
||||
_lookup = lookup;
|
||||
}
|
||||
|
||||
public void SetTransformNetId(ushort value)
|
||||
{
|
||||
_transformNetId = value;
|
||||
}
|
||||
|
||||
// Not thread safe
|
||||
public void EntityDeleted(EntityUid e)
|
||||
{
|
||||
@@ -204,17 +211,14 @@ namespace Robust.Server.GameStates
|
||||
var oldState = (TransformComponent.TransformComponentState) xform.GetComponentState(session);
|
||||
|
||||
entityStates.Add(new EntityState(entityUid,
|
||||
new ComponentChanged[]
|
||||
new[]
|
||||
{
|
||||
new(false, NetIDs.TRANSFORM, "Transform")
|
||||
},
|
||||
new ComponentState[]
|
||||
{
|
||||
new TransformComponent.TransformComponentState(Vector2NaN,
|
||||
oldState.Rotation,
|
||||
oldState.ParentID,
|
||||
oldState.NoLocalRotation,
|
||||
oldState.Anchored)
|
||||
ComponentChange.Changed(_transformNetId,
|
||||
new TransformComponent.TransformComponentState(Vector2NaN,
|
||||
oldState.Rotation,
|
||||
oldState.ParentID,
|
||||
oldState.NoLocalRotation,
|
||||
oldState.Anchored)),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,5 +17,6 @@ namespace Robust.Server.GameStates
|
||||
|
||||
bool PvsEnabled { get; set; }
|
||||
float PvsRange { get; set; }
|
||||
void SetTransformNetId(ushort netId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Map;
|
||||
using Robust.Server.Player;
|
||||
@@ -21,6 +22,7 @@ using Robust.Shared.Utility;
|
||||
namespace Robust.Server.GameStates
|
||||
{
|
||||
/// <inheritdoc cref="IServerGameStateManager"/>
|
||||
[UsedImplicitly]
|
||||
public class ServerGameStateManager : IServerGameStateManager, IPostInjectInit
|
||||
{
|
||||
// Mapping of net UID of clients -> last known acked state.
|
||||
@@ -53,6 +55,11 @@ namespace Robust.Server.GameStates
|
||||
set => _configurationManager.SetCVar(CVars.NetMaxUpdateRange, value);
|
||||
}
|
||||
|
||||
public void SetTransformNetId(ushort netId)
|
||||
{
|
||||
_entityView.SetTransformNetId(netId);
|
||||
}
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
_logger = Logger.GetSawmill("PVS");
|
||||
@@ -249,38 +256,44 @@ namespace Robust.Server.GameStates
|
||||
/// <returns>New entity State for the given entity.</returns>
|
||||
internal static EntityState GetEntityState(IComponentManager compMan, ICommonSession player, EntityUid entityUid, GameTick fromTick)
|
||||
{
|
||||
var compStates = new List<ComponentState>();
|
||||
var changed = new List<ComponentChanged>();
|
||||
var changed = new List<ComponentChange>();
|
||||
|
||||
foreach (var comp in compMan.GetNetComponents(entityUid))
|
||||
foreach (var (netId, component) in compMan.GetNetComponents(entityUid))
|
||||
{
|
||||
DebugTools.Assert(comp.Initialized);
|
||||
DebugTools.Assert(component.Initialized);
|
||||
|
||||
// NOTE: When LastModifiedTick or CreationTick are 0 it means that the relevant data is
|
||||
// "not different from entity creation".
|
||||
// i.e. when the client spawns the entity and loads the entity prototype,
|
||||
// the data it deserializes from the prototype SHOULD be equal
|
||||
// to what the component state / ComponentChanged would send.
|
||||
// to what the component state / ComponentChange would send.
|
||||
// As such, we can avoid sending this data in this case since the client "already has it".
|
||||
|
||||
if (comp.NetSyncEnabled && comp.LastModifiedTick != GameTick.Zero && comp.LastModifiedTick >= fromTick)
|
||||
compStates.Add(comp.GetComponentState(player));
|
||||
DebugTools.Assert(component.LastModifiedTick >= component.CreationTick);
|
||||
|
||||
if (comp.CreationTick != GameTick.Zero && comp.CreationTick >= fromTick && !comp.Deleted)
|
||||
if (component.CreationTick != GameTick.Zero && component.CreationTick >= fromTick && !component.Deleted)
|
||||
{
|
||||
ComponentState? state = null;
|
||||
if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero && component.LastModifiedTick >= fromTick)
|
||||
state = component.GetComponentState(player);
|
||||
|
||||
// Can't be null since it's returned by GetNetComponents
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
changed.Add(ComponentChanged.Added(comp.NetID!.Value, comp.Name));
|
||||
changed.Add(ComponentChange.Added(netId, state));
|
||||
}
|
||||
else if (comp.Deleted && comp.LastModifiedTick >= fromTick)
|
||||
else if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero && component.LastModifiedTick >= fromTick)
|
||||
{
|
||||
changed.Add(ComponentChange.Changed(netId, component.GetComponentState(player)));
|
||||
}
|
||||
else if (component.Deleted && component.LastModifiedTick >= fromTick)
|
||||
{
|
||||
// Can't be null since it's returned by GetNetComponents
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
changed.Add(ComponentChanged.Removed(comp.NetID!.Value));
|
||||
changed.Add(ComponentChange.Removed(netId));
|
||||
}
|
||||
}
|
||||
|
||||
return new EntityState(entityUid, changed.ToArray(), compStates.ToArray());
|
||||
return new EntityState(entityUid, changed.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Robust.Server
|
||||
/// Sets up the server, loads the game, gets ready for client connections.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool Start(Func<ILogHandler>? logHandler = null);
|
||||
bool Start(ServerOptions options, Func<ILogHandler>? logHandler = null);
|
||||
|
||||
/// <summary>
|
||||
/// Hard restarts the server, shutting it down, kicking all players, and starting the server again.
|
||||
@@ -44,11 +44,10 @@ namespace Robust.Server
|
||||
|
||||
internal interface IBaseServerInternal : IBaseServer
|
||||
{
|
||||
ServerOptions Options { get; }
|
||||
bool ContentStart { set; }
|
||||
bool LoadConfigAndUserData { set; }
|
||||
|
||||
void OverrideMainLoop(IGameLoop gameLoop);
|
||||
|
||||
void SetCommandLineArgs(CommandLineArgs args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ namespace Robust.Server.Maps
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var component in _componentManager.GetNetComponents(entity.Uid))
|
||||
foreach (var (netId, component) in _componentManager.GetNetComponents(entity.Uid))
|
||||
{
|
||||
var castComp = (Component) component;
|
||||
|
||||
|
||||
136
Robust.Server/Physics/GridFixtureSystem.cs
Normal file
136
Robust.Server/Physics/GridFixtureSystem.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Robust.Server.Physics
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles generating fixtures for MapGrids.
|
||||
/// </summary>
|
||||
internal sealed class GridFixtureSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadphase = default!;
|
||||
|
||||
// Is delaying fixture updates a good idea? IDEK. We definitely can't do them on every tile changed
|
||||
// because if someone changes 50 tiles that will kill perf. We could probably just run it every Update
|
||||
// (and at specific times due to race condition stuff).
|
||||
// At any rate, cooldown given here if someone wants it. CD of 0 just runs it every tick.
|
||||
private float _cooldown;
|
||||
private float _accumulator;
|
||||
|
||||
private HashSet<MapChunk> _queuedChunks = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(PhysicsSystem));
|
||||
SubscribeLocalEvent<RegenerateChunkCollisionEvent>(HandleCollisionRegenerate);
|
||||
_broadphase = Get<SharedBroadPhaseSystem>();
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.GridFixtureUpdateRate, value => _cooldown = value, true);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
_accumulator += frameTime;
|
||||
if (_accumulator < _cooldown) return;
|
||||
|
||||
_accumulator -= _cooldown;
|
||||
Process();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Go through every dirty chunk and re-generate their fixtures.
|
||||
/// </summary>
|
||||
public void Process()
|
||||
{
|
||||
foreach (var chunk in _queuedChunks)
|
||||
{
|
||||
RegenerateCollision(chunk);
|
||||
}
|
||||
|
||||
_queuedChunks.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue the chunk to generate (if cooldown > 0) or immediately process it.
|
||||
/// </summary>
|
||||
/// <param name="ev"></param>
|
||||
private void HandleCollisionRegenerate(RegenerateChunkCollisionEvent ev)
|
||||
{
|
||||
if (_cooldown <= 0f)
|
||||
{
|
||||
RegenerateCollision(ev.Chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
_queuedChunks.Add(ev.Chunk);
|
||||
}
|
||||
|
||||
private void RegenerateCollision(MapChunk chunk)
|
||||
{
|
||||
// Currently this is gonna be hella simple.
|
||||
if (!_mapManager.TryGetGrid(chunk.GridId, out var grid) ||
|
||||
!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt) ||
|
||||
!gridEnt.TryGetComponent(out PhysicsComponent? physicsComponent)) return;
|
||||
|
||||
// TODO: Lots of stuff here etc etc, make changes to mapgridchunk.
|
||||
var bounds = chunk.CalcLocalBounds();
|
||||
|
||||
// So something goes on with the chunk's internal bounds caching where if there's no data the bound is 0 or something?
|
||||
if (bounds.IsEmpty()) return;
|
||||
|
||||
var origin = chunk.Indices * chunk.ChunkSize;
|
||||
bounds = bounds.Translated(origin);
|
||||
|
||||
var oldFixture = chunk.Fixture;
|
||||
|
||||
var newFixture = new Fixture(
|
||||
new PolygonShape
|
||||
{
|
||||
Vertices = new List<Vector2>
|
||||
{
|
||||
bounds.BottomRight,
|
||||
bounds.TopRight,
|
||||
bounds.TopLeft,
|
||||
bounds.BottomLeft,
|
||||
}
|
||||
},
|
||||
MapGridHelpers.CollisionGroup,
|
||||
MapGridHelpers.CollisionGroup,
|
||||
true) {ID = $"grid-{grid.Index}_chunk-{chunk.Indices.X}-{chunk.Indices.Y}",
|
||||
Body = physicsComponent};
|
||||
|
||||
// TODO: Chunk will likely need multiple fixtures but future sloth problem lmao fucking dickhead
|
||||
if (oldFixture?.Equals(newFixture) == true) return;
|
||||
|
||||
if (oldFixture != null)
|
||||
physicsComponent.RemoveFixture(oldFixture);
|
||||
|
||||
physicsComponent.AddFixture(newFixture);
|
||||
chunk.Fixture = newFixture;
|
||||
|
||||
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Uid,new GridFixtureChangeEvent {OldFixture = oldFixture, NewFixture = newFixture});
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GridFixtureChangeEvent : EntityEventArgs
|
||||
{
|
||||
public Fixture? OldFixture { get; init; }
|
||||
public Fixture? NewFixture { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,10 @@ namespace Robust.Server
|
||||
|
||||
internal static void Main(string[] args)
|
||||
{
|
||||
Start(args);
|
||||
Start(args, new ServerOptions());
|
||||
}
|
||||
|
||||
internal static void Start(string[] args, bool contentStart = false)
|
||||
internal static void Start(string[] args, ServerOptions options, bool contentStart = false)
|
||||
{
|
||||
if (_hasStarted)
|
||||
{
|
||||
@@ -47,11 +47,11 @@ namespace Robust.Server
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskContinuationOptions.None,
|
||||
RobustTaskScheduler.Instance
|
||||
).StartNew(() => ParsedMain(parsed, contentStart))
|
||||
).StartNew(() => ParsedMain(parsed, contentStart, options))
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static void ParsedMain(CommandLineArgs args, bool contentStart)
|
||||
private static void ParsedMain(CommandLineArgs args, bool contentStart, ServerOptions options)
|
||||
{
|
||||
Thread.CurrentThread.Name = "Main Thread";
|
||||
IoCManager.InitThread();
|
||||
@@ -67,7 +67,7 @@ namespace Robust.Server
|
||||
|
||||
Logger.Info("Server -> Starting");
|
||||
|
||||
if (server.Start())
|
||||
if (server.Start(options))
|
||||
{
|
||||
Logger.Fatal("Server -> Can not start server");
|
||||
//Not like you'd see this, haha. Perhaps later for logging.
|
||||
|
||||
@@ -39,10 +39,11 @@ namespace Robust.Server
|
||||
IoCManager.Register<IBaseServerInternal, BaseServer>();
|
||||
IoCManager.Register<BaseServer, BaseServer>();
|
||||
IoCManager.Register<IGameTiming, GameTiming>();
|
||||
IoCManager.Register<IReflectionManager, ServerReflectionManager>();
|
||||
IoCManager.Register<IConsoleHost, ServerConsoleHost>();
|
||||
IoCManager.Register<IServerConsoleHost, ServerConsoleHost>();
|
||||
IoCManager.Register<IComponentFactory, ServerComponentFactory>();
|
||||
IoCManager.Register<IConGroupController, ConGroupController>();
|
||||
IoCManager.Register<IServerConsoleHost, ServerConsoleHost>();
|
||||
IoCManager.Register<IConsoleHost, ServerConsoleHost>();
|
||||
IoCManager.Register<IMapManager, ServerMapManager>();
|
||||
IoCManager.Register<IMapManagerInternal, ServerMapManager>();
|
||||
IoCManager.Register<IServerMapManager, ServerMapManager>();
|
||||
@@ -55,7 +56,6 @@ namespace Robust.Server
|
||||
IoCManager.Register<IPlayerManager, PlayerManager>();
|
||||
IoCManager.Register<ISharedPlayerManager, PlayerManager>();
|
||||
IoCManager.Register<IPrototypeManager, ServerPrototypeManager>();
|
||||
IoCManager.Register<IReflectionManager, ServerReflectionManager>();
|
||||
IoCManager.Register<IResourceManager, ResourceManager>();
|
||||
IoCManager.Register<IResourceManagerInternal, ResourceManager>();
|
||||
IoCManager.Register<IServerEntityManager, ServerEntityManager>();
|
||||
|
||||
54
Robust.Server/ServerOptions.cs
Normal file
54
Robust.Server/ServerOptions.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server
|
||||
{
|
||||
public class ServerOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether content sandboxing will be enabled & enforced.
|
||||
/// </summary>
|
||||
public bool Sandboxing { get; init; } = false;
|
||||
|
||||
// TODO: Expose mounting methods to games using Robust as a library.
|
||||
/// <summary>
|
||||
/// Lists of mount options to mount.
|
||||
/// </summary>
|
||||
public MountOptions MountOptions { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Assemblies with this prefix will be loaded.
|
||||
/// </summary>
|
||||
public string ContentModulePrefix { get; init; } = "Content.";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the content build directory, for game pack mounting purposes.
|
||||
/// </summary>
|
||||
public string ContentBuildDirectory { get; init; } = "Content.Server";
|
||||
|
||||
/// <summary>
|
||||
/// Directory to load all assemblies from.
|
||||
/// </summary>
|
||||
public ResourcePath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
|
||||
|
||||
/// <summary>
|
||||
/// Directory to load all prototypes from.
|
||||
/// </summary>
|
||||
public ResourcePath PrototypeDirectory { get; init; } = new(@"/Prototypes/");
|
||||
|
||||
/// <summary>
|
||||
/// Whether to disable mounting the "Resources/" folder on FULL_RELEASE.
|
||||
/// </summary>
|
||||
public bool ResourceMountDisabled { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to mount content resources when not on FULL_RELEASE.
|
||||
/// </summary>
|
||||
public bool LoadContentResources { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to load config and user data.
|
||||
/// </summary>
|
||||
public bool LoadConfigAndUserData { get; init; } = true;
|
||||
}
|
||||
}
|
||||
@@ -316,7 +316,7 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static float Area(in Box2 box)
|
||||
public static float Area(in Box2 box)
|
||||
=> box.Width * box.Height;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -149,6 +149,26 @@ namespace Robust.Shared.Maths
|
||||
return new Box2(X0, Y0, X1, Y1);
|
||||
}
|
||||
|
||||
public bool Contains(Vector2 worldPoint)
|
||||
{
|
||||
// Get the worldpoint in our frame of reference so we can do a faster AABB check.
|
||||
var localPoint = GetLocalPoint(worldPoint);
|
||||
return Box.Contains(localPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a point in world-space coordinates to our local coordinates.
|
||||
/// </summary>
|
||||
private Vector2 GetLocalPoint(Vector2 point)
|
||||
{
|
||||
// Could make this more efficient but works for now I guess...
|
||||
var boxCenter = Box.Center;
|
||||
|
||||
var result = point - boxCenter;
|
||||
result = Origin + Rotation.RotateVec(result - Origin);
|
||||
return result + boxCenter;
|
||||
}
|
||||
|
||||
#region Equality
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -66,6 +66,11 @@ namespace Robust.Shared.Maths
|
||||
return xOk && yOk;
|
||||
}
|
||||
|
||||
public readonly bool IsEmpty()
|
||||
{
|
||||
return Bottom == Top || Left == Right;
|
||||
}
|
||||
|
||||
/// <summary>Returns a UIBox2 translated by the given amount.</summary>
|
||||
public readonly Box2i Translated(Vector2i point)
|
||||
{
|
||||
|
||||
@@ -211,6 +211,32 @@ 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);
|
||||
|
||||
/*
|
||||
* Lookup
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Like MaxLightRadius this is how far we enlarge lookups to find intersecting components.
|
||||
/// This should be set to your maximum entity size.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> LookupEnlargementRange =
|
||||
CVarDef.Create("lookup.enlargement_range", 10.0f, CVar.ARCHIVE | CVar.REPLICATED | CVar.CHEAT);
|
||||
|
||||
/*
|
||||
* LOKI
|
||||
*/
|
||||
@@ -436,6 +462,13 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> MaxAngVelocity =
|
||||
CVarDef.Create("physics.maxangvelocity", 15f);
|
||||
|
||||
/// <summary>
|
||||
/// How frequently grid fixtures are updated. Given grid updates can be expensive they aren't run immediately.
|
||||
/// Set to 0 to run them immediately.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> GridFixtureUpdateRate =
|
||||
CVarDef.Create("physics.grid_fixture_update_rate", 0.2f);
|
||||
|
||||
/*
|
||||
* DISCORD
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -17,6 +18,7 @@ namespace Robust.Shared.Containers
|
||||
/// Holds data about a set of entity containers on this entity.
|
||||
/// </summary>
|
||||
[ComponentReference(typeof(IContainerManager))]
|
||||
[NetworkedComponent()]
|
||||
public class ContainerManagerComponent : Component, IContainerManager
|
||||
{
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
@@ -29,9 +31,6 @@ namespace Robust.Shared.Containers
|
||||
/// <inheritdoc />
|
||||
public sealed override string Name => "ContainerContainer";
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override uint? NetID => NetIDs.CONTAINER_MANAGER;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnRemove()
|
||||
{
|
||||
@@ -281,7 +280,7 @@ namespace Robust.Shared.Containers
|
||||
{
|
||||
public List<ContainerData> ContainerSet;
|
||||
|
||||
public ContainerManagerComponentState(List<ContainerData> containers) : base(NetIDs.CONTAINER_MANAGER)
|
||||
public ContainerManagerComponentState(List<ContainerData> containers)
|
||||
{
|
||||
ContainerSet = containers;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
@@ -18,14 +19,6 @@ namespace Robust.Shared.GameObjects
|
||||
[ViewVariables]
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public virtual uint? NetID => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public virtual bool NetworkSynchronizeExistence => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
[DataField("netsync")]
|
||||
@@ -249,14 +242,16 @@ namespace Robust.Shared.GameObjects
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public virtual void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) { }
|
||||
|
||||
private static readonly ComponentState DefaultComponentState = new();
|
||||
|
||||
/// <param name="player"></param>
|
||||
/// <inheritdoc />
|
||||
public virtual ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
if (NetID == null)
|
||||
throw new InvalidOperationException($"Cannot make state for component without Net ID: {GetType()}");
|
||||
if (!(Attribute.GetCustomAttribute(GetType(), typeof(NetworkedComponentAttribute)) is NetworkedComponentAttribute))
|
||||
throw new InvalidOperationException($"Calling base {nameof(GetComponentState)} without being networked.");
|
||||
|
||||
return new ComponentState(NetID.Value);
|
||||
return DefaultComponentState;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -3,31 +3,33 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public class ComponentFactory : IComponentFactory
|
||||
internal class ComponentFactory : IComponentFactory
|
||||
{
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _typeFactory = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
private readonly IDynamicTypeFactoryInternal _typeFactory;
|
||||
private readonly IReflectionManager _reflectionManager;
|
||||
|
||||
private class ComponentRegistration : IComponentRegistration
|
||||
{
|
||||
public string Name { get; }
|
||||
public uint? NetID { get; }
|
||||
public bool NetworkSynchronizeExistence { get; }
|
||||
public ushort? NetID { get; set; }
|
||||
public Type Type { get; }
|
||||
internal readonly List<Type> References = new();
|
||||
IReadOnlyList<Type> IComponentRegistration.References => References;
|
||||
|
||||
public ComponentRegistration(string name, Type type, uint? netID, bool networkSynchronizeExistence)
|
||||
public ComponentRegistration(string name, Type type)
|
||||
{
|
||||
Name = name;
|
||||
NetID = netID;
|
||||
NetworkSynchronizeExistence = networkSynchronizeExistence;
|
||||
NetID = null;
|
||||
Type = type;
|
||||
References.Add(type);
|
||||
}
|
||||
@@ -52,7 +54,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Mapping of network ID to type.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, ComponentRegistration> netIDs = new();
|
||||
private List<IComponentRegistration>? _networkedComponents;
|
||||
|
||||
/// <summary>
|
||||
/// Mapping of concrete component types to their registration.
|
||||
@@ -76,8 +78,34 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Type> AllRegisteredTypes => types.Keys;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<IComponentRegistration>? NetworkedComponents => _networkedComponents;
|
||||
|
||||
private IEnumerable<ComponentRegistration> AllRegistrations => types.Values;
|
||||
|
||||
public ComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
|
||||
{
|
||||
_typeFactory = typeFactory;
|
||||
_reflectionManager = reflectionManager;
|
||||
|
||||
conHost.RegisterCommand("dump_net_comps", "Prints the table of networked components.", "dump_net_comps", (shell, argStr, args) =>
|
||||
{
|
||||
if (_networkedComponents is null)
|
||||
{
|
||||
shell.WriteError("Registration still writeable, network ids have not been generated.");
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine("Networked Component Registrations:");
|
||||
|
||||
for (int netId = 0; netId < _networkedComponents.Count; netId++)
|
||||
{
|
||||
var registration = _networkedComponents[netId];
|
||||
shell.WriteLine($" [{netId,4}] {registration.Name,-16} {registration.Type.Name}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Obsolete("Use RegisterClass and Attributes instead of the Register/RegisterReference combo")]
|
||||
public void Register<T>(bool overwrite = false) where T : IComponent, new()
|
||||
{
|
||||
@@ -86,6 +114,9 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void Register(Type type, bool overwrite = false)
|
||||
{
|
||||
if (_networkedComponents is not null)
|
||||
throw new ComponentRegistrationLockException();
|
||||
|
||||
if (types.ContainsKey(type))
|
||||
{
|
||||
throw new InvalidOperationException($"Type is already registered: {type}");
|
||||
@@ -97,8 +128,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
var name = dummy.Name;
|
||||
var lowerCaseName = name.ToLowerInvariant();
|
||||
var netID = dummy.NetID;
|
||||
var netSyncExist = dummy.NetworkSynchronizeExistence;
|
||||
|
||||
if (IgnoredComponentNames.Contains(name))
|
||||
{
|
||||
@@ -128,24 +157,11 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
if (netID != null && netIDs.ContainsKey(netID.Value))
|
||||
{
|
||||
if (!overwrite)
|
||||
{
|
||||
throw new InvalidOperationException($"{name} has duplicate network ID {netID}, previous: {netIDs[netID.Value]}");
|
||||
}
|
||||
|
||||
RemoveComponent(netIDs[netID.Value].Name);
|
||||
}
|
||||
|
||||
var registration = new ComponentRegistration(name, type, netID, netSyncExist);
|
||||
var registration = new ComponentRegistration(name, type);
|
||||
names[name] = registration;
|
||||
_lowerCaseNames[lowerCaseName] = name;
|
||||
types[type] = registration;
|
||||
if (netID != null)
|
||||
{
|
||||
netIDs[netID.Value] = registration;
|
||||
}
|
||||
|
||||
ComponentAdded?.Invoke(registration);
|
||||
}
|
||||
|
||||
@@ -157,6 +173,9 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void RegisterReference(Type target, Type @interface)
|
||||
{
|
||||
if (_networkedComponents is not null)
|
||||
throw new ComponentRegistrationLockException();
|
||||
|
||||
if (!types.ContainsKey(target))
|
||||
{
|
||||
throw new InvalidOperationException($"Unregistered type: {target}");
|
||||
@@ -194,15 +213,14 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void RemoveComponent(string name)
|
||||
{
|
||||
if (_networkedComponents is not null)
|
||||
throw new ComponentRegistrationLockException();
|
||||
|
||||
var registration = names[name];
|
||||
|
||||
names.Remove(registration.Name);
|
||||
_lowerCaseNames.Remove(registration.Name.ToLowerInvariant());
|
||||
types.Remove(registration.Type);
|
||||
if (registration.NetID != null)
|
||||
{
|
||||
netIDs.Remove(registration.NetID.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public ComponentAvailability GetComponentAvailability(string componentName, bool ignoreCase = false)
|
||||
@@ -253,7 +271,7 @@ namespace Robust.Shared.GameObjects
|
||||
return _typeFactory.CreateInstanceUnchecked<IComponent>(GetRegistration(componentName).Type);
|
||||
}
|
||||
|
||||
public IComponent GetComponent(uint netId)
|
||||
public IComponent GetComponent(ushort netId)
|
||||
{
|
||||
return _typeFactory.CreateInstanceUnchecked<IComponent>(GetRegistration(netId).Type);
|
||||
}
|
||||
@@ -275,11 +293,14 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public IComponentRegistration GetRegistration(uint netID)
|
||||
public IComponentRegistration GetRegistration(ushort netID)
|
||||
{
|
||||
if (_networkedComponents is null)
|
||||
throw new ComponentRegistrationLockException();
|
||||
|
||||
try
|
||||
{
|
||||
return netIDs[netID];
|
||||
return _networkedComponents[netID];
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
@@ -343,9 +364,9 @@ namespace Robust.Shared.GameObjects
|
||||
return TryGetRegistration(typeof(T), out registration);
|
||||
}
|
||||
|
||||
public bool TryGetRegistration(uint netID, [NotNullWhen(true)] out IComponentRegistration? registration)
|
||||
public bool TryGetRegistration(ushort netID, [NotNullWhen(true)] out IComponentRegistration? registration)
|
||||
{
|
||||
if (netIDs.TryGetValue(netID, out var tempRegistration))
|
||||
if (_networkedComponents is not null && _networkedComponents.TryGetValue(netID, out var tempRegistration))
|
||||
{
|
||||
registration = tempRegistration;
|
||||
return true;
|
||||
@@ -406,15 +427,36 @@ namespace Robust.Shared.GameObjects
|
||||
return AllRegistrations.SelectMany(r => r.References).Distinct();
|
||||
}
|
||||
|
||||
public IEnumerable<uint> GetAllNetIds()
|
||||
/// <inheritdoc />
|
||||
public void GenerateNetIds()
|
||||
{
|
||||
foreach (var registration in AllRegistrations)
|
||||
// assumptions:
|
||||
// component names are guaranteed to be unique
|
||||
// component names are 1:1 with component concrete types
|
||||
|
||||
// a subset of component names are networked
|
||||
var networkedRegs = new List<IComponentRegistration>(names.Count);
|
||||
|
||||
foreach (var kvRegistration in names)
|
||||
{
|
||||
if (registration.NetID != null)
|
||||
var registration = kvRegistration.Value;
|
||||
if (Attribute.GetCustomAttribute(registration.Type, typeof(NetworkedComponentAttribute)) is NetworkedComponentAttribute)
|
||||
{
|
||||
yield return registration.NetID.Value;
|
||||
networkedRegs.Add(registration);
|
||||
}
|
||||
}
|
||||
|
||||
// The sorting implementation is unstable, but there are no duplicate names, so that isn't a problem.
|
||||
// Ordinal comparison is used so that the resulting order is always identical on every computer.
|
||||
networkedRegs.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
|
||||
for (ushort i = 0; i < networkedRegs.Count; i++)
|
||||
{
|
||||
var registration = (ComponentRegistration) networkedRegs[i];
|
||||
registration.NetID = i;
|
||||
}
|
||||
|
||||
_networkedComponents = networkedRegs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,4 +476,6 @@ namespace Robust.Shared.GameObjects
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
|
||||
public class ComponentRegistrationLockException : Exception { }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Internal.TypeSystem;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -28,7 +30,7 @@ namespace Robust.Shared.GameObjects
|
||||
private const int EntityCapacity = 1024;
|
||||
private const int NetComponentCapacity = 8;
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<uint, Component>> _netComponents
|
||||
private readonly Dictionary<EntityUid, Dictionary<ushort, Component>> _netComponents
|
||||
= new(EntityCapacity);
|
||||
|
||||
private readonly Dictionary<Type, Dictionary<EntityUid, Component>> _entTraitDict
|
||||
@@ -144,14 +146,14 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
// add the component to the netId grid
|
||||
if (component.NetID != null)
|
||||
if (reg.NetID != null)
|
||||
{
|
||||
// the main comp grid keeps this in sync
|
||||
var netId = component.NetID.Value;
|
||||
var netId = reg.NetID.Value;
|
||||
|
||||
if (!_netComponents.TryGetValue(uid, out var netSet))
|
||||
{
|
||||
netSet = new Dictionary<uint, Component>(NetComponentCapacity);
|
||||
netSet = new Dictionary<ushort, Component>(NetComponentCapacity);
|
||||
_netComponents.Add(uid, netSet);
|
||||
}
|
||||
netSet.Add(netId, component);
|
||||
@@ -191,7 +193,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RemoveComponent(EntityUid uid, uint netId)
|
||||
public void RemoveComponent(EntityUid uid, ushort netId)
|
||||
{
|
||||
RemoveComponentDeferred((Component)GetComponent(uid, netId), uid, false);
|
||||
}
|
||||
@@ -335,13 +337,13 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
// ReSharper disable once InvertIf
|
||||
if (component.NetID != null)
|
||||
if (reg.NetID != null)
|
||||
{
|
||||
var netSet = _netComponents[entityUid];
|
||||
if (netSet.Count == 1)
|
||||
_netComponents.Remove(entityUid);
|
||||
else
|
||||
netSet.Remove(component.NetID.Value);
|
||||
netSet.Remove(reg.NetID.Value);
|
||||
|
||||
component.Owner.Dirty();
|
||||
}
|
||||
@@ -367,7 +369,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool HasComponent(EntityUid uid, uint netId)
|
||||
public bool HasComponent(EntityUid uid, ushort netId)
|
||||
{
|
||||
return _netComponents.TryGetValue(uid, out var netSet)
|
||||
&& netSet.ContainsKey(netId);
|
||||
@@ -397,7 +399,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IComponent GetComponent(EntityUid uid, uint netId)
|
||||
public IComponent GetComponent(EntityUid uid, ushort netId)
|
||||
{
|
||||
return _netComponents[uid][netId];
|
||||
}
|
||||
@@ -436,7 +438,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetComponent(EntityUid uid, uint netId, [MaybeNullWhen(false)] out IComponent component)
|
||||
public bool TryGetComponent(EntityUid uid, ushort netId, [MaybeNullWhen(false)] out IComponent component)
|
||||
{
|
||||
if (_netComponents.TryGetValue(uid, out var netSet)
|
||||
&& netSet.TryGetValue(netId, out var comp))
|
||||
@@ -474,9 +476,9 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IComponent> GetNetComponents(EntityUid uid)
|
||||
public NetComponentEnumerable GetNetComponents(EntityUid uid)
|
||||
{
|
||||
return _netComponents[uid].Values;
|
||||
return new NetComponentEnumerable(_netComponents[uid]);
|
||||
}
|
||||
|
||||
#region Join Functions
|
||||
@@ -597,4 +599,30 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct NetComponentEnumerable
|
||||
{
|
||||
private readonly Dictionary<ushort, Component> _dictionary;
|
||||
|
||||
public NetComponentEnumerable(Dictionary<ushort, Component> dictionary) => _dictionary = dictionary;
|
||||
public NetComponentEnumerator GetEnumerator() => new(_dictionary);
|
||||
}
|
||||
|
||||
public struct NetComponentEnumerator
|
||||
{
|
||||
// DO NOT MAKE THIS READONLY
|
||||
private Dictionary<ushort, Component>.Enumerator _dictEnum;
|
||||
|
||||
public NetComponentEnumerator(Dictionary<ushort, Component> dictionary) => _dictEnum = dictionary.GetEnumerator();
|
||||
public bool MoveNext() => _dictEnum.MoveNext();
|
||||
public (ushort netId, IComponent component) Current
|
||||
{
|
||||
get
|
||||
{
|
||||
var val = _dictEnum.Current;
|
||||
return (val.Key, val.Value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using System;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[RequiresSerializable]
|
||||
[Serializable, NetSerializable]
|
||||
public class ComponentState
|
||||
{
|
||||
public uint NetID { get; }
|
||||
|
||||
public ComponentState(uint netID)
|
||||
{
|
||||
NetID = netID;
|
||||
}
|
||||
}
|
||||
public class ComponentState { }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -11,10 +12,10 @@ namespace Robust.Shared.GameObjects
|
||||
/// The data works using a simple key/value system. It is recommended to use enum keys to prevent errors.
|
||||
/// Visualization works client side with overrides of the <c>AppearanceVisualizer</c> class.
|
||||
/// </summary>
|
||||
[NetworkedComponent()]
|
||||
public abstract class SharedAppearanceComponent : Component
|
||||
{
|
||||
public override string Name => "Appearance";
|
||||
public override uint? NetID => NetIDs.APPEARANCE;
|
||||
|
||||
public abstract void SetData(string key, object value);
|
||||
public abstract void SetData(Enum key, object value);
|
||||
@@ -30,7 +31,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public readonly Dictionary<object, object> Data;
|
||||
|
||||
public AppearanceComponentState(Dictionary<object, object> data) : base(NetIDs.APPEARANCE)
|
||||
public AppearanceComponentState(Dictionary<object, object> data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public Box2? LocalBounds { get; }
|
||||
|
||||
public ClickableComponentState(Box2? localBounds) : base(NetIDs.CLICKABLE)
|
||||
public ClickableComponentState(Box2? localBounds)
|
||||
{
|
||||
LocalBounds = localBounds;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
@@ -44,6 +45,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(IPhysBody))]
|
||||
[NetworkedComponent()]
|
||||
public sealed class PhysicsComponent : Component, IPhysBody, ISerializationHooks
|
||||
{
|
||||
[DataField("status", readOnly: true)]
|
||||
@@ -52,9 +54,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Physics";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override uint? NetID => NetIDs.PHYSICS;
|
||||
|
||||
/// <summary>
|
||||
/// Has this body been added to an island previously in this tick.
|
||||
/// </summary>
|
||||
@@ -74,6 +73,23 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public bool IgnoreCCD { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public int ContactCount
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = 0;
|
||||
var edge = ContactEdges;
|
||||
while (edge != null)
|
||||
{
|
||||
edge = edge.Next;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linked-list of all of our contacts.
|
||||
/// </summary>
|
||||
@@ -834,6 +850,32 @@ namespace Robust.Shared.GameObjects
|
||||
[DataField("angularDamping")]
|
||||
private float _angularDamping = 0.02f;
|
||||
|
||||
/// <summary>
|
||||
/// Get the linear and angular velocities at the same time.
|
||||
/// </summary>
|
||||
public (Vector2 Linear, float Angular) MapVelocities
|
||||
{
|
||||
get
|
||||
{
|
||||
var linearVelocity = _linVelocity;
|
||||
var angularVelocity = _angVelocity;
|
||||
var parent = Owner.Transform.Parent?.Owner;
|
||||
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent.TryGetComponent(out PhysicsComponent? body))
|
||||
{
|
||||
linearVelocity += body.LinearVelocity;
|
||||
angularVelocity += body.AngularVelocity;
|
||||
}
|
||||
|
||||
parent = parent.Transform.Parent?.Owner;
|
||||
}
|
||||
|
||||
return (linearVelocity, angularVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current linear velocity of the entity in meters per second.
|
||||
/// </summary>
|
||||
@@ -862,6 +904,33 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private Vector2 _linVelocity;
|
||||
|
||||
/// <summary>
|
||||
/// Get the body's LinearVelocity in map terms.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Consider using <see cref="MapVelocities"/> if you need linear and angular at the same time.
|
||||
/// </remarks>
|
||||
public Vector2 MapLinearVelocity
|
||||
{
|
||||
get
|
||||
{
|
||||
var velocity = _linVelocity;
|
||||
var parent = Owner.Transform.Parent?.Owner;
|
||||
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent.TryGetComponent(out PhysicsComponent? body))
|
||||
{
|
||||
velocity += body.LinearVelocity;
|
||||
}
|
||||
|
||||
parent = parent.Transform.Parent?.Owner;
|
||||
}
|
||||
|
||||
return velocity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current angular velocity of the entity in radians per sec.
|
||||
/// </summary>
|
||||
@@ -890,6 +959,33 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private float _angVelocity;
|
||||
|
||||
/// <summary>
|
||||
/// Get the body's AngularVelocity in map terms.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Consider using <see cref="MapVelocities"/> if you need linear and angular at the same time.
|
||||
/// </remarks>
|
||||
public float MapAngularVelocity
|
||||
{
|
||||
get
|
||||
{
|
||||
var velocity = _angVelocity;
|
||||
var parent = Owner.Transform.Parent?.Owner;
|
||||
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent.TryGetComponent(out PhysicsComponent? body))
|
||||
{
|
||||
velocity += body.AngularVelocity;
|
||||
}
|
||||
|
||||
parent = parent.Transform.Parent?.Owner;
|
||||
}
|
||||
|
||||
return velocity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current momentum of the entity in kilogram meters per second
|
||||
/// </summary>
|
||||
|
||||
@@ -44,7 +44,6 @@ namespace Robust.Shared.GameObjects
|
||||
Vector2 linearVelocity,
|
||||
float angularVelocity,
|
||||
BodyType bodyType)
|
||||
: base(NetIDs.PHYSICS)
|
||||
{
|
||||
CanCollide = canCollide;
|
||||
SleepingAllowed = sleepingAllowed;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -11,10 +12,10 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// An optimisation component for stuff that should be set as collidable when it's awake and non-collidable when asleep.
|
||||
/// </summary>
|
||||
[NetworkedComponent()]
|
||||
public sealed class CollisionWakeComponent : Component
|
||||
{
|
||||
public override string Name => "CollisionWake";
|
||||
public override uint? NetID => NetIDs.COLLISION_WAKE;
|
||||
|
||||
[DataField("enabled")]
|
||||
private bool _enabled = true;
|
||||
@@ -64,7 +65,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public bool Enabled { get; }
|
||||
|
||||
public CollisionWakeState(bool enabled) : base(NetIDs.COLLISION_WAKE)
|
||||
public CollisionWakeState(bool enabled)
|
||||
{
|
||||
Enabled = enabled;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class EntityLookupComponent : Component
|
||||
{
|
||||
public override string Name => "EntityLookup";
|
||||
|
||||
internal DynamicTree<IEntity> Tree = default!;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[NetworkedComponent()]
|
||||
public class SharedEyeComponent : Component
|
||||
{
|
||||
public override string Name => "Eye";
|
||||
public override uint? NetID => NetIDs.EYE;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public virtual bool DrawFov { get; set; }
|
||||
@@ -39,7 +40,7 @@ namespace Robust.Shared.GameObjects
|
||||
public Angle Rotation { get; }
|
||||
public uint VisibilityMask { get; }
|
||||
|
||||
public EyeComponentState(bool drawFov, Vector2 zoom, Vector2 offset, Angle rotation, uint visibilityMask) : base(NetIDs.EYE)
|
||||
public EyeComponentState(bool drawFov, Vector2 zoom, Vector2 offset, Angle rotation, uint visibilityMask)
|
||||
{
|
||||
DrawFov = drawFov;
|
||||
Zoom = zoom;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -9,10 +10,10 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[NetworkedComponent()]
|
||||
public class OccluderComponent : Component
|
||||
{
|
||||
public sealed override string Name => "Occluder";
|
||||
public sealed override uint? NetID => NetIDs.OCCLUDER;
|
||||
|
||||
[DataField("enabled")]
|
||||
private bool _enabled = true;
|
||||
@@ -94,7 +95,7 @@ namespace Robust.Shared.GameObjects
|
||||
public bool Enabled { get; }
|
||||
public Box2 BoundingBox { get; }
|
||||
|
||||
public OccluderComponentState(bool enabled, Box2 boundingBox) : base(NetIDs.OCCLUDER)
|
||||
public OccluderComponentState(bool enabled, Box2 boundingBox)
|
||||
{
|
||||
Enabled = enabled;
|
||||
BoundingBox = boundingBox;
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace Robust.Shared.GameObjects
|
||||
public readonly Vector2 Offset;
|
||||
|
||||
public PointLightComponentState(bool enabled, Color color, float radius, Vector2 offset)
|
||||
: base(NetIDs.POINT_LIGHT)
|
||||
{
|
||||
Enabled = enabled;
|
||||
Color = color;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -10,10 +11,10 @@ namespace Robust.Shared.GameObjects.Components.Localization
|
||||
/// Overrides grammar attributes specified in prototypes or localization files.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent()]
|
||||
public class GrammarComponent : Component
|
||||
{
|
||||
public override string Name => "Grammar";
|
||||
public override uint? NetID => NetIDs.GRAMMAR;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("attributes")]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -18,6 +19,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc cref="IMapComponent"/>
|
||||
[ComponentReference(typeof(IMapComponent))]
|
||||
[NetworkedComponent()]
|
||||
public class MapComponent : Component, IMapComponent
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
@@ -27,9 +29,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Map";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override uint? NetID => NetIDs.MAP_MAP;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapId WorldMap
|
||||
{
|
||||
@@ -73,7 +72,6 @@ namespace Robust.Shared.GameObjects
|
||||
public MapId MapId { get; }
|
||||
|
||||
public MapComponentState(MapId mapId)
|
||||
: base(NetIDs.MAP_MAP)
|
||||
{
|
||||
MapId = mapId;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -25,6 +26,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc cref="IMapGridComponent"/>
|
||||
[ComponentReference(typeof(IMapGridComponent))]
|
||||
[NetworkedComponent()]
|
||||
internal class MapGridComponent : Component, IMapGridComponent
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
@@ -36,9 +38,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override string Name => "MapGrid";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override uint? NetID => NetIDs.MAP_GRID;
|
||||
|
||||
/// <inheritdoc />
|
||||
public GridId GridIndex
|
||||
{
|
||||
@@ -79,7 +78,6 @@ namespace Robust.Shared.GameObjects
|
||||
xform.Parent = Owner.Transform;
|
||||
|
||||
// anchor snapping
|
||||
xform.LocalRotation = xform.LocalRotation.GetCardinalDir().ToAngle();
|
||||
xform.LocalPosition = Grid.GridTileToLocal(Grid.TileIndicesFor(xform.LocalPosition)).Position;
|
||||
|
||||
xform.SetAnchored(result);
|
||||
@@ -146,7 +144,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="gridIndex">Index of the grid this component is linked to.</param>
|
||||
public MapGridComponentState(GridId gridIndex)
|
||||
: base(NetIDs.MAP_GRID)
|
||||
{
|
||||
GridIndex = gridIndex;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -35,7 +36,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="description">The in-game description of this entity.</param>
|
||||
/// <param name="prototypeId">The prototype this entity was created from, if any.</param>
|
||||
public MetaDataComponentState(string? name, string? description, string? prototypeId)
|
||||
: base(NetIDs.META_DATA)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
@@ -66,6 +66,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc cref="IMetaDataComponent"/>
|
||||
[ComponentReference(typeof(IMetaDataComponent))]
|
||||
[NetworkedComponent()]
|
||||
internal class MetaDataComponent : Component, IMetaDataComponent
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
@@ -79,9 +80,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override string Name => "MetaData";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override uint? NetID => NetIDs.META_DATA;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string EntityName
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public static class NetIDs
|
||||
{
|
||||
public const uint MAP_MAP = 1;
|
||||
public const uint MAP_GRID = 2;
|
||||
public const uint META_DATA = 4;
|
||||
public const uint TRANSFORM = 5;
|
||||
public const uint SPRITE = 8;
|
||||
public const uint POINT_LIGHT = 10;
|
||||
public const uint PHYSICS = 12;
|
||||
public const uint CLICKABLE = 14;
|
||||
public const uint APPEARANCE = 22;
|
||||
public const uint USERINTERFACE = 24;
|
||||
public const uint CONTAINER_MANAGER = 25;
|
||||
public const uint OCCLUDER = 26;
|
||||
public const uint EYE = 28;
|
||||
public const uint GRAMMAR = 29;
|
||||
public const uint COLLISION_WAKE = 30;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -7,10 +8,10 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[NetworkedComponent()]
|
||||
public abstract class SharedSpriteComponent : Component
|
||||
{
|
||||
public override string Name => "Sprite";
|
||||
public override uint? NetID => NetIDs.SPRITE;
|
||||
|
||||
public abstract bool Visible { get; set; }
|
||||
|
||||
@@ -42,7 +43,6 @@ namespace Robust.Shared.GameObjects
|
||||
string? baseRsiPath,
|
||||
List<PrototypeLayerData> layers,
|
||||
uint renderOrder)
|
||||
: base(NetIDs.SPRITE)
|
||||
{
|
||||
Visible = visible;
|
||||
DrawDepth = drawDepth;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -16,6 +17,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(ITransformComponent))]
|
||||
[NetworkedComponent()]
|
||||
internal class TransformComponent : Component, ITransformComponent, IComponentDebug
|
||||
{
|
||||
[DataField("parent")]
|
||||
@@ -54,9 +56,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Transform";
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override uint? NetID => NetIDs.TRANSFORM;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public MapId MapID { get; private set; }
|
||||
@@ -113,9 +112,6 @@ namespace Robust.Shared.GameObjects
|
||||
if (_localRotation.EqualsApprox(value, 0.00001))
|
||||
return;
|
||||
|
||||
if(Anchored)
|
||||
return;
|
||||
|
||||
var oldRotation = _localRotation;
|
||||
|
||||
// Set _nextRotation to null to break any active lerps if this is a client side prediction.
|
||||
@@ -899,7 +895,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="parentId">Current parent transform of this entity.</param>
|
||||
/// <param name="noLocalRotation"></param>
|
||||
public TransformComponentState(Vector2 localPosition, Angle rotation, EntityUid parentId, bool noLocalRotation, bool anchored)
|
||||
: base(NetIDs.TRANSFORM)
|
||||
{
|
||||
LocalPosition = localPosition;
|
||||
Rotation = rotation;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -6,10 +7,10 @@ using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[NetworkedComponent()]
|
||||
public abstract class SharedUserInterfaceComponent : Component
|
||||
{
|
||||
public sealed override string Name => "UserInterface";
|
||||
public sealed override uint? NetID => NetIDs.USERINTERFACE;
|
||||
|
||||
[DataDefinition]
|
||||
public sealed class PrototypeData : ISerializationHooks
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace Robust.Shared.GameObjects
|
||||
return;
|
||||
|
||||
_paused = value;
|
||||
EntityManager.EventBus.RaiseLocalEvent(Uid, new EntityPausedEvent(Uid, value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -400,7 +400,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public void ClearEventTables()
|
||||
{
|
||||
_eventTables.ClearEntities();
|
||||
_eventTables.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
[IoC.Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
[IoC.Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
|
||||
[IoC.Dependency] private readonly IComponentFactory ComponentFactory = default!;
|
||||
[IoC.Dependency] protected readonly IComponentFactory ComponentFactory = default!;
|
||||
[IoC.Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
@@ -32,6 +32,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public GameTick CurrentTick => _gameTiming.CurTick;
|
||||
|
||||
IComponentFactory IEntityManager.ComponentFactory => ComponentFactory;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IComponentManager ComponentManager => _componentManager;
|
||||
|
||||
@@ -479,7 +481,7 @@ namespace Robust.Shared.GameObjects
|
||||
var uid = netMsg.EntityUid;
|
||||
if (compMsg.Directed)
|
||||
{
|
||||
if (_componentManager.TryGetComponent(uid, netMsg.NetId, out var component))
|
||||
if (_componentManager.TryGetComponent(uid, (ushort) netMsg.NetId, out var component))
|
||||
component.HandleNetworkMessage(compMsg, compChannel, session);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -7,61 +7,71 @@ namespace Robust.Shared.GameObjects
|
||||
public sealed class EntityState
|
||||
{
|
||||
public EntityUid Uid { get; }
|
||||
public ComponentChanged[]? ComponentChanges { get; }
|
||||
public ComponentState[]? ComponentStates { get; }
|
||||
|
||||
public bool Empty => ComponentChanges is null && ComponentStates is null;
|
||||
public ComponentChange[]? ComponentChanges { get; }
|
||||
|
||||
public EntityState(EntityUid uid, ComponentChanged[]? changedComponents, ComponentState[]? componentStates)
|
||||
public bool Empty => ComponentChanges is null;
|
||||
|
||||
public EntityState(EntityUid uid, ComponentChange[]? changedComponents)
|
||||
{
|
||||
Uid = uid;
|
||||
|
||||
// empty lists are 5 bytes each
|
||||
ComponentChanges = changedComponents == null || changedComponents.Length == 0 ? null : changedComponents;
|
||||
ComponentStates = componentStates == null || componentStates.Length == 0 ? null : componentStates;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct ComponentChanged
|
||||
public readonly struct ComponentChange
|
||||
{
|
||||
// 15ish bytes to create a component (strings are big), 5 bytes to remove one
|
||||
|
||||
/// <summary>
|
||||
/// Was the component added or removed from the entity.
|
||||
/// Was the component removed from the entity.
|
||||
/// </summary>
|
||||
public readonly bool Deleted;
|
||||
|
||||
/// <summary>
|
||||
/// The Network ID of the component to remove.
|
||||
/// Was the component added to the entity.
|
||||
/// </summary>
|
||||
public readonly uint NetID;
|
||||
public readonly bool Created;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype name of the component to add.
|
||||
/// State data for the created/modified component, if any.
|
||||
/// </summary>
|
||||
public readonly string? ComponentName;
|
||||
public readonly ComponentState? State;
|
||||
|
||||
public ComponentChanged(bool deleted, uint netId, string? componentName)
|
||||
/// <summary>
|
||||
/// The Network ID of the component to remove.
|
||||
/// </summary>
|
||||
public readonly ushort NetID;
|
||||
|
||||
public ComponentChange(ushort netId, bool created, bool deleted, ComponentState? state)
|
||||
{
|
||||
Deleted = deleted;
|
||||
State = state;
|
||||
NetID = netId;
|
||||
ComponentName = componentName;
|
||||
Created = created;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{(Deleted ? "D" : "C")} {NetID} {ComponentName}";
|
||||
return $"{(Deleted ? "D" : "C")} {NetID} {State?.GetType().Name}";
|
||||
}
|
||||
|
||||
public static ComponentChanged Added(uint netId, string componentName)
|
||||
public static ComponentChange Added(ushort netId, ComponentState? state)
|
||||
{
|
||||
return new(false, netId, componentName);
|
||||
return new(netId, true, false, state);
|
||||
}
|
||||
|
||||
public static ComponentChanged Removed(uint netId)
|
||||
public static ComponentChange Changed(ushort netId, ComponentState state)
|
||||
{
|
||||
return new(true, netId, null);
|
||||
return new(netId, false, false, state);
|
||||
}
|
||||
|
||||
public static ComponentChange Removed(ushort netId)
|
||||
{
|
||||
return new(netId, false, true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public sealed class EntityPausedEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Entity { get; }
|
||||
public bool Paused { get; }
|
||||
|
||||
public EntityPausedEvent(EntityUid entity, bool paused)
|
||||
{
|
||||
Entity = entity;
|
||||
Paused = paused;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -13,16 +13,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </remarks>
|
||||
public interface IComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the network ID for the component.
|
||||
/// The network ID is used to determine which component will receive the component state
|
||||
/// on the other side of the network.
|
||||
/// If this is <c>null</c>, the component is not replicated across the network.
|
||||
/// </summary>
|
||||
/// <seealso cref="NetworkSynchronizeExistence" />
|
||||
/// <seealso cref="IComponentRegistration.NetID" />
|
||||
uint? NetID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Name that this component is represented with in prototypes.
|
||||
/// </summary>
|
||||
@@ -40,21 +30,11 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
ComponentLifeStage LifeStage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the client should synchronize component additions and removals.
|
||||
/// If this is false and the component gets added or removed server side, the client will not do the same.
|
||||
/// If this is true and the server adds or removes the component, the client will do as such too.
|
||||
/// This flag has no effect if <see cref="NetID" /> is <c>null</c>.
|
||||
/// This is disabled by default, usually the client builds their instance from a prototype.
|
||||
/// </summary>
|
||||
/// <seealso cref="IComponentRegistration.NetworkSynchronizeExistence" />
|
||||
bool NetworkSynchronizeExistence { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this component should be synchronized with clients when modified.
|
||||
/// If this is true, the server will synchronize all client instances with the data in this instance.
|
||||
/// If this is false, clients can modify the data in their instances without being overwritten by the server.
|
||||
/// This flag has no effect if <see cref="NetID" /> is <c>null</c>.
|
||||
/// This flag has no effect if <see cref="NetworkedComponentAttribute" /> is not defined on the component.
|
||||
/// This is enabled by default.
|
||||
/// </summary>
|
||||
bool NetSyncEnabled { get; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -58,6 +59,16 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
IEnumerable<Type> AllRegisteredTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The subset of all registered components that are networked, so that they can be
|
||||
/// referenced between the client and the server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will be null if the network Ids have not been generated yet.
|
||||
/// </remarks>
|
||||
/// <seealso cref="GenerateNetIds"/>
|
||||
IReadOnlyList<IComponentRegistration>? NetworkedComponents { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get whether a component is available right now.
|
||||
/// </summary>
|
||||
@@ -142,7 +153,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <exception cref="UnknownComponentException">
|
||||
/// Thrown if no component exists with the given id <see cref="netId"/>.
|
||||
/// </exception>
|
||||
IComponent GetComponent(uint netId);
|
||||
IComponent GetComponent(ushort netId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registration belonging to a component, throwing an exception if it does not exist.
|
||||
@@ -181,7 +192,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <exception cref="UnknownComponentException">
|
||||
/// Thrown if no component with id <see cref="netID"/> exists.
|
||||
/// </exception>
|
||||
IComponentRegistration GetRegistration(uint netID);
|
||||
IComponentRegistration GetRegistration(ushort netID);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registration of a component, throwing an exception if
|
||||
@@ -225,7 +236,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="netID">The network ID corresponding to the component.</param>
|
||||
/// <param name="registration">The registration if found, null otherwise.</param>
|
||||
/// <returns>true it found, false otherwise.</returns>
|
||||
bool TryGetRegistration(uint netID, [NotNullWhen(true)] out IComponentRegistration? registration);
|
||||
bool TryGetRegistration(ushort netID, [NotNullWhen(true)] out IComponentRegistration? registration);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the registration of a component.
|
||||
@@ -241,8 +252,7 @@ namespace Robust.Shared.GameObjects
|
||||
void DoAutoRegistrations();
|
||||
|
||||
IEnumerable<Type> GetAllRefTypes();
|
||||
|
||||
IEnumerable<uint> GetAllNetIds();
|
||||
void GenerateNetIds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -263,18 +273,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// ID used to reference the component type across the network.
|
||||
/// If null, no network synchronization will be available for this component.
|
||||
/// </summary>
|
||||
/// <seealso cref="IComponent.NetID" />
|
||||
uint? NetID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the addition and removal of the component will be synchronized to clients.
|
||||
/// This means that if the server adds or removes the component outside of prototype-based creation,
|
||||
/// the client will update accordingly.
|
||||
/// If false the client will ignore missing components even when the net ID checks out and could be instantiated.
|
||||
/// and the client won't delete the component if no state was sent for it.
|
||||
/// </summary>
|
||||
/// <seealso cref="IComponent.NetworkSynchronizeExistence" />
|
||||
bool NetworkSynchronizeExistence { get; }
|
||||
/// <seealso cref="NetworkedComponentAttribute" />
|
||||
ushort? NetID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type that will be instantiated if this component is created.
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to modify.</param>
|
||||
/// <param name="netID">Network ID of the component to remove.</param>
|
||||
void RemoveComponent(EntityUid uid, uint netID);
|
||||
void RemoveComponent(EntityUid uid, ushort netID);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified component.
|
||||
@@ -118,7 +118,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">Entity UID to check.</param>
|
||||
/// <param name="netId">Network ID to check for.</param>
|
||||
/// <returns>True if the entity has a component with the given network ID, otherwise false.</returns>
|
||||
bool HasComponent(EntityUid uid, uint netId);
|
||||
bool HasComponent(EntityUid uid, ushort netId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
@@ -143,7 +143,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">Entity UID to look on.</param>
|
||||
/// <param name="netId">Network ID of the component to retrieve.</param>
|
||||
/// <returns>The component with the specified network id.</returns>
|
||||
IComponent GetComponent(EntityUid uid, uint netId);
|
||||
IComponent GetComponent(EntityUid uid, ushort netId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
@@ -171,7 +171,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="netId">Component Network ID to check for.</param>
|
||||
/// <param name="component">Component with the specified network id.</param>
|
||||
/// <returns>If the component existed in the entity.</returns>
|
||||
bool TryGetComponent(EntityUid uid, uint netId, [NotNullWhen(true)] out IComponent? component);
|
||||
bool TryGetComponent(EntityUid uid, ushort netId, [NotNullWhen(true)] out IComponent? component);
|
||||
|
||||
/// <summary>
|
||||
/// Returns ALL component type instances on an entity. A single component instance
|
||||
@@ -195,7 +195,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to look on.</param>
|
||||
/// <returns>All components that have a network ID.</returns>
|
||||
IEnumerable<IComponent> GetNetComponents(EntityUid uid);
|
||||
NetComponentEnumerable GetNetComponents(EntityUid uid);
|
||||
|
||||
/// <summary>
|
||||
/// Returns ALL component instances of a specified type.
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
void FrameUpdate(float frameTime);
|
||||
|
||||
IComponentFactory ComponentFactory { get; }
|
||||
IComponentManager ComponentManager { get; }
|
||||
IEntitySystemManager EntitySysManager { get; }
|
||||
IEntityNetworkManager? EntityNetManager { get; }
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -66,15 +68,24 @@ namespace Robust.Shared.GameObjects
|
||||
[UsedImplicitly]
|
||||
public class EntityLookup : IEntityLookup, IEntityEventSubscriber
|
||||
{
|
||||
private readonly IComponentManager _compManager;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
|
||||
private readonly Dictionary<MapId, DynamicTree<IEntity>> _entityTreesPerMap = new();
|
||||
private const int GrowthRate = 256;
|
||||
|
||||
private const float PointEnlargeRange = .00001f / 2;
|
||||
|
||||
// Using stacks so we always use latest data (given we only run it once per entity).
|
||||
private readonly Stack<MoveEvent> _moveQueue = new();
|
||||
private readonly Stack<RotateEvent> _rotateQueue = new();
|
||||
private readonly Queue<EntMapIdChangedMessage> _mapChangeQueue = new();
|
||||
private readonly Queue<EntParentChangedMessage> _parentChangeQueue = new();
|
||||
|
||||
/// <summary>
|
||||
/// Like RenderTree we need to enlarge our lookup range for EntityLookupComponent as an entity is only ever on
|
||||
/// 1 EntityLookupComponent at a time (hence it may overlap without another lookup).
|
||||
/// </summary>
|
||||
private float _lookupEnlargementRange;
|
||||
|
||||
/// <summary>
|
||||
/// Move and rotate events generate the same update so no point duplicating work in the same tick.
|
||||
@@ -83,10 +94,17 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
// TODO: Should combine all of the methods that check for IPhysBody and just use the one GetWorldAabbFromEntity method
|
||||
|
||||
// TODO: Combine GridTileLookupSystem and entity anchoring together someday.
|
||||
// Queries are a bit of spaghet rn but ideally you'd just have:
|
||||
// A) The fast tile-based one
|
||||
// B) The physics-only one (given physics needs it to be fast af)
|
||||
// C) A generic use one that covers anything not caught in the above.
|
||||
|
||||
public bool Started { get; private set; } = false;
|
||||
|
||||
public EntityLookup(IEntityManager entityManager, IMapManager mapManager)
|
||||
public EntityLookup(IComponentManager compManager, IEntityManager entityManager, IMapManager mapManager)
|
||||
{
|
||||
_compManager = compManager;
|
||||
_entityManager = entityManager;
|
||||
_mapManager = mapManager;
|
||||
}
|
||||
@@ -98,14 +116,21 @@ namespace Robust.Shared.GameObjects
|
||||
throw new InvalidOperationException("Startup() called multiple times.");
|
||||
}
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.LookupEnlargementRange, value => _lookupEnlargementRange = value, true);
|
||||
|
||||
var eventBus = _entityManager.EventBus;
|
||||
eventBus.SubscribeEvent<MoveEvent>(EventSource.Local, this, ev => _moveQueue.Push(ev));
|
||||
eventBus.SubscribeEvent<RotateEvent>(EventSource.Local, this, ev => _rotateQueue.Push(ev));
|
||||
eventBus.SubscribeEvent<EntMapIdChangedMessage>(EventSource.Local, this, ev => _mapChangeQueue.Enqueue(ev));
|
||||
eventBus.SubscribeEvent<EntParentChangedMessage>(EventSource.Local, this, ev => _parentChangeQueue.Enqueue(ev));
|
||||
|
||||
eventBus.SubscribeLocalEvent<EntityLookupComponent, ComponentInit>(HandleLookupInit);
|
||||
eventBus.SubscribeLocalEvent<EntityLookupComponent, ComponentShutdown>(HandleLookupShutdown);
|
||||
eventBus.SubscribeEvent<GridInitializeEvent>(EventSource.Local, this, HandleGridInit);
|
||||
|
||||
_entityManager.EntityDeleted += HandleEntityDeleted;
|
||||
_entityManager.EntityStarted += HandleEntityStarted;
|
||||
_mapManager.MapCreated += HandleMapCreated;
|
||||
_mapManager.MapDestroyed += HandleMapDestroyed;
|
||||
Started = true;
|
||||
}
|
||||
|
||||
@@ -118,17 +143,43 @@ namespace Robust.Shared.GameObjects
|
||||
_moveQueue.Clear();
|
||||
_rotateQueue.Clear();
|
||||
_handledThisTick.Clear();
|
||||
_mapChangeQueue.Clear();
|
||||
_entityTreesPerMap.Clear();
|
||||
_parentChangeQueue.Clear();
|
||||
|
||||
_entityManager.EventBus.UnsubscribeEvents(this);
|
||||
_entityManager.EntityDeleted -= HandleEntityDeleted;
|
||||
_entityManager.EntityStarted -= HandleEntityStarted;
|
||||
_mapManager.MapCreated -= HandleMapCreated;
|
||||
_mapManager.MapDestroyed -= HandleMapDestroyed;
|
||||
Started = false;
|
||||
}
|
||||
|
||||
private void HandleLookupShutdown(EntityUid uid, EntityLookupComponent component, ComponentShutdown args)
|
||||
{
|
||||
component.Tree.Clear();
|
||||
}
|
||||
|
||||
private void HandleGridInit(GridInitializeEvent ev)
|
||||
{
|
||||
_entityManager.GetEntity(ev.EntityUid).EnsureComponent<EntityLookupComponent>();
|
||||
}
|
||||
|
||||
private void HandleLookupInit(EntityUid uid, EntityLookupComponent component, ComponentInit args)
|
||||
{
|
||||
var capacity = (int) Math.Min(256, Math.Ceiling(component.Owner.Transform.ChildCount / (float) GrowthRate) * GrowthRate);
|
||||
|
||||
component.Tree = new DynamicTree<IEntity>(
|
||||
GetRelativeAABBFromEntity,
|
||||
capacity: capacity,
|
||||
growthFunc: x => x == GrowthRate ? GrowthRate * 8 : x + GrowthRate
|
||||
);
|
||||
}
|
||||
|
||||
private static Box2 GetRelativeAABBFromEntity(in IEntity entity)
|
||||
{
|
||||
var aabb = GetWorldAABB(entity);
|
||||
var tree = GetLookup(entity);
|
||||
|
||||
return aabb.Translated(-tree?.Owner.Transform.WorldPosition ?? Vector2.Zero);
|
||||
}
|
||||
|
||||
private void HandleEntityDeleted(object? sender, EntityUid uid)
|
||||
{
|
||||
RemoveFromEntityTrees(_entityManager.GetEntity(uid));
|
||||
@@ -141,63 +192,78 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void HandleMapCreated(object? sender, MapEventArgs eventArgs)
|
||||
{
|
||||
_entityTreesPerMap[eventArgs.Map] = new DynamicTree<IEntity>(
|
||||
GetWorldAabbFromEntity,
|
||||
capacity: 16,
|
||||
growthFunc: x => x == 16 ? 3840 : x + 256
|
||||
);
|
||||
}
|
||||
if (eventArgs.Map == MapId.Nullspace) return;
|
||||
|
||||
private void HandleMapDestroyed(object? sender, MapEventArgs eventArgs)
|
||||
{
|
||||
_entityTreesPerMap.Remove(eventArgs.Map);
|
||||
_mapManager.GetMapEntity(eventArgs.Map).EnsureComponent<EntityLookupComponent>();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Acruid said he'd deal with Update being called around IEntityManager later.
|
||||
_handledThisTick.Clear();
|
||||
|
||||
while (_mapChangeQueue.TryDequeue(out var mapChangeEvent))
|
||||
// Could be more efficient but essentially nuke their old lookup and add to new lookup if applicable.
|
||||
while (_parentChangeQueue.TryDequeue(out var mapChangeEvent))
|
||||
{
|
||||
if (mapChangeEvent.Entity.Deleted) continue;
|
||||
RemoveFromEntityTree(mapChangeEvent.Entity, mapChangeEvent.OldMapId);
|
||||
UpdateEntityTree(mapChangeEvent.Entity, GetWorldAabbFromEntity(mapChangeEvent.Entity));
|
||||
_handledThisTick.Add(mapChangeEvent.Entity.Uid);
|
||||
RemoveFromEntityTrees(mapChangeEvent.Entity);
|
||||
|
||||
if (mapChangeEvent.Entity.Deleted) continue;
|
||||
UpdateEntityTree(mapChangeEvent.Entity, GetWorldAabbFromEntity(mapChangeEvent.Entity));
|
||||
}
|
||||
|
||||
while (_moveQueue.TryPop(out var moveEvent))
|
||||
{
|
||||
if (moveEvent.Sender.Deleted || _handledThisTick.Contains(moveEvent.Sender.Uid)) continue;
|
||||
if (moveEvent.Sender.Deleted || !_handledThisTick.Add(moveEvent.Sender.Uid)) continue;
|
||||
|
||||
UpdateEntityTree(moveEvent.Sender, moveEvent.WorldAABB);
|
||||
_handledThisTick.Add(moveEvent.Sender.Uid);
|
||||
}
|
||||
|
||||
while (_rotateQueue.TryPop(out var rotateEvent))
|
||||
{
|
||||
if (rotateEvent.Sender.Deleted || _handledThisTick.Contains(rotateEvent.Sender.Uid)) continue;
|
||||
if (rotateEvent.Sender.Deleted || !_handledThisTick.Add(rotateEvent.Sender.Uid)) continue;
|
||||
|
||||
UpdateEntityTree(rotateEvent.Sender, rotateEvent.WorldAABB);
|
||||
}
|
||||
|
||||
_handledThisTick.Clear();
|
||||
}
|
||||
|
||||
#region Spatial Queries
|
||||
|
||||
private IEnumerable<EntityLookupComponent> GetLookupsIntersecting(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) yield break;
|
||||
|
||||
// TODO: Recursive and all that.
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_lookupEnlargementRange)))
|
||||
{
|
||||
yield return _entityManager.GetEntity(grid.GridEntityId).GetComponent<EntityLookupComponent>();
|
||||
}
|
||||
|
||||
yield return _mapManager.GetMapEntity(mapId).GetComponent<EntityLookupComponent>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool AnyEntitiesIntersecting(MapId mapId, Box2 box, bool approximate = false)
|
||||
{
|
||||
var found = false;
|
||||
_entityTreesPerMap[mapId].QueryAabb(ref found, (ref bool found, in IEntity ent) =>
|
||||
{
|
||||
if (!ent.Deleted)
|
||||
{
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, box, approximate);
|
||||
foreach (var lookup in GetLookupsIntersecting(mapId, box))
|
||||
{
|
||||
var offsetBox = box.Translated(-lookup.Owner.Transform.WorldPosition);
|
||||
|
||||
lookup.Tree.QueryAabb(ref found, (ref bool found, in IEntity ent) =>
|
||||
{
|
||||
if (!ent.Deleted)
|
||||
{
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, offsetBox, approximate);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
@@ -205,31 +271,34 @@ namespace Robust.Shared.GameObjects
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public void FastEntitiesIntersecting(in MapId mapId, ref Box2 position, EntityQueryCallback callback)
|
||||
{
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
foreach (var lookup in GetLookupsIntersecting(mapId, position))
|
||||
{
|
||||
var offsetBox = position.Translated(-lookup.Owner.Transform.WorldPosition);
|
||||
|
||||
_entityTreesPerMap[mapId]._b2Tree
|
||||
.FastQuery(ref position, (ref IEntity data) => callback(data));
|
||||
lookup.Tree._b2Tree.FastQuery(ref offsetBox, (ref IEntity data) => callback(data));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Box2 position, bool approximate = false)
|
||||
{
|
||||
if (!_entityTreesPerMap.TryGetValue(mapId, out var mapTree))
|
||||
{
|
||||
return Enumerable.Empty<IEntity>();
|
||||
}
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<IEntity>();
|
||||
|
||||
var list = new List<IEntity>();
|
||||
|
||||
mapTree.QueryAabb(ref list, (ref List<IEntity> list, in IEntity ent) =>
|
||||
foreach (var lookup in GetLookupsIntersecting(mapId, position))
|
||||
{
|
||||
if (!ent.Deleted)
|
||||
var offsetBox = position.Translated(-lookup.Owner.Transform.WorldPosition);
|
||||
|
||||
lookup.Tree.QueryAabb(ref list, (ref List<IEntity> list, in IEntity ent) =>
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, position, approximate);
|
||||
if (!ent.Deleted)
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, offsetBox, approximate);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
@@ -237,25 +306,25 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Vector2 position, bool approximate = false)
|
||||
{
|
||||
const float range = .00001f / 2;
|
||||
var aabb = new Box2(position, position).Enlarged(range);
|
||||
|
||||
if (mapId == MapId.Nullspace)
|
||||
{
|
||||
return Enumerable.Empty<IEntity>();
|
||||
}
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<IEntity>();
|
||||
|
||||
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
|
||||
var list = new List<IEntity>();
|
||||
var state = (list, position);
|
||||
|
||||
_entityTreesPerMap[mapId].QueryAabb(ref state, (ref (List<IEntity> list, Vector2 position) state, in IEntity ent) =>
|
||||
foreach (var lookup in GetLookupsIntersecting(mapId, aabb))
|
||||
{
|
||||
if (Intersecting(ent, state.position))
|
||||
var offsetBox = aabb.Translated(-lookup.Owner.Transform.WorldPosition);
|
||||
|
||||
lookup.Tree.QueryAabb(ref state, (ref (List<IEntity> list, Vector2 position) state, in IEntity ent) =>
|
||||
{
|
||||
state.list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, aabb, approximate);
|
||||
if (Intersecting(ent, state.position))
|
||||
{
|
||||
state.list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, offsetBox, approximate);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
@@ -365,34 +434,44 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntitiesInMap(MapId mapId)
|
||||
{
|
||||
if (!_entityTreesPerMap.TryGetValue(mapId, out var trees))
|
||||
yield break;
|
||||
|
||||
foreach (var entity in trees)
|
||||
foreach (EntityLookupComponent comp in _compManager.EntityQuery<EntityLookupComponent>(true))
|
||||
{
|
||||
if (!entity.Deleted)
|
||||
foreach (var entity in comp.Tree)
|
||||
{
|
||||
if (entity.Deleted) continue;
|
||||
|
||||
yield return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntitiesAt(MapId mapId, Vector2 position, bool approximate = false)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<IEntity>();
|
||||
|
||||
var list = new List<IEntity>();
|
||||
|
||||
var state = (list, position);
|
||||
|
||||
_entityTreesPerMap[mapId].QueryPoint(ref state, (ref (List<IEntity> list, Vector2 position) state, in IEntity ent) =>
|
||||
{
|
||||
var transform = ent.Transform;
|
||||
if (MathHelper.CloseTo(transform.Coordinates.X, state.position.X) &&
|
||||
MathHelper.CloseTo(transform.Coordinates.Y, state.position.Y))
|
||||
{
|
||||
state.list.Add(ent);
|
||||
}
|
||||
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
|
||||
|
||||
return true;
|
||||
}, position, approximate);
|
||||
foreach (var lookup in GetLookupsIntersecting(mapId, aabb))
|
||||
{
|
||||
var offsetPos = position -lookup.Owner.Transform.WorldPosition;
|
||||
|
||||
lookup.Tree.QueryPoint(ref state, (ref (List<IEntity> list, Vector2 position) state, in IEntity ent) =>
|
||||
{
|
||||
var transform = ent.Transform;
|
||||
if (MathHelper.CloseTo(transform.Coordinates.X, state.position.X) &&
|
||||
MathHelper.CloseTo(transform.Coordinates.Y, state.position.Y))
|
||||
{
|
||||
state.list.Add(ent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, offsetPos, approximate);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
@@ -400,88 +479,119 @@ namespace Robust.Shared.GameObjects
|
||||
#endregion
|
||||
|
||||
#region Entity DynamicTree
|
||||
|
||||
private static EntityLookupComponent? GetLookup(IEntity entity)
|
||||
{
|
||||
if (entity.Transform.MapID == MapId.Nullspace)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// if it's map return null. Grids should return the map's broadphase.
|
||||
if (entity.HasComponent<EntityLookupComponent>() &&
|
||||
entity.Transform.Parent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var parent = entity.Transform.Parent?.Owner;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (parent == null) break;
|
||||
|
||||
if (parent.TryGetComponent(out EntityLookupComponent? comp)) return comp;
|
||||
parent = parent.Transform.Parent?.Owner;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool UpdateEntityTree(IEntity entity, Box2? worldAABB = null)
|
||||
{
|
||||
// look there's JANK everywhere but I'm just bandaiding it for now for shuttles and we'll fix it later when
|
||||
// PVS is more stable and entity anchoring has been battle-tested.
|
||||
if (entity.Deleted)
|
||||
{
|
||||
RemoveFromEntityTrees(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!entity.Initialized || !_entityManager.EntityExists(entity.Uid))
|
||||
if (!entity.Initialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var transform = entity.Transform;
|
||||
var lookup = GetLookup(entity);
|
||||
|
||||
if (lookup == null)
|
||||
{
|
||||
RemoveFromEntityTrees(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Temp PVS guard for when we clear dynamictree for now.
|
||||
worldAABB ??= GetWorldAabbFromEntity(entity);
|
||||
var center = worldAABB.Value.Center;
|
||||
|
||||
if (float.IsNaN(center.X) || float.IsNaN(center.Y))
|
||||
{
|
||||
RemoveFromEntityTrees(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
var transform = entity.Transform;
|
||||
DebugTools.Assert(transform.Initialized);
|
||||
|
||||
var mapId = transform.MapID;
|
||||
|
||||
if (!_entityTreesPerMap.TryGetValue(mapId, out var entTree))
|
||||
{
|
||||
entTree = new DynamicTree<IEntity>(
|
||||
GetWorldAabbFromEntity,
|
||||
capacity: 16,
|
||||
growthFunc: x => x == 16 ? 3840 : x + 256
|
||||
);
|
||||
_entityTreesPerMap.Add(mapId, entTree);
|
||||
}
|
||||
var aabb = worldAABB.Value.Translated(-lookup.Owner.Transform.WorldPosition);
|
||||
|
||||
// for debugging
|
||||
var necessary = 0;
|
||||
|
||||
if (entTree.AddOrUpdate(entity, worldAABB))
|
||||
if (lookup.Tree.AddOrUpdate(entity, aabb))
|
||||
{
|
||||
++necessary;
|
||||
}
|
||||
|
||||
foreach (var childTx in entity.Transform.ChildEntityUids)
|
||||
if (!entity.HasComponent<EntityLookupComponent>())
|
||||
{
|
||||
if (UpdateEntityTree(_entityManager.GetEntity(childTx)))
|
||||
foreach (var childTx in entity.Transform.ChildEntityUids)
|
||||
{
|
||||
++necessary;
|
||||
if (!_handledThisTick.Add(childTx)) continue;
|
||||
|
||||
if (UpdateEntityTree(_entityManager.GetEntity(childTx)))
|
||||
{
|
||||
++necessary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return necessary > 0;
|
||||
}
|
||||
|
||||
public bool RemoveFromEntityTree(IEntity entity, MapId mapId)
|
||||
{
|
||||
if (_entityTreesPerMap.TryGetValue(mapId, out var tree))
|
||||
{
|
||||
return tree.Remove(entity);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveFromEntityTrees(IEntity entity)
|
||||
{
|
||||
foreach (var mapId in _mapManager.GetAllMapIds())
|
||||
foreach (var lookup in _compManager.EntityQuery<EntityLookupComponent>(true))
|
||||
{
|
||||
if (_entityTreesPerMap.TryGetValue(mapId, out var entTree))
|
||||
{
|
||||
entTree.Remove(entity);
|
||||
}
|
||||
lookup.Tree.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public Box2 GetWorldAabbFromEntity(in IEntity ent)
|
||||
{
|
||||
if (ent.Deleted)
|
||||
return new Box2(0, 0, 0, 0);
|
||||
return GetWorldAABB(ent);
|
||||
}
|
||||
|
||||
private static Box2 GetWorldAABB(in IEntity ent)
|
||||
{
|
||||
var pos = ent.Transform.WorldPosition;
|
||||
|
||||
if (ent.TryGetComponent(out IPhysBody? collider))
|
||||
return collider.GetWorldAABB(pos);
|
||||
if (ent.Deleted || !ent.TryGetComponent(out PhysicsComponent? physics))
|
||||
return new Box2(pos, pos);
|
||||
|
||||
return new Box2(pos, pos);
|
||||
return physics.GetWorldAABB(pos);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -100,19 +100,59 @@ namespace Robust.Shared.GameObjects
|
||||
_mapManager.MapCreated += HandleMapCreated;
|
||||
_mapManager.MapDestroyed += HandleMapDestroyed;
|
||||
|
||||
SubscribeLocalEvent<GridInitializeEvent>(HandleGridInit);
|
||||
SubscribeLocalEvent<PhysicsUpdateMessage>(HandlePhysicsUpdateMessage);
|
||||
SubscribeLocalEvent<PhysicsWakeMessage>(HandleWakeMessage);
|
||||
SubscribeLocalEvent<PhysicsSleepMessage>(HandleSleepMessage);
|
||||
SubscribeLocalEvent<EntMapIdChangedMessage>(HandleMapChange);
|
||||
|
||||
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(HandleContainerInserted);
|
||||
SubscribeLocalEvent<EntRemovedFromContainerMessage>(HandleContainerRemoved);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(HandleParentChange);
|
||||
|
||||
BuildControllers();
|
||||
Logger.DebugS("physics", $"Found {_controllers.Count} physics controllers.");
|
||||
|
||||
IoCManager.Resolve<IIslandManager>().Initialize();
|
||||
}
|
||||
|
||||
private void HandleParentChange(EntParentChangedMessage args)
|
||||
{
|
||||
var entity = args.Entity;
|
||||
|
||||
if (!entity.Initialized ||
|
||||
!entity.TryGetComponent(out PhysicsComponent? body) ||
|
||||
entity.IsInContainer()) return;
|
||||
|
||||
var oldParent = args.OldParent;
|
||||
var linearVelocityDiff = Vector2.Zero;
|
||||
var angularVelocityDiff = 0f;
|
||||
|
||||
if (oldParent != null && oldParent.TryGetComponent(out PhysicsComponent? oldBody))
|
||||
{
|
||||
var (linear, angular) = oldBody.MapVelocities;
|
||||
|
||||
linearVelocityDiff += linear;
|
||||
angularVelocityDiff += angular;
|
||||
}
|
||||
|
||||
if (entity.Transform.Parent!.Owner.TryGetComponent(out PhysicsComponent? newBody))
|
||||
{
|
||||
var (linear, angular) = newBody.MapVelocities;
|
||||
|
||||
linearVelocityDiff -= linear;
|
||||
angularVelocityDiff -= angular;
|
||||
}
|
||||
|
||||
body.LinearVelocity += linearVelocityDiff;
|
||||
body.AngularVelocity += angularVelocityDiff;
|
||||
}
|
||||
|
||||
private void HandleGridInit(GridInitializeEvent ev)
|
||||
{
|
||||
if (!EntityManager.TryGetEntity(ev.EntityUid, out var gridEntity)) return;
|
||||
var collideComp = gridEntity.EnsureComponent<PhysicsComponent>();
|
||||
collideComp.BodyType = BodyType.Static;
|
||||
}
|
||||
|
||||
private void BuildControllers()
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user