Compare commits

...

76 Commits

Author SHA1 Message Date
metalgearsloth
8fea42ff9a Fix GetTilesIntersecting for circles (#1912) 2021-07-30 10:01:14 +02:00
metalgearsloth
86d20a0ef1 Bump up light radius
The ones around the singularity are 32 radius for whatever reason
2021-07-30 17:22:23 +10:00
metalgearsloth
09012ea4ff Change velocity constraints to structs (#1906) 2021-07-29 22:14:01 +02:00
metalgearsloth
e68297eb93 Multi-thread physics contacts (#1897) 2021-07-29 22:13:42 +02:00
metalgearsloth
c1a2e23ce2 Fix mission-critical typo 2021-07-30 00:08:17 +10:00
metalgearsloth
f208f6bfa9 Use ref var for manifold points (#1911) 2021-07-29 15:38:12 +02:00
Pieter-Jan Briers
ae526e2e10 FixedArray helpers.
May your stack allocations be fast and your GCs infrequent.
2021-07-29 15:37:43 +02:00
metalgearsloth
25549869b1 Use more values by reference in Collision (#1910)
Also a sneaky Span<ClipVertex> I forgot
2021-07-29 14:49:50 +02:00
metalgearsloth
f71e81d204 Use circle.Position for collisions. (#1909) 2021-07-29 14:49:18 +02:00
metalgearsloth
e67812fdb4 Add shutdowns to VirtualControllers (#1887) 2021-07-29 13:31:34 +02:00
Vera Aguilera Puerto
aa44b1cb8a RaiseLocalEvent non-generic overload that checks for GetType() (#1907)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2021-07-29 13:30:29 +02:00
Vera Aguilera Puerto
8ec75be244 Minor ViewSubscriber cleanup, remove all mentions of PVS eye 2021-07-29 13:29:54 +02:00
Vera Aguilera Puerto
48746b7bd3 Adds ViewSubscriberComponent and ViewSubscriberSystem (#1900) 2021-07-29 13:16:25 +02:00
metalgearsloth
a9791d2033 Fix potential SetTile crash when it unanchors entities (#1903) 2021-07-29 13:15:50 +02:00
Vera Aguilera Puerto
709f1f4284 EntityLookup now uses ILookupWorldBox2Component for getting entities' world AABB. 2021-07-29 13:01:24 +02:00
metalgearsloth
907094a5c8 Minor solver optimisation for invmass 2021-07-29 18:57:35 +10:00
Pieter-Jan Briers
a35a5e1645 Add EntitySystem.Subs property to aid helper methods. 2021-07-29 01:51:51 +02:00
Pieter-Jan Briers
ad8a59a72f Revert "Adds SetButtonDisabledRecursive() method to Control" (#1902)
This reverts commit e93c0f76a9.
2021-07-27 21:44:31 +02:00
Visne
e93c0f76a9 Adds SetButtonDisabledRecursive() method to Control (#1901) 2021-07-27 20:08:24 +02:00
metalgearsloth
7bac32d18e Don't use DeferMoveEvent if the position is NaN (#1896)
The issue is that currently moving around means any non-anchored entities have their positions updated to / from NaN. As you can imagine this is a performance nightmare for anything subscribing to it, especially considering it leads to the broadphase getting desynced for physics.

Realistically we need one of the alternatives Acruid has laid out because flooding the eventbus with NaNs will kill performance.
2021-07-28 00:28:07 +10:00
Pieter-Jan Briers
b6b1d46892 Ignore "CON" in DebugConsoleLogHandler. 2021-07-27 09:38:08 +02:00
Vera Aguilera Puerto
6f0bc3822e InputComponent is now public. 2021-07-27 08:51:02 +02:00
Vera Aguilera Puerto
b7c8452285 Use EntitySystem dependencies in a few places that cached systems before. 2021-07-26 12:57:39 +02:00
Paul Ritter
8c1e075c91 EntitySystem DependencyCollection & EntitySystem dependencies (#1890)
* started work

* tests

* namespace

* working

* thonk

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: Vera Aguilera Puerto <gradientvera@outlook.com>
2021-07-26 12:10:03 +02:00
metalgearsloth
b340e40c99 Cache physics transforms internally (#1873)
* Cache physics transforms internally

3 main points to cache them for:
1. Contact updates
2. Physics Islands solving
3. GetWorldManifold (though this is primarily for ss14).

Whenever multi-threading is done it will need adjustments to make sure no race conditions on accessing the transforms dictionary

* Also cache position and angle for GetWorldAABB

* Fix boogs

* woops
2021-07-26 18:23:10 +10:00
metalgearsloth
c4b124f48d Cleanup physics contacts a bit (#1895)
Removed a lot of the listener comments as we use the eventbus instead in this house.
2021-07-25 23:40:45 +10:00
metalgearsloth
7efae8fbc1 Remove IStartCollide (#1875)
* Remove IStartCollide

Needs several content PRs to be merged as well.

* Feex
2021-07-25 23:06:34 +10:00
Pieter-Jan Briers
7feeeb2f6f OOps 2021-07-25 14:50:14 +02:00
mirrorcult
f90462cf82 Improve RSI error messages (#1894) 2021-07-25 12:06:36 +02:00
metalgearsloth
b19ae9e69e Add AABB VVs for debugging 2021-07-25 19:04:25 +10:00
metalgearsloth
2132d6cbae Comment out the contactmanager crash safeguard for now 2021-07-24 13:50:27 +10:00
Ygg01
d2d6f9d08e Update Linguini to latest to fix escaping text literals (#1880) 2021-07-23 17:43:14 +02:00
metalgearsloth
4b58fcbff2 Fix anchoring on moved grid (#1885) 2021-07-23 10:16:19 +02:00
metalgearsloth
f83f6a8cd6 Remove manifolds from collision events
Nothing uses em anymore.
2021-07-23 13:06:24 +10:00
metalgearsloth
dfd7711506 Stop EndCollision from throwing exceptions
Won't fix the underlying problem but will log it and handle it more gracefully at least until I can reproduce it.
2021-07-23 13:03:54 +10:00
Pieter-Jan Briers
78f9d92c07 Add exception tolerance to DispatchEntityNetworkMessage. 2021-07-22 23:27:39 +02:00
Paul
3a86c827ea made xamlil errors show up in msbuild 2021-07-22 20:01:15 +02:00
metalgearsloth
325f25c547 Fix occluders for moved grid (#1878)
* Refactor occluders

* Copy pasting

* Reduce bounds

* Clear system updates on shutdown
2021-07-22 13:07:45 +10:00
Pieter-Jan Briers
be57b5d20b Add AnyCommandExecuted callback to IConsoleHost.
Intended use case here is for content to listen to ConCmds for AFK detection.
2021-07-21 21:32:32 +02:00
Pieter-Jan Briers
7124d86f94 Allow localization to pass through TimeSpan values. 2021-07-21 18:58:43 +02:00
Pieter-Jan Briers
229380a71d Add TimeSpan read/write helpers to NetMessageExt 2021-07-21 16:25:15 +02:00
metalgearsloth
e9eb536df5 Don't get fixture pairs if they're deleted 2021-07-21 23:08:10 +10:00
metalgearsloth
22297ef6d8 Out of the way System.Numerics 2021-07-21 21:14:41 +10:00
metalgearsloth
7f2e433087 Broadphase refactor (#1848)
* Broadphase refactor

* Stuff

* Working

* TODO

* Changes

* Which fucking madman came up with this shit

* Known gud state

* Kinda shitcodey but it works so fuck it doin it live

* Shuttle jankiness

* Refactor entitylookups to be 30% more based

* Refactor gucci

* Done?

* Fix most tests

* nothing suss

* Vera single-handedly saving shuttles

* fex

* Fix renderingtreecomp for relativity

* Fix IEntityLookup

* Fixes

* Testing jank please revert some of it before merging

* Fix the remaining bugs

* Fix crash

* Fix grid physics initialization

* Shuttle collisions working

* Fixes

* Fixes

* Velocity on transfers

* Velocity on parent change

* Fixes

* world angular velocity too

* Slightly faster map velocities

* showbb revert

* Sketch grids kinda workin

* Fix PVS contact crash

* Cleanup gridfixture updates 0.1%

* Grid fixtures

* AAAAAAAAAAAAAAA

* a

* termp

* Fixes

* Test reversion reversion?

* Tests

* Fix Equals

* Slight box2i cleanup

* Better initializer

* Fix merge issues

* Shuttles go BRRT

* Remove MoveProxy from DynamicTreeBroadphase

* Optimise a shit tonne

* Approx

* fix showbb

* clean

* Slightly more optimised

* My almonds are activating

* Contact transform caching

* Avoid duplicates

* Typo

* Logger

* Jitter 1% better

* Okay shit maybe that's not it.

* Contact fixes

* Move check thingy up front

* Revert some jank caching

* Contact filtering

* Fix master merge

* Test fixes

* Re-fix MapLoaderTest

* Use proxies instead

* uhh wtf?

* Woops

* Fix collisions

* Fix grid fixture generation

* Fix deserializing

* Tile window fix

* Cleanup broadphase

* Bit more cleanup

Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
2021-07-21 21:12:58 +10:00
Pieter-Jan Briers
18c32a0258 Don't ServerSendToAll to channels without completed handshake. 2021-07-21 03:06:32 +02:00
Pieter-Jan Briers
72314a102d EventBus improvements
Can do ordered session event subscription.

Can subscribe to "All" in one call, to reduce shared boilerplate.
2021-07-21 00:56:42 +02:00
Pieter-Jan Briers
719ea26a31 IConfigurationManager.UnsubValueChanged. 2021-07-20 17:26:52 +02:00
metalgearsloth
5cb8fe1897 End IEndCollide (#1874) 2021-07-20 17:01:30 +02:00
metalgearsloth
f35a52fc24 Change KinematicControllerCollision to use WorldNormal instead (#1872)
Content cares about the WorldNormal and not the LocalNormal so this fixes pushing bugs.
2021-07-19 10:17:39 +02:00
metalgearsloth
6bdb0cef47 Fix IMapGrid WorldToLocal + LocalToWorld (#1871) 2021-07-19 10:17:15 +02:00
Pieter-Jan Briers
fe3c9fe28f Fix late nwVar warning on connect. 2021-07-19 10:16:24 +02:00
Pieter-Jan Briers
6085671f22 Fix memory leak with physics islands and contact solvers.
They were doing config OnValueChanged for every instance and, as such, never getting released.
2021-07-19 10:06:20 +02:00
Visne
a2398da324 Replace most VBox/HBoxContainers with BoxContainers (#1867) 2021-07-18 18:42:08 +02:00
Pieter-Jan Briers
b27304cc58 Add System.Index and System.Range to sandbox whitelist. 2021-07-17 23:51:57 +02:00
Swept
3bf851a6cf FPS Counter no longer shows a decimal 2021-07-17 16:46:09 +00:00
Pieter-Jan Briers
cef92efd0f NetManager now enforces that only specific messages can be sent before serializer handshake completes. 2021-07-17 14:37:40 +02:00
Pieter-Jan Briers
c5961a5ab1 Fix possible race condition in net var handling and improve logging. 2021-07-17 14:37:35 +02:00
Pieter-Jan Briers
8ddd92993d Don't do initial net var stuff on server. 2021-07-17 02:03:37 +02:00
Pieter-Jan Briers
da253a5f34 Log user ID correctly on connection approval 2021-07-17 02:02:59 +02:00
Pieter-Jan Briers
ca9400a1ff Don't log encryption secrets on auth handshake. 2021-07-17 01:24:39 +02:00
Pieter-Jan Briers
f232195ceb Fix audio log warning not using interpolated string correctly. 2021-07-17 01:17:23 +02:00
Pieter-Jan Briers
b54a803519 Add ToString to NetChannel.
Some things already try to log like this so let's just go with it.
2021-07-17 01:16:47 +02:00
Vera Aguilera Puerto
a0d3d2108f Use ResourcePath instead of Path.Join 2021-07-16 21:48:19 +02:00
Vera Aguilera Puerto
977e4a017b Fixes tile window hardcoding tile sprites. 2021-07-16 08:21:58 +02:00
Swept
2d8b159016 Updates the dumb shit stupid path for tile window 2021-07-16 05:38:19 +00:00
Visne
9caa0dde4b Remove unused IEntityManager parameter from EntityCoordinates.FromMap() (#1866) 2021-07-15 18:51:11 +02:00
Pieter-Jan Briers
7a5a8c5eb1 Remove unused Process.WaitForExitAsync helper.
.NET 5 has its own implementation so we use that now.
2021-07-15 10:07:52 +02:00
Swept
95ba58f0a4 Fixes SpriteComponent error reporting 2021-07-14 22:41:51 +00:00
metalgearsloth
f780f04784 Deprecate PhysShapeGrid (#1862)
* Grid fixtures

* termp

* Fixes

* Test reversion reversion?

* Tests

* Fix Equals

* Slight box2i cleanup

* Better initializer

* Also add static grid assert
2021-07-14 18:47:17 +10:00
Pieter-Jan Briers
85782bda92 Make Split- and BoxContainer orientation adjustable, obsolete subtypes. 2021-07-13 17:21:21 +02:00
metalgearsloth
14a01df5b1 Add physics stacking tests (#1865)
Ported from content; only reason for PR is to make sure remote tests are also gucci.
2021-07-13 18:43:09 +10:00
metalgearsloth
644da60bfc ContactCount VV (#1864) 2021-07-13 14:11:27 +10:00
Pieter-Jan Briers
8c83999ad2 Make window creation synchronous.
The async code path isn't really async and if we ever make it so (by using a non shit rendering API) I'll just make it implicitly asynchronous.
2021-07-13 03:39:38 +02:00
Pieter-Jan Briers
24b9fc9eec Add ImageSharp to script console assemblies. 2021-07-12 17:26:54 +02:00
metalgearsloth
ba40185179 Make entitylookup grid-relative (#1849)
* Refactor entitylookups to be 30% more based

* Refactor gucci

* Done?

* Fix most tests

* nothing suss

* Vera single-handedly saving shuttles

* fex

* Copy lookup from shuttles

* sys

* comp recursion
2021-07-12 13:39:02 +02:00
Vera Aguilera Puerto
8b013cb424 Fix engine integration tests not generating Net IDs. 2021-07-12 10:42:38 +02:00
178 changed files with 4809 additions and 2986 deletions

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Net;
using Robust.Client.Debugging;
using Robust.Client.GameObjects;

View File

@@ -25,6 +25,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
@@ -76,6 +77,7 @@ namespace Robust.Client
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IMidiManager, MidiManager>();
IoCManager.Register<IAuthManager, AuthManager>();
IoCManager.Register<IPhysicsManager, PhysicsManager>();
switch (mode)
{
case GameController.DisplayMode.Headless:

View File

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

View File

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

View File

@@ -13,10 +13,11 @@ using Microsoft.CodeAnalysis.Text;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using Color = Robust.Shared.Maths.Color;
#nullable enable
@@ -116,7 +117,7 @@ namespace Robust.Client.Console
}
else
{
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager).AddReferences(typeof(Image).Assembly);
newScript = CSharpScript.Create(code, options, typeof(ScriptGlobals));
}

View File

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

View File

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

View File

@@ -28,6 +28,7 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
@@ -40,6 +41,8 @@ namespace Robust.Client.Debugging
* Used for debugging shapes, controllers, joints, contacts
*/
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
private const int MaxContactPoints = 2048;
internal int PointCount;
@@ -79,8 +82,7 @@ namespace Robust.Client.Debugging
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
Span<Vector2> points = stackalloc Vector2[2];
Vector2 normal;
contact.GetWorldManifold(out normal, points);
contact.GetWorldManifold(_physicsManager, out var normal, points);
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
{

View File

@@ -25,6 +25,7 @@ namespace Robust.Client.GameObjects
RegisterClass<InputComponent>();
RegisterClass<SpriteComponent>();
RegisterClass<ClientOccluderComponent>();
RegisterClass<OccluderTreeComponent>();
RegisterClass<EyeComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<AppearanceTestComponent>();

View File

@@ -1,7 +1,5 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
@@ -10,7 +8,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Defines data fields used in the <see cref="InputSystem"/>.
/// </summary>
class InputComponent : Component
public class InputComponent : Component
{
/// <inheritdoc />
public override string Name => "Input";

View File

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

View File

@@ -15,29 +15,43 @@ namespace Robust.Client.GameObjects
private static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
return new Box2(worldPos, worldPos);
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return new Box2(pos, pos);
}
private static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return Box2.CenteredAround(pos, (boxSize, boxSize));
}
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
return new Box2(worldPos.Value, worldPos.Value);
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return new Box2(pos, pos);
}
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos.Value, (boxSize, boxSize));
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return Box2.CenteredAround(pos, (boxSize, boxSize));
}
}
}

View File

@@ -23,8 +23,7 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IClydeAudio _clyde = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadPhaseSystem = default!;
private readonly List<PlayingStream> _playingClydeStreams = new();
@@ -38,7 +37,6 @@ namespace Robust.Client.GameObjects
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
@@ -301,7 +299,7 @@ namespace Robust.Client.GameObjects
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
{
source.Dispose();
Logger.Warning("Can't play positional audio \"{stream.Name}\", can't set position.");
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
return null;
}

View File

@@ -41,22 +41,12 @@ namespace Robust.Client.GameObjects
{
if (mapId == MapId.Nullspace) yield break;
var enclosed = false;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
// if we're enclosed then we know no other grids relevant + don't need the map's rendertree
if (grid.WorldBounds.Encloses(in worldAABB))
{
enclosed = true;
break;
}
}
if (!enclosed)
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
}
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
@@ -259,9 +249,8 @@ namespace Robust.Client.GameObjects
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
}
private RenderingTreeComponent? GetRenderTree(IEntity entity)
internal static RenderingTreeComponent? GetRenderTree(IEntity entity)
{
if (entity.Transform.MapID == MapId.Nullspace ||
entity.HasComponent<RenderingTreeComponent>()) return null;
@@ -302,8 +291,7 @@ namespace Robust.Client.GameObjects
continue;
}
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos).Translated(-treePos);
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos);
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
@@ -348,7 +336,7 @@ namespace Robust.Client.GameObjects
}
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos).Translated(-treePos);
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos);
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)

View File

@@ -15,15 +15,14 @@ namespace Robust.Client.GameObjects
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
private RenderingTreeSystem _treeSystem = default!;
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
public override void Initialize()
{
base.Initialize();
_treeSystem = Get<RenderingTreeSystem>();
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
}

View File

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

View File

@@ -210,11 +210,11 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.WindowRequestAttention(_windowing.MainWindow!);
}
public async Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
{
DebugTools.AssertNotNull(_windowing);
return await _windowing!.WindowCreate(parameters);
return _windowing!.WindowCreate(parameters);
}
private void DoDestroyWindow(WindowReg reg)

View File

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

View File

@@ -210,7 +210,7 @@ namespace Robust.Client.Graphics.Clyde
yield break;
}
public Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
{
var window = new DummyWindow(CreateRenderTarget((123, 123), default))
{
@@ -218,7 +218,7 @@ namespace Robust.Client.Graphics.Clyde
};
_windows.Add(window);
return Task.FromResult<IClydeWindow>(window);
return window;
}
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)

View File

@@ -22,8 +22,7 @@ namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
// Wait for it.
private sealed partial class GlfwWindowingImpl
private unsafe sealed partial class GlfwWindowingImpl
{
private readonly List<GlfwWindowReg> _windows = new();
@@ -36,45 +35,23 @@ namespace Robust.Client.Graphics.Clyde
private int _nextWindowId = 1;
private static bool _eglLoaded;
public async Task<WindowHandle> WindowCreate(WindowCreateParameters parameters)
public WindowHandle WindowCreate(WindowCreateParameters parameters)
{
// tfw await not allowed in unsafe contexts
// GL APIs don't take kindly to making a new window without unbinding the main context. Great.
// Leaving code for async path in, in case it works on like GLX.
var unbindContextAndBlock = true;
DebugTools.AssertNotNull(_mainWindow);
Task<GlfwWindowCreateResult> task;
unsafe
{
if (unbindContextAndBlock)
GLFW.MakeContextCurrent(null);
GLFW.MakeContextCurrent(null);
task = SharedWindowCreate(
_clyde._chosenRenderer,
parameters,
_mainWindow!.GlfwWindow);
}
var task = SharedWindowCreate(
_clyde._chosenRenderer,
parameters,
_mainWindow!.GlfwWindow);
if (unbindContextAndBlock)
{
unsafe
{
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
if (unbindContextAndBlock)
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
}
}
else
{
await task;
}
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
var (reg, error) = await task;
var (reg, error) = task.Result;
if (reg == null)
{
@@ -85,18 +62,11 @@ namespace Robust.Client.Graphics.Clyde
_clyde.CreateWindowRenderTexture(reg);
_clyde.InitWindowBlitThread(reg);
unsafe
{
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
}
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
return reg.Handle;
}
}
// Yes, you read that right.
private sealed unsafe partial class GlfwWindowingImpl
{
public bool TryInitMainWindow(Renderer renderer, [NotNullWhen(false)] out string? error)
{
var width = _cfg.GetCVar(CVars.DisplayWidth);
@@ -167,6 +137,21 @@ namespace Robust.Client.Graphics.Clyde
WindowCreateParameters parameters,
Window* share)
{
//
// IF YOU'RE WONDERING WHY THIS IS TASK-BASED:
// I originally wanted this to be async so we could avoid blocking the main thread
// while the OS takes its stupid 100~ms just to initialize a fucking GL context.
// This doesn't *work* because
// we have to release the GL context while the shared context is being created.
// (at least on WGL, I didn't test other platforms and I don't care to.)
// Not worth it to avoid a main thread blockage by allowing Clyde to temporarily release the GL context,
// because rendering would be locked up *anyways*.
//
// Basically what I'm saying is that everything about OpenGL is a fucking mistake
// and I should get on either Veldrid or Vulkan some time.
// Probably Veldrid tbh.
//
// Yes we ping-pong this TCS through the window thread and back, deal with it.
var tcs = new TaskCompletionSource<GlfwWindowCreateResult>();
SendCmd(new CmdWinCreate(
@@ -525,7 +510,6 @@ namespace Robust.Client.Graphics.Clyde
}
return window;
}

View File

@@ -38,7 +38,7 @@ namespace Robust.Client.Graphics.Clyde
void WindowRequestAttention(WindowReg window);
void WindowSwapBuffers(WindowReg window);
uint? WindowGetX11Id(WindowReg window);
Task<WindowHandle> WindowCreate(WindowCreateParameters parameters);
WindowHandle WindowCreate(WindowCreateParameters parameters);
void WindowDestroy(WindowReg reg);
string KeyGetName(Keyboard.Key key);

View File

@@ -130,6 +130,6 @@ namespace Robust.Client.Graphics
IEnumerable<IClydeMonitor> EnumerateMonitors();
Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters);
IClydeWindow CreateWindow(WindowCreateParameters parameters);
}
}

View File

@@ -20,6 +20,9 @@ namespace Robust.Client.Log
public void Log(string sawmillName, LogEvent message)
{
if (sawmillName == "CON")
return;
var formatted = new FormattedMessage(8);
var robustLevel = message.Level.ToRobust();
formatted.PushColor(Color.DarkGray);

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Client.Graphics;
namespace Robust.Client.Placement

View File

@@ -102,7 +102,9 @@ namespace Robust.Client.ResourceManagement
if (reg.Src.Width % frameSize.X != 0 || reg.Src.Height % frameSize.Y != 0)
{
throw new RSILoadException("State image size is not a multiple of the icon size.");
var regDims = $"{reg.Src.Width}x{reg.Src.Height}";
var iconDims = $"{frameSize.X}x{frameSize.Y}";
throw new RSILoadException($"State '{stateObject.StateId}' image size ({regDims}) is not a multiple of the icon size ({iconDims}).");
}
// Load all frames into a list so we can operate on it more sanely.
@@ -250,7 +252,7 @@ namespace Robust.Client.ResourceManagement
}
if (manifestJson == null)
throw new RSILoadException("Manifest JSON was null!");
throw new RSILoadException($"Manifest JSON failed to deserialize!");
var size = manifestJson.Size;
var states = new StateMetadata[manifestJson.States.Length];
@@ -270,7 +272,7 @@ namespace Robust.Client.ResourceManagement
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
_ => throw new RSILoadException($"Invalid direction for state '{stateName}': {dirValue}. Expected 1, 4 or 8")
};
}
else
@@ -291,7 +293,7 @@ namespace Robust.Client.ResourceManagement
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
$"Direction frames list count ({dirValue}) does not match amount of delays specified ({delays.Length}) for state '{stateName}'.");
}
for (var i = 0; i < delays.Length; i++)

View File

@@ -6,20 +6,31 @@ namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// A container that lays out its children sequentially.
/// Use <see cref="VBoxContainer"/> or <see cref="HBoxContainer"/> for an implementation.
/// </summary>
public abstract class BoxContainer : Container
public class BoxContainer : Container
{
private LayoutOrientation _orientation;
public const string StylePropertySeparation = "separation";
private const int DefaultSeparation = 0;
private protected abstract bool Vertical { get; }
/// <summary>
/// Specifies "where" the controls should be laid out.
/// </summary>
public AlignMode Align { get; set; }
private bool Vertical => Orientation == LayoutOrientation.Vertical;
public LayoutOrientation Orientation
{
get => _orientation;
set
{
_orientation = value;
InvalidateMeasure();
}
}
private int ActualSeparation
{
get
@@ -237,5 +248,11 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
End = 2
}
public enum LayoutOrientation : byte
{
Horizontal,
Vertical
}
}
}

View File

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

View File

@@ -1,10 +1,16 @@
namespace Robust.Client.UserInterface.Controls
using System;
namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// Container that lays its children out horizontally: from left to right.
/// </summary>
[Obsolete("Use BoxContainer and set Orientation instead")]
public class HBoxContainer : BoxContainer
{
private protected override bool Vertical => false;
public HBoxContainer()
{
Orientation = LayoutOrientation.Horizontal;
}
}
}

View File

@@ -1,7 +1,13 @@
using System;
namespace Robust.Client.UserInterface.Controls
{
[Obsolete("Use SplitContainer directly and set Orientation")]
public class HSplitContainer : SplitContainer
{
private protected sealed override bool Vertical => false;
public HSplitContainer()
{
Orientation = SplitOrientation.Horizontal;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Maths;
namespace Robust.Client.UserInterface.Controls
{
public abstract class SplitContainer : Container
public class SplitContainer : Container
{
/// <summary>
/// Defines how user-initiated moving of the split should work. See documentation
@@ -22,11 +22,10 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public float SplitEdgeSeparation { get; set; }
private protected abstract bool Vertical { get; }
private float _splitCenter;
private SplitState _splitState;
private bool _dragging;
private SplitOrientation _orientation;
// min / max x and y extents in relative virtual pixels of where the split can go regardless
// of anything else.
@@ -35,6 +34,18 @@ namespace Robust.Client.UserInterface.Controls
private float SplitMax =>
Vertical ? Height - (SplitWidth + SplitEdgeSeparation) : Width - (SplitWidth + SplitEdgeSeparation);
private bool Vertical => Orientation == SplitOrientation.Vertical;
public SplitOrientation Orientation
{
get => _orientation;
set
{
_orientation = value;
InvalidateMeasure();
}
}
public SplitContainer()
{
MouseFilter = MouseFilterMode.Stop;
@@ -264,5 +275,11 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
Manual = 1
}
public enum SplitOrientation : byte
{
Horizontal,
Vertical
}
}
}

View File

@@ -1,10 +1,16 @@
namespace Robust.Client.UserInterface.Controls
using System;
namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// Container that lays its children out vertically: from top to bottom.
/// </summary>
[Obsolete("Use BoxContainer and set Orientation instead")]
public class VBoxContainer : BoxContainer
{
private protected override bool Vertical => true;
public VBoxContainer()
{
Orientation = LayoutOrientation.Vertical;
}
}
}

View File

@@ -1,7 +1,13 @@
using System;
namespace Robust.Client.UserInterface.Controls
{
[Obsolete("Use SplitContainer directly and set Orientation")]
public class VSplitContainer : SplitContainer
{
private protected sealed override bool Vertical => true;
public VSplitContainer()
{
Orientation = SplitOrientation.Vertical;
}
}
}

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ namespace Robust.Client.UserInterface.CustomControls
}
var fps = _gameTiming.FramesPerSecondAvg;
Text = $"FPS: {fps:N1}";
Text = $"FPS: {fps:N0}";
}
}
}

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,7 @@ namespace Robust.Client.UserInterface
monitor = clyde.EnumerateMonitors().Single(m => m.Id == id);
}
var window = await clyde.CreateWindow(new WindowCreateParameters
var window = clyde.CreateWindow(new WindowCreateParameters
{
Maximized = true,
Title = "SS14 Debug Window",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,14 @@
using System.Collections.Generic;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
[RegisterComponent]
internal class ViewSubscriberComponent : Component
{
public override string Name => "ViewSubscriber";
internal readonly HashSet<IPlayerSession> SubscribedSessions = new();
}
}

View File

@@ -1,24 +1,22 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics;
namespace Robust.Server.GameObjects
{
[UsedImplicitly]
public class PhysicsSystem : SharedPhysicsSystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
public override void Initialize()
{
base.Initialize();
_mapManager.OnGridCreated += HandleGridCreated;
SubscribeLocalEvent<GridInitializeEvent>(HandleGridInit);
LoadMetricCVar();
_configurationManager.OnValueChanged(CVars.MetricsEnabled, _ => LoadMetricCVar());
}
@@ -28,19 +26,14 @@ namespace Robust.Server.GameObjects
MetricsEnabled = _configurationManager.GetCVar(CVars.MetricsEnabled);
}
public override void Shutdown()
private void HandleGridInit(GridInitializeEvent ev)
{
base.Shutdown();
_mapManager.OnGridCreated -= HandleGridCreated;
}
var guid = ev.EntityUid;
private void HandleGridCreated(MapId mapId, GridId gridId)
{
if (!EntityManager.TryGetEntity(_mapManager.GetGrid(gridId).GridEntityId, out var gridEntity)) return;
var grid = _mapManager.GetGrid(gridId);
var collideComp = gridEntity.AddComponent<PhysicsComponent>();
if (!EntityManager.TryGetEntity(guid, out var gridEntity)) return;
var collideComp = gridEntity.EnsureComponent<PhysicsComponent>();
collideComp.CanCollide = true;
collideComp.AddFixture(new Fixture(collideComp, new PhysShapeGrid(grid)) {CollisionMask = MapGridHelpers.CollisionGroup, CollisionLayer = MapGridHelpers.CollisionGroup});
collideComp.BodyType = BodyType.Static;
}
/// <inheritdoc />

View File

@@ -0,0 +1,92 @@
using Robust.Server.Player;
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
/// <summary>
/// Entity System that handles subscribing and unsubscribing to PVS views.
/// </summary>
public class ViewSubscriberSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ViewSubscriberComponent, ComponentShutdown>(OnViewSubscriberShutdown);
}
/// <summary>
/// Subscribes the session to get PVS updates from the point of view of the specified entity.
/// </summary>
public void AddViewSubscriber(EntityUid uid, IPlayerSession session)
{
// This will throw if you pass in an invalid uid.
var entity = EntityManager.GetEntity(uid);
// If the entity doesn't have the component, it will be added.
var viewSubscriber = entity.EnsureComponent<ViewSubscriberComponent>();
if (viewSubscriber.SubscribedSessions.Contains(session))
return; // Already subscribed, do nothing else.
viewSubscriber.SubscribedSessions.Add(session);
session.AddViewSubscription(uid);
RaiseLocalEvent(uid, new ViewSubscriberAddedEvent(entity, session));
}
/// <summary>
/// Unsubscribes the session from getting PVS updates from the point of view of the specified entity.
/// </summary>
public void RemoveViewSubscriber(EntityUid uid, IPlayerSession session)
{
if(!ComponentManager.TryGetComponent(uid, out ViewSubscriberComponent? viewSubscriber))
return; // Entity didn't have any subscriptions, do nothing.
if (!viewSubscriber.SubscribedSessions.Remove(session))
return; // Session wasn't subscribed, do nothing.
session.RemoveViewSubscription(uid);
RaiseLocalEvent(uid, new ViewSubscriberRemovedEvent(EntityManager.GetEntity(uid), session));
}
private void OnViewSubscriberShutdown(EntityUid uid, ViewSubscriberComponent component, ComponentShutdown _)
{
foreach (var session in component.SubscribedSessions)
{
session.RemoveViewSubscription(uid);
}
}
}
/// <summary>
/// Raised when a session subscribes to an entity's PVS view.
/// </summary>
public class ViewSubscriberAddedEvent : EntityEventArgs
{
public IEntity View { get; }
public IPlayerSession Subscriber { get; }
public ViewSubscriberAddedEvent(IEntity view, IPlayerSession subscriber)
{
View = view;
Subscriber = subscriber;
}
}
/// <summary>
/// Raised when a session is unsubscribed from an entity's PVS view.
/// Not raised when sessions are unsubscribed due to the component being removed.
/// </summary>
public class ViewSubscriberRemovedEvent : EntityEventArgs
{
public IEntity View { get; }
public IPlayerSession Subscriber { get; }
public ViewSubscriberRemovedEvent(IEntity view, IPlayerSession subscriber)
{
View = view;
Subscriber = subscriber;
}
}
}

View File

@@ -24,6 +24,7 @@ namespace Robust.Server.GameObjects
RegisterClass<CollisionWakeComponent>();
RegisterClass<ContainerManagerComponent>();
RegisterClass<OccluderComponent>();
RegisterClass<OccluderTreeComponent>();
RegisterClass<SpriteComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<SnapGridComponent>();

View File

@@ -224,20 +224,32 @@ namespace Robust.Server.GameObjects
}
}
switch (message.Type)
#if EXCEPTION_TOLERANCE
try
#endif
{
case EntityMessageType.ComponentMessage:
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message, player));
return;
switch (message.Type)
{
case EntityMessageType.ComponentMessage:
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message, player));
return;
case EntityMessageType.SystemMessage:
var msg = message.SystemMessage;
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(player), msg)!;
ReceivedSystemMessage?.Invoke(this, msg);
ReceivedSystemMessage?.Invoke(this, sessionMsg);
return;
case EntityMessageType.SystemMessage:
var msg = message.SystemMessage;
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
var sessionMsg =
Activator.CreateInstance(sessionType, new EntitySessionEventArgs(player), msg)!;
ReceivedSystemMessage?.Invoke(this, msg);
ReceivedSystemMessage?.Invoke(this, sessionMsg);
return;
}
}
#if EXCEPTION_TOLERANCE
catch (Exception e)
{
Logger.ErrorS("net.ent", $"Caught exception while dispatching {message.Type}: {e}");
}
#endif
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args)

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.ObjectPool;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -139,12 +140,15 @@ namespace Robust.Server.GameStates
if (session.Status != SessionStatus.InGame || session.AttachedEntityUid is null)
return viewers;
var query = _compMan.EntityQuery<ActorComponent>();
viewers.Add(session.AttachedEntityUid.Value);
foreach (var actorComp in query)
// This is awful, but we're not gonna add the list of view subscriptions to common session.
if (session is not IPlayerSession playerSession)
return viewers;
foreach (var uid in playerSession.ViewSubscriptions)
{
if (actorComp.PlayerSession == session)
viewers.Add(actorComp.Owner.Uid);
viewers.Add(uid);
}
return viewers;

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,183 @@
using System.Collections.Generic;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Server.Physics
{
/// <summary>
/// Handles generating fixtures for MapGrids.
/// </summary>
internal sealed class GridFixtureSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
// Is delaying fixture updates a good idea? IDEK. We definitely can't do them on every tile changed
// because if someone changes 50 tiles that will kill perf. We could probably just run it every Update
// (and at specific times due to race condition stuff).
// At any rate, cooldown given here if someone wants it. CD of 0 just runs it every tick.
private float _cooldown;
private float _accumulator;
private HashSet<MapChunk> _queuedChunks = new();
public override void Initialize()
{
base.Initialize();
UpdatesBefore.Add(typeof(PhysicsSystem));
SubscribeLocalEvent<RegenerateChunkCollisionEvent>(HandleCollisionRegenerate);
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.GridFixtureUpdateRate, value => _cooldown = value, true);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
_accumulator += frameTime;
if (_accumulator < _cooldown) return;
_accumulator -= _cooldown;
Process();
}
/// <summary>
/// Go through every dirty chunk and re-generate their fixtures.
/// </summary>
public void Process()
{
foreach (var chunk in _queuedChunks)
{
RegenerateCollision(chunk);
}
_queuedChunks.Clear();
}
/// <summary>
/// Queue the chunk to generate (if cooldown > 0) or immediately process it.
/// </summary>
/// <param name="ev"></param>
private void HandleCollisionRegenerate(RegenerateChunkCollisionEvent ev)
{
if (_cooldown <= 0f)
{
RegenerateCollision(ev.Chunk);
return;
}
_queuedChunks.Add(ev.Chunk);
}
private void RegenerateCollision(MapChunk chunk)
{
// Currently this is gonna be hella simple.
if (!_mapManager.TryGetGrid(chunk.GridId, out var grid) ||
!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt) ||
!gridEnt.TryGetComponent(out PhysicsComponent? physicsComponent)) return;
// TODO: Lots of stuff here etc etc, make changes to mapgridchunk.
var bounds = chunk.CalcLocalBounds();
// So something goes on with the chunk's internal bounds caching where if there's no data the bound is 0 or something?
if (bounds.IsEmpty()) return;
var origin = chunk.Indices * chunk.ChunkSize;
bounds = bounds.Translated(origin);
// 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(
new PolygonShape
{
Vertices = new List<Vector2>
{
bounds.BottomRight,
bounds.TopRight,
bounds.TopLeft,
bounds.BottomLeft,
}
},
MapGridHelpers.CollisionGroup,
MapGridHelpers.CollisionGroup,
true) {ID = $"grid_chunk-{chunk.Indices.X}-{chunk.Indices.Y}",
Body = physicsComponent};
// 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)
_broadphase.DestroyFixture(physicsComponent, oldFixture);
_broadphase.CreateFixture(physicsComponent, newFixture);
chunk.Fixture = newFixture;
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Uid,new GridFixtureChangeEvent {OldFixture = oldFixture, NewFixture = newFixture});
}
}
public sealed class GridFixtureChangeEvent : EntityEventArgs
{
public Fixture? OldFixture { get; init; }
public Fixture? NewFixture { get; init; }
}
}

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Players;
@@ -32,6 +34,8 @@ namespace Robust.Server.Player
void OnConnect();
void OnDisconnect();
IReadOnlySet<EntityUid> ViewSubscriptions { get; }
/// <summary>
/// Persistent data for this player.
/// </summary>
@@ -43,5 +47,17 @@ namespace Robust.Server.Player
/// and <see cref="DetachFromEntity"/> instead.
/// </summary>
internal void SetAttachedEntity(IEntity? entity);
/// <summary>
/// Internal method to add an entity Uid to <see cref="ViewSubscriptions"/>.
/// Do NOT use this outside of <see cref="ViewSubscriberSystem"/>.
/// </summary>
internal void AddViewSubscription(EntityUid eye);
/// <summary>
/// Internal method to remove an entity Uid from <see cref="ViewSubscriptions"/>.
/// Do NOT use this outside of <see cref="ViewSubscriberSystem"/>.
/// </summary>
internal void RemoveViewSubscription(EntityUid eye);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Robust.Server.GameObjects;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
@@ -35,6 +36,8 @@ namespace Robust.Server.Player
UpdatePlayerState();
}
private readonly HashSet<EntityUid> _viewSubscriptions = new();
[ViewVariables] public INetChannel ConnectedClient { get; }
[ViewVariables] public IEntity? AttachedEntity { get; set; }
@@ -104,6 +107,7 @@ namespace Robust.Server.Player
public NetUserId UserId { get; }
private readonly PlayerData _data;
[ViewVariables] public IPlayerData Data => _data;
/// <inheritdoc />
@@ -160,10 +164,13 @@ namespace Robust.Server.Player
{
Status = SessionStatus.Disconnected;
UnsubscribeAllViews();
DetachFromEntity();
UpdatePlayerState();
}
public IReadOnlySet<EntityUid> ViewSubscriptions => _viewSubscriptions;
private void SetAttachedEntityName()
{
if (Name != null && AttachedEntity != null)
@@ -193,6 +200,26 @@ namespace Robust.Server.Player
UpdatePlayerState();
}
void IPlayerSession.AddViewSubscription(EntityUid eye)
{
_viewSubscriptions.Add(eye);
}
void IPlayerSession.RemoveViewSubscription(EntityUid eye)
{
_viewSubscriptions.Remove(eye);
}
private void UnsubscribeAllViews()
{
var viewSubscriberSystem = EntitySystem.Get<ViewSubscriberSystem>();
foreach (var eye in _viewSubscriptions)
{
viewSubscriberSystem.RemoveViewSubscriber(eye, this);
}
}
private void UpdatePlayerState()
{
PlayerState.Status = Status;

View File

@@ -19,6 +19,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
@@ -71,6 +72,7 @@ namespace Robust.Server
IoCManager.Register<IScriptHost, ScriptHost>();
IoCManager.Register<IMetricsManager, MetricsManager>();
IoCManager.Register<IAuthManager, AuthManager>();
IoCManager.Register<IPhysicsManager, PhysicsManager>();
}
}
}

