Merge branch 'master' into robust-client-CEF

This commit is contained in:
Pieter-Jan Briers
2021-07-04 19:56:41 +02:00
51 changed files with 659 additions and 646 deletions

View File

@@ -628,10 +628,15 @@ namespace Robust.Client.Audio.Midi
void IMidiRenderer.InternalDispose()
{
Source?.Dispose();
_driver?.Dispose();
// Do NOT dispose of the sequencer after the synth or it'll cause a segfault for some fucking reason.
_sequencer?.UnregisterClient(_debugRegister);
_sequencer?.UnregisterClient(_synthRegister);
_sequencer?.Dispose();
_synth?.Dispose();
_player?.Dispose();
_driver?.Dispose();
_sequencer?.Dispose();
}
}
}

View File

@@ -0,0 +1,19 @@
#if DEBUG
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
internal sealed class LightDebugCommand : IConsoleCommand
{
public string Command => "lightbb";
public string Description => "Toggles whether to show light bounding boxes";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
}
}
}
#endif

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

@@ -3,8 +3,10 @@ using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Animations;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
@@ -168,6 +170,8 @@ namespace Robust.Client.GameObjects
get => _radius;
set
{
if (MathHelper.CloseTo(value, _radius)) return;
_radius = MathF.Max(value, 0.01f); // setting radius to 0 causes exceptions, so just use a value close enough to zero that it's unnoticeable.
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
}
@@ -181,17 +185,8 @@ namespace Robust.Client.GameObjects
Mask = null;
}
/// <summary>
/// What MapId we are intersecting for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
/// <summary>
/// What grids we're on for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal List<GridId> IntersectingGrids = new();
internal RenderingTreeComponent? RenderTree { get; set; }
void ISerializationHooks.AfterDeserialization()
{

View File

@@ -131,17 +131,8 @@ namespace Robust.Client.GameObjects
[DataField("directional")]
private bool _directional = true;
/// <summary>
/// What MapId we are intersecting for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
/// <summary>
/// What grids we're on for RenderingTreeSystem.
/// </summary>
[ViewVariables]
internal List<GridId> IntersectingGrids { get; } = new();
internal RenderingTreeComponent? RenderTree { get; set; } = null;
[DataField("layerDatums")]
private List<PrototypeLayerData> LayerDatums

View File

@@ -0,0 +1,43 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
public sealed class RenderingTreeComponent : Component
{
public override string Name => "RenderingTree";
internal DynamicTree<SpriteComponent> SpriteTree { get; private set; } = new(SpriteAabbFunc);
internal DynamicTree<PointLightComponent> LightTree { get; private set; } = new(LightAabbFunc);
private static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
return new Box2(worldPos, worldPos);
}
private static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
}
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
return new Box2(worldPos.Value, worldPos.Value);
}
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos.Value, (boxSize, boxSize));
}
}
}

View File

@@ -0,0 +1,84 @@
#if DEBUG
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
internal sealed class DebugLightTreeSystem : EntitySystem
{
private DebugLightOverlay? _lightOverlay;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_enabled)
{
_lightOverlay = new DebugLightOverlay(
IoCManager.Resolve<IEntityLookup>(),
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IMapManager>(),
Get<RenderingTreeSystem>());
overlayManager.AddOverlay(_lightOverlay);
}
else
{
overlayManager.RemoveOverlay(_lightOverlay!);
_lightOverlay = null;
}
}
}
private bool _enabled;
private sealed class DebugLightOverlay : Overlay
{
private IEntityLookup _lookup;
private IEyeManager _eyeManager;
private IMapManager _mapManager;
private RenderingTreeSystem _tree;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
{
_lookup = lookup;
_eyeManager = eyeManager;
_mapManager = mapManager;
_tree = tree;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var map = _eyeManager.CurrentMap;
if (map == MapId.Nullspace) return;
var viewport = _eyeManager.GetWorldViewport();
foreach (var tree in _tree.GetLightTrees(map, viewport))
{
foreach (var light in tree)
{
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
if (!aabb.Intersects(viewport)) continue;
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
}
}
}
}
}
}
#endif

View File

