Compare commits

...

15 Commits

Author SHA1 Message Date
Vera Aguilera Puerto
b67d24efee ITileDefinition now has a Path string that ClydeTileDefinitionManager uses (#1860) 2021-07-12 10:29:13 +02:00
mirrorcult
d992e47f30 Remove NetMessage deprecated boilerplate entirely (#1830)
* Remove NetMessage boilerplate in favor of virtual properties

* forgot some stuff
2021-07-12 10:28:46 +02:00
Acruid
dadd7b4cc3 Remove Static Component NetIds (#1842)
* ComponentNames are not sent over the network when components are created.

* Removed ComponentStates array from EntityState, now the state is stored directly inside the CompChange struct.

* Remove the unnecessary NetID property from ComponentState.

* Remove Component.NetworkSynchronizeExistence.

* Change GetNetComponents to return both the component and the component NetId.

* Remove public usages of the Component.NetID property.

* Adds the NetIDAttribute that can be applied to components.

* Removed Component.NetID.

* Revert changes to GetComponentState and how prediction works.

* Adds component netID automatic generation.

* Modifies ClientConsoleHost so that commands can be called before Initialize().

* Completely remove static NetIds.

* Renamed NetIDAttribute to NetworkedComponentAttribute.

* Fixing unit tests.
2021-07-12 10:23:13 +02:00
Paul Ritter
baef2bc7f8 some misc serv3 fixes (#1861)
adds a typeserializer
adds null parser to read
adds another overload for deserializationresult.value

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-07-11 15:44:12 +02:00
metalgearsloth
e0b1a7d64a Add Contains Vector2 method to Box2Rotated (#1851) 2021-07-11 22:49:59 +10:00
metalgearsloth
aea5f83002 Preserve world velocities on parent change (#1859)
* Preserve world velocities on parent change

* Faster func

* Test

* lag

* uhh LAAAGGG
2021-07-11 12:25:02 +10:00
Swept
7df2d1f430 Fixes console loc string (#1858) 2021-07-09 15:13:30 +02:00
Vera Aguilera Puerto
d216c3a1f6 VV command can now get IoC services that don't have an interface. 2021-07-07 18:03:20 +02:00
metalgearsloth
986ec3ef06 Do showbb transforms in the iterator (#1847) 2021-07-04 19:52:21 +02:00
metalgearsloth
60cec9cb84 Make showbb awakeness binary (#1846)
Makes it easier to spot mapping issues because otherwise it might have a massive sleep timer and not be easily visible.
2021-07-04 22:31:34 +10:00
Vera Aguilera Puerto
c06707d519 Adds ServerOptions, improve GameControllerOptions, fix engine integration tests (#1844)
* Adds ServerOptions, improve GameControllerOptions, fix engine integration tests

* Do component auto-registration in engine integration tests by default

* Fix integration tests on content, register components ONLY if not contentstarted or options are null

* Add integration test for engine integration tests working correctly

* Move cvar overrides out of content and into engine.
2021-07-03 15:19:46 +02:00
Pieter-Jan Briers
63128324ab Stop RunTicks overriding tick deltas in integration tests.
Now listens to game timing tick rate.
2021-07-03 13:06:19 +02:00
Pieter-Jan Briers
abea3024b4 Raise event when entity paused state changes. 2021-07-02 17:19:34 +02:00
Pieter-Jan Briers
07dafeb6cd Can now rotate anchored entities 2021-07-02 01:16:25 +02:00
Pieter-Jan Briers
a726d42ae3 Fix ResetPredictedEntities applying data early in some cases.
FullState was getting updated before ResetPredictedEntities ran instead of after. So ResetPredictedEntities was applying data to stuff like containers that depends on entities that hadn't been made yet.
2021-07-01 12:16:18 +02:00
109 changed files with 1054 additions and 533 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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());

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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))

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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>

View File

@@ -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");

View File

@@ -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;

View File

@@ -5,6 +5,7 @@ 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;
@@ -17,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; }

View File

@@ -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
{

View File

@@ -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;
}
}
}
}

View File

@@ -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}");
}
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -10,7 +10,7 @@ namespace Robust.Client.UserInterface.Controls
{
public Label Label { get; }
public Button() : base()
public Button()
{
AddStyleClass(StyleClassButton);
Label = new Label

View File

@@ -14,7 +14,7 @@ namespace Robust.Client.UserInterface.Controls
public Label Label { get; }
public TextureRect TextureRect { get; }
public CheckBox() : base()
public CheckBox()
{
ToggleMode = true;

View File

@@ -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();
}

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.UserInterface.Controls
public event EventHandler<ValueChangedEventArgs>? ValueChanged;
public SpinBox() : base()
public SpinBox()
{
MouseFilter = MouseFilterMode.Pass;

View File

@@ -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))

View File

@@ -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)
{

View File

@@ -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);
}
}
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;

