mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e93c0f76a9 | ||
|
|
7bac32d18e | ||
|
|
b6b1d46892 | ||
|
|
6f0bc3822e | ||
|
|
b7c8452285 | ||
|
|
8c1e075c91 | ||
|
|
b340e40c99 | ||
|
|
c4b124f48d | ||
|
|
7efae8fbc1 | ||
|
|
7feeeb2f6f | ||
|
|
f90462cf82 | ||
|
|
b19ae9e69e | ||
|
|
2132d6cbae | ||
|
|
d2d6f9d08e | ||
|
|
4b58fcbff2 | ||
|
|
f83f6a8cd6 | ||
|
|
dfd7711506 | ||
|
|
78f9d92c07 | ||
|
|
3a86c827ea | ||
|
|
325f25c547 | ||
|
|
be57b5d20b | ||
|
|
7124d86f94 | ||
|
|
229380a71d | ||
|
|
e9eb536df5 | ||
|
|
22297ef6d8 | ||
|
|
7f2e433087 | ||
|
|
18c32a0258 | ||
|
|
72314a102d | ||
|
|
719ea26a31 | ||
|
|
5cb8fe1897 | ||
|
|
f35a52fc24 | ||
|
|
6bdb0cef47 | ||
|
|
fe3c9fe28f | ||
|
|
6085671f22 | ||
|
|
a2398da324 | ||
|
|
b27304cc58 | ||
|
|
3bf851a6cf | ||
|
|
cef92efd0f | ||
|
|
c5961a5ab1 | ||
|
|
8ddd92993d | ||
|
|
da253a5f34 | ||
|
|
ca9400a1ff | ||
|
|
f232195ceb | ||
|
|
b54a803519 | ||
|
|
a0d3d2108f | ||
|
|
977e4a017b | ||
|
|
2d8b159016 | ||
|
|
9caa0dde4b | ||
|
|
7a5a8c5eb1 | ||
|
|
95ba58f0a4 |
2
Linguini
2
Linguini
Submodule Linguini updated: 62b0e75b91...26c2608f9b
@@ -29,26 +29,27 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
}
|
||||
|
||||
//formatted according to https://github.com/dotnet/msbuild/blob/main/src/Shared/CanonicalError.cs#L57
|
||||
class ConsoleBuildEngine : IBuildEngine
|
||||
{
|
||||
public void LogErrorEvent(BuildErrorEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL ERROR {e.Code}: {e.Message}");
|
||||
}
|
||||
|
||||
public void LogWarningEvent(BuildWarningEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL WARNING {e.Code}: {e.Message}");
|
||||
}
|
||||
|
||||
public void LogMessageEvent(BuildMessageEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL MESSAGE {e.Code}: {e.Message}");
|
||||
}
|
||||
|
||||
public void LogCustomEvent(CustomBuildEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"CUSTOM: {e.Message}");
|
||||
Console.WriteLine(e.Message);
|
||||
}
|
||||
|
||||
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
|
||||
|
||||
@@ -280,8 +280,8 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", res.Uri, 0, 0, 0, 0,
|
||||
e.ToString(), "", "CompileRobustXaml"));
|
||||
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
|
||||
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Robust.Client.Audio.Midi
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
private SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsAvailable
|
||||
@@ -175,7 +175,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread.Start();
|
||||
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
@@ -76,6 +77,7 @@ namespace Robust.Client
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
IoCManager.Register<IPhysicsManager, PhysicsManager>();
|
||||
switch (mode)
|
||||
{
|
||||
case GameController.DisplayMode.Headless:
|
||||
|
||||
@@ -83,6 +83,8 @@ namespace Robust.Client.Console
|
||||
OutputText(text, true, true);
|
||||
}
|
||||
|
||||
public override event ConAnyCommandCallback? AnyCommandExecuted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExecuteCommand(ICommonSession? session, string command)
|
||||
{
|
||||
@@ -103,7 +105,11 @@ namespace Robust.Client.Console
|
||||
{
|
||||
var command1 = AvailableCommands[commandName];
|
||||
args.RemoveAt(0);
|
||||
command1.Execute(new ConsoleShell(this, null), command, args.ToArray());
|
||||
var shell = new ConsoleShell(this, null);
|
||||
var cmdArgs = args.ToArray();
|
||||
|
||||
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
|
||||
command1.Execute(shell, command, cmdArgs);
|
||||
}
|
||||
else
|
||||
WriteError(null, "Unknown command: " + commandName);
|
||||
|
||||
@@ -27,6 +27,7 @@ using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -536,7 +537,10 @@ namespace Robust.Client.Console.Commands
|
||||
var scroll = new ScrollContainer();
|
||||
tabContainer.AddChild(scroll);
|
||||
//scroll.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
|
||||
var vBox = new VBoxContainer();
|
||||
var vBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
scroll.AddChild(vBox);
|
||||
|
||||
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
|
||||
@@ -594,7 +598,10 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
|
||||
var group = new ButtonGroup();
|
||||
var vBoxRadioButtons = new VBoxContainer();
|
||||
var vBoxRadioButtons = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
vBoxRadioButtons.AddChild(new Button
|
||||
@@ -610,8 +617,9 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
TabContainer.SetTabTitle(vBoxRadioButtons, "Radio buttons!!");
|
||||
|
||||
tabContainer.AddChild(new VBoxContainer
|
||||
tabContainer.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Name = "Slider",
|
||||
Children =
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.Console
|
||||
{
|
||||
@@ -18,7 +19,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
private readonly IReflectionManager _reflectionManager;
|
||||
|
||||
private readonly VBoxContainer _watchesVBox;
|
||||
private readonly BoxContainer _watchesVBox;
|
||||
private readonly LineEdit _addWatchEdit;
|
||||
private readonly Button _addWatchButton;
|
||||
|
||||
@@ -31,17 +32,20 @@ namespace Robust.Client.Console
|
||||
|
||||
Title = "Watch Window";
|
||||
|
||||
var mainVBox = new VBoxContainer
|
||||
var mainVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = (500, 300),
|
||||
Children =
|
||||
{
|
||||
(_watchesVBox = new VBoxContainer
|
||||
(_watchesVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
VerticalExpand = true
|
||||
}),
|
||||
new HBoxContainer
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(_addWatchEdit = new HistoryLineEdit
|
||||
@@ -105,8 +109,9 @@ namespace Robust.Client.Console
|
||||
Button delButton;
|
||||
_runner = runner;
|
||||
|
||||
AddChild(new HBoxContainer
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(_outputLabel = new Label
|
||||
@@ -166,8 +171,9 @@ namespace Robust.Client.Console
|
||||
public CompilationErrorControl(string message)
|
||||
{
|
||||
Button delButton;
|
||||
AddChild(new HBoxContainer
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
|
||||
@@ -162,12 +162,13 @@ namespace Robust.Client.Debugging
|
||||
if (viewport.IsEmpty()) return;
|
||||
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
var drawnJoints = new HashSet<Joint>();
|
||||
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(mapId, viewport))
|
||||
{
|
||||
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
|
||||
|
||||
// all entities have a TransformComponent
|
||||
var transform = physBody.Owner.Transform;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
@@ -40,6 +41,8 @@ namespace Robust.Client.Debugging
|
||||
* Used for debugging shapes, controllers, joints, contacts
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
|
||||
|
||||
private const int MaxContactPoints = 2048;
|
||||
internal int PointCount;
|
||||
|
||||
@@ -79,8 +82,7 @@ namespace Robust.Client.Debugging
|
||||
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
|
||||
|
||||
Span<Vector2> points = stackalloc Vector2[2];
|
||||
Vector2 normal;
|
||||
contact.GetWorldManifold(out normal, points);
|
||||
contact.GetWorldManifold(_physicsManager, out var normal, points);
|
||||
|
||||
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Robust.Client.GameObjects
|
||||
RegisterClass<InputComponent>();
|
||||
RegisterClass<SpriteComponent>();
|
||||
RegisterClass<ClientOccluderComponent>();
|
||||
RegisterClass<OccluderTreeComponent>();
|
||||
RegisterClass<EyeComponent>();
|
||||
RegisterClass<AppearanceComponent>();
|
||||
RegisterClass<AppearanceTestComponent>();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -10,7 +8,7 @@ namespace Robust.Client.GameObjects
|
||||
/// <summary>
|
||||
/// Defines data fields used in the <see cref="InputSystem"/>.
|
||||
/// </summary>
|
||||
class InputComponent : Component
|
||||
public class InputComponent : Component
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Input";
|
||||
|
||||
@@ -365,7 +365,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath);
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IClydeAudio _clyde = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
|
||||
@@ -38,7 +37,6 @@ namespace Robust.Client.GameObjects
|
||||
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
|
||||
|
||||
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
|
||||
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
|
||||
}
|
||||
|
||||
private void StopAudioMessageHandler(StopAudioMessageClient ev)
|
||||
@@ -301,7 +299,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning("Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,15 +15,14 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
|
||||
|
||||
private RenderingTreeSystem _treeSystem = default!;
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_treeSystem = Get<RenderingTreeSystem>();
|
||||
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -773,24 +774,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var ii = 0;
|
||||
var imi = 0;
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, expandedBounds, true))
|
||||
foreach (var comp in occluderSystem.GetOccluderTrees(map, expandedBounds))
|
||||
{
|
||||
if (!occluderSystem.TryGetOccluderTreeForGrid(map, gridId, out var occluderTree)) continue;
|
||||
// TODO: I know this doesn't work with rotated grids but when I come back to these I'm adding tests
|
||||
// because rotation bugs are common.
|
||||
var treeBounds = expandedBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
|
||||
Box2 gridBounds;
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = expandedBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Ideally this would clamp to the outer border of what we can see
|
||||
var grid = _mapManager.GetGrid(gridId);
|
||||
gridBounds = expandedBounds.Translated(-grid.WorldPosition);
|
||||
}
|
||||
|
||||
occluderTree.QueryAabb((in OccluderComponent sOccluder) =>
|
||||
comp.Tree.QueryAabb((in OccluderComponent sOccluder) =>
|
||||
{
|
||||
var occluder = (ClientOccluderComponent)sOccluder;
|
||||
var transform = occluder.Owner.Transform;
|
||||
@@ -919,7 +909,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
ami += 4;
|
||||
|
||||
return true;
|
||||
}, gridBounds);
|
||||
}, treeBounds);
|
||||
}
|
||||
|
||||
_occlusionDataLength = ii;
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
internal sealed partial class Clyde : IClydeInternal, IClydeAudio, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
@@ -20,6 +20,9 @@ namespace Robust.Client.Log
|
||||
|
||||
public void Log(string sawmillName, LogEvent message)
|
||||
{
|
||||
if (sawmillName == "CON")
|
||||
return;
|
||||
|
||||
var formatted = new FormattedMessage(8);
|
||||
var robustLevel = message.Level.ToRobust();
|
||||
formatted.PushColor(Color.DarkGray);
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Client.Utility;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
@@ -62,7 +63,7 @@ namespace Robust.Client.Map
|
||||
var row = i / dimensionX;
|
||||
|
||||
Image<Rgba32> image;
|
||||
using (var stream = _resourceCache.ContentFileRead(Path.Join(def.Path, $"{def.SpriteName}.png")))
|
||||
using (var stream = _resourceCache.ContentFileRead(new ResourcePath(def.Path) / $"{def.SpriteName}.png"))
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Client.Physics
|
||||
{
|
||||
internal sealed class BroadPhaseSystem : SharedBroadPhaseSystem
|
||||
internal sealed class BroadPhaseSystem : SharedBroadphaseSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -504,8 +504,8 @@ namespace Robust.Client.Placement
|
||||
coordinates = new EntityCoordinates();
|
||||
return false;
|
||||
}
|
||||
coordinates = EntityCoordinates.FromMap(ent.EntityManager, MapManager,
|
||||
eyeManager.ScreenToMap(_inputManager.MouseScreenPosition));
|
||||
coordinates = EntityCoordinates.FromMap(MapManager,
|
||||
eyeManager.ScreenToMap(_inputManager.MouseScreenPosition));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -232,7 +233,7 @@ namespace Robust.Client.Placement
|
||||
bounds.Width,
|
||||
bounds.Height);
|
||||
|
||||
return EntitySystem.Get<SharedBroadPhaseSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
|
||||
return EntitySystem.Get<SharedBroadphaseSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
|
||||
}
|
||||
|
||||
protected Vector2 ScreenToWorld(Vector2 point)
|
||||
@@ -250,7 +251,7 @@ namespace Robust.Client.Placement
|
||||
var mapCoords = pManager.eyeManager.ScreenToMap(coords.Position);
|
||||
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var grid))
|
||||
{
|
||||
return EntityCoordinates.FromMap(pManager.EntityManager, pManager.MapManager, mapCoords);
|
||||
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
|
||||
}
|
||||
|
||||
return EntityCoordinates.FromMap(pManager.EntityManager, grid.GridEntityId, mapCoords);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
|
||||
namespace Robust.Client.Placement
|
||||
|
||||
@@ -102,7 +102,9 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
if (reg.Src.Width % frameSize.X != 0 || reg.Src.Height % frameSize.Y != 0)
|
||||
{
|
||||
throw new RSILoadException("State image size is not a multiple of the icon size.");
|
||||
var regDims = $"{reg.Src.Width}x{reg.Src.Height}";
|
||||
var iconDims = $"{frameSize.X}x{frameSize.Y}";
|
||||
throw new RSILoadException($"State '{stateObject.StateId}' image size ({regDims}) is not a multiple of the icon size ({iconDims}).");
|
||||
}
|
||||
|
||||
// Load all frames into a list so we can operate on it more sanely.
|
||||
@@ -250,7 +252,7 @@ namespace Robust.Client.ResourceManagement
|
||||
}
|
||||
|
||||
if (manifestJson == null)
|
||||
throw new RSILoadException("Manifest JSON was null!");
|
||||
throw new RSILoadException($"Manifest JSON failed to deserialize!");
|
||||
|
||||
var size = manifestJson.Size;
|
||||
var states = new StateMetadata[manifestJson.States.Length];
|
||||
@@ -270,7 +272,7 @@ namespace Robust.Client.ResourceManagement
|
||||
1 => RSI.State.DirectionType.Dir1,
|
||||
4 => RSI.State.DirectionType.Dir4,
|
||||
8 => RSI.State.DirectionType.Dir8,
|
||||
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
|
||||
_ => throw new RSILoadException($"Invalid direction for state '{stateName}': {dirValue}. Expected 1, 4 or 8")
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -291,7 +293,7 @@ namespace Robust.Client.ResourceManagement
|
||||
if (delays.Length != dirValue)
|
||||
{
|
||||
throw new RSILoadException(
|
||||
"DirectionsdirectionFramesList count does not match amount of delays specified.");
|
||||
$"Direction frames list count ({dirValue}) does not match amount of delays specified ({delays.Length}) for state '{stateName}'.");
|
||||
}
|
||||
|
||||
for (var i = 0; i < delays.Length; i++)
|
||||
|
||||
@@ -763,6 +763,29 @@ namespace Robust.Client.UserInterface
|
||||
SetPositionInParent(Parent.ChildCount - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This searches recursively through all the children of "parent"
|
||||
/// and sets the Disabled value of any buttons found to "val"
|
||||
/// </summary>
|
||||
/// <param name="parent">The control which childrens get searched</param>
|
||||
/// <param name="val">The value to which disabled gets set</param>
|
||||
public void SetButtonDisabledRecursive(Control parent, bool val)
|
||||
{
|
||||
foreach (var child in parent.Children)
|
||||
{
|
||||
if (child is Button but)
|
||||
{
|
||||
but.Disabled = val;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.Children != null)
|
||||
{
|
||||
SetButtonDisabledRecursive(child, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when this control receives keyboard focus.
|
||||
/// </summary>
|
||||
|
||||
@@ -18,8 +18,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
ToggleMode = true;
|
||||
|
||||
var hBox = new HBoxContainer
|
||||
var hBox = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
StyleClasses = { StyleClassCheckBox },
|
||||
};
|
||||
AddChild(hBox);
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
@@ -13,9 +14,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
private readonly List<Menu> _menus = new();
|
||||
private readonly List<MenuBarTopButton> _buttons = new();
|
||||
private readonly HBoxContainer _hBox;
|
||||
private readonly BoxContainer _hBox;
|
||||
private readonly Popup _popup;
|
||||
private readonly VBoxContainer _popupVBox;
|
||||
private readonly BoxContainer _popupVBox;
|
||||
private bool _popupOpen;
|
||||
|
||||
public IList<Menu> Menus { get; }
|
||||
@@ -26,13 +27,21 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(_popupVBox = new VBoxContainer {MinSize = (300, 0)})
|
||||
(_popupVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = (300, 0)
|
||||
})
|
||||
}
|
||||
};
|
||||
_popup.OnPopupHide += PopupHidden;
|
||||
UserInterfaceManager.ModalRoot.AddChild(_popup);
|
||||
Menus = new MenuCollection(this);
|
||||
AddChild(_hBox = new HBoxContainer {SeparationOverride = 8});
|
||||
AddChild(_hBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 8
|
||||
});
|
||||
}
|
||||
|
||||
private void AddMenu(Menu menu)
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
@@ -20,7 +21,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
// map from key to buttondata index
|
||||
private Dictionary<TKey, int> _keyMap = new();
|
||||
private readonly Popup _popup;
|
||||
private readonly VBoxContainer _popupVBox;
|
||||
private readonly BoxContainer _popupVBox;
|
||||
private readonly Label _label;
|
||||
|
||||
public event Action<ItemPressedEventArgs>? OnItemSelected;
|
||||
@@ -60,11 +61,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
AddStyleClass(StyleClassButton);
|
||||
OnPressed += OnPressedInternal;
|
||||
|
||||
var hBox = new HBoxContainer();
|
||||
var hBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
};
|
||||
AddChild(hBox);
|
||||
|
||||
_popup = new Popup();
|
||||
_popupVBox = new VBoxContainer();
|
||||
_popupVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
_popup.AddChild(_popupVBox);
|
||||
_popup.OnPopupHide += OnPopupHide;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
@@ -13,7 +14,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private readonly List<ButtonData> _buttonData = new();
|
||||
private readonly Dictionary<int, int> _idMap = new();
|
||||
private readonly Popup _popup;
|
||||
private readonly VBoxContainer _popupVBox;
|
||||
private readonly BoxContainer _popupVBox;
|
||||
private readonly Label _label;
|
||||
private readonly TextureRect _triangle;
|
||||
|
||||
@@ -49,11 +50,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
Prefix = "";
|
||||
OnPressed += OnPressedInternal;
|
||||
|
||||
var hBox = new HBoxContainer();
|
||||
var hBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
};
|
||||
AddChild(hBox);
|
||||
|
||||
_popup = new Popup();
|
||||
_popupVBox = new VBoxContainer();
|
||||
_popupVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
_popup.AddChild(_popupVBox);
|
||||
_popup.OnPopupHide += OnPopupHide;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
@@ -34,11 +35,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
switch (layout)
|
||||
{
|
||||
case RadioOptionsLayout.Vertical:
|
||||
_container = new VBoxContainer();
|
||||
_container = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
break;
|
||||
case RadioOptionsLayout.Horizontal:
|
||||
default:
|
||||
_container = new HBoxContainer();
|
||||
_container = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
|
||||
<VBoxContainer>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<OutputPanel Name="Output" VerticalExpand="True">
|
||||
<OutputPanel.StyleBoxOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252add"
|
||||
@@ -9,5 +9,5 @@
|
||||
</OutputPanel.StyleBoxOverride>
|
||||
</OutputPanel>
|
||||
<HistoryLineEdit Name="CommandBar" PlaceHolder="{Loc 'console-line-edit-placeholder'}" />
|
||||
</VBoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -12,6 +12,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls
|
||||
{
|
||||
@@ -21,7 +22,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
private readonly IPrototypeManager prototypeManager;
|
||||
private readonly IResourceCache resourceCache;
|
||||
|
||||
private VBoxContainer MainVBox;
|
||||
private BoxContainer MainVBox;
|
||||
private PrototypeListContainer PrototypeList;
|
||||
private LineEdit SearchBar;
|
||||
private OptionButton OverrideMenu;
|
||||
@@ -71,13 +72,15 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
SetSize = (250, 300);
|
||||
MinSize = (250, 200);
|
||||
|
||||
Contents.AddChild(MainVBox = new VBoxContainer
|
||||
Contents.AddChild(MainVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Name = "AAAAAA",
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(SearchBar = new LineEdit
|
||||
@@ -102,8 +105,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
(PrototypeList = new PrototypeListContainer())
|
||||
}
|
||||
},
|
||||
new HBoxContainer
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(EraseButton = new Button
|
||||
@@ -472,8 +476,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
ToggleMode = true,
|
||||
});
|
||||
|
||||
AddChild(new HBoxContainer
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(EntityTextureRects = new LayeredTextureRect
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
}
|
||||
|
||||
var fps = _gameTiming.FramesPerSecondAvg;
|
||||
Text = $"FPS: {fps:N1}";
|
||||
Text = $"FPS: {fps:N0}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<SS14Window xmlns="https://spacestation14.io" MinWidth="100" MinHeight="50">
|
||||
<PanelContainer StyleClasses="windowPanel" />
|
||||
<VBoxContainer SeparationOverride="0">
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="0">
|
||||
<PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
|
||||
<HBoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Margin="5 0 0 0" HorizontalExpand="true" Name="TitleLabel" StyleIdentifier="foo" ClipText="True"
|
||||
Text="{Loc 'ss14window-placeholder-title'}" VAlign="Center" StyleClasses="windowTitle" />
|
||||
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton" VerticalAlignment="Center" />
|
||||
</HBoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
<Control Name="ContentsContainer" Margin="10" RectClipContent="True" VerticalExpand="true" />
|
||||
</VBoxContainer>
|
||||
</BoxContainer>
|
||||
</SS14Window>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls
|
||||
{
|
||||
@@ -12,8 +13,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
protected ScriptConsole()
|
||||
{
|
||||
Contents.AddChild(new VBoxContainer
|
||||
Contents.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
new PanelContainer
|
||||
@@ -29,8 +31,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
},
|
||||
VerticalExpand = true,
|
||||
},
|
||||
new HBoxContainer
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(InputBar = new HistoryLineEdit
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
using Robust.Shared.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls
|
||||
{
|
||||
@@ -32,9 +35,15 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
_placementManager = placementManager;
|
||||
_resourceCache = resourceCache;
|
||||
|
||||
var vBox = new VBoxContainer();
|
||||
var vBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
Contents.AddChild(vBox);
|
||||
var hBox = new HBoxContainer();
|
||||
var hBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
};
|
||||
vBox.AddChild(hBox);
|
||||
SearchBar = new LineEdit {PlaceHolder = "Search", HorizontalExpand = true};
|
||||
SearchBar.OnTextChanged += OnSearchBarTextChanged;
|
||||
@@ -105,7 +114,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
Texture? texture = null;
|
||||
if (!string.IsNullOrEmpty(entry.SpriteName))
|
||||
{
|
||||
texture = _resourceCache.GetResource<TextureResource>($"/Textures/Constructible/Tiles/{entry.SpriteName}.png");
|
||||
texture = _resourceCache.GetResource<TextureResource>(new ResourcePath(entry.Path) / $"{entry.SpriteName}.png");
|
||||
}
|
||||
TileList.AddItem(entry.DisplayName, texture);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Globalization;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
@@ -9,8 +10,9 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var hBox = new HBoxContainer
|
||||
var hBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
MinSize = new Vector2(200, 0)
|
||||
};
|
||||
var angle = (Angle) value!;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
@@ -13,8 +14,9 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var coords = (EntityCoordinates) value!;
|
||||
var hBoxContainer = new HBoxContainer
|
||||
var hBoxContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
MinSize = new Vector2(240, 0),
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
@@ -9,8 +10,9 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var hBox = new HBoxContainer
|
||||
var hBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
MinSize = new Vector2(200, 0)
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
@@ -20,7 +21,11 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
_localValue = value;
|
||||
|
||||
var hbox = new HBoxContainer() { HorizontalExpand = true };
|
||||
var hbox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
_lineEdit = new LineEdit()
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
@@ -20,7 +21,10 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var hBox = new HBoxContainer();
|
||||
var hBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
};
|
||||
|
||||
dynamic d = value!;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Globalization;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
@@ -17,8 +18,9 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var hBoxContainer = new HBoxContainer
|
||||
var hBoxContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
MinSize = new Vector2(200, 0),
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Globalization;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
@@ -16,8 +17,9 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var hBoxContainer = new HBoxContainer
|
||||
var hBoxContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
MinSize = new Vector2(240, 0),
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Control;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using static Robust.Client.UserInterface.Controls.LineEdit;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Instances
|
||||
@@ -48,10 +49,10 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
private ViewVariablesBlobMembers? _membersBlob;
|
||||
|
||||
private VBoxContainer _clientComponents = default!;
|
||||
private BoxContainer _clientComponents = default!;
|
||||
|
||||
private VBoxContainer _serverVariables = default!;
|
||||
private VBoxContainer _serverComponents = default!;
|
||||
private BoxContainer _serverVariables = default!;
|
||||
private BoxContainer _serverComponents = default!;
|
||||
|
||||
private Button _clientComponentsAddButton = default!;
|
||||
private Button _serverComponentsAddButton = default!;
|
||||
@@ -73,7 +74,10 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
var scrollContainer = new ScrollContainer();
|
||||
//scrollContainer.SetAnchorPreset(Control.LayoutPreset.Wide, true);
|
||||
window.Contents.AddChild(scrollContainer);
|
||||
var vBoxContainer = new VBoxContainer();
|
||||
var vBoxContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
scrollContainer.AddChild(vBoxContainer);
|
||||
|
||||
// Handle top bar displaying type and ToString().
|
||||
@@ -84,7 +88,11 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
{
|
||||
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/Fonts/CALIBRI.TTF"), 10);
|
||||
// Custom ToString() implementation.
|
||||
var headBox = new VBoxContainer {SeparationOverride = 0};
|
||||
var headBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
SeparationOverride = 0
|
||||
};
|
||||
headBox.AddChild(new Label {Text = stringified, ClipText = true});
|
||||
headBox.AddChild(new Label
|
||||
{
|
||||
@@ -102,7 +110,10 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
if (_entity.TryGetComponent(out ISpriteComponent? sprite))
|
||||
{
|
||||
var hBox = new HBoxContainer();
|
||||
var hBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
};
|
||||
top.HorizontalExpand = true;
|
||||
hBox.AddChild(top);
|
||||
hBox.AddChild(new SpriteView {Sprite = sprite});
|
||||
@@ -118,7 +129,11 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
_tabs.OnTabChanged += _tabsOnTabChanged;
|
||||
vBoxContainer.AddChild(_tabs);
|
||||
|
||||
var clientVBox = new VBoxContainer {SeparationOverride = 0};
|
||||
var clientVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
SeparationOverride = 0
|
||||
};
|
||||
_tabs.AddChild(clientVBox);
|
||||
_tabs.SetTabTitle(TabClientVars, Loc.GetString("view-variable-instance-entity-client-variables-tab-title"));
|
||||
|
||||
@@ -136,7 +151,11 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
}
|
||||
}
|
||||
|
||||
_clientComponents = new VBoxContainer {SeparationOverride = 0};
|
||||
_clientComponents = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
SeparationOverride = 0
|
||||
};
|
||||
_tabs.AddChild(_clientComponents);
|
||||
_tabs.SetTabTitle(TabClientComponents, Loc.GetString("view-variable-instance-entity-client-components-tab-title"));
|
||||
|
||||
@@ -144,11 +163,19 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
if (!_entity.Uid.IsClientSide())
|
||||
{
|
||||
_serverVariables = new VBoxContainer {SeparationOverride = 0};
|
||||
_serverVariables = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
SeparationOverride = 0
|
||||
};
|
||||
_tabs.AddChild(_serverVariables);
|
||||
_tabs.SetTabTitle(TabServerVars, Loc.GetString("view-variable-instance-entity-server-variables-tab-title"));
|
||||
|
||||
_serverComponents = new VBoxContainer {SeparationOverride = 0};
|
||||
_serverComponents = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
SeparationOverride = 0
|
||||
};
|
||||
_tabs.AddChild(_serverComponents);
|
||||
_tabs.SetTabTitle(TabServerComponents, Loc.GetString("view-variable-instance-entity-server-components-tab-title"));
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.Input;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Instances
|
||||
@@ -64,8 +65,9 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
var scrollContainer = new ScrollContainer();
|
||||
//scrollContainer.SetAnchorPreset(Control.LayoutPreset.Wide, true);
|
||||
window.Contents.AddChild(scrollContainer);
|
||||
var vBoxContainer = new VBoxContainer
|
||||
var vBoxContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
};
|
||||
@@ -73,7 +75,10 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
// Handle top bar.
|
||||
{
|
||||
var headBox = new HBoxContainer();
|
||||
var headBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
};
|
||||
var name = MakeTopBar(top, bottom);
|
||||
name.HorizontalExpand = true;
|
||||
headBox.AddChild(name);
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Client.ViewVariables.Editors;
|
||||
using Robust.Client.ViewVariables.Instances;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Traits
|
||||
{
|
||||
@@ -25,8 +26,8 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
private Button _leftButton = default!;
|
||||
private Button _rightButton = default!;
|
||||
private LineEdit _pageLabel = default!;
|
||||
private HBoxContainer _controlsHBox = default!;
|
||||
private VBoxContainer _elementsVBox = default!;
|
||||
private BoxContainer _controlsHBox = default!;
|
||||
private BoxContainer _elementsVBox = default!;
|
||||
|
||||
private int HighestKnownPage => Math.Max(0, ((_cache.Count + ElementsPerPage - 1) / ElementsPerPage) - 1);
|
||||
|
||||
@@ -47,9 +48,13 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
_enumerator = enumerable.GetEnumerator();
|
||||
}
|
||||
|
||||
var outerVBox = new VBoxContainer();
|
||||
_controlsHBox = new HBoxContainer
|
||||
var outerVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
_controlsHBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalAlignment = Control.HAlignment.Center
|
||||
};
|
||||
|
||||
@@ -70,7 +75,10 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
|
||||
outerVBox.AddChild(_controlsHBox);
|
||||
|
||||
_elementsVBox = new VBoxContainer();
|
||||
_elementsVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
outerVBox.AddChild(_elementsVBox);
|
||||
|
||||
instance.AddTab("IEnumerable", outerVBox);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Traits
|
||||
{
|
||||
@@ -13,12 +14,16 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
private readonly IViewVariablesManagerInternal _vvm;
|
||||
private readonly IRobustSerializer _robustSerializer;
|
||||
|
||||
private VBoxContainer _memberList = default!;
|
||||
private BoxContainer _memberList = default!;
|
||||
|
||||
public override void Initialize(ViewVariablesInstanceObject instance)
|
||||
{
|
||||
base.Initialize(instance);
|
||||
_memberList = new VBoxContainer {SeparationOverride = 0};
|
||||
_memberList = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
SeparationOverride = 0
|
||||
};
|
||||
instance.AddTab("Members", _memberList);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<cc:SS14Window xmlns:cc="clr-namespace:Robust.Client.UserInterface.CustomControls"
|
||||
xmlns:c="clr-namespace:Robust.Client.UserInterface.Controls">
|
||||
<c:VBoxContainer VerticalExpand="True">
|
||||
<c:BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<c:LineEdit Name="SearchLineEdit" PlaceHolder="Search..." HorizontalExpand="True" />
|
||||
<c:ItemList Name="EntryItemList" VerticalExpand="True" HorizontalExpand="True" SelectMode="Single" />
|
||||
<c:Button Name="AddButton" Text="Select" TextAlign="Center" HorizontalExpand="True"/>
|
||||
</c:VBoxContainer>
|
||||
</c:BoxContainer>
|
||||
</cc:SS14Window>
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables
|
||||
{
|
||||
@@ -126,7 +127,11 @@ namespace Robust.Client.ViewVariables
|
||||
// 10);
|
||||
|
||||
// Custom ToString() implementation.
|
||||
var headBox = new VBoxContainer {SeparationOverride = 0};
|
||||
var headBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
SeparationOverride = 0
|
||||
};
|
||||
headBox.AddChild(new Label {Text = top, ClipText = true});
|
||||
headBox.AddChild(new Label
|
||||
{
|
||||
|
||||
@@ -8,14 +8,15 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.ViewVariables
|
||||
{
|
||||
internal class ViewVariablesPropertyControl : PanelContainer
|
||||
{
|
||||
public VBoxContainer VBox { get; }
|
||||
public HBoxContainer TopContainer { get; }
|
||||
public HBoxContainer BottomContainer { get; }
|
||||
public BoxContainer VBox { get; }
|
||||
public BoxContainer TopContainer { get; }
|
||||
public BoxContainer BottomContainer { get; }
|
||||
public Label NameLabel { get; }
|
||||
|
||||
private readonly Label _bottomLabel;
|
||||
@@ -34,14 +35,23 @@ namespace Robust.Client.ViewVariables
|
||||
ToolTip = "Click to expand";
|
||||
MinHeight = 25;
|
||||
|
||||
VBox = new VBoxContainer {SeparationOverride = 0};
|
||||
VBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
SeparationOverride = 0
|
||||
};
|
||||
AddChild(VBox);
|
||||
|
||||
TopContainer = new HBoxContainer {VerticalExpand = true};
|
||||
TopContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
VerticalExpand = true
|
||||
};
|
||||
VBox.AddChild(TopContainer);
|
||||
|
||||
BottomContainer = new HBoxContainer
|
||||
BottomContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Visible = false
|
||||
};
|
||||
VBox.AddChild(BottomContainer);
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Robust.Server.Console
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
|
||||
|
||||
public override event ConAnyCommandCallback? AnyCommandExecuted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExecuteCommand(ICommonSession? session, string command)
|
||||
{
|
||||
@@ -87,20 +89,22 @@ namespace Robust.Server.Console
|
||||
|
||||
if (AvailableCommands.TryGetValue(cmdName, out var conCmd)) // command registered
|
||||
{
|
||||
args.RemoveAt(0);
|
||||
var cmdArgs = args.ToArray();
|
||||
if (shell.Player != null) // remote client
|
||||
{
|
||||
if (_groupController.CanCommand((IPlayerSession) shell.Player, cmdName)) // client has permission
|
||||
{
|
||||
args.RemoveAt(0);
|
||||
conCmd.Execute(shell, command, args.ToArray());
|
||||
AnyCommandExecuted?.Invoke(shell, cmdName, command, cmdArgs);
|
||||
conCmd.Execute(shell, command, cmdArgs);
|
||||
}
|
||||
else
|
||||
shell.WriteError($"Unknown command: '{cmdName}'");
|
||||
}
|
||||
else // system console
|
||||
{
|
||||
args.RemoveAt(0);
|
||||
conCmd.Execute(shell, command, args.ToArray());
|
||||
AnyCommandExecuted?.Invoke(shell, cmdName, command, cmdArgs);
|
||||
conCmd.Execute(shell, command, cmdArgs);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Robust.Server.GameObjects
|
||||
RegisterClass<CollisionWakeComponent>();
|
||||
RegisterClass<ContainerManagerComponent>();
|
||||
RegisterClass<OccluderComponent>();
|
||||
RegisterClass<OccluderTreeComponent>();
|
||||
RegisterClass<SpriteComponent>();
|
||||
RegisterClass<AppearanceComponent>();
|
||||
RegisterClass<SnapGridComponent>();
|
||||
|
||||
@@ -224,20 +224,32 @@ namespace Robust.Server.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
switch (message.Type)
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
#endif
|
||||
{
|
||||
case EntityMessageType.ComponentMessage:
|
||||
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message, player));
|
||||
return;
|
||||
switch (message.Type)
|
||||
{
|
||||
case EntityMessageType.ComponentMessage:
|
||||
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message, player));
|
||||
return;
|
||||
|
||||
case EntityMessageType.SystemMessage:
|
||||
var msg = message.SystemMessage;
|
||||
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(player), msg)!;
|
||||
ReceivedSystemMessage?.Invoke(this, msg);
|
||||
ReceivedSystemMessage?.Invoke(this, sessionMsg);
|
||||
return;
|
||||
case EntityMessageType.SystemMessage:
|
||||
var msg = message.SystemMessage;
|
||||
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
||||
var sessionMsg =
|
||||
Activator.CreateInstance(sessionType, new EntitySessionEventArgs(player), msg)!;
|
||||
ReceivedSystemMessage?.Invoke(this, msg);
|
||||
ReceivedSystemMessage?.Invoke(this, sessionMsg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("net.ent", $"Caught exception while dispatching {message.Type}: {e}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Physics;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -365,6 +366,11 @@ namespace Robust.Server.Maps
|
||||
// Run Initialize on all components.
|
||||
FinishEntitiesInitialization();
|
||||
|
||||
// Regardless of what the frequency is we'll process fixtures sometime on map load for anything
|
||||
// that needs it before MapInit.
|
||||
var gridFixtures = EntitySystem.Get<GridFixtureSystem>();
|
||||
gridFixtures.Process();
|
||||
|
||||
// Run Startup on all components.
|
||||
FinishEntitiesStartup();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.Core;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
|
||||
namespace Robust.Server.Physics
|
||||
{
|
||||
internal sealed class BroadPhaseSystem : SharedBroadPhaseSystem
|
||||
internal sealed class BroadPhaseSystem : SharedBroadphaseSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -19,8 +19,7 @@ namespace Robust.Server.Physics
|
||||
internal sealed class GridFixtureSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadphase = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
|
||||
|
||||
// Is delaying fixture updates a good idea? IDEK. We definitely can't do them on every tile changed
|
||||
// because if someone changes 50 tiles that will kill perf. We could probably just run it every Update
|
||||
@@ -36,7 +35,6 @@ namespace Robust.Server.Physics
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(PhysicsSystem));
|
||||
SubscribeLocalEvent<RegenerateChunkCollisionEvent>(HandleCollisionRegenerate);
|
||||
_broadphase = Get<SharedBroadPhaseSystem>();
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.GridFixtureUpdateRate, value => _cooldown = value, true);
|
||||
@@ -97,6 +95,14 @@ namespace Robust.Server.Physics
|
||||
var origin = chunk.Indices * chunk.ChunkSize;
|
||||
bounds = bounds.Translated(origin);
|
||||
|
||||
// So we store a reference to the fixture on the chunk because it's easier to cross-reference it.
|
||||
// This is because when we get multiple fixtures per chunk there's no easy way to tell which the old one
|
||||
// corresponds with.
|
||||
// We also ideally want to avoid re-creating the fixture every time a tile changes and pushing that data
|
||||
// to the client hence we diff it.
|
||||
|
||||
// Additionally, we need to handle map deserialization where content may have stored its own data
|
||||
// on the grid (e.g. mass) which we want to preserve.
|
||||
var oldFixture = chunk.Fixture;
|
||||
|
||||
var newFixture = new Fixture(
|
||||
@@ -112,16 +118,57 @@ namespace Robust.Server.Physics
|
||||
},
|
||||
MapGridHelpers.CollisionGroup,
|
||||
MapGridHelpers.CollisionGroup,
|
||||
true) {ID = $"grid-{grid.Index}_chunk-{chunk.Indices.X}-{chunk.Indices.Y}",
|
||||
true) {ID = $"grid_chunk-{chunk.Indices.X}-{chunk.Indices.Y}",
|
||||
Body = physicsComponent};
|
||||
|
||||
// TODO: Chunk will likely need multiple fixtures but future sloth problem lmao fucking dickhead
|
||||
if (oldFixture?.Equals(newFixture) == true) return;
|
||||
// Check if we have an existing fixture on MapGrid
|
||||
var existingFixture = physicsComponent.GetFixture(newFixture.ID);
|
||||
var same = true;
|
||||
|
||||
// Some fucky shit but we gotta handle map deserialization.
|
||||
if (existingFixture is {Shape: PolygonShape poly})
|
||||
{
|
||||
var newPoly = (PolygonShape) newFixture.Shape;
|
||||
|
||||
if (newPoly.Vertices.Count == poly.Vertices.Count)
|
||||
{
|
||||
for (var i = 0; i < poly.Vertices.Count; i++)
|
||||
{
|
||||
if (!poly.Vertices[i].EqualsApprox(newPoly.Vertices[i]))
|
||||
{
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
same = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
same = false;
|
||||
}
|
||||
|
||||
// TODO: Chunk will likely need multiple fixtures but future sloth problem lmao idiot
|
||||
if (same)
|
||||
{
|
||||
// If we're deserializing map this can occur so just update it.
|
||||
if (oldFixture == null && existingFixture != null)
|
||||
{
|
||||
chunk.Fixture = existingFixture;
|
||||
existingFixture.CollisionMask = MapGridHelpers.CollisionGroup;
|
||||
existingFixture.CollisionLayer = MapGridHelpers.CollisionGroup;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldFixture != null)
|
||||
physicsComponent.RemoveFixture(oldFixture);
|
||||
_broadphase.DestroyFixture(physicsComponent, oldFixture);
|
||||
|
||||
physicsComponent.AddFixture(newFixture);
|
||||
_broadphase.CreateFixture(physicsComponent, newFixture);
|
||||
chunk.Fixture = newFixture;
|
||||
|
||||
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Uid,new GridFixtureChangeEvent {OldFixture = oldFixture, NewFixture = newFixture});
|
||||
|
||||
@@ -19,6 +19,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
@@ -71,6 +72,7 @@ namespace Robust.Server
|
||||
IoCManager.Register<IScriptHost, ScriptHost>();
|
||||
IoCManager.Register<IMetricsManager, MetricsManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
IoCManager.Register<IPhysicsManager, PhysicsManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Robust.Shared.Configuration
|
||||
{
|
||||
// overwrite the value with the saved one
|
||||
cfgVar.Value = tomlValue;
|
||||
cfgVar.ValueChanged?.Invoke(cfgVar.Value);
|
||||
InvokeValueChanged(cfgVar, cfgVar.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -189,16 +189,13 @@ namespace Robust.Shared.Configuration
|
||||
public void RegisterCVar<T>(string name, T defaultValue, CVar flags = CVar.NONE, Action<T>? onValueChanged = null)
|
||||
where T : notnull
|
||||
{
|
||||
Action<object>? valueChangedDelegate = null;
|
||||
if (onValueChanged != null)
|
||||
{
|
||||
valueChangedDelegate = v => onValueChanged((T) v);
|
||||
}
|
||||
RegisterCVar(name, typeof(T), defaultValue, flags);
|
||||
|
||||
RegisterCVar(name, typeof(T), defaultValue, flags, valueChangedDelegate);
|
||||
if (onValueChanged != null)
|
||||
OnValueChanged(name, onValueChanged);
|
||||
}
|
||||
|
||||
private void RegisterCVar(string name, Type type, object defaultValue, CVar flags, Action<object>? onValueChanged)
|
||||
private void RegisterCVar(string name, Type type, object defaultValue, CVar flags)
|
||||
{
|
||||
DebugTools.Assert(!type.IsEnum || type.GetEnumUnderlyingType() == typeof(int),
|
||||
$"{name}: Enum cvars must have int as underlying type.");
|
||||
@@ -219,7 +216,6 @@ namespace Robust.Shared.Configuration
|
||||
cVar.DefaultValue = defaultValue;
|
||||
cVar.Flags = flags;
|
||||
cVar.Registered = true;
|
||||
cVar.ValueChanged = onValueChanged;
|
||||
|
||||
if (cVar.OverrideValue != null)
|
||||
{
|
||||
@@ -233,7 +229,6 @@ namespace Robust.Shared.Configuration
|
||||
{
|
||||
Registered = true,
|
||||
Value = defaultValue,
|
||||
ValueChanged = onValueChanged
|
||||
});
|
||||
}
|
||||
|
||||
@@ -247,7 +242,11 @@ namespace Robust.Shared.Configuration
|
||||
where T : notnull
|
||||
{
|
||||
var reg = _configVars[name];
|
||||
reg.ValueChanged += o => onValueChanged((T) o);
|
||||
var exDel = (Action<T>?) reg.ValueChanged;
|
||||
exDel += onValueChanged;
|
||||
reg.ValueChanged = exDel;
|
||||
|
||||
reg.ValueChangedInvoker ??= (del, v) => ((Action<T>) del)((T) v);
|
||||
|
||||
if (invokeImmediately)
|
||||
{
|
||||
@@ -255,6 +254,19 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
public void UnsubValueChanged<T>(CVarDef<T> cVar, Action<T> onValueChanged) where T : notnull
|
||||
{
|
||||
UnsubValueChanged(cVar.Name, onValueChanged);
|
||||
}
|
||||
|
||||
public void UnsubValueChanged<T>(string name, Action<T> onValueChanged) where T : notnull
|
||||
{
|
||||
var reg = _configVars[name];
|
||||
var exDel = (Action<T>?) reg.ValueChanged;
|
||||
exDel -= onValueChanged;
|
||||
reg.ValueChanged = exDel;
|
||||
}
|
||||
|
||||
public void LoadCVarsFromAssembly(Assembly assembly)
|
||||
{
|
||||
foreach (var defField in assembly
|
||||
@@ -282,7 +294,7 @@ namespace Robust.Shared.Configuration
|
||||
throw new InvalidOperationException($"CVarDef '{defField.Name}' on '{defField.DeclaringType?.FullName}' is null.");
|
||||
}
|
||||
|
||||
RegisterCVar(def.Name, type, def.DefaultValue, def.Flags, null);
|
||||
RegisterCVar(def.Name, type, def.DefaultValue, def.Flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +328,7 @@ namespace Robust.Shared.Configuration
|
||||
cVar.OverrideValueParsed = null;
|
||||
|
||||
cVar.Value = value;
|
||||
cVar.ValueChanged?.Invoke(value);
|
||||
InvokeValueChanged(cVar, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -362,7 +374,7 @@ namespace Robust.Shared.Configuration
|
||||
{
|
||||
cfgVar.OverrideValue = value;
|
||||
cfgVar.OverrideValueParsed = ParseOverrideValue(value, cfgVar.DefaultValue?.GetType());
|
||||
cfgVar.ValueChanged?.Invoke(cfgVar.OverrideValueParsed);
|
||||
InvokeValueChanged(cfgVar, cfgVar.OverrideValueParsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -422,6 +434,11 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
private static void InvokeValueChanged(ConfigVar var, object value)
|
||||
{
|
||||
var.ValueChangedInvoker?.Invoke(var.ValueChanged!, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the data for a single configuration variable.
|
||||
/// </summary>
|
||||
@@ -476,7 +493,9 @@ namespace Robust.Shared.Configuration
|
||||
/// <summary>
|
||||
/// Invoked when the value of this CVar is changed.
|
||||
/// </summary>
|
||||
public Action<object>? ValueChanged { get; set; }
|
||||
public Delegate? ValueChanged { get; set; }
|
||||
|
||||
public Action<Delegate, object>? ValueChangedInvoker { get; set; }
|
||||
|
||||
// We don't know what the type of the var is until it's registered.
|
||||
// So we can't actually parse them until then.
|
||||
|
||||
@@ -63,10 +63,48 @@ namespace Robust.Shared.Configuration
|
||||
/// <param name="name">The name of the CVar</param>
|
||||
Type GetCVarType(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Listen for an event for if the config value changes.
|
||||
/// </summary>
|
||||
/// <param name="cVar">The CVar to listen for.</param>
|
||||
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
|
||||
/// <param name="invokeImmediately">
|
||||
/// Whether to run the callback immediately in this method. Can help reduce boilerplate
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
/// <seealso cref="UnsubValueChanged{T}(Robust.Shared.Configuration.CVarDef{T},System.Action{T})"/>
|
||||
void OnValueChanged<T>(CVarDef<T> cVar, Action<T> onValueChanged, bool invokeImmediately = false)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Listen for an event for if the config value changes.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the CVar to listen for.</param>
|
||||
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
|
||||
/// <param name="invokeImmediately">
|
||||
/// Whether to run the callback immediately in this method. Can help reduce boilerplate
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
/// <seealso cref="UnsubValueChanged{T}(string,System.Action{T})"/>
|
||||
void OnValueChanged<T>(string name, Action<T> onValueChanged, bool invokeImmediately = false)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe an event previously registered with <see cref="OnValueChanged{T}(Robust.Shared.Configuration.CVarDef{T},System.Action{T},bool)"/>.
|
||||
/// </summary>
|
||||
/// <param name="cVar">The CVar to unsubscribe from.</param>
|
||||
/// <param name="onValueChanged">The delegate to unsubscribe.</param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
void UnsubValueChanged<T>(CVarDef<T> cVar, Action<T> onValueChanged)
|
||||
where T : notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe an event previously registered with <see cref="OnValueChanged{T}(string,System.Action{T},bool)"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the CVar to unsubscribe from.</param>
|
||||
/// <param name="onValueChanged">The delegate to unsubscribe.</param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
void UnsubValueChanged<T>(string name, Action<T> onValueChanged)
|
||||
where T : notnull;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Robust.Shared.Configuration
|
||||
|
||||
private void HandleNetVarMessage(MsgConVars message)
|
||||
{
|
||||
if(!_receivedInitialNwVars)
|
||||
if (_netManager.IsClient && !_receivedInitialNwVars)
|
||||
{
|
||||
_receivedInitialNwVars = true;
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace Robust.Shared.Configuration
|
||||
|
||||
ApplyNetVarChange(msg.MsgChannel, msg.NetworkedVars);
|
||||
|
||||
if(msg.Tick < _timing.LastRealTick)
|
||||
if(msg.Tick != default && msg.Tick < _timing.LastRealTick)
|
||||
Logger.WarningS("cfg", $"{msg.MsgChannel}: Received late nwVar message ({msg.Tick} < {_timing.LastRealTick} ).");
|
||||
|
||||
_netVarsMessages.RemoveSwap(i);
|
||||
@@ -148,45 +148,50 @@ namespace Robust.Shared.Configuration
|
||||
|
||||
private void ApplyNetVarChange(INetChannel msgChannel, List<(string name, object value)> networkedVars)
|
||||
{
|
||||
Logger.DebugS("cfg", "Handling replicated cvars...");
|
||||
Logger.DebugS("cfg", $"{msgChannel} Handling replicated cvars...");
|
||||
|
||||
foreach (var (name, value) in networkedVars)
|
||||
if (_netManager.IsClient)
|
||||
{
|
||||
if (_netManager.IsClient) // Server sent us a CVar update.
|
||||
// Server sent us a CVar update.
|
||||
foreach (var (name, value) in networkedVars)
|
||||
{
|
||||
// Actually set the CVar
|
||||
base.SetCVar(name, value);
|
||||
Logger.DebugS("cfg", $"name={name}, val={value}");
|
||||
}
|
||||
else // Client sent us a CVar update
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Client sent us a CVar update
|
||||
if (!_replicatedCVars.TryGetValue(msgChannel, out var clientCVars))
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate CVars but is not in _replicatedCVars.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (name, value) in networkedVars)
|
||||
{
|
||||
if (!_configVars.TryGetValue(name, out var cVar))
|
||||
{
|
||||
if (!_configVars.TryGetValue(name, out var cVar))
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unknown CVar '{name}.'");
|
||||
continue;
|
||||
}
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unknown CVar '{name}.'");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cVar.Registered)
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unregistered CVar '{name}.'");
|
||||
continue;
|
||||
}
|
||||
if (!cVar.Registered)
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unregistered CVar '{name}.'");
|
||||
continue;
|
||||
}
|
||||
|
||||
if((cVar.Flags & CVar.REPLICATED) != 0)
|
||||
{
|
||||
var clientCVars = _replicatedCVars[msgChannel];
|
||||
|
||||
if (clientCVars.ContainsKey(name))
|
||||
clientCVars[name] = value;
|
||||
else
|
||||
clientCVars.Add(name, value);
|
||||
|
||||
Logger.DebugS("cfg", $"name={name}, val={value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an un-replicated CVar '{name}.'");
|
||||
}
|
||||
if((cVar.Flags & CVar.REPLICATED) != 0)
|
||||
{
|
||||
clientCVars[name] = value;
|
||||
Logger.DebugS("cfg", $"name={name}, val={value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an un-replicated CVar '{name}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,7 +295,7 @@ namespace Robust.Shared.Configuration
|
||||
Logger.InfoS("cfg", "Sending client info...");
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgConVars>();
|
||||
msg.Tick = _timing.CurTick;
|
||||
msg.Tick = default;
|
||||
msg.NetworkedVars = GetReplicatedVars();
|
||||
_netManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace Robust.Shared.Console
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, IConsoleCommand> RegisteredCommands => AvailableCommands;
|
||||
|
||||
public abstract event ConAnyCommandCallback? AnyCommandExecuted;
|
||||
|
||||
protected ConsoleHost()
|
||||
{
|
||||
LocalShell = new ConsoleShell(this, null);
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Robust.Shared.Console
|
||||
/// <param name="args">An array of all the parsed arguments.</param>
|
||||
public delegate void ConCommandCallback(IConsoleShell shell, string argStr, string[] args);
|
||||
|
||||
public delegate void ConAnyCommandCallback(IConsoleShell shell, string commandName, string argStr, string[] args);
|
||||
|
||||
/// <summary>
|
||||
/// The console host exists as a singleton subsystem that provides all of the features of the console API.
|
||||
/// It will register console commands, spawn console shells and execute command strings.
|
||||
@@ -38,7 +40,10 @@ namespace Robust.Shared.Console
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, IConsoleCommand> RegisteredCommands { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Invoked before any console command is executed.
|
||||
/// </summary>
|
||||
event ConAnyCommandCallback AnyCommandExecuted;
|
||||
event EventHandler ClearText;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -871,6 +871,7 @@ Types:
|
||||
IEquatable`1: { }
|
||||
IFormatProvider: { All: True }
|
||||
IFormattable: { All: True }
|
||||
Index: { All: True }
|
||||
IndexOutOfRangeException: { All: True }
|
||||
Int16: { All: True }
|
||||
Int32: { All: True }
|
||||
@@ -912,6 +913,7 @@ Types:
|
||||
ParamArrayAttribute: { All: True }
|
||||
Predicate`1: { All: True } # Delegate
|
||||
Random: { All: True }
|
||||
Range: { All: True }
|
||||
ReadOnlyMemory`1:
|
||||
Methods:
|
||||
- "!0[] ToArray()"
|
||||
|
||||
@@ -1,35 +1,10 @@
|
||||
using System;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Called once when a collision starts
|
||||
/// </summary>
|
||||
public interface IStartCollide
|
||||
{
|
||||
/// <summary>
|
||||
/// We'll pass in both our body and the other body to save the behaviors having to get these components themselves.
|
||||
/// </summary>
|
||||
[Obsolete("Use StartCollideEvent instead")]
|
||||
void CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once when a collision ends.
|
||||
/// </summary>
|
||||
public interface IEndCollide
|
||||
{
|
||||
/// <summary>
|
||||
/// Run behaviour after all other collision behaviors have run.
|
||||
/// </summary>
|
||||
[Obsolete("Use EndCollideEvent instead")]
|
||||
void CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum BodyStatus: byte
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
@@ -59,6 +60,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public bool Island { get; set; }
|
||||
|
||||
internal BroadphaseComponent? Broadphase { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Store the body's index within the island so we can lookup its data.
|
||||
/// Key is Island's ID and value is our index.
|
||||
@@ -90,6 +93,47 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public Box2 LocalAABB
|
||||
{
|
||||
get
|
||||
{
|
||||
var broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
var broadphase = broadphaseSystem.GetBroadphase(this);
|
||||
|
||||
if (broadphase == null) return new Box2();
|
||||
|
||||
var worldPos = Owner.Transform.WorldPosition;
|
||||
var aabb = new Box2(worldPos, worldPos);
|
||||
|
||||
foreach (var fixture in Fixtures)
|
||||
{
|
||||
foreach (var proxy in fixture.Proxies)
|
||||
{
|
||||
aabb = aabb.Union(proxy.AABB);
|
||||
}
|
||||
}
|
||||
|
||||
return aabb;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public Box2 WorldAABB
|
||||
{
|
||||
get
|
||||
{
|
||||
var broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
var broadphase = broadphaseSystem.GetBroadphase(this);
|
||||
|
||||
if (broadphase == null) return new Box2();
|
||||
var localAABB = LocalAABB;
|
||||
var center = broadphase.Owner.Transform.WorldMatrix.Transform(localAABB.Center);
|
||||
|
||||
return new Box2Rotated(localAABB.Translated(center), broadphase.Owner.Transform.WorldRotation, center).CalcBoundingBox();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linked-list of all of our contacts.
|
||||
/// </summary>
|
||||
@@ -136,7 +180,7 @@ namespace Robust.Shared.GameObjects
|
||||
Force = Vector2.Zero;
|
||||
Torque = 0.0f;
|
||||
|
||||
RegenerateContacts();
|
||||
EntitySystem.Get<SharedBroadphaseSystem>().RegenerateContacts(this);
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PhysicsBodyTypeChangedEvent(_bodyType, oldType), false);
|
||||
}
|
||||
@@ -242,39 +286,10 @@ namespace Robust.Shared.GameObjects
|
||||
Awake = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all of our contacts and flags them as requiring regeneration next physics tick.
|
||||
/// </summary>
|
||||
public void RegenerateContacts()
|
||||
{
|
||||
var contactEdge = ContactEdges;
|
||||
while (contactEdge != null)
|
||||
{
|
||||
var contactEdge0 = contactEdge;
|
||||
contactEdge = contactEdge.Next;
|
||||
PhysicsMap.ContactManager.Destroy(contactEdge0.Contact!);
|
||||
}
|
||||
|
||||
ContactEdges = null;
|
||||
var broadphaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
|
||||
foreach (var fixture in Fixtures)
|
||||
{
|
||||
var proxyCount = fixture.ProxyCount;
|
||||
foreach (var (gridId, proxies) in fixture.Proxies)
|
||||
{
|
||||
var broadPhase = broadphaseSystem.GetBroadPhase(Owner.Transform.MapID, gridId);
|
||||
if (broadPhase == null) continue;
|
||||
for (var i = 0; i < proxyCount; i++)
|
||||
{
|
||||
broadPhase.TouchProxy(proxies[i].ProxyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
FixtureCount = _fixtures.Count;
|
||||
|
||||
foreach (var fixture in _fixtures)
|
||||
{
|
||||
fixture.Body = this;
|
||||
@@ -446,17 +461,19 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
var broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
|
||||
foreach (var fixture in toRemoveFixtures)
|
||||
{
|
||||
computeProperties = true;
|
||||
RemoveFixture(fixture);
|
||||
broadphaseSystem.DestroyFixture(this, fixture);
|
||||
}
|
||||
|
||||
// TODO: We also still need event listeners for shapes (Probably need C# events)
|
||||
foreach (var fixture in toAddFixtures)
|
||||
{
|
||||
computeProperties = true;
|
||||
AddFixture(fixture);
|
||||
broadphaseSystem.CreateFixture(this, fixture);
|
||||
fixture.Shape.ApplyState();
|
||||
}
|
||||
|
||||
@@ -568,9 +585,12 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public int FixtureCount { get; internal set; }
|
||||
|
||||
[DataField("fixtures")]
|
||||
[NeverPushInheritance]
|
||||
private List<Fixture> _fixtures = new();
|
||||
internal List<Fixture> _fixtures = new();
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disabled collision processing of this component.
|
||||
@@ -657,9 +677,6 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public bool HasProxies { get; set; }
|
||||
|
||||
// I made Mass read-only just because overwriting it doesn't touch inertia.
|
||||
/// <summary>
|
||||
/// Current mass of the entity in kilograms.
|
||||
@@ -1027,17 +1044,9 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public MapId BroadphaseMapId { get; set; }
|
||||
|
||||
public IEnumerable<PhysicsComponent> GetCollidingBodies()
|
||||
{
|
||||
foreach (var entity in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(this, Vector2.Zero))
|
||||
{
|
||||
yield return entity;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<PhysicsComponent> GetBodiesIntersecting()
|
||||
{
|
||||
foreach (var entity in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(Owner.Transform.MapID, GetWorldAABB()))
|
||||
foreach (var entity in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(Owner.Transform.MapID, GetWorldAABB()))
|
||||
{
|
||||
yield return entity;
|
||||
}
|
||||
@@ -1064,24 +1073,6 @@ namespace Robust.Shared.GameObjects
|
||||
return Transform.Mul(GetTransform(), localPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the proxies from all the broadphases.
|
||||
/// </summary>
|
||||
public void ClearProxies()
|
||||
{
|
||||
if (!HasProxies) return;
|
||||
|
||||
var broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
var mapId = BroadphaseMapId;
|
||||
|
||||
foreach (var fixture in Fixtures)
|
||||
{
|
||||
fixture.ClearProxies(mapId, broadPhaseSystem);
|
||||
}
|
||||
|
||||
HasProxies = false;
|
||||
}
|
||||
|
||||
public void FixtureChanged(Fixture fixture)
|
||||
{
|
||||
// TODO: Optimise this a LOT
|
||||
@@ -1089,12 +1080,32 @@ namespace Robust.Shared.GameObjects
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new FixtureUpdateMessage(this, fixture));
|
||||
}
|
||||
|
||||
private string GetFixtureName(Fixture fixture)
|
||||
public string GetFixtureName(Fixture fixture)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(fixture.ID)) return fixture.ID;
|
||||
|
||||
// For any fixtures that aren't named in the code we will assign one.
|
||||
return $"fixture-{_fixtures.IndexOf(fixture)}";
|
||||
var i = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var found = false;
|
||||
++i;
|
||||
var name = $"fixture_{i}";
|
||||
|
||||
foreach (var existing in _fixtures)
|
||||
{
|
||||
if (existing.ID.Equals(name))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetJointName(Joint joint)
|
||||
@@ -1158,73 +1169,6 @@ namespace Robust.Shared.GameObjects
|
||||
Force += force;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the proxies for each of our fixtures and add them to the broadphases.
|
||||
/// </summary>
|
||||
/// <param name="mapManager"></param>
|
||||
/// <param name="broadPhaseSystem"></param>
|
||||
public void CreateProxies(IMapManager? mapManager = null, SharedBroadPhaseSystem? broadPhaseSystem = null)
|
||||
{
|
||||
if (HasProxies) return;
|
||||
|
||||
BroadphaseMapId = Owner.Transform.MapID;
|
||||
|
||||
if (BroadphaseMapId == MapId.Nullspace)
|
||||
{
|
||||
HasProxies = true;
|
||||
return;
|
||||
}
|
||||
|
||||
broadPhaseSystem ??= EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
mapManager ??= IoCManager.Resolve<IMapManager>();
|
||||
var worldPosition = Owner.Transform.WorldPosition;
|
||||
var mapId = Owner.Transform.MapID;
|
||||
var worldAABB = GetWorldAABB();
|
||||
var worldRotation = Owner.Transform.WorldRotation.Theta;
|
||||
|
||||
// TODO: For singularity and shuttles: Any fixtures that have a MapGrid layer / mask needs to be added to the default broadphase (so it can collide with grids).
|
||||
|
||||
foreach (var gridId in mapManager.FindGridIdsIntersecting(mapId, worldAABB, true))
|
||||
{
|
||||
var broadPhase = broadPhaseSystem.GetBroadPhase(mapId, gridId);
|
||||
DebugTools.AssertNotNull(broadPhase);
|
||||
if (broadPhase == null) continue; // TODO
|
||||
|
||||
Vector2 offset = worldPosition;
|
||||
double gridRotation = worldRotation;
|
||||
|
||||
if (gridId != GridId.Invalid)
|
||||
{
|
||||
var grid = mapManager.GetGrid(gridId);
|
||||
offset -= grid.WorldPosition;
|
||||
// TODO: Should probably have a helper for this
|
||||
gridRotation = worldRotation - Owner.EntityManager.GetEntity(grid.GridEntityId).Transform.WorldRotation;
|
||||
}
|
||||
|
||||
foreach (var fixture in Fixtures)
|
||||
{
|
||||
fixture.ProxyCount = fixture.Shape.ChildCount;
|
||||
var proxies = new FixtureProxy[fixture.ProxyCount];
|
||||
|
||||
// TODO: Will need to pass in childIndex to this as well
|
||||
for (var i = 0; i < fixture.ProxyCount; i++)
|
||||
{
|
||||
var aabb = fixture.Shape.CalculateLocalBounds(gridRotation).Translated(offset);
|
||||
|
||||
var proxy = new FixtureProxy(aabb, fixture, i);
|
||||
|
||||
proxy.ProxyId = broadPhase.AddProxy(ref proxy);
|
||||
proxies[i] = proxy;
|
||||
DebugTools.Assert(proxies[i].ProxyId != DynamicTree.Proxy.Free);
|
||||
}
|
||||
|
||||
fixture.SetProxies(gridId, proxies);
|
||||
}
|
||||
}
|
||||
|
||||
HasProxies = true;
|
||||
}
|
||||
|
||||
// TOOD: Need SetTransformIgnoreContacts so we can teleport body and /ignore contacts/
|
||||
public void DestroyContacts()
|
||||
{
|
||||
@@ -1241,7 +1185,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
IEnumerable<IPhysBody> IPhysBody.GetCollidingEntities(Vector2 offset, bool approx)
|
||||
{
|
||||
return EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(this, offset, approx);
|
||||
return EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(this, offset, approx);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1254,6 +1198,9 @@ namespace Robust.Shared.GameObjects
|
||||
_awake = false;
|
||||
}
|
||||
|
||||
// TODO: Ordering fuckery need a new PR to fix some of this stuff
|
||||
PhysicsMap = EntitySystem.Get<SharedPhysicsSystem>().Maps[Owner.Transform.MapID];
|
||||
|
||||
Dirty();
|
||||
// Yeah yeah TODO Combine these
|
||||
// Implicitly assume that stuff doesn't cover if a non-collidable is initialized.
|
||||
@@ -1290,45 +1237,6 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public void AddFixture(Fixture fixture)
|
||||
{
|
||||
// TODO: SynchronizeFixtures could be more optimally done. Maybe just eventbus it
|
||||
// Also we need to queue updates and also not teardown completely every time.
|
||||
_fixtures.Add(fixture);
|
||||
Dirty();
|
||||
EntitySystem.Get<SharedBroadPhaseSystem>().AddFixture(this, fixture);
|
||||
}
|
||||
|
||||
public void RemoveFixture(Fixture fixture)
|
||||
{
|
||||
if (!_fixtures.Contains(fixture))
|
||||
{
|
||||
Logger.ErrorS("physics", $"Tried to remove fixture that isn't attached to the body {Owner.Uid}");
|
||||
return;
|
||||
}
|
||||
|
||||
ContactEdge? edge = ContactEdges;
|
||||
while (edge != null)
|
||||
{
|
||||
var contact = edge.Contact!;
|
||||
edge = edge.Next;
|
||||
|
||||
var fixtureA = contact.FixtureA;
|
||||
var fixtureB = contact.FixtureB;
|
||||
|
||||
if (fixture == fixtureA || fixture == fixtureB)
|
||||
{
|
||||
PhysicsMap.ContactManager.Destroy(contact);
|
||||
}
|
||||
}
|
||||
|
||||
_fixtures.Remove(fixture);
|
||||
EntitySystem.Get<SharedBroadPhaseSystem>().RemoveFixture(this, fixture);
|
||||
ResetMassData();
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public void AddJoint(Joint joint)
|
||||
{
|
||||
var id = GetJointName(joint);
|
||||
@@ -1356,7 +1264,7 @@ namespace Robust.Shared.GameObjects
|
||||
// Need to do these immediately in case collision behaviors deleted the body
|
||||
// TODO: Could be more optimal as currently broadphase will call this ANYWAY
|
||||
DestroyContacts();
|
||||
ClearProxies();
|
||||
EntitySystem.Get<SharedBroadphaseSystem>().RemoveBody(this);
|
||||
CanCollide = false;
|
||||
}
|
||||
|
||||
@@ -1382,7 +1290,27 @@ namespace Robust.Shared.GameObjects
|
||||
var fixMass = fixture.Mass;
|
||||
|
||||
_mass += fixMass;
|
||||
localCenter += fixture.Centroid * fixMass;
|
||||
|
||||
var center = Vector2.Zero;
|
||||
|
||||
// TODO: God this is garbage
|
||||
switch (fixture.Shape)
|
||||
{
|
||||
case PhysShapeAabb aabb:
|
||||
center = aabb.Centroid;
|
||||
break;
|
||||
case PhysShapeRect rect:
|
||||
center = rect.Centroid;
|
||||
break;
|
||||
case PolygonShape poly:
|
||||
center = poly.Centroid;
|
||||
break;
|
||||
case PhysShapeCircle circle:
|
||||
center = circle.Position;
|
||||
break;
|
||||
}
|
||||
|
||||
localCenter += center * fixMass;
|
||||
_inertia += fixture.Inertia;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -20,6 +18,8 @@ namespace Robust.Shared.GameObjects
|
||||
[DataField("boundingBox")]
|
||||
private Box2 _boundingBox = new(-0.5f, -0.5f, 0.5f, 0.5f);
|
||||
|
||||
internal OccluderTreeComponent? Tree = null;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Box2 BoundingBox
|
||||
{
|
||||
@@ -28,15 +28,10 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
_boundingBox = value;
|
||||
Dirty();
|
||||
BoundingBoxChanged();
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new OccluderUpdateEvent(this));
|
||||
}
|
||||
}
|
||||
|
||||
private void BoundingBoxChanged()
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new OccluderBoundingBoxChangedMessage(this));
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public virtual bool Enabled
|
||||
{
|
||||
@@ -47,30 +42,19 @@ namespace Robust.Shared.GameObjects
|
||||
return;
|
||||
|
||||
_enabled = value;
|
||||
if (_enabled)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new OccluderAddEvent(this));
|
||||
}
|
||||
else
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new OccluderRemoveEvent(this));
|
||||
}
|
||||
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
EntitySystem.Get<OccluderSystem>().AddOrUpdateEntity(Owner, Owner.Transform.Coordinates);
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
var transform = Owner.Transform;
|
||||
var map = transform.MapID;
|
||||
if (map != MapId.Nullspace)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new OccluderTreeRemoveOccluderMessage(this, map, transform.GridID));
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
return new OccluderComponentState(Enabled, BoundingBox);
|
||||
@@ -102,14 +86,4 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct OccluderBoundingBoxChangedMessage
|
||||
{
|
||||
public OccluderComponent Occluder;
|
||||
|
||||
public OccluderBoundingBoxChangedMessage(OccluderComponent occluder)
|
||||
{
|
||||
Occluder = occluder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the relevant occluder children of this entity.
|
||||
/// </summary>
|
||||
public sealed class OccluderTreeComponent : Component
|
||||
{
|
||||
public override string Name => "OccluderTree";
|
||||
|
||||
internal DynamicTree<OccluderComponent> Tree { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ namespace Robust.Shared.GameObjects
|
||||
xform.Parent = Owner.Transform;
|
||||
|
||||
// anchor snapping
|
||||
xform.LocalPosition = Grid.GridTileToLocal(Grid.TileIndicesFor(xform.LocalPosition)).Position;
|
||||
xform.LocalPosition = Grid.GridTileToLocal(Grid.TileIndicesFor(xform.WorldPosition)).Position;
|
||||
|
||||
xform.SetAnchored(result);
|
||||
|
||||
|
||||
@@ -734,7 +734,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
var newParentId = newState.ParentID;
|
||||
var rebuildMatrices = false;
|
||||
if (Parent?.Owner?.Uid != newParentId)
|
||||
if (Parent?.Owner.Uid != newParentId)
|
||||
{
|
||||
if (newParentId != _parent)
|
||||
{
|
||||
|
||||
@@ -11,46 +11,74 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
protected void SubscribeNetworkEvent<T>(
|
||||
EntityEventHandler<T> handler,
|
||||
Type[]? before=null, Type[]? after=null)
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where T : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeEvent(EventSource.Network, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<T>(EventSource.Network));
|
||||
}
|
||||
|
||||
protected void SubscribeNetworkEvent<T>(
|
||||
EntitySessionEventHandler<T> handler,
|
||||
Type[]? before=null, Type[]? after=null)
|
||||
where T : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeSessionEvent(EventSource.Network, this, handler);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(EventSource.Network));
|
||||
SubEvent(EventSource.Network, handler, before, after);
|
||||
}
|
||||
|
||||
protected void SubscribeLocalEvent<T>(
|
||||
EntityEventHandler<T> handler,
|
||||
Type[]? before=null, Type[]? after=null)
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where T : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, handler, GetType(), before, after);
|
||||
SubEvent(EventSource.Local, handler, before, after);
|
||||
}
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<T>(EventSource.Local));
|
||||
protected void SubscribeAllEvent<T>(
|
||||
EntityEventHandler<T> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where T : notnull
|
||||
{
|
||||
SubEvent(EventSource.All, handler, before, after);
|
||||
}
|
||||
|
||||
protected void SubscribeNetworkEvent<T>(
|
||||
EntitySessionEventHandler<T> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where T : notnull
|
||||
{
|
||||
SubSessionEvent(EventSource.Network, handler, before, after);
|
||||
}
|
||||
|
||||
protected void SubscribeLocalEvent<T>(
|
||||
EntitySessionEventHandler<T> handler,
|
||||
Type[]? before=null, Type[]? after=null)
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where T : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeSessionEvent(EventSource.Local, this, handler);
|
||||
SubSessionEvent(EventSource.Local, handler, before, after);
|
||||
}
|
||||
|
||||
protected void SubscribeAllEvent<T>(
|
||||
EntitySessionEventHandler<T> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where T : notnull
|
||||
{
|
||||
SubSessionEvent(EventSource.All, handler, before, after);
|
||||
}
|
||||
|
||||
private void SubEvent<T>(
|
||||
EventSource src,
|
||||
EntityEventHandler<T> handler,
|
||||
Type[]? before, Type[]? after)
|
||||
where T : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeEvent(src, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(EventSource.Local));
|
||||
_subscriptions.Add(new SubBroadcast<T>(src));
|
||||
}
|
||||
|
||||
private void SubSessionEvent<T>(
|
||||
EventSource src,
|
||||
EntitySessionEventHandler<T> handler,
|
||||
Type[]? before, Type[]? after)
|
||||
where T : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeSessionEvent(src, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(src));
|
||||
}
|
||||
|
||||
[Obsolete("Unsubscribing of entity system events is now automatic")]
|
||||
@@ -69,7 +97,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
protected void SubscribeLocalEvent<TComp, TEvent>(
|
||||
ComponentEventHandler<TComp, TEvent> handler,
|
||||
Type[]? before=null, Type[]? after=null)
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
#endif
|
||||
|
||||
private DependencyCollection _systemDependencyCollection = default!;
|
||||
private List<Type> _systemTypes = new();
|
||||
|
||||
private static readonly Histogram _tickUsageHistogram = Metrics.CreateHistogram("robust_entity_systems_update_usage",
|
||||
"Amount of time spent processing each entity system", new HistogramConfiguration
|
||||
{
|
||||
@@ -38,24 +41,11 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private readonly Stopwatch _stopwatch = new();
|
||||
|
||||
/// <summary>
|
||||
/// Maps system types to instances.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<Type, IEntitySystem> _systems = new();
|
||||
/// <summary>
|
||||
/// Maps system supertypes to instances.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<Type, IEntitySystem> _supertypeSystems = new();
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
[ViewVariables] private UpdateReg[] _updateOrder = Array.Empty<UpdateReg>();
|
||||
[ViewVariables] private IEntitySystem[] _frameUpdateOrder = Array.Empty<IEntitySystem>();
|
||||
|
||||
[ViewVariables] public IReadOnlyCollection<IEntitySystem> AllSystems => _systems.Values;
|
||||
|
||||
public bool MetricsEnabled { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -68,55 +58,36 @@ namespace Robust.Shared.GameObjects
|
||||
public T GetEntitySystem<T>()
|
||||
where T : IEntitySystem
|
||||
{
|
||||
var type = typeof(T);
|
||||
// check using exact match first, then check using the supertype
|
||||
if (!_systems.ContainsKey(type))
|
||||
{
|
||||
if (!_supertypeSystems.ContainsKey(type))
|
||||
{
|
||||
throw new InvalidEntitySystemException();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (T) _supertypeSystems[type];
|
||||
}
|
||||
}
|
||||
|
||||
return (T)_systems[type];
|
||||
return _systemDependencyCollection.Resolve<T>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetEntitySystem<T>([NotNullWhen(true)] out T? entitySystem)
|
||||
where T : IEntitySystem
|
||||
{
|
||||
if (_systems.TryGetValue(typeof(T), out var system))
|
||||
{
|
||||
entitySystem = (T) system;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_supertypeSystems.TryGetValue(typeof(T), out var systemFromSupertype))
|
||||
{
|
||||
entitySystem = (T) systemFromSupertype;
|
||||
return true;
|
||||
}
|
||||
|
||||
entitySystem = default;
|
||||
return false;
|
||||
return _systemDependencyCollection.TryResolveType<T>(out entitySystem);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
HashSet<Type> excludedTypes = new();
|
||||
var excludedTypes = new HashSet<Type>();
|
||||
|
||||
_systemDependencyCollection = new(IoCManager.Instance!);
|
||||
var subTypes = new Dictionary<Type, Type>();
|
||||
_systemTypes.Clear();
|
||||
foreach (var type in _reflectionManager.GetAllChildren<IEntitySystem>().Concat(_extraLoadedTypes))
|
||||
{
|
||||
Logger.DebugS("go.sys", "Initializing entity system {0}", type);
|
||||
// Force IoC inject of all systems
|
||||
var instance = _typeFactory.CreateInstanceUnchecked<IEntitySystem>(type, oneOff: true);
|
||||
|
||||
_systems.Add(type, instance);
|
||||
_systemDependencyCollection.Register(type);
|
||||
_systemTypes.Add(type);
|
||||
|
||||
excludedTypes.Add(type);
|
||||
if (subTypes.ContainsKey(type))
|
||||
{
|
||||
subTypes.Remove(type);
|
||||
}
|
||||
|
||||
// also register systems under their supertypes, so they can be retrieved by their supertype.
|
||||
// We don't do this if there are multiple subtype systems of that supertype though, otherwise
|
||||
@@ -127,27 +98,36 @@ namespace Robust.Shared.GameObjects
|
||||
// so don't register under the supertype because it would be unclear
|
||||
// which instance to return if we retrieved it by the supertype
|
||||
if (excludedTypes.Contains(baseType)) continue;
|
||||
if (_supertypeSystems.ContainsKey(baseType))
|
||||
|
||||
if (subTypes.ContainsKey(baseType))
|
||||
{
|
||||
_supertypeSystems.Remove(baseType);
|
||||
subTypes.Remove(baseType);
|
||||
excludedTypes.Add(baseType);
|
||||
}
|
||||
else
|
||||
{
|
||||
_supertypeSystems.Add(baseType, instance);
|
||||
subTypes.Add(baseType, type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach (var system in _systems.Values)
|
||||
foreach (var (baseType, type) in subTypes)
|
||||
{
|
||||
_systemDependencyCollection.Register(baseType, type, overwrite: true);
|
||||
_systemTypes.Remove(baseType);
|
||||
}
|
||||
|
||||
_systemDependencyCollection.BuildGraph();
|
||||
|
||||
foreach (var systemType in _systemTypes)
|
||||
{
|
||||
var system = (IEntitySystem)_systemDependencyCollection.ResolveType(systemType);
|
||||
system.Initialize();
|
||||
SystemLoaded?.Invoke(this, new SystemChangedArgs(system));
|
||||
}
|
||||
|
||||
// Create update order for entity systems.
|
||||
var (fUpdate, update) = CalculateUpdateOrder(_systems.Values, _supertypeSystems);
|
||||
var (fUpdate, update) = CalculateUpdateOrder(_systemTypes, subTypes, _systemDependencyCollection);
|
||||
|
||||
_frameUpdateOrder = fUpdate.ToArray();
|
||||
_updateOrder = update
|
||||
@@ -163,23 +143,24 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private static (IEnumerable<IEntitySystem> frameUpd, IEnumerable<IEntitySystem> upd)
|
||||
CalculateUpdateOrder(
|
||||
Dictionary<Type, IEntitySystem>.ValueCollection systems,
|
||||
Dictionary<Type, IEntitySystem> supertypeSystems)
|
||||
List<Type> systemTypes,
|
||||
Dictionary<Type, Type> subTypes,
|
||||
DependencyCollection dependencyCollection)
|
||||
{
|
||||
var allNodes = new List<TopologicalSort.GraphNode<IEntitySystem>>();
|
||||
var typeToNode = new Dictionary<Type, TopologicalSort.GraphNode<IEntitySystem>>();
|
||||
|
||||
foreach (var system in systems)
|
||||
foreach (var systemType in systemTypes)
|
||||
{
|
||||
var node = new TopologicalSort.GraphNode<IEntitySystem>(system);
|
||||
var node = new TopologicalSort.GraphNode<IEntitySystem>((IEntitySystem) dependencyCollection.ResolveType(systemType));
|
||||
|
||||
typeToNode.Add(system.GetType(), node);
|
||||
typeToNode.Add(systemType, node);
|
||||
allNodes.Add(node);
|
||||
}
|
||||
|
||||
foreach (var (type, system) in supertypeSystems)
|
||||
foreach (var (type, system) in subTypes)
|
||||
{
|
||||
var node = typeToNode[system.GetType()];
|
||||
var node = typeToNode[system];
|
||||
typeToNode[type] = node;
|
||||
}
|
||||
|
||||
@@ -220,18 +201,20 @@ namespace Robust.Shared.GameObjects
|
||||
public void Shutdown()
|
||||
{
|
||||
// System.Values is modified by RemoveSystem
|
||||
foreach (var system in _systems.Values)
|
||||
foreach (var systemType in _systemTypes)
|
||||
{
|
||||
if(_systemDependencyCollection == null) continue;
|
||||
var system = (IEntitySystem)_systemDependencyCollection.ResolveType(systemType);
|
||||
SystemUnloaded?.Invoke(this, new SystemChangedArgs(system));
|
||||
system.Shutdown();
|
||||
_entityManager.EventBus.UnsubscribeEvents(system);
|
||||
}
|
||||
|
||||
_systems.Clear();
|
||||
_systemTypes.Clear();
|
||||
_updateOrder = Array.Empty<UpdateReg>();
|
||||
_frameUpdateOrder = Array.Empty<IEntitySystem>();
|
||||
_supertypeSystems.Clear();
|
||||
_initialized = false;
|
||||
_systemDependencyCollection?.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -343,6 +326,4 @@ namespace Robust.Shared.GameObjects
|
||||
System = system;
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidEntitySystemException : Exception { }
|
||||
}
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public static class EventBusExt
|
||||
{
|
||||
public static void SubscribeSessionEvent<T>(this IEventBus eventBus, EventSource source,
|
||||
IEntityEventSubscriber subscriber, EntitySessionEventHandler<T> handler)
|
||||
public static void SubscribeSessionEvent<T>(
|
||||
this IEventBus eventBus,
|
||||
EventSource source,
|
||||
IEntityEventSubscriber subscriber,
|
||||
EntitySessionEventHandler<T> handler)
|
||||
{
|
||||
var wrapper = new HandlerWrapper<T>(handler);
|
||||
eventBus.SubscribeEvent<EntitySessionMessage<T>>(source, subscriber, wrapper.Invoke);
|
||||
}
|
||||
|
||||
public static void SubscribeSessionEvent<T>(
|
||||
this IEventBus eventBus,
|
||||
EventSource source,
|
||||
IEntityEventSubscriber subscriber,
|
||||
EntitySessionEventHandler<T> handler,
|
||||
Type orderType,
|
||||
Type[]? before=null,
|
||||
Type[]? after=null)
|
||||
{
|
||||
var wrapper = new HandlerWrapper<T>(handler);
|
||||
eventBus.SubscribeEvent<EntitySessionMessage<T>>(
|
||||
source,
|
||||
subscriber,
|
||||
wrapper.Invoke,
|
||||
orderType,
|
||||
before, after);
|
||||
}
|
||||
|
||||
private sealed class HandlerWrapper<T>
|
||||
{
|
||||
public HandlerWrapper(EntitySessionEventHandler<T> handler)
|
||||
|
||||
@@ -32,8 +32,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
event EventHandler<SystemChangedArgs> SystemUnloaded;
|
||||
|
||||
IReadOnlyCollection<IEntitySystem> AllSystems { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get an entity system of the specified type.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -10,54 +10,88 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public abstract class OccluderSystem : EntitySystem
|
||||
{
|
||||
|
||||
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
|
||||
private readonly Dictionary<MapId, Dictionary<GridId, DynamicTree<OccluderComponent>>> _gridTrees =
|
||||
new();
|
||||
private const float TreeGrowthRate = 256;
|
||||
|
||||
private readonly List<(OccluderComponent Occluder, EntityCoordinates Coordinates)> _occluderAddQueue =
|
||||
new();
|
||||
|
||||
private readonly List<(OccluderComponent Occluder, EntityCoordinates Coordinates)> _occluderRemoveQueue =
|
||||
new();
|
||||
|
||||
internal bool TryGetOccluderTreeForGrid(MapId mapId, GridId gridId, [NotNullWhen(true)] out DynamicTree<OccluderComponent>? gridTree)
|
||||
{
|
||||
gridTree = null;
|
||||
|
||||
if (!_gridTrees.TryGetValue(mapId, out var grids))
|
||||
return false;
|
||||
|
||||
if (!grids.TryGetValue(gridId, out gridTree))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
private Queue<OccluderEvent> _updates = new(64);
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_mapManager.MapCreated += OnMapCreated;
|
||||
_mapManager.MapDestroyed += OnMapDestroyed;
|
||||
_mapManager.OnGridCreated += OnGridCreated;
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
|
||||
SubscribeLocalEvent<MoveEvent>(EntMoved);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
|
||||
SubscribeLocalEvent<OccluderBoundingBoxChangedMessage>(OccluderBoundingBoxChanged);
|
||||
SubscribeLocalEvent<OccluderTreeRemoveOccluderMessage>(RemoveOccluder);
|
||||
SubscribeLocalEvent<GridInitializeEvent>(HandleGridInit);
|
||||
SubscribeLocalEvent<OccluderTreeComponent, ComponentInit>(HandleOccluderTreeInit);
|
||||
SubscribeLocalEvent<OccluderComponent, ComponentInit>(HandleOccluderInit);
|
||||
SubscribeLocalEvent<OccluderComponent, ComponentShutdown>(HandleOccluderShutdown);
|
||||
|
||||
SubscribeLocalEvent<OccluderComponent, MoveEvent>(EntMoved);
|
||||
SubscribeLocalEvent<OccluderComponent, EntParentChangedMessage>(EntParentChanged);
|
||||
SubscribeLocalEvent<OccluderEvent>(ev => _updates.Enqueue(ev));
|
||||
}
|
||||
|
||||
internal IEnumerable<OccluderTreeComponent> GetOccluderTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) yield break;
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<OccluderTreeComponent>();
|
||||
}
|
||||
|
||||
yield return _mapManager.GetMapEntity(mapId).GetComponent<OccluderTreeComponent>();
|
||||
}
|
||||
|
||||
private void HandleOccluderInit(EntityUid uid, OccluderComponent component, ComponentInit args)
|
||||
{
|
||||
if (!component.Enabled) return;
|
||||
_updates.Enqueue(new OccluderAddEvent(component));
|
||||
}
|
||||
|
||||
private void HandleOccluderShutdown(EntityUid uid, OccluderComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (!component.Enabled) return;
|
||||
_updates.Enqueue(new OccluderRemoveEvent(component));
|
||||
}
|
||||
|
||||
private void HandleOccluderTreeInit(EntityUid uid, OccluderTreeComponent component, ComponentInit args)
|
||||
{
|
||||
var capacity = (int) Math.Min(256, Math.Ceiling(component.Owner.Transform.ChildCount / TreeGrowthRate) * TreeGrowthRate);
|
||||
|
||||
component.Tree = new DynamicTree<OccluderComponent>(ExtractAabbFunc, capacity: capacity);
|
||||
}
|
||||
|
||||
private void HandleGridInit(GridInitializeEvent ev)
|
||||
{
|
||||
EntityManager.GetEntity(ev.EntityUid).EnsureComponent<OccluderTreeComponent>();
|
||||
}
|
||||
|
||||
private OccluderTreeComponent? GetOccluderTree(OccluderComponent component)
|
||||
{
|
||||
var entity = component.Owner;
|
||||
|
||||
if (entity.Transform.MapID == MapId.Nullspace) return null;
|
||||
|
||||
var parent = entity.Transform.Parent?.Owner;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (parent == null) break;
|
||||
|
||||
if (parent.TryGetComponent(out OccluderTreeComponent? comp)) return comp;
|
||||
parent = parent.Transform.Parent?.Owner;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_mapManager.MapCreated -= OnMapCreated;
|
||||
_mapManager.MapDestroyed -= OnMapDestroyed;
|
||||
_mapManager.OnGridCreated -= OnGridCreated;
|
||||
_mapManager.OnGridRemoved -= OnGridRemoved;
|
||||
_updates.Clear();
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
@@ -72,143 +106,59 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void UpdateTrees()
|
||||
{
|
||||
// Only care about stuff parented to a grid I think?
|
||||
foreach (var (occluder, coordinates) in _occluderRemoveQueue)
|
||||
while (_updates.TryDequeue(out var occluderUpdate))
|
||||
{
|
||||
if (coordinates.TryGetParent(EntityManager, out var parent) &&
|
||||
parent.HasComponent<MapGridComponent>())
|
||||
{
|
||||
var gridTree = _gridTrees[parent.Transform.MapID][parent.Transform.GridID];
|
||||
OccluderTreeComponent? tree;
|
||||
var component = occluderUpdate.Component;
|
||||
|
||||
gridTree.Remove(occluder);
|
||||
switch (occluderUpdate)
|
||||
{
|
||||
case OccluderAddEvent:
|
||||
if (component.Tree != null) break;
|
||||
tree = GetOccluderTree(component);
|
||||
if (tree == null) break;
|
||||
component.Tree = tree;
|
||||
tree.Tree.Add(component);
|
||||
break;
|
||||
case OccluderUpdateEvent:
|
||||
var oldTree = component.Tree;
|
||||
tree = GetOccluderTree(component);
|
||||
if (oldTree != tree)
|
||||
{
|
||||
oldTree?.Tree.Remove(component);
|
||||
tree?.Tree.Add(component);
|
||||
component.Tree = tree;
|
||||
break;
|
||||
}
|
||||
|
||||
tree?.Tree.Update(component);
|
||||
|
||||
break;
|
||||
case OccluderRemoveEvent:
|
||||
tree = component.Tree;
|
||||
tree?.Tree.Remove(component);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"No implemented occluder update for {occluderUpdate.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
_occluderRemoveQueue.Clear();
|
||||
|
||||
foreach (var (occluder, coordinates) in _occluderAddQueue)
|
||||
{
|
||||
if (occluder.Deleted) continue;
|
||||
|
||||
if (coordinates.TryGetParent(EntityManager, out var parent) &&
|
||||
parent.HasComponent<MapGridComponent>() || occluder.Owner.Transform.GridID == GridId.Invalid)
|
||||
{
|
||||
parent ??= EntityManager.GetEntity(occluder.Owner.Transform.ParentUid);
|
||||
var gridTree = _gridTrees[parent.Transform.MapID][parent.Transform.GridID];
|
||||
|
||||
gridTree.AddOrUpdate(occluder);
|
||||
}
|
||||
}
|
||||
|
||||
_occluderAddQueue.Clear();
|
||||
}
|
||||
|
||||
// If the Transform is removed BEFORE the Occluder,
|
||||
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
|
||||
// Otherwise these will still have their past MapId and that's all we need..
|
||||
private void RemoveOccluder(OccluderTreeRemoveOccluderMessage ev)
|
||||
private void EntMoved(EntityUid uid, OccluderComponent component, MoveEvent args)
|
||||
{
|
||||
_gridTrees[ev.MapId][ev.GridId].Remove(ev.Occluder);
|
||||
_updates.Enqueue(new OccluderUpdateEvent(component));
|
||||
}
|
||||
|
||||
private void OccluderBoundingBoxChanged(OccluderBoundingBoxChangedMessage ev)
|
||||
private void EntParentChanged(EntityUid uid, OccluderComponent component, EntParentChangedMessage args)
|
||||
{
|
||||
QueueUpdateOccluder(ev.Occluder, ev.Occluder.Owner.Transform.Coordinates);
|
||||
}
|
||||
|
||||
private void EntMoved(MoveEvent ev)
|
||||
{
|
||||
ev.OldPosition.TryGetParent(EntityManager, out var oldParent);
|
||||
ev.NewPosition.TryGetParent(EntityManager, out var newParent);
|
||||
|
||||
if (oldParent?.Uid != newParent?.Uid)
|
||||
RemoveEntity(ev.Sender, ev.OldPosition);
|
||||
|
||||
AddOrUpdateEntity(ev.Sender, ev.NewPosition);
|
||||
}
|
||||
|
||||
private void EntParentChanged(EntParentChangedMessage message)
|
||||
{
|
||||
if (!message.Entity.TryGetComponent(out OccluderComponent? occluder))
|
||||
return;
|
||||
|
||||
// Really only care if it's a map or grid
|
||||
if (message.OldParent != null && message.OldParent.TryGetComponent(out MapGridComponent? oldGrid))
|
||||
{
|
||||
var map = message.OldParent.Transform.MapID;
|
||||
if (_gridTrees[map].TryGetValue(oldGrid.GridIndex, out var tree))
|
||||
{
|
||||
tree.Remove(occluder);
|
||||
}
|
||||
}
|
||||
|
||||
var newParent = EntityManager.GetEntity(message.Entity.Transform.ParentUid);
|
||||
|
||||
newParent.TryGetComponent(out MapGridComponent? newGrid);
|
||||
var newGridIndex = newGrid?.GridIndex ?? GridId.Invalid;
|
||||
var newMap = newParent.Transform.MapID;
|
||||
|
||||
if (!_gridTrees.TryGetValue(newMap, out var newMapGrids))
|
||||
{
|
||||
newMapGrids = new Dictionary<GridId, DynamicTree<OccluderComponent>>();
|
||||
_gridTrees[newMap] = newMapGrids;
|
||||
}
|
||||
|
||||
if (!newMapGrids.TryGetValue(newGridIndex, out var newTree))
|
||||
{
|
||||
newTree = new DynamicTree<OccluderComponent>(ExtractAabbFunc);
|
||||
newMapGrids[newGridIndex] = newTree;
|
||||
}
|
||||
|
||||
newTree.AddOrUpdate(occluder);
|
||||
}
|
||||
|
||||
private void RemoveEntity(IEntity entity, EntityCoordinates coordinates)
|
||||
{
|
||||
if (entity.TryGetComponent(out OccluderComponent? occluder))
|
||||
{
|
||||
QueueRemoveOccluder(occluder, coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddOrUpdateEntity(IEntity entity, EntityCoordinates coordinates)
|
||||
{
|
||||
if (entity.TryGetComponent(out OccluderComponent? occluder))
|
||||
{
|
||||
QueueUpdateOccluder(occluder, coordinates);
|
||||
}
|
||||
// Do we even need the children update? Coz they be slow af and allocate a lot.
|
||||
// If you do end up adding children back in then for the love of GOD check if the entity has a mapgridcomponent
|
||||
}
|
||||
|
||||
private void OnMapDestroyed(object? sender, MapEventArgs e)
|
||||
{
|
||||
_gridTrees.Remove(e.Map);
|
||||
_updates.Enqueue(new OccluderUpdateEvent(component));
|
||||
}
|
||||
|
||||
private void OnMapCreated(object? sender, MapEventArgs e)
|
||||
{
|
||||
if (e.Map == MapId.Nullspace)
|
||||
return;
|
||||
if (e.Map == MapId.Nullspace) return;
|
||||
|
||||
_gridTrees[e.Map] = new Dictionary<GridId, DynamicTree<OccluderComponent>>();
|
||||
}
|
||||
|
||||
private void OnGridRemoved(MapId mapId, GridId gridId)
|
||||
{
|
||||
foreach (var (_, gridIds) in _gridTrees)
|
||||
{
|
||||
if (gridIds.Remove(gridId))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGridCreated(MapId mapId, GridId gridId)
|
||||
{
|
||||
if (!_gridTrees.TryGetValue(mapId, out var gridTree))
|
||||
return;
|
||||
|
||||
gridTree.Add(gridId, new DynamicTree<OccluderComponent>(ExtractAabbFunc));
|
||||
_mapManager.GetMapEntity(e.Map).EnsureComponent<OccluderTreeComponent>();
|
||||
}
|
||||
|
||||
private static Box2 ExtractAabbFunc(in OccluderComponent o)
|
||||
@@ -216,27 +166,26 @@ namespace Robust.Shared.GameObjects
|
||||
return o.BoundingBox.Translated(o.Owner.Transform.LocalPosition);
|
||||
}
|
||||
|
||||
private void QueueUpdateOccluder(OccluderComponent occluder, EntityCoordinates coordinates)
|
||||
{
|
||||
_occluderAddQueue.Add((occluder, coordinates));
|
||||
}
|
||||
|
||||
private void QueueRemoveOccluder(OccluderComponent occluder, EntityCoordinates coordinates)
|
||||
{
|
||||
_occluderRemoveQueue.Add((occluder, coordinates));
|
||||
}
|
||||
|
||||
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId originMapId, in Ray ray, float maxLength,
|
||||
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId mapId, in Ray ray, float maxLength,
|
||||
Func<IEntity, bool>? predicate = null, bool returnOnFirstHit = true)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<RayCastResults>();
|
||||
var list = new List<RayCastResults>();
|
||||
var worldBox = new Box2();
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(originMapId, worldBox, true))
|
||||
var endPoint = ray.Position + ray.Direction * maxLength;
|
||||
var worldBox = new Box2(Vector2.ComponentMin(ray.Position, endPoint), Vector2.ComponentMax(ray.Position, endPoint));
|
||||
|
||||
foreach (var comp in GetOccluderTrees(mapId, worldBox))
|
||||
{
|
||||
var gridTree = _gridTrees[originMapId][gridId];
|
||||
var transform = comp.Owner.Transform;
|
||||
var treePos = transform.WorldPosition;
|
||||
var treeRot = transform.WorldRotation;
|
||||
|
||||
gridTree.QueryRay(ref list,
|
||||
var relativePos = new Angle(-treeRot.Theta).RotateVec(ray.Position - treePos);
|
||||
|
||||
var treeRay = new Ray(relativePos, new Angle(-treeRot).RotateVec(ray.Direction));
|
||||
|
||||
comp.Tree.QueryRay(ref list,
|
||||
(ref List<RayCastResults> state, in OccluderComponent value, in Vector2 point, float distFromOrigin) =>
|
||||
{
|
||||
if (distFromOrigin > maxLength)
|
||||
@@ -251,24 +200,35 @@ namespace Robust.Shared.GameObjects
|
||||
var result = new RayCastResults(distFromOrigin, point, value.Owner);
|
||||
state.Add(result);
|
||||
return !returnOnFirstHit;
|
||||
}, ray);
|
||||
}, treeRay);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct OccluderTreeRemoveOccluderMessage
|
||||
internal sealed class OccluderAddEvent : OccluderEvent
|
||||
{
|
||||
public readonly OccluderComponent Occluder;
|
||||
public readonly MapId MapId;
|
||||
public readonly GridId GridId;
|
||||
public OccluderAddEvent(OccluderComponent component) : base(component) {}
|
||||
}
|
||||
|
||||
public OccluderTreeRemoveOccluderMessage(OccluderComponent occluder, MapId mapId, GridId gridId)
|
||||
internal sealed class OccluderUpdateEvent : OccluderEvent
|
||||
{
|
||||
public OccluderUpdateEvent(OccluderComponent component) : base(component) {}
|
||||
}
|
||||
|
||||
internal sealed class OccluderRemoveEvent : OccluderEvent
|
||||
{
|
||||
public OccluderRemoveEvent(OccluderComponent component) : base(component) {}
|
||||
}
|
||||
|
||||
internal abstract class OccluderEvent : EntityEventArgs
|
||||
{
|
||||
public OccluderComponent Component { get; }
|
||||
|
||||
public OccluderEvent(OccluderComponent component)
|
||||
{
|
||||
Occluder = occluder;
|
||||
MapId = mapId;
|
||||
GridId = gridId;
|
||||
Component = component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -11,10 +12,23 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private Queue<MoveEvent> _queuedEvents = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MoveEvent>(HandleMove);
|
||||
SubscribeLocalEvent<MoveEvent>(ev => _queuedEvents.Enqueue(ev));
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
// Need to queue because otherwise calling HandleMove during FrameUpdate will lead to prediction isues.
|
||||
while (_queuedEvents.TryDequeue(out var moveEvent))
|
||||
{
|
||||
HandleMove(moveEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleMove(MoveEvent moveEvent)
|
||||
@@ -31,9 +45,16 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
var transform = entity.Transform;
|
||||
|
||||
if (float.IsNaN(moveEvent.NewPosition.X) || float.IsNaN(moveEvent.NewPosition.Y))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapPos = moveEvent.NewPosition.ToMapPos(EntityManager);
|
||||
|
||||
// Change parent if necessary
|
||||
if (_mapManager.TryFindGridAt(transform.MapID, moveEvent.NewPosition.ToMapPos(EntityManager), out var grid) &&
|
||||
grid.GridEntityId.IsValid() &&
|
||||
if (_mapManager.TryFindGridAt(transform.MapID, mapPos, out var grid) &&
|
||||
EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt) &&
|
||||
grid.GridEntityId != entity.Uid)
|
||||
{
|
||||
// Some minor duplication here with AttachParent but only happens when going on/off grid so not a big deal ATM.
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace Robust.Shared.GameObjects
|
||||
});
|
||||
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
|
||||
|
||||
public IReadOnlyDictionary<MapId, PhysicsMap> Maps => _maps;
|
||||
private Dictionary<MapId, PhysicsMap> _maps = new();
|
||||
@@ -83,7 +84,7 @@ namespace Robust.Shared.GameObjects
|
||||
internal IReadOnlyList<VirtualController> Controllers => _controllers;
|
||||
private List<VirtualController> _controllers = new();
|
||||
|
||||
public Action<Fixture, Fixture, float, Manifold>? KinematicControllerCollision;
|
||||
public Action<Fixture, Fixture, float, Vector2>? KinematicControllerCollision;
|
||||
|
||||
public bool MetricsEnabled;
|
||||
private readonly Stopwatch _stopwatch = new();
|
||||
@@ -146,7 +147,7 @@ namespace Robust.Shared.GameObjects
|
||||
body.LinearVelocity += linearVelocityDiff;
|
||||
body.AngularVelocity += angularVelocityDiff;
|
||||
}
|
||||
|
||||
|
||||
private void HandleGridInit(GridInitializeEvent ev)
|
||||
{
|
||||
if (!EntityManager.TryGetEntity(ev.EntityUid, out var gridEntity)) return;
|
||||
@@ -355,6 +356,8 @@ namespace Robust.Shared.GameObjects
|
||||
if (mapId == MapId.Nullspace) continue;
|
||||
map.ProcessQueue();
|
||||
}
|
||||
|
||||
_physicsManager.ClearTransforms();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -68,7 +69,13 @@ namespace Robust.Shared.GameObjects
|
||||
if (ev.Sender.Deleted)
|
||||
continue;
|
||||
|
||||
RaiseLocalEvent(ev);
|
||||
// Hopefully we can remove this when PVS gets updated to not use NaNs
|
||||
if (!ev.NewPosition.IsValid(EntityManager))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RaiseLocalEvent(ev.Sender.Uid, ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,22 @@ namespace Robust.Shared.IoC
|
||||
_parentCollection = parentCollection;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryResolveType<T>([NotNullWhen(true)] out T? instance)
|
||||
{
|
||||
if (TryResolveType(typeof(T), out object? rawInstance))
|
||||
{
|
||||
if (rawInstance is T typedInstance)
|
||||
{
|
||||
instance = typedInstance;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
instance = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryResolveType(Type objectType, [MaybeNullWhen(false)] out object instance)
|
||||
{
|
||||
@@ -95,6 +111,8 @@ namespace Robust.Shared.IoC
|
||||
}, overwrite);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register<TInterface, TImplementation>(DependencyFactoryDelegate<TImplementation> factory, bool overwrite = false)
|
||||
where TImplementation : class, TInterface
|
||||
@@ -108,9 +126,12 @@ namespace Robust.Shared.IoC
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register(Type implementation, DependencyFactoryDelegate<object>? factory = null, bool overwrite = false)
|
||||
public void Register(Type implementation, DependencyFactoryDelegate<object>? factory = null,
|
||||
bool overwrite = false) => Register(implementation, implementation, factory, overwrite);
|
||||
|
||||
public void Register(Type interfaceType, Type implementation, DependencyFactoryDelegate<object>? factory = null, bool overwrite = false)
|
||||
{
|
||||
CheckRegisterInterface(implementation, implementation, overwrite);
|
||||
CheckRegisterInterface(interfaceType, implementation, overwrite);
|
||||
|
||||
object DefaultFactory()
|
||||
{
|
||||
@@ -144,9 +165,9 @@ namespace Robust.Shared.IoC
|
||||
return Activator.CreateInstance(implementation, parameters)!;
|
||||
}
|
||||
|
||||
_resolveTypes[implementation] = implementation;
|
||||
_resolveTypes[interfaceType] = implementation;
|
||||
_resolveFactories[implementation] = factory ?? DefaultFactory;
|
||||
_pendingResolves.Enqueue(implementation);
|
||||
_pendingResolves.Enqueue(interfaceType);
|
||||
}
|
||||
|
||||
[AssertionMethod]
|
||||
@@ -174,19 +195,25 @@ namespace Robust.Shared.IoC
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterInstance<TInterface>(object implementation, bool overwrite = false)
|
||||
{
|
||||
RegisterInstance(typeof(TInterface), implementation, overwrite);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterInstance(Type type, object implementation, bool overwrite = false)
|
||||
{
|
||||
if (implementation == null)
|
||||
throw new ArgumentNullException(nameof(implementation));
|
||||
|
||||
if (!(implementation is TInterface))
|
||||
if (!implementation.GetType().IsAssignableTo(type))
|
||||
throw new InvalidOperationException(
|
||||
$"Implementation type {implementation.GetType()} is not assignable to interface type {typeof(TInterface)}");
|
||||
$"Implementation type {implementation.GetType()} is not assignable to type {type}");
|
||||
|
||||
CheckRegisterInterface(typeof(TInterface), implementation.GetType(), overwrite);
|
||||
CheckRegisterInterface(type, implementation.GetType(), overwrite);
|
||||
|
||||
// do the equivalent of BuildGraph with a single type.
|
||||
_resolveTypes[typeof(TInterface)] = implementation.GetType();
|
||||
_services[typeof(TInterface)] = implementation;
|
||||
_resolveTypes[type] = implementation.GetType();
|
||||
_services[type] = implementation;
|
||||
|
||||
InjectDependencies(implementation, true);
|
||||
|
||||
|
||||
@@ -75,6 +75,19 @@ namespace Robust.Shared.IoC
|
||||
/// </param>
|
||||
void Register(Type implementation, DependencyFactoryDelegate<object>? factory = null, bool overwrite = false);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a simple implementation without an interface.
|
||||
/// </summary>
|
||||
/// <param name="interfaceType">The type that will be resolvable.</param>
|
||||
/// <param name="implementation">The type that will be resolvable.</param>
|
||||
/// <param name="factory">A factory method to construct the instance of the implementation.</param>
|
||||
/// <param name="overwrite">
|
||||
/// If true, do not throw an <see cref="InvalidOperationException"/> if an interface is already registered,
|
||||
/// replace the current implementation instead.
|
||||
/// </param>
|
||||
void Register(Type interfaceType, Type implementation, DependencyFactoryDelegate<object>? factory = null,
|
||||
bool overwrite = false);
|
||||
|
||||
/// <summary>
|
||||
/// Registers an interface to an existing instance of an implementation,
|
||||
/// making it accessible to <see cref="IDependencyCollection.Resolve{T}"/>.
|
||||
@@ -82,7 +95,6 @@ namespace Robust.Shared.IoC
|
||||
/// <see cref="IDependencyCollection.BuildGraph"/> does not need to be called after registering an instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">The type that will be resolvable.</typeparam>
|
||||
/// <typeparam name="TImplementation">The type that will be constructed as implementation.</typeparam>
|
||||
/// <param name="implementation">The existing instance to use as the implementation.</param>
|
||||
/// <param name="overwrite">
|
||||
/// If true, do not throw an <see cref="InvalidOperationException"/> if an interface is already registered,
|
||||
@@ -90,6 +102,20 @@ namespace Robust.Shared.IoC
|
||||
/// </param>
|
||||
void RegisterInstance<TInterface>(object implementation, bool overwrite = false);
|
||||
|
||||
/// <summary>
|
||||
/// Registers an interface to an existing instance of an implementation,
|
||||
/// making it accessible to <see cref="IDependencyCollection.Resolve{T}"/>.
|
||||
/// Unlike <see cref="IDependencyCollection.Register{TInterface, TImplementation}"/>,
|
||||
/// <see cref="IDependencyCollection.BuildGraph"/> does not need to be called after registering an instance.
|
||||
/// </summary>
|
||||
/// <param name="type">The type that will be resolvable.</param>
|
||||
/// <param name="implementation">The existing instance to use as the implementation.</param>
|
||||
/// <param name="overwrite">
|
||||
/// If true, do not throw an <see cref="InvalidOperationException"/> if an interface is already registered,
|
||||
/// replace the current implementation instead.
|
||||
/// </param>
|
||||
void RegisterInstance(Type type, object implementation, bool overwrite = false);
|
||||
|
||||
/// <summary>
|
||||
/// Clear all services and types.
|
||||
/// Use this between unit tests and on program shutdown.
|
||||
@@ -119,6 +145,11 @@ namespace Robust.Shared.IoC
|
||||
[Pure]
|
||||
object ResolveType(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a dependency manually.
|
||||
/// </summary>
|
||||
bool TryResolveType<T>([NotNullWhen(true)] out T? instance);
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a dependency manually.
|
||||
/// </summary>
|
||||
|
||||
@@ -142,6 +142,14 @@ namespace Robust.Shared.Localization
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record LocValueTimeSpan(TimeSpan Value) : LocValue<TimeSpan>(Value)
|
||||
{
|
||||
public override string Format(LocContext ctx)
|
||||
{
|
||||
return Value.ToString("g", ctx.Culture);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record LocValueString(string Value) : LocValue<string>(Value)
|
||||
{
|
||||
public override string Format(LocContext ctx)
|
||||
@@ -200,4 +208,4 @@ namespace Robust.Shared.Localization
|
||||
return matchValue.Equals(Value.ToString(), StringComparison.InvariantCultureIgnoreCase);
|
||||
}#1#
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +257,7 @@ namespace Robust.Shared.Localization
|
||||
ILocValue wrap => new FluentLocWrapperType(wrap),
|
||||
IEntity entity => new FluentLocWrapperType(new LocValueEntity(entity)),
|
||||
DateTime dateTime => new FluentLocWrapperType(new LocValueDateTime(dateTime)),
|
||||
TimeSpan timeSpan => new FluentLocWrapperType(new LocValueTimeSpan(timeSpan)),
|
||||
bool or Enum => (FluentString)obj.ToString()!.ToLowerInvariant(),
|
||||
string str => (FluentString)str,
|
||||
byte num => (FluentNumber)num,
|
||||
@@ -280,9 +281,8 @@ namespace Robust.Shared.Localization
|
||||
LocValueNone => FluentNone.None,
|
||||
LocValueNumber number => (FluentNumber)number.Value,
|
||||
LocValueString str => (FluentString)str.Value,
|
||||
LocValueDateTime dateTime => new FluentLocWrapperType(dateTime),
|
||||
_ => new FluentLocWrapperType(locValue),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,10 +365,10 @@ namespace Robust.Shared.Map
|
||||
/// <summary>
|
||||
/// Creates a set of EntityCoordinates given some MapCoordinates.
|
||||
/// </summary>
|
||||
/// <param name="entityManager"></param>
|
||||
/// <param name="mapManager"></param>
|
||||
/// <param name="coordinates"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Use FromMap(IMapManager mapManager, MapCoordinates coordinates) instead.")]
|
||||
public static EntityCoordinates FromMap(IEntityManager entityManager, IMapManager mapManager, MapCoordinates coordinates)
|
||||
{
|
||||
var mapId = coordinates.MapId;
|
||||
@@ -377,6 +377,19 @@ namespace Robust.Shared.Map
|
||||
return new EntityCoordinates(mapEntity.Uid, coordinates.Position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a set of EntityCoordinates given some MapCoordinates.
|
||||
/// </summary>
|
||||
/// <param name="mapManager"></param>
|
||||
/// <param name="coordinates"></param>
|
||||
public static EntityCoordinates FromMap(IMapManager mapManager, MapCoordinates coordinates)
|
||||
{
|
||||
var mapId = coordinates.MapId;
|
||||
var mapEntity = mapManager.GetMapEntity(mapId);
|
||||
|
||||
return new EntityCoordinates(mapEntity.Uid, coordinates.Position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this set of coordinates to Vector2i.
|
||||
/// </summary>
|
||||
|
||||
@@ -47,6 +47,10 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
Vector2 WorldPosition { get; set; }
|
||||
|
||||
Matrix3 WorldMatrix { get; }
|
||||
|
||||
Matrix3 InvWorldMatrix { get; }
|
||||
|
||||
#region TileAccess
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -112,6 +112,34 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public Matrix3 WorldMatrix
|
||||
{
|
||||
get
|
||||
{
|
||||
//TODO: Make grids real parents of entities.
|
||||
if(GridEntityId.IsValid())
|
||||
return _mapManager.EntityManager.GetEntity(GridEntityId).Transform.WorldMatrix;
|
||||
|
||||
return Matrix3.Identity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public Matrix3 InvWorldMatrix
|
||||
{
|
||||
get
|
||||
{
|
||||
//TODO: Make grids real parents of entities.
|
||||
if(GridEntityId.IsValid())
|
||||
return _mapManager.EntityManager.GetEntity(GridEntityId).Transform.InvWorldMatrix;
|
||||
|
||||
return Matrix3.Identity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands the AABB for this grid when a new tile is added. If the tile is already inside the existing AABB,
|
||||
/// nothing happens. If it is outside, the AABB is expanded to fit the new tile.
|
||||
@@ -489,7 +517,7 @@ namespace Robust.Shared.Map
|
||||
/// <inheritdoc />
|
||||
public Vector2 WorldToLocal(Vector2 posWorld)
|
||||
{
|
||||
return posWorld - WorldPosition;
|
||||
return InvWorldMatrix.Transform(posWorld);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -509,7 +537,7 @@ namespace Robust.Shared.Map
|
||||
/// <inheritdoc />
|
||||
public Vector2 LocalToWorld(Vector2 posLocal)
|
||||
{
|
||||
return posLocal + WorldPosition;
|
||||
return WorldMatrix.Transform(posLocal);
|
||||
}
|
||||
|
||||
public Vector2i WorldToTile(Vector2 posWorld)
|
||||
|
||||
@@ -523,7 +523,10 @@ namespace Robust.Shared.Map
|
||||
/// <inheritdoc />
|
||||
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, [NotNullWhen(true)] out IMapGrid? grid)
|
||||
{
|
||||
// TODO: this won't actually "work" but we need the broadphase refactor to finish it.
|
||||
// TODO: this won't actually "work" but tests are fucking me hard
|
||||
// We probably need to move these methods over to SharedBroadphaseSystem as they need to go through
|
||||
// physics to find grids intersecting a point anyway but the level of refactoring required for that
|
||||
// will kill me.
|
||||
foreach (var mapGrid in _grids.Values)
|
||||
{
|
||||
if (mapGrid.ParentMapId != mapId)
|
||||
@@ -548,31 +551,30 @@ namespace Robust.Shared.Map
|
||||
|
||||
public IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2 worldArea)
|
||||
{
|
||||
// TODO: Unfortunately can't use BroadphaseSystem here as it will explode. Need to suss it out with DI
|
||||
// TryFindGridAt works as is and helps with grid traversals.
|
||||
return _grids.Values.Where(g => g.ParentMapId == mapId && g.WorldBounds.Intersects(worldArea));
|
||||
}
|
||||
|
||||
public IEnumerable<GridId> FindGridIdsIntersecting(MapId mapId, Box2 worldArea, bool includeInvalid = false)
|
||||
{
|
||||
foreach (var (_, grid) in _grids)
|
||||
var broadphase = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
|
||||
foreach (var broady in broadphase.GetBroadphases(mapId, worldArea))
|
||||
{
|
||||
if (grid.ParentMapId != mapId) continue;
|
||||
if (!broady.Owner.TryGetComponent(out MapGridComponent? mapGridComponent)) continue;
|
||||
|
||||
var gridBounds = grid.WorldBounds;
|
||||
// If the worldArea is wholly contained within a grid then no need to get invalid
|
||||
if (gridBounds.Encloses(worldArea))
|
||||
yield return mapGridComponent.GridIndex;
|
||||
|
||||
// TODO: Optimise this. Need to avoid returning invalid unless absolutely necessary but still this check
|
||||
// be hella expensive. Also doesn't account for rotation so.
|
||||
if (broady.Owner.GetComponent<PhysicsComponent>().GetWorldAABB().Encloses(worldArea))
|
||||
{
|
||||
yield return grid.Index;
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (gridBounds.Intersects(worldArea))
|
||||
{
|
||||
yield return grid.Index;
|
||||
}
|
||||
}
|
||||
|
||||
if (includeInvalid)
|
||||
yield return GridId.Invalid;
|
||||
yield return GridId.Invalid;
|
||||
}
|
||||
|
||||
public virtual void DeleteGrid(GridId gridID)
|
||||
|
||||
@@ -56,6 +56,11 @@ namespace Robust.Shared.Network
|
||||
|
||||
NetUserData UserData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Has the serializer handshake completed and <see cref="INetManager.Connected"/> been ran?
|
||||
/// </summary>
|
||||
bool IsHandshakeComplete { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new NetMessage to be filled up and sent.
|
||||
/// </summary>
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Robust.Shared.Network.Messages
|
||||
[UsedImplicitly]
|
||||
internal class MsgMapStrClientHandshake : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
public override MsgGroups MsgGroup => MsgGroups.String;
|
||||
|
||||
/// <value>
|
||||
/// <c>true</c> if the client needs a new copy of the mapping,
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
private static byte[] MakeAuthHash(byte[] sharedSecret, byte[] pkBytes)
|
||||
{
|
||||
Logger.DebugS("auth", "shared: {0}, pk: {1}", Convert.ToBase64String(sharedSecret), Convert.ToBase64String(pkBytes));
|
||||
// Logger.DebugS("auth", "shared: {0}, pk: {1}", Convert.ToBase64String(sharedSecret), Convert.ToBase64String(pkBytes));
|
||||
|
||||
var incHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
|
||||
incHash.AppendData(sharedSecret);
|
||||
|
||||
@@ -46,6 +46,8 @@ namespace Robust.Shared.Network
|
||||
[ViewVariables] public NetUserId UserId => UserData.UserId;
|
||||
[ViewVariables] public NetUserData UserData { get; }
|
||||
|
||||
public bool IsHandshakeComplete { get; set; }
|
||||
|
||||
// Only used on server, contains the encryption to use for this channel.
|
||||
public NetEncryption? Encryption { get; set; }
|
||||
|
||||
@@ -88,6 +90,11 @@ namespace Robust.Shared.Network
|
||||
if (_connection.Status == NetConnectionStatus.Connected)
|
||||
_connection.Disconnect(reason);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{RemoteEndPoint}/{UserId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
Logger.InfoS("net",
|
||||
"Approved {ConnectionEndpoint} with username {Username} user ID {userId} into the server",
|
||||
connection.RemoteEndPoint, userData.UserName, userData.UserName);
|
||||
connection.RemoteEndPoint, userData.UserName, userData.UserId);
|
||||
|
||||
// Handshake complete!
|
||||
HandleInitialHandshakeComplete(peer, connection, userData, encryption, type);
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace Robust.Shared.Network
|
||||
/// <summary>
|
||||
/// Holds lookup table for NetMessage.Id -> NetMessage.Type
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Type> _messages = new();
|
||||
private readonly Dictionary<string, (Type type, bool isHandshake)> _messages = new();
|
||||
|
||||
/// <summary>
|
||||
/// The StringTable for transforming packet Ids to Packet name.
|
||||
@@ -204,7 +204,9 @@ namespace Robust.Shared.Network
|
||||
public IReadOnlyDictionary<Type, ProcessMessage> CallbackAudit => _callbacks;
|
||||
|
||||
/// <inheritdoc />
|
||||
public INetChannel? ServerChannel
|
||||
public INetChannel? ServerChannel => ServerChannelImpl;
|
||||
|
||||
private NetChannel? ServerChannelImpl
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -261,7 +263,7 @@ namespace Robust.Shared.Network
|
||||
_serializer.ClientHandshakeComplete += () =>
|
||||
{
|
||||
Logger.InfoS("net", "Client completed serializer handshake.");
|
||||
OnConnected(ServerChannel!);
|
||||
OnConnected(ServerChannelImpl!);
|
||||
};
|
||||
|
||||
_initialized = true;
|
||||
@@ -792,6 +794,7 @@ namespace Robust.Shared.Network
|
||||
var id = msg.ReadByte();
|
||||
|
||||
ref var entry = ref _netMsgFunctions[id];
|
||||
|
||||
if (entry.CreateFunction == null)
|
||||
{
|
||||
Logger.WarningS("net",
|
||||
@@ -801,6 +804,15 @@ namespace Robust.Shared.Network
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!channel.IsHandshakeComplete && !entry.IsHandshake)
|
||||
{
|
||||
Logger.WarningS("net",
|
||||
$"{msg.SenderConnection.RemoteEndPoint}: Got non-handshake message {entry.Type.Name} before handshake completion.");
|
||||
|
||||
channel.Disconnect("Got unacceptable net message before handshake completion");
|
||||
return true;
|
||||
}
|
||||
|
||||
var type = entry.Type;
|
||||
|
||||
var instance = entry.CreateFunction(channel);
|
||||
@@ -837,6 +849,7 @@ namespace Robust.Shared.Network
|
||||
// Callback must be available or else construction delegate will not be registered.
|
||||
var callback = _callbacks[type];
|
||||
|
||||
// Logger.DebugS("net", $"RECV: {instance.GetType().Name}");
|
||||
try
|
||||
{
|
||||
callback?.Invoke(instance);
|
||||
@@ -857,11 +870,13 @@ namespace Robust.Shared.Network
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_messages.TryGetValue(name, out var packetType))
|
||||
if (!_messages.TryGetValue(name, out var msgDat))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var (packetType, isHandshake) = msgDat;
|
||||
|
||||
if (!_callbacks.ContainsKey(packetType))
|
||||
{
|
||||
return;
|
||||
@@ -897,6 +912,7 @@ namespace Robust.Shared.Network
|
||||
ref var entry = ref _netMsgFunctions[id];
|
||||
entry.CreateFunction = @delegate;
|
||||
entry.Type = packetType;
|
||||
entry.IsHandshake = isHandshake;
|
||||
}
|
||||
|
||||
#region NetMessages
|
||||
@@ -909,7 +925,7 @@ namespace Robust.Shared.Network
|
||||
var name = new T().MsgName;
|
||||
var id = _strings.AddString(name);
|
||||
|
||||
_messages.Add(name, typeof(T));
|
||||
_messages.Add(name, (typeof(T), (accept & NetMessageAccept.Handshake) != 0));
|
||||
|
||||
var thisSide = IsServer ? NetMessageAccept.Server : NetMessageAccept.Client;
|
||||
|
||||
@@ -986,6 +1002,9 @@ namespace Robust.Shared.Network
|
||||
|
||||
foreach (var channel in _channels.Values)
|
||||
{
|
||||
if (!channel.IsHandshakeComplete)
|
||||
continue;
|
||||
|
||||
ServerSendMessage(message, channel);
|
||||
}
|
||||
}
|
||||
@@ -1006,6 +1025,12 @@ namespace Robust.Shared.Network
|
||||
|
||||
var method = message.DeliveryMethod;
|
||||
peer.SendMessage(packet, channel.Connection, method);
|
||||
LogSend(message, method, packet);
|
||||
}
|
||||
|
||||
private static void LogSend(NetMessage message, NetDeliveryMethod method, NetOutgoingMessage packet)
|
||||
{
|
||||
// Logger.DebugS("net", $"SEND: {message.GetType().Name} {method} {packet.LengthBytes}");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1042,6 +1067,7 @@ namespace Robust.Shared.Network
|
||||
}
|
||||
|
||||
peer.Peer.SendMessage(packet, peer.ConnectionsWithChannels[0], method);
|
||||
LogSend(message, method, packet);
|
||||
}
|
||||
|
||||
#endregion NetMessages
|
||||
@@ -1068,8 +1094,10 @@ namespace Robust.Shared.Network
|
||||
ConnectFailed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private void OnConnected(INetChannel channel)
|
||||
private void OnConnected(NetChannel channel)
|
||||
{
|
||||
channel.IsHandshakeComplete = true;
|
||||
|
||||
Connected?.Invoke(this, new NetChannelArgs(channel));
|
||||
}
|
||||
|
||||
@@ -1151,6 +1179,7 @@ namespace Robust.Shared.Network
|
||||
private struct NetMsgEntry
|
||||
{
|
||||
public Func<INetChannel, NetMessage>? CreateFunction;
|
||||
public bool IsHandshake;
|
||||
public Type Type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,26 @@ namespace Robust.Shared.Network
|
||||
/// <summary>
|
||||
/// Message can only be received on the server and it is an error to send it to a client.
|
||||
/// </summary>
|
||||
Server = 1,
|
||||
Server = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Message can only be received on the client and it is an error to send it to a server.
|
||||
/// </summary>
|
||||
Client = 2,
|
||||
Client = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Message can be received on both client and server.
|
||||
/// </summary>
|
||||
Both = Client | Server
|
||||
Both = Client | Server,
|
||||
|
||||
/// <summary>
|
||||
/// Message is used during connection handshake and may be sent before the initial handshake is completed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There is a window of time between the initial authentication handshake and serialization handshake,
|
||||
/// where the connection *does* have an INetChannel.
|
||||
/// During this handshake messages are still blocked however unless this flag is sent on the message type.
|
||||
/// </remarks>
|
||||
Handshake = 1 << 2,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,5 +106,15 @@ namespace Robust.Shared.Network
|
||||
message.Position += length * 8;
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static TimeSpan ReadTimeSpan(this NetIncomingMessage message)
|
||||
{
|
||||
return TimeSpan.FromTicks(message.ReadInt64());
|
||||
}
|
||||
|
||||
public static void Write(this NetOutgoingMessage message, TimeSpan timeSpan)
|
||||
{
|
||||
message.Write(timeSpan.Ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
_callback = callback;
|
||||
_updateCallback = updateCallback;
|
||||
_network.RegisterNetMessage<MsgStringTableEntries>(ReceiveEntries, NetMessageAccept.Client);
|
||||
_network.RegisterNetMessage<MsgStringTableEntries>(ReceiveEntries, NetMessageAccept.Client | NetMessageAccept.Handshake);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
@@ -369,17 +369,6 @@ namespace Robust.Shared.Physics
|
||||
return _nodes[proxy].UserData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the fat AABB for a proxy.
|
||||
/// </summary>
|
||||
/// <param name="proxyId">The proxy id.</param>
|
||||
/// <param name="fatAABB">The fat AABB.</param>
|
||||
public void GetFatAABB(Proxy proxy, out Box2 fatAABB)
|
||||
{
|
||||
DebugTools.Assert(0 <= proxy && proxy < Capacity);
|
||||
fatAABB = _nodes[proxy].Aabb;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool WasMoved(Proxy proxy)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
@@ -8,20 +7,10 @@ namespace Robust.Shared.Physics.Broadphase
|
||||
{
|
||||
public class DynamicTreeBroadPhase : IBroadPhase
|
||||
{
|
||||
public MapId MapId { get; set; }
|
||||
|
||||
public GridId GridId { get; set; }
|
||||
|
||||
// TODO: DynamicTree seems slow at updates when we have large entity counts so when we have boxstation
|
||||
// need to suss out whether chunking it might be useful.
|
||||
private B2DynamicTree<FixtureProxy> _tree = new(capacity: 256);
|
||||
private B2DynamicTree<FixtureProxy> _tree = default!;
|
||||
|
||||
private readonly DynamicTree<FixtureProxy>.ExtractAabbDelegate _extractAabb = ExtractAabbFunc;
|
||||
|
||||
private DynamicTree.Proxy[] _moveBuffer;
|
||||
private int _moveCapacity;
|
||||
private int _moveCount;
|
||||
|
||||
private (DynamicTree.Proxy ProxyA, DynamicTree.Proxy ProxyB)[] _pairBuffer;
|
||||
private int _pairCapacity;
|
||||
private int _pairCount;
|
||||
@@ -29,84 +18,24 @@ namespace Robust.Shared.Physics.Broadphase
|
||||
private B2DynamicTree<FixtureProxy>.QueryCallback _queryCallback;
|
||||
private DynamicTree.Proxy _queryProxyId;
|
||||
|
||||
public DynamicTreeBroadPhase(MapId mapId, GridId gridId)
|
||||
public DynamicTreeBroadPhase(int capacity)
|
||||
{
|
||||
MapId = mapId;
|
||||
GridId = gridId;
|
||||
|
||||
_tree = new B2DynamicTree<FixtureProxy>(capacity: capacity);
|
||||
_queryCallback = QueryCallback;
|
||||
_proxyCount = 0;
|
||||
|
||||
_pairCapacity = 16;
|
||||
_pairCount = 0;
|
||||
_pairBuffer = new (DynamicTree.Proxy ProxyA, DynamicTree.Proxy ProxyB)[_pairCapacity];
|
||||
|
||||
_moveCapacity = 16;
|
||||
_moveCount = 0;
|
||||
_moveBuffer = new DynamicTree.Proxy[_moveCapacity];
|
||||
}
|
||||
|
||||
public DynamicTreeBroadPhase() : this(256) {}
|
||||
|
||||
private static Box2 ExtractAabbFunc(in FixtureProxy proxy)
|
||||
{
|
||||
return proxy.AABB;
|
||||
}
|
||||
|
||||
public void UpdatePairs(BroadPhaseDelegate callback)
|
||||
{
|
||||
// Reset pair buffer
|
||||
_pairCount = 0;
|
||||
|
||||
// Perform tree queries for all moving proxies.
|
||||
for (int j = 0; j < _moveCount; ++j)
|
||||
{
|
||||
_queryProxyId = _moveBuffer[j];
|
||||
if (_queryProxyId == DynamicTree.Proxy.Free)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have to query the tree with the fat AABB so that
|
||||
// we don't fail to create a pair that may touch later.
|
||||
Box2 fatAABB;
|
||||
_tree.GetFatAABB(_queryProxyId, out fatAABB);
|
||||
|
||||
// Query tree, create pairs and add them pair buffer.
|
||||
_tree.Query(_queryCallback, in fatAABB);
|
||||
}
|
||||
|
||||
// Reset move buffer
|
||||
_moveCount = 0;
|
||||
|
||||
// Sort the pair buffer to expose duplicates.
|
||||
Array.Sort(_pairBuffer, 0, _pairCount);
|
||||
|
||||
// Send the pairs back to the client.
|
||||
int i = 0;
|
||||
while (i < _pairCount)
|
||||
{
|
||||
var primaryPair = _pairBuffer[i];
|
||||
FixtureProxy userDataA = _tree.GetUserData(primaryPair.ProxyA)!;
|
||||
FixtureProxy userDataB = _tree.GetUserData(primaryPair.ProxyB)!;
|
||||
|
||||
callback(GridId, in userDataA, in userDataB);
|
||||
++i;
|
||||
|
||||
// Skip any duplicate pairs.
|
||||
while (i < _pairCount)
|
||||
{
|
||||
(DynamicTree.Proxy ProxyA, DynamicTree.Proxy ProxyB) pair = _pairBuffer[i];
|
||||
if (pair.ProxyA != primaryPair.ProxyA || pair.ProxyB != primaryPair.ProxyB)
|
||||
{
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to keep the tree balanced.
|
||||
//_tree.Rebalance(4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called from DynamicTree.Query when we are gathering pairs.
|
||||
/// </summary>
|
||||
@@ -136,82 +65,29 @@ namespace Robust.Shared.Physics.Broadphase
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Refactor to use fatAABB
|
||||
/// <summary>
|
||||
/// Already assumed to be within the same broadphase.
|
||||
/// </summary>
|
||||
/// <param name="proxyIdA"></param>
|
||||
/// <param name="proxyIdB"></param>
|
||||
/// <returns></returns>
|
||||
public bool TestOverlap(DynamicTree.Proxy proxyIdA, DynamicTree.Proxy proxyIdB)
|
||||
public Box2 GetFatAabb(DynamicTree.Proxy proxy)
|
||||
{
|
||||
var proxyA = _tree.GetUserData(proxyIdA);
|
||||
var proxyB = _tree.GetUserData(proxyIdB);
|
||||
|
||||
if (proxyA == null || proxyB == null) return false;
|
||||
|
||||
return proxyB.AABB.Intersects(proxyA.AABB);
|
||||
return _tree.GetFatAabb(proxy);
|
||||
}
|
||||
|
||||
public DynamicTree.Proxy AddProxy(ref FixtureProxy proxy)
|
||||
{
|
||||
var proxyID = _tree.CreateProxy(proxy.AABB, proxy);
|
||||
_proxyCount++;
|
||||
BufferMove(proxyID);
|
||||
return proxyID;
|
||||
}
|
||||
|
||||
public void MoveProxy(DynamicTree.Proxy proxy, in Box2 aabb, Vector2 displacement)
|
||||
{
|
||||
var buffer = _tree.MoveProxy(proxy, in aabb, displacement);
|
||||
if (buffer)
|
||||
{
|
||||
BufferMove(proxy);
|
||||
}
|
||||
}
|
||||
|
||||
public void TouchProxy(DynamicTree.Proxy proxy)
|
||||
{
|
||||
BufferMove(proxy);
|
||||
}
|
||||
|
||||
private void BufferMove(DynamicTree.Proxy proxyId)
|
||||
{
|
||||
if (_moveCount == _moveCapacity)
|
||||
{
|
||||
DynamicTree.Proxy[] oldBuffer = _moveBuffer;
|
||||
_moveCapacity *= 2;
|
||||
_moveBuffer = new DynamicTree.Proxy[_moveCapacity];
|
||||
Array.Copy(oldBuffer, _moveBuffer, _moveCount);
|
||||
}
|
||||
|
||||
_moveBuffer[_moveCount] = proxyId;
|
||||
_moveCount++;
|
||||
}
|
||||
|
||||
private void UnBufferMove(int proxyId)
|
||||
{
|
||||
for (int i = 0; i < _moveCount; ++i)
|
||||
{
|
||||
if (_moveBuffer[i] == proxyId)
|
||||
{
|
||||
_moveBuffer[i] = DynamicTree.Proxy.Free;
|
||||
}
|
||||
}
|
||||
_tree.MoveProxy(proxy, in aabb, displacement);
|
||||
}
|
||||
|
||||
public void RemoveProxy(DynamicTree.Proxy proxy)
|
||||
{
|
||||
UnBufferMove(proxy);
|
||||
_proxyCount--;
|
||||
_tree.DestroyProxy(proxy);
|
||||
}
|
||||
|
||||
public void QueryAABB(DynamicTree<FixtureProxy>.QueryCallbackDelegate callback, Box2 aabb, bool approx = false)
|
||||
{
|
||||
QueryAabb(ref callback, EasyQueryCallback, aabb, approx);
|
||||
}
|
||||
|
||||
public FixtureProxy? GetProxy(DynamicTree.Proxy proxy)
|
||||
{
|
||||
return _tree.GetUserData(proxy);
|
||||
@@ -232,14 +108,18 @@ namespace Robust.Shared.Physics.Broadphase
|
||||
public IEnumerable<FixtureProxy> QueryAabb(Box2 aabb, bool approx = false)
|
||||
{
|
||||
var list = new List<FixtureProxy>();
|
||||
return QueryAabb(list, aabb, approx);
|
||||
}
|
||||
|
||||
QueryAabb(ref list, (ref List<FixtureProxy> lst, in FixtureProxy i) =>
|
||||
public IEnumerable<FixtureProxy> QueryAabb(List<FixtureProxy> proxies, Box2 aabb, bool approx = false)
|
||||
{
|
||||
QueryAabb(ref proxies, (ref List<FixtureProxy> lst, in FixtureProxy i) =>
|
||||
{
|
||||
lst.Add(i);
|
||||
return true;
|
||||
}, aabb, approx);
|
||||
|
||||
return list;
|
||||
return proxies;
|
||||
}
|
||||
|
||||
public void QueryPoint(DynamicTree<FixtureProxy>.QueryCallbackDelegate callback, Vector2 point, bool approx = false)
|
||||
|
||||
@@ -1,913 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Broadphase
|
||||
{
|
||||
public abstract class SharedBroadPhaseSystem : EntitySystem
|
||||
{
|
||||
/*
|
||||
* That's right both the system implements IBroadPhase and also each grid has its own as well.
|
||||
* The reason for this is other stuff should just be able to check for broadphase with no regard
|
||||
* for the concept of grids, whereas internally this needs to worry about it.
|
||||
*/
|
||||
|
||||
// Anything in a container is removed from the graph and anything removed from a container is added to the graph.
|
||||
|
||||
/*
|
||||
* So the Box2D derivatives just use a generic "SynchronizeFixtures" method but it's kinda obtuse so
|
||||
* I just made other methods (AddFixture, RefreshFixtures, etc.) that are clearer on what they're doing.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private readonly Dictionary<MapId, Dictionary<GridId, IBroadPhase>> _graph = new();
|
||||
|
||||
private Dictionary<IPhysBody, List<IBroadPhase>> _lastBroadPhases = new();
|
||||
|
||||
/// <summary>
|
||||
/// Given MoveEvent and RotateEvent do the same thing we won't double up on work.
|
||||
/// </summary>
|
||||
private HashSet<IEntity> _handledThisTick = new();
|
||||
|
||||
private Queue<MoveEvent> _queuedMoveEvents = new();
|
||||
private Queue<RotateEvent> _queuedRotateEvent = new();
|
||||
private Queue<EntMapIdChangedMessage> _queuedMapChanges = new();
|
||||
private Queue<FixtureUpdateMessage> _queuedFixtureUpdates = new();
|
||||
private Queue<PhysicsUpdateMessage> _queuedCollisionChanges = new();
|
||||
private Queue<EntInsertedIntoContainerMessage> _queuedContainerInsert = new();
|
||||
private Queue<EntRemovedFromContainerMessage> _queuedContainerRemove = new();
|
||||
|
||||
private IEnumerable<IBroadPhase> BroadPhases()
|
||||
{
|
||||
foreach (var (_, grids) in _graph)
|
||||
{
|
||||
foreach (var (_, broad) in grids)
|
||||
{
|
||||
yield return broad;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding broadphase for this grid.
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="gridId"></param>
|
||||
/// <returns>null if broadphase already destroyed or none exists</returns>
|
||||
public IBroadPhase? GetBroadPhase(MapId mapId, GridId gridId)
|
||||
{
|
||||
// Might be null if the grid is being instantiated.
|
||||
if (mapId == MapId.Nullspace || !_graph[mapId].TryGetValue(gridId, out var grid))
|
||||
return null;
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
public ICollection<IBroadPhase> GetBroadPhases(MapId mapId)
|
||||
{
|
||||
return _graph[mapId].Values;
|
||||
}
|
||||
|
||||
public IEnumerable<IBroadPhase> GetBroadPhases(PhysicsComponent body)
|
||||
{
|
||||
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases)) return Array.Empty<IBroadPhase>();
|
||||
return broadPhases;
|
||||
}
|
||||
|
||||
// Look for now I've hardcoded grids
|
||||
public IEnumerable<(IBroadPhase Broadphase, GridId GridId)> GetBroadphases(PhysicsComponent body)
|
||||
{
|
||||
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases)) yield break;
|
||||
|
||||
foreach (var (_, grids) in _graph)
|
||||
{
|
||||
foreach (var broad in broadPhases)
|
||||
{
|
||||
foreach (var (gridId, broadPhase) in grids)
|
||||
{
|
||||
if (broad == broadPhase)
|
||||
{
|
||||
yield return (broadPhase, gridId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TestOverlap(FixtureProxy proxyA, FixtureProxy proxyB)
|
||||
{
|
||||
// TODO: This should only ever be called on the same grid I think so maybe just assert
|
||||
var mapA = proxyA.Fixture.Body.Owner.Transform.MapID;
|
||||
var mapB = proxyB.Fixture.Body.Owner.Transform.MapID;
|
||||
|
||||
if (mapA != mapB)
|
||||
return false;
|
||||
|
||||
return proxyA.AABB.Intersects(proxyB.AABB);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the percentage that 2 bodies overlap. Ignores whether collision is turned on for either body.
|
||||
/// </summary>
|
||||
/// <param name="bodyA"></param>
|
||||
/// <param name="bodyB"></param>
|
||||
/// <returns> 0 -> 1.0f based on WorldAABB overlap</returns>
|
||||
public float IntersectionPercent(PhysicsComponent bodyA, PhysicsComponent bodyB)
|
||||
{
|
||||
// TODO: Use actual shapes and not just the AABB?
|
||||
return bodyA.GetWorldAABB().IntersectPercentage(bodyB.GetWorldAABB());
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PhysicsUpdateMessage>(QueueCollisionChange);
|
||||
SubscribeLocalEvent<MoveEvent>(QueuePhysicsMove);
|
||||
SubscribeLocalEvent<RotateEvent>(QueuePhysicsRotate);
|
||||
SubscribeLocalEvent<EntMapIdChangedMessage>(QueueMapChange);
|
||||
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(QueueContainerInsertMessage);
|
||||
SubscribeLocalEvent<EntRemovedFromContainerMessage>(QueueContainerRemoveMessage);
|
||||
SubscribeLocalEvent<FixtureUpdateMessage>(QueueFixtureUpdate);
|
||||
_mapManager.OnGridCreated += HandleGridCreated;
|
||||
_mapManager.OnGridRemoved += HandleGridRemoval;
|
||||
_mapManager.MapCreated += HandleMapCreated;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
while (_queuedMoveEvents.Count > 0)
|
||||
{
|
||||
var moveEvent = _queuedMoveEvents.Dequeue();
|
||||
|
||||
// Doing this seems to fuck with tp so leave off for now I guess, it's mainly to avoid the rotate duplication
|
||||
if (_handledThisTick.Contains(moveEvent.Sender)) continue;
|
||||
|
||||
_handledThisTick.Add(moveEvent.Sender);
|
||||
|
||||
if (moveEvent.Sender.Deleted || !moveEvent.Sender.TryGetComponent(out PhysicsComponent? physicsComponent)) continue;
|
||||
|
||||
SynchronizeFixtures(physicsComponent, moveEvent.NewPosition.ToMapPos(EntityManager) - moveEvent.OldPosition.ToMapPos(EntityManager), moveEvent.WorldAABB);
|
||||
}
|
||||
|
||||
while (_queuedRotateEvent.Count > 0)
|
||||
{
|
||||
var rotateEvent = _queuedRotateEvent.Dequeue();
|
||||
|
||||
if (_handledThisTick.Contains(rotateEvent.Sender)) continue;
|
||||
|
||||
_handledThisTick.Add(rotateEvent.Sender);
|
||||
|
||||
if (rotateEvent.Sender.Deleted || !rotateEvent.Sender.TryGetComponent(out PhysicsComponent? physicsComponent))
|
||||
return;
|
||||
|
||||
SynchronizeFixtures(physicsComponent, Vector2.Zero, rotateEvent.WorldAABB);
|
||||
}
|
||||
|
||||
_handledThisTick.Clear();
|
||||
|
||||
// TODO: Just call ProcessEventQueue directly?
|
||||
// Manually manage queued stuff ourself given EventBus.QueueEvent happens at the same time every time
|
||||
while (_queuedMapChanges.Count > 0)
|
||||
{
|
||||
HandleMapChange(_queuedMapChanges.Dequeue());
|
||||
}
|
||||
|
||||
while (_queuedContainerInsert.Count > 0)
|
||||
{
|
||||
HandleContainerInsert(_queuedContainerInsert.Dequeue());
|
||||
}
|
||||
|
||||
while (_queuedContainerRemove.Count > 0)
|
||||
{
|
||||
HandleContainerRemove(_queuedContainerRemove.Dequeue());
|
||||
}
|
||||
|
||||
while (_queuedCollisionChanges.Count > 0)
|
||||
{
|
||||
var message = _queuedCollisionChanges.Dequeue();
|
||||
if (message.Component.CanCollide && !message.Component.Deleted)
|
||||
{
|
||||
AddBody(message.Component);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveBody(message.Component);
|
||||
}
|
||||
}
|
||||
|
||||
while (_queuedFixtureUpdates.Count > 0)
|
||||
{
|
||||
var message = _queuedFixtureUpdates.Dequeue();
|
||||
RefreshFixture(message.Body, message.Fixture);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_mapManager.OnGridCreated -= HandleGridCreated;
|
||||
_mapManager.OnGridRemoved -= HandleGridRemoval;
|
||||
_mapManager.MapCreated -= HandleMapCreated;
|
||||
}
|
||||
|
||||
private void QueuePhysicsMove(MoveEvent moveEvent)
|
||||
{
|
||||
_queuedMoveEvents.Enqueue(moveEvent);
|
||||
}
|
||||
|
||||
private void QueuePhysicsRotate(RotateEvent rotateEvent)
|
||||
{
|
||||
if (!rotateEvent.Sender.TryGetComponent(out PhysicsComponent? physicsComponent))
|
||||
return;
|
||||
|
||||
SynchronizeFixtures(physicsComponent, Vector2.Zero);
|
||||
}
|
||||
|
||||
private void QueueCollisionChange(PhysicsUpdateMessage message)
|
||||
{
|
||||
_queuedCollisionChanges.Enqueue(message);
|
||||
}
|
||||
|
||||
private void QueueMapChange(EntMapIdChangedMessage message)
|
||||
{
|
||||
_queuedMapChanges.Enqueue(message);
|
||||
}
|
||||
|
||||
private void QueueContainerInsertMessage(EntInsertedIntoContainerMessage message)
|
||||
{
|
||||
_queuedContainerInsert.Enqueue(message);
|
||||
}
|
||||
|
||||
private void QueueContainerRemoveMessage(EntRemovedFromContainerMessage message)
|
||||
{
|
||||
_queuedContainerRemove.Enqueue(message);
|
||||
}
|
||||
|
||||
private void HandleContainerInsert(EntInsertedIntoContainerMessage message)
|
||||
{
|
||||
if (!message.Entity.Deleted && message.Entity.TryGetComponent(out IPhysBody? physicsComponent))
|
||||
{
|
||||
physicsComponent.CanCollide = false;
|
||||
physicsComponent.Awake = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleContainerRemove(EntRemovedFromContainerMessage message)
|
||||
{
|
||||
if (!message.Entity.Deleted && message.Entity.TryGetComponent(out IPhysBody? physicsComponent))
|
||||
{
|
||||
physicsComponent.CanCollide = true;
|
||||
physicsComponent.Awake = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueFixtureUpdate(FixtureUpdateMessage message)
|
||||
{
|
||||
_queuedFixtureUpdates.Enqueue(message);
|
||||
}
|
||||
|
||||
public void AddBroadPhase(PhysicsComponent body, IBroadPhase broadPhase)
|
||||
{
|
||||
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (broadPhases.Contains(broadPhase)) return;
|
||||
broadPhases.Add(broadPhase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles map changes for bodies completely
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
private void HandleMapChange(EntMapIdChangedMessage message)
|
||||
{
|
||||
if (message.Entity.Deleted ||
|
||||
!message.Entity.TryGetComponent(out PhysicsComponent? body) ||
|
||||
!_lastBroadPhases.TryGetValue(body, out var broadPhases))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Get<SharedPhysicsSystem>().Maps.TryGetValue(message.OldMapId, out var oldMap))
|
||||
{
|
||||
oldMap.RemoveBody(body);
|
||||
}
|
||||
|
||||
body.ClearProxies();
|
||||
|
||||
if (Get<SharedPhysicsSystem>().Maps.TryGetValue(message.Entity.Transform.MapID, out var newMap))
|
||||
{
|
||||
newMap.AddBody(body);
|
||||
body.CreateProxies();
|
||||
SetBroadPhases(body);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetBroadPhases(IPhysBody body)
|
||||
{
|
||||
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases))
|
||||
{
|
||||
broadPhases = new List<IBroadPhase>();
|
||||
_lastBroadPhases[body] = broadPhases;
|
||||
}
|
||||
|
||||
broadPhases.Clear();
|
||||
|
||||
foreach (var fixture in body.Fixtures)
|
||||
{
|
||||
foreach (var (gridId, _) in fixture.Proxies)
|
||||
{
|
||||
var broadPhase = GetBroadPhase(body.Owner.Transform.MapID, gridId);
|
||||
if (broadPhase == null) continue;
|
||||
broadPhases.Add(broadPhase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleGridCreated(MapId mapId, GridId gridId)
|
||||
{
|
||||
if (!_graph.TryGetValue(mapId, out var grids))
|
||||
{
|
||||
grids = new Dictionary<GridId, IBroadPhase>();
|
||||
_graph[mapId] = grids;
|
||||
}
|
||||
|
||||
grids[gridId] = new DynamicTreeBroadPhase(mapId, gridId);
|
||||
}
|
||||
|
||||
private void HandleMapCreated(object? sender, MapEventArgs eventArgs)
|
||||
{
|
||||
_graph[eventArgs.Map] = new Dictionary<GridId, IBroadPhase>()
|
||||
{
|
||||
{
|
||||
GridId.Invalid,
|
||||
new DynamicTreeBroadPhase(eventArgs.Map, GridId.Invalid)
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private void HandleGridRemoval(MapId mapId, GridId gridId)
|
||||
{
|
||||
foreach (var (_, grids) in _graph)
|
||||
{
|
||||
if (!grids.TryGetValue(gridId, out var broadPhase)) continue;
|
||||
|
||||
var toCleanup = new List<IPhysBody>();
|
||||
// Need to cleanup every body that was touching this grid.
|
||||
foreach (var (body, broadPhases) in _lastBroadPhases)
|
||||
{
|
||||
if (broadPhases.Contains(broadPhase))
|
||||
{
|
||||
toCleanup.Add(body);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var body in toCleanup)
|
||||
{
|
||||
RemoveBody(body);
|
||||
}
|
||||
|
||||
grids.Remove(gridId);
|
||||
|
||||
foreach (var body in toCleanup)
|
||||
{
|
||||
AddBody(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBody(IPhysBody body)
|
||||
{
|
||||
if (_lastBroadPhases.ContainsKey(body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = body.Owner.Transform.MapID;
|
||||
|
||||
if (mapId == MapId.Nullspace)
|
||||
{
|
||||
_lastBroadPhases[body] = new List<IBroadPhase>();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var fixture in body.Fixtures)
|
||||
{
|
||||
DebugTools.Assert(fixture.Proxies.Count == 0, "Can't add a body to broadphase when it already has proxies!");
|
||||
}
|
||||
|
||||
var broadPhases = new List<IBroadPhase>();
|
||||
_lastBroadPhases[body] = broadPhases;
|
||||
|
||||
body.CreateProxies(_mapManager, this);
|
||||
SetBroadPhases(body);
|
||||
}
|
||||
|
||||
public void RemoveBody(IPhysBody body)
|
||||
{
|
||||
if (!_lastBroadPhases.ContainsKey(body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
body.ClearProxies();
|
||||
body.DestroyContacts();
|
||||
_lastBroadPhases.Remove(body);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreates this fixture in the relevant broadphases.
|
||||
/// </summary>
|
||||
/// <param name="body"></param>
|
||||
/// <param name="fixture"></param>
|
||||
public void RefreshFixture(PhysicsComponent body, Fixture fixture)
|
||||
{
|
||||
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = body.Owner.Transform.MapID;
|
||||
|
||||
if (mapId == MapId.Nullspace || body.Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fixture.ClearProxies(mapId, this);
|
||||
fixture.CreateProxies(_mapManager, this);
|
||||
|
||||
// Need to update what broadphases are relevant.
|
||||
SetBroadPhases(body);
|
||||
}
|
||||
|
||||
internal void AddFixture(PhysicsComponent body, Fixture fixture)
|
||||
{
|
||||
// If the entity's still being initialized it might have MoveEvent called (might change in future?)
|
||||
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = body.Owner.Transform.MapID;
|
||||
DebugTools.Assert(fixture.Proxies.Count == 0);
|
||||
|
||||
if (mapId == MapId.Nullspace || body.Deleted)
|
||||
{
|
||||
body.ClearProxies();
|
||||
return;
|
||||
}
|
||||
|
||||
broadPhases.Clear();
|
||||
fixture.CreateProxies(_mapManager, this);
|
||||
|
||||
foreach (var fix in body.Fixtures)
|
||||
{
|
||||
foreach (var (gridId, _) in fix.Proxies)
|
||||
{
|
||||
var broadPhase = GetBroadPhase(mapId, gridId);
|
||||
if (broadPhase == null) continue;
|
||||
broadPhases.Add(broadPhase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveFixture(PhysicsComponent body, Fixture fixture)
|
||||
{
|
||||
// If the entity's still being initialized it might have MoveEvent called (might change in future?)
|
||||
if (!_lastBroadPhases.TryGetValue(body, out var broadPhases))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = body.Owner.Transform.MapID;
|
||||
fixture.ClearProxies(mapId, this);
|
||||
|
||||
if (mapId == MapId.Nullspace || body.Deleted)
|
||||
{
|
||||
body.ClearProxies();
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to re-build the broadphases.
|
||||
broadPhases.Clear();
|
||||
|
||||
foreach (var fix in body.Fixtures)
|
||||
{
|
||||
foreach (var (gridId, _) in fix.Proxies)
|
||||
{
|
||||
var broadPhase = GetBroadPhase(mapId, gridId);
|
||||
if (broadPhase == null) continue;
|
||||
broadPhases.Add(broadPhase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move all of the fixtures on this body.
|
||||
/// </summary>
|
||||
/// <param name="body"></param>
|
||||
/// <param name="displacement"></param>
|
||||
private void SynchronizeFixtures(PhysicsComponent body, Vector2 displacement, Box2? worldAABB = null)
|
||||
{
|
||||
// If the entity's still being initialized it might have MoveEvent called (might change in future?)
|
||||
if (!_lastBroadPhases.TryGetValue(body, out var oldBroadPhases))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var worldPosition = body.Owner.Transform.WorldPosition;
|
||||
var worldRotation = body.Owner.Transform.WorldRotation;
|
||||
|
||||
var mapId = body.Owner.Transform.MapID;
|
||||
worldAABB ??= body.GetWorldAABB(worldPosition, worldRotation);
|
||||
|
||||
// 99% of the time this is going to be 1, maybe 2, so HashSet probably slower?
|
||||
|
||||
var newBroadPhases = _mapManager
|
||||
.FindGridIdsIntersecting(mapId, worldAABB.Value, true)
|
||||
.Select(gridId => GetBroadPhase(mapId, gridId))
|
||||
.ToList();
|
||||
|
||||
// Remove from old broadphases
|
||||
for (var i = oldBroadPhases.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var broadPhase = oldBroadPhases[i];
|
||||
|
||||
if (newBroadPhases.Contains(broadPhase)) continue;
|
||||
|
||||
var gridId = GetGridId(broadPhase);
|
||||
|
||||
foreach (var fixture in body.Fixtures)
|
||||
{
|
||||
fixture.ClearProxies(mapId, this, gridId);
|
||||
}
|
||||
|
||||
oldBroadPhases.RemoveAt(i);
|
||||
}
|
||||
|
||||
// Update retained broadphases
|
||||
// TODO: These will need swept broadPhases
|
||||
|
||||
|
||||
foreach (var broadPhase in oldBroadPhases)
|
||||
{
|
||||
if (!newBroadPhases.Contains(broadPhase)) continue;
|
||||
|
||||
var gridId = GetGridId(broadPhase);
|
||||
|
||||
foreach (var fixture in body.Fixtures)
|
||||
{
|
||||
if (!fixture.Proxies.TryGetValue(gridId, out var proxies)) continue;
|
||||
|
||||
foreach (var proxy in proxies)
|
||||
{
|
||||
var gridRotation = worldRotation;
|
||||
var gridPos = worldPosition;
|
||||
|
||||
if (gridId != GridId.Invalid)
|
||||
{
|
||||
var grid = _mapManager.GetGrid(gridId);
|
||||
gridPos -= grid.WorldPosition;
|
||||
// TODO: Should probably have a helper for this
|
||||
gridRotation -= body.Owner.EntityManager.GetEntity(grid.GridEntityId).Transform.WorldRotation;
|
||||
}
|
||||
|
||||
var aabb = fixture.Shape.CalculateLocalBounds(gridRotation).Translated(gridPos);
|
||||
proxy.AABB = aabb;
|
||||
|
||||
broadPhase.MoveProxy(proxy.ProxyId, in aabb, displacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to new broadphases
|
||||
foreach (var broadPhase in newBroadPhases)
|
||||
{
|
||||
if (broadPhase == null || oldBroadPhases.Contains(broadPhase)) continue;
|
||||
var gridId = GetGridId(broadPhase);
|
||||
oldBroadPhases.Add(broadPhase);
|
||||
|
||||
foreach (var fixture in body.Fixtures)
|
||||
{
|
||||
DebugTools.Assert(!fixture.Proxies.ContainsKey(gridId));
|
||||
|
||||
fixture.CreateProxies(broadPhase, _mapManager, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GridId GetGridId(IBroadPhase broadPhase)
|
||||
{
|
||||
foreach (var (_, grids) in _graph)
|
||||
{
|
||||
foreach (var (gridId, broad) in grids)
|
||||
{
|
||||
if (broadPhase == broad)
|
||||
{
|
||||
return gridId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Unable to find GridId for broadPhase");
|
||||
}
|
||||
|
||||
// This is dirty but so is a lot of other shit so it'll get refactored at some stage tm
|
||||
public List<PhysicsComponent> GetAwakeBodies(MapId mapId, GridId gridId)
|
||||
{
|
||||
var bodies = new List<PhysicsComponent>();
|
||||
var map = Get<SharedPhysicsSystem>().Maps[mapId];
|
||||
|
||||
foreach (var body in map.AwakeBodies)
|
||||
{
|
||||
if (body.Owner.Transform.GridID == gridId)
|
||||
bodies.Add(body);
|
||||
}
|
||||
|
||||
return bodies;
|
||||
}
|
||||
|
||||
#region Queries
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the specified collision rectangle collides with any of the physBodies under management.
|
||||
/// Also fires the OnCollide event of the first managed physBody to intersect with the collider.
|
||||
/// </summary>
|
||||
/// <param name="collider">Collision rectangle to check</param>
|
||||
/// <param name="mapId">Map to check on</param>
|
||||
/// <param name="approximate"></param>
|
||||
/// <returns>true if collides, false if not</returns>
|
||||
public bool TryCollideRect(Box2 collider, MapId mapId, bool approximate = true)
|
||||
{
|
||||
var state = (collider, mapId, found: false);
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(mapId, collider, true))
|
||||
{
|
||||
var gridCollider = _mapManager.GetGrid(gridId).WorldToLocal(collider.Center);
|
||||
var gridBox = collider.Translated(gridCollider);
|
||||
|
||||
_graph[mapId][gridId].QueryAabb(ref state, (ref (Box2 collider, MapId map, bool found) state, in FixtureProxy proxy) =>
|
||||
{
|
||||
if (proxy.Fixture.CollisionLayer == 0x0)
|
||||
return true;
|
||||
|
||||
if (proxy.AABB.Intersects(gridBox))
|
||||
{
|
||||
state.found = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, gridBox, approximate);
|
||||
}
|
||||
|
||||
return state.found;
|
||||
}
|
||||
|
||||
public IEnumerable<PhysicsComponent> GetCollidingEntities(PhysicsComponent body, Vector2 offset, bool approximate = true)
|
||||
{
|
||||
// TODO: In an ideal world we'd just iterate over the body's contacts (need to make more stuff immediate
|
||||
// for physics for this to be viable so future-work).
|
||||
|
||||
// If the body has just had its collision enabled or disabled it may not be ready yet so we'll wait a tick.
|
||||
if (!body.CanCollide || body.Owner.Transform.MapID == MapId.Nullspace)
|
||||
{
|
||||
return Array.Empty<PhysicsComponent>();
|
||||
}
|
||||
|
||||
// Unfortunately due to the way grids are currently created we have to queue CanCollide event changes, hence we need to do this here.
|
||||
if (!_lastBroadPhases.ContainsKey(body))
|
||||
{
|
||||
AddBody(body);
|
||||
}
|
||||
|
||||
var entities = new List<PhysicsComponent>();
|
||||
|
||||
var state = (body, entities);
|
||||
|
||||
foreach (var broadPhase in _lastBroadPhases[body])
|
||||
{
|
||||
foreach (var fixture in body.Fixtures)
|
||||
{
|
||||
foreach (var proxy in fixture.Proxies[GetGridId(broadPhase)])
|
||||
{
|
||||
broadPhase.QueryAabb(ref state,
|
||||
(ref (PhysicsComponent body, List<PhysicsComponent> entities) state,
|
||||
in FixtureProxy other) =>
|
||||
{
|
||||
if (other.Fixture.Body.Deleted || other.Fixture.Body == body) return true;
|
||||
if ((proxy.Fixture.CollisionMask & other.Fixture.CollisionLayer) == 0x0) return true;
|
||||
if (!body.ShouldCollide(other.Fixture.Body)) return true;
|
||||
|
||||
state.entities.Add(other.Fixture.Body);
|
||||
return true;
|
||||
}, proxy.AABB, approximate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all entities colliding with a certain body.
|
||||
/// </summary>
|
||||
/// <param name="body"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<PhysicsComponent> GetCollidingEntities(MapId mapId, in Box2 worldAABB)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Array.Empty<PhysicsComponent>();
|
||||
|
||||
var bodies = new HashSet<PhysicsComponent>();
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(mapId, worldAABB, true))
|
||||
{
|
||||
Vector2 offset;
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
offset = Vector2.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = -_mapManager.GetGrid(gridId).WorldPosition;
|
||||
}
|
||||
|
||||
var gridBox = worldAABB.Translated(offset);
|
||||
foreach (var proxy in _graph[mapId][gridId].QueryAabb(gridBox, false))
|
||||
{
|
||||
if (bodies.Contains(proxy.Fixture.Body)) continue;
|
||||
bodies.Add(proxy.Fixture.Body);
|
||||
}
|
||||
}
|
||||
|
||||
return bodies;
|
||||
}
|
||||
|
||||
// TODO: This will get every body but we don't need to do that
|
||||
/// <summary>
|
||||
/// Checks whether a body is colliding
|
||||
/// </summary>
|
||||
/// <param name="body"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsColliding(PhysicsComponent body, Vector2 offset, bool approximate)
|
||||
{
|
||||
return GetCollidingEntities(body, offset, approximate).Any();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region RayCast
|
||||
/// <summary>
|
||||
/// Casts a ray in the world, returning the first entity it hits (or all entities it hits, if so specified)
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="ray">Ray to cast in the world.</param>
|
||||
/// <param name="maxLength">Maximum length of the ray in meters.</param>
|
||||
/// <param name="predicate">A predicate to check whether to ignore an entity or not. If it returns true, it will be ignored.</param>
|
||||
/// <param name="returnOnFirstHit">If true, will only include the first hit entity in results. Otherwise, returns all of them.</param>
|
||||
/// <returns>A result object describing the hit, if any.</returns>
|
||||
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId mapId, CollisionRay ray,
|
||||
float maxLength = 50F,
|
||||
Func<IEntity, bool>? predicate = null, bool returnOnFirstHit = true)
|
||||
{
|
||||
List<RayCastResults> results = new();
|
||||
var endPoint = ray.Position + ray.Direction.Normalized * maxLength;
|
||||
var rayBox = new Box2(Vector2.ComponentMin(ray.Position, endPoint),
|
||||
Vector2.ComponentMax(ray.Position, endPoint));
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(mapId, rayBox, true))
|
||||
{
|
||||
Vector2 offset;
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
offset = Vector2.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = _mapManager.GetGrid(gridId).WorldPosition;
|
||||
}
|
||||
|
||||
var broadPhase = GetBroadPhase(mapId, gridId);
|
||||
var gridRay = new CollisionRay(ray.Position - offset, ray.Direction, ray.CollisionMask);
|
||||
// TODO: Probably need rotation when we get rotatable grids
|
||||
|
||||
broadPhase?.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) =>
|
||||
{
|
||||
if (returnOnFirstHit && results.Count > 0) return true;
|
||||
|
||||
if (distFromOrigin > maxLength)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (predicate?.Invoke(proxy.Fixture.Body.Owner) == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Shape raycast here
|
||||
|
||||
// Need to convert it back to world-space.
|
||||
var result = new RayCastResults(distFromOrigin, point + offset, proxy.Fixture.Body.Owner);
|
||||
results.Add(result);
|
||||
EntityManager.EventBus.QueueEvent(EventSource.Local,
|
||||
new DebugDrawRayMessage(
|
||||
new DebugRayData(ray, maxLength, result)));
|
||||
return true;
|
||||
}, gridRay);
|
||||
}
|
||||
|
||||
if (results.Count == 0)
|
||||
{
|
||||
EntityManager.EventBus.QueueEvent(EventSource.Local,
|
||||
new DebugDrawRayMessage(
|
||||
new DebugRayData(ray, maxLength, null)));
|
||||
}
|
||||
|
||||
results.Sort((a, b) => a.Distance.CompareTo(b.Distance));
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray in the world and returns the first entity it hits, or a list of all entities it hits.
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="ray">Ray to cast in the world.</param>
|
||||
/// <param name="maxLength">Maximum length of the ray in meters.</param>
|
||||
/// <param name="ignoredEnt">A single entity that can be ignored by the RayCast. Useful if the ray starts inside the body of an entity.</param>
|
||||
/// <param name="returnOnFirstHit">If false, will return a list of everything it hits, otherwise will just return a list of the first entity hit</param>
|
||||
/// <returns>An enumerable of either the first entity hit or everything hit</returns>
|
||||
public IEnumerable<RayCastResults> IntersectRay(MapId mapId, CollisionRay ray, float maxLength = 50, IEntity? ignoredEnt = null, bool returnOnFirstHit = true)
|
||||
=> IntersectRayWithPredicate(mapId, ray, maxLength, entity => entity == ignoredEnt, returnOnFirstHit);
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray in the world and returns the distance the ray traveled while colliding with entities
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="ray">Ray to cast in the world.</param>
|
||||
/// <param name="maxLength">Maximum length of the ray in meters.</param>
|
||||
/// <param name="ignoredEnt">A single entity that can be ignored by the RayCast. Useful if the ray starts inside the body of an entity.</param>
|
||||
/// <returns>The distance the ray traveled while colliding with entities</returns>
|
||||
public float IntersectRayPenetration(MapId mapId, CollisionRay ray, float maxLength, IEntity? ignoredEnt = null)
|
||||
{
|
||||
var penetration = 0f;
|
||||
// TODO: Just make an actual box
|
||||
var rayBox = new Box2(ray.Position - maxLength, ray.Position + maxLength);
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(mapId, rayBox, true))
|
||||
{
|
||||
var offset = gridId == GridId.Invalid ? Vector2.Zero : _mapManager.GetGrid(gridId).WorldPosition;
|
||||
|
||||
var broadPhase = GetBroadPhase(mapId, gridId);
|
||||
if (broadPhase == null) continue;
|
||||
|
||||
var gridRay = new CollisionRay(ray.Position - offset, ray.Direction, ray.CollisionMask);
|
||||
// TODO: Probably need rotation when we get rotatable grids
|
||||
|
||||
broadPhase.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) =>
|
||||
{
|
||||
if (distFromOrigin > maxLength || proxy.Fixture.Body.Owner == ignoredEnt) return true;
|
||||
|
||||
if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (new Ray(point + ray.Direction * proxy.AABB.Size.Length * 2, -ray.Direction).Intersects(
|
||||
proxy.AABB, out _, out var exitPoint))
|
||||
{
|
||||
penetration += (point - exitPoint).Length;
|
||||
}
|
||||
return true;
|
||||
}, gridRay);
|
||||
}
|
||||
|
||||
return penetration;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
12
Robust.Shared/Physics/BroadphaseComponent.cs
Normal file
12
Robust.Shared/Physics/BroadphaseComponent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class BroadphaseComponent : Component
|
||||
{
|
||||
public override string Name => "Broadphase";
|
||||
|
||||
internal IBroadPhase Tree = default!;
|
||||
}
|
||||
}
|
||||
@@ -64,18 +64,19 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
_normals = new List<Vector2>(_vertices.Count);
|
||||
|
||||
// Compute normals. Ensure the edges have non-zero length.
|
||||
for (int i = 0; i < _vertices.Count; ++i)
|
||||
for (var i = 0; i < _vertices.Count; ++i)
|
||||
{
|
||||
int next = i + 1 < _vertices.Count ? i + 1 : 0;
|
||||
Vector2 edge = _vertices[next] - _vertices[i];
|
||||
var next = i + 1 < _vertices.Count ? i + 1 : 0;
|
||||
var edge = _vertices[next] - _vertices[i];
|
||||
DebugTools.Assert(edge.LengthSquared > float.Epsilon * float.Epsilon);
|
||||
|
||||
//FPE optimization: Normals.Add(MathHelper.Cross(edge, 1.0f));
|
||||
Vector2 temp = new Vector2(edge.Y, -edge.X);
|
||||
var temp = new Vector2(edge.Y, -edge.X);
|
||||
_normals.Add(temp.Normalized);
|
||||
}
|
||||
|
||||
Centroid = ComputeCentroid(_vertices);
|
||||
_cachedAABB = CalculateAABB();
|
||||
|
||||
// Compute the polygon mass data
|
||||
// TODO: Update fixture. Maybe use events for it? Who tf knows.
|
||||
@@ -85,6 +86,8 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
|
||||
private List<Vector2> _vertices = new();
|
||||
|
||||
private Box2 _cachedAABB;
|
||||
|
||||
internal Vector2 Centroid { get; set; } = Vector2.Zero;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
@@ -208,26 +211,35 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
return true;
|
||||
}
|
||||
|
||||
public Box2 CalculateLocalBounds(Angle rotation)
|
||||
/// <summary>
|
||||
/// Calculating the AABB for a polyshape isn't cheap so we'll just cache it whenever its vertices change.
|
||||
/// </summary>
|
||||
private Box2 CalculateAABB()
|
||||
{
|
||||
if (Vertices.Count == 0) return new Box2();
|
||||
if (_vertices.Count == 0) return new Box2();
|
||||
|
||||
var aabb = new Box2();
|
||||
Vector2 lower = Vertices[0];
|
||||
Vector2 upper = lower;
|
||||
var lower = Vertices[0];
|
||||
var upper = lower;
|
||||
|
||||
for (int i = 1; i < Vertices.Count; ++i)
|
||||
for (var i = 1; i < Vertices.Count; ++i)
|
||||
{
|
||||
Vector2 v = Vertices[i];
|
||||
var v = Vertices[i];
|
||||
lower = Vector2.ComponentMin(lower, v);
|
||||
upper = Vector2.ComponentMax(upper, v);
|
||||
}
|
||||
|
||||
Vector2 r = new Vector2(Radius, Radius);
|
||||
aabb.BottomLeft = lower - r;
|
||||
aabb.TopRight = upper + r;
|
||||
var r = new Vector2(Radius, Radius);
|
||||
|
||||
return aabb;
|
||||
return new Box2
|
||||
{
|
||||
BottomLeft = lower - r,
|
||||
TopRight = upper + r
|
||||
};
|
||||
}
|
||||
|
||||
public Box2 CalculateLocalBounds(Angle rotation)
|
||||
{
|
||||
return rotation == Angle.Zero ? _cachedAABB : new Box2Rotated(_cachedAABB, rotation).CalcBoundingBox();
|
||||
}
|
||||
|
||||
public void ApplyState()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user