@@ -3,8 +3,11 @@ using System.Drawing;
using System.Linq;
using JetBrains.Annotations;
using Robust.Client.Physics;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
@@ -18,25 +21,58 @@ namespace Robust.Client.GameObjects
[UsedImplicitly]
public sealed class RenderingTreeSystem : EntitySystem
{
internal const string LoggerSawmill = "rendertree";
// Nullspace is not indexed. Keep that in mind.
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
private readonly Dictionary<MapId, Dictionary<GridId, MapTrees>> _gridTrees = new();
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly List<SpriteComponent> _spriteQueue = new();
private readonly List<PointLightComponent> _lightQueue = new();
private HashSet<EntityUid> _checkedChildren = new();
internal DynamicTree<SpriteComponent> GetSpriteTreeForMap(MapId map, GridId grid)
/// <summary>
/// <see cref="CVars.MaxLightRadius"/>
/// </summary>
public float MaxLightRadius { get; private set; }
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2 worldAABB)
{
return _gridTrees[map][grid].SpriteTree;
if (mapId == MapId.Nullspace) yield break;
var enclosed = false;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
// if we're enclosed then we know no other grids relevant + don't need the map's rendertree
if (grid.WorldBounds.Encloses(in worldAABB))
{
enclosed = true;
break;
}
}
if (!enclosed)
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
}
internal DynamicTree<PointLightComponent> GetLightTreeForMap(MapId map, GridId grid)
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
{
return _gridTrees[map][grid].LightTree;
foreach (var comp in GetRenderTrees(mapId, worldAABB))
{
yield return comp.SpriteTree;
}
}
internal IEnumerable<DynamicTree<PointLightComponent>> GetLightTrees(MapId mapId, Box2 worldAABB)
{
foreach (var comp in GetRenderTrees(mapId, worldAABB))
{
yield return comp.LightTree;
}
}
public override void Initialize()
@@ -48,9 +84,7 @@ namespace Robust.Client.GameObjects
UpdatesAfter.Add(typeof(PhysicsSystem));
_mapManager.MapCreated += MapManagerOnMapCreated;
_mapManager.MapDestroyed += MapManagerOnMapDestroyed;
_mapManager.OnGridCreated += MapManagerOnGridCreated;
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
// Due to how recursion works, this must be done.
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
@@ -65,6 +99,11 @@ namespace Robust.Client.GameObjects
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
SubscribeLocalEvent<PointLightComponent, RenderTreeRemoveLightEvent>(RemoveLight);
SubscribeLocalEvent<PointLightComponent, PointLightUpdateEvent>(HandleLightUpdate);
SubscribeLocalEvent<RenderingTreeComponent, ComponentRemove>(HandleTreeRemove);
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.MaxLightRadius, value => MaxLightRadius = value, true);
}
private void HandleLightUpdate(EntityUid uid, PointLightComponent component, PointLightUpdateEvent args)
@@ -88,12 +127,12 @@ namespace Robust.Client.GameObjects
{
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
if (!_checkedChildren.Add(sender.Owner.Uid) ||
sender.Owner.HasComponent<MapGridComponent>() ||
sender.Owner.HasComponent<MapComponent>()) return;
sender.Owner.HasComponent<RenderingTreeComponent>()) return;
// This recursive search is needed, as MoveEvent is defined to not care about indirect events like children.
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
// (Struct-based events ok though)
// Ironically this was lagging the GC lolz
if (sender.Owner.TryGetComponent(out SpriteComponent? sprite))
QueueSpriteUpdate(sprite);
@@ -129,16 +168,10 @@ namespace Robust.Client.GameObjects
private void ClearSprite(SpriteComponent component)
{
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
{
foreach (var gridId in component.IntersectingGrids)
{
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
tree.SpriteTree.Remove(component);
}
}
if (component.RenderTree == null) return;
component.IntersectingGrids.Clear();
component.RenderTree.SpriteTree.Remove(component);
component.RenderTree = null;
}
private void QueueSpriteUpdate(SpriteComponent component)
@@ -173,16 +206,10 @@ namespace Robust.Client.GameObjects
private void ClearLight(PointLightComponent component)
{
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
{
foreach (var gridId in component.IntersectingGrids)
{
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
tree.LightTree.Remove(component);
}
}
if (component.RenderTree == null) return;
component.IntersectingGrids.Clear();
component.RenderTree.LightTree.Remove(component);
component.RenderTree = null;
}
private void QueueLightUpdate(PointLightComponent component)
@@ -198,31 +225,23 @@ namespace Robust.Client.GameObjects
{
base.Shutdown();
_mapManager.MapCreated -= MapManagerOnMapCreated;
_mapManager.MapDestroyed -= MapManagerOnMapDestroyed;
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
_mapManager.OnGridRemoved -= MapManagerOnGridRemoved;
}
private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e)
private void HandleTreeRemove(EntityUid uid, RenderingTreeComponent component, ComponentRemove args)
{
foreach (var (_, gridTree) in _gridTrees[e.Map])
foreach (var sprite in component.SpriteTree)
{
foreach (var comp in gridTree.LightTree)
{
comp.IntersectingGrids.Clear();
}
foreach (var comp in gridTree.SpriteTree)
{
comp.IntersectingGrids.Clear();
}
// Just in case?
gridTree.LightTree.Clear();
gridTree.SpriteTree.Clear();
sprite.RenderTree = null;
}
_gridTrees.Remove(e.Map);
foreach (var light in component.LightTree)
{
light.RenderTree = null;
}
component.SpriteTree.Clear();
component.LightTree.Clear();
}
private void MapManagerOnMapCreated(object? sender, MapEventArgs e)
@@ -232,35 +251,31 @@ namespace Robust.Client.GameObjects
return;
}
_gridTrees.Add(e.Map, new Dictionary<GridId, MapTrees>
{
{GridId.Invalid, new MapTrees()}
});
_mapManager.GetMapEntity(e.Map).EnsureComponent<RenderingTreeComponent>();
}
private void MapManagerOnGridCreated(MapId mapId, GridId gridId)
{
_gridTrees[mapId].Add(gridId, new MapTrees());
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
}
private void MapManagerOnGridRemoved(MapId mapId, GridId gridId)
private RenderingTreeComponent? GetRenderTree(IEntity entity)
{
var gridTree = _gridTrees[mapId][gridId];
foreach (var sprite in gridTree.SpriteTree)
if (entity.Transform.MapID == MapId.Nullspace ||
entity.HasComponent<RenderingTreeComponent>()) return null;
var parent = entity.Transform.Parent?.Owner;
while (true)
{
sprite.IntersectingGrids.Remove(gridId);
if (parent == null) break;
if (parent.TryGetComponent(out RenderingTreeComponent? comp)) return comp;
parent = parent.Transform.Parent?.Owner;
}
foreach (var light in gridTree.LightTree)
{
light.IntersectingGrids.Remove(gridId);
}
// Clear in case
gridTree.LightTree.Clear();
gridTree.SpriteTree.Clear();
_gridTrees[mapId].Remove(gridId);
return null;
}
public override void FrameUpdate(float frameTime)
@@ -270,55 +285,43 @@ namespace Robust.Client.GameObjects
foreach (var sprite in _spriteQueue)
{
sprite.TreeUpdateQueued = false;
var mapId = sprite.Owner.Transform.MapID;
if (!sprite.Visible || sprite.ContainerOccluded)
{
ClearSprite(sprite);
continue;
}
// If we're on a new map then clear the old one.
if (sprite.IntersectingMapId != mapId)
var oldMapTree = sprite.RenderTree;
var newMapTree = GetRenderTree(sprite.Owner);
// TODO: Temp PVS guard
var worldPos = sprite.Owner.Transform.WorldPosition;
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
{
ClearSprite(sprite);
continue;
}
sprite.IntersectingMapId = mapId;
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos).Translated(-treePos);
if (mapId == MapId.Nullspace) continue;
var mapTree = _gridTrees[mapId];
var aabb = MapTrees.SpriteAabbFunc(sprite);
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
// Remove from old
foreach (var gridId in sprite.IntersectingGrids)
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
{
if (intersectingGrids.Contains(gridId)) continue;
mapTree[gridId].SpriteTree.Remove(sprite);
ClearSprite(sprite);
newMapTree?.SpriteTree.Add(sprite, aabb);
}
// Rebuild in the update below
sprite.IntersectingGrids.Clear();
// Update / add to new
foreach (var gridId in intersectingGrids)
else
{
var translated = aabb.Translated(gridId == GridId.Invalid
? Vector2.Zero
: -_mapManager.GetGrid(gridId).WorldPosition);
mapTree[gridId].SpriteTree.AddOrUpdate(sprite, translated);
sprite.IntersectingGrids.Add(gridId);
newMapTree?.SpriteTree.Update(sprite, aabb);
}
sprite.RenderTree = newMapTree;
}
foreach (var light in _lightQueue)
{
light.TreeUpdateQueued = false;
var mapId = light.Owner.Transform.MapID;
if (!light.Enabled || light.ContainerOccluded)
{
@@ -326,72 +329,44 @@ namespace Robust.Client.GameObjects
continue;
}
// If we're on a new map then clear the old one.
if (light.IntersectingMapId != mapId)
var oldMapTree = light.RenderTree;
var newMapTree = GetRenderTree(light.Owner);
// TODO: Temp PVS guard
var worldPos = light.Owner.Transform.WorldPosition;
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
{
ClearLight(light);
continue;
}
light.IntersectingMapId = mapId;
if (mapId == MapId.Nullspace) continue;
var mapTree = _gridTrees[mapId];
var aabb = MapTrees.LightAabbFunc(light);
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
// Remove from old
foreach (var gridId in light.IntersectingGrids)
// TODO: Events need a bit of cleanup so we only validate this on initialize and radius changed events
// this is fine for now IMO as it's 1 float check for every light that moves
if (light.Radius > MaxLightRadius)
{
if (intersectingGrids.Contains(gridId)) continue;
mapTree[gridId].LightTree.Remove(light);
Logger.WarningS(LoggerSawmill, $"Light radius for {light.Owner} set above max radius of {MaxLightRadius}. This may lead to pop-in.");
}
// Rebuild in the update below
light.IntersectingGrids.Clear();
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos).Translated(-treePos);
// Update / add to new
foreach (var gridId in intersectingGrids)
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
{
var translated = aabb.Translated(gridId == GridId.Invalid
? Vector2.Zero
: -_mapManager.GetGrid(gridId).WorldPosition);
mapTree[gridId].LightTree.AddOrUpdate(light, translated);
light.IntersectingGrids.Add(gridId);
ClearLight(light);
newMapTree?.LightTree.Add(light, aabb);
}
else
{
newMapTree?.LightTree.Update(light, aabb);
}
light.RenderTree = newMapTree;
}
_spriteQueue.Clear();
_lightQueue.Clear();
}
private sealed class MapTrees
{
public readonly DynamicTree<SpriteComponent> SpriteTree;
public readonly DynamicTree<PointLightComponent> LightTree;
public MapTrees()
{
SpriteTree = new DynamicTree<SpriteComponent>(SpriteAabbFunc);
LightTree = new DynamicTree<PointLightComponent>(LightAabbFunc);
}
internal static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
return new Box2(worldPos, worldPos);
}
internal static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
}
}
}
internal class RenderTreeRemoveLightEvent : EntityEventArgs