View File

@@ -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

View File

@@ -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");

View File

@@ -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;

View File

@@ -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)),
}));
}

View File

@@ -17,5 +17,6 @@ namespace Robust.Server.GameStates
bool PvsEnabled { get; set; }
float PvsRange { get; set; }
void SetTransformNetId(ushort netId);
}
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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>();

View 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;
}
}

View File

@@ -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 />

View File

@@ -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;
}

View File

@@ -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 />

View File

@@ -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 { }
}

View File

@@ -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);
}
}
}
}

View File

@@ -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 { }
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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>
@@ -834,6 +833,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 +887,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 +942,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>

View File

@@ -44,7 +44,6 @@ namespace Robust.Shared.GameObjects
Vector2 linearVelocity,
float angularVelocity,
BodyType bodyType)
: base(NetIDs.PHYSICS)
{
CanCollide = canCollide;
SleepingAllowed = sleepingAllowed;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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")]

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -84,6 +84,7 @@ namespace Robust.Shared.GameObjects
return;
_paused = value;
EntityManager.EventBus.RaiseLocalEvent(Uid, new EntityPausedEvent(Uid, value));
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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; }

View File

@@ -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.

View File

@@ -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.

View File

@@ -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; }

View File

@@ -104,15 +104,47 @@ namespace Robust.Shared.GameObjects
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 BuildControllers()
{

View File

@@ -0,0 +1,10 @@
using System;
namespace Robust.Shared.GameStates
{
/// <summary>
/// This attribute marks a component as networked, so that it is replicated to clients.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class NetworkedComponentAttribute : Attribute { }
}

View File

@@ -25,6 +25,11 @@
/// </summary>
string SpriteName { get; }
/// <summary>
/// Path to the folder where the tile sprite is contained.
/// </summary>
string Path { get; }
/// <summary>
/// Physics objects that are interacting on this tile are slowed down by this float.
/// </summary>

View File

@@ -116,23 +116,6 @@ namespace Robust.Shared.Network
/// </summary>
event EventHandler<NetDisconnectedArgs> Disconnect;
#region StringTable
/// <summary>
/// Registers a NetMessage to be sent or received.
/// </summary>
/// <typeparam name="T">Type to register.</typeparam>
/// <param name="name">String ID of the message.</param>
/// <param name="rxCallback">Callback function to process the received message.</param>
/// <param name="accept">
/// The side of the network this message is accepted on.
/// If we are not on the side specified, the receive callback will not be registered even if provided.
/// </param>
[Obsolete("Use the method without a name argument instead")]
void RegisterNetMessage<T>(string name, ProcessMessage<T>? rxCallback = null,
NetMessageAccept accept = NetMessageAccept.Both)
where T : NetMessage;
/// <summary>
/// Registers a NetMessage to be sent or received.
/// </summary>
@@ -156,8 +139,5 @@ namespace Robust.Shared.Network
/// <typeparam name="T">Type of NetMessage to send.</typeparam>
/// <returns>Instance of the NetMessage.</returns>
T CreateNetMessage<T>() where T : NetMessage;
#endregion StringTable
}
}

View File

