mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Robust.Client.GameObjects
|
||||
RegisterClass<InputComponent>();
|
||||
RegisterClass<SpriteComponent>();
|
||||
RegisterClass<ClientOccluderComponent>();
|
||||
RegisterClass<OccluderTreeComponent>();
|
||||
RegisterClass<EyeComponent>();
|
||||
RegisterClass<AppearanceComponent>();
|
||||
RegisterClass<AppearanceTestComponent>();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
private SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Robust.Client.GameObjects
|
||||
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
|
||||
|
||||
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
|
||||
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
|
||||
_broadPhaseSystem = Get<SharedBroadphaseSystem>();
|
||||
}
|
||||
|
||||
private void StopAudioMessageHandler(StopAudioMessageClient ev)
|
||||
@@ -301,7 +301,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Robust.Server.Physics
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadphase = default!;
|
||||
private SharedBroadphaseSystem _broadphase = default!;
|
||||
|
||||
// Is delaying fixture updates a good idea? IDEK. We definitely can't do them on every tile changed
|
||||
// because if someone changes 50 tiles that will kill perf. We could probably just run it every Update
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Server.Physics
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(PhysicsSystem));
|
||||
SubscribeLocalEvent<RegenerateChunkCollisionEvent>(HandleCollisionRegenerate);
|
||||
_broadphase = Get<SharedBroadPhaseSystem>();
|
||||
_broadphase = Get<SharedBroadphaseSystem>();
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.GridFixtureUpdateRate, value => _cooldown = value, true);
|
||||
@@ -97,6 +97,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 +120,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});
|
||||
|
||||
@@ -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()"
|
||||
|
||||
@@ -18,18 +18,6 @@ namespace Robust.Shared.GameObjects
|
||||
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.
|
||||
@@ -136,7 +139,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 +245,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 +420,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 +544,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 +636,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 +1003,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 +1032,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 +1039,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 +1128,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 +1144,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 +1157,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 +1196,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 +1223,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 +1249,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!;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -83,7 +83,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 +146,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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
@@ -46,24 +46,19 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
internal MapId MapId { get; set; }
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Called when the broadphase finds two fixtures close to each other.
|
||||
/// </summary>
|
||||
public BroadPhaseDelegate OnBroadPhaseCollision;
|
||||
private SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
public readonly ContactHead ContactList;
|
||||
public int ContactCount { get; private set; }
|
||||
private const int ContactPoolInitialSize = 64;
|
||||
|
||||
internal Stack<Contact> ContactPoolList = new Stack<Contact>(ContactPoolInitialSize);
|
||||
internal Stack<Contact> ContactPoolList = new(ContactPoolInitialSize);
|
||||
|
||||
// Didn't use the eventbus because muh allocs on something being run for every collision every frame.
|
||||
/// <summary>
|
||||
/// Invoked whenever a KinematicController body collides. The first body is always guaranteed to be a KinematicController
|
||||
/// </summary>
|
||||
internal event Action<Fixture, Fixture, float, Manifold>? KinematicControllerCollision;
|
||||
internal event Action<Fixture, Fixture, float, Vector2>? KinematicControllerCollision;
|
||||
|
||||
// TODO: Need to migrate the interfaces to comp bus when possible
|
||||
// TODO: Also need to clean the station up to not have 160 contacts on roundstart
|
||||
@@ -75,13 +70,12 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
ContactList = new ContactHead();
|
||||
ContactCount = 0;
|
||||
OnBroadPhaseCollision = AddPair;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
InitializePool();
|
||||
}
|
||||
|
||||
@@ -93,49 +87,34 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
}
|
||||
}
|
||||
|
||||
public void FindNewContacts(MapId mapId)
|
||||
{
|
||||
foreach (var broadPhase in _broadPhaseSystem.GetBroadPhases(mapId))
|
||||
{
|
||||
broadPhase.UpdatePairs(OnBroadPhaseCollision);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Go through the cached broadphase movement and update contacts.
|
||||
/// </summary>
|
||||
/// <param name="gridId"></param>
|
||||
/// <param name="proxyA"></param>
|
||||
/// <param name="proxyB"></param>
|
||||
private void AddPair(GridId gridId, in FixtureProxy proxyA, in FixtureProxy proxyB)
|
||||
internal void AddPair(in FixtureProxy proxyA, in FixtureProxy proxyB)
|
||||
{
|
||||
Fixture fixtureA = proxyA.Fixture;
|
||||
Fixture fixtureB = proxyB.Fixture;
|
||||
|
||||
int indexA = proxyA.ChildIndex;
|
||||
int indexB = proxyB.ChildIndex;
|
||||
var indexA = proxyA.ChildIndex;
|
||||
var indexB = proxyB.ChildIndex;
|
||||
|
||||
PhysicsComponent bodyA = fixtureA.Body;
|
||||
PhysicsComponent bodyB = fixtureB.Body;
|
||||
|
||||
// Are the fixtures on the same body?
|
||||
if (bodyA.Owner.Uid.Equals(bodyB.Owner.Uid)) return;
|
||||
|
||||
// Box2D checks the mask / layer below but IMO doing it before contact is better.
|
||||
// Check default filter
|
||||
if (!ShouldCollide(fixtureA, fixtureB))
|
||||
return;
|
||||
// Broadphase has already done the faster check for collision mask / layers
|
||||
// so no point duplicating
|
||||
|
||||
// Does a contact already exist?
|
||||
var edge = bodyB.ContactEdges;
|
||||
|
||||
for (ContactEdge? ceB = bodyB.ContactEdges; ceB != null; ceB = ceB?.Next)
|
||||
while (edge != null)
|
||||
{
|
||||
if (ceB.Other == bodyA)
|
||||
if (edge.Other == bodyA)
|
||||
{
|
||||
Fixture fA = ceB.Contact?.FixtureA!;
|
||||
Fixture fB = ceB.Contact?.FixtureB!;
|
||||
var iA = ceB.Contact!.ChildIndexA;
|
||||
var iB = ceB.Contact!.ChildIndexB;
|
||||
var fA = edge.Contact!.FixtureA;
|
||||
var fB = edge.Contact!.FixtureB;
|
||||
var iA = edge.Contact.ChildIndexA;
|
||||
var iB = edge.Contact.ChildIndexB;
|
||||
|
||||
if (fA == fixtureA && fB == fixtureB && iA == indexA && iB == indexB)
|
||||
{
|
||||
@@ -150,7 +129,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
}
|
||||
}
|
||||
|
||||
ceB = ceB.Next;
|
||||
edge = edge.Next;
|
||||
}
|
||||
|
||||
// Does a joint override collision? Is at least one body dynamic?
|
||||
@@ -167,7 +146,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
*/
|
||||
|
||||
// Call the factory.
|
||||
Contact c = Contact.Create(this, gridId, fixtureA, indexA, fixtureB, indexB);
|
||||
Contact c = Contact.Create(this, fixtureA, indexA, fixtureB, indexB);
|
||||
|
||||
// Sloth: IDK why Farseer and Aether2D have this shit but fuck it.
|
||||
if (c == null) return;
|
||||
@@ -219,7 +198,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldCollide(Fixture fixtureA, Fixture fixtureB)
|
||||
internal static bool ShouldCollide(Fixture fixtureA, Fixture fixtureB)
|
||||
{
|
||||
// TODO: Should we only be checking one side's mask? I think maybe fixtureB? IDK
|
||||
return !((fixtureA.CollisionMask & fixtureB.CollisionLayer) == 0x0 &&
|
||||
@@ -290,7 +269,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
PhysicsComponent bodyA = fixtureA.Body;
|
||||
PhysicsComponent bodyB = fixtureB.Body;
|
||||
|
||||
//Do no try to collide disabled bodies
|
||||
// Do not try to collide disabled bodies
|
||||
if (!bodyA.CanCollide || !bodyB.CanCollide)
|
||||
{
|
||||
contact = contact.Next;
|
||||
@@ -343,22 +322,34 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
continue;
|
||||
}
|
||||
|
||||
bool? overlap = false;
|
||||
var proxyA = fixtureA.Proxies[indexA];
|
||||
var proxyB = fixtureB.Proxies[indexB];
|
||||
var broadphaseA = bodyA.Broadphase;
|
||||
var broadphaseB = bodyB.Broadphase;
|
||||
|
||||
// Sloth addition: Kind of hacky and might need to be removed at some point.
|
||||
// One of the bodies was probably put into nullspace so we need to remove I think.
|
||||
if (fixtureA.Proxies.ContainsKey(contact.GridId) && fixtureB.Proxies.ContainsKey(contact.GridId))
|
||||
// TODO: IT MIGHT BE THE FATAABB STUFF FOR MOVEPROXY SO TRY THAT
|
||||
var overlap = false;
|
||||
|
||||
// We can have cross-broadphase proxies hence need to change them to worldspace
|
||||
if (broadphaseA != null && broadphaseB != null)
|
||||
{
|
||||
var proxyIdA = fixtureA.Proxies[contact.GridId][indexA].ProxyId;
|
||||
var proxyIdB = fixtureB.Proxies[contact.GridId][indexB].ProxyId;
|
||||
if (broadphaseA == broadphaseB)
|
||||
{
|
||||
overlap = proxyA.AABB.Intersects(proxyB.AABB);
|
||||
}
|
||||
else
|
||||
{
|
||||
// These should really be destroyed before map changes.
|
||||
DebugTools.Assert(broadphaseA.Owner.Transform.MapID == broadphaseB.Owner.Transform.MapID);
|
||||
|
||||
var broadPhase = _broadPhaseSystem.GetBroadPhase(MapId, contact.GridId);
|
||||
|
||||
overlap = broadPhase?.TestOverlap(proxyIdA, proxyIdB);
|
||||
var proxyAWorldAABB = proxyA.AABB.Translated(broadphaseA.Owner.Transform.WorldPosition);
|
||||
var proxyBWorldAABB = proxyB.AABB.Translated(broadphaseB.Owner.Transform.WorldPosition);
|
||||
overlap = proxyAWorldAABB.Intersects(proxyBWorldAABB);
|
||||
}
|
||||
}
|
||||
|
||||
// Here we destroy contacts that cease to overlap in the broad-phase.
|
||||
if (overlap == false)
|
||||
if (!overlap)
|
||||
{
|
||||
Contact cNuke = contact;
|
||||
contact = contact.Next;
|
||||
@@ -366,12 +357,17 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
continue;
|
||||
}
|
||||
|
||||
// The contact persists.
|
||||
contact.Update(this, _startCollisions, _endCollisions);
|
||||
|
||||
contact = contact.Next;
|
||||
}
|
||||
|
||||
// TODO: Look at making a manager to cache world positions + rotations during physics step
|
||||
// Ideally: Set them here (once we know what contacts we need)
|
||||
// Re-use in Physics island.
|
||||
// Maybbbeee also have broadphase use it too?
|
||||
// This will actually be decently big savings.
|
||||
// Aether multi-threads the Update too so potentially look at making the manager thread-safe.
|
||||
|
||||
foreach (var contact in _startCollisions)
|
||||
{
|
||||
// It's possible for contacts to get nuked by other collision behaviors running on an entity deleting it
|
||||
@@ -412,20 +408,6 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyA.Owner.Uid, new EndCollideEvent(fixtureA, fixtureB, manifold));
|
||||
_entityManager.EventBus.RaiseLocalEvent(bodyB.Owner.Uid, new EndCollideEvent(fixtureB, fixtureA, manifold));
|
||||
|
||||
#pragma warning disable 618
|
||||
foreach (var comp in bodyA.Owner.GetAllComponents<IEndCollide>().ToArray())
|
||||
{
|
||||
if (bodyB.Deleted) break;
|
||||
comp.CollideWith(fixtureA, fixtureB, manifold);
|
||||
}
|
||||
|
||||
foreach (var comp in bodyB.Owner.GetAllComponents<IEndCollide>().ToArray())
|
||||
{
|
||||
if (bodyA.Deleted) break;
|
||||
comp.CollideWith(fixtureB, fixtureA, manifold);
|
||||
}
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
_startCollisions.Clear();
|
||||
@@ -434,26 +416,29 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
public void PreSolve(float frameTime)
|
||||
{
|
||||
Span<Vector2> points = stackalloc Vector2[2];
|
||||
|
||||
// We'll do pre and post-solve around all islands rather than each specific island as it seems cleaner with race conditions.
|
||||
for (var contact = ContactList.Next; contact != ContactList; contact = contact?.Next)
|
||||
{
|
||||
if (contact == null || !contact.IsTouching || !contact.Enabled)
|
||||
if (contact is not {IsTouching: true, Enabled: true})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var bodyA = contact.FixtureA!.Body;
|
||||
var bodyB = contact.FixtureB!.Body;
|
||||
contact.GetWorldManifold(out var worldNormal, points);
|
||||
|
||||
// Didn't use an EntitySystemMessage as this is called FOR EVERY COLLISION AND IS REALLY EXPENSIVE
|
||||
// so we just use the Action. Also we'll sort out BodyA / BodyB for anyone listening first.
|
||||
if (bodyA.BodyType == BodyType.KinematicController)
|
||||
{
|
||||
KinematicControllerCollision?.Invoke(contact.FixtureA!, contact.FixtureB!, frameTime, contact.Manifold);
|
||||
KinematicControllerCollision?.Invoke(contact.FixtureA!, contact.FixtureB!, frameTime, -worldNormal);
|
||||
}
|
||||
else if (bodyB.BodyType == BodyType.KinematicController)
|
||||
{
|
||||
KinematicControllerCollision?.Invoke(contact.FixtureB!, contact.FixtureA!, frameTime, contact.Manifold);
|
||||
KinematicControllerCollision?.Invoke(contact.FixtureB!, contact.FixtureA!, frameTime, worldNormal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -464,7 +449,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void BroadPhaseDelegate(GridId gridId, in FixtureProxy proxyA, in FixtureProxy proxyB);
|
||||
public delegate void BroadPhaseDelegate(in FixtureProxy proxyA, in FixtureProxy proxyB);
|
||||
|
||||
#region Collide Events Classes
|
||||
|
||||
@@ -472,20 +457,18 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
public Fixture OurFixture { get; }
|
||||
public Fixture OtherFixture { get; }
|
||||
public Manifold Manifold { get; }
|
||||
|
||||
public CollideEvent(Fixture ourFixture, Fixture otherFixture, Manifold manifold)
|
||||
public CollideEvent(Fixture ourFixture, Fixture otherFixture)
|
||||
{
|
||||
OurFixture = ourFixture;
|
||||
OtherFixture = otherFixture;
|
||||
Manifold = manifold;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StartCollideEvent : CollideEvent
|
||||
{
|
||||
public StartCollideEvent(Fixture ourFixture, Fixture otherFixture, Manifold manifold)
|
||||
: base(ourFixture, otherFixture, manifold)
|
||||
: base(ourFixture, otherFixture)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -493,7 +476,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
public sealed class EndCollideEvent : CollideEvent
|
||||
{
|
||||
public EndCollideEvent(Fixture ourFixture, Fixture otherFixture, Manifold manifold)
|
||||
: base(ourFixture, otherFixture, manifold)
|
||||
: base(ourFixture, otherFixture)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
/// <summary>
|
||||
/// Gets a new contact to use, using the contact pool if relevant.
|
||||
/// </summary>
|
||||
internal static Contact Create(ContactManager contactManager, GridId gridId, Fixture fixtureA, int indexA, Fixture fixtureB, int indexB)
|
||||
internal static Contact Create(ContactManager contactManager, Fixture fixtureA, int indexA, Fixture fixtureB, int indexB)
|
||||
{
|
||||
var type1 = fixtureA.Shape.ShapeType;
|
||||
var type2 = fixtureB.Shape.ShapeType;
|
||||
@@ -197,7 +197,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
// Pull out a spare contact object
|
||||
contactManager.ContactPoolList.TryPop(out var contact);
|
||||
|
||||
// Edge+Polygon is non-symetrical due to the way Erin handles collision type registration.
|
||||
// Edge+Polygon is non-symmetrical due to the way Erin handles collision type registration.
|
||||
if ((type1 >= type2 || (type1 == ShapeType.Edge && type2 == ShapeType.Polygon)) && !(type2 == ShapeType.Edge && type1 == ShapeType.Polygon))
|
||||
{
|
||||
if (contact == null)
|
||||
@@ -213,7 +213,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
contact.Reset(fixtureB, indexB, fixtureA, indexA);
|
||||
}
|
||||
|
||||
contact.GridId = gridId;
|
||||
contact._type = _registers[(int)type1, (int)type2];
|
||||
|
||||
return contact;
|
||||
@@ -296,43 +295,46 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
if (FixtureA == null || FixtureB == null)
|
||||
return;
|
||||
|
||||
Manifold oldManifold = Manifold;
|
||||
var oldManifold = Manifold;
|
||||
|
||||
// Re-enable this contact.
|
||||
Enabled = true;
|
||||
|
||||
bool touching;
|
||||
bool wasTouching = IsTouching;
|
||||
var wasTouching = IsTouching;
|
||||
|
||||
bool sensor = !(FixtureA.Hard && FixtureB.Hard);
|
||||
var sensor = !(FixtureA.Hard && FixtureB.Hard);
|
||||
|
||||
var bodyATransform = bodyA.GetTransform();
|
||||
var bodyBTransform = bodyB.GetTransform();
|
||||
|
||||
// Is this contact a sensor?
|
||||
if (sensor)
|
||||
{
|
||||
IPhysShape shapeA = FixtureA.Shape;
|
||||
IPhysShape shapeB = FixtureB.Shape;
|
||||
touching = _collisionManager.TestOverlap(shapeA, ChildIndexA, shapeB, ChildIndexB, bodyA.GetTransform(), bodyB.GetTransform());
|
||||
touching = _collisionManager.TestOverlap(shapeA, ChildIndexA, shapeB, ChildIndexB, bodyATransform, bodyBTransform);
|
||||
|
||||
// Sensors don't generate manifolds.
|
||||
Manifold.PointCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Evaluate(ref Manifold, bodyA.GetTransform(), bodyB.GetTransform());
|
||||
Evaluate(ref Manifold, bodyATransform, bodyBTransform);
|
||||
touching = Manifold.PointCount > 0;
|
||||
|
||||
// Match old contact ids to new contact ids and copy the
|
||||
// stored impulses to warm start the solver.
|
||||
for (int i = 0; i < Manifold.PointCount; ++i)
|
||||
{
|
||||
ManifoldPoint mp2 = Manifold.Points[i];
|
||||
var mp2 = Manifold.Points[i];
|
||||
mp2.NormalImpulse = 0.0f;
|
||||
mp2.TangentImpulse = 0.0f;
|
||||
ContactID id2 = mp2.Id;
|
||||
var id2 = mp2.Id;
|
||||
|
||||
for (int j = 0; j < oldManifold.PointCount; ++j)
|
||||
for (var j = 0; j < oldManifold.PointCount; ++j)
|
||||
{
|
||||
ManifoldPoint mp1 = oldManifold.Points[j];
|
||||
var mp1 = oldManifold.Points[j];
|
||||
|
||||
if (mp1.Id.Key == id2.Key)
|
||||
{
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -32,8 +29,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
{
|
||||
internal sealed class ContactSolver
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
private bool _warmStarting;
|
||||
private float _velocityThreshold;
|
||||
private float _baumgarte;
|
||||
@@ -52,24 +47,13 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
private ContactVelocityConstraint[] _velocityConstraints = Array.Empty<ContactVelocityConstraint>();
|
||||
private ContactPositionConstraint[] _positionConstraints = Array.Empty<ContactPositionConstraint>();
|
||||
|
||||
public void Initialize()
|
||||
public void LoadConfig(in IslandCfg cfg)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_warmStarting = _configManager.GetCVar(CVars.WarmStarting);
|
||||
_configManager.OnValueChanged(CVars.WarmStarting, value => _warmStarting = value);
|
||||
|
||||
_velocityThreshold = _configManager.GetCVar(CVars.VelocityThreshold);
|
||||
_configManager.OnValueChanged(CVars.VelocityThreshold, value => _velocityThreshold = value);
|
||||
|
||||
_baumgarte = _configManager.GetCVar(CVars.Baumgarte);
|
||||
_configManager.OnValueChanged(CVars.Baumgarte, value => _baumgarte = value);
|
||||
|
||||
_linearSlop = _configManager.GetCVar(CVars.LinearSlop);
|
||||
_configManager.OnValueChanged(CVars.LinearSlop, value => _linearSlop = value);
|
||||
|
||||
_maxLinearCorrection = _configManager.GetCVar(CVars.MaxLinearCorrection);
|
||||
_configManager.OnValueChanged(CVars.MaxLinearCorrection, value => _maxLinearCorrection = value);
|
||||
_warmStarting = cfg.WarmStarting;
|
||||
_velocityThreshold = cfg.VelocityThreshold;
|
||||
_baumgarte = cfg.Baumgarte;
|
||||
_linearSlop = cfg.LinearSlop;
|
||||
_maxLinearCorrection = cfg.MaxLinearCorrection;
|
||||
}
|
||||
|
||||
public void Reset(SolverData data, int contactCount, Contact[] contacts)
|
||||
|
||||
@@ -55,13 +55,11 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("id", true)]
|
||||
[DataField("id")]
|
||||
public string ID { get; set; } = string.Empty;
|
||||
|
||||
public IReadOnlyDictionary<GridId, FixtureProxy[]> Proxies => _proxies;
|
||||
|
||||
[NonSerialized]
|
||||
private readonly Dictionary<GridId, FixtureProxy[]> _proxies = new();
|
||||
[field: NonSerialized]
|
||||
public FixtureProxy[] Proxies { get; set; } = Array.Empty<FixtureProxy>();
|
||||
|
||||
[ViewVariables]
|
||||
[NonSerialized]
|
||||
@@ -130,8 +128,8 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
if (_hard == value)
|
||||
return;
|
||||
|
||||
Body.RegenerateContacts();
|
||||
_hard = value;
|
||||
Body.Awake = true;
|
||||
Body.FixtureChanged(this);
|
||||
}
|
||||
}
|
||||
@@ -142,10 +140,6 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// MassData
|
||||
// The reason these aren't a struct is because Serv3 + doing MassData in yaml everywhere would suck.
|
||||
// Plus now it's WAYYY easier to share shapes even among different prototypes.
|
||||
public Vector2 Centroid => _centroid;
|
||||
|
||||
private Vector2 _centroid = Vector2.Zero;
|
||||
|
||||
public float Inertia => _inertia;
|
||||
|
||||
private float _inertia;
|
||||
@@ -161,8 +155,11 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseTo(value, _mass)) return;
|
||||
_mass = value;
|
||||
|
||||
_mass = MathF.Max(0f, value);
|
||||
Body.FixtureChanged(this);
|
||||
Body.ResetMassData();
|
||||
ComputeProperties();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +178,9 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
if (_collisionLayer == value)
|
||||
return;
|
||||
|
||||
Body.RegenerateContacts();
|
||||
_collisionLayer = value;
|
||||
Body.FixtureChanged(this);
|
||||
EntitySystem.Get<SharedBroadphaseSystem>().Refilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,15 +199,37 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
if (_collisionMask == value)
|
||||
return;
|
||||
|
||||
Body.RegenerateContacts();
|
||||
_collisionMask = value;
|
||||
Body.FixtureChanged(this);
|
||||
EntitySystem.Get<SharedBroadphaseSystem>().Refilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("mask", customTypeSerializer: typeof(FlagSerializer<CollisionMask>))]
|
||||
private int _collisionMask;
|
||||
|
||||
public float Area
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Shape)
|
||||
{
|
||||
case PhysShapeAabb aabb:
|
||||
return aabb.LocalBounds.Width * aabb.LocalBounds.Height;
|
||||
case PhysShapeRect rect:
|
||||
return rect.Rectangle.Width * rect.Rectangle.Height;
|
||||
case PhysShapeCircle circle:
|
||||
return MathF.PI * circle.Radius * circle.Radius;
|
||||
case PolygonShape poly:
|
||||
ComputePoly(poly, out var area);
|
||||
return area;
|
||||
default:
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
// TODO: Temporary until PhysShapeAabb is fixed because some weird shit happens with collisions.
|
||||
@@ -261,158 +280,6 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
fixture._collisionMask = _collisionMask;
|
||||
}
|
||||
|
||||
internal void SetProxies(GridId gridId, FixtureProxy[] proxies)
|
||||
{
|
||||
DebugTools.Assert(!_proxies.ContainsKey(gridId));
|
||||
_proxies[gridId] = proxies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear this fixture's proxies from the broadphase.
|
||||
/// If doing this for every fixture at once consider using the method on PhysicsComponent instead.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Broadphase system will also need cleaning up for the cached broadphases for the body.
|
||||
/// </remarks>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="broadPhaseSystem"></param>
|
||||
public void ClearProxies(MapId? mapId = null, SharedBroadPhaseSystem? broadPhaseSystem = null)
|
||||
{
|
||||
mapId ??= Body.Owner.Transform.MapID;
|
||||
broadPhaseSystem ??= EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
|
||||
foreach (var (gridId, proxies) in _proxies)
|
||||
{
|
||||
var broadPhase = broadPhaseSystem.GetBroadPhase(mapId.Value, gridId);
|
||||
if (broadPhase == null) continue;
|
||||
|
||||
foreach (var proxy in proxies)
|
||||
{
|
||||
broadPhase.RemoveProxy(proxy.ProxyId);
|
||||
}
|
||||
}
|
||||
|
||||
_proxies.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the particular grid's proxies for this fixture.
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="broadPhaseSystem"></param>
|
||||
/// <param name="gridId"></param>
|
||||
public void ClearProxies(MapId mapId, SharedBroadPhaseSystem broadPhaseSystem, GridId gridId)
|
||||
{
|
||||
if (!Proxies.TryGetValue(gridId, out var proxies)) return;
|
||||
|
||||
var broadPhase = broadPhaseSystem.GetBroadPhase(mapId, gridId);
|
||||
|
||||
if (broadPhase != null)
|
||||
{
|
||||
foreach (var proxy in proxies)
|
||||
{
|
||||
broadPhase.RemoveProxy(proxy.ProxyId);
|
||||
}
|
||||
}
|
||||
|
||||
_proxies.Remove(gridId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates FixtureProxies on the relevant broadphases.
|
||||
/// If doing this for every fixture at once consider using the method on PhysicsComponent instead.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You will need to manually add this to the body's broadphases.
|
||||
/// </remarks>
|
||||
public void CreateProxies(IMapManager? mapManager = null, SharedBroadPhaseSystem? broadPhaseSystem = null)
|
||||
{
|
||||
DebugTools.Assert(_proxies.Count == 0);
|
||||
ProxyCount = Shape.ChildCount;
|
||||
|
||||
var mapId = Body.Owner.Transform.MapID;
|
||||
mapManager ??= IoCManager.Resolve<IMapManager>();
|
||||
broadPhaseSystem ??= EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
|
||||
var worldPosition = Body.Owner.Transform.WorldPosition;
|
||||
var worldRotation = Body.Owner.Transform.WorldRotation;
|
||||
var worldAABB = Body.GetWorldAABB(worldPosition, worldRotation);
|
||||
|
||||
foreach (var gridId in mapManager.FindGridIdsIntersecting(mapId, worldAABB, true))
|
||||
{
|
||||
var broadPhase = broadPhaseSystem.GetBroadPhase(mapId, gridId);
|
||||
if (broadPhase == null) continue;
|
||||
|
||||
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 - Body.Owner.EntityManager.GetEntity(grid.GridEntityId).Transform.WorldRotation;
|
||||
}
|
||||
|
||||
var proxies = new FixtureProxy[Shape.ChildCount];
|
||||
_proxies[gridId] = proxies;
|
||||
|
||||
for (var i = 0; i < ProxyCount; i++)
|
||||
{
|
||||
// TODO: Will need to pass in childIndex to this as well
|
||||
var aabb = Shape.CalculateLocalBounds(gridRotation).Translated(offset);
|
||||
|
||||
var proxy = new FixtureProxy(aabb, this, i);
|
||||
|
||||
proxy.ProxyId = broadPhase.AddProxy(ref proxy);
|
||||
proxies[i] = proxy;
|
||||
DebugTools.Assert(proxies[i].ProxyId != DynamicTree.Proxy.Free);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates FixtureProxies on the relevant broadphase.
|
||||
/// If doing this for every fixture at once consider using the method on PhysicsComponent instead.
|
||||
/// </summary>
|
||||
public void CreateProxies(IBroadPhase broadPhase, IMapManager? mapManager = null, SharedBroadPhaseSystem? broadPhaseSystem = null)
|
||||
{
|
||||
// TODO: Combine with the above method to be less DRY.
|
||||
mapManager ??= IoCManager.Resolve<IMapManager>();
|
||||
broadPhaseSystem ??= EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
|
||||
var gridId = broadPhaseSystem.GetGridId(broadPhase);
|
||||
|
||||
Vector2 offset = Body.Owner.Transform.WorldPosition;
|
||||
var worldRotation = Body.Owner.Transform.WorldRotation;
|
||||
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 - Body.Owner.EntityManager.GetEntity(grid.GridEntityId).Transform.WorldRotation;
|
||||
}
|
||||
|
||||
var proxies = new FixtureProxy[Shape.ChildCount];
|
||||
_proxies[gridId] = proxies;
|
||||
|
||||
for (var i = 0; i < ProxyCount; i++)
|
||||
{
|
||||
// TODO: Will need to pass in childIndex to this as well
|
||||
var aabb = Shape.CalculateLocalBounds(gridRotation).Translated(offset);
|
||||
|
||||
var proxy = new FixtureProxy(aabb, this, i);
|
||||
|
||||
proxy.ProxyId = broadPhase.AddProxy(ref proxy);
|
||||
proxies[i] = proxy;
|
||||
DebugTools.Assert(proxies[i].ProxyId != DynamicTree.Proxy.Free);
|
||||
}
|
||||
|
||||
broadPhaseSystem.AddBroadPhase(Body, broadPhase);
|
||||
}
|
||||
|
||||
// Moved from Shape because no MassData on Shape anymore (due to serv3 and physics ease-of-use etc etc.)
|
||||
internal void ComputeProperties()
|
||||
{
|
||||
@@ -431,7 +298,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
ComputeRect(rect);
|
||||
break;
|
||||
case PolygonShape poly:
|
||||
ComputePoly(poly);
|
||||
ComputePoly(poly, out _);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
@@ -452,7 +319,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
var density = area > 0.0f ? Mass / area : 0.0f;
|
||||
|
||||
// Center of mass
|
||||
_centroid = Vector2.Zero;
|
||||
aabb.Centroid = Vector2.Zero;
|
||||
|
||||
// Inertia tensor relative to the local origin (point s).
|
||||
_inertia = density * I;
|
||||
@@ -471,13 +338,13 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
var density = area > 0.0f ? Mass / area : 0.0f;
|
||||
|
||||
// Center of mass
|
||||
_centroid = Vector2.Zero;
|
||||
rect.Centroid = Vector2.Zero;
|
||||
|
||||
// Inertia tensor relative to the local origin (point s).
|
||||
_inertia = density * I;
|
||||
}
|
||||
|
||||
private void ComputePoly(PolygonShape poly)
|
||||
private void ComputePoly(PolygonShape poly, out float area)
|
||||
{
|
||||
// Polygon mass, centroid, and inertia.
|
||||
// Let rho be the polygon density in mass per unit area.
|
||||
@@ -507,7 +374,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
//FPE optimization: Consolidated the calculate centroid and mass code to a single method.
|
||||
Vector2 center = Vector2.Zero;
|
||||
var area = 0.0f;
|
||||
area = 0.0f;
|
||||
float I = 0.0f;
|
||||
|
||||
// pRef is the reference point for forming triangles.
|
||||
@@ -555,27 +422,26 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
// Center of mass
|
||||
center *= 1.0f / area;
|
||||
_centroid = center + s;
|
||||
poly.Centroid = center + s;
|
||||
|
||||
// Inertia tensor relative to the local origin (point s).
|
||||
_inertia = density * I;
|
||||
|
||||
// Shift to center of mass then to original body origin.
|
||||
_inertia += Mass * (Vector2.Dot(_centroid, _centroid) - Vector2.Dot(center, center));
|
||||
_inertia += Mass * (Vector2.Dot(poly.Centroid, poly.Centroid) - Vector2.Dot(center, center));
|
||||
}
|
||||
|
||||
private void ComputeCircle(PhysShapeCircle circle)
|
||||
{
|
||||
var radSquared = MathF.Pow(circle.Radius, 2);
|
||||
_centroid = circle.Position;
|
||||
|
||||
// inertia about the local origin
|
||||
_inertia = Mass * (0.5f * radSquared + Vector2.Dot(_centroid, _centroid));
|
||||
_inertia = Mass * (0.5f * radSquared + Vector2.Dot(circle.Position, circle.Position));
|
||||
}
|
||||
|
||||
private void ComputeEdge(EdgeShape edge)
|
||||
{
|
||||
_centroid = (edge.Vertex1 + edge.Vertex2) * 0.5f;
|
||||
edge.Centroid = (edge.Vertex1 + edge.Vertex2) * 0.5f;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -22,15 +22,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using System.Diagnostics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
@@ -140,13 +137,13 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
*/
|
||||
public sealed class PhysicsIsland
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
// if you add new deps to this, the IoCManager inject dependencies is behind #if too.
|
||||
#if DEBUG
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
private List<IPhysBody> _debugBodies = new(8);
|
||||
#endif
|
||||
|
||||
private ContactSolver _contactSolver = default!;
|
||||
private readonly ContactSolver _contactSolver = new();
|
||||
|
||||
internal int ID { get; set; } = -1;
|
||||
|
||||
@@ -211,53 +208,25 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
/// </summary>
|
||||
public int JointCount { get; private set; }
|
||||
|
||||
public void Initialize()
|
||||
[Conditional("DEBUG")]
|
||||
internal void Initialize()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_contactSolver = new ContactSolver();
|
||||
_contactSolver.Initialize();
|
||||
}
|
||||
|
||||
// Values
|
||||
_angTolSqr = MathF.Pow(_configManager.GetCVar(CVars.AngularSleepTolerance), 2);
|
||||
_configManager.OnValueChanged(CVars.AngularSleepTolerance, value => _angTolSqr = MathF.Pow(value, 2));
|
||||
internal void LoadConfig(in IslandCfg cfg)
|
||||
{
|
||||
_angTolSqr = cfg.AngTolSqr;
|
||||
_linTolSqr = cfg.LinTolSqr;
|
||||
_warmStarting = cfg.WarmStarting;
|
||||
_velocityIterations = cfg.VelocityIterations;
|
||||
_maxLinearVelocity = cfg.MaxLinearVelocity;
|
||||
_maxAngularVelocity = cfg.MaxAngularVelocity;
|
||||
_positionIterations = cfg.PositionIterations;
|
||||
_sleepAllowed = cfg.SleepAllowed;
|
||||
_timeToSleep = cfg.TimeToSleep;
|
||||
|
||||
_linTolSqr = MathF.Pow(_configManager.GetCVar(CVars.LinearSleepTolerance), 2);
|
||||
_configManager.OnValueChanged(CVars.LinearSleepTolerance, value => _linTolSqr = MathF.Pow(value, 2));
|
||||
|
||||
_warmStarting = _configManager.GetCVar(CVars.WarmStarting);
|
||||
_configManager.OnValueChanged(CVars.WarmStarting, value => _warmStarting = value);
|
||||
|
||||
_velocityIterations = _configManager.GetCVar(CVars.VelocityIterations);
|
||||
_configManager.OnValueChanged(CVars.VelocityIterations, value => _velocityIterations = value);
|
||||
|
||||
_configManager.OnValueChanged(CVars.MaxLinVelocity, value => SetMaxLinearVelocity(value, null));
|
||||
_configManager.OnValueChanged(CVars.NetTickrate, value => SetMaxLinearVelocity(null, value));
|
||||
void SetMaxLinearVelocity(float? maxLinVelocity = null, int? tickrate = null)
|
||||
{
|
||||
maxLinVelocity ??= _configManager.GetCVar(CVars.MaxLinVelocity);
|
||||
tickrate ??= _configManager.GetCVar(CVars.NetTickrate);
|
||||
_maxLinearVelocity = (float)(maxLinVelocity / tickrate);
|
||||
}
|
||||
SetMaxLinearVelocity();
|
||||
|
||||
_configManager.OnValueChanged(CVars.MaxAngVelocity, value => SetMaxAngularVelocity(value, null));
|
||||
_configManager.OnValueChanged(CVars.NetTickrate, value => SetMaxAngularVelocity(null, value));
|
||||
void SetMaxAngularVelocity(float? maxAngVelocity = null, int? tickrate = null)
|
||||
{
|
||||
maxAngVelocity ??= _configManager.GetCVar(CVars.MaxAngVelocity);
|
||||
tickrate ??= _configManager.GetCVar(CVars.NetTickrate);
|
||||
_maxAngularVelocity = (float)((MathF.PI * 2 * maxAngVelocity) / tickrate);
|
||||
}
|
||||
SetMaxAngularVelocity();
|
||||
|
||||
_positionIterations = _configManager.GetCVar(CVars.PositionIterations);
|
||||
_configManager.OnValueChanged(CVars.PositionIterations, value => _positionIterations = value);
|
||||
|
||||
_sleepAllowed = _configManager.GetCVar(CVars.SleepAllowed);
|
||||
_configManager.OnValueChanged(CVars.SleepAllowed, value => _sleepAllowed = value);
|
||||
|
||||
_timeToSleep = _configManager.GetCVar(CVars.TimeToSleep);
|
||||
_configManager.OnValueChanged(CVars.TimeToSleep, value => _timeToSleep = value);
|
||||
_contactSolver.LoadConfig(cfg);
|
||||
}
|
||||
|
||||
public void Append(List<IPhysBody> bodies, List<Contact> contacts, List<Joint> joints)
|
||||
@@ -631,4 +600,24 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
public Vector2[] Positions { get; set; } = default!;
|
||||
public float[] Angles { get; set; } = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains all configuration parameters that need to be passed to physics islands.
|
||||
/// </summary>
|
||||
internal struct IslandCfg
|
||||
{
|
||||
public float AngTolSqr;
|
||||
public float LinTolSqr;
|
||||
public bool SleepAllowed;
|
||||
public bool WarmStarting;
|
||||
public int VelocityIterations;
|
||||
public float MaxLinearVelocity;
|
||||
public float MaxAngularVelocity;
|
||||
public int PositionIterations;
|
||||
public float TimeToSleep;
|
||||
public float VelocityThreshold;
|
||||
public float Baumgarte;
|
||||
public float LinearSlop;
|
||||
public float MaxLinearCorrection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +386,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// Box2D does this at the end of a step and also here when there's a fixture update.
|
||||
// Given external stuff can move bodies we'll just do this here.
|
||||
// Unfortunately this NEEDS to be predicted to make pushing remotely fucking good.
|
||||
ContactManager.FindNewContacts(MapId);
|
||||
EntitySystem.Get<SharedBroadphaseSystem>().FindNewContacts(MapId);
|
||||
|
||||
var invDt = frameTime > 0.0f ? 1.0f / frameTime : 0.0f;
|
||||
var dtRatio = _invDt0 * frameTime;
|
||||
|
||||
@@ -7,9 +7,7 @@ namespace Robust.Shared.Physics {
|
||||
|
||||
public interface IBroadPhase
|
||||
{
|
||||
void UpdatePairs(BroadPhaseDelegate callback);
|
||||
|
||||
bool TestOverlap(DynamicTree.Proxy proxyA, DynamicTree.Proxy proxyB);
|
||||
Box2 GetFatAabb(DynamicTree.Proxy proxy);
|
||||
|
||||
DynamicTree.Proxy AddProxy(ref FixtureProxy proxy);
|
||||
|
||||
@@ -17,12 +15,6 @@ namespace Robust.Shared.Physics {
|
||||
|
||||
void RemoveProxy(DynamicTree.Proxy proxy);
|
||||
|
||||
void TouchProxy(DynamicTree.Proxy proxy);
|
||||
|
||||
void QueryAABB(DynamicTree<FixtureProxy>.QueryCallbackDelegate callback,
|
||||
Box2 aabb,
|
||||
bool approx = false);
|
||||
|
||||
void QueryAabb<TState>(
|
||||
ref TState state,
|
||||
DynamicTree<FixtureProxy>.QueryCallbackDelegate<TState> callback,
|
||||
@@ -31,6 +23,8 @@ namespace Robust.Shared.Physics {
|
||||
|
||||
IEnumerable<FixtureProxy> QueryAabb(Box2 aabb, bool approx = false);
|
||||
|
||||
IEnumerable<FixtureProxy> QueryAabb(List<FixtureProxy> proxies, Box2 aabb, bool approx = false);
|
||||
|
||||
void QueryPoint(DynamicTree<FixtureProxy>.QueryCallbackDelegate callback,
|
||||
Vector2 point,
|
||||
bool approx = false);
|
||||
|
||||
@@ -49,10 +49,6 @@ namespace Robust.Shared.Physics
|
||||
/// </summary>
|
||||
int CollisionMask { get; }
|
||||
|
||||
void CreateProxies(IMapManager? mapManager = null, SharedBroadPhaseSystem? broadPhaseSystem = null);
|
||||
|
||||
void ClearProxies();
|
||||
|
||||
/// <summary>
|
||||
/// Removes all of the currently active contacts for this body.
|
||||
/// </summary>
|
||||
|
||||
@@ -10,7 +10,11 @@ subject to the following restrictions:
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -31,6 +35,8 @@ namespace Robust.Shared.Physics
|
||||
/// </summary>
|
||||
internal sealed class IslandManager : IIslandManager
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private static readonly IslandBodyCapacitySort CapacitySort = new();
|
||||
private static readonly IslandBodyCountSort CountSort = new();
|
||||
|
||||
@@ -67,13 +73,83 @@ namespace Robust.Shared.Physics
|
||||
}
|
||||
}
|
||||
|
||||
private int _tickRate;
|
||||
private float _maxLinearVelocityRaw;
|
||||
private float _maxAngularVelocityRaw;
|
||||
private IslandCfg _islandCfg;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
InitConfig();
|
||||
|
||||
_loneIsland.Initialize();
|
||||
// Set an initial size so we don't spam a bunch of array resizes at the start
|
||||
_loneIsland.Resize(64, 32, 8);
|
||||
}
|
||||
|
||||
private void InitConfig()
|
||||
{
|
||||
// Config stuff.
|
||||
CfgVar(CVars.AngularSleepTolerance, value => _islandCfg.AngTolSqr = value * value);
|
||||
CfgVar(CVars.LinearSleepTolerance, value => _islandCfg.LinTolSqr = value * value);
|
||||
CfgVar(CVars.WarmStarting, value => _islandCfg.WarmStarting = value);
|
||||
CfgVar(CVars.VelocityIterations, value => _islandCfg.VelocityIterations = value);
|
||||
CfgVar(CVars.PositionIterations, value => _islandCfg.PositionIterations = value);
|
||||
CfgVar(CVars.SleepAllowed, value => _islandCfg.SleepAllowed = value);
|
||||
CfgVar(CVars.TimeToSleep, value => _islandCfg.TimeToSleep = value);
|
||||
CfgVar(CVars.VelocityThreshold, value => _islandCfg.VelocityThreshold = value);
|
||||
CfgVar(CVars.Baumgarte, value => _islandCfg.Baumgarte = value);
|
||||
CfgVar(CVars.LinearSlop, value => _islandCfg.LinearSlop = value);
|
||||
CfgVar(CVars.MaxLinearCorrection, value => _islandCfg.MaxLinearCorrection = value);
|
||||
CfgVar(CVars.MaxLinVelocity, value =>
|
||||
{
|
||||
_maxLinearVelocityRaw = value;
|
||||
UpdateMaxLinearVelocity();
|
||||
});
|
||||
CfgVar(CVars.MaxAngVelocity, value =>
|
||||
{
|
||||
_maxAngularVelocityRaw = value;
|
||||
UpdateMaxAngularVelocity();
|
||||
});
|
||||
CfgVar(CVars.NetTickrate, value =>
|
||||
{
|
||||
_tickRate = value;
|
||||
UpdateMaxLinearVelocity();
|
||||
UpdateMaxAngularVelocity();
|
||||
});
|
||||
|
||||
void UpdateMaxLinearVelocity()
|
||||
{
|
||||
_islandCfg.MaxLinearVelocity = _maxLinearVelocityRaw / _tickRate;
|
||||
}
|
||||
|
||||
void UpdateMaxAngularVelocity()
|
||||
{
|
||||
_islandCfg.MaxAngularVelocity = (MathF.PI * 2 * _maxAngularVelocityRaw) / _tickRate;
|
||||
}
|
||||
|
||||
void CfgVar<T>(CVarDef<T> cVar, Action<T> callback) where T : notnull
|
||||
{
|
||||
_cfg.OnValueChanged(cVar, value =>
|
||||
{
|
||||
callback(value);
|
||||
UpdateIslandCfg();
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIslandCfg()
|
||||
{
|
||||
// OOP bad
|
||||
|
||||
_loneIsland.LoadConfig(_islandCfg);
|
||||
|
||||
foreach (var island in _allocatedIslands)
|
||||
{
|
||||
island.LoadConfig(_islandCfg);
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializePools()
|
||||
{
|
||||
_loneIsland.Clear();
|
||||
@@ -159,6 +235,7 @@ namespace Robust.Shared.Physics
|
||||
{
|
||||
island = new PhysicsIsland();
|
||||
island.Initialize();
|
||||
island.LoadConfig(_islandCfg);
|
||||
_allocatedIslands.Add(island);
|
||||
}
|
||||
|
||||
|
||||
1122
Robust.Shared/Physics/SharedBroadphaseSystem.cs
Normal file
1122
Robust.Shared/Physics/SharedBroadphaseSystem.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -214,9 +214,9 @@ namespace Robust.Shared.Serialization
|
||||
/// <seealso cref="OnClientCompleteHandshake"/>
|
||||
private void NetworkInitialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgMapStrServerHandshake>(HandleServerHandshake, NetMessageAccept.Client);
|
||||
_net.RegisterNetMessage<MsgMapStrClientHandshake>(HandleClientHandshake, NetMessageAccept.Server);
|
||||
_net.RegisterNetMessage<MsgMapStrStrings>(HandleStringsMessage, NetMessageAccept.Client);
|
||||
_net.RegisterNetMessage<MsgMapStrServerHandshake>(HandleServerHandshake, NetMessageAccept.Client | NetMessageAccept.Handshake);
|
||||
_net.RegisterNetMessage<MsgMapStrClientHandshake>(HandleClientHandshake, NetMessageAccept.Server | NetMessageAccept.Handshake);
|
||||
_net.RegisterNetMessage<MsgMapStrStrings>(HandleStringsMessage, NetMessageAccept.Client | NetMessageAccept.Handshake);
|
||||
|
||||
_net.Disconnect += NetOnDisconnect;
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Robust.Shared.Utility
|
||||
{
|
||||
public static class ProcessExt
|
||||
{
|
||||
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
void ProcessExited(object? sender, EventArgs e)
|
||||
{
|
||||
tcs.TrySetResult(true);
|
||||
}
|
||||
|
||||
process.EnableRaisingEvents = true;
|
||||
process.Exited += ProcessExited;
|
||||
|
||||
try
|
||||
{
|
||||
if (process.HasExited)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
|
||||
{
|
||||
await tcs.Task;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
process.Exited -= ProcessExited;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Client.GameObjects.Components
|
||||
@@ -19,10 +20,6 @@ namespace Robust.UnitTesting.Client.GameObjects.Components
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterEntitySystems(factory =>
|
||||
{
|
||||
factory.LoadExtraSystemType<SharedTransformSystem>();
|
||||
})
|
||||
.RegisterComponents(factory =>
|
||||
{
|
||||
factory.RegisterClass<ContainerManagerComponent>();
|
||||
|
||||
@@ -2,6 +2,7 @@ using NUnit.Framework;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.UnitTesting.Client.UserInterface.Controls
|
||||
{
|
||||
@@ -15,7 +16,11 @@ namespace Robust.UnitTesting.Client.UserInterface.Controls
|
||||
public void TestLayoutBasic()
|
||||
{
|
||||
var root = new LayoutContainer();
|
||||
var boxContainer = new VBoxContainer {MinSize = (50, 60)};
|
||||
var boxContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = (50, 60)
|
||||
};
|
||||
var control1 = new Control {MinSize = (20, 20)};
|
||||
var control2 = new Control {MinSize = (30, 30)};
|
||||
|
||||
@@ -37,7 +42,11 @@ namespace Robust.UnitTesting.Client.UserInterface.Controls
|
||||
public void TestLayoutExpand()
|
||||
{
|
||||
var root = new LayoutContainer();
|
||||
var boxContainer = new VBoxContainer {MinSize = (50, 60)};
|
||||
var boxContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = (50, 60)
|
||||
};
|
||||
var control1 = new Control
|
||||
{
|
||||
VerticalExpand = true
|
||||
@@ -61,7 +70,10 @@ namespace Robust.UnitTesting.Client.UserInterface.Controls
|
||||
[Test]
|
||||
public void TestCalcMinSize()
|
||||
{
|
||||
var boxContainer = new VBoxContainer();
|
||||
var boxContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
var control1 = new Control
|
||||
{
|
||||
MinSize = (50, 30)
|
||||
@@ -80,7 +92,11 @@ namespace Robust.UnitTesting.Client.UserInterface.Controls
|
||||
public void TestTwoExpand()
|
||||
{
|
||||
var root = new LayoutContainer();
|
||||
var boxContainer = new VBoxContainer {MinSize = (30, 80)};
|
||||
var boxContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = (30, 80)
|
||||
};
|
||||
var control1 = new Control
|
||||
{
|
||||
VerticalExpand = true,
|
||||
|
||||
@@ -387,6 +387,8 @@ namespace Robust.UnitTesting
|
||||
|
||||
public bool IsConnected { get; set; }
|
||||
public NetUserData UserData { get; }
|
||||
// integration tests don't simulate serializer handshake so this is always true.
|
||||
public bool IsHandshakeComplete => true;
|
||||
|
||||
// TODO: Should this port value make sense?
|
||||
public IPEndPoint RemoteEndPoint { get; } = new(IPAddress.Loopback, 1212);
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
@@ -53,7 +54,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
var mock = new Mock<IEntitySystemManager>();
|
||||
var broady = new BroadPhaseSystem();
|
||||
var physics = new PhysicsSystem();
|
||||
mock.Setup(m => m.GetEntitySystem<SharedBroadPhaseSystem>()).Returns(broady);
|
||||
mock.Setup(m => m.GetEntitySystem<SharedBroadphaseSystem>()).Returns(broady);
|
||||
mock.Setup(m => m.GetEntitySystem<SharedPhysicsSystem>()).Returns(physics);
|
||||
|
||||
IoCManager.RegisterInstance<IEntitySystemManager>(mock.Object, true);
|
||||
|
||||
@@ -67,8 +67,10 @@ entities:
|
||||
var mock = new Mock<IEntitySystemManager>();
|
||||
var broady = new BroadPhaseSystem();
|
||||
var physics = new PhysicsSystem();
|
||||
mock.Setup(m => m.GetEntitySystem<SharedBroadPhaseSystem>()).Returns(broady);
|
||||
var gridFixtures = new GridFixtureSystem();
|
||||
mock.Setup(m => m.GetEntitySystem<SharedBroadphaseSystem>()).Returns(broady);
|
||||
mock.Setup(m => m.GetEntitySystem<SharedPhysicsSystem>()).Returns(physics);
|
||||
mock.Setup(m => m.GetEntitySystem<GridFixtureSystem>()).Returns(gridFixtures);
|
||||
|
||||
IoCManager.RegisterInstance<IEntitySystemManager>(mock.Object, true);
|
||||
//IoCManager.RegisterInstance<ICustomFormatManager>(mockFormat.Object, true);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user