View File

@@ -50,13 +50,11 @@ namespace Robust.Client.GameObjects
return;
}
foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true))
foreach (var comp in _treeSystem.GetRenderTrees(currentMap, pvsBounds))
{
var gridBounds = gridId == GridId.Invalid ? pvsBounds : pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
var bounds = pvsBounds.Translated(-comp.Owner.Transform.WorldPosition);
var mapTree = _treeSystem.GetSpriteTreeForMap(currentMap, gridId);
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
comp.SpriteTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
{
if (value.IsInert)
{
@@ -65,7 +63,7 @@ namespace Robust.Client.GameObjects
value.FrameUpdate(state);
return true;
}, gridBounds, approx: true);
}, bounds, true);
}
}
}

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects

View File

@@ -210,6 +210,11 @@ namespace Robust.Client.GameStates
ResetPredictedEntities(_timing.CurTick);
}
if (!curState.Extrapolated)
{
_processor.UpdateFullRep(curState);
}
// Store last tick we got from the GameStateProcessor.
_lastProcessedTick = _timing.CurTick;
@@ -422,6 +427,7 @@ namespace Robust.Client.GameStates
//Known entities
if (_entities.TryGetEntity(es.Uid, out var entity))
{
// Logger.Debug($"[{IGameTiming.TickStampStatic}] MOD {es.Uid}");
toApply.Add(entity, (es, null));
}
else //Unknown entities
@@ -432,6 +438,7 @@ namespace Robust.Client.GameStates
{
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
}
// Logger.Debug($"[{IGameTiming.TickStampStatic}] CREATE {es.Uid} {metaState.PrototypeId}");
var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid);
toApply.Add(newEntity, (es, null));
toInitialize.Add(newEntity);
@@ -471,6 +478,7 @@ namespace Robust.Client.GameStates
foreach (var id in deletions)
{
// Logger.Debug($"[{IGameTiming.TickStampStatic}] DELETE {id}");
_entities.DeleteEntity(id);
}