View File

@@ -316,7 +316,7 @@ namespace Robust.Shared.Maths
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Area(in Box2 box)
public static float Area(in Box2 box)
=> box.Width * box.Height;
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -66,6 +66,11 @@ namespace Robust.Shared.Maths
return xOk && yOk;
}
public readonly bool IsEmpty()
{
return Bottom == Top || Left == Right;
}
/// <summary>Returns a UIBox2 translated by the given amount.</summary>
public readonly Box2i Translated(Vector2i point)
{

View File

@@ -224,7 +224,18 @@ namespace Robust.Shared
/// outside of our viewport.
/// </remarks>
public static readonly CVarDef<float> MaxLightRadius =
CVarDef.Create("light.max_radius", 20.0f, CVar.CLIENTONLY);
CVarDef.Create("light.max_radius", 32.1f, CVar.CLIENTONLY);
/*
* Lookup
*/
/// <summary>
/// Like MaxLightRadius this is how far we enlarge lookups to find intersecting components.
/// This should be set to your maximum entity size.
/// </summary>
public static readonly CVarDef<float> LookupEnlargementRange =
CVarDef.Create("lookup.enlargement_range", 10.0f, CVar.ARCHIVE | CVar.REPLICATED | CVar.CHEAT);
/*
* LOKI
@@ -340,6 +351,13 @@ namespace Robust.Shared
* PHYSICS
*/
// - Contacts
public static readonly CVarDef<int> ContactMultithreadThreshold =
CVarDef.Create("physics.contact_multithread_threshold", 32);
public static readonly CVarDef<int> ContactMinimumThreads =
CVarDef.Create("physics.contact_minimum_threads", 2);
// - Sleep
public static readonly CVarDef<float> AngularSleepTolerance =
CVarDef.Create("physics.angsleeptol", 2.0f / 180.0f * MathF.PI);
@@ -451,6 +469,13 @@ namespace Robust.Shared
public static readonly CVarDef<float> MaxAngVelocity =
CVarDef.Create("physics.maxangvelocity", 15f);
/// <summary>
/// How frequently grid fixtures are updated. Given grid updates can be expensive they aren't run immediately.
/// Set to 0 to run them immediately.
/// </summary>
public static readonly CVarDef<float> GridFixtureUpdateRate =
CVarDef.Create("physics.grid_fixture_update_rate", 0.2f);
/*
* DISCORD
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
using Robust.Shared.Maths;
namespace Robust.Shared.GameObjects
{
public interface ILookupWorldBox2Component
{
Box2 GetWorldAABB(Vector2? worldPos = null, Angle? worldRot = null);
}
}

View File

@@ -1,35 +1,10 @@
using System;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// Called once when a collision starts
/// </summary>
public interface IStartCollide
{
/// <summary>
/// We'll pass in both our body and the other body to save the behaviors having to get these components themselves.
/// </summary>
[Obsolete("Use StartCollideEvent instead")]
void CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold);
}
/// <summary>
/// Called once when a collision ends.
/// </summary>
public interface IEndCollide
{
/// <summary>
/// Run behaviour after all other collision behaviors have run.
/// </summary>
[Obsolete("Use EndCollideEvent instead")]
void CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold);
}
[Serializable, NetSerializable]
public enum BodyStatus: byte
{

View File

@@ -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;
@@ -44,9 +45,10 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[ComponentReference(typeof(ILookupWorldBox2Component))]
[ComponentReference(typeof(IPhysBody))]
[NetworkedComponent()]
public sealed class PhysicsComponent : Component, IPhysBody, ISerializationHooks
public sealed class PhysicsComponent : Component, IPhysBody, ISerializationHooks, ILookupWorldBox2Component
{
[DataField("status", readOnly: true)]
private BodyStatus _bodyStatus = BodyStatus.OnGround;
@@ -59,6 +61,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.
@@ -73,6 +77,64 @@ namespace Robust.Shared.GameObjects
public bool IgnoreCCD { get; set; }
[ViewVariables]
public int ContactCount
{
get
{
var count = 0;
var edge = ContactEdges;
while (edge != null)
{
edge = edge.Next;
count++;
}
return count;
}
}
[ViewVariables]
public Box2 LocalAABB
{
get
{
var broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
var broadphase = broadphaseSystem.GetBroadphase(this);
if (broadphase == null) return new Box2();
var worldPos = Owner.Transform.WorldPosition;
var aabb = new Box2(worldPos, worldPos);
foreach (var fixture in Fixtures)
{
foreach (var proxy in fixture.Proxies)
{
aabb = aabb.Union(proxy.AABB);
}
}
return aabb;
}
}
[ViewVariables]
public Box2 WorldAABB
{
get
{
var broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
var broadphase = broadphaseSystem.GetBroadphase(this);
if (broadphase == null) return new Box2();
var localAABB = LocalAABB;
var center = broadphase.Owner.Transform.WorldMatrix.Transform(localAABB.Center);
return new Box2Rotated(localAABB.Translated(center), broadphase.Owner.Transform.WorldRotation, center).CalcBoundingBox();
}
}
/// <summary>
/// Linked-list of all of our contacts.
/// </summary>
@@ -119,7 +181,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);
}
@@ -225,39 +287,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;
@@ -429,17 +462,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();
}
@@ -551,9 +586,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.
@@ -640,9 +678,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.
@@ -1010,17 +1045,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;
}
@@ -1047,24 +1074,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
@@ -1072,12 +1081,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)
@@ -1141,73 +1170,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()
{
@@ -1224,7 +1186,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 />
@@ -1237,6 +1199,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.
@@ -1273,45 +1238,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);
@@ -1339,7 +1265,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;
}
@@ -1365,7 +1291,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;
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.Physics;
namespace Robust.Shared.GameObjects
{
[RegisterComponent]
public sealed class EntityLookupComponent : Component
{
public override string Name => "EntityLookup";
internal DynamicTree<IEntity> Tree = default!;
}
}

View File

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

View File

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

View File

@@ -78,7 +78,7 @@ namespace Robust.Shared.GameObjects
xform.Parent = Owner.Transform;
// anchor snapping
xform.LocalPosition = Grid.GridTileToLocal(Grid.TileIndicesFor(xform.LocalPosition)).Position;
xform.LocalPosition = Grid.GridTileToLocal(Grid.TileIndicesFor(xform.WorldPosition)).Position;
xform.SetAnchored(result);

View File

@@ -734,7 +734,7 @@ namespace Robust.Shared.GameObjects
var newParentId = newState.ParentID;
var rebuildMatrices = false;
if (Parent?.Owner?.Uid != newParentId)
if (Parent?.Owner.Uid != newParentId)
{
if (newParentId != _parent)
{

View File

@@ -12,6 +12,8 @@ namespace Robust.Shared.GameObjects
void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = true)
where TEvent:EntityEventArgs;
void RaiseLocalEvent(EntityUid uid, EntityEventArgs args, bool broadcast = true);
void SubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
where TComp : IComponent
where TEvent : EntityEventArgs;
@@ -82,6 +84,24 @@ namespace Robust.Shared.GameObjects
RaiseEvent(EventSource.Local, args);
}
/// <inheritdoc />
public void RaiseLocalEvent(EntityUid uid, EntityEventArgs args, bool broadcast = true)
{
var type = args.GetType();
if (_orderedEvents.Contains(type))
{
RaiseLocalOrdered(uid, args, broadcast);
return;
}
_eventTables.Dispatch(uid, type, args);
// we also broadcast it so the call site does not have to.
if(broadcast)
RaiseEvent(EventSource.Local, args);
}
/// <inheritdoc />
public void SubscribeLocalEvent<TComp, TEvent>(ComponentEventHandler<TComp, TEvent> handler)
where TComp : IComponent
@@ -400,7 +420,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public void ClearEventTables()
{
_eventTables.ClearEntities();
_eventTables.Clear();
}
public void Dispose()

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Robust.Shared.GameObjects
{
@@ -7,50 +8,83 @@ namespace Robust.Shared.GameObjects
{
private List<SubBase>? _subscriptions;
/// <summary>
/// A handle to allow subscription on this entity system's behalf.
/// </summary>
protected Subscriptions Subs { get; }
// NOTE: EntityEventHandler<T> and EntitySessionEventHandler<T> CANNOT BE ORDERED BETWEEN EACH OTHER.
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 +103,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
{
@@ -108,6 +142,51 @@ namespace Robust.Shared.GameObjects
_subscriptions = null;
}
/// <summary>
/// API class to allow registering on an EntitySystem's behalf.
/// Intended to support creation of boilerplate-reduction-methods
/// that need to subscribe stuff on an entity system.
/// </summary>
[PublicAPI]
public sealed class Subscriptions
{
public EntitySystem System { get; }
internal Subscriptions(EntitySystem system)
{
System = system;
}
// Intended for helper methods, so minimal API.
public void SubEvent<T>(
EventSource src,
EntityEventHandler<T> handler,
Type[]? before = null, Type[]? after = null)
where T : notnull
{
System.SubEvent(src, handler, before, after);
}
public void SubSessionEvent<T>(
EventSource src,
EntitySessionEventHandler<T> handler,
Type[]? before = null, Type[]? after = null)
where T : notnull
{
System.SubSessionEvent(src, handler, before, after);
}
public void SubscribeLocalEvent<TComp, TEvent>(
ComponentEventHandler<TComp, TEvent> handler,
Type[]? before = null, Type[]? after = null)
where TComp : IComponent
where TEvent : EntityEventArgs
{
System.SubscribeLocalEvent(handler, before, after);
}
}
private abstract class SubBase
{
public abstract void Unsubscribe(EntitySystem sys, IEventBus bus);

View File

@@ -30,6 +30,11 @@ namespace Robust.Shared.GameObjects
IEnumerable<Type> IEntitySystem.UpdatesAfter => UpdatesAfter;
IEnumerable<Type> IEntitySystem.UpdatesBefore => UpdatesBefore;
protected EntitySystem()
{
Subs = new Subscriptions(this);
}
/// <inheritdoc />
public virtual void Initialize() { }
@@ -93,6 +98,11 @@ namespace Robust.Shared.GameObjects
EntityManager.EventBus.RaiseLocalEvent(uid, args, broadcast);
}
protected void RaiseLocalEvent(EntityUid uid, EntityEventArgs args, bool broadcast = true)
{
EntityManager.EventBus.RaiseLocalEvent(uid, args, broadcast);
}
#endregion
#region Static Helpers

View File

@@ -26,6 +26,9 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
#endif
private DependencyCollection _systemDependencyCollection = default!;
private List<Type> _systemTypes = new();
private static readonly Histogram _tickUsageHistogram = Metrics.CreateHistogram("robust_entity_systems_update_usage",
"Amount of time spent processing each entity system", new HistogramConfiguration
{
@@ -38,24 +41,11 @@ namespace Robust.Shared.GameObjects
private readonly Stopwatch _stopwatch = new();
/// <summary>
/// Maps system types to instances.
/// </summary>
[ViewVariables]
private readonly Dictionary<Type, IEntitySystem> _systems = new();
/// <summary>
/// Maps system supertypes to instances.
/// </summary>
[ViewVariables]
private readonly Dictionary<Type, IEntitySystem> _supertypeSystems = new();
private bool _initialized;
[ViewVariables] private UpdateReg[] _updateOrder = Array.Empty<UpdateReg>();
[ViewVariables] private IEntitySystem[] _frameUpdateOrder = Array.Empty<IEntitySystem>();
[ViewVariables] public IReadOnlyCollection<IEntitySystem> AllSystems => _systems.Values;
public bool MetricsEnabled { get; set; }
/// <inheritdoc />
@@ -68,55 +58,36 @@ namespace Robust.Shared.GameObjects
public T GetEntitySystem<T>()
where T : IEntitySystem
{
var type = typeof(T);
// check using exact match first, then check using the supertype
if (!_systems.ContainsKey(type))
{
if (!_supertypeSystems.ContainsKey(type))
{
throw new InvalidEntitySystemException();
}
else
{
return (T) _supertypeSystems[type];
}
}
return (T)_systems[type];
return _systemDependencyCollection.Resolve<T>();
}
/// <inheritdoc />
public bool TryGetEntitySystem<T>([NotNullWhen(true)] out T? entitySystem)
where T : IEntitySystem
{
if (_systems.TryGetValue(typeof(T), out var system))
{
entitySystem = (T) system;
return true;
}
if (_supertypeSystems.TryGetValue(typeof(T), out var systemFromSupertype))
{
entitySystem = (T) systemFromSupertype;
return true;
}
entitySystem = default;
return false;
return _systemDependencyCollection.TryResolveType<T>(out entitySystem);
}
/// <inheritdoc />
public void Initialize()
{
HashSet<Type> excludedTypes = new();
var excludedTypes = new HashSet<Type>();
_systemDependencyCollection = new(IoCManager.Instance!);
var subTypes = new Dictionary<Type, Type>();
_systemTypes.Clear();
foreach (var type in _reflectionManager.GetAllChildren<IEntitySystem>().Concat(_extraLoadedTypes))
{
Logger.DebugS("go.sys", "Initializing entity system {0}", type);
// Force IoC inject of all systems
var instance = _typeFactory.CreateInstanceUnchecked<IEntitySystem>(type, oneOff: true);
_systems.Add(type, instance);
_systemDependencyCollection.Register(type);
_systemTypes.Add(type);
excludedTypes.Add(type);
if (subTypes.ContainsKey(type))
{
subTypes.Remove(type);
}
// also register systems under their supertypes, so they can be retrieved by their supertype.
// We don't do this if there are multiple subtype systems of that supertype though, otherwise
@@ -127,27 +98,36 @@ namespace Robust.Shared.GameObjects
// so don't register under the supertype because it would be unclear
// which instance to return if we retrieved it by the supertype
if (excludedTypes.Contains(baseType)) continue;
if (_supertypeSystems.ContainsKey(baseType))
if (subTypes.ContainsKey(baseType))
{
_supertypeSystems.Remove(baseType);
subTypes.Remove(baseType);
excludedTypes.Add(baseType);
}
else
{
_supertypeSystems.Add(baseType, instance);
subTypes.Add(baseType, type);
}
}
}
foreach (var system in _systems.Values)
foreach (var (baseType, type) in subTypes)
{
_systemDependencyCollection.Register(baseType, type, overwrite: true);
_systemTypes.Remove(baseType);
}
_systemDependencyCollection.BuildGraph();
foreach (var systemType in _systemTypes)
{
var system = (IEntitySystem)_systemDependencyCollection.ResolveType(systemType);
system.Initialize();
SystemLoaded?.Invoke(this, new SystemChangedArgs(system));
}
// Create update order for entity systems.
var (fUpdate, update) = CalculateUpdateOrder(_systems.Values, _supertypeSystems);
var (fUpdate, update) = CalculateUpdateOrder(_systemTypes, subTypes, _systemDependencyCollection);
_frameUpdateOrder = fUpdate.ToArray();
_updateOrder = update
@@ -163,23 +143,24 @@ namespace Robust.Shared.GameObjects
private static (IEnumerable<IEntitySystem> frameUpd, IEnumerable<IEntitySystem> upd)
CalculateUpdateOrder(
Dictionary<Type, IEntitySystem>.ValueCollection systems,
Dictionary<Type, IEntitySystem> supertypeSystems)
List<Type> systemTypes,
Dictionary<Type, Type> subTypes,
DependencyCollection dependencyCollection)
{
var allNodes = new List<TopologicalSort.GraphNode<IEntitySystem>>();
var typeToNode = new Dictionary<Type, TopologicalSort.GraphNode<IEntitySystem>>();
foreach (var system in systems)
foreach (var systemType in systemTypes)
{
var node = new TopologicalSort.GraphNode<IEntitySystem>(system);
var node = new TopologicalSort.GraphNode<IEntitySystem>((IEntitySystem) dependencyCollection.ResolveType(systemType));
typeToNode.Add(system.GetType(), node);
typeToNode.Add(systemType, node);
allNodes.Add(node);
}
foreach (var (type, system) in supertypeSystems)
foreach (var (type, system) in subTypes)
{
var node = typeToNode[system.GetType()];
var node = typeToNode[system];
typeToNode[type] = node;
}
@@ -220,18 +201,20 @@ namespace Robust.Shared.GameObjects
public void Shutdown()
{
// System.Values is modified by RemoveSystem
foreach (var system in _systems.Values)
foreach (var systemType in _systemTypes)
{
if(_systemDependencyCollection == null) continue;
var system = (IEntitySystem)_systemDependencyCollection.ResolveType(systemType);
SystemUnloaded?.Invoke(this, new SystemChangedArgs(system));
system.Shutdown();
_entityManager.EventBus.UnsubscribeEvents(system);
}
_systems.Clear();
_systemTypes.Clear();
_updateOrder = Array.Empty<UpdateReg>();
_frameUpdateOrder = Array.Empty<IEntitySystem>();
_supertypeSystems.Clear();
_initialized = false;
_systemDependencyCollection?.Clear();
}
/// <inheritdoc />
@@ -343,6 +326,4 @@ namespace Robust.Shared.GameObjects
System = system;
}
}
public class InvalidEntitySystemException : Exception { }
}

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