mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6491e51f3d | ||
|
|
58e7fb3a17 | ||
|
|
a9c7926226 | ||
|
|
241dc0b752 | ||
|
|
566948f1c0 | ||
|
|
dbba440f7e | ||
|
|
5b1e9eec27 | ||
|
|
8ef95f0199 | ||
|
|
b71e8f140a | ||
|
|
f8397099de | ||
|
|
5bbf1703ac | ||
|
|
1357e38759 | ||
|
|
655ecbab45 | ||
|
|
a0ef63bd4a | ||
|
|
f34763f11e | ||
|
|
b1f6b4cbe0 | ||
|
|
94708881b3 | ||
|
|
0f6dbac51c | ||
|
|
e67de121ca | ||
|
|
17979db216 | ||
|
|
8d0070b5c3 | ||
|
|
034c392cbe | ||
|
|
160bbc3a72 | ||
|
|
3a4d228e94 | ||
|
|
57f57f9d9f | ||
|
|
0592444252 | ||
|
|
d8499f2e60 | ||
|
|
476b5182f8 | ||
|
|
6c7ab1bd82 | ||
|
|
526ed31b0d | ||
|
|
3ac5552276 | ||
|
|
7a51c22514 | ||
|
|
aa339eb504 | ||
|
|
ffb3800664 | ||
|
|
39eb1a7d75 | ||
|
|
c25ab2fcb1 | ||
|
|
22e0fbc6c1 | ||
|
|
72071826a4 | ||
|
|
8b9c1ab9a1 | ||
|
|
16d5da5414 | ||
|
|
39f82694bf | ||
|
|
79c3c09851 | ||
|
|
ad7370f0b3 | ||
|
|
75cd8a0a12 | ||
|
|
915a812832 | ||
|
|
c8e7fe9f1f | ||
|
|
9803ed9cad | ||
|
|
f5f8a59c86 | ||
|
|
f4e3dfa601 | ||
|
|
9305b261bb | ||
|
|
3a161af4a5 | ||
|
|
0447d8d3b9 | ||
|
|
c2eed5c007 | ||
|
|
d9464e2621 | ||
|
|
1a70813f3c | ||
|
|
a3a69f821f | ||
|
|
c72988b05b | ||
|
|
11bc9c0fe4 | ||
|
|
98593b7b33 | ||
|
|
4b30a94126 | ||
|
|
1c8958d312 | ||
|
|
ef2f81a77a | ||
|
|
3935c63a80 | ||
|
|
76ab68dc3d | ||
|
|
a986292aa2 | ||
|
|
082fac52cd | ||
|
|
a60993c60d | ||
|
|
97ffc9ecc2 | ||
|
|
ab4f2c91b4 | ||
|
|
5f2cc942cb | ||
|
|
5a343477a9 | ||
|
|
7bc847c9a4 | ||
|
|
cfc7f26100 | ||
|
|
6eb3989362 | ||
|
|
04fd324e4a | ||
|
|
c9e999d024 | ||
|
|
243f405bab | ||
|
|
2353f0ed6f | ||
|
|
8be4b39449 | ||
|
|
e2c7d95519 | ||
|
|
0e6c00573f | ||
|
|
e7e08e5dd6 | ||
|
|
9ac8db37cc | ||
|
|
2d6eebfae2 | ||
|
|
5540d643ef | ||
|
|
76c1d9f97b |
@@ -1,52 +0,0 @@
|
||||
environment:
|
||||
sonarqubekey:
|
||||
secure: h3llq6OeVa94hJ71UOEQSQDq75vFt+doso7iFry0gvt/fFcyeonY9wY+ETOIVITK
|
||||
global:
|
||||
PYTHONUNBUFFERED: True
|
||||
HEADLESS: 1 # For the unit tests.
|
||||
|
||||
version: 0.1.0.{build}
|
||||
pull_requests:
|
||||
do_not_increment_build_number: true
|
||||
|
||||
image: Visual Studio 2019
|
||||
install:
|
||||
- ps: >
|
||||
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
|
||||
{
|
||||
cinst msbuild-sonarqube-runner;
|
||||
}
|
||||
|
||||
before_build:
|
||||
- cmd: py -3.5 -m pip install --user requests
|
||||
- cmd: git submodule update --init --recursive
|
||||
- ps: >
|
||||
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
|
||||
{
|
||||
SonarScanner.MSBuild.exe begin /k:"ss14" /d:"sonar.host.url=https://sonarcloud.io" /d:"sonar.login=$env:sonarqubekey" /o:"space-wizards" /d:sonar.cs.nunit.reportsPaths="$(Get-Location)\nunitTestResult.xml";
|
||||
}
|
||||
|
||||
platform: x64
|
||||
configuration: Debug
|
||||
|
||||
cache:
|
||||
- packages -> **\*.csproj
|
||||
- Dependencies
|
||||
|
||||
build:
|
||||
project: RobustToolbox.sln
|
||||
parallel: false
|
||||
verbosity: minimal
|
||||
|
||||
build_script:
|
||||
- ps: dotnet build RobustToolbox.sln /p:AppVeyor=yes
|
||||
|
||||
test_script:
|
||||
- ps: dotnet test Robust.UnitTesting/Robust.UnitTesting.csproj
|
||||
|
||||
after_test:
|
||||
- ps: >
|
||||
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
|
||||
{
|
||||
SonarScanner.MSBuild.exe end /d:"sonar.login=$env:sonarqubekey";
|
||||
}
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -18,4 +18,4 @@
|
||||
url = https://github.com/space-wizards/Linguini
|
||||
[submodule "cefglue"]
|
||||
path = cefglue
|
||||
url = https://gitlab.com/xiliumhq/chromiumembedded/cefglue/
|
||||
url = https://github.com/space-wizards/cefglue.git
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 4b36b36f39...dd285c9246
@@ -3,10 +3,10 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<DefineConstants Condition="'$(TargetFramework)' == 'netstandard2.1' or '$(TargetFramework)' == 'netcoreapp3.1'">$(DefineConstants);HAS_FULL_SPAN</DefineConstants>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<DefaultItemExcludes>Lidgren.Network/**/*</DefaultItemExcludes>
|
||||
<DefineConstants>$(DefineConstants);USE_RELEASE_STATISTICS</DefineConstants>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Submodule NetSerializer updated: ab1b651292...b75b301bea
@@ -2,29 +2,15 @@ preset raw;
|
||||
|
||||
#include "/Shaders/Internal/shadow_cast_shared.swsl"
|
||||
|
||||
#include "/Shaders/Internal/fov_shared.swsl"
|
||||
|
||||
const highp float g_MinVariance = 0.0;
|
||||
|
||||
varying highp vec2 worldPosition;
|
||||
|
||||
// Center of the FOV, in world coordinates.
|
||||
uniform highp vec2 center;
|
||||
|
||||
void vertex()
|
||||
{
|
||||
highp vec3 transformed = modelMatrix * vec3(VERTEX, 1.0);
|
||||
worldPosition = transformed.xy;
|
||||
transformed = projectionMatrix * viewMatrix * transformed;
|
||||
|
||||
VERTEX = transformed.xy;
|
||||
}
|
||||
|
||||
void fragment()
|
||||
{
|
||||
highp vec2 diff = worldPosition - center;
|
||||
highp float ourDist = length(worldSpaceDiff);
|
||||
|
||||
highp float ourDist = length(diff);
|
||||
|
||||
highp vec2 occlDist = occludeDepth(diff, TEXTURE, 0.25);
|
||||
highp vec2 occlDist = occludeDepth(worldSpaceDiff, TEXTURE, 0.25);
|
||||
|
||||
highp float occlusion = ChebyshevUpperBound(occlDist, ourDist);
|
||||
|
||||
|
||||
@@ -2,32 +2,18 @@ preset raw;
|
||||
|
||||
#include "/Shaders/Internal/shadow_cast_shared.swsl"
|
||||
|
||||
#include "/Shaders/Internal/fov_shared.swsl"
|
||||
|
||||
const highp float g_MinVariance = 0.0;
|
||||
|
||||
varying highp vec2 worldPosition;
|
||||
|
||||
// Center of the FOV, in world coordinates.
|
||||
uniform highp vec2 center;
|
||||
|
||||
void vertex()
|
||||
{
|
||||
highp vec3 transformed = modelMatrix * vec3(VERTEX, 1.0);
|
||||
worldPosition = transformed.xy;
|
||||
transformed = projectionMatrix * viewMatrix * transformed;
|
||||
|
||||
VERTEX = transformed.xy;
|
||||
}
|
||||
|
||||
void fragment()
|
||||
{
|
||||
highp vec2 diff = worldPosition - center;
|
||||
highp float ourDist = length(worldSpaceDiff);
|
||||
|
||||
highp float ourDist = length(diff);
|
||||
|
||||
highp float occlDist = occludeDepth(diff, TEXTURE, 0.75).r;
|
||||
highp float occlDist = occludeDepth(worldSpaceDiff, TEXTURE, 0.75).r;
|
||||
|
||||
// *Very* simple biased shadow check for FOV.
|
||||
if (!doesOcclude(diff, TEXTURE, 0.75, -0.75/32.0))
|
||||
if (!doesOcclude(worldSpaceDiff, TEXTURE, 0.75, -0.75/32.0))
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
16
Resources/Shaders/Internal/fov_shared.swsl
Normal file
16
Resources/Shaders/Internal/fov_shared.swsl
Normal file
@@ -0,0 +1,16 @@
|
||||
// Shared between fov-lighting.swsl and fov.swsl, which both use the Clyde quad,
|
||||
// manually transformed into clip-space to cover the entire viewport
|
||||
|
||||
// World-space position offset from centre to pixel.
|
||||
varying highp vec2 worldSpaceDiff;
|
||||
|
||||
// Inverted transformation matrix from clip coordinates to difference coordinates.
|
||||
uniform highp mat3 clipToDiff;
|
||||
|
||||
void vertex()
|
||||
{
|
||||
// Convert quad-space (0.0 to 1.0) to clip-space (-1.0 to 1.0)
|
||||
VERTEX = (VERTEX.xy - 0.5) * 2.0;
|
||||
worldSpaceDiff = (clipToDiff * vec3(VERTEX, 1.0)).xy;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ void fragment()
|
||||
highp vec2 diff = worldPosition - lightCenter;
|
||||
|
||||
// Totally not hacky PCF on top of VSM.
|
||||
highp float occlusion = createOcclusion(diff);
|
||||
highp float occlusion = lightIndex < 0.0 ? 1.0 : createOcclusion(diff);
|
||||
|
||||
if (occlusion == 0.0)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -92,7 +93,7 @@ namespace Robust.Analyzers
|
||||
continue;
|
||||
|
||||
// If we find that the containing class is specified in the attribute, return! All is good.
|
||||
if (SymbolEqualityComparer.Default.Equals(containingType, t))
|
||||
if (InheritsFromOrEquals(containingType, t))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,5 +104,26 @@ namespace Robust.Analyzers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool InheritsFromOrEquals(INamedTypeSymbol type, INamedTypeSymbol baseType)
|
||||
{
|
||||
foreach (var otherType in GetBaseTypesAndThis(type))
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(otherType, baseType))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<INamedTypeSymbol> GetBaseTypesAndThis(INamedTypeSymbol namedType)
|
||||
{
|
||||
var current = namedType;
|
||||
while (current != null)
|
||||
{
|
||||
yield return current;
|
||||
current = current.BaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,15 +51,12 @@ namespace Robust.Client.CEF
|
||||
|
||||
// --------------------------- README --------------------------------------------------
|
||||
// By the way! You're gonna need the CEF binaries in your client's bin folder.
|
||||
// More specifically, version cef_binary_91.1.21+g9dd45fe+chromium-91.0.4472.114
|
||||
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_windows64_minimal.tar.bz2
|
||||
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_linux64_minimal.tar.bz2
|
||||
// More specifically, version cef_binary_94.4.1+g4b61a8c+chromium-94.0.4606.54
|
||||
// Windows: https://cef-builds.spotifycdn.com/cef_binary_94.4.1%2Bg4b61a8c%2Bchromium-94.0.4606.54_windows64_minimal.tar.bz2
|
||||
// Linux: https://cef-builds.spotifycdn.com/cef_binary_94.4.1%2Bg4b61a8c%2Bchromium-94.0.4606.54_linux64_minimal.tar.bz2
|
||||
// Here's how to get it to work:
|
||||
// 1. Copy all the contents of "Release" to the bin folder.
|
||||
// 2. Copy all the contents of "Resources" to the bin folder.
|
||||
// Supposedly, you should just need libcef.so in Release and icudtl.dat in Resources...
|
||||
// The rest might be optional.
|
||||
// Maybe. Good luck! If you get odd crashes with no info and a weird exit code, use GDB!
|
||||
// 1. Copy all the contents of "Release" to the bin/Content.Client folder.
|
||||
// 2. Copy all the contents of "Resources" to the bin/Content.Client folder.
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
_app = new RobustCefApp();
|
||||
|
||||
@@ -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 SharedPhysicsSystem _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<SharedPhysicsSystem>();
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStates = default!;
|
||||
[Dependency] private readonly IDebugDrawingManager _debugDrawMan = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ushort DefaultPort { get; } = 1212;
|
||||
@@ -57,7 +56,6 @@ namespace Robust.Client
|
||||
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged, invokeImmediately: true);
|
||||
|
||||
_playMan.Initialize();
|
||||
_debugDrawMan.Initialize();
|
||||
Reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@ namespace Robust.Client
|
||||
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
|
||||
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
|
||||
IoCManager.Register<IDebugDrawing, DebugDrawing>();
|
||||
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
|
||||
IoCManager.Register<ILightManager, LightManager>();
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
|
||||
@@ -161,19 +161,6 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal class ShowBoundingBoxesCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showbb";
|
||||
public string Help => "";
|
||||
public string Description => "Enables debug drawing over all bounding boxes in the game, showing their size.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IDebugDrawing>();
|
||||
mgr.DebugColliders = !mgr.DebugColliders;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ShowPositionsCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showpos";
|
||||
@@ -201,16 +188,16 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
if (!float.TryParse(args[0], out var duration))
|
||||
{
|
||||
shell.WriteError($"{args[0]} is not a valid integer.");
|
||||
shell.WriteError($"{args[0]} is not a valid float.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mgr = IoCManager.Resolve<IDebugDrawingManager>();
|
||||
var mgr = EntitySystem.Get<DebugRayDrawingSystem>();
|
||||
mgr.DebugDrawRays = !mgr.DebugDrawRays;
|
||||
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays.ToString());
|
||||
mgr.DebugRayLifetime = TimeSpan.FromSeconds((double)int.Parse(args[0], CultureInfo.InvariantCulture));
|
||||
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays);
|
||||
mgr.DebugRayLifetime = TimeSpan.FromSeconds(duration);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Robust.Client.Console.Commands
|
||||
public sealed class PhysicsOverlayCommands : IConsoleCommand
|
||||
{
|
||||
public string Command => "physics";
|
||||
public string Description => $"{Command} <contactnormals / contactpoints / shapes>";
|
||||
public string Help => $"{Command} <overlay>";
|
||||
public string Description => $"Shows a debug physics overlay. The arg supplied specifies the overlay.";
|
||||
public string Help => $"{Command} <aabbs / contactnormals / contactpoints / joints / shapeinfo / shapes>";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
@@ -21,12 +21,21 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
switch (args[0])
|
||||
{
|
||||
case "aabbs":
|
||||
system.Flags ^= PhysicsDebugFlags.AABBs;
|
||||
break;
|
||||
case "contactnormals":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactNormals;
|
||||
break;
|
||||
case "contactpoints":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactPoints;
|
||||
break;
|
||||
case "joints":
|
||||
system.Flags ^= PhysicsDebugFlags.Joints;
|
||||
break;
|
||||
case "shapeinfo":
|
||||
system.Flags ^= PhysicsDebugFlags.ShapeInfo;
|
||||
break;
|
||||
case "shapes":
|
||||
system.Flags ^= PhysicsDebugFlags.Shapes;
|
||||
break;
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
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.Dynamics.Joints;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
@@ -22,39 +11,10 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
private bool _debugColliders;
|
||||
private bool _debugPositions;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool DebugColliders
|
||||
{
|
||||
get => _debugColliders;
|
||||
set
|
||||
{
|
||||
if (value == DebugColliders)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_debugColliders = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<PhysicsOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new PhysicsOverlay(_eyeManager,
|
||||
_prototypeManager, _inputManager, _mapManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<PhysicsOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool DebugPositions
|
||||
{
|
||||
@@ -79,194 +39,6 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
}
|
||||
|
||||
private class PhysicsOverlay : Overlay
|
||||
{
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly IInputManager _inputManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
||||
private readonly ShaderInstance _shader;
|
||||
private readonly Font _font;
|
||||
|
||||
private Vector2 _hoverStartScreen = Vector2.Zero;
|
||||
private List<IPhysBody> _hoverBodies = new();
|
||||
|
||||
|
||||
public PhysicsOverlay(IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IMapManager mapManager)
|
||||
{
|
||||
_eyeManager = eyeMan;
|
||||
_inputManager = inputManager;
|
||||
_mapManager = mapManager;
|
||||
|
||||
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
switch (args.Space)
|
||||
{
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen(args);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld(args);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DrawScreen(in OverlayDrawArgs args)
|
||||
{
|
||||
var screenHandle = args.ScreenHandle;
|
||||
var lineHeight = _font.GetLineHeight(1f);
|
||||
Vector2 drawPos = _hoverStartScreen + new Vector2(20, 0) + new Vector2(0, -(_hoverBodies.Count * 4 * lineHeight / 2f));
|
||||
int row = 0;
|
||||
|
||||
foreach (var body in _hoverBodies)
|
||||
{
|
||||
if (body != _hoverBodies[0])
|
||||
{
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
|
||||
row++;
|
||||
}
|
||||
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
row++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DrawWorld(in OverlayDrawArgs args)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
worldHandle.UseShader(_shader);
|
||||
var drawing = new PhysDrawingAdapter(worldHandle);
|
||||
|
||||
_hoverBodies.Clear();
|
||||
var mouseScreenPos = _inputManager.MouseScreenPosition;
|
||||
var mouseWorldPos = _eyeManager.ScreenToMap(mouseScreenPos).Position;
|
||||
_hoverStartScreen = mouseScreenPos.Position;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
var viewBounds = _eyeManager.GetWorldViewbounds();
|
||||
|
||||
if (viewport.IsEmpty()) return;
|
||||
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
var drawnJoints = new HashSet<Joint>();
|
||||
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
|
||||
|
||||
// all entities have a TransformComponent
|
||||
var transform = physBody.Owner.Transform;
|
||||
|
||||
var worldBox = physBody.GetWorldAABB();
|
||||
if (worldBox.IsEmpty()) continue;
|
||||
|
||||
var pTransform = physBody.GetTransform();
|
||||
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
var shape = fixture.Shape;
|
||||
var sleepPercent = physBody.Awake ? 0.0f : 1.0f;
|
||||
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
|
||||
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
|
||||
var aabb = shape.ComputeAABB(pTransform, 0);
|
||||
worldHandle.DrawRect(aabb, Color.Blue, false);
|
||||
}
|
||||
|
||||
foreach (var joint in physBody.Joints)
|
||||
{
|
||||
if (drawnJoints.Contains(joint)) continue;
|
||||
drawnJoints.Add(joint);
|
||||
|
||||
joint.DebugDraw(drawing, in viewport);
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
}
|
||||
|
||||
if (worldBox.Contains(mouseWorldPos))
|
||||
{
|
||||
_hoverBodies.Add(physBody);
|
||||
}
|
||||
|
||||
// draw AABB
|
||||
worldHandle.DrawRect(worldBox, colorEdge, false);
|
||||
}
|
||||
}
|
||||
|
||||
private class PhysDrawingAdapter : DebugDrawingHandle
|
||||
{
|
||||
private readonly DrawingHandleWorld _handle;
|
||||
|
||||
public PhysDrawingAdapter(DrawingHandleWorld worldHandle)
|
||||
{
|
||||
_handle = worldHandle;
|
||||
}
|
||||
|
||||
public override Color WakeMixColor => Color.White;
|
||||
public override Color GridFillColor => Color.Blue.WithAlpha(0.05f);
|
||||
public override Color RectFillColor => Color.Green.WithAlpha(0.25f);
|
||||
|
||||
public override Color CalcWakeColor(Color color, float wakePercent)
|
||||
{
|
||||
var percent = MathHelper.Clamp(wakePercent, 0, 1);
|
||||
|
||||
var r = 1 - (percent * (1 - color.R));
|
||||
var g = 1 - (percent * (1 - color.G));
|
||||
var b = 1 - (percent * (1 - color.B));
|
||||
|
||||
return new Color(r, g, b, color.A);
|
||||
}
|
||||
|
||||
public override void DrawRect(in Box2 box, in Color color)
|
||||
{
|
||||
_handle.DrawRect(box, color);
|
||||
}
|
||||
|
||||
public override void DrawRect(in Box2Rotated box, in Color color)
|
||||
{
|
||||
_handle.DrawRect(box, color);
|
||||
}
|
||||
|
||||
public override void DrawCircle(Vector2 origin, float radius, in Color color)
|
||||
{
|
||||
_handle.DrawCircle(origin, radius, color);
|
||||
}
|
||||
|
||||
public override void DrawPolygonShape(Vector2[] vertices, in Color color)
|
||||
{
|
||||
_handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, vertices, color);
|
||||
}
|
||||
|
||||
public override void DrawLine(Vector2 start, Vector2 end, in Color color)
|
||||
{
|
||||
_handle.DrawLine(start, end, color);
|
||||
}
|
||||
|
||||
public override void SetTransform(in Matrix3 transform)
|
||||
{
|
||||
_handle.SetTransform(transform);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityPositionOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityLookup _lookup;
|
||||
|
||||
@@ -20,22 +20,49 @@
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 Erin Catto
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
/* Heavily inspired by Farseer */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
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.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
internal sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
|
||||
public sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
|
||||
{
|
||||
/*
|
||||
* Used for debugging shapes, controllers, joints, contacts
|
||||
@@ -46,7 +73,7 @@ namespace Robust.Client.Debugging
|
||||
private const int MaxContactPoints = 2048;
|
||||
internal int PointCount;
|
||||
|
||||
internal ContactPoint[] _points = new ContactPoint[MaxContactPoints];
|
||||
internal ContactPoint[] Points = new ContactPoint[MaxContactPoints];
|
||||
|
||||
public PhysicsDebugFlags Flags
|
||||
{
|
||||
@@ -56,7 +83,14 @@ namespace Robust.Client.Debugging
|
||||
if (value == _flags) return;
|
||||
|
||||
if (_flags == PhysicsDebugFlags.None)
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(
|
||||
new PhysicsDebugOverlay(
|
||||
EntityManager,
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IInputManager>(),
|
||||
IoCManager.Resolve<IResourceCache>(),
|
||||
this,
|
||||
Get<SharedPhysicsSystem>()));
|
||||
|
||||
if (value == PhysicsDebugFlags.None)
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
|
||||
@@ -69,7 +103,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
public override void HandlePreSolve(Contact contact, in Manifold oldManifold)
|
||||
{
|
||||
if ((Flags & PhysicsDebugFlags.ContactPoints) != 0)
|
||||
if ((Flags & (PhysicsDebugFlags.ContactPoints | PhysicsDebugFlags.ContactNormals)) != 0)
|
||||
{
|
||||
Manifold manifold = contact.Manifold;
|
||||
|
||||
@@ -78,22 +112,24 @@ namespace Robust.Client.Debugging
|
||||
|
||||
Fixture fixtureA = contact.FixtureA!;
|
||||
|
||||
PointState[] state1, state2;
|
||||
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
|
||||
var state1 = new PointState[2];
|
||||
var state2 = new PointState[2];
|
||||
|
||||
CollisionManager.GetPointStates(ref state1, ref state2, oldManifold, manifold);
|
||||
|
||||
Span<Vector2> points = stackalloc Vector2[2];
|
||||
contact.GetWorldManifold(_physicsManager, out var normal, points);
|
||||
|
||||
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
|
||||
ContactPoint cp = Points[PointCount];
|
||||
for (var i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
|
||||
{
|
||||
if (fixtureA == null)
|
||||
_points[i] = new ContactPoint();
|
||||
Points[i] = new ContactPoint();
|
||||
|
||||
ContactPoint cp = _points[PointCount];
|
||||
cp.Position = points[i];
|
||||
cp.Normal = normal;
|
||||
cp.State = state2[i];
|
||||
_points[PointCount] = cp;
|
||||
Points[PointCount] = cp;
|
||||
++PointCount;
|
||||
}
|
||||
}
|
||||
@@ -108,58 +144,288 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum PhysicsDebugFlags : byte
|
||||
public enum PhysicsDebugFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// Shows the world point for each contact in the viewport.
|
||||
/// </summary>
|
||||
ContactPoints = 1 << 0,
|
||||
/// <summary>
|
||||
/// Shows the world normal for each contact in the viewport.
|
||||
/// </summary>
|
||||
ContactNormals = 1 << 1,
|
||||
/// <summary>
|
||||
/// Shows all physics shapes in the viewport.
|
||||
/// </summary>
|
||||
Shapes = 1 << 2,
|
||||
ShapeInfo = 1 << 3,
|
||||
Joints = 1 << 4,
|
||||
AABBs = 1 << 5,
|
||||
}
|
||||
|
||||
internal sealed class PhysicsDebugOverlay : Overlay
|
||||
{
|
||||
private DebugPhysicsSystem _physics = default!;
|
||||
private IEntityManager _entityManager = default!;
|
||||
private IEyeManager _eyeManager = default!;
|
||||
private IInputManager _inputManager = default!;
|
||||
private DebugPhysicsSystem _debugPhysicsSystem = default!;
|
||||
private SharedPhysicsSystem _physicsSystem = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
||||
|
||||
public PhysicsDebugOverlay(DebugPhysicsSystem system)
|
||||
private static readonly Color JointColor = new(0.5f, 0.8f, 0.8f);
|
||||
|
||||
private readonly Font _font;
|
||||
|
||||
private HashSet<Joint> _drawnJoints = new();
|
||||
|
||||
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IResourceCache cache, DebugPhysicsSystem system, SharedPhysicsSystem physicsSystem)
|
||||
{
|
||||
_physics = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_inputManager = inputManager;
|
||||
_debugPhysicsSystem = system;
|
||||
_physicsSystem = physicsSystem;
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
private void DrawWorld(DrawingHandleWorld worldHandle)
|
||||
{
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
var viewBounds = _eyeManager.GetWorldViewbounds();
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0 && !viewport.IsEmpty())
|
||||
{
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
|
||||
|
||||
var xform = physBody.GetTransform();
|
||||
|
||||
const float AlphaModifier = 0.2f;
|
||||
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
// Invalid shape - Box2D doesn't check for IsSensor
|
||||
if (physBody.BodyType == BodyType.Dynamic && fixture.Mass == 0f)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, Color.Red.WithAlpha(AlphaModifier));
|
||||
}
|
||||
else if (!physBody.CanCollide)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.3f).WithAlpha(AlphaModifier));
|
||||
}
|
||||
else if (physBody.BodyType == BodyType.Static)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.9f, 0.5f).WithAlpha(AlphaModifier));
|
||||
}
|
||||
else if ((physBody.BodyType & (BodyType.Kinematic | BodyType.KinematicController)) != 0x0)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.9f).WithAlpha(AlphaModifier));
|
||||
}
|
||||
else if (!physBody.Awake)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, new Color(0.6f, 0.6f, 0.6f).WithAlpha(AlphaModifier));
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, new Color(0.9f, 0.7f, 0.7f).WithAlpha(AlphaModifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.AABBs) != 0 && !viewport.IsEmpty())
|
||||
{
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
|
||||
|
||||
var xform = physBody.GetTransform();
|
||||
|
||||
const float AlphaModifier = 0.2f;
|
||||
Box2? aabb = null;
|
||||
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
var shapeBB = fixture.Shape.ComputeAABB(xform, i);
|
||||
aabb = aabb?.Union(shapeBB) ?? shapeBB;
|
||||
}
|
||||
}
|
||||
|
||||
if (aabb == null) continue;
|
||||
|
||||
worldHandle.DrawRect(aabb.Value, Color.Red.WithAlpha(AlphaModifier), false);
|
||||
}
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Joints) != 0x0)
|
||||
{
|
||||
_drawnJoints.Clear();
|
||||
|
||||
foreach (var jointComponent in _entityManager.EntityQuery<JointComponent>(true))
|
||||
{
|
||||
if (jointComponent.JointCount == 0 ||
|
||||
!_entityManager.TryGetComponent(jointComponent.Owner.Uid, out TransformComponent? xf1) ||
|
||||
!viewport.Contains(xf1.WorldPosition)) continue;
|
||||
|
||||
foreach (var (_, joint) in jointComponent.Joints)
|
||||
{
|
||||
if (_drawnJoints.Contains(joint)) continue;
|
||||
DrawJoint(worldHandle, joint);
|
||||
_drawnJoints.Add(joint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & (PhysicsDebugFlags.ContactPoints | PhysicsDebugFlags.ContactNormals)) != 0)
|
||||
{
|
||||
const float axisScale = 0.3f;
|
||||
|
||||
for (var i = 0; i < _debugPhysicsSystem.PointCount; ++i)
|
||||
{
|
||||
var point = _debugPhysicsSystem.Points[i];
|
||||
|
||||
const float radius = 0.1f;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ContactPoints) != 0)
|
||||
{
|
||||
if (point.State == PointState.Add)
|
||||
worldHandle.DrawCircle(point.Position, radius, new Color(255, 77, 243, 255));
|
||||
else if (point.State == PointState.Persist)
|
||||
worldHandle.DrawCircle(point.Position, radius, new Color(255, 77, 77, 255));
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ContactNormals) != 0)
|
||||
{
|
||||
Vector2 p1 = point.Position;
|
||||
Vector2 p2 = p1 + point.Normal * axisScale;
|
||||
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 255));
|
||||
}
|
||||
}
|
||||
|
||||
_debugPhysicsSystem.PointCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawScreen(DrawingHandleScreen screenHandle)
|
||||
{
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mousePos = _inputManager.MouseScreenPosition;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
|
||||
{
|
||||
var hoverBodies = new List<PhysicsComponent>();
|
||||
var bounds = Box2.UnitCentered.Translated(_eyeManager.ScreenToMap(mousePos.Position).Position);
|
||||
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, bounds))
|
||||
{
|
||||
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
|
||||
hoverBodies.Add(physBody);
|
||||
}
|
||||
|
||||
var lineHeight = _font.GetLineHeight(1f);
|
||||
var drawPos = mousePos.Position + new Vector2(20, 0) + new Vector2(0, -(hoverBodies.Count * 4 * lineHeight / 2f));
|
||||
int row = 0;
|
||||
|
||||
foreach (var body in hoverBodies)
|
||||
{
|
||||
if (body != hoverBodies[0])
|
||||
{
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
|
||||
row++;
|
||||
}
|
||||
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
row++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (_physics.Flags == PhysicsDebugFlags.None) return;
|
||||
if (_debugPhysicsSystem.Flags == PhysicsDebugFlags.None) return;
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.Shapes) != 0)
|
||||
switch (args.Space)
|
||||
{
|
||||
// Port DebugDrawing over.
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen((DrawingHandleScreen) args.DrawingHandle);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld((DrawingHandleWorld) args.DrawingHandle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.ContactPoints) != 0)
|
||||
private void DrawShape(DrawingHandleWorld worldHandle, Fixture fixture, Transform xform, Color color)
|
||||
{
|
||||
switch (fixture.Shape)
|
||||
{
|
||||
const float axisScale = 0.3f;
|
||||
case PhysShapeCircle circle:
|
||||
var center = Transform.Mul(xform, circle.Position);
|
||||
worldHandle.DrawCircle(center, circle.Radius, color);
|
||||
break;
|
||||
case EdgeShape edge:
|
||||
var v1 = Transform.Mul(xform, edge.Vertex1);
|
||||
var v2 = Transform.Mul(xform, edge.Vertex2);
|
||||
worldHandle.DrawLine(v1, v2, color);
|
||||
|
||||
for (int i = 0; i < _physics.PointCount; ++i)
|
||||
{
|
||||
DebugPhysicsSystem.ContactPoint point = _physics._points[i];
|
||||
|
||||
if (point.State == PointState.Add)
|
||||
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 243, 77));
|
||||
else if (point.State == PointState.Persist)
|
||||
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 77, 77));
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.ContactNormals) != 0)
|
||||
if (edge.OneSided)
|
||||
{
|
||||
Vector2 p1 = point.Position;
|
||||
Vector2 p2 = p1 + point.Normal * axisScale;
|
||||
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 102));
|
||||
worldHandle.DrawCircle(v1, 0.5f, color);
|
||||
worldHandle.DrawCircle(v2, 0.5f, color);
|
||||
}
|
||||
}
|
||||
|
||||
_physics.PointCount = 0;
|
||||
break;
|
||||
case PolygonShape poly:
|
||||
Span<Vector2> verts = stackalloc Vector2[poly.Vertices.Length];
|
||||
|
||||
for (var i = 0; i < verts.Length; i++)
|
||||
{
|
||||
verts[i] = Transform.Mul(xform, poly.Vertices[i]);
|
||||
}
|
||||
|
||||
worldHandle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, color);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJoint(DrawingHandleWorld worldHandle, Joint joint)
|
||||
{
|
||||
if (!_entityManager.TryGetComponent(joint.BodyAUid, out TransformComponent? xform1) ||
|
||||
!_entityManager.TryGetComponent(joint.BodyBUid, out TransformComponent? xform2)) return;
|
||||
|
||||
var matrix1 = xform1.WorldMatrix;
|
||||
var matrix2 = xform2.WorldMatrix;
|
||||
|
||||
var xf1 = new Vector2(matrix1.R0C2, matrix1.R1C2);
|
||||
var xf2 = new Vector2(matrix2.R0C2, matrix2.R1C2);
|
||||
|
||||
var p1 = matrix1.Transform(joint.LocalAnchorA);
|
||||
var p2 = matrix2.Transform(joint.LocalAnchorB);
|
||||
|
||||
switch (joint)
|
||||
{
|
||||
case DistanceJoint:
|
||||
worldHandle.DrawLine(xf1, xf2, JointColor);
|
||||
break;
|
||||
default:
|
||||
worldHandle.DrawLine(xf1, p1, JointColor);
|
||||
worldHandle.DrawLine(p1, p2, JointColor);
|
||||
worldHandle.DrawLine(xf2, p2, JointColor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,22 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
internal class DebugDrawingManager : IDebugDrawingManager
|
||||
internal sealed class DebugRayDrawingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTimer = default!;
|
||||
|
||||
private readonly List<RayWithLifetime> raysWithLifeTime = new();
|
||||
private readonly List<RayWithLifetime> _raysWithLifeTime = new();
|
||||
private bool _debugDrawRays;
|
||||
|
||||
private struct RayWithLifetime
|
||||
@@ -52,9 +53,30 @@ namespace Robust.Client.Debugging
|
||||
|
||||
public TimeSpan DebugRayLifetime { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
public void Initialize()
|
||||
public override void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgRay>(HandleDrawRay);
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<MsgRay>(HandleDrawRay);
|
||||
// To catch anything that's client-only and not sent by the server.
|
||||
SubscribeLocalEvent<DebugDrawRayMessage>(OnDebugDrawRay);
|
||||
}
|
||||
|
||||
private void OnDebugDrawRay(DebugDrawRayMessage ev)
|
||||
{
|
||||
if (!_debugDrawRays)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newRayWithLifetime = new RayWithLifetime
|
||||
{
|
||||
DidActuallyHit = ev.Data.Results != null,
|
||||
RayOrigin = ev.Data.Ray.Position,
|
||||
RayHit = ev.Data.Results?.HitPos ?? ev.Data.Ray.Direction * ev.Data.MaxLength + ev.Data.Ray.Position,
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime
|
||||
};
|
||||
|
||||
_raysWithLifeTime.Add(newRayWithLifetime);
|
||||
}
|
||||
|
||||
private void HandleDrawRay(MsgRay msg)
|
||||
@@ -74,15 +96,15 @@ namespace Robust.Client.Debugging
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime
|
||||
};
|
||||
|
||||
raysWithLifeTime.Add(newRayWithLifetime);
|
||||
_raysWithLifeTime.Add(newRayWithLifetime);
|
||||
}
|
||||
|
||||
private sealed class DebugDrawRayOverlay : Overlay
|
||||
{
|
||||
private readonly DebugDrawingManager _owner;
|
||||
private readonly DebugRayDrawingSystem _owner;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public DebugDrawRayOverlay(DebugDrawingManager owner)
|
||||
public DebugDrawRayOverlay(DebugRayDrawingSystem owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
@@ -90,7 +112,7 @@ namespace Robust.Client.Debugging
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
foreach (var ray in _owner.raysWithLifeTime)
|
||||
foreach (var ray in _owner._raysWithLifeTime)
|
||||
{
|
||||
handle.DrawLine(
|
||||
ray.RayOrigin,
|
||||
@@ -103,7 +125,7 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
_owner.raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
|
||||
_owner._raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,6 @@
|
||||
/// </summary>
|
||||
public interface IDebugDrawing
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of physics bodies for each entity on screen.
|
||||
/// </summary>
|
||||
bool DebugColliders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
public interface IDebugDrawingManager
|
||||
{
|
||||
bool DebugDrawRays { get; set; }
|
||||
TimeSpan DebugRayLifetime { get; set; }
|
||||
void Initialize();
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
void IClientEntityManagerInternal.InitializeEntity(IEntity entity)
|
||||
{
|
||||
EntityManager.InitializeEntity((Entity)entity);
|
||||
base.InitializeEntity((Entity)entity);
|
||||
}
|
||||
|
||||
void IClientEntityManagerInternal.StartEntity(IEntity entity)
|
||||
|
||||
@@ -120,6 +120,12 @@ namespace Robust.Client.GameObjects
|
||||
set => _visibleNested = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this pointlight should cast shadows
|
||||
/// </summary>
|
||||
[DataField("castShadows")]
|
||||
public bool CastShadows = true;
|
||||
|
||||
[DataField("nestedvisible")]
|
||||
private bool _visibleNested = true;
|
||||
[DataField("autoRot")]
|
||||
|
||||
@@ -564,7 +564,7 @@ namespace Robust.Client.GameObjects
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, Environment.StackTrace);
|
||||
}
|
||||
|
||||
return AddLayer(stateId, res?.RSI);
|
||||
return AddLayer(stateId, res?.RSI, newIndex);
|
||||
}
|
||||
|
||||
public int AddLayerState(string stateId, ResourcePath rsiPath, int? newIndex = null)
|
||||
@@ -2146,7 +2146,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
public ITransformComponent Transform { get; } = null!;
|
||||
public IMetaDataComponent MetaData { get; } = null!;
|
||||
public MetaDataComponent MetaData { get; } = null!;
|
||||
|
||||
private Dictionary<Type, IComponent> _components = new();
|
||||
private EntityLifeStage _lifeStage;
|
||||
|
||||
@@ -12,21 +12,20 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AudioSystem : EntitySystem, IAudioSystem
|
||||
public class AudioSystem : SharedAudioSystem, IAudioSystem
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IClydeAudio _clyde = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _broadPhaseSystem = default!;
|
||||
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
|
||||
@@ -65,7 +64,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
var stream = (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.AudioParams);
|
||||
var stream = (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams);
|
||||
if (stream != null)
|
||||
{
|
||||
stream.NetIdentifier = ev.Identifier;
|
||||
@@ -84,8 +83,8 @@ namespace Robust.Client.GameObjects
|
||||
private void PlayAudioEntityHandler(PlayAudioEntityMessage ev)
|
||||
{
|
||||
var stream = EntityManager.TryGetEntity(ev.EntityUid, out var entity) ?
|
||||
(PlayingStream?) Play(ev.FileName, entity, ev.AudioParams)
|
||||
: (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.AudioParams);
|
||||
(PlayingStream?) Play(ev.FileName, entity, ev.FallbackCoordinates, ev.AudioParams)
|
||||
: (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams);
|
||||
|
||||
if (stream != null)
|
||||
{
|
||||
@@ -135,6 +134,10 @@ namespace Robust.Client.GameObjects
|
||||
mapPos = stream.TrackingEntity.Transform.MapPosition;
|
||||
}
|
||||
|
||||
// TODO Remove when coordinates can't be NaN
|
||||
if (mapPos == null || !float.IsFinite(mapPos.Value.X) || !float.IsFinite(mapPos.Value.Y))
|
||||
mapPos = stream.TrackingFallbackCoordinates?.ToMap(_entityManager);
|
||||
|
||||
if (mapPos != null)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
@@ -212,7 +215,6 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
stream.Source.SetVelocity(stream.TrackingEntity.GlobalLinearVelocity());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,12 +279,14 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when entity is invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(string filename, IEntity entity, AudioParams? audioParams = null)
|
||||
private IPlayingAudioStream? Play(string filename, IEntity entity, EntityCoordinates fallbackCoordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
|
||||
{
|
||||
return Play(audio, entity, audioParams);
|
||||
return Play(audio, entity, fallbackCoordinates, audioParams);
|
||||
}
|
||||
|
||||
Logger.Error($"Server tried to play audio file {filename} which does not exist.");
|
||||
@@ -294,8 +298,10 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when entity is invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(AudioStream stream, IEntity entity, AudioParams? audioParams = null)
|
||||
private IPlayingAudioStream? Play(AudioStream stream, IEntity entity, EntityCoordinates fallbackCoordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
var source = _clyde.CreateAudioSource(stream);
|
||||
if (!source.SetPosition(entity.Transform.WorldPosition))
|
||||
@@ -312,6 +318,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Source = source,
|
||||
TrackingEntity = entity,
|
||||
TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null,
|
||||
Attenuation = audioParams?.Attenuation ?? Attenuation.Default,
|
||||
MaxDistance = audioParams?.MaxDistance ?? float.MaxValue,
|
||||
ReferenceDistance = audioParams?.ReferenceDistance ?? 1f,
|
||||
@@ -327,12 +334,14 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when coordinates are invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, EntityCoordinates fallbackCoordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
|
||||
{
|
||||
return Play(audio, coordinates, audioParams);
|
||||
return Play(audio, coordinates, fallbackCoordinates, audioParams);
|
||||
}
|
||||
|
||||
Logger.Error($"Server tried to play audio file {filename} which does not exist.");
|
||||
@@ -344,18 +353,24 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when coordinates are invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
EntityCoordinates fallbackCoordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
var source = _clyde.CreateAudioSource(stream);
|
||||
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
|
||||
if (!source.SetPosition(fallbackCoordinates.Position))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!coordinates.IsValid(_entityManager))
|
||||
{
|
||||
coordinates = fallbackCoordinates;
|
||||
}
|
||||
|
||||
ApplyAudioParams(audioParams, source);
|
||||
|
||||
source.StartPlaying();
|
||||
@@ -363,6 +378,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Source = source,
|
||||
TrackingCoordinates = coordinates,
|
||||
TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null,
|
||||
Attenuation = audioParams?.Attenuation ?? Attenuation.Default,
|
||||
MaxDistance = audioParams?.MaxDistance ?? float.MaxValue,
|
||||
ReferenceDistance = audioParams?.ReferenceDistance ?? 1f,
|
||||
@@ -395,6 +411,7 @@ namespace Robust.Client.GameObjects
|
||||
public IClydeAudioSource Source = default!;
|
||||
public IEntity TrackingEntity = default!;
|
||||
public EntityCoordinates? TrackingCoordinates;
|
||||
public EntityCoordinates? TrackingFallbackCoordinates;
|
||||
public bool Done;
|
||||
public float Volume;
|
||||
|
||||
@@ -440,19 +457,19 @@ namespace Robust.Client.GameObjects
|
||||
/// <inheritdoc />
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, entity, audioParams);
|
||||
return Play(filename, entity, GetFallbackCoordinates(entity.Transform.MapPosition), audioParams);
|
||||
}
|
||||
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return EntityManager.TryGetEntity(uid, out var entity)
|
||||
? Play(filename, entity, audioParams) : null;
|
||||
? Play(filename, entity, GetFallbackCoordinates(entity.Transform.MapPosition), audioParams) : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, coordinates, audioParams);
|
||||
return Play(filename, coordinates, GetFallbackCoordinates(coordinates.ToMap(_entityManager)), audioParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@@ -28,6 +29,9 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
private bool _isLerping = false;
|
||||
private ITransformComponent? _lastParent;
|
||||
|
||||
private float _accumulator;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
@@ -59,27 +63,6 @@ namespace Robust.Client.GameObjects
|
||||
var currentEye = _eyeManager.CurrentEye;
|
||||
|
||||
// TODO: Content should have its own way of handling this. We should have a default behavior that they can overwrite.
|
||||
/*
|
||||
var inputSystem = EntitySystemManager.GetEntitySystem<InputSystem>();
|
||||
|
||||
var direction = 0;
|
||||
if (inputSystem.CmdStates[EngineKeyFunctions.CameraRotateRight] == BoundKeyState.Down)
|
||||
{
|
||||
direction += 1;
|
||||
}
|
||||
|
||||
if (inputSystem.CmdStates[EngineKeyFunctions.CameraRotateLeft] == BoundKeyState.Down)
|
||||
{
|
||||
direction -= 1;
|
||||
}
|
||||
|
||||
// apply camera rotation
|
||||
if(direction != 0)
|
||||
{
|
||||
currentEye.Rotation += CameraRotateSpeed * frameTime * direction;
|
||||
currentEye.Rotation = currentEye.Rotation.Reduced();
|
||||
}
|
||||
*/
|
||||
|
||||
var playerTransform = _playerManager.LocalPlayer?.ControlledEntity?.Transform;
|
||||
|
||||
@@ -91,7 +74,22 @@ namespace Robust.Client.GameObjects
|
||||
gridEnt.Transform
|
||||
: _mapManager.GetMapEntity(playerTransform.MapID).Transform;
|
||||
|
||||
if (!_isLerping)
|
||||
if (parent != _lastParent)
|
||||
{
|
||||
_accumulator += frameTime;
|
||||
|
||||
if (_accumulator >= 0.3f)
|
||||
{
|
||||
_accumulator = 0f;
|
||||
_lastParent = parent;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastParent = parent;
|
||||
}
|
||||
|
||||
if (_lastParent == parent)
|
||||
{
|
||||
// TODO: Detect parent change and start lerping
|
||||
var parentRotation = parent.WorldRotation;
|
||||
|
||||
@@ -266,10 +266,10 @@ namespace Robust.Client.GameStates
|
||||
|
||||
DebugTools.Assert(_timing.InSimulation);
|
||||
|
||||
if (!Predicting) return;
|
||||
|
||||
using(var _ = _timing.StartPastPredictionArea())
|
||||
if (Predicting)
|
||||
{
|
||||
using var _ = _timing.StartPastPredictionArea();
|
||||
|
||||
if (_pendingInputs.Count > 0)
|
||||
{
|
||||
Logger.DebugS(CVars.NetPredict.Name, "CL> Predicted:");
|
||||
@@ -412,64 +412,57 @@ namespace Robust.Client.GameStates
|
||||
private List<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
|
||||
{
|
||||
_config.TickProcessMessages();
|
||||
_mapManager.ApplyGameStatePre(curState.MapData, curState.EntityStates);
|
||||
var createdEntities = ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
|
||||
nextState?.EntityStates);
|
||||
_players.ApplyPlayerStates(curState.PlayerStates);
|
||||
_mapManager.ApplyGameStatePre(curState.MapData, curState.EntityStates.Span);
|
||||
var createdEntities = ApplyEntityStates(curState.EntityStates.Span, curState.EntityDeletions.Span,
|
||||
nextState != null ? nextState.EntityStates.Span : default);
|
||||
_players.ApplyPlayerStates(curState.PlayerStates.Value ?? Array.Empty<PlayerState>());
|
||||
_mapManager.ApplyGameStatePost(curState.MapData);
|
||||
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState));
|
||||
return createdEntities;
|
||||
}
|
||||
|
||||
private List<EntityUid> ApplyEntityStates(EntityState[]? curEntStates, IEnumerable<EntityUid>? deletions,
|
||||
EntityState[]? nextEntStates)
|
||||
private List<EntityUid> ApplyEntityStates(ReadOnlySpan<EntityState> curEntStates, ReadOnlySpan<EntityUid> deletions,
|
||||
ReadOnlySpan<EntityState> nextEntStates)
|
||||
{
|
||||
var toApply = new Dictionary<IEntity, (EntityState?, EntityState?)>();
|
||||
var toInitialize = new List<Entity>();
|
||||
var created = new List<EntityUid>();
|
||||
deletions ??= new EntityUid[0];
|
||||
|
||||
if (curEntStates != null && curEntStates.Length != 0)
|
||||
foreach (var es in curEntStates)
|
||||
{
|
||||
foreach (var es in curEntStates)
|
||||
//Known entities
|
||||
if (_entities.TryGetEntity(es.Uid, out var entity))
|
||||
{
|
||||
//Known entities
|
||||
if (_entities.TryGetEntity(es.Uid, out var entity))
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] MOD {es.Uid}");
|
||||
toApply.Add(entity, (es, null));
|
||||
}
|
||||
else //Unknown entities
|
||||
{
|
||||
var metaState = (MetaDataComponentState?) es.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
|
||||
if (metaState == null)
|
||||
{
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] MOD {es.Uid}");
|
||||
toApply.Add(entity, (es, null));
|
||||
}
|
||||
else //Unknown entities
|
||||
{
|
||||
var metaState = (MetaDataComponentState?) es.ComponentChanges?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
|
||||
if (metaState == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
|
||||
}
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] CREATE {es.Uid} {metaState.PrototypeId}");
|
||||
var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid);
|
||||
toApply.Add(newEntity, (es, null));
|
||||
toInitialize.Add(newEntity);
|
||||
created.Add(newEntity.Uid);
|
||||
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
|
||||
}
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] CREATE {es.Uid} {metaState.PrototypeId}");
|
||||
var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid);
|
||||
toApply.Add(newEntity, (es, null));
|
||||
toInitialize.Add(newEntity);
|
||||
created.Add(newEntity.Uid);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextEntStates != null && nextEntStates.Length != 0)
|
||||
foreach (var es in nextEntStates)
|
||||
{
|
||||
foreach (var es in nextEntStates)
|
||||
if (_entities.TryGetEntity(es.Uid, out var entity))
|
||||
{
|
||||
if (_entities.TryGetEntity(es.Uid, out var entity))
|
||||
if (toApply.TryGetValue(entity, out var state))
|
||||
{
|
||||
if (toApply.TryGetValue(entity, out var state))
|
||||
{
|
||||
toApply[entity] = (state.Item1, es);
|
||||
}
|
||||
else
|
||||
{
|
||||
toApply[entity] = (null, es);
|
||||
}
|
||||
toApply[entity] = (state.Item1, es);
|
||||
}
|
||||
else
|
||||
{
|
||||
toApply[entity] = (null, es);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -553,9 +546,9 @@ namespace Robust.Client.GameStates
|
||||
var compStateWork = new Dictionary<ushort, (ComponentState? curState, ComponentState? nextState)>();
|
||||
var entityUid = entity.Uid;
|
||||
|
||||
if (curState?.ComponentChanges != null)
|
||||
if (curState != null)
|
||||
{
|
||||
foreach (var compChange in curState.ComponentChanges)
|
||||
foreach (var compChange in curState.ComponentChanges.Span)
|
||||
{
|
||||
if (compChange.Deleted)
|
||||
{
|
||||
@@ -578,19 +571,16 @@ namespace Robust.Client.GameStates
|
||||
compStateWork[compChange.NetID] = (compChange.State, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (curState?.ComponentChanges != null)
|
||||
{
|
||||
foreach (var compChange in curState.ComponentChanges)
|
||||
foreach (var compChange in curState.ComponentChanges.Span)
|
||||
{
|
||||
compStateWork[compChange.NetID] = (compChange.State, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextState?.ComponentChanges != null)
|
||||
if (nextState != null)
|
||||
{
|
||||
foreach (var compState in nextState.ComponentChanges)
|
||||
foreach (var compState in nextState.ComponentChanges.Span)
|
||||
{
|
||||
if (compStateWork.TryGetValue(compState.NetID, out var state))
|
||||
{
|
||||
|
||||
@@ -168,38 +168,29 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state.EntityDeletions != null)
|
||||
foreach (var deletion in state.EntityDeletions.Span)
|
||||
{
|
||||
foreach (var deletion in state.EntityDeletions)
|
||||
{
|
||||
_lastStateFullRep.Remove(deletion);
|
||||
}
|
||||
_lastStateFullRep.Remove(deletion);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.EntityStates != null)
|
||||
foreach (var entityState in state.EntityStates.Span)
|
||||
{
|
||||
foreach (var entityState in state.EntityStates)
|
||||
if (!_lastStateFullRep.TryGetValue(entityState.Uid, out var compData))
|
||||
{
|
||||
if (!_lastStateFullRep.TryGetValue(entityState.Uid, out var compData))
|
||||
{
|
||||
compData = new Dictionary<uint, ComponentState>();
|
||||
_lastStateFullRep.Add(entityState.Uid, compData);
|
||||
}
|
||||
compData = new Dictionary<uint, ComponentState>();
|
||||
_lastStateFullRep.Add(entityState.Uid, compData);
|
||||
}
|
||||
|
||||
if (entityState.ComponentChanges != null)
|
||||
foreach (var change in entityState.ComponentChanges.Span)
|
||||
{
|
||||
if (change.Deleted)
|
||||
{
|
||||
foreach (var change in entityState.ComponentChanges)
|
||||
{
|
||||
if (change.Deleted)
|
||||
{
|
||||
compData.Remove(change.NetID);
|
||||
}
|
||||
else if (change.State is not null)
|
||||
{
|
||||
compData[change.NetID] = change.State;
|
||||
}
|
||||
}
|
||||
compData.Remove(change.NetID);
|
||||
}
|
||||
else if (change.State is not null)
|
||||
{
|
||||
compData[change.NetID] = change.State;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,7 +343,7 @@ namespace Robust.Client.GameStates
|
||||
/// </summary>
|
||||
private static GameState ExtrapolateState(GameTick fromSequence, GameTick toSequence, uint lastInput)
|
||||
{
|
||||
var state = new GameState(fromSequence, toSequence, lastInput, null, null, null, null);
|
||||
var state = new GameState(fromSequence, toSequence, lastInput, default, default, default, null);
|
||||
state.Extrapolated = true;
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -65,10 +65,10 @@ namespace Robust.Client.GameStates
|
||||
|
||||
var gameState = args.AppliedState;
|
||||
|
||||
if(gameState.EntityStates is not null)
|
||||
if(gameState.EntityStates.HasContents)
|
||||
{
|
||||
// Loop over every entity that gets updated this state and record the traffic
|
||||
foreach (var entityState in gameState.EntityStates)
|
||||
foreach (var entityState in gameState.EntityStates.Span)
|
||||
{
|
||||
var newEnt = true;
|
||||
for(var i=0;i<_netEnts.Count;i++)
|
||||
|
||||
@@ -79,17 +79,17 @@ namespace Robust.Client.GameStates
|
||||
var conShell = IoCManager.Resolve<IConsoleHost>().LocalShell;
|
||||
|
||||
var entStates = args.AppliedState.EntityStates;
|
||||
if (entStates is not null)
|
||||
if (entStates.HasContents)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var entState in entStates)
|
||||
foreach (var entState in entStates.Span)
|
||||
{
|
||||
if (entState.Uid == WatchEntId)
|
||||
{
|
||||
if(entState.ComponentChanges is not null)
|
||||
if(entState.ComponentChanges.HasContents)
|
||||
{
|
||||
sb.Append($"\n Changes:");
|
||||
foreach (var compChange in entState.ComponentChanges)
|
||||
foreach (var compChange in entState.ComponentChanges.Span)
|
||||
{
|
||||
var registration = _componentFactory.GetRegistration(compChange.NetID);
|
||||
var create = compChange.Created ? 'C' : '\0';
|
||||
@@ -107,10 +107,10 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
var entDeletes = args.AppliedState.EntityDeletions;
|
||||
if (entDeletes is not null)
|
||||
if (entDeletes.HasContents)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var entDelete in entDeletes)
|
||||
foreach (var entDelete in entDeletes.Span)
|
||||
{
|
||||
if (entDelete == WatchEntId)
|
||||
{
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace Robust.Client.Graphics
|
||||
/// <returns>Corresponding point in UI screen space.</returns>
|
||||
ScreenCoordinates CoordinatesToScreen(EntityCoordinates point);
|
||||
|
||||
ScreenCoordinates MapToScreen(MapCoordinates point);
|
||||
|
||||
/// <summary>
|
||||
/// Unprojects a point from UI screen space to world space using the viewport under the screen coordinates.
|
||||
/// </summary>
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private bool _hasGLSamplerObjects;
|
||||
private bool _hasGLSrgb;
|
||||
private bool _hasGLPrimitiveRestart;
|
||||
private bool _hasGLPrimitiveRestartFixedIndex;
|
||||
private bool _hasGLReadFramebuffer;
|
||||
private bool _hasGLUniformBuffers;
|
||||
private bool HasGLAnyVertexArrayObjects => _hasGLVertexArrayObject || _hasGLVertexArrayObjectOes;
|
||||
@@ -105,7 +106,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
CheckGLCap(ref _hasGLPixelBufferObjects, "pixel_buffer_object", (3, 0));
|
||||
CheckGLCap(ref _hasGLStandardDerivatives, "standard_derivatives", (3, 0), "GL_OES_standard_derivatives");
|
||||
CheckGLCap(ref _hasGLReadFramebuffer, "read_framebuffer", (3, 0));
|
||||
CheckGLCap(ref _hasGLPrimitiveRestart, "primitive_restart", (3, 1));
|
||||
CheckGLCap(ref _hasGLPrimitiveRestartFixedIndex, "primitive_restart", (3, 0));
|
||||
CheckGLCap(ref _hasGLUniformBuffers, "uniform_buffers", (3, 0));
|
||||
CheckGLCap(ref _hasGLFloatFramebuffers, "float_framebuffers", (3, 2), "GL_EXT_color_buffer_float");
|
||||
CheckGLCap(ref _hasGLES3Shaders, "gles3_shaders", (3, 0));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -51,8 +52,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var transform = _entityManager.GetComponent<ITransformComponent>(grid.GridEntityId);
|
||||
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
|
||||
grid.GetMapChunks(worldBounds, out var enumerator);
|
||||
|
||||
foreach (var chunk in grid.GetMapChunks(worldBounds))
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
if (_isChunkDirty(grid, chunk))
|
||||
{
|
||||
@@ -85,50 +87,39 @@ namespace Robust.Client.Graphics.Clyde
|
||||
datum = _initChunkBuffers(grid, chunk);
|
||||
}
|
||||
|
||||
var vertexPool = ArrayPool<Vertex2D>.Shared;
|
||||
var indexPool = ArrayPool<ushort>.Shared;
|
||||
Span<ushort> indexBuffer = stackalloc ushort[_indicesPerChunk(chunk)];
|
||||
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
|
||||
|
||||
var vertexBuffer = vertexPool.Rent(_verticesPerChunk(chunk));
|
||||
var indexBuffer = indexPool.Rent(_indicesPerChunk(chunk));
|
||||
|
||||
try
|
||||
var i = 0;
|
||||
foreach (var tile in chunk)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var tile in chunk)
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.Tile);
|
||||
if (regionMaybe == null)
|
||||
{
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.Tile);
|
||||
if (regionMaybe == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var region = regionMaybe.Value;
|
||||
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(tile.X, tile.Y, region.Left, region.Bottom);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(tile.X + 1, tile.Y, region.Right, region.Bottom);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(tile.X + 1, tile.Y + 1, region.Right, region.Top);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(tile.X, tile.Y + 1, region.Left, region.Top);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort) (i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
GL.BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
datum.EBO.Use();
|
||||
datum.VBO.Use();
|
||||
datum.EBO.Reallocate(new Span<ushort>(indexBuffer, 0, i * GetQuadBatchIndexCount()));
|
||||
datum.VBO.Reallocate(new Span<Vertex2D>(vertexBuffer, 0, i * 4));
|
||||
datum.Dirty = false;
|
||||
datum.TileCount = i;
|
||||
}
|
||||
finally
|
||||
{
|
||||
vertexPool.Return(vertexBuffer);
|
||||
indexPool.Return(indexBuffer);
|
||||
var region = regionMaybe.Value;
|
||||
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(tile.X, tile.Y, region.Left, region.Bottom);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(tile.X + 1, tile.Y, region.Right, region.Bottom);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(tile.X + 1, tile.Y + 1, region.Right, region.Top);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(tile.X, tile.Y + 1, region.Left, region.Top);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort) (i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
GL.BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
datum.EBO.Use();
|
||||
datum.VBO.Use();
|
||||
datum.EBO.Reallocate(indexBuffer[..(i * GetQuadBatchIndexCount())]);
|
||||
datum.VBO.Reallocate(vertexBuffer[..(i * 4)]);
|
||||
datum.Dirty = false;
|
||||
datum.TileCount = i;
|
||||
}
|
||||
|
||||
private MapChunkData _initChunkBuffers(IMapGrid grid, IMapChunk chunk)
|
||||
|
||||
@@ -118,6 +118,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
overlay.ClydeRender(_renderHandle, space, null, vp, new UIBox2i((0, 0), vp.Size), worldBox);
|
||||
}
|
||||
|
||||
FlushRenderQueue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,8 +214,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB);
|
||||
|
||||
var screenSize = viewport.Size;
|
||||
eye.GetViewMatrix(out var eyeMatrix, eye.Scale);
|
||||
|
||||
ProcessSpriteEntities(mapId, worldBounds, _drawingSpriteList);
|
||||
ProcessSpriteEntities(mapId, eyeMatrix, worldBounds, _drawingSpriteList);
|
||||
|
||||
var worldOverlays = new List<Overlay>();
|
||||
|
||||
@@ -362,7 +365,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void ProcessSpriteEntities(MapId map, Box2Rotated worldBounds,
|
||||
private void ProcessSpriteEntities(MapId map, Matrix3 eyeMatrix, Box2Rotated worldBounds,
|
||||
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
|
||||
{
|
||||
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
|
||||
@@ -380,10 +383,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
entry.sprite = value;
|
||||
entry.worldRot = transform.WorldRotation;
|
||||
entry.matrix = transform.WorldMatrix;
|
||||
var worldPos = new Vector2(entry.matrix.R0C2, entry.matrix.R1C2);
|
||||
var eyePos = eyeMatrix.Transform(new Vector2(entry.matrix.R0C2, entry.matrix.R1C2));
|
||||
// Didn't use the bounds from the query as that has to be re-calculated (and is probably more expensive than this).
|
||||
var bounds = value.CalculateBoundingBox(worldPos);
|
||||
entry.yWorldPos = worldPos.Y - bounds.Extents.Y;
|
||||
var bounds = value.CalculateBoundingBox(eyePos);
|
||||
entry.yWorldPos = eyePos.Y - bounds.Extents.Y;
|
||||
return true;
|
||||
|
||||
}, bounds, true);
|
||||
@@ -469,7 +472,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowWorld, worldAABB);
|
||||
FlushRenderQueue();
|
||||
|
||||
using (DebugGroup("Grids"))
|
||||
{
|
||||
@@ -483,7 +485,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldAABB);
|
||||
FlushRenderQueue();
|
||||
|
||||
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
|
||||
{
|
||||
@@ -517,7 +518,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB);
|
||||
FlushRenderQueue();
|
||||
|
||||
_currentViewport = oldVp;
|
||||
});
|
||||
|
||||
@@ -361,6 +361,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var (light, lightPos, _) = lights[i];
|
||||
|
||||
if (!light.CastShadows) continue;
|
||||
|
||||
DrawOcclusionDepth(lightPos, ShadowMapSize, light.Radius, i);
|
||||
}
|
||||
}
|
||||
@@ -459,7 +461,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
lightShader.SetUniformMaybe("lightCenter", lightPos);
|
||||
lightShader.SetUniformMaybe("lightIndex", (i + 0.5f) / ShadowTexture.Height);
|
||||
lightShader.SetUniformMaybe("lightIndex", component.CastShadows ? (i + 0.5f) / ShadowTexture.Height : -1);
|
||||
|
||||
var offset = new Vector2(component.Radius, component.Radius);
|
||||
|
||||
@@ -749,9 +751,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
SetTexture(TextureUnit.Texture0, FovTexture);
|
||||
|
||||
fovShader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
||||
fovShader.SetUniformMaybe("center", eye.Position.Position);
|
||||
|
||||
DrawBlit(viewport, fovShader);
|
||||
FovSetTransformAndBlit(viewport, eye.Position.Position, fovShader);
|
||||
|
||||
GL.StencilMask(0x00);
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
@@ -786,7 +787,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
fovShader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
||||
fovShader.SetUniformMaybe("center", eye.Position.Position);
|
||||
|
||||
GL.StencilMask(0xFF);
|
||||
CheckGlError();
|
||||
@@ -795,7 +795,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, TKStencilOp.Replace);
|
||||
CheckGlError();
|
||||
|
||||
DrawBlit(viewport, fovShader);
|
||||
FovSetTransformAndBlit(viewport, eye.Position.Position, fovShader);
|
||||
|
||||
if (_hasGLSamplerObjects)
|
||||
{
|
||||
@@ -811,12 +811,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBlit(Viewport vp, GLShaderProgram shader)
|
||||
private void FovSetTransformAndBlit(Viewport vp, Vector2 fovCentre, GLShaderProgram fovShader)
|
||||
{
|
||||
var a = ScreenToMap((-1, -1), vp);
|
||||
var b = ScreenToMap(vp.Size + Vector2i.One, vp);
|
||||
// It might be an idea if there was a proper way to get the LocalToWorld matrix.
|
||||
// But actually constructing the matrix tends to be more trouble than it's worth in most cases.
|
||||
// (Maybe if there was some way to observe Eye matrix changes that wouldn't be the case, as viewport could dynamically update.)
|
||||
// This is expected to run a grand total of twice per frame for 6 LocalToWorld calls.
|
||||
// Something else to note is that modifications must be made anyway.
|
||||
|
||||
_drawQuad(a, b, Matrix3.Identity, shader);
|
||||
// Something ELSE to note is that it's absolutely critical that this be calculated in the "right way" due to precision issues!
|
||||
|
||||
// Bit of an interesting little trick here - need to set things up correctly.
|
||||
// 0, 0 in clip-space is the centre of the screen, and 1, 1 is the top-right corner.
|
||||
var halfSize = vp.Size / 2.0f;
|
||||
var uZero = vp.LocalToWorld(halfSize).Position;
|
||||
var uX = vp.LocalToWorld(halfSize + (Vector2.UnitX * halfSize.X)).Position - uZero;
|
||||
var uY = vp.LocalToWorld(halfSize - (Vector2.UnitY * halfSize.Y)).Position - uZero;
|
||||
|
||||
// Second modification is that output must be fov-centred (difference-space)
|
||||
uZero -= fovCentre;
|
||||
|
||||
var clipToDiff = new Matrix3(ref uX, ref uY, ref uZero);
|
||||
|
||||
fovShader.SetUniformMaybe("clipToDiff", clipToDiff);
|
||||
_drawQuad(Vector2.Zero, Vector2.One, Matrix3.Identity, fovShader);
|
||||
}
|
||||
|
||||
private void UpdateOcclusionGeometry(MapId map, Box2 expandedBounds, Matrix3 eyeTransform)
|
||||
|
||||
@@ -376,7 +376,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
int divisions = Math.Max(16,(int)(radius * 16));
|
||||
float arcLength = MathF.PI * 2 / divisions;
|
||||
|
||||
var filledTriangle = new Vector2[3];
|
||||
Span<Vector2> filledTriangle = stackalloc Vector2[3];
|
||||
|
||||
// Draws a "circle", but its just a polygon with a bunch of sides
|
||||
// this is the GL_LINES version, not GL_LINE_STRIP
|
||||
@@ -389,9 +389,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle.DrawLine(startPos, endPos, color);
|
||||
else
|
||||
{
|
||||
filledTriangle[0] = startPos;
|
||||
filledTriangle[1] = endPos;
|
||||
filledTriangle[2] = Vector2.Zero;
|
||||
filledTriangle[0] = startPos + position;
|
||||
filledTriangle[1] = endPos + position;
|
||||
filledTriangle[2] = Vector2.Zero + position;
|
||||
|
||||
_renderHandle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, filledTriangle, color);
|
||||
}
|
||||
|
||||
@@ -273,7 +273,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public Vector2i Size;
|
||||
public bool IsSrgb;
|
||||
|
||||
#pragma warning disable 649
|
||||
// Gets assigned by (currently commented out) GLContextAngle.
|
||||
// It's fine don't worry about it.
|
||||
public bool FlipY;
|
||||
#pragma warning restore 649
|
||||
|
||||
public RTCF ColorFormat;
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private WindowReg? _mainWindow;
|
||||
|
||||
private IWindowingImpl? _windowing;
|
||||
#pragma warning disable 414
|
||||
// Keeping this for if/when we ever get a new renderer.
|
||||
private Renderer _chosenRenderer;
|
||||
#pragma warning restore 414
|
||||
|
||||
private ResourcePath? _windowIconPath;
|
||||
private Thread? _windowingThread;
|
||||
|
||||
@@ -206,6 +206,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.PrimitiveRestartIndex(PrimitiveRestartIndex);
|
||||
CheckGlError();
|
||||
}
|
||||
if (_hasGLPrimitiveRestartFixedIndex)
|
||||
{
|
||||
GL.Enable(EnableCap.PrimitiveRestartFixedIndex);
|
||||
CheckGlError();
|
||||
}
|
||||
if (!HasGLAnyVertexArrayObjects)
|
||||
{
|
||||
_sawmillOgl.Warning("NO VERTEX ARRAY OBJECTS! Things will probably go terribly, terribly wrong (no fallback path yet)");
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (_clyde._windowMode == WindowMode.Fullscreen)
|
||||
{
|
||||
win.PrevWindowSize = win.WindowSize;
|
||||
win.PrevWindowPos = win.PrevWindowPos;
|
||||
win.PrevWindowPos = win.WindowPos;
|
||||
|
||||
SendCmd(new CmdWinSetFullscreen((nint) win.GlfwWindow));
|
||||
}
|
||||
@@ -262,7 +262,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var (reg, errorResult) = task.Result;
|
||||
|
||||
if (reg != null)
|
||||
{
|
||||
reg.Owner = reg.Handle;
|
||||
return (reg, null);
|
||||
}
|
||||
|
||||
var (desc, errCode) = errorResult!.Value;
|
||||
return (null, $"[{errCode}]: {desc}");
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Map
|
||||
{
|
||||
internal class ClientMapManager : MapManager, IClientMapManager
|
||||
{
|
||||
public void ApplyGameStatePre(GameStateMapData? data, EntityState[]? entityStates)
|
||||
public void ApplyGameStatePre(GameStateMapData? data, ReadOnlySpan<EntityState> entityStates)
|
||||
{
|
||||
// There was no map data this tick, so nothing to do.
|
||||
if(data == null)
|
||||
@@ -20,7 +20,7 @@ namespace Robust.Client.Map
|
||||
// First we need to figure out all the NEW MAPS.
|
||||
if(data.CreatedMaps != null)
|
||||
{
|
||||
DebugTools.Assert(entityStates is not null, "Received new maps, but no entity state.");
|
||||
DebugTools.Assert(!entityStates.IsEmpty, "Received new maps, but no entity state.");
|
||||
|
||||
foreach (var mapId in data.CreatedMaps)
|
||||
{
|
||||
@@ -33,10 +33,7 @@ namespace Robust.Client.Map
|
||||
//get shared euid of map comp entity
|
||||
foreach (var entityState in entityStates!)
|
||||
{
|
||||
if(entityState.ComponentChanges is null)
|
||||
continue;
|
||||
|
||||
foreach (var compChange in entityState.ComponentChanges)
|
||||
foreach (var compChange in entityState.ComponentChanges.Span)
|
||||
{
|
||||
if (compChange.State is not MapComponentState mapCompState || mapCompState.MapId != mapId)
|
||||
continue;
|
||||
@@ -68,10 +65,7 @@ namespace Robust.Client.Map
|
||||
//get shared euid of map comp entity
|
||||
foreach (var entityState in entityStates!)
|
||||
{
|
||||
if (entityState.ComponentChanges is null)
|
||||
continue;
|
||||
|
||||
foreach (var compState in entityState.ComponentChanges)
|
||||
foreach (var compState in entityState.ComponentChanges.Span)
|
||||
{
|
||||
if (compState.State is not MapGridComponentState gridCompState || gridCompState.GridIndex != gridId)
|
||||
continue;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
@@ -8,7 +9,7 @@ namespace Robust.Client.Map
|
||||
{
|
||||
// Two methods here, so that new grids etc can be made BEFORE entities get states applied,
|
||||
// but old ones can be deleted after.
|
||||
void ApplyGameStatePre(GameStateMapData? data, EntityState[]? entityStates);
|
||||
void ApplyGameStatePre(GameStateMapData? data, ReadOnlySpan<EntityState> entityStates);
|
||||
void ApplyGameStatePost(GameStateMapData? data);
|
||||
}
|
||||
}
|
||||
|
||||
68
Robust.Client/Physics/JointSystem.cs
Normal file
68
Robust.Client/Physics/JointSystem.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
|
||||
namespace Robust.Client.Physics
|
||||
{
|
||||
public sealed class JointSystem : SharedJointSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<JointComponent, ComponentHandleState>(HandleComponentState);
|
||||
}
|
||||
|
||||
private void HandleComponentState(EntityUid uid, JointComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not JointComponent.JointComponentState jointState) return;
|
||||
|
||||
var changed = new List<string>();
|
||||
|
||||
foreach (var (existing, _) in component.Joints)
|
||||
{
|
||||
if (!jointState.Joints.ContainsKey(existing))
|
||||
{
|
||||
changed.Add(existing);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var removed in changed)
|
||||
{
|
||||
RemoveJoint(component.Joints[removed]);
|
||||
}
|
||||
|
||||
foreach (var (id, state) in jointState.Joints)
|
||||
{
|
||||
Joint joint;
|
||||
|
||||
if (!component.Joints.ContainsKey(id))
|
||||
{
|
||||
// Add new joint (if possible).
|
||||
// Need to wait for BOTH joint components to come in first before we can add it. Yay dependencies!
|
||||
if (!EntityManager.HasComponent<JointComponent>(state.UidA) ||
|
||||
!EntityManager.HasComponent<JointComponent>(state.UidB)) continue;
|
||||
|
||||
joint = state.GetJoint();
|
||||
AddJoint(joint);
|
||||
continue;
|
||||
}
|
||||
|
||||
joint = state.GetJoint();
|
||||
var existing = component.Joints[id];
|
||||
|
||||
if (!existing.Equals(joint))
|
||||
{
|
||||
existing.ApplyState(state);
|
||||
component.Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (changed.Count > 0)
|
||||
{
|
||||
component.Dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,8 @@ using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
public class SnapgridBorder : PlacementMode
|
||||
public class SnapgridBorder : SnapgridCenter
|
||||
{
|
||||
private bool onGrid;
|
||||
private float snapSize;
|
||||
|
||||
public override bool HasLineMode => true;
|
||||
public override bool HasGridMode => true;
|
||||
|
||||
@@ -17,79 +14,33 @@ namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
}
|
||||
|
||||
public override void Render(DrawingHandleWorld handle)
|
||||
{
|
||||
if (onGrid)
|
||||
{
|
||||
const int ppm = EyeManager.PixelsPerMeter;
|
||||
var viewportSize = (Vector2)pManager._clyde.ScreenSize;
|
||||
|
||||
var position = pManager.eyeManager.ScreenToMap(Vector2.Zero);
|
||||
|
||||
var gridstartx = (float) MathF.Round(position.X / snapSize, MidpointRounding.AwayFromZero) * snapSize;
|
||||
var gridstarty = (float) MathF.Round(position.Y / snapSize, MidpointRounding.AwayFromZero) * snapSize;
|
||||
var gridstart = pManager.eyeManager.WorldToScreen(
|
||||
new Vector2( //Find snap grid closest to screen origin and convert back to screen coords
|
||||
gridstartx,
|
||||
gridstarty));
|
||||
for (var a = gridstart.X;
|
||||
a < viewportSize.X;
|
||||
a += snapSize * ppm) //Iterate through screen creating gridlines
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(a, 0));
|
||||
var to = ScreenToWorld(new Vector2(a, viewportSize.Y));
|
||||
handle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
}
|
||||
|
||||
for (var a = gridstart.Y; a < viewportSize.Y; a += snapSize * ppm)
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(0, a));
|
||||
var to = ScreenToWorld(new Vector2(viewportSize.X, a));
|
||||
handle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw grid BELOW the ghost.
|
||||
base.Render(handle);
|
||||
}
|
||||
|
||||
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||
{
|
||||
MouseCoords = ScreenToCursorGrid(mouseScreen);
|
||||
|
||||
snapSize = 1f;
|
||||
|
||||
var gridId = MouseCoords.GetGridId(pManager.EntityManager);
|
||||
SnapSize = 1f;
|
||||
if (gridId.IsValid())
|
||||
{
|
||||
snapSize = pManager.MapManager.GetGrid(gridId).TileSize; //Find snap size for the grid.
|
||||
onGrid = true;
|
||||
Grid = pManager.MapManager.GetGrid(gridId);
|
||||
SnapSize = Grid.TileSize; //Find snap size for the grid.
|
||||
}
|
||||
else
|
||||
{
|
||||
onGrid = false;
|
||||
Grid = null;
|
||||
}
|
||||
|
||||
GridDistancing = snapSize;
|
||||
GridDistancing = SnapSize;
|
||||
|
||||
var mouselocal = new Vector2( //Round local coordinates onto the snap grid
|
||||
(float) MathF.Round(MouseCoords.X / snapSize, MidpointRounding.AwayFromZero) * snapSize,
|
||||
(float) MathF.Round(MouseCoords.Y / snapSize, MidpointRounding.AwayFromZero) * snapSize);
|
||||
(float) MathF.Round(MouseCoords.X / SnapSize, MidpointRounding.AwayFromZero) * SnapSize,
|
||||
(float) MathF.Round(MouseCoords.Y / SnapSize, MidpointRounding.AwayFromZero) * SnapSize);
|
||||
|
||||
//Convert back to original world and screen coordinates after applying offset
|
||||
MouseCoords =
|
||||
new EntityCoordinates(
|
||||
MouseCoords.EntityId, mouselocal + new Vector2(pManager.PlacementOffset.X, pManager.PlacementOffset.Y));
|
||||
}
|
||||
|
||||
public override bool IsValidPosition(EntityCoordinates position)
|
||||
{
|
||||
if (!RangeCheck(position))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -7,8 +9,8 @@ namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
public class SnapgridCenter : PlacementMode
|
||||
{
|
||||
bool onGrid;
|
||||
float snapSize;
|
||||
protected IMapGrid? Grid;
|
||||
protected float SnapSize;
|
||||
|
||||
public override bool HasLineMode => true;
|
||||
public override bool HasGridMode => true;
|
||||
@@ -17,22 +19,22 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
public override void Render(DrawingHandleWorld handle)
|
||||
{
|
||||
if (onGrid)
|
||||
if (Grid != null)
|
||||
{
|
||||
var viewportSize = (Vector2)pManager._clyde.ScreenSize;
|
||||
|
||||
var position = pManager.eyeManager.ScreenToMap(Vector2.Zero);
|
||||
var gridPosition = Grid.MapToGrid(pManager.eyeManager.ScreenToMap(Vector2.Zero));
|
||||
|
||||
var gridstart = pManager.eyeManager.WorldToScreen(new Vector2( //Find snap grid closest to screen origin and convert back to screen coords
|
||||
(float)(MathF.Round(position.X / snapSize - 0.5f, MidpointRounding.AwayFromZero) + 0.5f) * snapSize,
|
||||
(float)(MathF.Round(position.Y / snapSize - 0.5f, MidpointRounding.AwayFromZero) + 0.5f) * snapSize));
|
||||
for (var a = gridstart.X; a < viewportSize.X; a += snapSize * 32) //Iterate through screen creating gridlines
|
||||
var gridstart = pManager.eyeManager.CoordinatesToScreen(
|
||||
gridPosition.WithPosition(new Vector2(MathF.Floor(gridPosition.X), MathF.Floor(gridPosition.Y))));
|
||||
|
||||
for (var a = gridstart.X; a < viewportSize.X; a += SnapSize * EyeManager.PixelsPerMeter) //Iterate through screen creating gridlines
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(a, 0));
|
||||
var to = ScreenToWorld(new Vector2(a, viewportSize.Y));
|
||||
handle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
}
|
||||
for (var a = gridstart.Y; a < viewportSize.Y; a += snapSize * 32)
|
||||
for (var a = gridstart.Y; a < viewportSize.Y; a += SnapSize * EyeManager.PixelsPerMeter)
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(0, a));
|
||||
var to = ScreenToWorld(new Vector2(viewportSize.X, a));
|
||||
@@ -49,22 +51,22 @@ namespace Robust.Client.Placement.Modes
|
||||
MouseCoords = ScreenToCursorGrid(mouseScreen);
|
||||
|
||||
var gridId = MouseCoords.GetGridId(pManager.EntityManager);
|
||||
snapSize = 1f;
|
||||
SnapSize = 1f;
|
||||
if (gridId.IsValid())
|
||||
{
|
||||
snapSize = pManager.MapManager.GetGrid(gridId).TileSize; //Find snap size for the grid.
|
||||
onGrid = true;
|
||||
Grid = pManager.MapManager.GetGrid(gridId);
|
||||
SnapSize = Grid.TileSize; //Find snap size for the grid.
|
||||
}
|
||||
else
|
||||
{
|
||||
onGrid = false;
|
||||
Grid = null;
|
||||
}
|
||||
|
||||
GridDistancing = snapSize;
|
||||
GridDistancing = SnapSize;
|
||||
|
||||
var mouseLocal = new Vector2( //Round local coordinates onto the snap grid
|
||||
(float)(MathF.Round((MouseCoords.Position.X / snapSize - 0.5f), MidpointRounding.AwayFromZero) + 0.5) * snapSize,
|
||||
(float)(MathF.Round((MouseCoords.Position.Y / snapSize - 0.5f), MidpointRounding.AwayFromZero) + 0.5) * snapSize);
|
||||
(float)(MathF.Round((MouseCoords.Position.X / SnapSize - 0.5f), MidpointRounding.AwayFromZero) + 0.5) * SnapSize,
|
||||
(float)(MathF.Round((MouseCoords.Position.Y / SnapSize - 0.5f), MidpointRounding.AwayFromZero) + 0.5) * SnapSize);
|
||||
|
||||
//Adjust mouseCoords to new calculated position
|
||||
MouseCoords = new EntityCoordinates(MouseCoords.EntityId, mouseLocal + new Vector2(pManager.PlacementOffset.X, pManager.PlacementOffset.Y));
|
||||
|
||||
@@ -233,7 +233,7 @@ namespace Robust.Client.Placement
|
||||
bounds.Width,
|
||||
bounds.Height);
|
||||
|
||||
return EntitySystem.Get<SharedBroadphaseSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
|
||||
return EntitySystem.Get<SharedPhysicsSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
|
||||
}
|
||||
|
||||
protected Vector2 ScreenToWorld(Vector2 point)
|
||||
|
||||
26
Robust.Client/Player/FilterSystem.cs
Normal file
26
Robust.Client/Player/FilterSystem.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Robust.Client.Player
|
||||
{
|
||||
internal class FilterSystem : SharedFilterSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override Filter FromEntities(Filter filter, params EntityUid[] entities)
|
||||
{
|
||||
if (_playerManager.LocalPlayer is not { } localPlayer
|
||||
|| localPlayer.Session.AttachedEntityUid is not {} attachedUid)
|
||||
return filter;
|
||||
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
if (uid == attachedUid)
|
||||
filter.AddPlayer(localPlayer.Session);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace Robust.Client.Player
|
||||
void Startup();
|
||||
void Shutdown();
|
||||
|
||||
void ApplyPlayerStates(IEnumerable<PlayerState>? list);
|
||||
void ApplyPlayerStates(IReadOnlyCollection<PlayerState> list);
|
||||
}
|
||||
|
||||
public class LocalPlayerChangedEventArgs : EventArgs
|
||||
|
||||
@@ -103,9 +103,9 @@ namespace Robust.Client.Player
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ApplyPlayerStates(IEnumerable<PlayerState>? list)
|
||||
public void ApplyPlayerStates(IReadOnlyCollection<PlayerState> list)
|
||||
{
|
||||
if (list == null)
|
||||
if (list.Count == 0)
|
||||
{
|
||||
// This happens when the server says "nothing changed!"
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public int ScrollSpeedX { get; set; } = 50;
|
||||
public int ScrollSpeedY { get; set; } = 50;
|
||||
|
||||
public bool ReturnMeasure { get; set; } = false;
|
||||
|
||||
public ScrollContainer()
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
@@ -89,11 +91,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
size = Vector2.ComponentMax(size, child.DesiredSize);
|
||||
}
|
||||
|
||||
// Unlike WPF/Avalonia we report ZERO here instead of available size.
|
||||
// This is to fix a bunch of jank with e.g. BoxContainer.
|
||||
// Tbh this might be a mistake.
|
||||
// DockPanel when.
|
||||
return Vector2.Zero;
|
||||
// Unlike WPF/Avalonia we default to reporting ZERO here instead of available size. This is to fix a bunch
|
||||
// of jank with e.g. BoxContainer.
|
||||
if (!ReturnMeasure)
|
||||
return Vector2.Zero;
|
||||
|
||||
if (_vScrollEnabled)
|
||||
size.X += _vScrollBar.DesiredSize.X;
|
||||
|
||||
if (_hScrollEnabled)
|
||||
size.Y += _hScrollBar.DesiredSize.Y;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
|
||||
@@ -450,7 +450,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Arrange(UIBox2.FromDimensions(0, offset, Width, height));
|
||||
child.Arrange(UIBox2.FromDimensions(0, offset, finalSize.X, height));
|
||||
offset += Separation + height;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Robust.Client.UserInterface
|
||||
public string Description => "A";
|
||||
public string Help => "A";
|
||||
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
var monitor = clyde.EnumerateMonitors().First();
|
||||
|
||||
@@ -552,7 +552,7 @@ namespace Robust.Server
|
||||
|
||||
if (_config.GetCVar(CVars.LogRuntimeLog))
|
||||
{
|
||||
// Wrtie down exception log
|
||||
// Write down exception log
|
||||
var logPath = _config.GetCVar(CVars.LogPath);
|
||||
var relPath = PathHelpers.ExecutableRelativeFile(logPath);
|
||||
Directory.CreateDirectory(relPath);
|
||||
|
||||
@@ -153,11 +153,11 @@ namespace Robust.Server.Console.Commands
|
||||
{
|
||||
var network = IoCManager.Resolve<IServerNetManager>();
|
||||
|
||||
var reason = "Kicked by console.";
|
||||
string reason;
|
||||
if (args.Length >= 2)
|
||||
{
|
||||
reason = reason + args[1];
|
||||
}
|
||||
reason = $"Kicked by console: {string.Join(' ', args[1..])}";
|
||||
else
|
||||
reason = "Kicked by console";
|
||||
|
||||
network.DisconnectChannel(target.ConnectedClient, reason);
|
||||
}
|
||||
|
||||
@@ -331,24 +331,18 @@ namespace Robust.Server.Console.Commands
|
||||
fixture.CollisionLayer = 2;
|
||||
}
|
||||
|
||||
// TODO: Should Joints be their own entities? Box2D just adds them directly to the world.
|
||||
// HMMM
|
||||
// At the least it should be its own damn component
|
||||
var revolute = new RevoluteJoint(ground, body)
|
||||
{
|
||||
LocalAnchorA = new Vector2(0f, 10f),
|
||||
LocalAnchorB = new Vector2(0f, 0f),
|
||||
ReferenceAngle = 0f,
|
||||
MotorSpeed = 0.05f * MathF.PI,
|
||||
MaxMotorTorque = 100000000f,
|
||||
EnableMotor = true
|
||||
};
|
||||
body.AddJoint(revolute);
|
||||
var revolute = EntitySystem.Get<SharedJointSystem>().CreateRevoluteJoint(ground.Owner.Uid, body.Owner.Uid);
|
||||
revolute.LocalAnchorA = new Vector2(0f, 10f);
|
||||
revolute.LocalAnchorB = new Vector2(0f, 0f);
|
||||
revolute.ReferenceAngle = 0f;
|
||||
revolute.MotorSpeed = 0.05f * MathF.PI;
|
||||
revolute.MaxMotorTorque = 100000000f;
|
||||
revolute.EnableMotor = true;
|
||||
|
||||
// Box2D has this as 800 which is jesus christo.
|
||||
// Wouldn't recommend higher than 100 in debug and higher than 300 on release unless
|
||||
// you really want a profile.
|
||||
var count = 200;
|
||||
var count = 300;
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
@@ -11,21 +9,19 @@ namespace Robust.Server.Debugging
|
||||
[UsedImplicitly]
|
||||
internal class DebugDrawingManager : IDebugDrawingManager, IEntityEventSubscriber
|
||||
{
|
||||
[Dependency] private readonly IServerNetManager _net = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgRay>();
|
||||
#if DEBUG
|
||||
IoCManager.Resolve<IEntityManager>().EventBus.SubscribeEvent<DebugDrawRayMessage>(EventSource.Local, this, PhysicsOnDebugDrawRay);
|
||||
_entManager.EventBus.SubscribeEvent<DebugDrawRayMessage>(EventSource.Local, this, PhysicsOnDebugDrawRay);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void PhysicsOnDebugDrawRay(DebugDrawRayMessage @event)
|
||||
{
|
||||
var data = @event.Data;
|
||||
var msg = _net.CreateNetMessage<MsgRay>();
|
||||
msg.RayOrigin = data.Ray.Position;
|
||||
var msg = new MsgRay {RayOrigin = data.Ray.Position};
|
||||
if (data.Results != null)
|
||||
{
|
||||
msg.DidHit = true;
|
||||
@@ -36,7 +32,7 @@ namespace Robust.Server.Debugging
|
||||
msg.RayHit = data.Ray.Position + data.Ray.Direction * data.MaxLength;
|
||||
}
|
||||
|
||||
_net.ServerSendToAll(msg);
|
||||
_entManager.EventBus.RaiseEvent(EventSource.Network, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Robust.Server.GameObjects
|
||||
private Vector2 _zoom = Vector2.One;
|
||||
private Vector2 _offset;
|
||||
private Angle _rotation;
|
||||
private uint _visibilityMask;
|
||||
private uint _visibilityMask = 1;
|
||||
|
||||
public override bool DrawFov
|
||||
{
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Robust.Server.GameObjects
|
||||
/// <summary>
|
||||
/// All of the sessions currently subscribed to this UserInterface.
|
||||
/// </summary>
|
||||
public IEnumerable<IPlayerSession> SubscribedSessions => _subscribedSessions;
|
||||
public IReadOnlyCollection<IPlayerSession> SubscribedSessions => _subscribedSessions;
|
||||
|
||||
public event Action<ServerBoundUserInterfaceMessage>? OnReceiveMessage;
|
||||
public event Action<IPlayerSession>? OnClosed;
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
@@ -10,8 +11,10 @@ using Robust.Shared.Players;
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AudioSystem : EntitySystem, IAudioSystem
|
||||
public class AudioSystem : SharedAudioSystem, IAudioSystem
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private const int AudioDistanceRange = 25;
|
||||
|
||||
private uint _streamIndex;
|
||||
@@ -101,10 +104,13 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
var id = CacheIdentifier();
|
||||
|
||||
var fallbackCoordinates = GetFallbackCoordinates(transform.MapPosition);
|
||||
|
||||
var msg = new PlayAudioEntityMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Coordinates = transform.Coordinates,
|
||||
FallbackCoordinates = fallbackCoordinates,
|
||||
EntityUid = uid,
|
||||
AudioParams = audioParams ?? AudioParams.Default,
|
||||
Identifier = id,
|
||||
@@ -126,10 +132,14 @@ namespace Robust.Server.GameObjects
|
||||
var range = audioParams is null || audioParams.Value.MaxDistance <= 0 ? AudioDistanceRange : audioParams.Value.MaxDistance;
|
||||
|
||||
var id = CacheIdentifier();
|
||||
|
||||
var fallbackCoordinates = GetFallbackCoordinates(coordinates.ToMap(_entityManager));
|
||||
|
||||
var msg = new PlayAudioPositionalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Coordinates = coordinates,
|
||||
FallbackCoordinates = fallbackCoordinates,
|
||||
AudioParams = audioParams ?? AudioParams.Default,
|
||||
Identifier = id
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
internal sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
@@ -120,102 +121,139 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
#region Proxy Methods
|
||||
|
||||
public bool HasUi(EntityUid uid, object uiKey)
|
||||
public bool HasUi(EntityUid uid, object uiKey, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent<ServerUserInterfaceComponent>(uid, out var ui))
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
return ui.HasBoundUserInterface(uiKey);
|
||||
}
|
||||
|
||||
public BoundUserInterface GetUi(EntityUid uid, object uiKey)
|
||||
public BoundUserInterface GetUi(EntityUid uid, object uiKey, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
return EntityManager.GetComponent<ServerUserInterfaceComponent>(uid).GetBoundUserInterface(uiKey);
|
||||
if (!Resolve(uid, ref ui))
|
||||
throw new InvalidOperationException($"Cannot get {typeof(BoundUserInterface)} from an entity without {typeof(ServerUserInterfaceComponent)}!");
|
||||
|
||||
return ui.GetBoundUserInterface(uiKey);
|
||||
}
|
||||
|
||||
public BoundUserInterface? GetUiOrNull(EntityUid uid, object uiKey)
|
||||
public BoundUserInterface? GetUiOrNull(EntityUid uid, object uiKey, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
return TryGetUi(uid, uiKey, out var ui)
|
||||
? ui
|
||||
return TryGetUi(uid, uiKey, out var bui)
|
||||
? bui
|
||||
: null;
|
||||
}
|
||||
|
||||
public bool TryGetUi(EntityUid uid, object uiKey, [NotNullWhen(true)] out BoundUserInterface? ui)
|
||||
public bool TryGetUi(EntityUid uid, object uiKey, [NotNullWhen(true)] out BoundUserInterface? bui, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
ui = null;
|
||||
bui = null;
|
||||
|
||||
return EntityManager.TryGetComponent(uid, out ServerUserInterfaceComponent uiComp)
|
||||
&& uiComp.TryGetBoundUserInterface(uiKey, out ui);
|
||||
return Resolve(uid, ref ui) && ui.TryGetBoundUserInterface(uiKey, out bui);
|
||||
}
|
||||
|
||||
public bool TrySetUiState(EntityUid uid, object uiKey, BoundUserInterfaceState state, IPlayerSession? session = null)
|
||||
public bool IsUiOpen(EntityUid uid, object uiKey, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var ui))
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
ui.SetState(state, session);
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
return bui.SubscribedSessions.Count > 0;
|
||||
}
|
||||
|
||||
public bool TrySetUiState(EntityUid uid, object uiKey, BoundUserInterfaceState state, IPlayerSession? session = null, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
bui.SetState(state, session);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryToggleUi(EntityUid uid, object uiKey, IPlayerSession session)
|
||||
public bool TryToggleUi(EntityUid uid, object uiKey, IPlayerSession session, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var ui))
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
ui.Toggle(session);
|
||||
if (!TryGetUi(uid, uiKey, out var bui))
|
||||
return false;
|
||||
|
||||
bui.Toggle(session);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryOpen(EntityUid uid, object uiKey, IPlayerSession session)
|
||||
public bool TryOpen(EntityUid uid, object uiKey, IPlayerSession session, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var ui))
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
return ui.Open(session);
|
||||
if (!TryGetUi(uid, uiKey, out var bui))
|
||||
return false;
|
||||
|
||||
return bui.Open(session);
|
||||
}
|
||||
|
||||
public bool TryClose(EntityUid uid, object uiKey, IPlayerSession session)
|
||||
public bool TryClose(EntityUid uid, object uiKey, IPlayerSession session, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var ui))
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
return ui.Close(session);
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
return bui.Close(session);
|
||||
}
|
||||
|
||||
public bool TryCloseAll(EntityUid uid, object uiKey)
|
||||
public bool TryCloseAll(EntityUid uid, object uiKey, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var ui))
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
ui.CloseAll();
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
bui.CloseAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SessionHasOpenUi(EntityUid uid, object uiKey, IPlayerSession session)
|
||||
public bool SessionHasOpenUi(EntityUid uid, object uiKey, IPlayerSession session, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var ui))
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
return ui.SessionHasOpen(session);
|
||||
if (!TryGetUi(uid, uiKey, out var bui))
|
||||
return false;
|
||||
|
||||
return bui.SessionHasOpen(session);
|
||||
}
|
||||
|
||||
public bool TrySendUiMessage(EntityUid uid, object uiKey, BoundUserInterfaceMessage message)
|
||||
public bool TrySendUiMessage(EntityUid uid, object uiKey, BoundUserInterfaceMessage message, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var ui))
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
ui.SendMessage(message);
|
||||
if (!TryGetUi(uid, uiKey, out var bui))
|
||||
return false;
|
||||
|
||||
bui.SendMessage(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySendUiMessage(EntityUid uid, object uiKey, BoundUserInterfaceMessage message, IPlayerSession session)
|
||||
public bool TrySendUiMessage(EntityUid uid, object uiKey, BoundUserInterfaceMessage message, IPlayerSession session, ServerUserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var ui))
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
if (!TryGetUi(uid, uiKey, out var bui))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
ui.SendMessage(message, session);
|
||||
bui.SendMessage(message, session);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
public interface IServerEntityNetworkManager : IEntityNetworkManager
|
||||
{
|
||||
uint GetLastMessageSequence(IPlayerSession session);
|
||||
List<ushort> GetDeletedComponents(EntityUid uid, GameTick fromTick);
|
||||
void CullDeletionHistory(GameTick oldestAck);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,8 @@ namespace Robust.Server.GameObjects
|
||||
private readonly Dictionary<IPlayerSession, uint> _lastProcessedSequencesCmd =
|
||||
new();
|
||||
|
||||
private readonly Dictionary<EntityUid, List<(GameTick tick, ushort netId)>> _componentDeletionHistory = new();
|
||||
|
||||
private bool _logLateMsgs;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -109,6 +111,10 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
_networkManager.RegisterNetMessage<MsgEntity>(HandleEntityNetworkMessage);
|
||||
|
||||
// For syncing component deletions.
|
||||
EntityDeleted += OnEntityRemoved;
|
||||
ComponentRemoved += OnComponentRemoved;
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
|
||||
_configurationManager.OnValueChanged(CVars.NetLogLateMsg, b => _logLateMsgs = b, true);
|
||||
@@ -135,6 +141,65 @@ namespace Robust.Server.GameObjects
|
||||
return _lastProcessedSequencesCmd[session];
|
||||
}
|
||||
|
||||
private void OnEntityRemoved(object? sender, EntityUid e)
|
||||
{
|
||||
if (_componentDeletionHistory.ContainsKey(e))
|
||||
_componentDeletionHistory.Remove(e);
|
||||
}
|
||||
|
||||
private void OnComponentRemoved(object? sender, ComponentEventArgs e)
|
||||
{
|
||||
var reg = ComponentFactory.GetRegistration(e.Component.GetType());
|
||||
|
||||
// We only keep track of networked components being removed.
|
||||
if (reg.NetID is not {} netId)
|
||||
return;
|
||||
|
||||
var uid = e.OwnerUid;
|
||||
|
||||
if (!_componentDeletionHistory.TryGetValue(uid, out var list))
|
||||
{
|
||||
list = new List<(GameTick tick, ushort netId)>();
|
||||
_componentDeletionHistory[uid] = list;
|
||||
}
|
||||
|
||||
list.Add((_gameTiming.CurTick, netId));
|
||||
}
|
||||
|
||||
public List<ushort> GetDeletedComponents(EntityUid uid, GameTick fromTick)
|
||||
{
|
||||
// TODO: Maybe make this a struct enumerator? Right now it's a list for consistency...
|
||||
var list = new List<ushort>();
|
||||
|
||||
if (!_componentDeletionHistory.TryGetValue(uid, out var history))
|
||||
return list;
|
||||
|
||||
foreach (var (tick, id) in history)
|
||||
{
|
||||
if (tick >= fromTick) list.Add(id);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public void CullDeletionHistory(GameTick oldestAck)
|
||||
{
|
||||
var remQueue = new RemQueue<EntityUid>();
|
||||
|
||||
foreach (var (uid, list) in _componentDeletionHistory)
|
||||
{
|
||||
list.RemoveAll(hist => hist.tick < oldestAck);
|
||||
|
||||
if(list.Count == 0)
|
||||
remQueue.Add(uid);
|
||||
}
|
||||
|
||||
foreach (var uid in remQueue)
|
||||
{
|
||||
_componentDeletionHistory.Remove(uid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component,
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Robust.Server.GameStates
|
||||
{
|
||||
internal sealed class EntityViewCulling
|
||||
{
|
||||
private const int ViewSetCapacity = 128; // starting number of entities that are in view
|
||||
private const int ViewSetCapacity = 256; // starting number of entities that are in view
|
||||
private const int PlayerSetSize = 64; // Starting number of players
|
||||
private const int MaxVisPoolSize = 1024; // Maximum number of pooled objects
|
||||
|
||||
@@ -41,11 +41,18 @@ namespace Robust.Server.GameStates
|
||||
private readonly List<(GameTick tick, EntityUid uid)> _deletionHistory = new();
|
||||
|
||||
private readonly ObjectPool<HashSet<EntityUid>> _visSetPool
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new DefaultPooledObjectPolicy<HashSet<EntityUid>>(), MaxVisPoolSize);
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new VisSetPolicy(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<HashSet<EntityUid>> _viewerEntsPool
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new DefaultPooledObjectPolicy<HashSet<EntityUid>>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<Dictionary<GridId, HashSet<IMapChunkInternal>>> _includedChunksPool =
|
||||
new DefaultObjectPool<Dictionary<GridId, HashSet<IMapChunkInternal>>>(new DefaultPooledObjectPolicy<Dictionary<GridId, HashSet<IMapChunkInternal>>>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<HashSet<IMapChunkInternal>> _chunkPool =
|
||||
new DefaultObjectPool<HashSet<IMapChunkInternal>>(
|
||||
new DefaultPooledObjectPolicy<HashSet<IMapChunkInternal>>(), MaxVisPoolSize);
|
||||
|
||||
private ushort _transformNetId = 0;
|
||||
|
||||
/// <summary>
|
||||
@@ -58,6 +65,22 @@ namespace Robust.Server.GameStates
|
||||
/// </summary>
|
||||
public float ViewSize { get; set; }
|
||||
|
||||
private sealed class VisSetPolicy : PooledObjectPolicy<HashSet<EntityUid>>
|
||||
{
|
||||
public override HashSet<EntityUid> Create()
|
||||
{
|
||||
return new(ViewSetCapacity);
|
||||
}
|
||||
|
||||
public override bool Return(HashSet<EntityUid> obj)
|
||||
{
|
||||
// TODO: This clear can be pretty expensive so maybe make a custom datatype given we're swapping
|
||||
// 70 - 300 entities a tick? Or do we even need to clear given it's just value types?
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public EntityViewCulling(IServerEntityManager entMan, IMapManager mapManager, IEntityLookup lookup)
|
||||
{
|
||||
_entMan = entMan;
|
||||
@@ -97,7 +120,9 @@ namespace Robust.Server.GameStates
|
||||
// Not thread safe
|
||||
public void AddPlayer(ICommonSession session)
|
||||
{
|
||||
_playerVisibleSets.Add(session, new HashSet<EntityUid>(ViewSetCapacity));
|
||||
var visSet = _visSetPool.Get();
|
||||
|
||||
_playerVisibleSets.Add(session, visSet);
|
||||
PlayerChunks.Add(session, new Dictionary<IMapChunkInternal, GameTick>(32));
|
||||
_streamingChunks.Add(session, new ChunkStreamingData());
|
||||
}
|
||||
@@ -193,7 +218,7 @@ namespace Robust.Server.GameStates
|
||||
|
||||
foreach (var eyeEuid in viewers)
|
||||
{
|
||||
var includedChunks = new Dictionary<GridId, HashSet<IMapChunkInternal>>();
|
||||
var includedChunks = _includedChunksPool.Get();
|
||||
|
||||
var (viewBox, mapId) = CalcViewBounds(in eyeEuid);
|
||||
|
||||
@@ -201,18 +226,14 @@ namespace Robust.Server.GameStates
|
||||
if (_entMan.TryGetComponent<EyeComponent>(eyeEuid, out var eyeComp))
|
||||
visMask = eyeComp.VisibilityMask;
|
||||
|
||||
//Always include the map entity of the eye, if it exists.
|
||||
if(_mapManager.MapExists(mapId))
|
||||
visibleEnts.Add(_mapManager.GetMapEntityId(mapId));
|
||||
|
||||
//Always include viewable ent itself
|
||||
visibleEnts.Add(eyeEuid);
|
||||
RecursiveAdd(eyeEuid, visibleEnts, includedChunks, chunksSeen, visMask);
|
||||
|
||||
// grid entity should be added through this
|
||||
// assume there are no deleted ents in here, cull them first in ent/comp manager
|
||||
_lookup.FastEntitiesIntersecting(in mapId, ref viewBox, entity =>
|
||||
{
|
||||
RecursiveAdd(entity.Uid, visibleEnts, includedChunks, visMask);
|
||||
RecursiveAdd(entity.Uid, visibleEnts, includedChunks, chunksSeen, visMask);
|
||||
}, LookupFlags.None);
|
||||
|
||||
//Calculate states for all visible anchored ents
|
||||
@@ -222,6 +243,15 @@ namespace Robust.Server.GameStates
|
||||
|
||||
// To fix pop-in we'll go through nearby chunks and send them little-by-little
|
||||
StreamChunks(newChunkCount, session, chunksSeen, entityStates, viewBox, fromTick, mapId);
|
||||
|
||||
foreach (var (_, chunks) in includedChunks)
|
||||
{
|
||||
chunks.Clear();
|
||||
_chunkPool.Return(chunks);
|
||||
}
|
||||
|
||||
includedChunks.Clear();
|
||||
_includedChunksPool.Return(includedChunks);
|
||||
}
|
||||
|
||||
viewers.Clear();
|
||||
@@ -242,14 +272,16 @@ namespace Robust.Server.GameStates
|
||||
Dictionary<IMapChunkInternal, GameTick> chunksSeen,
|
||||
Dictionary<GridId, HashSet<IMapChunkInternal>> includedChunks)
|
||||
{
|
||||
foreach (var publicMapGrid in _mapManager.FindGridsIntersecting(mapId, viewBox))
|
||||
_mapManager.FindGridsIntersectingEnumerator(mapId, viewBox, out var gridEnumerator, true);
|
||||
|
||||
while (gridEnumerator.MoveNext(out var mapGrid))
|
||||
{
|
||||
var grid = (IMapGridInternal)publicMapGrid;
|
||||
var grid = (IMapGridInternal) mapGrid;
|
||||
grid.GetMapChunks(viewBox, out var enumerator);
|
||||
|
||||
// Can't really check when grid was modified here because we may need to dump new chunks on the person
|
||||
// as right now if you make a new chunk the client never actually gets these entities here.
|
||||
|
||||
foreach (var chunk in grid.GetMapChunks(viewBox))
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
// for each chunk, check dirty
|
||||
if (chunksSeen.TryGetValue(chunk, out var chunkSeen) && chunk.LastAnchoredModifiedTick < chunkSeen)
|
||||
@@ -257,7 +289,7 @@ namespace Robust.Server.GameStates
|
||||
|
||||
if (!includedChunks.TryGetValue(grid.Index, out var chunks))
|
||||
{
|
||||
chunks = new HashSet<IMapChunkInternal>();
|
||||
chunks = _chunkPool.Get();
|
||||
includedChunks[grid.Index] = chunks;
|
||||
}
|
||||
|
||||
@@ -280,7 +312,7 @@ namespace Robust.Server.GameStates
|
||||
foreach (var (gridId, chunks) in includedChunks)
|
||||
{
|
||||
// at least 1 anchored entity is going to be added, so add the grid (all anchored ents are parented to grid)
|
||||
RecursiveAdd(_mapManager.GetGrid(gridId).GridEntityId, visibleEnts, includedChunks, visMask);
|
||||
RecursiveAdd(_mapManager.GetGrid(gridId).GridEntityId, visibleEnts, includedChunks, chunksSeen, visMask);
|
||||
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
@@ -290,6 +322,7 @@ namespace Robust.Server.GameStates
|
||||
lastSeenChunk = GameTick.Zero;
|
||||
count++;
|
||||
}
|
||||
// Assume we've already done the chunkdirty check.
|
||||
|
||||
chunk.FastGetAllAnchoredEnts(uid =>
|
||||
{
|
||||
@@ -372,12 +405,14 @@ namespace Robust.Server.GameStates
|
||||
|
||||
// Find a new chunk to start streaming in range.
|
||||
var enlarged = viewBox.Enlarged(StreamRange);
|
||||
_mapManager.FindGridsIntersectingEnumerator(mapId, enlarged, out var gridEnumerator, true);
|
||||
|
||||
foreach (var publicMapGrid in _mapManager.FindGridsIntersecting(mapId, enlarged))
|
||||
while (gridEnumerator.MoveNext(out var mapGrid))
|
||||
{
|
||||
var grid = (IMapGridInternal) publicMapGrid;
|
||||
var grid = (IMapGridInternal) mapGrid;
|
||||
grid.GetMapChunks(enlarged, out var enumerator);
|
||||
|
||||
foreach (var chunk in grid.GetMapChunks(enlarged))
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
// if we've ever seen this chunk don't worry about it.
|
||||
if (chunksSeen.ContainsKey(chunk))
|
||||
@@ -470,7 +505,6 @@ namespace Robust.Server.GameStates
|
||||
|
||||
// swap out vis sets
|
||||
_playerVisibleSets[session] = currentSet;
|
||||
previousSet.Clear();
|
||||
_visSetPool.Return(previousSet);
|
||||
}
|
||||
|
||||
@@ -496,7 +530,7 @@ namespace Robust.Server.GameStates
|
||||
// Read Safe
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private bool RecursiveAdd(EntityUid uid, HashSet<EntityUid> visSet, Dictionary<GridId, HashSet<IMapChunkInternal>> includedChunks, uint visMask)
|
||||
private bool RecursiveAdd(EntityUid uid, HashSet<EntityUid> visSet, Dictionary<GridId, HashSet<IMapChunkInternal>> includedChunks, Dictionary<IMapChunkInternal, GameTick> chunksSeen, uint visMask)
|
||||
{
|
||||
// we are done, this ent has already been checked and is visible
|
||||
if (visSet.Contains(uid))
|
||||
@@ -523,7 +557,7 @@ namespace Robust.Server.GameStates
|
||||
// parent is already in the set
|
||||
if (visSet.Contains(parentUid))
|
||||
{
|
||||
EnsureAnchoredChunk(xform, includedChunks);
|
||||
EnsureAnchoredChunk(xform, includedChunks, chunksSeen);
|
||||
if (!xform.Anchored)
|
||||
visSet.Add(uid);
|
||||
|
||||
@@ -531,10 +565,10 @@ namespace Robust.Server.GameStates
|
||||
}
|
||||
|
||||
// parent was not added, so we are not either
|
||||
if (!RecursiveAdd(parentUid, visSet, includedChunks, visMask))
|
||||
if (!RecursiveAdd(parentUid, visSet, includedChunks, chunksSeen, visMask))
|
||||
return false;
|
||||
|
||||
EnsureAnchoredChunk(xform, includedChunks);
|
||||
EnsureAnchoredChunk(xform, includedChunks, chunksSeen);
|
||||
|
||||
// add us
|
||||
if (!xform.Anchored)
|
||||
@@ -546,7 +580,7 @@ namespace Robust.Server.GameStates
|
||||
/// <summary>
|
||||
/// If we recursively get an anchored entity need to ensure the entire chunk is included (as it may be out of view).
|
||||
/// </summary>
|
||||
private void EnsureAnchoredChunk(TransformComponent xform, Dictionary<GridId, HashSet<IMapChunkInternal>> includedChunks)
|
||||
private void EnsureAnchoredChunk(TransformComponent xform, Dictionary<GridId, HashSet<IMapChunkInternal>> includedChunks, Dictionary<IMapChunkInternal, GameTick> chunksSeen)
|
||||
{
|
||||
// If we recursively get an anchored entity need to ensure the entire chunk is included (as it may be out of view).
|
||||
if (!xform.Anchored) return;
|
||||
@@ -556,9 +590,13 @@ namespace Robust.Server.GameStates
|
||||
var local = xform.Coordinates;
|
||||
var chunk = mapGrid.GetChunk(mapGrid.LocalToChunkIndices(local));
|
||||
|
||||
// Don't need to worry about getting the parent as we've already seen it before from this chunk.
|
||||
if (chunksSeen.TryGetValue(chunk, out var lastSeen) && lastSeen >= chunk.LastAnchoredModifiedTick)
|
||||
return;
|
||||
|
||||
if (!includedChunks.TryGetValue(xform.GridID, out var chunks))
|
||||
{
|
||||
chunks = new HashSet<IMapChunkInternal>();
|
||||
chunks = _chunkPool.Get();
|
||||
includedChunks[xform.GridID] = chunks;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Map;
|
||||
@@ -172,6 +172,7 @@ namespace Robust.Server.GameStates
|
||||
if (!_networkManager.IsConnected)
|
||||
{
|
||||
// Prevent deletions piling up if we have no clients.
|
||||
_entityManager.CullDeletionHistory(GameTick.MaxValue);
|
||||
_entityView.CullDeletionHistory(GameTick.MaxValue);
|
||||
_mapManager.CullDeletionHistory(GameTick.MaxValue);
|
||||
return;
|
||||
@@ -179,20 +180,20 @@ namespace Robust.Server.GameStates
|
||||
|
||||
var inputSystem = _systemManager.GetEntitySystem<InputSystem>();
|
||||
|
||||
var oldestAck = GameTick.MaxValue;
|
||||
var oldestAckValue = GameTick.MaxValue.Value;
|
||||
|
||||
var mainThread = Thread.CurrentThread;
|
||||
(MsgState, INetChannel) GenerateMail(IPlayerSession session)
|
||||
var parentDeps = IoCManager.Instance!;
|
||||
|
||||
void SendStateUpdate(IPlayerSession session)
|
||||
{
|
||||
// KILL IT WITH FIRE
|
||||
if(mainThread != Thread.CurrentThread)
|
||||
IoCManager.InitThread(new DependencyCollection(), true);
|
||||
IoCManager.InitThread(new DependencyCollection(parentDeps), true);
|
||||
|
||||
// people not in the game don't get states
|
||||
if (session.Status != SessionStatus.InGame)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return;
|
||||
|
||||
var channel = session.ConnectedClient;
|
||||
|
||||
@@ -208,13 +209,11 @@ namespace Robust.Server.GameStates
|
||||
// lastAck varies with each client based on lag and such, we can't just make 1 global state and send it to everyone
|
||||
var lastInputCommand = inputSystem.GetLastInputCommand(session);
|
||||
var lastSystemMessage = _entityNetworkManager.GetLastMessageSequence(session);
|
||||
var state = new GameState(lastAck, _gameTiming.CurTick, Math.Max(lastInputCommand, lastSystemMessage), entStates?.ToArray(), playerStates?.ToArray(), deletions?.ToArray(), mapData);
|
||||
if (lastAck < oldestAck)
|
||||
{
|
||||
oldestAck = lastAck;
|
||||
}
|
||||
var state = new GameState(lastAck, _gameTiming.CurTick, Math.Max(lastInputCommand, lastSystemMessage), entStates, playerStates, deletions, mapData);
|
||||
|
||||
DebugTools.Assert(state.MapData?.CreatedMaps is null || (state.MapData?.CreatedMaps is not null && state.EntityStates is not null), "Sending new maps, but no entity state.");
|
||||
InterlockedHelper.Min(ref oldestAckValue, lastAck.Value);
|
||||
|
||||
DebugTools.Assert(state.MapData?.CreatedMaps is null || (state.MapData?.CreatedMaps is not null && state.EntityStates.HasContents), "Sending new maps, but no entity state.");
|
||||
|
||||
// actually send the state
|
||||
var stateUpdateMessage = _networkManager.CreateNetMessage<MsgState>();
|
||||
@@ -226,45 +225,35 @@ namespace Robust.Server.GameStates
|
||||
// When we send them reliably, we immediately update the ack so that the next state will not be huge.
|
||||
if (stateUpdateMessage.ShouldSendReliably())
|
||||
{
|
||||
_ackedStates[channel.ConnectionId] = _gameTiming.CurTick;
|
||||
// TODO: remove this lock by having a single state object per session that contains all per-session state needed.
|
||||
lock (_ackedStates)
|
||||
{
|
||||
_ackedStates[channel.ConnectionId] = _gameTiming.CurTick;
|
||||
}
|
||||
}
|
||||
|
||||
return (stateUpdateMessage, channel);
|
||||
_networkManager.ServerSendMessage(stateUpdateMessage, channel);
|
||||
}
|
||||
|
||||
(MsgState, INetChannel?) SafeGenerateMail(IPlayerSession session)
|
||||
Parallel.ForEach(_playerManager.GetAllPlayers(), session =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return GenerateMail(session);
|
||||
SendStateUpdate(session);
|
||||
}
|
||||
catch (Exception e) // Catch EVERY exception
|
||||
{
|
||||
_logger.Log(LogLevel.Error, e, "Caught exception while generating mail.");
|
||||
}
|
||||
});
|
||||
|
||||
var msg = _networkManager.CreateNetMessage<MsgState>();
|
||||
msg.MsgChannel = session.ConnectedClient;
|
||||
|
||||
return (msg, null);
|
||||
}
|
||||
|
||||
var mailBag = _playerManager.GetAllPlayers()
|
||||
.Where(s => s.Status == SessionStatus.InGame)
|
||||
.AsParallel()
|
||||
.Select(SafeGenerateMail);
|
||||
|
||||
foreach (var (msg, chan) in mailBag)
|
||||
{
|
||||
// see session.Status != SessionStatus.InGame above
|
||||
if (chan == null) continue;
|
||||
_networkManager.ServerSendMessage(msg, chan);
|
||||
}
|
||||
var oldestAck = new GameTick(oldestAckValue);
|
||||
|
||||
// keep the deletion history buffers clean
|
||||
if (oldestAck > _lastOldestAck)
|
||||
{
|
||||
_lastOldestAck = oldestAck;
|
||||
_entityManager.CullDeletionHistory(oldestAck);
|
||||
_entityView.CullDeletionHistory(oldestAck);
|
||||
_mapManager.CullDeletionHistory(oldestAck);
|
||||
}
|
||||
@@ -278,7 +267,7 @@ namespace Robust.Server.GameStates
|
||||
/// <param name="entityUid">Uid of the entity to generate the state from.</param>
|
||||
/// <param name="fromTick">Only provide delta changes from this tick.</param>
|
||||
/// <returns>New entity State for the given entity.</returns>
|
||||
internal static EntityState GetEntityState(IEntityManager entMan, ICommonSession player, EntityUid entityUid, GameTick fromTick)
|
||||
internal static EntityState GetEntityState(IServerEntityManager entMan, ICommonSession player, EntityUid entityUid, GameTick fromTick)
|
||||
{
|
||||
var bus = entMan.EventBus;
|
||||
var changed = new List<ComponentChange>();
|
||||
@@ -310,12 +299,11 @@ namespace Robust.Server.GameStates
|
||||
{
|
||||
changed.Add(ComponentChange.Changed(netId, entMan.GetComponentState(bus, component, player)));
|
||||
}
|
||||
else if (component.Deleted && component.LastModifiedTick >= fromTick)
|
||||
{
|
||||
// Can't be null since it's returned by GetNetComponents
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
changed.Add(ComponentChange.Removed(netId));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var netId in entMan.GetDeletedComponents(entityUid, fromTick))
|
||||
{
|
||||
changed.Add(ComponentChange.Removed(netId));
|
||||
}
|
||||
|
||||
return new EntityState(entityUid, changed.ToArray());
|
||||
@@ -324,7 +312,7 @@ namespace Robust.Server.GameStates
|
||||
/// <summary>
|
||||
/// Gets all entity states that have been modified after and including the provided tick.
|
||||
/// </summary>
|
||||
internal static List<EntityState>? GetAllEntityStates(IEntityManager entityMan, ICommonSession player, GameTick fromTick)
|
||||
internal static List<EntityState>? GetAllEntityStates(IServerEntityManager entityMan, ICommonSession player, GameTick fromTick)
|
||||
{
|
||||
var stateEntities = new List<EntityState>();
|
||||
foreach (var entity in entityMan.GetEntities())
|
||||
|
||||
29
Robust.Server/Physics/JointSystem.cs
Normal file
29
Robust.Server/Physics/JointSystem.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
|
||||
namespace Robust.Server.Physics
|
||||
{
|
||||
public sealed class JointSystem : SharedJointSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<JointComponent, ComponentGetState>(GetCompState);
|
||||
}
|
||||
|
||||
private void GetCompState(EntityUid uid, JointComponent component, ref ComponentGetState args)
|
||||
{
|
||||
var states = new Dictionary<string, JointState>(component.Joints.Count);
|
||||
|
||||
foreach (var (_, joint) in component.Joints)
|
||||
{
|
||||
states.Add(joint.ID, joint.GetState());
|
||||
}
|
||||
|
||||
args.State = new JointComponent.JointComponentState(states);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Robust.Server/Player/FilterSystem.cs
Normal file
20
Robust.Server/Player/FilterSystem.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Robust.Server.Player
|
||||
{
|
||||
internal class FilterSystem : SharedFilterSystem
|
||||
{
|
||||
public override Filter FromEntities(Filter filter, params EntityUid[] entities)
|
||||
{
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(uid, out ActorComponent? actor))
|
||||
filter.AddPlayer(actor.PlayerSession);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ namespace Robust.Server.Player
|
||||
|
||||
if (!EntitySystem.Get<ActorSystem>().Detach(AttachedEntity))
|
||||
{
|
||||
Logger.Warning($"Couldn't detach player \"{this}\" to entity \"{AttachedEntity}\"! Is it missing an ActorComponent?");
|
||||
Logger.Warning($"Couldn't detach player \"{this}\" from entity \"{AttachedEntity}\"! Is it missing an ActorComponent?");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,25 +84,62 @@ namespace Robust.Shared.Maths
|
||||
return ang;
|
||||
}
|
||||
|
||||
private static Vector2[] directionVectors = new[]
|
||||
{
|
||||
new Vector2(0, -1),
|
||||
private static readonly Vector2[] DirectionVectors = {
|
||||
new (0, -1),
|
||||
new Vector2(1, -1).Normalized,
|
||||
new Vector2(1, 0),
|
||||
new (1, 0),
|
||||
new Vector2(1, 1).Normalized,
|
||||
new Vector2(0, 1),
|
||||
new (0, 1),
|
||||
new Vector2(-1, 1).Normalized,
|
||||
new Vector2(-1, 0),
|
||||
new (-1, 0),
|
||||
new Vector2(-1, -1).Normalized
|
||||
};
|
||||
|
||||
private static readonly Vector2i[] IntDirectionVectors = {
|
||||
new (0, -1),
|
||||
new (1, -1),
|
||||
new (1, 0),
|
||||
new (1, 1),
|
||||
new (0, 1),
|
||||
new (-1, 1),
|
||||
new (-1, 0),
|
||||
new (-1, -1)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Direction to a normalized Direction vector.
|
||||
/// </summary>
|
||||
/// <param name="dir"></param>
|
||||
/// <returns></returns>
|
||||
/// <returns>a normalized 2D Vector</returns>
|
||||
/// <exception cref="IndexOutOfRangeException">if invalid Direction is used</exception>
|
||||
/// <seealso cref="Vector2"/>
|
||||
public static Vector2 ToVec(this Direction dir)
|
||||
{
|
||||
return directionVectors[(int) dir];
|
||||
return DirectionVectors[(int) dir];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Direction to a Vector2i. Useful for getting adjacent tiles.
|
||||
/// </summary>
|
||||
/// <param name="dir">Direction</param>
|
||||
/// <returns>an 2D int Vector</returns>
|
||||
/// <exception cref="IndexOutOfRangeException">if invalid Direction is used</exception>
|
||||
/// <seealso cref="Vector2i"/>
|
||||
public static Vector2i ToIntVec(this Direction dir)
|
||||
{
|
||||
return IntDirectionVectors[(int) dir];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offset 2D integer vector by a given direction.
|
||||
/// Convenience for adding <see cref="ToIntVec"/> to <see cref="Vector2i"/>
|
||||
/// </summary>
|
||||
/// <param name="vec">2D integer vector</param>
|
||||
/// <param name="dir">Direction by which we offset</param>
|
||||
/// <returns>a newly vector offset by the <param name="dir">dir</param> or exception if the direction is invalid</returns>
|
||||
public static Vector2i Offset(this Vector2i vec, Direction dir)
|
||||
{
|
||||
return vec + dir.ToIntVec();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -337,6 +337,28 @@ namespace Robust.Shared.Maths
|
||||
R2C2 = matrix.Row2.Z;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance from 3 vectors to quickly synthesize affine transforms.
|
||||
/// </summary>
|
||||
/// <param name="x">A Vector2 for the first, conventionally X basis, vector.</param>
|
||||
/// <param name="y">A Vector2 for the second, conventionally Y basis, vector.</param>
|
||||
/// <param name="origin">A Vector2 for the third, conventionally origin vector.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3(ref Vector2 x, ref Vector2 y, ref Vector2 origin)
|
||||
{
|
||||
R0C0 = x.X;
|
||||
R0C1 = y.X;
|
||||
R0C2 = origin.X;
|
||||
|
||||
R1C0 = x.Y;
|
||||
R1C1 = y.Y;
|
||||
R1C2 = origin.Y;
|
||||
|
||||
R2C0 = 0;
|
||||
R2C1 = 0;
|
||||
R2C2 = 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Matrix3 CreateTranslation(float x, float y)
|
||||
{
|
||||
|
||||
@@ -470,7 +470,7 @@ namespace Robust.Shared
|
||||
|
||||
// - Sleep
|
||||
public static readonly CVarDef<float> AngularSleepTolerance =
|
||||
CVarDef.Create("physics.angsleeptol", 2.0f / 180.0f * MathF.PI);
|
||||
CVarDef.Create("physics.angsleeptol", 0.25f / 180.0f * MathF.PI);
|
||||
|
||||
public static readonly CVarDef<float> LinearSleepTolerance =
|
||||
CVarDef.Create("physics.linsleeptol", 0.1f);
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
{
|
||||
@@ -19,6 +20,7 @@ namespace Robust.Shared.Console
|
||||
[Dependency] protected readonly IReflectionManager ReflectionManager = default!;
|
||||
[Dependency] protected readonly INetManager NetManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
protected readonly Dictionary<string, IConsoleCommand> AvailableCommands = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -218,6 +218,47 @@ namespace Robust.Shared.Containers
|
||||
return userContainer == otherContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether a given entity can see another entity despite whatever containers they may be in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is effectively a variant of <see cref="IsInSameOrParentContainer"/> that also checks whether the
|
||||
/// containers are transparent. Additionally, an entity can "see" the entity that contains it, but unless
|
||||
/// otherwise specified the containing entity cannot see into itself. For example, a human in a locker can
|
||||
/// see the locker and other items in that locker, but the human cannot see their own organs. Note that
|
||||
/// this means that the two entity arguments are NOT interchangeable.
|
||||
/// </remarks>
|
||||
public static bool IsInSameOrTransparentContainer(this IEntity user, IEntity other, bool userSeeInsideSelf = false)
|
||||
{
|
||||
DebugTools.AssertNotNull(user);
|
||||
DebugTools.AssertNotNull(other);
|
||||
|
||||
TryGetContainer(user, out IContainer? userContainer);
|
||||
TryGetContainer(other, out IContainer? otherContainer);
|
||||
|
||||
// Are both entities in the same container (or none)?
|
||||
if (userContainer == otherContainer) return true;
|
||||
|
||||
// Is the user contained in the other entity?
|
||||
if (userContainer?.Owner == other) return true;
|
||||
|
||||
// Does the user contain the other and can they see through themselves?
|
||||
if (userSeeInsideSelf && otherContainer?.Owner == user) return true;
|
||||
|
||||
// Next we check for see-through containers. This uses some recursion, but it should be fine unless people
|
||||
// start spawning in glass matryoshka dolls.
|
||||
|
||||
// Is the user in a see-through container?
|
||||
if (userContainer?.ShowContents ?? false)
|
||||
return IsInSameOrTransparentContainer(userContainer.Owner, other);
|
||||
|
||||
// Is the other entity in a see-through container?
|
||||
if (otherContainer?.ShowContents ?? false)
|
||||
return IsInSameOrTransparentContainer(user, otherContainer.Owner);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut method to make creation of containers easier.
|
||||
/// Creates a new container on the entity and gives it back to you.
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Shared.Containers
|
||||
/// </summary>
|
||||
[ComponentReference(typeof(IContainerManager))]
|
||||
[NetworkedComponent]
|
||||
public class ContainerManagerComponent : Component, IContainerManager
|
||||
public class ContainerManagerComponent : Component, IContainerManager, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
|
||||
@@ -30,6 +30,15 @@ namespace Robust.Shared.Containers
|
||||
/// <inheritdoc />
|
||||
public sealed override string Name => "ContainerContainer";
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
foreach (var (_, container) in Containers)
|
||||
{
|
||||
var baseContainer = (BaseContainer) container;
|
||||
baseContainer.Manager = this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnRemove()
|
||||
{
|
||||
|
||||
@@ -22,10 +22,8 @@ namespace Robust.Shared.Containers
|
||||
public T MakeContainer<T>(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
|
||||
where T : IContainer
|
||||
{
|
||||
if (!Resolve(uid, ref containerManager))
|
||||
// TODO: I am a Sad Vera. Merge ComponentManager and EntityManager so we don't have to do this. Pretty please?
|
||||
// ps. The to-do above is in effect for this whole file.
|
||||
containerManager = EntityManager.AddComponent<ContainerManagerComponent>(EntityManager.GetEntity(uid));
|
||||
if (!Resolve(uid, ref containerManager, false))
|
||||
containerManager = EntityManager.AddComponent<ContainerManagerComponent>(uid); // Happy Vera.
|
||||
|
||||
return containerManager.MakeContainer<T>(id);
|
||||
}
|
||||
@@ -33,8 +31,8 @@ namespace Robust.Shared.Containers
|
||||
public T EnsureContainer<T>(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
|
||||
where T : IContainer
|
||||
{
|
||||
if (!Resolve(uid, ref containerManager))
|
||||
containerManager = EntityManager.AddComponent<ContainerManagerComponent>(EntityManager.GetEntity(uid));
|
||||
if (!Resolve(uid, ref containerManager, false))
|
||||
containerManager = EntityManager.AddComponent<ContainerManagerComponent>(uid);
|
||||
|
||||
if (TryGetContainer(uid, id, out var container, containerManager))
|
||||
return (T)container;
|
||||
@@ -52,7 +50,7 @@ namespace Robust.Shared.Containers
|
||||
|
||||
public bool HasContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager)
|
||||
{
|
||||
if (!Resolve(uid, ref containerManager))
|
||||
if (!Resolve(uid, ref containerManager, false))
|
||||
return false;
|
||||
|
||||
return containerManager.HasContainer(id);
|
||||
@@ -60,7 +58,7 @@ namespace Robust.Shared.Containers
|
||||
|
||||
public bool TryGetContainer(EntityUid uid, string id, [NotNullWhen(true)] out IContainer? container, ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
if (Resolve(uid, ref containerManager))
|
||||
if (Resolve(uid, ref containerManager, false))
|
||||
return containerManager.TryGetContainer(id, out container);
|
||||
|
||||
container = null;
|
||||
@@ -69,7 +67,7 @@ namespace Robust.Shared.Containers
|
||||
|
||||
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out IContainer? container, ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
if (Resolve(uid, ref containerManager) && EntityManager.TryGetEntity(containedUid, out var containedEntity))
|
||||
if (Resolve(uid, ref containerManager, false) && EntityManager.TryGetEntity(containedUid, out var containedEntity))
|
||||
return containerManager.TryGetContainer(containedEntity, out container);
|
||||
|
||||
container = null;
|
||||
@@ -110,7 +108,7 @@ namespace Robust.Shared.Containers
|
||||
public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out IContainer? container, ITransformComponent? transform = null)
|
||||
{
|
||||
container = null;
|
||||
if (!Resolve(uid, ref transform))
|
||||
if (!Resolve(uid, ref transform, false))
|
||||
return false;
|
||||
|
||||
if (!transform.ParentUid.IsValid())
|
||||
|
||||
@@ -809,13 +809,13 @@ namespace Robust.Shared.ContentPack
|
||||
continue;
|
||||
}
|
||||
|
||||
return new PEReader(File.OpenRead(path));
|
||||
return ReaderFromStream(File.OpenRead(path));
|
||||
}
|
||||
|
||||
var extraStream = _parent.ExtraRobustLoader?.Invoke(dllName);
|
||||
if (extraStream != null)
|
||||
{
|
||||
return new PEReader(extraStream);
|
||||
return ReaderFromStream(extraStream);
|
||||
}
|
||||
|
||||
foreach (var resLoadPath in _resLoadPaths)
|
||||
@@ -823,7 +823,7 @@ namespace Robust.Shared.ContentPack
|
||||
try
|
||||
{
|
||||
var path = resLoadPath / dllName;
|
||||
return new PEReader(_parent._res.ContentFileRead(path));
|
||||
return ReaderFromStream(_parent._res.ContentFileRead(path));
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
@@ -833,6 +833,23 @@ namespace Robust.Shared.ContentPack
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PEReader ReaderFromStream(Stream stream)
|
||||
{
|
||||
if (OperatingSystem.IsLinux() && stream is FileStream)
|
||||
{
|
||||
// PEReader is bugged on Linux and not properly thread safe when doing memory mapping.
|
||||
// As such, we never pass it a file stream so it uses a different, non-bugged code path.
|
||||
// See https://github.com/dotnet/runtime/issues/60545
|
||||
var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
stream.Dispose();
|
||||
stream = ms;
|
||||
}
|
||||
|
||||
return new PEReader(stream);
|
||||
}
|
||||
|
||||
public PEReader? Resolve(string simpleName)
|
||||
{
|
||||
return _dictionary.GetOrAdd(simpleName, ResolveCore);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file controls all whitelists and other rules enforced by AssemblyTypeChecker.
|
||||
# This file controls all whitelists and other rules enforced by AssemblyTypeChecker.
|
||||
# Yes, I typed most of this out by hand.
|
||||
|
||||
# ILVerify errors that are allowed.
|
||||
@@ -13,6 +13,7 @@ AllowedVerifierErrors:
|
||||
WhitelistedNamespaces:
|
||||
- Robust
|
||||
- Content
|
||||
- OpenDreamShared
|
||||
|
||||
# The type whitelist does NOT care about which assembly types come from.
|
||||
# This is because types switch assembly all the time.
|
||||
@@ -299,6 +300,7 @@ Types:
|
||||
IEnumerator: { All: True }
|
||||
IReadOnlyList`1: { All: True }
|
||||
System.ComponentModel:
|
||||
CancelEventArgs: { All: True }
|
||||
PropertyDescriptor: { }
|
||||
ISite: { All: True }
|
||||
IComponent: { All: True }
|
||||
@@ -332,6 +334,7 @@ Types:
|
||||
CompareOptions: { }
|
||||
CultureInfo: { All: True }
|
||||
DateTimeStyles: { All: True } # Enum
|
||||
NumberStyles: { } # Enum
|
||||
TextInfo:
|
||||
Methods:
|
||||
- "bool get_IsRightToLeft()"
|
||||
@@ -342,7 +345,11 @@ Types:
|
||||
- "string ToTitleCase(string)"
|
||||
- "string ToTitleCase(string)"
|
||||
- "string ToUpper(string)"
|
||||
System.IO.Compression:
|
||||
CompressionMode: { } # Enum
|
||||
DeflateStream: { All: True }
|
||||
System.IO:
|
||||
BinaryReader: { All: True }
|
||||
FileAccess: { } # Enum
|
||||
FileMode: { } # Enum
|
||||
FileShare: { } # Enum
|
||||
@@ -350,6 +357,7 @@ Types:
|
||||
InvalidDataException: { All: True }
|
||||
IOException: { All: True }
|
||||
MemoryStream: { All: True }
|
||||
SeekOrigin: { } # Enum
|
||||
Stream: { All: True }
|
||||
StreamReader:
|
||||
Fields:
|
||||
@@ -467,6 +475,8 @@ Types:
|
||||
UnmanagedType: { } # `unmanaged` constraint in C# modreqs with this.
|
||||
System.Runtime.Versioning:
|
||||
TargetFrameworkAttribute: { All: True }
|
||||
System.Text.Json.Serialization:
|
||||
JsonIgnoreAttribute: { All: True }
|
||||
System.Text.RegularExpressions:
|
||||
Capture: { All: True }
|
||||
CaptureCollection: { All: True }
|
||||
@@ -801,6 +811,9 @@ Types:
|
||||
Attribute: { All: True }
|
||||
AttributeTargets: { }
|
||||
AttributeUsageAttribute: { All: True }
|
||||
BitConverter:
|
||||
Methods:
|
||||
- "uint ToUInt32(System.ReadOnlySpan`1<byte>)"
|
||||
Base64FormattingOptions: { } # Enum
|
||||
Boolean: { All: True }
|
||||
Byte: { All: True }
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// A component that toggles collision on an entity being toggled.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class CollideOnAnchorComponent : Component
|
||||
{
|
||||
public override string Name => "CollideOnAnchor";
|
||||
|
||||
/// <summary>
|
||||
/// Whether we toggle collision on or off when anchoring (and vice versa when unanchoring).
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("enable")]
|
||||
public bool Enable { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ 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;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -141,13 +140,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
internal ContactEdge? ContactEdges { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Linked-list of all of our joints.
|
||||
/// </summary>
|
||||
internal JointEdge? JointEdges { get; set; } = null;
|
||||
// TODO: Should there be a VV thing for joints? Would be useful. Same with contacts.
|
||||
// Though not sure how to do it well with the linked-list.
|
||||
|
||||
public bool IgnorePaused { get; set; }
|
||||
|
||||
internal SharedPhysicsMapComponent? PhysicsMap { get; set; }
|
||||
@@ -310,14 +302,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override ComponentState GetComponentState(ICommonSession session)
|
||||
{
|
||||
// TODO: Could optimise the shit out of this because only linear velocity and angular velocity are changing 99% of the time.
|
||||
var joints = new List<Joint>();
|
||||
for (var je = JointEdges; je != null; je = je.Next)
|
||||
{
|
||||
joints.Add(je.Joint);
|
||||
}
|
||||
|
||||
return new PhysicsComponentState(_canCollide, _sleepingAllowed, _fixedRotation, _bodyStatus, _fixtures, joints, LinearVelocity, AngularVelocity, BodyType);
|
||||
return new PhysicsComponentState(_canCollide, _sleepingAllowed, _fixedRotation, _bodyStatus, _fixtures, LinearVelocity, AngularVelocity, BodyType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -334,72 +319,6 @@ namespace Robust.Shared.GameObjects
|
||||
// So transform doesn't apply MapId in the HandleComponentState because ??? so MapId can still be 0.
|
||||
// Fucking kill me, please. You have no idea deep the rabbit hole of shitcode goes to make this work.
|
||||
|
||||
// We will pray that this deferred joint is handled properly.
|
||||
|
||||
/*
|
||||
* -- Joints --
|
||||
*/
|
||||
|
||||
// TODO: Iterating like this is inefficient and bloated as fuck but on the other hand the linked-list is very convenient
|
||||
// for bodies with a large number of fixtures / joints.
|
||||
// Probably store them in Dictionaries but still store the linked-list stuff on the fixture / joint itself.
|
||||
var existingJoints = Joints.ToList();
|
||||
var toAddJoints = new List<Joint>();
|
||||
var toRemoveJoints = new List<Joint>();
|
||||
|
||||
foreach (var newJoint in newState.Joints)
|
||||
{
|
||||
var jointFound = false;
|
||||
|
||||
foreach (var joint in existingJoints)
|
||||
{
|
||||
if (joint.ID.Equals(newJoint.ID))
|
||||
{
|
||||
if (!newJoint.Equals(joint) && TrySetupNetworkedJoint(newJoint))
|
||||
{
|
||||
toAddJoints.Add(newJoint);
|
||||
toRemoveJoints.Add(joint);
|
||||
}
|
||||
|
||||
jointFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!jointFound && TrySetupNetworkedJoint(newJoint))
|
||||
{
|
||||
toAddJoints.Add(newJoint);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var joint in existingJoints)
|
||||
{
|
||||
var jointFound = false;
|
||||
|
||||
foreach (var newJoint in newState.Joints)
|
||||
{
|
||||
if (joint.ID.Equals(newJoint.ID))
|
||||
{
|
||||
jointFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (jointFound) continue;
|
||||
|
||||
toRemoveJoints.Add(joint);
|
||||
}
|
||||
|
||||
foreach (var joint in toRemoveJoints)
|
||||
{
|
||||
RemoveJoint(joint);
|
||||
}
|
||||
|
||||
foreach (var joint in toAddJoints)
|
||||
{
|
||||
AddJoint(joint);
|
||||
}
|
||||
|
||||
/*
|
||||
* -- Fixtures --
|
||||
*/
|
||||
@@ -496,29 +415,6 @@ namespace Robust.Shared.GameObjects
|
||||
Predict = false;
|
||||
}
|
||||
|
||||
private bool TrySetupNetworkedJoint(Joint joint)
|
||||
{
|
||||
// This can fail if we've already deleted the entity or remove physics from it I think?
|
||||
|
||||
if (!Owner.EntityManager.TryGetEntity(joint.BodyAUid, out var entityA) ||
|
||||
!entityA.TryGetComponent(out PhysicsComponent? bodyA))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Owner.EntityManager.TryGetEntity(joint.BodyBUid, out var entityB) ||
|
||||
!entityB.TryGetComponent(out PhysicsComponent? bodyB))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
joint.BodyA = bodyA;
|
||||
joint.BodyB = bodyB;
|
||||
joint.EdgeA = new JointEdge();
|
||||
joint.EdgeB = new JointEdge();
|
||||
return true;
|
||||
}
|
||||
|
||||
public Fixture? GetFixture(string name)
|
||||
{
|
||||
// Sooo I'd rather have fixtures as a list in serialization but there's not really an easy way to have it as a
|
||||
@@ -570,27 +466,13 @@ namespace Robust.Shared.GameObjects
|
||||
return bounds;
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public int FixtureCount { get; internal set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public IReadOnlyList<Fixture> Fixtures => _fixtures;
|
||||
|
||||
public IEnumerable<Joint> Joints
|
||||
{
|
||||
get
|
||||
{
|
||||
JointEdge? edge = JointEdges;
|
||||
|
||||
while (edge != null)
|
||||
{
|
||||
yield return edge.Joint;
|
||||
edge = edge.Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public int FixtureCount { get; internal set; }
|
||||
|
||||
[DataField("fixtures")]
|
||||
[NeverPushInheritance]
|
||||
internal List<Fixture> _fixtures = new();
|
||||
@@ -1045,7 +927,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public IEnumerable<PhysicsComponent> GetBodiesIntersecting()
|
||||
{
|
||||
foreach (var entity in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(Owner.Transform.MapID, GetWorldAABB()))
|
||||
foreach (var entity in EntitySystem.Get<SharedPhysicsSystem>().GetCollidingEntities(Owner.Transform.MapID, GetWorldAABB()))
|
||||
{
|
||||
yield return entity;
|
||||
}
|
||||
@@ -1107,38 +989,6 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private string GetJointName(Joint joint)
|
||||
{
|
||||
var id = joint.ID;
|
||||
|
||||
if (!string.IsNullOrEmpty(id)) return id;
|
||||
|
||||
var jointCount = Joints.Count();
|
||||
|
||||
for (var i = 0; i < jointCount + 1; i++)
|
||||
{
|
||||
id = $"joint-{i}";
|
||||
if (GetJoint(id) != null) continue;
|
||||
return id;
|
||||
}
|
||||
|
||||
Logger.WarningS("physics", $"Unable to get a joint ID; using its hashcode instead.");
|
||||
return joint.GetHashCode().ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a joint with the specified ID.
|
||||
/// </summary>
|
||||
public Joint? GetJoint(string id)
|
||||
{
|
||||
foreach (var joint in Joints)
|
||||
{
|
||||
if (joint.ID == id) return joint;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal Transform GetTransform()
|
||||
{
|
||||
return new(Owner.Transform.WorldPosition, (float) Owner.Transform.WorldRotation.Theta);
|
||||
@@ -1200,7 +1050,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
IEnumerable<IPhysBody> IPhysBody.GetCollidingEntities(Vector2 offset, bool approx)
|
||||
{
|
||||
return EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(this, offset, approx);
|
||||
return EntitySystem.Get<SharedPhysicsSystem>().GetCollidingEntities(this, offset, approx);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1249,35 +1099,6 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearJoints()
|
||||
{
|
||||
for (var je = JointEdges; je != null; je = je.Next)
|
||||
{
|
||||
RemoveJoint(je.Joint);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddJoint(Joint joint)
|
||||
{
|
||||
var id = GetJointName(joint);
|
||||
|
||||
foreach (var existing in Joints)
|
||||
{
|
||||
// This can happen if a server created joint is sent and applied before the client can create it locally elsewhere
|
||||
if (existing.ID.Equals(id)) return;
|
||||
}
|
||||
|
||||
PhysicsMap?.AddJoint(joint);
|
||||
joint.ID = id;
|
||||
Logger.DebugS("physics", $"Added joint id: {joint.ID} type: {joint.GetType().Name} to {Owner}");
|
||||
}
|
||||
|
||||
public void RemoveJoint(Joint joint)
|
||||
{
|
||||
PhysicsMap?.RemoveJoint(joint);
|
||||
Logger.DebugS("physics", $"Removed joint id: {joint.ID} type: {joint.GetType().Name} from {Owner}");
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
@@ -1389,11 +1210,29 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
// Does a joint prevent collision?
|
||||
for (var jn = JointEdges; jn != null; jn = jn.Next)
|
||||
// if one of them doesn't have jointcomp then they can't share a common joint.
|
||||
// otherwise, only need to iterate over the joints of one component as they both store the same joint.
|
||||
if (Owner.TryGetComponent(out JointComponent? jointComponentA) &&
|
||||
other.Owner.TryGetComponent(out JointComponent? jointComponentB))
|
||||
{
|
||||
if (jn.Other != other) continue;
|
||||
if (jn.Joint.CollideConnected) continue;
|
||||
return false;
|
||||
var aUid = jointComponentA.Owner.Uid;
|
||||
var bUid = jointComponentB.Owner.Uid;
|
||||
|
||||
ValueTuple<EntityUid, EntityUid> uids;
|
||||
|
||||
uids = aUid.CompareTo(bUid) < 0 ?
|
||||
new ValueTuple<EntityUid, EntityUid>(aUid, bUid) :
|
||||
new ValueTuple<EntityUid, EntityUid>(bUid, aUid);
|
||||
|
||||
foreach (var (_, joint) in jointComponentA.Joints)
|
||||
{
|
||||
// Check if either: the joint even allows collisions OR the other body on the joint is actually the other body we're checking.
|
||||
if (joint.CollideConnected ||
|
||||
uids.Item1 != joint.BodyAUid ||
|
||||
uids.Item2 != joint.BodyBUid) continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var preventCollideMessage = new PreventCollideEvent(this, other);
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Robust.Shared.GameObjects
|
||||
public readonly bool FixedRotation;
|
||||
public readonly BodyStatus Status;
|
||||
public readonly List<Fixture> Fixtures;
|
||||
public readonly List<Joint> Joints;
|
||||
|
||||
public readonly Vector2 LinearVelocity;
|
||||
public readonly float AngularVelocity;
|
||||
@@ -40,7 +39,6 @@ namespace Robust.Shared.GameObjects
|
||||
bool fixedRotation,
|
||||
BodyStatus status,
|
||||
List<Fixture> fixtures,
|
||||
List<Joint> joints,
|
||||
Vector2 linearVelocity,
|
||||
float angularVelocity,
|
||||
BodyType bodyType)
|
||||
@@ -50,7 +48,6 @@ namespace Robust.Shared.GameObjects
|
||||
FixedRotation = fixedRotation;
|
||||
Status = status;
|
||||
Fixtures = fixtures;
|
||||
Joints = joints;
|
||||
|
||||
LinearVelocity = linearVelocity;
|
||||
AngularVelocity = angularVelocity;
|
||||
|
||||
@@ -46,41 +46,22 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Contains meta data about this entity that isn't component specific.
|
||||
/// </summary>
|
||||
public interface IMetaDataComponent : IComponent
|
||||
[NetworkedComponent]
|
||||
public class MetaDataComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The in-game name of this entity.
|
||||
/// </summary>
|
||||
string EntityName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The in-game description of this entity.
|
||||
/// </summary>
|
||||
string EntityDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The prototype this entity was created from, if any.
|
||||
/// </summary>
|
||||
EntityPrototype? EntityPrototype { get; set; }
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IMetaDataComponent"/>
|
||||
[ComponentReference(typeof(IMetaDataComponent))]
|
||||
[NetworkedComponent()]
|
||||
internal class MetaDataComponent : Component, IMetaDataComponent
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
|
||||
[DataField("name")]
|
||||
private string? _entityName;
|
||||
[DataField("desc")]
|
||||
private string? _entityDescription;
|
||||
private EntityPrototype? _entityPrototype;
|
||||
private bool _entityPaused;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "MetaData";
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The in-game name of this entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string EntityName
|
||||
{
|
||||
@@ -104,7 +85,9 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The in-game description of this entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string EntityDescription
|
||||
{
|
||||
@@ -128,7 +111,9 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The prototype this entity was created from, if any.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityPrototype? EntityPrototype
|
||||
{
|
||||
@@ -140,14 +125,37 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="player"></param>
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The current lifetime stage of this entity. You can use this to check
|
||||
/// if the entity is initialized or being deleted.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityLifeStage EntityLifeStage { get; internal set; }
|
||||
|
||||
[ViewVariables]
|
||||
public bool EntityPaused
|
||||
{
|
||||
get => _entityPaused;
|
||||
set
|
||||
{
|
||||
if (_entityPaused == value || value && Owner.HasComponent<IgnorePauseComponent>())
|
||||
return;
|
||||
|
||||
_entityPaused = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new EntityPausedEvent(Owner.Uid, value));
|
||||
}
|
||||
}
|
||||
|
||||
public bool EntityInitialized => EntityLifeStage >= EntityLifeStage.Initialized;
|
||||
public bool EntityInitializing => EntityLifeStage == EntityLifeStage.Initializing;
|
||||
public bool EntityDeleted => EntityLifeStage >= EntityLifeStage.Deleted;
|
||||
|
||||
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
return new MetaDataComponentState(_entityName, _entityDescription, EntityPrototype?.ID);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
@@ -159,7 +167,7 @@ namespace Robust.Shared.GameObjects
|
||||
_entityDescription = state.Description;
|
||||
|
||||
if(state.PrototypeId != null)
|
||||
_entityPrototype = _prototypes.Index<EntityPrototype>(state.PrototypeId);
|
||||
_entityPrototype = IoCManager.Resolve<IPrototypeManager>().Index<EntityPrototype>(state.PrototypeId);
|
||||
}
|
||||
|
||||
internal override void ClearTicks()
|
||||
|
||||
@@ -22,9 +22,11 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <summary>
|
||||
/// Should this component be removed when no more timers are running?
|
||||
/// N.B. This is set to false because https://github.com/space-wizards/RobustToolbox/pull/2091 caused massive issues.
|
||||
/// Changing this to false won't cause issues but masks the underlying problem, while leaving the option to turn it on for testing available.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool RemoveOnEmpty { get; set; } = true;
|
||||
public bool RemoveOnEmpty { get; set; } = false;
|
||||
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Robust.Shared.GameObjects
|
||||
if(_noLocalRotation)
|
||||
return;
|
||||
|
||||
if (_localRotation.EqualsApprox(value, 0.0001f))
|
||||
if (_localRotation.EqualsApprox(value))
|
||||
return;
|
||||
|
||||
var oldRotation = _localRotation;
|
||||
@@ -775,7 +775,7 @@ namespace Robust.Shared.GameObjects
|
||||
rebuildMatrices = true;
|
||||
}
|
||||
|
||||
if (!_localPosition.EqualsApprox(newState.LocalPosition, 0.0001))
|
||||
if (!_localPosition.EqualsApprox(newState.LocalPosition))
|
||||
{
|
||||
var oldPos = Coordinates;
|
||||
_localPosition = newState.LocalPosition;
|
||||
@@ -926,7 +926,7 @@ namespace Robust.Shared.GameObjects
|
||||
_anchored = value;
|
||||
Dirty();
|
||||
|
||||
var anchorStateChangedEvent = new AnchorStateChangedEvent(Owner);
|
||||
var anchorStateChangedEvent = new AnchorStateChangedEvent(Owner, value);
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ref anchorStateChangedEvent);
|
||||
}
|
||||
}
|
||||
@@ -985,9 +985,12 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public readonly IEntity Entity;
|
||||
|
||||
public AnchorStateChangedEvent(IEntity entity)
|
||||
public readonly bool Anchored;
|
||||
|
||||
public AnchorStateChangedEvent(IEntity entity, bool anchored)
|
||||
{
|
||||
Entity = entity;
|
||||
Anchored = anchored;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -24,19 +23,11 @@ namespace Robust.Shared.GameObjects
|
||||
[ViewVariables]
|
||||
public EntityUid Uid { get; }
|
||||
|
||||
private EntityLifeStage _lifeStage;
|
||||
|
||||
/// <inheritdoc cref="IEntity.LifeStage" />
|
||||
[ViewVariables]
|
||||
internal EntityLifeStage LifeStage
|
||||
{
|
||||
get => _lifeStage;
|
||||
set => _lifeStage = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
EntityLifeStage IEntity.LifeStage { get => LifeStage; set => LifeStage = value; }
|
||||
|
||||
public EntityLifeStage LifeStage { get => MetaData.EntityLifeStage; internal set => MetaData.EntityLifeStage = value; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public EntityPrototype? Prototype
|
||||
@@ -76,20 +67,7 @@ namespace Robust.Shared.GameObjects
|
||||
public bool Deleted => LifeStage >= EntityLifeStage.Deleted;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Paused
|
||||
{
|
||||
get => _paused;
|
||||
set
|
||||
{
|
||||
if (_paused == value || value && HasComponent<IgnorePauseComponent>())
|
||||
return;
|
||||
|
||||
_paused = value;
|
||||
EntityManager.EventBus.RaiseLocalEvent(Uid, new EntityPausedEvent(Uid, value));
|
||||
}
|
||||
}
|
||||
|
||||
private bool _paused;
|
||||
public bool Paused { get => MetaData.EntityPaused; set => MetaData.EntityPaused = value; }
|
||||
|
||||
private ITransformComponent? _transform;
|
||||
|
||||
@@ -97,11 +75,15 @@ namespace Robust.Shared.GameObjects
|
||||
[ViewVariables]
|
||||
public ITransformComponent Transform => _transform ??= GetComponent<ITransformComponent>();
|
||||
|
||||
private IMetaDataComponent? _metaData;
|
||||
private MetaDataComponent? _metaData;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public IMetaDataComponent MetaData => _metaData ??= GetComponent<IMetaDataComponent>();
|
||||
public MetaDataComponent MetaData
|
||||
{
|
||||
get => _metaData ??= GetComponent<MetaDataComponent>();
|
||||
internal set => _metaData = value;
|
||||
}
|
||||
|
||||
#endregion Members
|
||||
|
||||
@@ -124,6 +106,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public void InitializeComponents()
|
||||
{
|
||||
// TODO: Move this to EntityManager.
|
||||
|
||||
DebugTools.Assert(LifeStage == EntityLifeStage.PreInit);
|
||||
LifeStage = EntityLifeStage.Initializing;
|
||||
|
||||
@@ -166,6 +150,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public void StartAllComponents()
|
||||
{
|
||||
// TODO: Move this to EntityManager.
|
||||
// Startup() can modify _components
|
||||
// This code can only handle additions to the list. Is there a better way? Probably not.
|
||||
var comps = EntityManager.GetComponents(Uid)
|
||||
@@ -323,9 +308,11 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public void Dirty()
|
||||
{
|
||||
// TODO: Move this to EntityManager.
|
||||
|
||||
LastModifiedTick = EntityManager.CurrentTick;
|
||||
|
||||
if (LifeStage >= EntityLifeStage.Initialized && Transform.Anchored)
|
||||
if (LifeStage >= EntityLifeStage.Initialized && Transform.Anchored && Transform.ParentUid.IsValid())
|
||||
EntityManager.GetComponent<IMapGridComponent>(Transform.ParentUid).AnchoredEntityDirty(Transform);
|
||||
}
|
||||
|
||||
|
||||
@@ -485,10 +485,13 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Enumerates all subscriptions for an event on a specific entity, returning the component instances and registrations.
|
||||
/// </summary>
|
||||
private bool TryGetSubscriptions(Type eventType, EntityUid euid,
|
||||
[NotNullWhen(true)] out SubscriptionsEnumerator enumerator)
|
||||
private bool TryGetSubscriptions(Type eventType, EntityUid euid, [NotNullWhen(true)] out SubscriptionsEnumerator enumerator)
|
||||
{
|
||||
var eventTable = _eventTables[euid];
|
||||
if (!_eventTables.TryGetValue(euid, out var eventTable))
|
||||
{
|
||||
enumerator = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
// No subscriptions to this event type, return null.
|
||||
if (!eventTable.TryGetValue(eventType, out var subscribedComps))
|
||||
|
||||
@@ -56,8 +56,6 @@ namespace Robust.Shared.GameObjects
|
||||
if (Initialized)
|
||||
throw new InvalidOperationException("Already initialized.");
|
||||
|
||||
Initialized = true;
|
||||
|
||||
FillComponentDict();
|
||||
_componentFactory.ComponentAdded += OnComponentAdded;
|
||||
_componentFactory.ComponentReferenceAdded += OnComponentReferenceAdded;
|
||||
@@ -90,7 +88,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
#region Component Management
|
||||
|
||||
/// <inheritdoc />
|
||||
public T AddComponent<T>(IEntity entity) where T : Component, new()
|
||||
{
|
||||
if (entity == null) throw new ArgumentNullException(nameof(entity));
|
||||
@@ -104,18 +101,32 @@ namespace Robust.Shared.GameObjects
|
||||
return newComponent;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T AddComponent<T>(EntityUid uid) where T : Component, new()
|
||||
{
|
||||
if (!TryGetEntity(uid, out var entity)) throw new ArgumentException("Entity is not valid or deleted.", nameof(uid));
|
||||
|
||||
return AddComponent<T>(entity);
|
||||
}
|
||||
|
||||
public void AddComponent<T>(IEntity entity, T component, bool overwrite = false) where T : Component
|
||||
{
|
||||
if (entity == null || !entity.IsValid())
|
||||
throw new ArgumentException("Entity is not valid.", nameof(entity));
|
||||
AddComponent(entity.Uid, component, overwrite);
|
||||
}
|
||||
|
||||
public void AddComponent<T>(EntityUid uid, T component, bool overwrite = false) where T : Component
|
||||
{
|
||||
if (!uid.IsValid() || !EntityExists(uid))
|
||||
throw new ArgumentException("Entity is not valid.", nameof(uid));
|
||||
|
||||
if (component == null) throw new ArgumentNullException(nameof(component));
|
||||
|
||||
if (component.Owner != entity) throw new InvalidOperationException("Component is not owned by entity.");
|
||||
if (component.Owner.Uid != uid) throw new InvalidOperationException("Component is not owned by entity.");
|
||||
|
||||
var uid = entity.Uid;
|
||||
AddComponentInternal(uid, component, overwrite);
|
||||
}
|
||||
|
||||
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite = false) where T : Component
|
||||
{
|
||||
// get interface aliases for mapping
|
||||
var reg = _componentFactory.GetRegistration(component);
|
||||
|
||||
@@ -131,10 +142,10 @@ namespace Robust.Shared.GameObjects
|
||||
$"Component reference type {type} already occupied by {duplicate}");
|
||||
|
||||
// these two components are required on all entities and cannot be overwritten.
|
||||
if (duplicate is ITransformComponent || duplicate is IMetaDataComponent)
|
||||
if (duplicate is ITransformComponent || duplicate is MetaDataComponent)
|
||||
throw new InvalidOperationException("Tried to overwrite a protected component.");
|
||||
|
||||
RemoveComponentImmediate(duplicate);
|
||||
RemoveComponentImmediate(duplicate, uid, false);
|
||||
}
|
||||
|
||||
// add the component to the grid
|
||||
@@ -163,16 +174,18 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
ComponentAdded?.Invoke(this, new AddedComponentEventArgs(component, uid));
|
||||
|
||||
_componentDependencyManager.OnComponentAdd(entity.Uid, component);
|
||||
_componentDependencyManager.OnComponentAdd(uid, component);
|
||||
|
||||
component.LifeAddToEntity();
|
||||
|
||||
if (!entity.Initialized && !entity.Initializing)
|
||||
var metadata = GetComponent<MetaDataComponent>(uid);
|
||||
|
||||
if (!metadata.EntityInitialized && !metadata.EntityInitializing)
|
||||
return;
|
||||
|
||||
component.LifeInitialize();
|
||||
|
||||
if (entity.Initialized)
|
||||
if (metadata.EntityInitialized)
|
||||
component.LifeStartup();
|
||||
}
|
||||
|
||||
@@ -214,8 +227,8 @@ namespace Robust.Shared.GameObjects
|
||||
static int Sequence(IComponent x)
|
||||
=> x switch
|
||||
{
|
||||
ITransformComponent _ => 0,
|
||||
IMetaDataComponent _ => 1,
|
||||
MetaDataComponent _ => 0,
|
||||
ITransformComponent _ => 1,
|
||||
IPhysBody _ => 2,
|
||||
_ => int.MaxValue
|
||||
};
|
||||
@@ -258,7 +271,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
#endif
|
||||
// these two components are required on all entities and cannot be removed normally.
|
||||
if (!removeProtected && (component is ITransformComponent || component is IMetaDataComponent))
|
||||
if (!removeProtected && (component is ITransformComponent || component is MetaDataComponent))
|
||||
{
|
||||
DebugTools.Assert("Tried to remove a protected component.");
|
||||
return;
|
||||
@@ -287,14 +300,18 @@ namespace Robust.Shared.GameObjects
|
||||
#endif
|
||||
}
|
||||
|
||||
private void RemoveComponentImmediate(Component component)
|
||||
private void RemoveComponentImmediate(Component component, EntityUid uid, bool removeProtected)
|
||||
{
|
||||
if (component == null) throw new ArgumentNullException(nameof(component));
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
if (!component.Deleted)
|
||||
{
|
||||
// these two components are required on all entities and cannot be removed.
|
||||
if (component is ITransformComponent || component is IMetaDataComponent)
|
||||
if (!removeProtected && component is ITransformComponent || component is MetaDataComponent)
|
||||
{
|
||||
DebugTools.Assert("Tried to remove a protected component.");
|
||||
return;
|
||||
@@ -306,9 +323,17 @@ namespace Robust.Shared.GameObjects
|
||||
if (component.LifeStage != ComponentLifeStage.PreAdd)
|
||||
component.LifeRemoveFromEntity(); // Sets delete
|
||||
|
||||
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, component.Owner.Uid));
|
||||
|
||||
_componentDependencyManager.OnComponentRemove(uid, component);
|
||||
ComponentRemoved?.Invoke(this, new RemovedComponentEventArgs(component, uid));
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_runtimeLog.LogException(e,
|
||||
$"RemoveComponentImmediate, owner={component.Owner}, type={component.GetType()}");
|
||||
}
|
||||
#endif
|
||||
|
||||
DeleteComponent(component);
|
||||
}
|
||||
@@ -385,6 +410,15 @@ namespace Robust.Shared.GameObjects
|
||||
return AddComponent<T>(entity);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T EnsureComponent<T>(EntityUid uid) where T : Component, new()
|
||||
{
|
||||
if (TryGetComponent<T>(uid, out var component))
|
||||
return component;
|
||||
|
||||
return AddComponent<T>(uid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetComponent<T>(EntityUid uid)
|
||||
|
||||
@@ -75,6 +75,8 @@ namespace Robust.Shared.GameObjects
|
||||
_eventBus = new EntityEventBus(this);
|
||||
|
||||
InitializeComponents();
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
public virtual void Startup()
|
||||
@@ -176,11 +178,7 @@ namespace Robust.Shared.GameObjects
|
||||
throw new InvalidOperationException($"Tried to spawn entity {protoName} on invalid coordinates {coordinates}.");
|
||||
|
||||
var entity = CreateEntityUninitialized(protoName, coordinates);
|
||||
|
||||
InitializeAndStartEntity((Entity) entity);
|
||||
|
||||
if (_pauseManager.IsMapInitialized(coordinates.GetMapId(this))) entity.RunMapInit();
|
||||
|
||||
InitializeAndStartEntity((Entity) entity, coordinates.GetMapId(this));
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -188,7 +186,7 @@ namespace Robust.Shared.GameObjects
|
||||
public virtual IEntity SpawnEntity(string? protoName, MapCoordinates coordinates)
|
||||
{
|
||||
var entity = CreateEntityUninitialized(protoName, coordinates);
|
||||
InitializeAndStartEntity((Entity) entity);
|
||||
InitializeAndStartEntity((Entity) entity, coordinates.MapId);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -253,6 +251,7 @@ namespace Robust.Shared.GameObjects
|
||||
return;
|
||||
|
||||
var transform = entity.Transform;
|
||||
var metadata = entity.MetaData;
|
||||
entity.LifeStage = EntityLifeStage.Terminating;
|
||||
|
||||
EventBus.RaiseLocalEvent(entity.Uid, new EntityTerminatingEvent(), false);
|
||||
@@ -274,7 +273,7 @@ namespace Robust.Shared.GameObjects
|
||||
transform.DetachParentToNull();
|
||||
}
|
||||
|
||||
entity.LifeStage = EntityLifeStage.Deleted;
|
||||
metadata.EntityLifeStage = EntityLifeStage.Deleted;
|
||||
EntityDeleted?.Invoke(this, entity.Uid);
|
||||
EventBus.RaiseEvent(EventSource.Local, new EntityDeletedMessage(entity));
|
||||
Entities.Remove(entity.Uid);
|
||||
@@ -300,13 +299,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public bool EntityExists(EntityUid uid)
|
||||
{
|
||||
if (!TryGetEntity(uid, out var ent))
|
||||
return false;
|
||||
|
||||
if (ent.Deleted)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return TryGetEntity(uid, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -363,8 +356,12 @@ namespace Robust.Shared.GameObjects
|
||||
// We do this after the event, so if the event throws we have not committed
|
||||
Entities[entity.Uid] = entity;
|
||||
|
||||
// allocate the required MetaDataComponent
|
||||
AddComponent<MetaDataComponent>(entity);
|
||||
// Create the MetaDataComponent and set it directly on the Entity to avoid a stack overflow in DEBUG.
|
||||
var metadata = new MetaDataComponent() { Owner = entity };
|
||||
entity.MetaData = metadata;
|
||||
|
||||
// add the required MetaDataComponent directly.
|
||||
AddComponentInternal(uid.Value, metadata);
|
||||
|
||||
// allocate the required TransformComponent
|
||||
AddComponent<TransformComponent>(entity);
|
||||
@@ -401,13 +398,16 @@ namespace Robust.Shared.GameObjects
|
||||
EntityPrototype.LoadEntity(entity.Prototype, entity, ComponentFactory, context);
|
||||
}
|
||||
|
||||
private protected void InitializeAndStartEntity(Entity entity)
|
||||
private void InitializeAndStartEntity(Entity entity, MapId mapId)
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeEntity(entity);
|
||||
EntityInitialized?.Invoke(this, entity.Uid);
|
||||
StartEntity(entity);
|
||||
|
||||
// If the map we're initializing the entity on is initialized, run map init on it.
|
||||
if (_pauseManager.IsMapInitialized(mapId))
|
||||
entity.RunMapInit();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -416,9 +416,10 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private protected static void InitializeEntity(Entity entity)
|
||||
protected void InitializeEntity(Entity entity)
|
||||
{
|
||||
entity.InitializeComponents();
|
||||
EntityInitialized?.Invoke(this, entity.Uid);
|
||||
}
|
||||
|
||||
protected void StartEntity(Entity entity)
|
||||
|
||||
14
Robust.Shared/GameObjects/EntityManagerExt.cs
Normal file
14
Robust.Shared/GameObjects/EntityManagerExt.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public static class EntityManagerExt
|
||||
{
|
||||
public static T? GetComponentOrNull<T>(this IEntityManager entityManager, EntityUid entityUid)
|
||||
where T : class, IComponent
|
||||
{
|
||||
if (entityManager.TryGetComponent(entityUid, out T? component))
|
||||
return component;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using System;
|
||||
using NetSerializer;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
@@ -8,16 +9,15 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public EntityUid Uid { get; }
|
||||
|
||||
public ComponentChange[]? ComponentChanges { get; }
|
||||
public NetListAsArray<ComponentChange> ComponentChanges { get; }
|
||||
|
||||
public bool Empty => ComponentChanges is null;
|
||||
public bool Empty => ComponentChanges.Value is null or { Count: 0 };
|
||||
|
||||
public EntityState(EntityUid uid, ComponentChange[]? changedComponents)
|
||||
public EntityState(EntityUid uid, NetListAsArray<ComponentChange> changedComponents)
|
||||
{
|
||||
Uid = uid;
|
||||
|
||||
// empty lists are 5 bytes each
|
||||
ComponentChanges = changedComponents == null || changedComponents.Length == 0 ? null : changedComponents;
|
||||
ComponentChanges = changedComponents;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -11,14 +14,24 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity where to query the components.</param>
|
||||
/// <param name="component">A reference to the variable storing the component, or null if it has to be resolved.</param>
|
||||
/// <param name="logMissing">Whether to log missing components.</param>
|
||||
/// <typeparam name="TComp">The component type to resolve.</typeparam>
|
||||
/// <returns>True if the component is not null or was resolved correctly, false if the component couldn't be resolved.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve<TComp>(EntityUid uid, [NotNullWhen(true)] ref TComp? component)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected bool Resolve<TComp>(EntityUid uid, [NotNullWhen(true)] ref TComp? component, bool logMissing = true)
|
||||
where TComp : IComponent
|
||||
{
|
||||
DebugTools.Assert(component == null || uid == component.Owner.Uid, "Specified Entity is not the component's Owner!");
|
||||
return component != null || EntityManager.TryGetComponent(uid, out component);
|
||||
|
||||
if (component != null)
|
||||
return true;
|
||||
|
||||
var found = EntityManager.TryGetComponent(uid, out component);
|
||||
|
||||
if(logMissing && !found)
|
||||
Logger.ErrorS("resolve", $"Can't resolve \"{typeof(TComp)}\" on entity {uid}!\n{new StackTrace(1, true)}");
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -27,15 +40,16 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">The entity where to query the components.</param>
|
||||
/// <param name="comp1">A reference to the variable storing the component, or null if it has to be resolved.</param>
|
||||
/// <param name="comp2">A reference to the variable storing the component, or null if it has to be resolved.</param>
|
||||
/// <param name="logMissing">Whether to log missing components.</param>
|
||||
/// <typeparam name="TComp1">The component type to resolve.</typeparam>
|
||||
/// <typeparam name="TComp2">The component type to resolve.</typeparam>
|
||||
/// <returns>True if the components are not null or were resolved correctly, false if any of the component couldn't be resolved.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve<TComp1, TComp2>(EntityUid uid, [NotNullWhen(true)] ref TComp1? comp1, [NotNullWhen(true)] ref TComp2? comp2)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected bool Resolve<TComp1, TComp2>(EntityUid uid, [NotNullWhen(true)] ref TComp1? comp1, [NotNullWhen(true)] ref TComp2? comp2, bool logMissing = true)
|
||||
where TComp1 : IComponent
|
||||
where TComp2 : IComponent
|
||||
{
|
||||
return Resolve(uid, ref comp1) && Resolve(uid, ref comp2);
|
||||
return Resolve(uid, ref comp1, logMissing) & Resolve(uid, ref comp2, logMissing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,17 +59,18 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="comp1">A reference to the variable storing the component, or null if it has to be resolved.</param>
|
||||
/// <param name="comp2">A reference to the variable storing the component, or null if it has to be resolved.</param>
|
||||
/// <param name="comp3">A reference to the variable storing the component, or null if it has to be resolved.</param>
|
||||
/// <param name="logMissing">Whether to log missing components.</param>
|
||||
/// <typeparam name="TComp1">The component type to resolve.</typeparam>
|
||||
/// <typeparam name="TComp2">The component type to resolve.</typeparam>
|
||||
/// <typeparam name="TComp3">The component type to resolve.</typeparam>
|
||||
/// <returns>True if the components are not null or were resolved correctly, false if any of the component couldn't be resolved.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve<TComp1, TComp2, TComp3>(EntityUid uid, [NotNullWhen(true)] ref TComp1? comp1, [NotNullWhen(true)] ref TComp2? comp2, [NotNullWhen(true)] ref TComp3? comp3)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected bool Resolve<TComp1, TComp2, TComp3>(EntityUid uid, [NotNullWhen(true)] ref TComp1? comp1, [NotNullWhen(true)] ref TComp2? comp2, [NotNullWhen(true)] ref TComp3? comp3, bool logMissing = true)
|
||||
where TComp1 : IComponent
|
||||
where TComp2 : IComponent
|
||||
where TComp3 : IComponent
|
||||
{
|
||||
return Resolve(uid, ref comp1, ref comp2) && Resolve(uid, ref comp3);
|
||||
return Resolve(uid, ref comp1, ref comp2, logMissing) & Resolve(uid, ref comp3, logMissing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -66,19 +81,20 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="comp2">A reference to the variable storing the component, or null if it has to be resolved.</param>
|
||||
/// <param name="comp3">A reference to the variable storing the component, or null if it has to be resolved.</param>
|
||||
/// <param name="comp4">A reference to the variable storing the component, or null if it has to be resolved.</param>
|
||||
/// <param name="logMissing">Whether to log missing components.</param>
|
||||
/// <typeparam name="TComp1">The component type to resolve.</typeparam>
|
||||
/// <typeparam name="TComp2">The component type to resolve.</typeparam>
|
||||
/// <typeparam name="TComp3">The component type to resolve.</typeparam>
|
||||
/// <typeparam name="TComp4">The component type to resolve.</typeparam>
|
||||
/// <returns>True if the components are not null or were resolved correctly, false if any of the component couldn't be resolved.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve<TComp1, TComp2, TComp3, TComp4>(EntityUid uid, [NotNullWhen(true)] ref TComp1? comp1, [NotNullWhen(true)] ref TComp2? comp2, [NotNullWhen(true)] ref TComp3? comp3, [NotNullWhen(true)] ref TComp4? comp4)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected bool Resolve<TComp1, TComp2, TComp3, TComp4>(EntityUid uid, [NotNullWhen(true)] ref TComp1? comp1, [NotNullWhen(true)] ref TComp2? comp2, [NotNullWhen(true)] ref TComp3? comp3, [NotNullWhen(true)] ref TComp4? comp4, bool logMissing = true)
|
||||
where TComp1 : IComponent
|
||||
where TComp2 : IComponent
|
||||
where TComp3 : IComponent
|
||||
where TComp4 : IComponent
|
||||
{
|
||||
return Resolve(uid, ref comp1, ref comp2) && Resolve(uid, ref comp3, ref comp4);
|
||||
return Resolve(uid, ref comp1, ref comp2, logMissing) & Resolve(uid, ref comp3, ref comp4, logMissing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +33,14 @@ namespace Robust.Shared.GameObjects
|
||||
public class PlayAudioPositionalMessage : AudioMessage
|
||||
{
|
||||
public EntityCoordinates Coordinates { get; set; }
|
||||
public EntityCoordinates FallbackCoordinates { get; set; }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class PlayAudioEntityMessage : AudioMessage
|
||||
{
|
||||
public EntityCoordinates Coordinates { get; set; }
|
||||
public EntityUid EntityUid { get; set; }
|
||||
public EntityCoordinates Coordinates { get; set; }
|
||||
public EntityCoordinates FallbackCoordinates { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// The MetaData Component of this entity.
|
||||
/// </summary>
|
||||
IMetaDataComponent MetaData { get; }
|
||||
MetaDataComponent MetaData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Public method to add a component to an entity.
|
||||
|
||||
@@ -32,6 +32,14 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>The newly added component.</returns>
|
||||
T AddComponent<T>(IEntity entity) where T : Component, new();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Component type to an entity. If the entity is already Initialized, the component will
|
||||
/// automatically be Initialized and Started.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Concrete component type to add.</typeparam>
|
||||
/// <returns>The newly added component.</returns>
|
||||
T AddComponent<T>(EntityUid uid) where T : Component, new();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Component to an entity. If the entity is already Initialized, the component will
|
||||
/// automatically be Initialized and Started.
|
||||
@@ -41,6 +49,15 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="overwrite">Should it overwrite existing components?</param>
|
||||
void AddComponent<T>(IEntity entity, T component, bool overwrite = false) where T : Component;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Component to an entity. If the entity is already Initialized, the component will
|
||||
/// automatically be Initialized and Started.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity being modified.</param>
|
||||
/// <param name="component">Component to add.</param>
|
||||
/// <param name="overwrite">Should it overwrite existing components?</param>
|
||||
void AddComponent<T>(EntityUid uid, T component, bool overwrite = false) where T : Component;
|
||||
|
||||
/// <summary>
|
||||
/// Removes the component with the specified reference type,
|
||||
/// Without needing to have the component itself.
|
||||
@@ -109,8 +126,22 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>True if the entity has a component with the given network ID, otherwise false.</returns>
|
||||
bool HasComponent(EntityUid uid, ushort netId);
|
||||
|
||||
/// <summary>
|
||||
/// This method will always return a component for a certain entity, adding it if it's not there already.
|
||||
/// </summary>
|
||||
/// <param name="entity">Entity to modify.</param>
|
||||
/// <typeparam name="T">Component to add.</typeparam>
|
||||
/// <returns>The component in question</returns>
|
||||
T EnsureComponent<T>(IEntity entity) where T : Component, new();
|
||||
|
||||
/// <summary>
|
||||
/// This method will always return a component for a certain entity, adding it if it's not there already.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity to modify.</param>
|
||||
/// <typeparam name="T">Component to add.</typeparam>
|
||||
/// <returns>The component in question</returns>
|
||||
T EnsureComponent<T>(EntityUid uid) where T : Component, new();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
/// </summary>
|
||||
|
||||
38
Robust.Shared/GameObjects/Systems/CollideOnAnchorSystem.cs
Normal file
38
Robust.Shared/GameObjects/Systems/CollideOnAnchorSystem.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public sealed class CollideOnAnchorSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CollideOnAnchorComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<CollideOnAnchorComponent, AnchorStateChangedEvent>(OnAnchor);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, CollideOnAnchorComponent component, ComponentStartup args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out TransformComponent? transformComponent)) return;
|
||||
|
||||
SetCollide(uid, component, transformComponent.Anchored);
|
||||
}
|
||||
|
||||
private void OnAnchor(EntityUid uid, CollideOnAnchorComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
SetCollide(uid, component, args.Anchored);
|
||||
}
|
||||
|
||||
private void SetCollide(EntityUid uid, CollideOnAnchorComponent component, bool anchored)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? body)) return;
|
||||
|
||||
var enabled = component.Enable;
|
||||
|
||||
if (!anchored)
|
||||
{
|
||||
enabled ^= true;
|
||||
}
|
||||
|
||||
body.CanCollide = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,10 @@ namespace Robust.Shared.GameObjects
|
||||
if (!EntityManager.TryGetComponent<PhysicsComponent>(uid, out var body)) return;
|
||||
|
||||
// If we're attached to the map we'll also just never disable collision due to how grid movement works.
|
||||
body.CanCollide = !component.Enabled || body.Awake || body.Joints.Any() || EntityManager.GetComponent<TransformComponent>(uid).GridID == GridId.Invalid;
|
||||
body.CanCollide = !component.Enabled ||
|
||||
body.Awake ||
|
||||
(EntityManager.TryGetComponent(uid, out JointComponent? jointComponent) && jointComponent.JointCount > 0) ||
|
||||
EntityManager.GetComponent<TransformComponent>(uid).GridID == GridId.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
@@ -40,7 +41,7 @@ namespace Robust.Shared.GameObjects
|
||||
IEnumerable<IEntity> GetEntitiesInArc(EntityCoordinates coordinates, float range, Angle direction,
|
||||
float arcWidth, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Box2 position, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<IEntity> GetEntitiesIntersecting(IEntity entity, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
@@ -50,7 +51,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
void FastEntitiesIntersecting(in MapId mapId, ref Box2 position, EntityQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
void FastEntitiesIntersecting(in MapId mapId, ref Box2 worldAABB, EntityQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<IEntity> GetEntitiesInRange(EntityCoordinates position, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
@@ -263,17 +264,52 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
#region Spatial Queries
|
||||
|
||||
private IEnumerable<EntityLookupComponent> GetLookupsIntersecting(MapId mapId, Box2 worldAABB)
|
||||
private LookupsEnumerator GetLookupsIntersecting(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) yield break;
|
||||
_mapManager.FindGridsIntersectingEnumerator(mapId, worldAABB, out var gridEnumerator, true);
|
||||
|
||||
// TODO: Recursive and all that.
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_lookupEnlargementRange)))
|
||||
return new LookupsEnumerator(_entityManager, _mapManager, mapId, gridEnumerator);
|
||||
}
|
||||
|
||||
private struct LookupsEnumerator
|
||||
{
|
||||
private IEntityManager _entityManager;
|
||||
private IMapManager _mapManager;
|
||||
|
||||
private MapId _mapId;
|
||||
private FindGridsEnumerator _enumerator;
|
||||
|
||||
private bool _final;
|
||||
|
||||
public LookupsEnumerator(IEntityManager entityManager, IMapManager mapManager, MapId mapId, FindGridsEnumerator enumerator)
|
||||
{
|
||||
yield return _entityManager.GetEntity(grid.GridEntityId).GetComponent<EntityLookupComponent>();
|
||||
_entityManager = entityManager;
|
||||
_mapManager = mapManager;
|
||||
|
||||
_mapId = mapId;
|
||||
_enumerator = enumerator;
|
||||
_final = false;
|
||||
}
|
||||
|
||||
yield return _mapManager.GetMapEntity(mapId).GetComponent<EntityLookupComponent>();
|
||||
public bool MoveNext([NotNullWhen(true)] out EntityLookupComponent? component)
|
||||
{
|
||||
if (!_enumerator.MoveNext(out var grid))
|
||||
{
|
||||
if (_final || _mapId == MapId.Nullspace)
|
||||
{
|
||||
component = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
_final = true;
|
||||
component = _mapManager.GetMapEntity(_mapId).GetComponent<EntityLookupComponent>();
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Recursive and all that.
|
||||
component = _entityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IEntity> GetAnchored(MapId mapId, Box2 worldAABB, LookupFlags flags)
|
||||
@@ -293,8 +329,9 @@ namespace Robust.Shared.GameObjects
|
||||
public bool AnyEntitiesIntersecting(MapId mapId, Box2 box, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var found = false;
|
||||
var enumerator = GetLookupsIntersecting(mapId, box);
|
||||
|
||||
foreach (var lookup in GetLookupsIntersecting(mapId, box))
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = lookup.Owner.Transform.InvWorldMatrix.TransformBox(box);
|
||||
|
||||
@@ -320,20 +357,21 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public void FastEntitiesIntersecting(in MapId mapId, ref Box2 position, EntityQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
public void FastEntitiesIntersecting(in MapId mapId, ref Box2 worldAABB, EntityQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
foreach (var lookup in GetLookupsIntersecting(mapId, position))
|
||||
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = lookup.Owner.Transform.InvWorldMatrix.TransformBox(position);
|
||||
var offsetBox = lookup.Owner.Transform.InvWorldMatrix.TransformBox(worldAABB);
|
||||
|
||||
lookup.Tree._b2Tree.FastQuery(ref offsetBox, (ref IEntity data) => callback(data));
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
|
||||
{
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, position))
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(position))
|
||||
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
|
||||
{
|
||||
if (!_entityManager.TryGetEntity(uid, out var ent)) continue;
|
||||
callback(ent);
|
||||
@@ -343,15 +381,16 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Box2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
public IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<IEntity>();
|
||||
|
||||
var list = new List<IEntity>();
|
||||
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
|
||||
|
||||
foreach (var lookup in GetLookupsIntersecting(mapId, position))
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = lookup.Owner.Transform.InvWorldMatrix.TransformBox(position);
|
||||
var offsetBox = lookup.Owner.Transform.InvWorldMatrix.TransformBox(worldAABB);
|
||||
|
||||
lookup.Tree.QueryAabb(ref list, (ref List<IEntity> list, in IEntity ent) =>
|
||||
{
|
||||
@@ -363,7 +402,7 @@ namespace Robust.Shared.GameObjects
|
||||
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
foreach (var ent in GetAnchored(mapId, position, flags))
|
||||
foreach (var ent in GetAnchored(mapId, worldAABB, flags))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
@@ -380,7 +419,9 @@ namespace Robust.Shared.GameObjects
|
||||
var list = new List<IEntity>();
|
||||
var state = (list, position);
|
||||
|
||||
foreach (var lookup in GetLookupsIntersecting(mapId, aabb))
|
||||
var enumerator = GetLookupsIntersecting(mapId, aabb);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var localPoint = lookup.Owner.Transform.InvWorldMatrix.Transform(position);
|
||||
|
||||
@@ -545,8 +586,9 @@ namespace Robust.Shared.GameObjects
|
||||
var state = (list, position);
|
||||
|
||||
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
|
||||
var enumerator = GetLookupsIntersecting(mapId, aabb);
|
||||
|
||||
foreach (var lookup in GetLookupsIntersecting(mapId, aabb))
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetPos = lookup.Owner.Transform.InvWorldMatrix.Transform(position);
|
||||
|
||||
@@ -669,9 +711,10 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
// TODO: Need to fix ordering issues and then we can just directly remove it from the tree
|
||||
// rather than this O(n) legacy garbage.
|
||||
// Also we can't early returns because somehow it gets added to multiple trees!!!
|
||||
foreach (var lookup in _entityManager.EntityQuery<EntityLookupComponent>(true))
|
||||
{
|
||||
if (lookup.Tree.Remove(entity)) return;
|
||||
lookup.Tree.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
Robust.Shared/GameObjects/Systems/SharedAudioSystem.cs
Normal file
27
Robust.Shared/GameObjects/Systems/SharedAudioSystem.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public class SharedAudioSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
protected EntityCoordinates GetFallbackCoordinates(MapCoordinates mapCoordinates)
|
||||
{
|
||||
if (_mapManager.TryFindGridAt(mapCoordinates, out var mapGrid))
|
||||
{
|
||||
return new EntityCoordinates(mapGrid.GridEntityId,
|
||||
mapGrid.WorldToLocal(mapCoordinates.Position));
|
||||
}
|
||||
|
||||
if (_mapManager.HasMapEntity(mapCoordinates.MapId))
|
||||
{
|
||||
return new EntityCoordinates(_mapManager.GetMapEntityId(mapCoordinates.MapId),
|
||||
mapCoordinates.Position);
|
||||
}
|
||||
|
||||
return EntityCoordinates.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// Only reason this exists is so we can avoid the message allocation when not needed.
|
||||
/// It's only useful for debugging and is kind of a perf sink.
|
||||
/// </summary>
|
||||
public class SharedDebugPhysicsSystem : EntitySystem
|
||||
public abstract class SharedDebugPhysicsSystem : EntitySystem
|
||||
{
|
||||
public virtual void HandlePreSolve(Contact contact, in Manifold oldManifold) {}
|
||||
}
|
||||
|
||||
348
Robust.Shared/GameObjects/Systems/SharedPhysicsSystem.Queries.cs
Normal file
348
Robust.Shared/GameObjects/Systems/SharedPhysicsSystem.Queries.cs
Normal file
@@ -0,0 +1,348 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/*
|
||||
* Handles all of the public query methods for physics.
|
||||
*/
|
||||
public partial class SharedPhysicsSystem
|
||||
{
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadphaseSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Get the percentage that 2 bodies overlap. Ignores whether collision is turned on for either body.
|
||||
/// </summary>
|
||||
/// <param name="bodyA"></param>
|
||||
/// <param name="bodyB"></param>
|
||||
/// <returns> 0 -> 1.0f based on WorldAABB overlap</returns>
|
||||
public float IntersectionPercent(PhysicsComponent bodyA, PhysicsComponent bodyB)
|
||||
{
|
||||
// TODO: Use actual shapes and not just the AABB?
|
||||
return bodyA.GetWorldAABB().IntersectPercentage(bodyB.GetWorldAABB());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the specified collision rectangle collides with any of the physBodies under management.
|
||||
/// Also fires the OnCollide event of the first managed physBody to intersect with the collider.
|
||||
/// </summary>
|
||||
/// <param name="collider">Collision rectangle to check</param>
|
||||
/// <param name="mapId">Map to check on</param>
|
||||
/// <param name="approximate"></param>
|
||||
/// <returns>true if collides, false if not</returns>
|
||||
public bool TryCollideRect(Box2 collider, MapId mapId, bool approximate = true)
|
||||
{
|
||||
var state = (collider, mapId, found: false);
|
||||
|
||||
foreach (var broadphase in _broadphaseSystem.GetBroadphases(mapId, collider))
|
||||
{
|
||||
var gridCollider = broadphase.Owner.Transform.InvWorldMatrix.TransformBox(collider);
|
||||
|
||||
broadphase.Tree.QueryAabb(ref state, (ref (Box2 collider, MapId map, bool found) state, in FixtureProxy proxy) =>
|
||||
{
|
||||
if (proxy.Fixture.CollisionLayer == 0x0)
|
||||
return true;
|
||||
|
||||
if (proxy.AABB.Intersects(gridCollider))
|
||||
{
|
||||
state.found = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, gridCollider, approximate);
|
||||
}
|
||||
|
||||
return state.found;
|
||||
}
|
||||
|
||||
public IEnumerable<PhysicsComponent> GetCollidingEntities(PhysicsComponent body, Vector2 offset, bool approximate = true)
|
||||
{
|
||||
var broadphase = body.Broadphase;
|
||||
if (broadphase == null)
|
||||
{
|
||||
return Array.Empty<PhysicsComponent>();
|
||||
}
|
||||
|
||||
var entities = new List<PhysicsComponent>();
|
||||
|
||||
var state = (body, entities);
|
||||
|
||||
foreach (var fixture in body._fixtures)
|
||||
{
|
||||
foreach (var proxy in fixture.Proxies)
|
||||
{
|
||||
broadphase.Tree.QueryAabb(ref state,
|
||||
(ref (PhysicsComponent body, List<PhysicsComponent> entities) state,
|
||||
in FixtureProxy other) =>
|
||||
{
|
||||
if (other.Fixture.Body.Deleted || other.Fixture.Body == body) return true;
|
||||
if ((proxy.Fixture.CollisionMask & other.Fixture.CollisionLayer) == 0x0) return true;
|
||||
if (!body.ShouldCollide(other.Fixture.Body)) return true;
|
||||
|
||||
state.entities.Add(other.Fixture.Body);
|
||||
return true;
|
||||
}, proxy.AABB, approximate);
|
||||
}
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all entities colliding with a certain body.
|
||||
/// </summary>
|
||||
public IEnumerable<PhysicsComponent> GetCollidingEntities(MapId mapId, in Box2 worldAABB)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Array.Empty<PhysicsComponent>();
|
||||
|
||||
var bodies = new HashSet<PhysicsComponent>();
|
||||
|
||||
foreach (var broadphase in _broadphaseSystem.GetBroadphases(mapId, worldAABB))
|
||||
{
|
||||
var gridAABB = broadphase.Owner.Transform.InvWorldMatrix.TransformBox(worldAABB);
|
||||
|
||||
foreach (var proxy in broadphase.Tree.QueryAabb(gridAABB, false))
|
||||
{
|
||||
bodies.Add(proxy.Fixture.Body);
|
||||
}
|
||||
}
|
||||
|
||||
return bodies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all entities colliding with a certain body.
|
||||
/// </summary>
|
||||
public IEnumerable<PhysicsComponent> GetCollidingEntities(MapId mapId, in Box2Rotated worldBounds)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Array.Empty<PhysicsComponent>();
|
||||
|
||||
var bodies = new HashSet<PhysicsComponent>();
|
||||
|
||||
foreach (var broadphase in _broadphaseSystem.GetBroadphases(mapId, worldBounds.CalcBoundingBox()))
|
||||
{
|
||||
var gridAABB = broadphase.Owner.Transform.InvWorldMatrix.TransformBox(worldBounds);
|
||||
|
||||
foreach (var proxy in broadphase.Tree.QueryAabb(gridAABB, false))
|
||||
{
|
||||
bodies.Add(proxy.Fixture.Body);
|
||||
}
|
||||
}
|
||||
|
||||
return bodies;
|
||||
}
|
||||
|
||||
// TODO: This is mainly just a hack to get rotated grid doors working in SS14 but whenever we do querysystem we'll clean this shit up
|
||||
/// <summary>
|
||||
/// Returns all enabled physics bodies intersecting this body.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not consider CanCollide on the provided body.
|
||||
/// </remarks>
|
||||
/// <param name="body">The body to check for intersection</param>
|
||||
/// <param name="enlarge">How much to enlarge / shrink the bounds by given we often extend them slightly.</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<PhysicsComponent> GetCollidingEntities(PhysicsComponent body, float enlarge = 0f)
|
||||
{
|
||||
// TODO: Should use the collisionmanager test for overlap instead (once I optimise and check it actually works).
|
||||
var mapId = body.Owner.Transform.MapID;
|
||||
|
||||
if (mapId == MapId.Nullspace || body.FixtureCount == 0) return Array.Empty<PhysicsComponent>();
|
||||
|
||||
var bodies = new HashSet<PhysicsComponent>();
|
||||
var transform = body.GetTransform();
|
||||
var worldAABB = body.GetWorldAABB(transform.Position, transform.Quaternion2D.Angle);
|
||||
|
||||
foreach (var broadphase in _broadphaseSystem.GetBroadphases(mapId, worldAABB))
|
||||
{
|
||||
var invMatrix = broadphase.Owner.Transform.InvWorldMatrix;
|
||||
|
||||
var localTransform = new Transform(invMatrix.Transform(transform.Position), transform.Quaternion2D.Angle - broadphase.Owner.Transform.WorldRotation);
|
||||
|
||||
foreach (var fixture in body._fixtures)
|
||||
{
|
||||
var collisionMask = fixture.CollisionMask;
|
||||
var collisionLayer = fixture.CollisionLayer;
|
||||
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
var aabb = fixture.Shape.ComputeAABB(localTransform, i).Enlarged(enlarge);
|
||||
|
||||
foreach (var proxy in broadphase.Tree.QueryAabb(aabb, false))
|
||||
{
|
||||
var proxyFixture = proxy.Fixture;
|
||||
var proxyBody = proxyFixture.Body;
|
||||
|
||||
if (proxyBody == body ||
|
||||
(proxyFixture.CollisionMask & collisionLayer) == 0x0 &&
|
||||
(collisionMask & proxyFixture.CollisionLayer) == 0x0) continue;
|
||||
|
||||
bodies.Add(proxyBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bodies;
|
||||
}
|
||||
|
||||
// TODO: This will get every body but we don't need to do that
|
||||
/// <summary>
|
||||
/// Checks whether a body is colliding
|
||||
/// </summary>
|
||||
/// <param name="body"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsColliding(PhysicsComponent body, Vector2 offset, bool approximate)
|
||||
{
|
||||
return GetCollidingEntities(body, offset, approximate).Any();
|
||||
}
|
||||
|
||||
#region RayCast
|
||||
/// <summary>
|
||||
/// Casts a ray in the world, returning the first entity it hits (or all entities it hits, if so specified)
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="ray">Ray to cast in the world.</param>
|
||||
/// <param name="maxLength">Maximum length of the ray in meters.</param>
|
||||
/// <param name="predicate">A predicate to check whether to ignore an entity or not. If it returns true, it will be ignored.</param>
|
||||
/// <param name="returnOnFirstHit">If true, will only include the first hit entity in results. Otherwise, returns all of them.</param>
|
||||
/// <returns>A result object describing the hit, if any.</returns>
|
||||
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId mapId, CollisionRay ray,
|
||||
float maxLength = 50F,
|
||||
Func<IEntity, bool>? predicate = null, bool returnOnFirstHit = true)
|
||||
{
|
||||
List<RayCastResults> results = new();
|
||||
var endPoint = ray.Position + ray.Direction.Normalized * maxLength;
|
||||
var rayBox = new Box2(Vector2.ComponentMin(ray.Position, endPoint),
|
||||
Vector2.ComponentMax(ray.Position, endPoint));
|
||||
|
||||
foreach (var broadphase in _broadphaseSystem.GetBroadphases(mapId, rayBox))
|
||||
{
|
||||
var invMatrix = broadphase.Owner.Transform.InvWorldMatrix;
|
||||
var matrix = broadphase.Owner.Transform.WorldMatrix;
|
||||
|
||||
var position = invMatrix.Transform(ray.Position);
|
||||
var gridRot = new Angle(-broadphase.Owner.Transform.WorldRotation.Theta);
|
||||
var direction = gridRot.RotateVec(ray.Direction);
|
||||
|
||||
var gridRay = new CollisionRay(position, direction, ray.CollisionMask);
|
||||
|
||||
broadphase.Tree.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) =>
|
||||
{
|
||||
if (returnOnFirstHit && results.Count > 0) return true;
|
||||
|
||||
if (distFromOrigin > maxLength)
|
||||
return true;
|
||||
|
||||
if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (predicate?.Invoke(proxy.Fixture.Body.Owner) == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Shape raycast here
|
||||
|
||||
// Need to convert it back to world-space.
|
||||
var result = new RayCastResults(distFromOrigin, matrix.Transform(point), proxy.Fixture.Body.Owner);
|
||||
results.Add(result);
|
||||
#if DEBUG
|
||||
EntityManager.EventBus.QueueEvent(EventSource.Local,
|
||||
new DebugDrawRayMessage(
|
||||
new DebugRayData(ray, maxLength, result)));
|
||||
#endif
|
||||
return true;
|
||||
}, gridRay);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (results.Count == 0)
|
||||
{
|
||||
EntityManager.EventBus.QueueEvent(EventSource.Local,
|
||||
new DebugDrawRayMessage(
|
||||
new DebugRayData(ray, maxLength, null)));
|
||||
}
|
||||
#endif
|
||||
|
||||
results.Sort((a, b) => a.Distance.CompareTo(b.Distance));
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray in the world and returns the first entity it hits, or a list of all entities it hits.
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="ray">Ray to cast in the world.</param>
|
||||
/// <param name="maxLength">Maximum length of the ray in meters.</param>
|
||||
/// <param name="ignoredEnt">A single entity that can be ignored by the RayCast. Useful if the ray starts inside the body of an entity.</param>
|
||||
/// <param name="returnOnFirstHit">If false, will return a list of everything it hits, otherwise will just return a list of the first entity hit</param>
|
||||
/// <returns>An enumerable of either the first entity hit or everything hit</returns>
|
||||
public IEnumerable<RayCastResults> IntersectRay(MapId mapId, CollisionRay ray, float maxLength = 50, IEntity? ignoredEnt = null, bool returnOnFirstHit = true)
|
||||
=> IntersectRayWithPredicate(mapId, ray, maxLength, entity => entity == ignoredEnt, returnOnFirstHit);
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray in the world and returns the distance the ray traveled while colliding with entities
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="ray">Ray to cast in the world.</param>
|
||||
/// <param name="maxLength">Maximum length of the ray in meters.</param>
|
||||
/// <param name="ignoredEnt">A single entity that can be ignored by the RayCast. Useful if the ray starts inside the body of an entity.</param>
|
||||
/// <returns>The distance the ray traveled while colliding with entities</returns>
|
||||
public float IntersectRayPenetration(MapId mapId, CollisionRay ray, float maxLength, IEntity? ignoredEnt = null)
|
||||
{
|
||||
var penetration = 0f;
|
||||
var endPoint = ray.Position + ray.Direction.Normalized * maxLength;
|
||||
var rayBox = new Box2(Vector2.ComponentMin(ray.Position, endPoint),
|
||||
Vector2.ComponentMax(ray.Position, endPoint));
|
||||
|
||||
foreach (var broadphase in _broadphaseSystem.GetBroadphases(mapId, rayBox))
|
||||
{
|
||||
var offset = broadphase.Owner.Transform.InvWorldMatrix.Transform(ray.Position);
|
||||
var gridRot = new Angle(-broadphase.Owner.Transform.WorldRotation.Theta);
|
||||
var direction = gridRot.RotateVec(ray.Direction);
|
||||
|
||||
var gridRay = new CollisionRay(offset, direction, ray.CollisionMask);
|
||||
|
||||
broadphase.Tree.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) =>
|
||||
{
|
||||
if (distFromOrigin > maxLength || proxy.Fixture.Body.Owner == ignoredEnt) return true;
|
||||
|
||||
if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (new Ray(point + ray.Direction * proxy.AABB.Size.Length * 2, -ray.Direction).Intersects(
|
||||
proxy.AABB, out _, out var exitPoint))
|
||||
{
|
||||
penetration += (point - exitPoint).Length;
|
||||
}
|
||||
return true;
|
||||
}, gridRay);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (penetration > 0f)
|
||||
{
|
||||
EntityManager.EventBus.QueueEvent(EventSource.Local,
|
||||
new DebugDrawRayMessage(
|
||||
new DebugRayData(ray, maxLength, null)));
|
||||
}
|
||||
#endif
|
||||
|
||||
return penetration;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user