View File

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

View File

@@ -366,24 +366,11 @@ namespace Robust.Client.Graphics.Clyde
private void ProcessSpriteEntities(MapId map, Box2 worldBounds,
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
{
var spriteSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
{
Box2 gridBounds;
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
if (gridId == GridId.Invalid)
{
gridBounds = worldBounds;
}
else
{
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var tree = spriteSystem.GetSpriteTreeForMap(map, gridId);
tree.QueryAabb(ref list, ((
comp.SpriteTree.QueryAabb(ref list, ((
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
in SpriteComponent value) =>
{
@@ -398,7 +385,7 @@ namespace Robust.Client.Graphics.Clyde
entry.yWorldPos = worldPos.Y;
return true;
}), gridBounds, approx: true);
}), bounds, true);
}
}

View File

@@ -494,24 +494,16 @@ namespace Robust.Client.Graphics.Clyde
GetLightsToRender(MapId map, in Box2 worldBounds)
{
var renderingTreeSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
var enlargedBounds = worldBounds.Enlarged(renderingTreeSystem.MaxLightRadius);
// Use worldbounds for this one as we only care if the light intersects our actual bounds
var state = (this, worldBounds, count: 0);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
{
Box2 gridBounds;
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
if (gridId == GridId.Invalid)
{
gridBounds = worldBounds;
}
else
{
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var lightTree = renderingTreeSystem.GetLightTreeForMap(map, gridId);
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
{
var transform = light.Owner.Transform;
@@ -535,7 +527,7 @@ namespace Robust.Client.Graphics.Clyde
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
return true;
}, gridBounds);
}, bounds);
}
if (state.count > _maxLightsPerScene)

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

@@ -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,7 +342,7 @@ 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();
@@ -492,7 +499,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

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

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

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

@@ -211,6 +211,21 @@ namespace Robust.Shared
public static readonly CVarDef<bool> LogRuntimeLog =
CVarDef.Create("log.runtimelog", true, CVar.ARCHIVE | CVar.SERVERONLY);
/*
* Light
*/
/// <summary>
/// This is the maximum the viewport is enlarged to check for any intersecting render-trees for lights.
/// This should be set to your maximum light radius.
/// </summary>
/// <remarks>
/// If this value is too small it just means there may be pop-in where a light is located on a render-tree
/// outside of our viewport.
/// </remarks>
public static readonly CVarDef<float> MaxLightRadius =
CVarDef.Create("light.max_radius", 20.0f, CVar.CLIENTONLY);
/*
* LOKI
*/

View File

@@ -186,14 +186,14 @@ namespace Robust.Shared.GameObjects
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveComponent(EntityUid uid, Type type)
{
RemoveComponentDeferred((Component) GetComponent(uid, type), uid, false);
RemoveComponentDeferred((Component)GetComponent(uid, type), uid, false);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveComponent(EntityUid uid, uint netId)
{
RemoveComponentDeferred((Component) GetComponent(uid, netId), uid, false);
RemoveComponentDeferred((Component)GetComponent(uid, netId), uid, false);
}
/// <inheritdoc />
@@ -205,7 +205,7 @@ namespace Robust.Shared.GameObjects
if (component.Owner == null || component.Owner.Uid != uid)
throw new InvalidOperationException("Component is not owned by entity.");
RemoveComponentDeferred((Component) component, uid, false);
RemoveComponentDeferred((Component)component, uid, false);
}
private static IEnumerable<Component> InSafeOrder(IEnumerable<Component> comps, bool forCreation = false)
@@ -256,26 +256,26 @@ namespace Robust.Shared.GameObjects
try
{
#endif
// these two components are required on all entities and cannot be removed normally.
if (!removeProtected && (component is ITransformComponent || component is IMetaDataComponent))
{
DebugTools.Assert("Tried to remove a protected component.");
return;
}
// these two components are required on all entities and cannot be removed normally.
if (!removeProtected && (component is ITransformComponent || component is IMetaDataComponent))
{
DebugTools.Assert("Tried to remove a protected component.");
return;
}
if (!_deleteSet.Add(component))
{
// already deferred deletion
return;
}
if (!_deleteSet.Add(component))
{
// already deferred deletion
return;
}
if(component.Running)
component.LifeShutdown();
if (component.Running)
component.LifeShutdown();
if (component.LifeStage != ComponentLifeStage.PreAdd)
component.LifeRemoveFromEntity();
_componentDependencyManager.OnComponentRemove(uid, component);
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, uid));
if (component.LifeStage != ComponentLifeStage.PreAdd)
component.LifeRemoveFromEntity();
_componentDependencyManager.OnComponentRemove(uid, component);
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, uid));
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
@@ -299,10 +299,10 @@ namespace Robust.Shared.GameObjects
return;
}
if(component.Running)
if (component.Running)
component.LifeShutdown();
if(component.LifeStage != ComponentLifeStage.PreAdd)
if (component.LifeStage != ComponentLifeStage.PreAdd)
component.LifeRemoveFromEntity(); // Sets delete
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, component.Owner.Uid));
@@ -377,7 +377,7 @@ namespace Robust.Shared.GameObjects
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T GetComponent<T>(EntityUid uid)
{
return (T) GetComponent(uid, typeof(T));
return (T)GetComponent(uid, typeof(T));
}
/// <inheritdoc />
@@ -409,7 +409,7 @@ namespace Robust.Shared.GameObjects
{
if (!comp.Deleted)
{
component = (T) comp;
component = (T)comp;
return true;
}
}
@@ -489,7 +489,7 @@ namespace Robust.Shared.GameObjects
{
if (comp.Deleted || !includePaused && comp.Paused) continue;
yield return (T) (object) comp;
yield return (T)(object)comp;
}
}
@@ -510,7 +510,7 @@ namespace Robust.Shared.GameObjects
if (!trait2.TryGetValue(uid, out var t2Comp) || t2Comp.Deleted || !includePaused && kvComp.Value.Paused)
continue;
yield return ((TComp1) (object) kvComp.Value, (TComp2) (object) t2Comp);
yield return ((TComp1)(object)kvComp.Value, (TComp2)(object)t2Comp);
}
}
@@ -534,9 +534,9 @@ namespace Robust.Shared.GameObjects
if (!trait3.TryGetValue(uid, out var t3Comp) || t3Comp.Deleted)
continue;
yield return ((TComp1) (object) kvComp.Value,
(TComp2) (object) t2Comp,
(TComp3) (object) t3Comp);
yield return ((TComp1)(object)kvComp.Value,
(TComp2)(object)t2Comp,
(TComp3)(object)t3Comp);
}
}
@@ -565,10 +565,10 @@ namespace Robust.Shared.GameObjects
if (!trait4.TryGetValue(uid, out var t4Comp) || t4Comp.Deleted)
continue;
yield return ((TComp1) (object) kvComp.Value,
(TComp2) (object) t2Comp,
(TComp3) (object) t3Comp,
(TComp4) (object) t4Comp);
yield return ((TComp1)(object)kvComp.Value,
(TComp2)(object)t2Comp,
(TComp3)(object)t3Comp,
(TComp4)(object)t4Comp);
}
}