@@ -1,7 +1,5 @@
using Lidgren.Network;
#nullable disable
namespace Robust.Shared.Network.Messages
{
public class MsgPlayerListReq : NetMessage

View File

@@ -902,10 +902,11 @@ namespace Robust.Shared.Network
#region NetMessages
/// <inheritdoc />
public void RegisterNetMessage<T>(string name, ProcessMessage<T>? rxCallback = null,
public void RegisterNetMessage<T>(ProcessMessage<T>? rxCallback = null,
NetMessageAccept accept = NetMessageAccept.Both)
where T : NetMessage
where T : NetMessage, new()
{
var name = new T().MsgName;
var id = _strings.AddString(name);
_messages.Add(name, typeof(T));
@@ -928,14 +929,6 @@ namespace Robust.Shared.Network
CacheBlankFunction(typeof(T));
}
/// <inheritdoc />
public void RegisterNetMessage<T>(ProcessMessage<T>? rxCallback = null,
NetMessageAccept accept = NetMessageAccept.Both)
where T : NetMessage, new()
{
RegisterNetMessage(new T().MsgName, rxCallback, accept);
}
/// <inheritdoc />
public T CreateNetMessage<T>()
where T : NetMessage

View File

@@ -66,18 +66,6 @@ namespace Robust.Shared.Network
/// </summary>
public int MsgSize { get; set; }
/// <summary>
/// Constructs an instance of the NetMessage.
/// </summary>
/// <param name="name">String identifier of the message type.</param>
/// <param name="group">The group this message type belongs to.</param>
[Obsolete("Use NetMessageAttribute and no constructor instead")]
protected NetMessage(string name, MsgGroups group)
{
MsgName = name;
MsgGroup = group;
}
protected NetMessage()
{
MsgName = GetType().Name;

View File

@@ -151,7 +151,6 @@ namespace Robust.Shared.Physics.Collision.Shapes
m.R1C2 = modelMatrix.R1C2;
handle.SetTransform(m);
handle.DrawLine(Vertex1, Vertex2, handle.CalcWakeColor(handle.RectFillColor, sleepPercent));
handle.SetTransform(in Matrix3.Identity);
}
}
}

View File

@@ -82,7 +82,6 @@ namespace Robust.Shared.Physics.Collision.Shapes
handle.SetTransform(m);
handle.DrawRect(LocalBounds, handle.CalcWakeColor(handle.RectFillColor, sleepPercent));
handle.SetTransform(in Matrix3.Identity);
}
// TODO

View File

@@ -78,7 +78,6 @@ namespace Robust.Shared.Physics.Collision.Shapes
{
handle.SetTransform(in modelMatrix);
handle.DrawCircle(Vector2.Zero, _radius, handle.CalcWakeColor(handle.RectFillColor, sleepPercent));
handle.SetTransform(in Matrix3.Identity);
}
public bool Equals(IPhysShape? other)

View File

@@ -70,7 +70,6 @@ namespace Robust.Shared.Physics.Collision.Shapes
handle.DrawRect(localBox, handle.CalcWakeColor(handle.GridFillColor, sleepPercent));
}
}
handle.SetTransform(in Matrix3.Identity);
}
/// <summary>

View File

@@ -60,7 +60,6 @@ namespace Robust.Shared.Physics.Collision.Shapes
var rotationMatrix = Matrix3.CreateRotation(Math.PI);
handle.SetTransform(rotationMatrix * modelMatrix);
handle.DrawRect(Rectangle, handle.CalcWakeColor(handle.RectFillColor, sleepPercent));
handle.SetTransform(in Matrix3.Identity);
}
[field: NonSerialized]

View File

@@ -188,7 +188,6 @@ namespace Robust.Shared.Physics.Collision.Shapes
{
handle.SetTransform(modelMatrix);
handle.DrawPolygonShape(_vertices.ToArray(), handle.CalcWakeColor(handle.RectFillColor, sleepPercent));
handle.SetTransform(in Matrix3.Identity);
}
public static explicit operator PolygonShape(PhysShapeAabb aabb)

View File