View File

@@ -1,10 +1,8 @@
using System;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -81,7 +79,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);
@@ -117,7 +114,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public override ComponentState GetComponentState(ICommonSession player)
{
return new MapGridComponentState(_gridIndex, Grid.HasGravity);
return new MapGridComponentState(_gridIndex);
}
/// <inheritdoc />
@@ -125,11 +122,10 @@ namespace Robust.Shared.GameObjects
{
base.HandleComponentState(curState, nextState);
if (!(curState is MapGridComponentState state))
if (curState is not MapGridComponentState state)
return;
_gridIndex = state.GridIndex;
Grid.HasGravity = state.HasGravity;
}
}
@@ -144,17 +140,14 @@ namespace Robust.Shared.GameObjects
/// </summary>
public GridId GridIndex { get; }
public bool HasGravity { get; }
/// <summary>
/// Constructs a new instance of <see cref="MapGridComponentState"/>.
/// </summary>
/// <param name="gridIndex">Index of the grid this component is linked to.</param>
public MapGridComponentState(GridId gridIndex, bool hasGravity)
public MapGridComponentState(GridId gridIndex)
: base(NetIDs.MAP_GRID)
{
GridIndex = gridIndex;
HasGravity = hasGravity;
}
}
}

View File

@@ -113,9 +113,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.

View File

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

View File

@@ -1,14 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Prometheus;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -157,9 +154,8 @@ namespace Robust.Shared.GameObjects
{
var newEntity = CreateEntity(prototypeName);
if (TryGetEntity(coordinates.EntityId, out var entity))
if (coordinates.IsValid(this))
{
newEntity.Transform.AttachParent(entity);
newEntity.Transform.Coordinates = coordinates;
}
@@ -228,12 +224,7 @@ namespace Robust.Shared.GameObjects
return false;
}
[Obsolete("IEntityQuery is obsolete")]
public IEnumerable<IEntity> GetEntities(IEntityQuery query)
{
return query.Match(this);
}
/// <inheritdoc />
public IEnumerable<IEntity> GetEntities()
{
// Need to do an iterator loop to avoid issues with concurrent access.

View File

@@ -1,178 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.Physics;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// An entity query that will let all entities pass.
/// This is the same as matching <c>ITransformComponent</c>, but faster.
/// </summary>
[PublicAPI, Obsolete]
public class AllEntityQuery : IEntityQuery
{
/// <inheritdoc />
public bool Match(IEntity entity) => true;
/// <inheritdoc />
public IEnumerable<IEntity> Match(IEntityManager entityMan)
{
return entityMan.GetEntities();
}
}
/// <summary>
/// An entity query which will match entities based on a predicate.
/// If you only want a single type of Component, use <c>TypeEntityQuery</c>.
/// </summary>
[PublicAPI, Obsolete]
public class PredicateEntityQuery : IEntityQuery
{
private readonly Predicate<IEntity> Predicate;
/// <summary>
/// Constructs a new instance of <c>PredicateEntityQuery</c>.
/// </summary>
/// <param name="predicate"></param>
public PredicateEntityQuery(Predicate<IEntity> predicate)
{
if (predicate == null)
throw new ArgumentNullException(nameof(predicate));
Predicate = predicate;
}
/// <inheritdoc />
public bool Match(IEntity entity) => Predicate(entity);
/// <inheritdoc />
public IEnumerable<IEntity> Match(IEntityManager entityMan)
{
return entityMan.GetEntities().Where(entity => Predicate(entity));
}
}
/// <summary>
/// An entity query that will match one type of component.
/// This the fastest and most common query, and should be the default choice.
/// </summary>
[PublicAPI, Obsolete]
public class TypeEntityQuery : IEntityQuery
{
private readonly Type ComponentType;
/// <summary>
/// Constructs a new instance of <c>TypeEntityQuery</c>.
/// </summary>
/// <param name="componentType">Type of the component to match.</param>
public TypeEntityQuery(Type componentType)
{
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(componentType), "componentType must inherit IComponent");
ComponentType = componentType;
}
/// <inheritdoc />
public bool Match(IEntity entity) => entity.HasComponent(ComponentType);
/// <inheritdoc />
public IEnumerable<IEntity> Match(IEntityManager entityMan)
{
return entityMan.ComponentManager.GetAllComponents(ComponentType, true).Select(component => component.Owner);
}
}
/// <summary>
/// An entity query that will match one type of component.
/// This the fastest and most common query, and should be the default choice.
/// </summary>
/// <typeparamref name="T">Type of component to match.</typeparamref>
[PublicAPI, Obsolete]
public class TypeEntityQuery<T> : IEntityQuery where T : IComponent
{
public bool Match(IEntity entity) => entity.HasComponent<T>();
public IEnumerable<IEntity> Match(IEntityManager entityMan)
{
return entityMan.ComponentManager.EntityQuery<T>(true).Select(component => component.Owner);
}
}
/// <summary>
/// An entity query that will match all entities that intersect with the argument entity.
/// </summary>
[PublicAPI, Obsolete]
public class IntersectingEntityQuery : IEntityQuery
{
private readonly IEntity Entity;
/// <summary>
/// Constructs a new instance of <c>TypeEntityQuery</c>.
/// </summary>
/// <param name="componentType">Type of the component to match.</param>
public IntersectingEntityQuery(IEntity entity)
{
Entity = entity;
}
/// <inheritdoc />
public bool Match(IEntity entity)
{
if(Entity.TryGetComponent<IPhysBody>(out var physics))
{
return physics.Owner.Transform.MapID == entity.Transform.MapID && physics.GetWorldAABB().Contains(entity.Transform.WorldPosition);
}
return false;
}
public IEnumerable<IEntity> Match(IEntityManager entityMan)
{
return entityMan.GetEntities().Where(entity => Match(entity));
}
}
/// <summary>
/// An entity query that will match entities that have all of the provided components.
/// </summary>
[PublicAPI, Obsolete]
public class MultipleTypeEntityQuery : IEntityQuery
{
private readonly List<Type> ComponentTypes;
/// <summary>
/// Constructs a new instance of <c>MultipleTypeEntityQuery</c>.
/// </summary>
/// <param name="componentTypes">List of component types to match.</param>
public MultipleTypeEntityQuery(List<Type> componentTypes)
{
foreach (var componentType in componentTypes)
{
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(componentType), "componentType must inherit IComponent");
}
ComponentTypes = componentTypes;
}
/// <inheritdoc />
public bool Match(IEntity entity)
{
foreach (var componentType in ComponentTypes)
{
if (!entity.HasComponent(componentType))
{
return false;
}
}
return true;
}
/// <inheritdoc />
public IEnumerable<IEntity> Match(IEntityManager entityMan)
{
return entityMan.GetEntities(new TypeEntityQuery(ComponentTypes.First())).Where(entity => Match(entity));
}
}
}

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

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Prometheus;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
@@ -77,13 +76,9 @@ namespace Robust.Shared.GameObjects
bool TryGetEntity(EntityUid uid, [NotNullWhen(true)] out IEntity? entity);
/// <summary>
/// Returns all entities that match with the provided query.
/// Returns all entities
/// </summary>
/// <param name="query">The query to test.</param>
/// <returns>An enumerable over all matching entities.</returns>
[Obsolete("IEntityQuery is obsolete")]
IEnumerable<IEntity> GetEntities(IEntityQuery query);
/// <returns></returns>
IEnumerable<IEntity> GetEntities();
public void QueueDeleteEntity(IEntity entity);