@@ -12,6 +12,11 @@ namespace Robust.Shared
{
return contentStart ? "../../" : "../../../";
}
private static string FindEngineRootDir(bool contentStart)
{
return contentStart ? "../../RobustToolbox/" : "../../";
}
#endif
internal static void PrintRuntimeInfo(ISawmill sawmill)
@@ -20,17 +25,21 @@ namespace Robust.Shared
sawmill.Debug($"OS: {RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture}");
}
internal static void DoMounts(IResourceManagerInternal res, MountOptions? options, string contentBuildDir,
internal static void DoMounts(IResourceManagerInternal res, MountOptions? options, string contentBuildDir, ResourcePath assembliesPath, bool loadContentResources = true,
bool loader = false, bool contentStart = false)
{
#if FULL_RELEASE
if (!loader)
res.MountContentDirectory(@"Resources/");
#else
var contentRootDir = FindContentRootDir(contentStart);
res.MountContentDirectory($@"{contentRootDir}RobustToolbox/Resources/");
res.MountContentDirectory($@"{contentRootDir}bin/{contentBuildDir}/", new ResourcePath("/Assemblies/"));
res.MountContentDirectory($@"{contentRootDir}Resources/");
res.MountContentDirectory($@"{FindEngineRootDir(contentStart)}Resources/");
if (loadContentResources)
{
var contentRootDir = FindContentRootDir(contentStart);
res.MountContentDirectory($@"{contentRootDir}bin/{contentBuildDir}/", assembliesPath);
res.MountContentDirectory($@"{contentRootDir}Resources/");
}
#endif
if (options == null)

View File

@@ -12,10 +12,16 @@ namespace Robust.Shared.Serialization.Manager.Result
public abstract void CallAfterDeserializationHook();
public static DeserializationResult Value<T>(T value) where T : notnull
public static DeserializationResult Value<T>(T value)
{
var type = typeof(DeserializedValue<>).MakeGenericType(value.GetType());
return (DeserializationResult) Activator.CreateInstance(type, value)!;
return Value(typeof(T), value);
}
public static DeserializationResult Value(Type type, object? value)
{
var genericType = typeof(DeserializedValue<>).MakeGenericType(type);
return (DeserializationResult) Activator.CreateInstance(genericType, value)!;
}
public T Cast<T>() where T : DeserializationResult

View File

@@ -115,6 +115,8 @@ namespace Robust.Shared.Serialization.Manager
_copyByRefRegistrations.Add(type);
}
_copyByRefRegistrations.Add(typeof(Type));
_initialized = true;
_initializing = false;
}
@@ -299,6 +301,11 @@ namespace Robust.Shared.Serialization.Manager
public DeserializationResult Read(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false)
{
if (type.IsNullable() && node is ValueDataNode {Value: "null"})
{
return DeserializationResult.Value(type, null);
}
var underlyingType = type.EnsureNotNullableType();
if (underlyingType.IsArray)

View File

@@ -214,9 +214,9 @@ namespace Robust.Shared.Serialization
/// <seealso cref="OnClientCompleteHandshake"/>
private void NetworkInitialize()
{
_net.RegisterNetMessage<MsgMapStrServerHandshake>(nameof(MsgMapStrServerHandshake), HandleServerHandshake, NetMessageAccept.Client);
_net.RegisterNetMessage<MsgMapStrClientHandshake>(nameof(MsgMapStrClientHandshake), HandleClientHandshake, NetMessageAccept.Server);
_net.RegisterNetMessage<MsgMapStrStrings>(nameof(MsgMapStrStrings), HandleStringsMessage, NetMessageAccept.Client);
_net.RegisterNetMessage<MsgMapStrServerHandshake>(HandleServerHandshake, NetMessageAccept.Client);
_net.RegisterNetMessage<MsgMapStrClientHandshake>(HandleClientHandshake, NetMessageAccept.Server);
_net.RegisterNetMessage<MsgMapStrStrings>(HandleStringsMessage, NetMessageAccept.Client);
_net.Disconnect += NetOnDisconnect;
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Shared.Serialization.TypeSerializers.Implementations
{
[TypeSerializer]
public class TypeSerializer : ITypeSerializer<Type, ValueDataNode>
{
private static readonly Dictionary<string, Type> _shortcuts = new ()
{
{"bool", typeof(bool)}
};
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
if (_shortcuts.ContainsKey(node.Value)) return new ValidatedValueNode(node);
return dependencies.Resolve<IReflectionManager>().GetType(node.Value) == null
? new ErrorNode(node, $"Type '{node.Value}' not found.")
: new ValidatedValueNode(node);
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
{
if(_shortcuts.TryGetValue(node.Value, out var shortcutType)) return DeserializationResult.Value(shortcutType);
var type = dependencies.Resolve<IReflectionManager>().GetType(node.Value);
if (type == null) throw new InvalidMappingException($"Type '{node.Value}' not found.");
return DeserializationResult.Value(type);
}
public DataNode Write(ISerializationManager serializationManager, Type value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
return new ValueDataNode(value.FullName ?? value.Name);
}
public Type Copy(ISerializationManager serializationManager, Type source, Type target, bool skipHook,
ISerializationContext? context = null)
{
return source;
}
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using JetBrains.Annotations;
namespace Robust.Shared.Utility
{
@@ -37,6 +38,18 @@ namespace Robust.Shared.Utility
return dict;
}
public static bool TryGetValue<T>(this IList<T> list, int index, out T value)
{
if (list.Count > index)
{
value = list[index];
return true;
}
value = default!;
return false;
}
/// <summary>
/// Remove an item from the list, replacing it with the one at the very end of the list.
/// This means that the order will not be preserved, but it should be an O(1) operation.

View File

@@ -33,7 +33,7 @@ namespace Robust.UnitTesting
public string? ContentRootDir { get; set; }
public void Run(GameController.DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null)
public void Run(GameController.DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
{
}

View File

@@ -272,9 +272,9 @@ namespace Robust.UnitTesting
public event EventHandler<NetChannelArgs>? Connected;
public event EventHandler<NetDisconnectedArgs>? Disconnect;
public void RegisterNetMessage<T>(string name, ProcessMessage<T>? rxCallback = null,
NetMessageAccept accept = NetMessageAccept.Both) where T : NetMessage
public void RegisterNetMessage<T>(ProcessMessage<T>? rxCallback = null, NetMessageAccept accept = NetMessageAccept.Both) where T : NetMessage, new()
{
var name = new T().MsgName;
var thisSide = IsServer ? NetMessageAccept.Server : NetMessageAccept.Client;
_registeredMessages.Add(typeof(T));
@@ -282,11 +282,6 @@ namespace Robust.UnitTesting
_callbacks.Add(typeof(T), msg => rxCallback((T) msg));
}
public void RegisterNetMessage<T>(ProcessMessage<T>? rxCallback = null, NetMessageAccept accept = NetMessageAccept.Both) where T : NetMessage, new()
{
RegisterNetMessage(new T().MsgName, rxCallback, accept);
}
public T CreateNetMessage<T>() where T : NetMessage
{
var type = typeof(T);

View File

@@ -17,6 +17,7 @@ using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
@@ -296,7 +297,7 @@ namespace Robust.UnitTesting
{
_isSurelyIdle = false;
_currentTicksId += 1;
_toInstanceWriter.TryWrite(new RunTicksMessage(ticks, 1 / 60f, _currentTicksId));
_toInstanceWriter.TryWrite(new RunTicksMessage(ticks, _currentTicksId));
}
/// <summary>
@@ -422,7 +423,15 @@ namespace Robust.UnitTesting
var server = DependencyCollection.Resolve<BaseServer>();
server.LoadConfigAndUserData = false;
var serverOptions = _options != null ? _options.Options : new ServerOptions()
{
LoadConfigAndUserData = false,
LoadContentResources = false,
};
// Autoregister components if options are null or we're NOT starting from content.
if(!_options?.ContentStart ?? true)
IoCManager.Resolve<IComponentFactory>().DoAutoRegistrations();
if (_options?.ContentAssemblies != null)
{
@@ -447,7 +456,7 @@ namespace Robust.UnitTesting
var failureLevel = _options == null ? LogLevel.Error : _options.FailureLogLevel;
server.ContentStart = _options?.ContentStart ?? false;
if (server.Start(() => new TestLogHandler("SERVER", failureLevel)))
if (server.Start(serverOptions, () => new TestLogHandler("SERVER", failureLevel)))
{
throw new Exception("Server failed to start.");
}
@@ -535,13 +544,21 @@ namespace Robust.UnitTesting
var client = DependencyCollection.Resolve<GameController>();
var clientOptions = _options != null ? _options.Options : new GameControllerOptions()
{
LoadContentResources = false,
LoadConfigAndUserData = false,
};
// Autoregister components if options are null or we're NOT starting from content.
if(!_options?.ContentStart ?? true)
IoCManager.Resolve<IComponentFactory>().DoAutoRegistrations();
if (_options?.ContentAssemblies != null)
{
IoCManager.Resolve<TestingModLoader>().Assemblies = _options.ContentAssemblies;
}
client.LoadConfigAndUserData = false;
var cfg = IoCManager.Resolve<IConfigurationManagerInternal>();
if (_options != null)
@@ -556,15 +573,25 @@ namespace Robust.UnitTesting
}
}
cfg.OverrideConVars(new[] {(CVars.NetPredictLagBias.Name, "0")});
cfg.OverrideConVars(new[]
{
(CVars.NetPredictLagBias.Name, "0"),
// Connecting to Discord is a massive waste of time.
// Basically just makes the CI logs a mess.
(CVars.DiscordEnabled.Name, "false"),
// Avoid preloading textures.
(CVars.TexturePreloadingEnabled.Name, "false"),
});
GameLoop = new IntegrationGameLoop(DependencyCollection.Resolve<IGameTiming>(),
_fromInstanceWriter, _toInstanceReader);
var failureLevel = _options == null ? LogLevel.Error : _options.FailureLogLevel;
client.OverrideMainLoop(GameLoop);
client.ContentStart = true;
client.StartupSystemSplash(() => new TestLogHandler("CLIENT", failureLevel));
client.ContentStart = _options?.ContentStart ?? false;
client.StartupSystemSplash(clientOptions, () => new TestLogHandler("CLIENT", failureLevel));
client.StartupContinue(GameController.DisplayMode.Headless);
GameLoop.RunInit();
@@ -634,7 +661,7 @@ namespace Robust.UnitTesting
{
case RunTicksMessage msg:
_gameTiming.InSimulation = true;
var simFrameEvent = new FrameEventArgs(msg.Delta);
var simFrameEvent = new FrameEventArgs((float) _gameTiming.TickPeriod.TotalSeconds);
for (var i = 0; i < msg.Ticks && Running; i++)
{
Input?.Invoke(this, simFrameEvent);
@@ -674,10 +701,20 @@ namespace Robust.UnitTesting
public class ServerIntegrationOptions : IntegrationOptions
{
public virtual ServerOptions Options { get; set; } = new()
{
LoadConfigAndUserData = false,
LoadContentResources = false,
};
}
public class ClientIntegrationOptions : IntegrationOptions
{
public virtual GameControllerOptions Options { get; set; } = new()
{
LoadContentResources = false,
LoadConfigAndUserData = false,
};
}
public abstract class IntegrationOptions
@@ -698,15 +735,13 @@ namespace Robust.UnitTesting
/// </summary>
private sealed class RunTicksMessage
{
public RunTicksMessage(int ticks, float delta, int messageId)
public RunTicksMessage(int ticks, int messageId)
{
Ticks = ticks;
Delta = delta;
MessageId = messageId;
}
public int Ticks { get; }
public float Delta { get; }
public int MessageId { get; }
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
@@ -267,7 +267,6 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
var containerMan = entity.GetComponent<IContainerManager>();
var state = (ContainerManagerComponent.ContainerManagerComponentState)containerMan.GetComponentState(new Mock<ICommonSession>().Object);
Assert.That(state.NetID, Is.EqualTo(containerMan.NetID));
Assert.That(state.ContainerSet.Count, Is.EqualTo(1));
Assert.That(state.ContainerSet[0].Id, Is.EqualTo("dummy"));
Assert.That(state.ContainerSet[0].OccludesLight, Is.True);

View File

@@ -62,6 +62,8 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
[OneTimeSetUp]
public void Setup()
{
IoCManager.Resolve<IComponentFactory>().GenerateNetIds();
EntityManager = IoCManager.Resolve<IServerEntityManagerInternal>();
MapManager = IoCManager.Resolve<IMapManager>();
MapManager.CreateMap();

View File

@@ -41,6 +41,7 @@ namespace Robust.UnitTesting.Server.GameObjects
_componentFactory.RegisterClass<ThrowsInAddComponent>();
_componentFactory.RegisterClass<ThrowsInInitializeComponent>();
_componentFactory.RegisterClass<ThrowsInStartupComponent>();
_componentFactory.GenerateNetIds();
EntityManager = IoCManager.Resolve<IServerEntityManager>();
MapManager = IoCManager.Resolve<IMapManager>();

View File

@@ -84,6 +84,7 @@ entities:
{
var compFactory = IoCManager.Resolve<IComponentFactory>();
compFactory.RegisterClass<MapDeserializeTestComponent>();
compFactory.GenerateNetIds();
IoCManager.Resolve<ISerializationManager>().Initialize();
var resourceManager = IoCManager.Resolve<IResourceManagerInternal>();

View File

@@ -10,6 +10,7 @@ using Robust.Server.Reflection;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
@@ -190,6 +191,7 @@ namespace Robust.UnitTesting.Server
container.RegisterInstance<IGameTiming>(new Mock<IGameTiming>().Object); // TODO: get timing working similar to RobustIntegrationTest
//Tier 2: Simulation
container.RegisterInstance<IConsoleHost>(new Mock<IConsoleHost>().Object); //Console is technically a frontend, we want to run headless
container.Register<IEntityManager, EntityManager>();
container.Register<IMapManager, MapManager>();
container.Register<IEntityLookup, EntityLookup>();
@@ -226,6 +228,8 @@ namespace Robust.UnitTesting.Server
_regDelegate?.Invoke(compFactory);
compFactory.GenerateNetIds();
var entityMan = container.Resolve<IEntityManager>();
entityMan.Initialize();

View File

@@ -1,6 +1,7 @@
using System.IO;
using NUnit.Framework;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
@@ -61,6 +62,9 @@ namespace Robust.UnitTesting.Shared.ContentPack
[OneTimeSetUp]
public void Setup()
{
var componentFactory = IoCManager.Resolve<IComponentFactory>();
componentFactory.GenerateNetIds();
var stream = new MemoryStream(Data);
var resourceManager = IoCManager.Resolve<IResourceManagerInternal>();
resourceManager.MountStreamAt(stream, new ResourcePath("/a/b/c.dat"));

View File

@@ -0,0 +1,75 @@
using System.Threading.Tasks;
using NUnit.Framework;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using IPlayerManager = Robust.Server.Player.IPlayerManager;
namespace Robust.UnitTesting.Shared
{
[TestFixture]
public class EngineIntegrationTest_Test : RobustIntegrationTest
{
[Test]
public void ServerStartsCorrectlyTest()
{
ServerIntegrationInstance? server = null;
Assert.DoesNotThrow(() => server = StartServer());
Assert.That(server, Is.Not.Null);
}
[Test]
public void ClientStartsCorrectlyTest()
{
ClientIntegrationInstance? client = null;
Assert.DoesNotThrow(() => client = StartClient());
Assert.That(client, Is.Not.Null);
}
[Test]
public async Task ServerClientPairConnectCorrectlyTest()
{
var server = StartServer();
var client = StartClient();
Assert.That(server, Is.Not.Null);
Assert.That(client, Is.Not.Null);
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
// Connect client to the server...
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
client.Post(() => IoCManager.Resolve<IClientNetManager>().ClientConnect(null!, 0, null!));
// Run 10 synced ticks...
for (int i = 0; i < 10; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
await server.WaitAssertion(() =>
{
var playerManager = IoCManager.Resolve<IPlayerManager>();
// There must be a player connected.
Assert.That(playerManager.PlayerCount, Is.EqualTo(1));
// Get the only player...
var player = playerManager.GetAllPlayers()[0];
Assert.That(player.Status, Is.EqualTo(SessionStatus.Connected));
Assert.That(player.ConnectedClient.IsConnected, Is.True);
});
await client.WaitAssertion(() =>
{
var netManager = IoCManager.Resolve<IClientNetManager>();
Assert.That(netManager.IsConnected, Is.True);
Assert.That(netManager.ServerChannel, Is.Not.Null);
Assert.That(netManager.ServerChannel!.IsConnected, Is.True);
});
}
}
}

View File

@@ -164,6 +164,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
componentFactory.RegisterClass<TestFiveComponent>();
componentFactory.RegisterClass<TestSixComponent>();
componentFactory.RegisterClass<TestSevenComponent>();
componentFactory.GenerateNetIds();
IoCManager.Resolve<ISerializationManager>().Initialize();
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();

View File

@@ -1,6 +1,7 @@
using System.Linq;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.UnitTesting.Server;
@@ -10,7 +11,6 @@ namespace Robust.UnitTesting.Shared.GameObjects
[TestFixture, Parallelizable ,TestOf(typeof(ComponentManager))]
public class ComponentManager_Tests
{
private const uint CompNetId = 3;
private static readonly EntityCoordinates DefaultCoords = new(new EntityUid(1), Vector2.Zero);
[Test]
@@ -98,13 +98,17 @@ namespace Robust.UnitTesting.Shared.GameObjects
{
// Arrange
var sim = SimulationFactory();
var factory = sim.Resolve<IComponentFactory>();
var netId = factory.GetRegistration<DummyComponent>().NetID!;
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
entity.AddComponent<DummyComponent>();
// Act
var result = manager.HasComponent(entity.Uid, CompNetId);
var result = manager.HasComponent(entity.Uid, netId.Value);
// Assert
Assert.That(result, Is.True);
@@ -115,13 +119,17 @@ namespace Robust.UnitTesting.Shared.GameObjects
{
// Arrange
var sim = SimulationFactory();
var factory = sim.Resolve<IComponentFactory>();
var netId = factory.GetRegistration<DummyComponent>().NetID!;
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
var result = manager.GetComponent(entity.Uid, CompNetId);
var result = manager.GetComponent(entity.Uid, netId.Value);
// Assert
Assert.That(result, Is.EqualTo(component));
@@ -150,13 +158,17 @@ namespace Robust.UnitTesting.Shared.GameObjects
{
// Arrange
var sim = SimulationFactory();
var factory = sim.Resolve<IComponentFactory>();
var netId = factory.GetRegistration<DummyComponent>().NetID!;
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
var result = manager.TryGetComponent(entity.Uid, CompNetId, out var comp);
var result = manager.TryGetComponent(entity.Uid, netId.Value, out var comp);
// Assert
Assert.That(result, Is.True);
@@ -186,13 +198,17 @@ namespace Robust.UnitTesting.Shared.GameObjects
{
// Arrange
var sim = SimulationFactory();
var factory = sim.Resolve<IComponentFactory>();
var netId = factory.GetRegistration<DummyComponent>().NetID!;
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
manager.RemoveComponent(entity.Uid, CompNetId);
manager.RemoveComponent(entity.Uid, netId.Value);
manager.CullRemovedComponents();
// Assert
@@ -269,10 +285,10 @@ namespace Robust.UnitTesting.Shared.GameObjects
return sim;
}
[NetworkedComponent()]
private class DummyComponent : Component, ICompType1, ICompType2
{
public override string Name => "Dummy";
public override uint? NetID => CompNetId;
}
private interface ICompType1 { }

Some files were not shown because too many files have changed in this diff Show More