View File

@@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// An entity query is a type that can filter entities based on certain criteria,
/// for example based on the components that the entity has.
/// </summary>
[Obsolete("Use any of the other entity query methods instead.")]
public interface IEntityQuery
{
/// <summary>
/// Match the entity and see if it passes the criteria.
/// </summary>
/// <param name="entity">The entity to test.</param>
/// <returns>True if the entity is included in this query, false otherwise</returns>
bool Match(IEntity entity);
/// <summary>
/// Matches every entity in an EntityManager to see if it passes the criteria.
/// </summary>
/// <param name="entityMan">An EntityManager containing a set of entities.</param>
/// <returns>Enumeration of all entities that successfully matched the criteria.</returns>
IEnumerable<IEntity> Match(IEntityManager entityMan);
}
}

View File

@@ -14,11 +14,33 @@ namespace Robust.Shared.GameObjects
base.Initialize();
SubscribeLocalEvent<MapGridComponent, ComponentRemove>(RemoveHandler);
SubscribeLocalEvent<MapGridComponent, ComponentInit>(HandleGridInitialize);
}
private void RemoveHandler(EntityUid uid, MapGridComponent component, ComponentRemove args)
{
_mapManager.OnComponentRemoved(component);
}
private void HandleGridInitialize(EntityUid uid, MapGridComponent component, ComponentInit args)
{
var msg = new GridInitializeEvent(uid, component.GridIndex);
EntityManager.EventBus.RaiseLocalEvent(uid, msg);
}
}
/// <summary>
/// Raised whenever a grid is being initialized.
/// </summary>
public sealed class GridInitializeEvent : EntityEventArgs
{
public EntityUid EntityUid { get; }
public GridId GridId { get; }
public GridInitializeEvent(EntityUid uid, GridId gridId)
{
EntityUid = uid;
GridId = gridId;
}
}
}

View File

@@ -47,11 +47,6 @@ namespace Robust.Shared.Map
/// </summary>
Vector2 WorldPosition { get; set; }
/// <summary>
/// Whether or not this grid has gravity
/// </summary>
bool HasGravity { get; set; }
#region TileAccess
/// <summary>

View File

@@ -34,30 +34,13 @@ namespace Robust.Shared.Map
[ViewVariables]
public EntityUid GridEntityId { get; internal set; }
[ViewVariables]
public bool HasGravity
{
get => _hasGravity;
set
{
_hasGravity = value;
if (GridEntityId.IsValid())
{
// HasGravity is synchronized MapGridComponent states.
_mapManager.EntityManager.GetEntity(GridEntityId).GetComponent<MapGridComponent>().Dirty();
}
}
}
/// <summary>
/// Grid chunks than make up this grid.
/// </summary>
private readonly Dictionary<Vector2i, IMapChunkInternal> _chunks = new();
private readonly IMapManagerInternal _mapManager;
private readonly IEntityManager _entityManager;
private bool _hasGravity;
private readonly IEntityManager _entityManager;
/// <summary>
/// Initializes a new instance of the <see cref="MapGrid"/> class.
@@ -76,7 +59,6 @@ namespace Robust.Shared.Map
ChunkSize = chunkSize;
ParentMapId = parentMapId;
LastModifiedTick = CreatedTick = _mapManager.GameTiming.CurTick;
HasGravity = false;
}
/// <summary>

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

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

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

@@ -55,9 +55,10 @@ namespace Robust.UnitTesting.Server.GameObjects
//NOTE: The grids have not moved, so we can assert worldpos == localpos for the test
}
[Test]
public void Test([Values("throwInAdd", "throwsInInitialize", "throwsInStartup")]
string prototypeName)
[TestCase("throwInAdd")]
[TestCase("throwsInInitialize")]
[TestCase("throwsInStartup")]
public void Test(string prototypeName)
{
Assert.That(() => EntityManager.SpawnEntity(prototypeName, MapCoordinates.Nullspace),
Throws.TypeOf<EntityCreationException>());

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

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using NUnit.Framework;
using Robust.Server.Reflection;
@@ -64,7 +64,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
},
new []
{
new MapGridComponentState(new GridId(0), true),
new MapGridComponentState(new GridId(0)),
});
serializer.Serialize(stream, payload);

View File

@@ -100,31 +100,6 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
}
}
/// <summary>
/// When an entity is anchored to a grid tile, it's local rotation is rounded to the nearest cardinal 4 direction.
/// </summary>
[Test]
public void OnAnchored_WorldRotation_Cardinal()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId);
// can only be anchored to a tile
var grid = mapMan.GetGrid(TestGridId);
grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1));
var ent1 = entMan.SpawnEntity(null, coordinates);
ent1.Transform.LocalRotation = Angle.FromDegrees(60);
// Act
ent1.Transform.Anchored = true;
Assert.That(ent1.Transform.LocalRotation.Degrees, Is.EqualTo(90));
}
/// <summary>
/// When an entity is anchored to a grid tile, it's parent is set to the grid.
/// </summary>
@@ -247,47 +222,6 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
}
}
/// <summary>
/// Local rotation of an anchored entity cannot be changed (can still rotate/orbit in word via parent).
/// Writing to the property is a no-op and is silently ignored.
/// Because the position cannot be changed, RotateEvents are not raised when setting the property.
/// </summary>
[Test]
public void Anchored_SetRotation_Nop()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId);
// can only be anchored to a tile
var grid = mapMan.GetGrid(TestGridId);
grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1));
var subscriber = new Subscriber();
int calledCount = 0;
var ent1 = entMan.SpawnEntity(null, coordinates);
ent1.Transform.NoLocalRotation = false;
var testAngle = Angle.FromDegrees(90);
ent1.Transform.LocalRotation = testAngle;
entMan.EventBus.SubscribeEvent<RotateEvent>(EventSource.Local, subscriber, MoveEventHandler);
ent1.Transform.Anchored = true;
// Act
ent1.Transform.WorldRotation = Angle.FromDegrees(180);
ent1.Transform.LocalRotation = Angle.FromDegrees(180);
Assert.That(ent1.Transform.LocalRotation, Is.EqualTo(testAngle));
Assert.That(calledCount, Is.EqualTo(0));
void MoveEventHandler(RotateEvent ev)
{
Assert.Fail("RotateEvent raised when entity is anchored.");
calledCount++;
}
}
/// <summary>
/// Changing the parent of the entity un-anchors it.
/// </summary>
@@ -299,7 +233,7 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
var mapMan = sim.Resolve<IMapManager>();
var coordinates = new MapCoordinates(new Vector2(7, 7), TestMapId);
var grid = mapMan.GetGrid(TestGridId);
var ent1 = entMan.SpawnEntity(null, coordinates);
@@ -503,7 +437,7 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
var ent1 = entMan.SpawnEntity(null, new MapCoordinates(new Vector2(7, 7), TestMapId));
var tileIndices = grid.TileIndicesFor(ent1.Transform.Coordinates);
grid.SetTile(tileIndices, new Tile(1));
var gridEnt = entMan.GetEntity(grid.GridEntityId);
var containerMan = gridEnt.AddComponent<ContainerManagerComponent>();
var container = containerMan.MakeContainer<Container>("TestContainer");