mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3538d2797d | ||
|
|
f7cb66f832 | ||
|
|
c9f6fc7182 | ||
|
|
bc8b37de9a | ||
|
|
9d1b15ab4b | ||
|
|
ea1cc5e446 | ||
|
|
bcb5c2d35d | ||
|
|
c011eff80e | ||
|
|
e163c496c3 | ||
|
|
fec81bc2a1 | ||
|
|
7016facb9a | ||
|
|
0c41a041e3 | ||
|
|
55571ef5b1 | ||
|
|
afaef645b0 | ||
|
|
d442d90d60 | ||
|
|
fea592e1d5 | ||
|
|
bb9517fd19 | ||
|
|
a734bc50fa | ||
|
|
9e9ac56c95 | ||
|
|
6979a63b1e | ||
|
|
ae7725aafe | ||
|
|
1a7e490e4b | ||
|
|
51971d0994 | ||
|
|
d2aa8ecb5a | ||
|
|
c4a5752c2a | ||
|
|
6a336d236b | ||
|
|
fc55c8e0d3 | ||
|
|
2719b9f0c8 | ||
|
|
bd69d51d36 | ||
|
|
1bf0687671 | ||
|
|
bdef9e3401 | ||
|
|
43648201ce | ||
|
|
2b2d08ba47 | ||
|
|
d7f6a9ba43 | ||
|
|
15f81751f7 | ||
|
|
033c52751a | ||
|
|
51edceae4d | ||
|
|
5d7720755a | ||
|
|
acc7bf7595 | ||
|
|
de55d1bc52 | ||
|
|
d5e6e91b58 | ||
|
|
da2bfdaa10 | ||
|
|
af6cac14d6 | ||
|
|
3f37846731 | ||
|
|
d9bf1d1afb | ||
|
|
b9b80192e7 | ||
|
|
e03aec47ef | ||
|
|
ee906af16e | ||
|
|
e205ae3627 | ||
|
|
d818c5aa0c | ||
|
|
aa8fe8ac92 | ||
|
|
4ba6687b9d | ||
|
|
8f2817aa4e | ||
|
|
89c7839fe2 | ||
|
|
9799132001 | ||
|
|
34ffa56c57 | ||
|
|
f8410a4674 | ||
|
|
43b991c690 | ||
|
|
c463fc5e78 | ||
|
|
e21b3e069a | ||
|
|
c8f94ab40d | ||
|
|
2a882b5555 | ||
|
|
32d8a1cba9 | ||
|
|
5d84be9c78 | ||
|
|
eaaa70437a | ||
|
|
448e8b0c2c | ||
|
|
42948d8f8e | ||
|
|
039468f4b6 | ||
|
|
6a3f88b1c6 | ||
|
|
a314c5f797 |
@@ -57,7 +57,7 @@
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
210
RELEASE-NOTES.md
210
RELEASE-NOTES.md
@@ -54,6 +54,216 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 246.0.2
|
||||
|
||||
|
||||
## 246.0.1
|
||||
|
||||
|
||||
## 246.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The fixes to renderer state may have inadvertantly broken some rendering code that relied upon the old behavior.
|
||||
* TileRenderFlag has been removed and now it's just a byte flag on the tile for content usage.
|
||||
|
||||
### New features
|
||||
|
||||
* Add BeforeLighting overlay draw space for overlays that need to draw directly to lighting and want to do it immediately beforehand.
|
||||
* Change BlurLights to BlurRenderTarget and make it public for content usage.
|
||||
* Add ContentFlag to tiles for content-flag usage.
|
||||
* Add a basic mix shader for doing canvas blends.
|
||||
* Add GetClearColorEvent for content to override the clear color behavior.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix pushing renderer state not restoring stencil status, blend status, queued shader instance scissor state.
|
||||
|
||||
|
||||
## 245.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add more info to the AnchorEntity debug message.
|
||||
* Make ParseObject public where it will parse a supplied Type and string into the specified object.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix EntityPrototypeView not always updating the entity correctly.
|
||||
* Tweak BUI shutdown to potentially avoid skipping closing.
|
||||
|
||||
### Other
|
||||
|
||||
* Increase Audio entity despawn buffer to avoid clipping.
|
||||
|
||||
|
||||
## 245.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `BoundUserInterface.Open()` now has the `MustCallBase` attribute
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an error in `MappingDataNode.TryAddCopy()`, which was causing yaml inheritance/deserialization bugs.
|
||||
|
||||
|
||||
## 244.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Increase physics speedcap default from 35m/s to 400m/s in-line with box2d v3.
|
||||
|
||||
### New features
|
||||
|
||||
* Add EntityManager overloads for ComponentRegistration that's faster than the generic methods.
|
||||
* Add CreateWindowCenteredRight for BUIs.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Avoid calling UpdateState before opening a BUI.
|
||||
|
||||
|
||||
## 243.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `BaseWindow` sometimes not properly updating the mouse cursor shape.
|
||||
* Revert `BaseWindow` OnClose ordering due to prior reliance upon the ordering.
|
||||
|
||||
|
||||
## 243.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* RemoveChild is called after OnClose for BaseWindow.
|
||||
|
||||
### New features
|
||||
|
||||
* BUIs now have their positions saved when closed and re-used when opened when using the `CreateWindow<T>` helper or via manually registering it via RegisterControl.
|
||||
|
||||
### Other
|
||||
|
||||
* Ensure grid fixtures get updated in client state handling even if exceptions occur.
|
||||
|
||||
|
||||
## 242.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed prototype reloading/hotloading not properly handling data-fields with the `AlwaysPushInheritanceAttribute`
|
||||
* Fix the pooled polygons using incorrect vertices for EntityLookup and MapManager.
|
||||
|
||||
### Internal
|
||||
|
||||
* Avoid normalizing angles constructed from vectors.
|
||||
|
||||
|
||||
## 242.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The order in which the client initialises networked entities has changed. It will now always apply component states, initialise, and start an entity's parent before processing any children. This might break anything that was relying on the old behaviour where all component states were applied before any entities were initialised & started.
|
||||
* `IClydeViewport` overlay rendering methods now take in an `IRenderHandle` instead of a world/screen handle.
|
||||
* The `OverlayDrawArgs` struct now has an internal constructor.
|
||||
|
||||
### New features
|
||||
|
||||
* Controls can now be manually restyled via `Control.InvalidateStyleSheet()` and `Control.DoStyleUpdate()`
|
||||
* Added `IUserInterfaceManager.RenderControl()` for manually drawing controls.
|
||||
* `OverlayDrawArgs` struct now has an `IRenderHandle` field such that overlays can use the new `RenderControl()` methods.
|
||||
* TileSpawnWindow will now take focus when opened.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a client-side bug where `TransformComponent.GridUid` does not get set properly when an existing entity is attached to a new entity outside of the player's PVS range.
|
||||
* EntityPrototypeView will only create entities when it's on the UI tree and not when the prototype is set.
|
||||
* Make CollisionWake not log errors if it can't resolve.
|
||||
|
||||
### Other
|
||||
|
||||
* Replace IPhysShape API with generics on IMapManager and EntityLookupSystem.
|
||||
|
||||
### Internal
|
||||
|
||||
* Significantly reduce allocations for Box2 / Box2Rotated queries.
|
||||
|
||||
|
||||
## 241.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove DeferredClose from BUIs.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `EntityManager.DirtyFields()`, which allows components with delta states to simultaneously mark several fields as dirty at the same time.
|
||||
* Add `CloserUserUIs<T>` to close keys of a specific key.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `RaisePredictiveEvent()` not properly re-raising events during prediction for event handlers that did not take an `EntitySessionEventArgs` argument.
|
||||
* BUI openings are now deferred to avoid having slight desync between deferred closes and opens occurring in the same tick.
|
||||
|
||||
|
||||
## 240.1.2
|
||||
|
||||
|
||||
## 240.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed one of the `IOverlayManager.RemoveOverlay` overrides not fully removing the overlay.
|
||||
|
||||
|
||||
## 240.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added an `AsNullable` extension method for converting an `Entity<T>` into an `Entity<T?>`
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an exception in `PhysicsSystem.DestroyContacts()` that could result in entities getting stuck with broken physics.
|
||||
|
||||
### Other
|
||||
|
||||
* `GamePrototypeLoadManager` will now send all uploaded prototypes to connecting players in a single `GamePrototypeLoadMessage`, as opposed to one message per upload.
|
||||
|
||||
|
||||
## 240.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `SharedBroadphaseSystem.GetBroadphases()` not returning the map itself, which was causing physics to not work properly off-grid.
|
||||
|
||||
|
||||
## 240.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `ComponentRegistry` no longer implements `ISerializationContext`
|
||||
* Tickrate values are now `ushort`, allowing them to go up to 65535.
|
||||
|
||||
### New features
|
||||
|
||||
* Console completion options now have new flags for preventing suggestions from being escaped or quoted.
|
||||
* Added `ILocalizationManager.HasCulture()`.
|
||||
* Static `EntProtoId<T>` fields are now validated to exist.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a state handling bug in replays, which was causing exceptions to be thrown when applying delta states.
|
||||
|
||||
### Other
|
||||
|
||||
* Reduced amount of `DynamicMethod`s used by serialization system. This should improve performance somewhat.
|
||||
|
||||
### Internal
|
||||
|
||||
* Avoided sorting overlays every render frame.
|
||||
* Various clean up to grid fixture code/adding asserts.
|
||||
|
||||
## 239.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
kind: canvas
|
||||
light_mode: unshaded
|
||||
|
||||
# Simple mix blend
|
||||
- type: shader
|
||||
id: Mix
|
||||
kind: canvas
|
||||
blend_mode: Mix
|
||||
|
||||
- type: shader
|
||||
id: shaded
|
||||
kind: canvas
|
||||
|
||||
@@ -66,6 +66,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
@@ -106,6 +107,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
@@ -147,6 +149,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
@@ -188,6 +191,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
|
||||
96
Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs
Normal file
96
Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.EntityManager;
|
||||
|
||||
[Virtual]
|
||||
public partial class HasComponentBenchmark
|
||||
{
|
||||
private static readonly Consumer Consumer = new();
|
||||
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entityManager = default!;
|
||||
|
||||
private ComponentRegistration _compReg = default!;
|
||||
|
||||
private A _dummyA = new();
|
||||
|
||||
[UsedImplicitly]
|
||||
[Params(1, 10, 100, 1000)]
|
||||
public int N;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(f => f.RegisterClass<A>())
|
||||
.InitializeInstance();
|
||||
|
||||
_entityManager = _simulation.Resolve<IEntityManager>();
|
||||
var map = _simulation.CreateMap().Uid;
|
||||
var coords = new EntityCoordinates(map, default);
|
||||
_compReg = _entityManager.ComponentFactory.GetRegistration(typeof(A));
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
var uid = _entityManager.SpawnEntity(null, coords);
|
||||
_entityManager.AddComponent<A>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentGeneric()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var result = _entityManager.HasComponent<A>(uid);
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentCompReg()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var result = _entityManager.HasComponent(uid, _compReg);
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentType()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var result = _entityManager.HasComponent(uid, typeof(A));
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentGetType()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var type = _dummyA.GetType();
|
||||
var result = _entityManager.HasComponent(uid, type);
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[ComponentProtoName("A")]
|
||||
public sealed partial class A : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -88,10 +88,10 @@ namespace Robust.Client
|
||||
{
|
||||
if (GameInfo != null)
|
||||
{
|
||||
GameInfo.TickRate = (byte) tickrate;
|
||||
GameInfo.TickRate = (ushort) tickrate;
|
||||
}
|
||||
|
||||
_timing.SetTickRateAt((byte) tickrate, info.TickChanged);
|
||||
_timing.SetTickRateAt((ushort) tickrate, info.TickChanged);
|
||||
_logger.Info($"Tickrate changed to: {tickrate} on tick {_timing.CurTick}");
|
||||
}
|
||||
|
||||
@@ -395,6 +395,6 @@ namespace Robust.Client
|
||||
/// </summary>
|
||||
public int ServerMaxPlayers { get; set; }
|
||||
|
||||
public byte TickRate { get; internal set; }
|
||||
public uint TickRate { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +382,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
|
||||
@@ -101,11 +101,33 @@ namespace Robust.Client.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T>(Entity<T> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
// Client only dirties during prediction
|
||||
// Client only dirties during prediction
|
||||
if (_gameTiming.InPrediction)
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
public override void DirtyField<T>(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null)
|
||||
{
|
||||
// TODO Prediction
|
||||
// does the client actually need to dirty the field?
|
||||
// I.e., can't it just dirty the whole component to trigger a reset?
|
||||
|
||||
// Client only dirties during prediction
|
||||
if (_gameTiming.InPrediction)
|
||||
base.DirtyField(uid, comp, fieldName, metadata);
|
||||
}
|
||||
|
||||
public override void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
{
|
||||
// TODO Prediction
|
||||
// does the client actually need to dirty the field?
|
||||
// I.e., can't it just dirty the whole component to trigger a reset?
|
||||
|
||||
// Client only dirties during prediction
|
||||
if (_gameTiming.InPrediction)
|
||||
base.DirtyFields(uid, comp, meta, fields);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T1, T2>(Entity<T1, T2> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
protected override void OnAppearanceGetState(EntityUid uid, AppearanceComponent component, ref ComponentGetState args)
|
||||
{
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
var clone = CloneAppearanceData(component.AppearanceData);
|
||||
args.State = new AppearanceComponentState(clone);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
private Dictionary<EntityUid, Dictionary<Enum, Vector2>> _savedPositions = new();
|
||||
private Dictionary<BoundUserInterface, Control> _registeredControls = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -17,6 +25,59 @@ public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
ProtoManager.PrototypesReloaded -= OnProtoReload;
|
||||
}
|
||||
|
||||
protected override void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
base.OnUserInterfaceShutdown(ent, ref args);
|
||||
_savedPositions.Remove(ent.Owner);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OpenUi(Entity<UserInterfaceComponent?> entity, Enum key, bool predicted = false)
|
||||
{
|
||||
var player = Player.LocalEntity;
|
||||
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
OpenUi(entity, key, player.Value, predicted);
|
||||
}
|
||||
|
||||
protected override void SavePosition(BoundUserInterface bui)
|
||||
{
|
||||
if (!_registeredControls.Remove(bui, out var control))
|
||||
return;
|
||||
|
||||
var keyed = _savedPositions[bui.Owner];
|
||||
keyed[bui.UiKey] = control.Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a control so it will later have its position stored by <see cref="SavePosition"/> when the BUI is closed.
|
||||
/// </summary>
|
||||
public void RegisterControl(BoundUserInterface bui, Control control)
|
||||
{
|
||||
DebugTools.Assert(!_registeredControls.ContainsKey(bui));
|
||||
_registeredControls[bui] = control;
|
||||
_savedPositions.GetOrNew(bui.Owner);
|
||||
}
|
||||
|
||||
public override bool TryGetPosition(Entity<UserInterfaceComponent?> entity, Enum key, out Vector2 position)
|
||||
{
|
||||
position = default;
|
||||
|
||||
if (!_savedPositions.TryGetValue(entity.Owner, out var keyed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!keyed.TryGetValue(key, out position))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
var player = Player.LocalEntity;
|
||||
|
||||
@@ -23,7 +23,6 @@ using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Profiling;
|
||||
@@ -42,13 +41,13 @@ namespace Robust.Client.GameStates
|
||||
private uint _nextInputCmdSeq = 1;
|
||||
private readonly Queue<FullInputCmdMessage> _pendingInputs = new();
|
||||
|
||||
private readonly Queue<(uint sequence, GameTick sourceTick, EntityEventArgs msg, object sessionMsg)>
|
||||
private readonly Queue<(uint sequence, GameTick sourceTick, object msg, object sessionMsg)>
|
||||
_pendingSystemMessages
|
||||
= new();
|
||||
|
||||
// Game state dictionaries that get used every tick.
|
||||
private readonly Dictionary<EntityUid, (NetEntity NetEntity, MetaDataComponent Meta, bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState? nextState)> _toApply = new();
|
||||
private readonly Dictionary<NetEntity, EntityState> _toCreate = new();
|
||||
private readonly Dictionary<EntityUid, StateData> _toApply = new();
|
||||
private StateData[] _toApplySorted = default!;
|
||||
private readonly Dictionary<ushort, (IComponent Component, IComponentState? curState, IComponentState? nextState)> _compStateWork = new();
|
||||
private readonly Dictionary<EntityUid, HashSet<Type>> _pendingReapplyNetStates = new();
|
||||
private readonly HashSet<NetEntity> _stateEnts = new();
|
||||
@@ -56,15 +55,29 @@ namespace Robust.Client.GameStates
|
||||
private readonly List<IComponent> _toRemove = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _outputData = new();
|
||||
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
|
||||
private readonly HashSet<EntityUid> _sorted = new();
|
||||
private readonly List<NetEntity> _created = new();
|
||||
private readonly List<NetEntity> _detached = new();
|
||||
|
||||
private readonly record struct StateData(
|
||||
EntityUid Uid,
|
||||
NetEntity NetEntity,
|
||||
MetaDataComponent Meta,
|
||||
bool Created,
|
||||
bool EnteringPvs,
|
||||
GameTick LastApplied,
|
||||
EntityState? CurState,
|
||||
EntityState? NextState,
|
||||
HashSet<Type>? PendingReapply);
|
||||
|
||||
private readonly ObjectPool<Dictionary<ushort, IComponentState?>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, IComponentState?>>(new DictPolicy<ushort, IComponentState?>(), 256);
|
||||
|
||||
private uint _metaCompNetId;
|
||||
private uint _xformCompNetId;
|
||||
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly IClientNetManager _network = default!;
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
@@ -72,7 +85,7 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly INetConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly ClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly ClientEntityManager _entities = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly ProfManager _prof = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
@@ -126,7 +139,6 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private bool _resettingPredictedEntities;
|
||||
private readonly List<EntityUid> _brokenEnts = new();
|
||||
private readonly List<(EntityUid, NetEntity)> _toStart = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
@@ -172,6 +184,12 @@ namespace Robust.Client.GameStates
|
||||
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
|
||||
|
||||
_metaCompNetId = metaId.Value;
|
||||
|
||||
var xformId = _compFactory.GetRegistration(typeof(TransformComponent)).NetID;
|
||||
if (!xformId.HasValue)
|
||||
throw new InvalidOperationException("TransformComponent does not have a NetId.");
|
||||
|
||||
_xformCompNetId = xformId.Value;
|
||||
}
|
||||
|
||||
private void OnComponentAdded(AddedComponentEventArgs args)
|
||||
@@ -183,11 +201,11 @@ namespace Robust.Client.GameStates
|
||||
if (comp.NetID == null)
|
||||
return;
|
||||
|
||||
if (_entityManager.IsClientSide(args.BaseArgs.Owner))
|
||||
if (_entities.IsClientSide(args.BaseArgs.Owner))
|
||||
return;
|
||||
|
||||
_sawmill.Error($"""
|
||||
Added component {comp.Name} to entity {_entityManager.ToPrettyString(args.BaseArgs.Owner)} while resetting predicted entities.
|
||||
Added component {comp.Name} to entity {_entities.ToPrettyString(args.BaseArgs.Owner)} while resetting predicted entities.
|
||||
Stack trace:
|
||||
{Environment.StackTrace}
|
||||
""");
|
||||
@@ -385,7 +403,7 @@ namespace Robust.Client.GameStates
|
||||
try
|
||||
{
|
||||
#endif
|
||||
createdEntities = ApplyGameState(curState, nextState);
|
||||
ApplyGameState(curState, nextState);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (MissingMetadataException e)
|
||||
@@ -399,7 +417,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
using (_prof.Group("MergeImplicitData"))
|
||||
{
|
||||
MergeImplicitData(createdEntities);
|
||||
MergeImplicitData();
|
||||
}
|
||||
|
||||
if (_lastProcessedInput < curState.LastProcessedInput)
|
||||
@@ -456,7 +474,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
using (_prof.Group("Tick"))
|
||||
{
|
||||
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
|
||||
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled, histogram: null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,9 +522,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= _timing.CurTick)
|
||||
{
|
||||
var msg = pendingMessagesEnumerator.Current.msg;
|
||||
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.msg);
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
|
||||
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
}
|
||||
@@ -545,7 +561,7 @@ namespace Robust.Client.GameStates
|
||||
PredictionNeedsResetting = false;
|
||||
var countReset = 0;
|
||||
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
|
||||
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
|
||||
var metaQuery = _entities.GetEntityQuery<MetaDataComponent>();
|
||||
RemQueue<IComponent> toRemove = new();
|
||||
|
||||
foreach (var entity in system.DirtyEntities)
|
||||
@@ -632,7 +648,7 @@ namespace Robust.Client.GameStates
|
||||
if (!last.TryGetValue(netId, out var state))
|
||||
continue;
|
||||
|
||||
var comp = _entityManager.AddComponent(entity, netId, meta);
|
||||
var comp = _entities.AddComponent(entity, netId, meta);
|
||||
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was removed: {comp.GetType()}");
|
||||
@@ -652,7 +668,7 @@ namespace Robust.Client.GameStates
|
||||
meta.EntityLastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
|
||||
_entityManager.System<PhysicsSystem>().ResetContacts();
|
||||
_entities.System<PhysicsSystem>().ResetContacts();
|
||||
|
||||
// TODO maybe reset more of physics?
|
||||
// E.g., warm impulses for warm starting?
|
||||
@@ -671,21 +687,21 @@ namespace Robust.Client.GameStates
|
||||
/// initial server state for any newly created entity. It does this by simply using the standard <see
|
||||
/// cref="IEntityManager.GetComponentState"/>.
|
||||
/// </remarks>
|
||||
private void MergeImplicitData(IEnumerable<NetEntity> createdEntities)
|
||||
public void MergeImplicitData()
|
||||
{
|
||||
var bus = _entityManager.EventBus;
|
||||
var bus = _entities.EventBus;
|
||||
|
||||
foreach (var netEntity in createdEntities)
|
||||
foreach (var netEntity in _created)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (!_entityManager.TryGetEntityData(netEntity, out _, out var meta))
|
||||
if (!_entities.TryGetEntityData(netEntity, out _, out var meta))
|
||||
{
|
||||
_sawmill.Error($"Encountered deleted entity while merging implicit data! NetEntity: {netEntity}");
|
||||
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw new KeyNotFoundException();
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
var (_, meta) = _entityManager.GetEntityData(netEntity);
|
||||
#endif
|
||||
|
||||
var compData = _compDataPool.Get();
|
||||
_outputData.Add(netEntity, compData);
|
||||
@@ -694,12 +710,13 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
var state = _entities.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState);
|
||||
compData.Add(netId, state);
|
||||
}
|
||||
}
|
||||
|
||||
_created.Clear();
|
||||
_processor.MergeImplicitData(_outputData);
|
||||
|
||||
foreach (var data in _outputData.Values)
|
||||
@@ -735,10 +752,9 @@ namespace Robust.Client.GameStates
|
||||
_config.TickProcessMessages();
|
||||
}
|
||||
|
||||
(IEnumerable<NetEntity> Created, List<NetEntity> Detached) output;
|
||||
using (_prof.Group("Entity"))
|
||||
{
|
||||
output = ApplyEntityStates(curState, nextState);
|
||||
ApplyEntityStates(curState, nextState);
|
||||
}
|
||||
|
||||
using (_prof.Group("Player"))
|
||||
@@ -748,13 +764,13 @@ namespace Robust.Client.GameStates
|
||||
|
||||
using (_prof.Group("Callback"))
|
||||
{
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState, output.Detached));
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState, _detached));
|
||||
}
|
||||
|
||||
return output.Created;
|
||||
return _created;
|
||||
}
|
||||
|
||||
private (IEnumerable<NetEntity> Created, List<NetEntity> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
|
||||
private void ApplyEntityStates(GameState curState, GameState? nextState)
|
||||
{
|
||||
var metas = _entities.GetEntityQuery<MetaDataComponent>();
|
||||
var xforms = _entities.GetEntityQuery<TransformComponent>();
|
||||
@@ -762,90 +778,74 @@ namespace Robust.Client.GameStates
|
||||
|
||||
var enteringPvs = 0;
|
||||
_toApply.Clear();
|
||||
_toCreate.Clear();
|
||||
_created.Clear();
|
||||
_pendingReapplyNetStates.Clear();
|
||||
var curSpan = curState.EntityStates.Span;
|
||||
|
||||
// Create new entities
|
||||
// This is done BEFORE state application to ensure any new parents exist before existing children have their states applied, otherwise, we may have issues with entity transforms!
|
||||
{
|
||||
using var _ = _prof.Group("Create uninitialized entities");
|
||||
var count = 0;
|
||||
|
||||
using (_prof.Group("Create uninitialized entities"))
|
||||
{
|
||||
var created = 0;
|
||||
foreach (var es in curSpan)
|
||||
{
|
||||
if (_entityManager.TryGetEntity(es.NetEntity, out var nUid))
|
||||
if (_entities.TryGetEntity(es.NetEntity, out var nUid))
|
||||
{
|
||||
DebugTools.Assert(_entityManager.EntityExists(nUid));
|
||||
DebugTools.Assert(_entities.EntityExists(nUid));
|
||||
continue;
|
||||
}
|
||||
|
||||
count++;
|
||||
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
|
||||
if (metaState == null)
|
||||
throw new MissingMetadataException(es.NetEntity);
|
||||
|
||||
var uid = _entities.CreateEntity(metaState.PrototypeId, out var newMeta);
|
||||
_toCreate.Add(es.NetEntity, es);
|
||||
_toApply.Add(uid, (es.NetEntity, newMeta, false, GameTick.Zero, es, null));
|
||||
|
||||
// Client creates a client-side net entity for the newly created entity.
|
||||
// We need to clear this mapping before assigning the real net id.
|
||||
// TODO NetEntity Jank: prevent the client from creating this in the first place.
|
||||
_entityManager.ClearNetEntity(newMeta.NetEntity);
|
||||
|
||||
_entityManager.SetNetEntity(uid, es.NetEntity, newMeta);
|
||||
newMeta.LastStateApplied = curState.ToSequence;
|
||||
|
||||
// Check if there's any component states awaiting this entity.
|
||||
if (_entityManager.PendingNetEntityStates.Remove(es.NetEntity, out var value))
|
||||
{
|
||||
foreach (var (type, owner) in value)
|
||||
{
|
||||
var pending = _pendingReapplyNetStates.GetOrNew(owner);
|
||||
pending.Add(type);
|
||||
}
|
||||
}
|
||||
created++;
|
||||
CreateNewEntity(es, curState.ToSequence);
|
||||
}
|
||||
|
||||
_prof.WriteValue("Count", ProfData.Int32(count));
|
||||
_prof.WriteValue("Count", ProfData.Int32(created));
|
||||
}
|
||||
|
||||
// Add entity entities that aren't new to _toCreate.
|
||||
// In the process, we also check if these entities are re-entering PVS range.
|
||||
foreach (var es in curSpan)
|
||||
{
|
||||
if (_toCreate.ContainsKey(es.NetEntity))
|
||||
if (!_entities.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
continue;
|
||||
|
||||
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
continue;
|
||||
|
||||
bool isEnteringPvs = (meta.Flags & MetaDataFlags.Detached) != 0;
|
||||
var isEnteringPvs = (meta.Flags & MetaDataFlags.Detached) != 0;
|
||||
if (isEnteringPvs)
|
||||
{
|
||||
// _toApply already contains newly created entities, but these should never be "entering PVS"
|
||||
DebugTools.Assert(!_toApply.ContainsKey(uid.Value));
|
||||
|
||||
meta.Flags &= ~MetaDataFlags.Detached;
|
||||
enteringPvs++;
|
||||
}
|
||||
else if (meta.LastStateApplied >= es.EntityLastModified && meta.LastStateApplied != GameTick.Zero)
|
||||
{
|
||||
// _toApply already contains newly created entities, but for those this set should have no effect
|
||||
DebugTools.Assert(!_toApply.ContainsKey(uid.Value) || meta.LastStateApplied == curState.ToSequence);
|
||||
|
||||
meta.LastStateApplied = curState.ToSequence;
|
||||
continue;
|
||||
}
|
||||
|
||||
_toApply.Add(uid.Value, (es.NetEntity, meta, isEnteringPvs, meta.LastStateApplied, es, null));
|
||||
// Any newly created entities already added to _toApply should've already been caught by the previous continue
|
||||
DebugTools.Assert(!_toApply.ContainsKey(uid.Value));
|
||||
|
||||
_toApply.Add(uid.Value, new(uid.Value, es.NetEntity, meta, false, isEnteringPvs, meta.LastStateApplied, es, null, null));
|
||||
meta.LastStateApplied = curState.ToSequence;
|
||||
}
|
||||
|
||||
// Detach entities to null space
|
||||
var containerSys = _entitySystemManager.GetEntitySystem<ContainerSystem>();
|
||||
var lookupSys = _entitySystemManager.GetEntitySystem<EntityLookupSystem>();
|
||||
var detached = ProcessPvsDeparture(curState.ToSequence, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
ProcessPvsDeparture(curState.ToSequence, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
|
||||
// Check next state (AFTER having created new entities introduced in curstate)
|
||||
if (nextState != null)
|
||||
{
|
||||
foreach (var es in nextState.EntityStates.Span)
|
||||
{
|
||||
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
if (!_entities.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
continue;
|
||||
|
||||
// Does the next state actually have any future information about this entity that could be used for interpolation?
|
||||
@@ -854,15 +854,14 @@ namespace Robust.Client.GameStates
|
||||
|
||||
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid.Value, out var exists);
|
||||
|
||||
if (exists)
|
||||
state = (es.NetEntity, meta, state.EnteringPvs, state.LastApplied, state.curState, es);
|
||||
else
|
||||
state = (es.NetEntity, meta, false, GameTick.Zero, null, es);
|
||||
state = exists
|
||||
? state with {NextState = es}
|
||||
: new(uid.Value, es.NetEntity, meta, false, false, GameTick.Zero, null, es, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Check pending states and see if we need to force any entities to re-run component states.
|
||||
foreach (var uid in _pendingReapplyNetStates.Keys)
|
||||
foreach (var (uid, pending) in _pendingReapplyNetStates)
|
||||
{
|
||||
// Original entity referencing the NetEntity may have been deleted.
|
||||
if (!metas.TryGetComponent(uid, out var meta))
|
||||
@@ -879,51 +878,30 @@ namespace Robust.Client.GameStates
|
||||
|
||||
DebugTools.Assert(!curState.EntityDeletions.Value.Contains(meta.NetEntity));
|
||||
|
||||
// State already being re-applied so don't bulldoze it.
|
||||
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid, out var exists);
|
||||
|
||||
if (exists)
|
||||
continue;
|
||||
|
||||
state = (meta.NetEntity, meta, false, GameTick.Zero, null, null);
|
||||
state = exists
|
||||
? state with {PendingReapply = pending}
|
||||
: new(uid, meta.NetEntity, meta, false, false, GameTick.Zero, null, null, pending);
|
||||
}
|
||||
|
||||
_queuedBroadphaseUpdates.Clear();
|
||||
|
||||
using (_prof.Group("Sort States"))
|
||||
{
|
||||
SortStates(_toApply);
|
||||
}
|
||||
|
||||
// Apply entity states.
|
||||
using (_prof.Group("Apply States"))
|
||||
{
|
||||
foreach (var (entity, data) in _toApply)
|
||||
var span = _toApplySorted.AsSpan(0, _toApply.Count);
|
||||
foreach (ref var data in span)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
|
||||
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(entity)}. Exception: {e}");
|
||||
_entityManager.DeleteEntity(entity);
|
||||
RequestFullState();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (!data.EnteringPvs)
|
||||
continue;
|
||||
|
||||
// Now that things like collision data, fixtures, and positions have been updated, we queue a
|
||||
// broadphase update. However, if this entity is parented to some other entity also re-entering PVS,
|
||||
// we only need to update it's parent (as it recursively updates children anyways).
|
||||
var xform = xforms.GetComponent(entity);
|
||||
DebugTools.Assert(xform.Broadphase == BroadphaseData.Invalid);
|
||||
xform.Broadphase = null;
|
||||
if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs)
|
||||
_queuedBroadphaseUpdates.Add((entity, xform));
|
||||
ApplyEntState(data, curState.ToSequence);
|
||||
}
|
||||
|
||||
Array.Clear(_toApplySorted, 0, _toApply.Count);
|
||||
_prof.WriteValue("Count", ProfData.Int32(_toApply.Count));
|
||||
}
|
||||
|
||||
@@ -958,14 +936,166 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize and start the newly created entities.
|
||||
if (_toCreate.Count > 0)
|
||||
InitializeAndStart(_toCreate, metas, xforms);
|
||||
// Delete any entities that failed to properly initialize/start
|
||||
foreach (var entity in _brokenEnts)
|
||||
{
|
||||
_entities.DeleteEntity(entity);
|
||||
}
|
||||
|
||||
_brokenEnts.Clear();
|
||||
|
||||
_prof.WriteValue("State Size", ProfData.Int32(curSpan.Length));
|
||||
_prof.WriteValue("Entered PVS", ProfData.Int32(enteringPvs));
|
||||
}
|
||||
|
||||
return (_toCreate.Keys, detached);
|
||||
private void ApplyEntState(in StateData data, GameTick toTick)
|
||||
{
|
||||
try
|
||||
{
|
||||
HandleEntityState(data, _entities.EventBus, toTick);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(data.Uid)}. Exception: {e}");
|
||||
_brokenEnts.Add(data.Uid);
|
||||
RequestFullState();
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.Created)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entities.InitializeEntity(data.Uid, data.Meta);
|
||||
_entities.StartEntity(data.Uid);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error(
|
||||
$"Caught exception while initializing or starting entity: {_entities.ToPrettyString(data.Uid)}. Exception: {e}");
|
||||
_brokenEnts.Add(data.Uid);
|
||||
RequestFullState();
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.EnteringPvs)
|
||||
return;
|
||||
|
||||
// Now that things like collision data, fixtures, and positions have been updated, we queue a
|
||||
// broadphase update. However, if this entity is parented to some other entity also re-entering PVS,
|
||||
// we only need to update it's parent (as it recursively updates children anyways).
|
||||
var xform = _entities.TransformQuery.Comp(data.Uid);
|
||||
DebugTools.Assert(xform.Broadphase == BroadphaseData.Invalid);
|
||||
xform.Broadphase = null;
|
||||
if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs)
|
||||
_queuedBroadphaseUpdates.Add((data.Uid, xform));
|
||||
}
|
||||
|
||||
private void CreateNewEntity(EntityState state, GameTick toTick)
|
||||
{
|
||||
// TODO GAME STATE
|
||||
// store MetaData & Transform information separately.
|
||||
var metaState =
|
||||
(MetaDataComponentState?) state.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId)
|
||||
.State;
|
||||
|
||||
if (metaState == null)
|
||||
throw new MissingMetadataException(state.NetEntity);
|
||||
|
||||
var uid = _entities.CreateEntity(metaState.PrototypeId, out var newMeta);
|
||||
_toApply.Add(uid, new(uid, state.NetEntity, newMeta, true, false, GameTick.Zero, state, null, null));
|
||||
_created.Add(state.NetEntity);
|
||||
|
||||
// Client creates a client-side net entity for the newly created entity.
|
||||
// We need to clear this mapping before assigning the real net id.
|
||||
// TODO NetEntity Jank: prevent the client from creating this in the first place.
|
||||
_entities.ClearNetEntity(newMeta.NetEntity);
|
||||
|
||||
_entities.SetNetEntity(uid, state.NetEntity, newMeta);
|
||||
newMeta.LastStateApplied = toTick;
|
||||
|
||||
// Check if there's any component states awaiting this entity.
|
||||
if (!_entities.PendingNetEntityStates.Remove(state.NetEntity, out var value))
|
||||
return;
|
||||
|
||||
foreach (var (type, owner) in value)
|
||||
{
|
||||
var pending = _pendingReapplyNetStates.GetOrNew(owner);
|
||||
pending.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sort states to ensure that we always apply states, initialize, and start parent entities before any of their
|
||||
/// children.
|
||||
/// </summary>
|
||||
private void SortStates(Dictionary<EntityUid, StateData> toApply)
|
||||
{
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (_toApplySorted == null || _toApplySorted.Length < toApply.Count)
|
||||
Array.Resize(ref _toApplySorted, toApply.Count);
|
||||
|
||||
_sorted.Clear();
|
||||
|
||||
var i = 0;
|
||||
foreach (var (ent, data) in toApply)
|
||||
{
|
||||
AddToSorted(ent, data, ref i);
|
||||
}
|
||||
|
||||
DebugTools.AssertEqual(i, toApply.Count);
|
||||
}
|
||||
|
||||
private void AddToSorted(EntityUid ent, in StateData data, ref int i)
|
||||
{
|
||||
if (!_sorted.Add(ent))
|
||||
return;
|
||||
|
||||
EnsureParentsSorted(ent, data, ref i);
|
||||
_toApplySorted[i++] = data;
|
||||
}
|
||||
|
||||
private void EnsureParentsSorted(EntityUid ent, in StateData data, ref int i)
|
||||
{
|
||||
var parent = GetStateParent(ent, data);
|
||||
|
||||
while (parent != EntityUid.Invalid)
|
||||
{
|
||||
if (_toApply.TryGetValue(parent, out var parentData))
|
||||
{
|
||||
AddToSorted(parent, parentData, ref i);
|
||||
// The above method will handle the rest of the transform hierarchy, so we can just return early.
|
||||
return;
|
||||
}
|
||||
|
||||
parent = _entities.TransformQuery.GetComponent(parent).ParentUid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the entity's parent in the game state that is being applies. I.e., if the state contains a new
|
||||
/// transform state, get the parent from that. Otherwise, return the entity's current parent.
|
||||
/// </summary>
|
||||
private EntityUid GetStateParent(EntityUid uid, in StateData data)
|
||||
{
|
||||
// TODO GAME STATE
|
||||
// store MetaData & Transform information separately.
|
||||
if (data.CurState != null
|
||||
&& data.CurState.ComponentChanges.Value
|
||||
.TryFirstOrNull(c => c.NetID == _xformCompNetId, out var found))
|
||||
{
|
||||
var state = (TransformComponentState) found.Value.State!;
|
||||
return _entities.GetEntity(state.ParentID);
|
||||
}
|
||||
|
||||
return _entities.TransformQuery.GetComponent(uid).ParentUid;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1000,7 +1130,7 @@ namespace Robust.Client.GameStates
|
||||
_toDelete.Clear();
|
||||
|
||||
// Client side entities won't need the transform, but that should always be a tiny minority of entities
|
||||
var metaQuery = _entityManager.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
|
||||
var metaQuery = _entities.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
|
||||
|
||||
while (metaQuery.MoveNext(out var ent, out var metadata, out var xform))
|
||||
{
|
||||
@@ -1067,9 +1197,9 @@ namespace Robust.Client.GameStates
|
||||
foreach (var netEntity in delSpan)
|
||||
{
|
||||
// Don't worry about this for later.
|
||||
_entityManager.PendingNetEntityStates.Remove(netEntity);
|
||||
_entities.PendingNetEntityStates.Remove(netEntity);
|
||||
|
||||
if (!_entityManager.TryGetEntity(netEntity, out var id))
|
||||
if (!_entities.TryGetEntity(netEntity, out var id))
|
||||
continue;
|
||||
|
||||
if (!xforms.TryGetComponent(id, out var xform))
|
||||
@@ -1099,9 +1229,10 @@ namespace Robust.Client.GameStates
|
||||
var containerSys = _entitySystemManager.GetEntitySystem<ContainerSystem>();
|
||||
var lookupSys = _entitySystemManager.GetEntitySystem<EntityLookupSystem>();
|
||||
Detach(GameTick.MaxValue, null, entities, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
_detached.Clear();
|
||||
}
|
||||
|
||||
private List<NetEntity> ProcessPvsDeparture(
|
||||
private void ProcessPvsDeparture(
|
||||
GameTick toTick,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
@@ -1110,25 +1241,23 @@ namespace Robust.Client.GameStates
|
||||
EntityLookupSystem lookupSys)
|
||||
{
|
||||
var toDetach = _processor.GetEntitiesToDetach(toTick, _pvsDetachBudget);
|
||||
var detached = new List<NetEntity>();
|
||||
|
||||
if (toDetach.Count == 0)
|
||||
return detached;
|
||||
return;
|
||||
|
||||
// TODO optimize
|
||||
// If an entity is leaving PVS, so are all of its children. If we can preserve the hierarchy we can avoid
|
||||
// things like container insertion and ejection.
|
||||
|
||||
using var _ = _prof.Group("Leave PVS");
|
||||
detached.EnsureCapacity(toDetach.Count);
|
||||
|
||||
_detached.Clear();
|
||||
foreach (var (tick, ents) in toDetach)
|
||||
{
|
||||
Detach(tick, toTick, ents, metas, xforms, xformSys, containerSys, lookupSys, detached);
|
||||
Detach(tick, toTick, ents, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
}
|
||||
|
||||
_prof.WriteValue("Count", ProfData.Int32(detached.Count));
|
||||
return detached;
|
||||
_prof.WriteValue("Count", ProfData.Int32(_detached.Count));
|
||||
}
|
||||
|
||||
private void Detach(GameTick maxTick,
|
||||
@@ -1138,12 +1267,11 @@ namespace Robust.Client.GameStates
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
SharedTransformSystem xformSys,
|
||||
ContainerSystem containerSys,
|
||||
EntityLookupSystem lookupSys,
|
||||
List<NetEntity>? detached = null)
|
||||
EntityLookupSystem lookupSys)
|
||||
{
|
||||
foreach (var netEntity in entities)
|
||||
{
|
||||
if (!_entityManager.TryGetEntityData(netEntity, out var ent, out var meta))
|
||||
if (!_entities.TryGetEntityData(netEntity, out var ent, out var meta))
|
||||
continue;
|
||||
|
||||
if (meta.LastStateApplied > maxTick)
|
||||
@@ -1184,159 +1312,75 @@ namespace Robust.Client.GameStates
|
||||
containerSys.AddExpectedEntity(netEntity, container);
|
||||
}
|
||||
|
||||
detached?.Add(netEntity);
|
||||
_detached.Add(netEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeAndStart(
|
||||
Dictionary<NetEntity, EntityState> toCreate,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
_toStart.Clear();
|
||||
|
||||
using (_prof.Group("Initialize Entity"))
|
||||
{
|
||||
EntityUid entity = default;
|
||||
foreach (var netEntity in toCreate.Keys)
|
||||
{
|
||||
(entity, var meta) = _entityManager.GetEntityData(netEntity);
|
||||
InitializeRecursive(entity, meta, metas, xforms);
|
||||
}
|
||||
}
|
||||
|
||||
using (_prof.Group("Start Entity"))
|
||||
{
|
||||
foreach (var (entity, netEntity) in _toStart)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entities.StartEntity(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Start: nent={netEntity}, ent={_entityManager.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
_toCreate.Remove(netEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var entity in _brokenEnts)
|
||||
{
|
||||
_entityManager.DeleteEntity(entity);
|
||||
}
|
||||
_brokenEnts.Clear();
|
||||
}
|
||||
|
||||
private void InitializeRecursive(
|
||||
EntityUid entity,
|
||||
MetaDataComponent meta,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
var xform = xforms.GetComponent(entity);
|
||||
if (xform.ParentUid is {Valid: true} parent)
|
||||
{
|
||||
var parentMeta = metas.GetComponent(parent);
|
||||
if (parentMeta.EntityLifeStage < EntityLifeStage.Initialized)
|
||||
InitializeRecursive(parent, parentMeta, metas, xforms);
|
||||
}
|
||||
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Initialized)
|
||||
{
|
||||
// Was probably already initialized because one of its children appeared earlier in the list.
|
||||
DebugTools.AssertEqual(_toStart.Count(x => x.Item1 == entity), 1);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_entities.InitializeEntity(entity, meta);
|
||||
_toStart.Add((entity, meta.NetEntity));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Init: nent={meta.NetEntity}, ent={_entities.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
_toCreate.Remove(meta.NetEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
|
||||
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
|
||||
private void HandleEntityState(in StateData data, IEventBus bus, GameTick toTick)
|
||||
{
|
||||
_compStateWork.Clear();
|
||||
|
||||
// First remove any deleted components
|
||||
if (curState?.NetComponents != null)
|
||||
if (data.CurState?.NetComponents is {} netComps)
|
||||
{
|
||||
_toRemove.Clear();
|
||||
|
||||
foreach (var (id, comp) in meta.NetComponents)
|
||||
foreach (var (id, comp) in data.Meta.NetComponents)
|
||||
{
|
||||
DebugTools.Assert(comp.NetSyncEnabled);
|
||||
|
||||
if (!curState.NetComponents.Contains(id))
|
||||
if (!netComps.Contains(id))
|
||||
_toRemove.Add(comp);
|
||||
}
|
||||
|
||||
foreach (var comp in _toRemove)
|
||||
{
|
||||
_entities.RemoveComponent(uid, comp, meta);
|
||||
_entities.RemoveComponent(data.Uid, comp, data.Meta);
|
||||
}
|
||||
}
|
||||
|
||||
if (enteringPvs)
|
||||
if (data.EnteringPvs)
|
||||
{
|
||||
// last-server state has already been updated with new information from curState
|
||||
// --> simply reset to the most recent server state.
|
||||
//
|
||||
// as to why we need to reset: because in the process of detaching to null-space, we will have dirtied
|
||||
// the entity. most notably, all entities will have been ejected from their containers.
|
||||
foreach (var (id, state) in _processor.GetLastServerStates(netEntity))
|
||||
foreach (var (id, state) in _processor.GetLastServerStates(data.NetEntity))
|
||||
{
|
||||
if (!meta.NetComponents.TryGetValue(id, out var comp))
|
||||
if (!data.Meta.NetComponents.TryGetValue(id, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(id);
|
||||
_entityManager.AddComponent(uid, comp, true, metadata: meta);
|
||||
_entities.AddComponent(data.Uid, comp, true, metadata: data.Meta);
|
||||
}
|
||||
|
||||
_compStateWork[id] = (comp, state, null);
|
||||
}
|
||||
}
|
||||
else if (curState != null)
|
||||
else if (data.CurState != null)
|
||||
{
|
||||
foreach (var compChange in curState.ComponentChanges.Span)
|
||||
foreach (var compChange in data.CurState.ComponentChanges.Span)
|
||||
{
|
||||
if (!meta.NetComponents.TryGetValue(compChange.NetID, out var comp))
|
||||
if (!data.Meta.NetComponents.TryGetValue(compChange.NetID, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(compChange.NetID);
|
||||
_entityManager.AddComponent(uid, comp, true, metadata:meta);
|
||||
_entities.AddComponent(data.Uid, comp, true, metadata: data.Meta);
|
||||
}
|
||||
else if (compChange.LastModifiedTick <= lastApplied && lastApplied != GameTick.Zero)
|
||||
else if (compChange.LastModifiedTick <= data.LastApplied && data.LastApplied != GameTick.Zero)
|
||||
continue;
|
||||
|
||||
_compStateWork[compChange.NetID] = (comp, compChange.State, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextState != null)
|
||||
if (data.NextState != null)
|
||||
{
|
||||
foreach (var compState in nextState.ComponentChanges.Span)
|
||||
foreach (var compState in data.NextState.ComponentChanges.Span)
|
||||
{
|
||||
if (compState.LastModifiedTick != toTick + 1)
|
||||
continue;
|
||||
|
||||
if (!meta.NetComponents.TryGetValue(compState.NetID, out var comp))
|
||||
if (!data.Meta.NetComponents.TryGetValue(compState.NetID, out var comp))
|
||||
{
|
||||
// The component can be null here due to interp, because the NEXT state will have a new
|
||||
// component, but the component does not yet exist.
|
||||
@@ -1354,9 +1398,10 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
// If we have a NetEntity we reference come in then apply their state.
|
||||
if (_pendingReapplyNetStates.TryGetValue(uid, out var reapplyTypes))
|
||||
DebugTools.Assert(_pendingReapplyNetStates.ContainsKey(data.Uid) == (data.PendingReapply != null));
|
||||
if (data.PendingReapply is {} reapplyTypes)
|
||||
{
|
||||
var lastState = _processor.GetLastServerStates(netEntity);
|
||||
var lastState = _processor.GetLastServerStates(data.NetEntity);
|
||||
|
||||
foreach (var type in reapplyTypes)
|
||||
{
|
||||
@@ -1366,7 +1411,7 @@ namespace Robust.Client.GameStates
|
||||
if (netId == null)
|
||||
continue;
|
||||
|
||||
if (!meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
|
||||
if (!data.Meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
|
||||
!lastState.TryGetValue(netId.Value, out var lastCompState))
|
||||
{
|
||||
continue;
|
||||
@@ -1388,7 +1433,7 @@ namespace Robust.Client.GameStates
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
bus.RaiseComponentEvent(data.Uid, comp, ref handleState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1510,7 +1555,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
using var _ = _timing.StartStateApplicationArea();
|
||||
|
||||
var query = _entityManager.AllEntityQueryEnumerator<MetaDataComponent>();
|
||||
var query = _entities.AllEntityQueryEnumerator<MetaDataComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var meta))
|
||||
{
|
||||
@@ -1536,14 +1581,14 @@ namespace Robust.Client.GameStates
|
||||
if (!meta.NetComponents.TryGetValue(id, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(id);
|
||||
_entityManager.AddComponent(uid, comp, true, meta);
|
||||
_entities.AddComponent(uid, comp, true, meta);
|
||||
}
|
||||
|
||||
if (state == null)
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(state, null);
|
||||
_entityManager.EventBus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
_entities.EventBus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
}
|
||||
|
||||
// ensure we don't have any extra components
|
||||
|
||||
@@ -82,6 +82,8 @@ namespace Robust.Client.GameStates
|
||||
/// </summary>
|
||||
IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState);
|
||||
|
||||
void MergeImplicitData();
|
||||
|
||||
/// <summary>
|
||||
/// Resets any entities that have changed while predicting future ticks.
|
||||
/// </summary>
|
||||
|
||||
5
Robust.Client/GameStates/PvsOverrideSystem.cs
Normal file
5
Robust.Client/GameStates/PvsOverrideSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Client.GameStates;
|
||||
|
||||
public sealed partial class PvsOverrideSystem : SharedPvsOverrideSystem;
|
||||
@@ -125,7 +125,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
|
||||
|
||||
var args = new OverlayDrawArgs(space, null, vp, _renderHandle.DrawingHandleWorld, new UIBox2i((0, 0), vp.Size), vp.Eye!.Position.MapId, worldBox, worldBounds);
|
||||
var mapId = vp.Eye!.Position.MapId;
|
||||
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldBox, worldBounds);
|
||||
|
||||
if (!overlay.BeforeDraw(args))
|
||||
return;
|
||||
@@ -165,7 +166,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void RenderOverlaysDirect(
|
||||
Viewport vp,
|
||||
IViewportControl vpControl,
|
||||
DrawingHandleBase handle,
|
||||
IRenderHandle handle,
|
||||
OverlaySpace space,
|
||||
in UIBox2i bounds)
|
||||
{
|
||||
@@ -175,8 +176,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var worldBounds = CalcWorldBounds(vp);
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
var mapId = vp.Eye!.Position.MapId;
|
||||
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, vp.Eye!.Position.MapId, worldAABB, worldBounds);
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldAABB, worldBounds);
|
||||
|
||||
foreach (var overlay in list)
|
||||
{
|
||||
@@ -215,8 +217,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
_overlays.Sort(OverlayComparer.Instance);
|
||||
|
||||
return _overlays;
|
||||
}
|
||||
|
||||
@@ -423,12 +423,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var oldTransform = _currentMatrixModel;
|
||||
var oldScissor = _currentScissorState;
|
||||
var oldMatrixProj = _currentMatrixProj;
|
||||
var oldMatrixView = _currentMatrixView;
|
||||
var oldBoundTarget = _currentBoundRenderTarget;
|
||||
var oldRenderTarget = _currentRenderTarget;
|
||||
var oldShader = _queuedShaderInstance;
|
||||
var oldCaps = _glCaps;
|
||||
|
||||
// Need to get state before flushing render queue in case they modify the original state.
|
||||
var state = PushRenderStateFull();
|
||||
|
||||
// Have to flush the render queue so that all commands finish rendering to the previous framebuffer.
|
||||
FlushRenderQueue();
|
||||
|
||||
var state = PushRenderStateFull();
|
||||
|
||||
{
|
||||
BindRenderTargetFull(RtToLoaded(rt));
|
||||
if (clearColor is not null)
|
||||
@@ -450,8 +457,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
PopRenderStateFull(state);
|
||||
_updateUniformConstants(_currentRenderTarget.Size);
|
||||
|
||||
SetScissorFull(oldScissor);
|
||||
_currentMatrixModel = oldTransform;
|
||||
|
||||
DebugTools.Assert(oldCaps.Equals(_glCaps));
|
||||
DebugTools.Assert(_currentMatrixModel.Equals(oldTransform));
|
||||
DebugTools.Assert(_currentScissorState.Equals(oldScissor));
|
||||
DebugTools.Assert(_currentMatrixProj.Equals(oldMatrixProj));
|
||||
DebugTools.Assert(oldMatrixView.Equals(_currentMatrixView));
|
||||
DebugTools.Assert(oldRenderTarget.Equals(_currentRenderTarget));
|
||||
DebugTools.Assert(oldBoundTarget.Equals(_currentBoundRenderTarget));
|
||||
DebugTools.Assert(oldShader.Equals(_queuedShaderInstance));
|
||||
}
|
||||
|
||||
private void RenderViewport(Viewport viewport)
|
||||
@@ -574,17 +589,5 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
return new Box2Rotated(aabb, rotation, aabb.Center);
|
||||
}
|
||||
|
||||
private sealed class OverlayComparer : IComparer<Overlay>
|
||||
{
|
||||
public static readonly OverlayComparer Instance = new();
|
||||
|
||||
public int Compare(Overlay? x, Overlay? y)
|
||||
{
|
||||
var zX = x?.ZIndex ?? 0;
|
||||
var zY = y?.ZIndex ?? 0;
|
||||
return zX.CompareTo(zY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
|
||||
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Client.ComponentTrees;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics;
|
||||
using static Robust.Shared.GameObjects.OccluderComponent;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -279,8 +277,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
const float arbitraryDistanceMax = 1234;
|
||||
|
||||
GL.Disable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
IsBlending = false;
|
||||
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
CheckGlError();
|
||||
@@ -329,8 +326,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
CheckGlError();
|
||||
|
||||
GL.Enable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
IsBlending = true;
|
||||
}
|
||||
|
||||
private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
|
||||
@@ -394,21 +390,43 @@ namespace Robust.Client.Graphics.Clyde
|
||||
FinalizeDepthDraw();
|
||||
}
|
||||
|
||||
GL.Enable(EnableCap.StencilTest);
|
||||
_isStencilling = true;
|
||||
IsStencilling = true;
|
||||
|
||||
var (lightW, lightH) = GetLightMapSize(viewport.Size);
|
||||
GL.Viewport(0, 0, lightW, lightH);
|
||||
CheckGlError();
|
||||
|
||||
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
|
||||
DebugTools.Assert(_currentBoundRenderTarget.TextureHandle.Equals(viewport.LightRenderTarget.Texture.TextureId));
|
||||
CheckGlError();
|
||||
GLClearColor(_entityManager.GetComponentOrNull<MapLightComponent>(mapUid)?.AmbientLightColor ?? MapLightComponent.DefaultColor);
|
||||
|
||||
var clearEv = new GetClearColorEvent();
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, ref clearEv);
|
||||
|
||||
var clearColor = clearEv.Color ?? GetClearColor(mapUid);
|
||||
GLClearColor(clearColor);
|
||||
GL.ClearStencil(0xFF);
|
||||
GL.StencilMask(0xFF);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit);
|
||||
CheckGlError();
|
||||
|
||||
var oldTarget = _currentRenderTarget;
|
||||
var oldProj = _currentMatrixProj;
|
||||
var oldShader = _queuedShaderInstance;
|
||||
var oldModel = _currentMatrixModel;
|
||||
var oldScissor = _currentScissorState;
|
||||
var state = PushRenderStateFull();
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.BeforeLighting, worldAABB, worldBounds);
|
||||
PopRenderStateFull(state);
|
||||
|
||||
DebugTools.Assert(oldScissor.Equals(_currentScissorState));
|
||||
DebugTools.Assert(oldModel.Equals(_currentMatrixModel));
|
||||
DebugTools.Assert(oldShader.Equals(_queuedShaderInstance));
|
||||
DebugTools.Assert(oldProj.Equals(_currentMatrixProj));
|
||||
DebugTools.Assert(oldTarget.Equals(_currentRenderTarget));
|
||||
DebugTools.Assert(_currentBoundRenderTarget.TextureHandle.Equals(viewport.LightRenderTarget.Texture.TextureId));
|
||||
|
||||
ApplyLightingFovToBuffer(viewport, eye);
|
||||
|
||||
var lightShader = _loadedShaders[_enableSoftShadows ? _lightSoftShaderHandle : _lightHardShaderHandle]
|
||||
@@ -509,13 +527,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
ResetBlendFunc();
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
_isStencilling = false;
|
||||
IsStencilling = false;
|
||||
|
||||
CheckGlError();
|
||||
|
||||
if (_cfg.GetCVar(CVars.LightBlur))
|
||||
BlurLights(viewport, eye);
|
||||
BlurRenderTarget(viewport, viewport.LightRenderTarget, viewport.LightBlurTarget, eye, 14f);
|
||||
|
||||
using (_prof.Group("BlurOntoWalls"))
|
||||
{
|
||||
@@ -531,9 +548,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.Viewport(0, 0, viewport.Size.X, viewport.Size.Y);
|
||||
CheckGlError();
|
||||
|
||||
Array.Clear(_lightsToRenderList, 0, count);
|
||||
|
||||
_lightingReady = true;
|
||||
Array.Clear(_lightsToRenderList, 0, count);
|
||||
}
|
||||
|
||||
private static bool LightQuery(ref (
|
||||
@@ -643,21 +659,33 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return (state.count, expandedBounds);
|
||||
}
|
||||
|
||||
private void BlurLights(Viewport viewport, IEye eye)
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
public Color GetClearColor(EntityUid mapUid)
|
||||
{
|
||||
using var _ = DebugGroup(nameof(BlurLights));
|
||||
return _entityManager.GetComponentOrNull<MapLightComponent>(mapUid)?.AmbientLightColor ??
|
||||
MapLightComponent.DefaultColor;
|
||||
}
|
||||
|
||||
GL.Disable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
/// <inheritdoc/>
|
||||
public void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier)
|
||||
{
|
||||
if (target is not RenderTexture rTexture || blurBuffer is not RenderTexture blurTexture)
|
||||
return;
|
||||
|
||||
using var _ = DebugGroup(nameof(BlurRenderTarget));
|
||||
|
||||
var state = PushRenderStateFull();
|
||||
IsBlending = false;
|
||||
CalcScreenMatrices(viewport.Size, out var proj, out var view);
|
||||
SetProjViewBuffer(proj, view);
|
||||
|
||||
var shader = _loadedShaders[_lightBlurShaderHandle].Program;
|
||||
shader.Use();
|
||||
|
||||
SetupGlobalUniformsImmediate(shader, viewport.LightRenderTarget.Texture);
|
||||
SetupGlobalUniformsImmediate(shader, rTexture.Texture);
|
||||
|
||||
var size = viewport.LightRenderTarget.Size;
|
||||
var size = target.Size;
|
||||
shader.SetUniformMaybe("size", (Vector2)size);
|
||||
shader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
||||
|
||||
@@ -667,14 +695,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Initially we're pulling from the light render target.
|
||||
// So we set it out of the loop so
|
||||
// _wallBleedIntermediateRenderTarget2 gets bound at the end of the loop body.
|
||||
SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
|
||||
SetTexture(TextureUnit.Texture0, rTexture.Texture);
|
||||
|
||||
// Have to scale the blurring radius based on viewport size and camera zoom.
|
||||
const float refCameraHeight = 14;
|
||||
var facBase = _cfg.GetCVar(CVars.LightBlurFactor);
|
||||
var cameraSize = eye.Zoom.Y * viewport.Size.Y * (1 / viewport.RenderScale.Y) / EyeManager.PixelsPerMeter;
|
||||
// 7e-3f is just a magic factor that makes it look ok.
|
||||
var factor = facBase * (refCameraHeight / cameraSize);
|
||||
var factor = facBase * (multiplier / cameraSize);
|
||||
|
||||
// Multi-iteration gaussian blur.
|
||||
for (var i = 3; i > 0; i--)
|
||||
@@ -683,35 +710,31 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Set factor.
|
||||
shader.SetUniformMaybe("radius", scale);
|
||||
|
||||
BindRenderTargetFull(viewport.LightBlurTarget);
|
||||
BindRenderTargetImmediate(RtToLoaded(blurBuffer));
|
||||
|
||||
// Blur horizontally to _wallBleedIntermediateRenderTarget1.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitX);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, viewport.LightBlurTarget.Texture);
|
||||
SetTexture(TextureUnit.Texture0, blurTexture.Texture);
|
||||
|
||||
BindRenderTargetFull(viewport.LightRenderTarget);
|
||||
BindRenderTargetImmediate(RtToLoaded(rTexture));
|
||||
|
||||
// Blur vertically to _wallBleedIntermediateRenderTarget2.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitY);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
|
||||
SetTexture(TextureUnit.Texture0, rTexture.Texture);
|
||||
}
|
||||
|
||||
GL.Enable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
// We didn't trample over the old _currentMatrices so just roll it back.
|
||||
SetProjViewBuffer(_currentMatrixProj, _currentMatrixView);
|
||||
PopRenderStateFull(state);
|
||||
}
|
||||
|
||||
private void BlurOntoWalls(Viewport viewport, IEye eye)
|
||||
{
|
||||
using var _ = DebugGroup(nameof(BlurOntoWalls));
|
||||
|
||||
GL.Disable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
IsBlending = false;
|
||||
CalcScreenMatrices(viewport.Size, out var proj, out var view);
|
||||
SetProjViewBuffer(proj, view);
|
||||
|
||||
@@ -761,8 +784,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
SetTexture(TextureUnit.Texture0, viewport.WallBleedIntermediateRenderTarget2.Texture);
|
||||
}
|
||||
|
||||
GL.Enable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
IsBlending = true;
|
||||
// We didn't trample over the old _currentMatrices so just roll it back.
|
||||
SetProjViewBuffer(_currentMatrixProj, _currentMatrixView);
|
||||
}
|
||||
@@ -775,8 +797,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
GL.Viewport(0, 0, viewport.LightRenderTarget.Size.X, viewport.LightRenderTarget.Size.Y);
|
||||
CheckGlError();
|
||||
GL.Disable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
IsBlending = false;
|
||||
|
||||
var shader = _loadedShaders[_mergeWallLayerShaderHandle].Program;
|
||||
shader.Use();
|
||||
@@ -796,8 +817,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
IntPtr.Zero);
|
||||
CheckGlError();
|
||||
|
||||
GL.Enable(EnableCap.Blend);
|
||||
CheckGlError();
|
||||
IsBlending = true;
|
||||
}
|
||||
|
||||
private void ApplyFovToBuffer(Viewport viewport, IEye eye)
|
||||
@@ -827,8 +847,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
FovSetTransformAndBlit(viewport, eye.Position.Position, fovShader);
|
||||
|
||||
GL.StencilMask(0x00);
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
_isStencilling = false;
|
||||
IsStencilling = false;
|
||||
}
|
||||
|
||||
private void ApplyLightingFovToBuffer(Viewport viewport, IEye eye)
|
||||
@@ -1135,22 +1154,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var lightMapSize = GetLightMapSize(viewport.Size);
|
||||
var lightMapSizeQuart = GetLightMapSize(viewport.Size, true);
|
||||
var lightMapColorFormat = _hasGLFloatFramebuffers
|
||||
? RenderTargetColorFormat.R11FG11FB10F
|
||||
: RenderTargetColorFormat.Rgba8;
|
||||
var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
|
||||
|
||||
viewport.LightRenderTarget?.Dispose();
|
||||
viewport.WallMaskRenderTarget?.Dispose();
|
||||
viewport.WallBleedIntermediateRenderTarget1?.Dispose();
|
||||
viewport.WallBleedIntermediateRenderTarget2?.Dispose();
|
||||
var lightMapColorFormat = _hasGLFloatFramebuffers
|
||||
? RenderTargetColorFormat.R11FG11FB10F
|
||||
: RenderTargetColorFormat.Rgba8;
|
||||
var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
|
||||
|
||||
viewport.WallMaskRenderTarget = CreateRenderTarget(viewport.Size, RenderTargetColorFormat.R8,
|
||||
name: $"{viewport.Name}-{nameof(viewport.WallMaskRenderTarget)}");
|
||||
|
||||
viewport.LightRenderTarget = CreateRenderTarget(lightMapSize,
|
||||
new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: true),
|
||||
lightMapSampleParameters,
|
||||
viewport.LightRenderTarget = (RenderTexture) CreateLightRenderTarget(lightMapSize,
|
||||
$"{viewport.Name}-{nameof(viewport.LightRenderTarget)}");
|
||||
|
||||
viewport.LightBlurTarget = CreateRenderTarget(lightMapSize,
|
||||
@@ -1158,11 +1175,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
lightMapSampleParameters,
|
||||
$"{viewport.Name}-{nameof(viewport.LightBlurTarget)}");
|
||||
|
||||
viewport.WallBleedIntermediateRenderTarget1 = CreateRenderTarget(lightMapSizeQuart, lightMapColorFormat,
|
||||
viewport.WallBleedIntermediateRenderTarget1 = CreateRenderTarget(lightMapSizeQuart,
|
||||
new RenderTargetFormatParameters(lightMapColorFormat),
|
||||
lightMapSampleParameters,
|
||||
$"{viewport.Name}-{nameof(viewport.WallBleedIntermediateRenderTarget1)}");
|
||||
|
||||
viewport.WallBleedIntermediateRenderTarget2 = CreateRenderTarget(lightMapSizeQuart, lightMapColorFormat,
|
||||
viewport.WallBleedIntermediateRenderTarget2 = CreateRenderTarget(lightMapSizeQuart,
|
||||
new RenderTargetFormatParameters(lightMapColorFormat),
|
||||
lightMapSampleParameters,
|
||||
$"{viewport.Name}-{nameof(viewport.WallBleedIntermediateRenderTarget2)}");
|
||||
}
|
||||
|
||||
@@ -30,6 +30,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// It, like _mainWindowRenderTarget, is initialized in Clyde's constructor
|
||||
private LoadedRenderTarget _currentBoundRenderTarget;
|
||||
|
||||
|
||||
public IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true)
|
||||
{
|
||||
var lightMapColorFormat = _hasGLFloatFramebuffers
|
||||
? RTCF.R11FG11FB10F
|
||||
: RTCF.Rgba8;
|
||||
var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
|
||||
|
||||
return CreateRenderTarget(size,
|
||||
new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: depthStencil),
|
||||
lightMapSampleParameters,
|
||||
name: name);
|
||||
}
|
||||
|
||||
IRenderTexture IClyde.CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
|
||||
TextureSampleParameters? sampleParameters, string? name)
|
||||
{
|
||||
@@ -204,7 +218,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Size = size,
|
||||
TextureHandle = textureObject.TextureId,
|
||||
MemoryPressure = pressure,
|
||||
ColorFormat = format.ColorFormat
|
||||
ColorFormat = format.ColorFormat,
|
||||
SampleParameters = sampleParameters,
|
||||
};
|
||||
|
||||
//GC.AddMemoryPressure(pressure);
|
||||
@@ -251,9 +266,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private LoadedRenderTarget RtToLoaded(RenderTargetBase rt)
|
||||
private LoadedRenderTarget RtToLoaded(IRenderTarget rt)
|
||||
{
|
||||
return _renderTargets[rt.Handle];
|
||||
switch (rt)
|
||||
{
|
||||
case RenderTargetBase based:
|
||||
return _renderTargets[based.Handle];
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -302,6 +323,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Renderbuffer handle
|
||||
public GLHandle DepthStencilHandle;
|
||||
public long MemoryPressure;
|
||||
|
||||
public TextureSampleParameters? SampleParameters;
|
||||
}
|
||||
|
||||
private abstract class RenderTargetBase : IRenderTarget
|
||||
|
||||
@@ -90,9 +90,61 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// (queue) and (misc), current state of the scissor test. Null if disabled.
|
||||
private UIBox2i? _currentScissorState;
|
||||
|
||||
// Some simple flags that basically just tracks the current state of glEnable(GL_STENCIL/GL_SCISSOR_TEST)
|
||||
private bool _isScissoring;
|
||||
private bool _isStencilling;
|
||||
/// <summary>
|
||||
/// Tracks enabled GL capabilities for renderer state.
|
||||
/// </summary>
|
||||
private GLCaps _glCaps = GLCaps.None;
|
||||
|
||||
private bool IsStencilling
|
||||
{
|
||||
get => (_glCaps & GLCaps.Stencilling) == GLCaps.Stencilling;
|
||||
set
|
||||
{
|
||||
if (value == IsStencilling)
|
||||
return;
|
||||
|
||||
if (value)
|
||||
{
|
||||
_glCaps |= GLCaps.Stencilling;
|
||||
GL.Enable(EnableCap.StencilTest);
|
||||
}
|
||||
else
|
||||
{
|
||||
_glCaps &= ~GLCaps.Stencilling;
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
}
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsBlending
|
||||
{
|
||||
get => (_glCaps & GLCaps.Blending) == GLCaps.Blending;
|
||||
set
|
||||
{
|
||||
if (value == IsBlending)
|
||||
return;
|
||||
|
||||
if (value)
|
||||
{
|
||||
_glCaps |= GLCaps.Blending;
|
||||
GL.Enable(EnableCap.Blend);
|
||||
}
|
||||
else
|
||||
{
|
||||
_glCaps &= ~GLCaps.Blending;
|
||||
GL.Disable(EnableCap.Blend);
|
||||
}
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsScissoring
|
||||
{
|
||||
get => _currentScissorState != null;
|
||||
}
|
||||
|
||||
private readonly RefList<RenderCommand> _queuedRenderCommands = new RefList<RenderCommand>();
|
||||
|
||||
@@ -364,16 +416,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void SetScissorImmediate(bool enable, in UIBox2i box)
|
||||
{
|
||||
var oldIsScissoring = _isScissoring;
|
||||
_isScissoring = enable;
|
||||
if (_isScissoring)
|
||||
if (enable)
|
||||
{
|
||||
if (!oldIsScissoring)
|
||||
{
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
CheckGlError();
|
||||
}
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
}
|
||||
|
||||
if (enable)
|
||||
{
|
||||
// Don't forget to flip it, these coordinates have bottom left as origin.
|
||||
// TODO: Broken when rendering to non-screen render targets.
|
||||
|
||||
@@ -387,11 +440,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
CheckGlError();
|
||||
}
|
||||
else if (oldIsScissoring)
|
||||
{
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: sRGB IS IN LINEAR IF FRAMEBUFFER_SRGB IS ACTIVE.
|
||||
@@ -420,17 +468,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var program = shader.Program;
|
||||
|
||||
program.Use();
|
||||
IsStencilling = instance.Stencil.Enabled;
|
||||
|
||||
// Handle stencil parameters.
|
||||
if (instance.Stencil.Enabled)
|
||||
{
|
||||
if (!_isStencilling)
|
||||
{
|
||||
GL.Enable(EnableCap.StencilTest);
|
||||
CheckGlError();
|
||||
_isStencilling = true;
|
||||
}
|
||||
|
||||
GL.StencilMask(instance.Stencil.WriteMask);
|
||||
CheckGlError();
|
||||
GL.StencilFunc(ToGLStencilFunc(instance.Stencil.Func), instance.Stencil.Ref, instance.Stencil.ReadMask);
|
||||
@@ -438,12 +480,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, ToGLStencilOp(instance.Stencil.Op));
|
||||
CheckGlError();
|
||||
}
|
||||
else if (_isStencilling)
|
||||
{
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
CheckGlError();
|
||||
_isStencilling = false;
|
||||
}
|
||||
|
||||
if (instance.Parameters.Count == 0)
|
||||
return (program, instance);
|
||||
@@ -859,17 +895,34 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private FullStoredRendererState PushRenderStateFull()
|
||||
{
|
||||
return new FullStoredRendererState(_currentMatrixProj, _currentMatrixView, _currentRenderTarget);
|
||||
return new FullStoredRendererState(
|
||||
_currentMatrixProj,
|
||||
_currentMatrixView,
|
||||
_currentBoundRenderTarget,
|
||||
_currentRenderTarget,
|
||||
_queuedShaderInstance,
|
||||
_currentScissorState,
|
||||
_glCaps);
|
||||
}
|
||||
|
||||
private void PopRenderStateFull(in FullStoredRendererState state)
|
||||
{
|
||||
SetProjViewFull(state.ProjMatrix, state.ViewMatrix);
|
||||
BindRenderTargetFull(state.RenderTarget);
|
||||
BindRenderTargetImmediate(state.BoundRenderTarget);
|
||||
|
||||
var (width, height) = state.RenderTarget.Size;
|
||||
_queuedShaderInstance = state.QueuedShaderInstance;
|
||||
_currentRenderTarget = state.RenderTarget;
|
||||
var (width, height) = state.BoundRenderTarget.Size;
|
||||
GL.Viewport(0, 0, width, height);
|
||||
CheckGlError();
|
||||
|
||||
IsStencilling = (state.GLCaps & GLCaps.Stencilling) == GLCaps.Stencilling;
|
||||
IsBlending = (state.GLCaps & GLCaps.Blending) == GLCaps.Blending;
|
||||
|
||||
SetScissorFull(state.ScissorState);
|
||||
|
||||
GL.ClearStencil(0xFF);
|
||||
GL.StencilMask(0xFF);
|
||||
GL.Clear(ClearBufferMask.StencilBufferBit);
|
||||
}
|
||||
|
||||
private void SetViewportImmediate(Box2i box)
|
||||
@@ -1061,15 +1114,44 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
public readonly Matrix3x2 ProjMatrix;
|
||||
public readonly Matrix3x2 ViewMatrix;
|
||||
public readonly LoadedRenderTarget BoundRenderTarget;
|
||||
public readonly LoadedRenderTarget RenderTarget;
|
||||
public readonly ClydeShaderInstance QueuedShaderInstance;
|
||||
|
||||
public FullStoredRendererState(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix,
|
||||
LoadedRenderTarget renderTarget)
|
||||
public readonly UIBox2i? ScissorState;
|
||||
|
||||
public readonly GLCaps GLCaps;
|
||||
|
||||
public FullStoredRendererState(
|
||||
in Matrix3x2 projMatrix,
|
||||
in Matrix3x2 viewMatrix,
|
||||
LoadedRenderTarget boundRenderTarget,
|
||||
LoadedRenderTarget renderTarget,
|
||||
ClydeShaderInstance queuedShaderInstance,
|
||||
UIBox2i? scissorState,
|
||||
GLCaps glcaps
|
||||
)
|
||||
{
|
||||
ProjMatrix = projMatrix;
|
||||
ViewMatrix = viewMatrix;
|
||||
BoundRenderTarget = boundRenderTarget;
|
||||
RenderTarget = renderTarget;
|
||||
QueuedShaderInstance = queuedShaderInstance;
|
||||
|
||||
ScissorState = scissorState;
|
||||
GLCaps = glcaps;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum GLCaps : ushort
|
||||
{
|
||||
// If you add flags here make sure to update PopRenderState!
|
||||
None = 0,
|
||||
|
||||
Blending = 1 << 0,
|
||||
|
||||
Stencilling = 1 << 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysBelow(
|
||||
DrawingHandleScreen handle,
|
||||
IRenderHandle handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
@@ -179,7 +179,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysAbove(
|
||||
DrawingHandleScreen handle,
|
||||
IRenderHandle handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
|
||||
|
||||
@@ -245,7 +246,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
overrideVersion != null,
|
||||
_windowing!.GetDescription());
|
||||
|
||||
GL.Enable(EnableCap.Blend);
|
||||
IsBlending = true;
|
||||
if (_hasGLSrgb && !_isGLES)
|
||||
{
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -189,6 +190,22 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new DummyTexture(size);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Color GetClearColor(EntityUid mapUid)
|
||||
{
|
||||
return Color.Transparent;
|
||||
}
|
||||
|
||||
public void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier)
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true)
|
||||
{
|
||||
return CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.R8, hasDepthStencil: depthStencil), null, name: name);
|
||||
}
|
||||
|
||||
public IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
|
||||
TextureSampleParameters? sampleParameters = null, string? name = null)
|
||||
{
|
||||
@@ -497,7 +514,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysBelow(
|
||||
DrawingHandleScreen handle,
|
||||
IRenderHandle handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
@@ -505,7 +522,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysAbove(
|
||||
DrawingHandleScreen handle,
|
||||
IRenderHandle handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
baseline += new Vector2(metrics.Value.BearingX, -metrics.Value.BearingY);
|
||||
if(handle is DrawingHandleWorld worldhandle)
|
||||
worldhandle.DrawTextureRect(texture, Box2.FromDimensions(baseline, texture.Size));
|
||||
worldhandle.DrawTextureRect(texture, Box2.FromDimensions(baseline, texture.Size), color);
|
||||
else
|
||||
handle.DrawTexture(texture, baseline, color);
|
||||
return metrics.Value.Advance;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -71,6 +74,24 @@ namespace Robust.Client.Graphics
|
||||
in TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the clear color for the specified map viewport.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
Color GetClearColor(EntityUid mapUid);
|
||||
|
||||
/// <summary>
|
||||
/// Applies a blur to the specified render target. Requires a separate buffer with similar properties to draw intermediate steps into.
|
||||
/// </summary>
|
||||
/// <param name="viewport">The viewport being used for drawing.</param>
|
||||
/// <param name="target">The blur target.</param>
|
||||
/// <param name="blurBuffer">The separate buffer to draw into.</param>
|
||||
/// <param name="eye">The eye being drawn with.</param>
|
||||
/// <param name="multiplier">Scale of how much blur to blur by.</param>
|
||||
void BlurRenderTarget(IClydeViewport viewport, IRenderTarget target, IRenderTarget blurBuffer, IEye eye, float multiplier);
|
||||
|
||||
IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true);
|
||||
|
||||
IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
|
||||
TextureSampleParameters? sampleParameters = null, string? name = null);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
IRenderTexture RenderTarget { get; }
|
||||
IRenderTexture LightRenderTarget { get; }
|
||||
|
||||
IEye? Eye { get; set; }
|
||||
Vector2i Size { get; }
|
||||
|
||||
@@ -66,7 +67,7 @@ namespace Robust.Client.Graphics
|
||||
/// Not relative to the current transform of <see cref="handle"/>.
|
||||
/// </param>
|
||||
public void RenderScreenOverlaysBelow(
|
||||
DrawingHandleScreen handle,
|
||||
IRenderHandle handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds);
|
||||
|
||||
@@ -80,7 +81,7 @@ namespace Robust.Client.Graphics
|
||||
/// Not relative to the current transform of <see cref="handle"/>.
|
||||
/// </param>
|
||||
public void RenderScreenOverlaysAbove(
|
||||
DrawingHandleScreen handle,
|
||||
IRenderHandle handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
@@ -15,5 +17,42 @@ namespace Robust.Client.Graphics
|
||||
Vector2i Size { get; }
|
||||
|
||||
void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion = null) where T : unmanaged, IPixel<T>;
|
||||
|
||||
public Vector2 LocalToWorld(IEye eye, Vector2 point, Vector2 scale)
|
||||
{
|
||||
var newPoint = point;
|
||||
|
||||
// (inlined version of UiProjMatrix^-1)
|
||||
newPoint -= Size / 2f;
|
||||
newPoint *= new Vector2(1, -1) / EyeManager.PixelsPerMeter;
|
||||
|
||||
// view matrix
|
||||
eye.GetViewMatrixInv(out var viewMatrixInv, scale);
|
||||
newPoint = Vector2.Transform(newPoint, viewMatrixInv);
|
||||
|
||||
return newPoint;
|
||||
}
|
||||
|
||||
public Vector2 WorldToLocal(Vector2 point, IEye eye, Vector2 scale)
|
||||
{
|
||||
var newPoint = point;
|
||||
|
||||
eye.GetViewMatrix(out var viewMatrix, scale);
|
||||
newPoint = Vector2.Transform(newPoint, viewMatrix);
|
||||
|
||||
// (inlined version of UiProjMatrix)
|
||||
newPoint *= new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
newPoint += Size / 2f;
|
||||
|
||||
return newPoint;
|
||||
}
|
||||
|
||||
public Matrix3x2 GetWorldToLocalMatrix(IEye eye, Vector2 scale)
|
||||
{
|
||||
eye.GetViewMatrix(out var viewMatrix, scale * new Vector2(EyeManager.PixelsPerMeter, -EyeManager.PixelsPerMeter));
|
||||
viewMatrix.M31 += Size.X / 2f;
|
||||
viewMatrix.M32 += Size.Y / 2f;
|
||||
return viewMatrix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
Robust.Client/Graphics/Lighting/GetClearColorEvent.cs
Normal file
13
Robust.Client/Graphics/Lighting/GetClearColorEvent.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Raised by the engine if content wishes to override the default clear color.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct GetClearColorEvent
|
||||
{
|
||||
public Color? Color;
|
||||
}
|
||||
@@ -9,6 +9,5 @@ namespace Robust.Client.Graphics
|
||||
public bool DrawHardFov { get; set; } = true;
|
||||
public bool DrawLighting { get; set; } = true;
|
||||
public bool LockConsoleAccess { get; set; } = false;
|
||||
public Color AmbientLightColor { get; set; } = Color.FromSrgb(Color.Black);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -39,6 +40,8 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
public readonly UIBox2i ViewportBounds;
|
||||
|
||||
public readonly EntityUid MapUid;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="MapId"/> of the viewport's eye.
|
||||
/// </summary>
|
||||
@@ -54,24 +57,32 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
public readonly Box2Rotated WorldBounds;
|
||||
|
||||
public readonly IRenderHandle RenderHandle;
|
||||
|
||||
public DrawingHandleScreen ScreenHandle => (DrawingHandleScreen) DrawingHandle;
|
||||
public DrawingHandleWorld WorldHandle => (DrawingHandleWorld) DrawingHandle;
|
||||
|
||||
public OverlayDrawArgs(
|
||||
internal OverlayDrawArgs(
|
||||
OverlaySpace space,
|
||||
IViewportControl? viewportControl,
|
||||
IClydeViewport viewport,
|
||||
DrawingHandleBase drawingHandle,
|
||||
IRenderHandle renderHandle,
|
||||
in UIBox2i viewportBounds,
|
||||
in EntityUid mapUid,
|
||||
in MapId mapId,
|
||||
in Box2 worldAabb,
|
||||
in Box2Rotated worldBounds)
|
||||
{
|
||||
DrawingHandle = space is OverlaySpace.ScreenSpace or OverlaySpace.ScreenSpaceBelowWorld
|
||||
? renderHandle.DrawingHandleScreen
|
||||
: renderHandle.DrawingHandleWorld;
|
||||
|
||||
Space = space;
|
||||
ViewportControl = viewportControl;
|
||||
Viewport = viewport;
|
||||
DrawingHandle = drawingHandle;
|
||||
RenderHandle = renderHandle;
|
||||
ViewportBounds = viewportBounds;
|
||||
MapUid = mapUid;
|
||||
MapId = mapId;
|
||||
WorldAABB = worldAabb;
|
||||
WorldBounds = worldBounds;
|
||||
|
||||
@@ -13,10 +13,22 @@ internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
|
||||
private readonly Dictionary<Type, Overlay> _overlays = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list that duplicates a value from <see cref="_overlays"/>,
|
||||
/// but already sorted, by invoking <see cref="Sort"/>
|
||||
/// in <see cref="AddOverlay"/> and <see cref="RemoveOverlay(System.Type)"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private readonly List<Overlay> _sortedOverlays = [];
|
||||
|
||||
private ISawmill _logger = default!;
|
||||
|
||||
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
|
||||
/// <summary>
|
||||
/// Returns a list of all overlays sorted by <see cref="Overlay.ZIndex"/>
|
||||
/// </summary>
|
||||
public IEnumerable<Overlay> AllOverlays => _sortedOverlays;
|
||||
|
||||
public void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
@@ -28,9 +40,10 @@ internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
|
||||
public bool AddOverlay(Overlay overlay)
|
||||
{
|
||||
if (_overlays.ContainsKey(overlay.GetType()))
|
||||
if (!_overlays.TryAdd(overlay.GetType(), overlay))
|
||||
return false;
|
||||
_overlays.Add(overlay.GetType(), overlay);
|
||||
|
||||
Sort();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -42,7 +55,9 @@ internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
return false;
|
||||
}
|
||||
|
||||
return _overlays.Remove(overlayClass);
|
||||
var result = _overlays.Remove(overlayClass);
|
||||
Sort();
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool RemoveOverlay<T>() where T : Overlay
|
||||
@@ -52,7 +67,7 @@ internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
|
||||
public bool RemoveOverlay(Overlay overlay)
|
||||
{
|
||||
return _overlays.Remove(overlay.GetType());
|
||||
return RemoveOverlay(overlay.GetType());
|
||||
}
|
||||
|
||||
public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay)
|
||||
@@ -104,8 +119,27 @@ internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit
|
||||
return _overlays.ContainsKey(typeof(T));
|
||||
}
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
_sortedOverlays.Clear();
|
||||
_sortedOverlays.AddRange(_overlays.Values);
|
||||
_sortedOverlays.Sort(OverlayComparer.Instance);
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_logger = _logMan.GetSawmill("overlay");
|
||||
}
|
||||
|
||||
private sealed class OverlayComparer : IComparer<Overlay>
|
||||
{
|
||||
public static readonly OverlayComparer Instance = new();
|
||||
|
||||
public int Compare(Overlay? x, Overlay? y)
|
||||
{
|
||||
var zX = x?.ZIndex ?? 0;
|
||||
var zY = y?.ZIndex ?? 0;
|
||||
return zX.CompareTo(zY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public sealed partial class PhysicsSystem
|
||||
var maps = new HashSet<EntityUid>();
|
||||
|
||||
var enumerator = AllEntityQuery<PredictedPhysicsComponent, PhysicsComponent, TransformComponent>();
|
||||
while (enumerator.MoveNext(out var _, out var physics, out var xform))
|
||||
while (enumerator.MoveNext(out _, out var physics, out var xform))
|
||||
{
|
||||
DebugTools.Assert(physics.Predict);
|
||||
|
||||
|
||||
@@ -352,6 +352,9 @@ public sealed partial class ReplayLoadManager
|
||||
// prototype changes when jumping around in time. This also requires reworking how the initial
|
||||
// implicit state data is generated, because we can't simply cache it anymore.
|
||||
// Also, does reloading prototypes in release mode modify existing entities?
|
||||
// Yes, yes it does. See PrototypeReloadSystem.UpdateEntity()
|
||||
// Its just not supported ATM.
|
||||
// TBH it'd be easier if overriding existing prototypes in release mode was just forbidden.
|
||||
|
||||
var msg = $"Overwriting an existing prototype! Kind: {kind.Name}. Ids: {string.Join(", ", ids)}";
|
||||
if (_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
@@ -361,7 +364,6 @@ public sealed partial class ReplayLoadManager
|
||||
}
|
||||
}
|
||||
|
||||
_protoMan.ResolveResults();
|
||||
_protoMan.ReloadPrototypes(changed);
|
||||
_locMan.ReloadLocalizations();
|
||||
}
|
||||
|
||||
@@ -95,9 +95,21 @@ public sealed partial class ReplayLoadManager
|
||||
_gameState.ClearDetachQueue();
|
||||
_gameState.ApplyGameState(checkpoint.State, next);
|
||||
|
||||
// Sort entities to ensure that we initialize parents before children
|
||||
var sorted = new List<EntityUid>(entities.Count);
|
||||
var added = new HashSet<EntityUid>(entities.Count);
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
AddSorted(uid, sorted, added, xformQuery);
|
||||
}
|
||||
DebugTools.AssertEqual(sorted.Count, entities.Count);
|
||||
DebugTools.AssertEqual(added.Count, entities.Count);
|
||||
await callback(i, total, LoadingState.Initializing, false);
|
||||
|
||||
i = 0;
|
||||
var query = _entMan.GetEntityQuery<MetaDataComponent>();
|
||||
foreach (var uid in entities)
|
||||
foreach (var uid in sorted)
|
||||
{
|
||||
_entMan.InitializeEntity(uid, query.GetComponent(uid));
|
||||
if (i++ % 50 == 0)
|
||||
@@ -109,7 +121,7 @@ public sealed partial class ReplayLoadManager
|
||||
|
||||
i = 0;
|
||||
await callback(0, total, LoadingState.Starting, true);
|
||||
foreach (var uid in entities)
|
||||
foreach (var uid in sorted)
|
||||
{
|
||||
_entMan.StartEntity(uid);
|
||||
if (i++ % 50 == 0)
|
||||
@@ -132,4 +144,16 @@ public sealed partial class ReplayLoadManager
|
||||
_replayPlayback.StartReplay(data);
|
||||
_timing.Paused = false;
|
||||
}
|
||||
|
||||
private void AddSorted(EntityUid uid, List<EntityUid> sortedList, HashSet<EntityUid> added, EntityQuery<TransformComponent> query)
|
||||
{
|
||||
if (!added.Add(uid))
|
||||
return;
|
||||
|
||||
var parent = query.Comp(uid).ParentUid;
|
||||
if (parent != EntityUid.Invalid)
|
||||
AddSorted(parent, sortedList, added, query);
|
||||
|
||||
sortedList.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ internal sealed partial class ReplayPlaybackManager
|
||||
var next = Replay.NextState;
|
||||
BeforeApplyState?.Invoke((state, next));
|
||||
_gameState.ApplyGameState(state, next);
|
||||
_gameState.MergeImplicitData();
|
||||
DebugTools.Assert(Replay.LastApplied >= state.FromSequence);
|
||||
DebugTools.Assert(Replay.LastApplied + 1 <= state.ToSequence);
|
||||
Replay.LastApplied = state.ToSequence;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -6,14 +7,63 @@ namespace Robust.Client.UserInterface;
|
||||
|
||||
public static class BoundUserInterfaceExt
|
||||
{
|
||||
private static T GetWindow<T>(BoundUserInterface bui) where T : BaseWindow, new()
|
||||
{
|
||||
var window = bui.CreateDisposableControl<T>();
|
||||
window.OnClose += bui.Close;
|
||||
var system = bui.EntMan.System<UserInterfaceSystem>();
|
||||
system.RegisterControl(bui, window);
|
||||
return window;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a window and also handle closing the BUI when it's closed.
|
||||
/// </summary>
|
||||
public static T CreateWindow<T>(this BoundUserInterface bui) where T : BaseWindow, new()
|
||||
{
|
||||
var window = bui.CreateDisposableControl<T>();
|
||||
window.OpenCentered();
|
||||
window.OnClose += bui.Close;
|
||||
var window = GetWindow<T>(bui);
|
||||
|
||||
if (bui.EntMan.System<UserInterfaceSystem>().TryGetPosition(bui.Owner, bui.UiKey, out var position))
|
||||
{
|
||||
window.Open(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.OpenCentered();
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
public static T CreateWindowCenteredLeft<T>(this BoundUserInterface bui) where T : BaseWindow, new()
|
||||
{
|
||||
var window = GetWindow<T>(bui);
|
||||
|
||||
if (bui.EntMan.System<UserInterfaceSystem>().TryGetPosition(bui.Owner, bui.UiKey, out var position))
|
||||
{
|
||||
window.Open(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
public static T CreateWindowCenteredRight<T>(this BoundUserInterface bui) where T : BaseWindow, new()
|
||||
{
|
||||
var window = GetWindow<T>(bui);
|
||||
|
||||
if (bui.EntMan.System<UserInterfaceSystem>().TryGetPosition(bui.Owner, bui.UiKey, out var position))
|
||||
{
|
||||
window.Open(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.OpenCenteredRight();
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Robust.Client.UserInterface
|
||||
UserInterfaceManagerInternal.QueueStyleUpdate(this);
|
||||
}
|
||||
|
||||
internal void StyleSheetUpdate()
|
||||
public void InvalidateStyleSheet()
|
||||
{
|
||||
_stylesheetUpdateNeeded = true;
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
internal void StylesheetUpdateRecursive()
|
||||
{
|
||||
StyleSheetUpdate();
|
||||
InvalidateStyleSheet();
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
@@ -149,7 +149,7 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
internal void DoStyleUpdate()
|
||||
public void DoStyleUpdate()
|
||||
{
|
||||
_styleProperties.Clear();
|
||||
|
||||
|
||||
@@ -549,7 +549,7 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
}
|
||||
|
||||
internal virtual void DrawInternal(IRenderHandle renderHandle)
|
||||
protected internal virtual void Draw(IRenderHandle renderHandle)
|
||||
{
|
||||
Draw(renderHandle.DrawingHandleScreen);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,6 @@ public sealed class TileSpawningUIController : UIController
|
||||
return;
|
||||
_window = UIManager.CreateWindow<TileSpawnWindow>();
|
||||
LayoutContainer.SetAnchorPreset(_window,LayoutContainer.LayoutPreset.CenterLeft);
|
||||
_window.SearchBar.GrabKeyboardFocus();
|
||||
_window.ClearButton.OnPressed += OnTileClearPressed;
|
||||
_window.SearchBar.OnTextChanged += OnTileSearchChanged;
|
||||
_window.TileList.OnItemSelected += OnTileItemSelected;
|
||||
|
||||
@@ -220,7 +220,7 @@ internal partial class UserInterfaceManager
|
||||
continue;
|
||||
|
||||
var typeDict = _assignerRegistry.GetOrNew(fieldInfo.FieldType);
|
||||
var assigner = (InternalReflectionUtils.AssignField<UIController, object?>)InternalReflectionUtils.EmitFieldAssigner<UIController>(backingField, true);
|
||||
var assigner = (InternalReflectionUtils.AssignField<UIController, object?>)InternalReflectionUtils.EmitFieldAssigner(typeof(UIController), backingField, true);
|
||||
typeDict.Add(controllerType, assigner);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ public class EntityPrototypeView : SpriteView
|
||||
{
|
||||
private string? _currentPrototype;
|
||||
private EntityUid? _ourEntity;
|
||||
private bool _isShowing;
|
||||
|
||||
public EntityPrototypeView()
|
||||
{
|
||||
@@ -22,8 +23,6 @@ public class EntityPrototypeView : SpriteView
|
||||
|
||||
public void SetPrototype(EntProtoId? entProto)
|
||||
{
|
||||
SpriteSystem ??= EntMan.System<SpriteSystem>();
|
||||
|
||||
if (entProto == _currentPrototype
|
||||
&& EntMan.TryGetComponent(Entity?.Owner, out MetaDataComponent? meta)
|
||||
&& meta.EntityPrototype?.ID == _currentPrototype)
|
||||
@@ -32,14 +31,10 @@ public class EntityPrototypeView : SpriteView
|
||||
}
|
||||
|
||||
_currentPrototype = entProto;
|
||||
SetEntity(null);
|
||||
EntMan.DeleteEntity(_ourEntity);
|
||||
|
||||
if (_currentPrototype != null)
|
||||
if (_ourEntity != null || _isShowing)
|
||||
{
|
||||
_ourEntity = EntMan.Spawn(_currentPrototype);
|
||||
SpriteSystem.ForceUpdate(_ourEntity.Value);
|
||||
SetEntity(_ourEntity);
|
||||
UpdateEntity();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,12 +43,38 @@ public class EntityPrototypeView : SpriteView
|
||||
base.EnteredTree();
|
||||
|
||||
if (_currentPrototype != null)
|
||||
SetPrototype(_currentPrototype);
|
||||
{
|
||||
UpdateEntity();
|
||||
}
|
||||
|
||||
_isShowing = true;
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
EntMan.TryQueueDeleteEntity(_ourEntity);
|
||||
_ourEntity = null;
|
||||
|
||||
_isShowing = false;
|
||||
}
|
||||
|
||||
private void UpdateEntity()
|
||||
{
|
||||
SetEntity(null);
|
||||
EntMan.DeleteEntity(_ourEntity);
|
||||
|
||||
if (_currentPrototype != null)
|
||||
{
|
||||
SpriteSystem ??= EntMan.System<SpriteSystem>();
|
||||
|
||||
_ourEntity = EntMan.Spawn(_currentPrototype);
|
||||
SpriteSystem.ForceUpdate(_ourEntity.Value);
|
||||
SetEntity(_ourEntity);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ourEntity = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_spriteSize = new Vector2(longestRotatedSide, longestRotatedSide);
|
||||
}
|
||||
|
||||
internal override void DrawInternal(IRenderHandle renderHandle)
|
||||
protected internal override void Draw(IRenderHandle renderHandle)
|
||||
{
|
||||
if (!ResolveEntity(out var uid, out var sprite, out var xform))
|
||||
return;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -102,6 +103,8 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
{
|
||||
var cursor = CursorShape.Arrow;
|
||||
var previewDragMode = GetDragModeFor(args.RelativePosition);
|
||||
previewDragMode &= ~DragMode.Move;
|
||||
|
||||
switch (previewDragMode)
|
||||
{
|
||||
case DragMode.Top:
|
||||
@@ -116,9 +119,6 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
case DragMode.Bottom | DragMode.Left:
|
||||
case DragMode.Top | DragMode.Right:
|
||||
cursor = CursorShape.Crosshair;
|
||||
break;
|
||||
|
||||
case DragMode.Bottom | DragMode.Right:
|
||||
case DragMode.Top | DragMode.Left:
|
||||
cursor = CursorShape.Crosshair;
|
||||
@@ -160,15 +160,6 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
var rect = new UIBox2(left, top, right, bottom);
|
||||
LayoutContainer.SetPosition(this, rect.TopLeft);
|
||||
SetSize = rect.Size;
|
||||
|
||||
/*
|
||||
var timing = IoCManager.Resolve<IGameTiming>();
|
||||
|
||||
var l = GetValue<float>(LayoutContainer.MarginLeftProperty);
|
||||
var t = GetValue<float>(LayoutContainer.MarginTopProperty);
|
||||
|
||||
Logger.Debug($"{timing.CurFrame}: {rect.TopLeft}/({l}, {t}), {rect.Size}/{SetSize}");
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,6 +220,16 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
OnOpen?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the window and places it at the specified position.
|
||||
/// </summary>
|
||||
public void Open(Vector2 position)
|
||||
{
|
||||
Measure(Vector2Helpers.Infinity);
|
||||
Open();
|
||||
LayoutContainer.SetPosition(this, position);
|
||||
}
|
||||
|
||||
public void OpenCentered() => OpenCenteredAt(new Vector2(0.5f, 0.5f));
|
||||
|
||||
public void OpenToLeft() => OpenCenteredAt(new Vector2(0, 0.5f));
|
||||
|
||||
@@ -276,10 +276,14 @@ public sealed partial class DebugConsole
|
||||
// This means that letter casing will match the completion suggestion.
|
||||
CommandBar.CursorPosition = lastRange.end;
|
||||
CommandBar.SelectionStart = lastRange.start;
|
||||
var insertValue = CommandParsing.Escape(completion);
|
||||
|
||||
var insertValue = (completionFlags & CompletionOptionFlags.NoEscape) == 0
|
||||
? CommandParsing.Escape(completion)
|
||||
: completion;
|
||||
|
||||
// If the replacement contains a space, we must quote it to treat it as a single argument.
|
||||
var mustQuote = insertValue.Contains(' ');
|
||||
var mustQuote = (completionFlags & CompletionOptionFlags.NoQuote) == 0 && insertValue.Contains(' ');
|
||||
|
||||
if ((completionFlags & CompletionOptionFlags.PartialCompletion) == 0)
|
||||
{
|
||||
if (mustQuote)
|
||||
|
||||
@@ -10,4 +10,11 @@ public sealed partial class TileSpawnWindow : DefaultWindow
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
SearchBar.GrabKeyboardFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +65,13 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
// -- Handlers: Out --
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
protected internal override void Draw(IRenderHandle handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (Viewport == null)
|
||||
{
|
||||
handle.DrawRect(UIBox2.FromDimensions(new Vector2(0, 0), Size * UIScale), Color.Red);
|
||||
handle.DrawingHandleScreen.DrawRect(UIBox2.FromDimensions(new Vector2(0, 0), Size * UIScale), Color.Red);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -82,7 +82,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
Viewport.RenderScreenOverlaysBelow(handle, this, viewportBounds);
|
||||
|
||||
Viewport.Render();
|
||||
handle.DrawTextureRect(Viewport.RenderTarget.Texture,
|
||||
handle.DrawingHandleScreen.DrawTextureRect(Viewport.RenderTarget.Texture,
|
||||
UIBox2.FromDimensions(new Vector2(0, 0), (Vector2i) (Viewport.Size / _viewportResolution)));
|
||||
|
||||
Viewport.RenderScreenOverlaysAbove(handle, this, viewportBounds);
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface
|
||||
{
|
||||
@@ -146,6 +147,16 @@ namespace Robust.Client.UserInterface
|
||||
/// but not necessarily a new or existing control is rearranged.
|
||||
/// </summary>
|
||||
void UpdateHovered();
|
||||
|
||||
/// <summary>
|
||||
/// Render a control and all of its children.
|
||||
/// </summary>
|
||||
void RenderControl(in Control.ControlRenderArguments args, Control control);
|
||||
|
||||
/// <summary>
|
||||
/// Render a control and all of its children.
|
||||
/// </summary>
|
||||
void RenderControl(IRenderHandle handle, Control control, Vector2i position);
|
||||
}
|
||||
|
||||
public readonly struct PostDrawUIRootEventArgs
|
||||
|
||||
@@ -313,7 +313,10 @@ internal partial class UserInterfaceManager
|
||||
|
||||
private void _clearTooltip()
|
||||
{
|
||||
if (!_showingTooltip) return;
|
||||
_resetTooltipTimer();
|
||||
|
||||
if (!_showingTooltip)
|
||||
return;
|
||||
|
||||
if (_suppliedTooltip != null)
|
||||
{
|
||||
@@ -322,7 +325,6 @@ internal partial class UserInterfaceManager
|
||||
}
|
||||
|
||||
CurrentlyHovered?.PerformHideTooltip();
|
||||
_resetTooltipTimer();
|
||||
_showingTooltip = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ internal sealed partial class UserInterfaceManager
|
||||
_roots.Add(newRoot);
|
||||
_windowsToRoot.Add(window.Id, newRoot);
|
||||
|
||||
newRoot.StyleSheetUpdate();
|
||||
newRoot.InvalidateStyleSheet();
|
||||
newRoot.InvalidateMeasure();
|
||||
QueueMeasureUpdate(newRoot);
|
||||
QueueArrangeUpdate(newRoot);
|
||||
|
||||
@@ -335,6 +335,30 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderControl(in Control.ControlRenderArguments args, Control control)
|
||||
{
|
||||
var _ = 0;
|
||||
RenderControl(args.Handle,
|
||||
ref _,
|
||||
control,
|
||||
args.Position,
|
||||
args.Modulate,
|
||||
args.ScissorBox,
|
||||
args.CoordinateTransform);
|
||||
}
|
||||
|
||||
public void RenderControl(IRenderHandle handle, Control control, Vector2i position)
|
||||
{
|
||||
var _ = 0;
|
||||
RenderControl(handle,
|
||||
ref _,
|
||||
control,
|
||||
position,
|
||||
Color.White,
|
||||
null,
|
||||
Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
public void RenderControl(IRenderHandle renderHandle, ref int total, Control control, Vector2i position, Color modulate,
|
||||
UIBox2i? scissorBox, Matrix3x2 coordinateTransform)
|
||||
{
|
||||
@@ -393,7 +417,7 @@ namespace Robust.Client.UserInterface
|
||||
// Handle modulation with care.
|
||||
var oldMod = handle.Modulate;
|
||||
handle.Modulate = modulate * control.ActualModulateSelf;
|
||||
control.DrawInternal(renderHandle);
|
||||
control.Draw(renderHandle);
|
||||
handle.Modulate = oldMod;
|
||||
handle.UseShader(null);
|
||||
}
|
||||
@@ -409,10 +433,11 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
control.PreRenderChildren(ref args);
|
||||
|
||||
foreach (var child in control.Children)
|
||||
for (var index = 0; index < control.ChildCount; index++)
|
||||
{
|
||||
var child = control.GetChild(index);
|
||||
var pos = position + (Vector2i)Vector2.Transform(child.PixelPosition, coordinateTransform);
|
||||
control.RenderChildOverride(ref args, child.GetPositionInParent(), pos);
|
||||
control.RenderChildOverride(ref args, index, pos);
|
||||
}
|
||||
|
||||
control.PostRenderChildren(ref args);
|
||||
|
||||
@@ -153,6 +153,7 @@ public sealed class ComponentPauseGenerator : IIncrementalGenerator
|
||||
|
||||
builder.AppendLine($$"""
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class {{info.PartialTypeInfo.Name}}_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace Robust.Server
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir);
|
||||
_resources.Initialize(dataDir, hideUserDataDir: false);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
@@ -586,7 +586,7 @@ namespace Robust.Server
|
||||
{
|
||||
_config.OnValueChanged(CVars.NetTickrate, i =>
|
||||
{
|
||||
var b = (byte) i;
|
||||
var b = (ushort) i;
|
||||
_time.TickRate = b;
|
||||
|
||||
_logger.Info($"Tickrate changed to: {b} on tick {_time.CurTick}");
|
||||
@@ -594,7 +594,7 @@ namespace Robust.Server
|
||||
|
||||
var startOffset = TimeSpan.FromSeconds(_config.GetCVar(CVars.NetTimeStartOffset));
|
||||
_time.TimeBase = (startOffset, GameTick.First);
|
||||
_time.TickRate = (byte) _config.GetCVar(CVars.NetTickrate);
|
||||
_time.TickRate = (ushort) _config.GetCVar(CVars.NetTickrate);
|
||||
|
||||
_logger.Info($"Name: {ServerName}");
|
||||
_logger.Info($"TickRate: {_time.TickRate}({_time.TickPeriod.TotalMilliseconds:0.00}ms)");
|
||||
|
||||
@@ -20,6 +20,10 @@ namespace Robust.Server.GameStates;
|
||||
/// </summary>
|
||||
internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<PvsData> memoryRegion)
|
||||
{
|
||||
#if DEBUG
|
||||
public HashSet<NetEntity> ToSendSet = new();
|
||||
#endif
|
||||
|
||||
public readonly ICommonSession Session = session;
|
||||
|
||||
public readonly ResizableMemoryRegion<PvsData> DataMemory = memoryRegion;
|
||||
@@ -197,7 +201,7 @@ internal struct PvsMetadata
|
||||
{
|
||||
DebugTools.AssertEqual(NetEntity, comp.NetEntity);
|
||||
DebugTools.AssertEqual(VisMask, comp.VisibilityMask);
|
||||
DebugTools.AssertEqual(LifeStage, comp.EntityLifeStage);
|
||||
DebugTools.Assert(LifeStage == comp.EntityLifeStage);
|
||||
DebugTools.Assert(LastModifiedTick == comp.EntityLastModifiedTick || LastModifiedTick.Value == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
public sealed class PvsOverrideSystem : EntitySystem
|
||||
public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IConsoleHost _console = default!;
|
||||
@@ -134,8 +135,10 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations,
|
||||
/// causing them to always be sent to all clients.
|
||||
/// </summary>
|
||||
public void AddGlobalOverride(EntityUid uid)
|
||||
public override void AddGlobalOverride(EntityUid uid)
|
||||
{
|
||||
base.AddGlobalOverride(uid);
|
||||
|
||||
if (GlobalOverride.Add(uid))
|
||||
_hasOverride.Add(uid);
|
||||
}
|
||||
@@ -143,8 +146,10 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Removes an entity from the global overrides.
|
||||
/// </summary>
|
||||
public void RemoveGlobalOverride(EntityUid uid)
|
||||
public override void RemoveGlobalOverride(EntityUid uid)
|
||||
{
|
||||
base.RemoveGlobalOverride(uid);
|
||||
|
||||
GlobalOverride.Remove(uid);
|
||||
// Not bothering to clear _hasOverride, as we'd have to check all the other collections, and at that point we
|
||||
// might as well just do that when the entity gets deleted anyways.
|
||||
@@ -203,8 +208,10 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations for a
|
||||
/// specific session.
|
||||
/// </summary>
|
||||
public void AddSessionOverride(EntityUid uid, ICommonSession session)
|
||||
public override void AddSessionOverride(EntityUid uid, ICommonSession session)
|
||||
{
|
||||
base.AddSessionOverride(uid, session);
|
||||
|
||||
if (SessionOverrides.GetOrNew(session).Add(uid))
|
||||
_hasOverride.Add(uid);
|
||||
}
|
||||
@@ -212,8 +219,10 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Removes an entity from a session's overrides.
|
||||
/// </summary>
|
||||
public void RemoveSessionOverride(EntityUid uid, ICommonSession session)
|
||||
public override void RemoveSessionOverride(EntityUid uid, ICommonSession session)
|
||||
{
|
||||
base.RemoveSessionOverride(uid, session);
|
||||
|
||||
if (!SessionOverrides.TryGetValue(session, out var overrides))
|
||||
return;
|
||||
|
||||
@@ -228,8 +237,10 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations,
|
||||
/// causing them to always be sent to all clients.
|
||||
/// </summary>
|
||||
public void AddSessionOverrides(EntityUid uid, Filter filter)
|
||||
public override void AddSessionOverrides(EntityUid uid, Filter filter)
|
||||
{
|
||||
base.AddSessionOverrides(uid, filter);
|
||||
|
||||
foreach (var session in filter.Recipients)
|
||||
{
|
||||
AddSessionOverride(uid, session);
|
||||
|
||||
@@ -302,7 +302,9 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
// Process all entities in visible PVS chunks
|
||||
AddPvsChunks(session);
|
||||
|
||||
#if DEBUG
|
||||
VerifySessionData(session);
|
||||
#endif
|
||||
|
||||
var toSend = session.ToSend!;
|
||||
session.ToSend = null;
|
||||
@@ -332,11 +334,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
session.Overflow = oldEntry.Value;
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
#if DEBUG
|
||||
private void VerifySessionData(PvsSession pvsSession)
|
||||
{
|
||||
var toSend = pvsSession.ToSend;
|
||||
var toSendSet = new HashSet<NetEntity>(toSend!.Count);
|
||||
var toSend = pvsSession.ToSend!;
|
||||
var toSendSet = pvsSession.ToSendSet;
|
||||
toSendSet.Clear();
|
||||
|
||||
foreach (var intPtr in toSend)
|
||||
{
|
||||
@@ -360,6 +363,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|| data.LastSeen == _gameTiming.CurTick - 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private (Vector2 worldPos, float range, EntityUid? map) CalcViewBounds(Entity<TransformComponent, EyeComponent?> eye)
|
||||
{
|
||||
|
||||
@@ -73,7 +73,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32();
|
||||
var flags = (TileRenderFlag)reader.ReadByte();
|
||||
var flags = reader.ReadByte();
|
||||
var variant = reader.ReadByte();
|
||||
|
||||
var defName = tileMap[id];
|
||||
|
||||
@@ -336,6 +336,7 @@ namespace Robust.Server.ServerStatus
|
||||
{
|
||||
RespondShared();
|
||||
|
||||
_context.Response.StatusCode = (int)code;
|
||||
_context.Response.ContentType = "application/json";
|
||||
|
||||
await JsonSerializer.SerializeAsync(_context.Response.OutputStream, jsonData);
|
||||
|
||||
@@ -50,14 +50,14 @@ public sealed class GamePrototypeLoadManager : SharedPrototypeLoadManager
|
||||
|
||||
internal void SendToNewUser(INetChannel channel)
|
||||
{
|
||||
if (LoadedPrototypes.Count == 0)
|
||||
return;
|
||||
|
||||
// Just dump all the prototypes on connect, before them missing could be an issue.
|
||||
foreach (var prototype in LoadedPrototypes)
|
||||
var msg = new GamePrototypeLoadMessage
|
||||
{
|
||||
var msg = new GamePrototypeLoadMessage
|
||||
{
|
||||
PrototypeData = prototype
|
||||
};
|
||||
channel.SendMessage(msg);
|
||||
}
|
||||
PrototypeData = string.Join("\n\n", LoadedPrototypes)
|
||||
};
|
||||
channel.SendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,8 +346,8 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
|
||||
if (value is GlobalEntityUidName or GlobalNullableEntityUidName)
|
||||
{
|
||||
networkedType = $"Dictionary<{key}, {value}>";
|
||||
value = valueNullable ? GlobalNetEntityNullableName : GlobalNetEntityName;
|
||||
networkedType = $"Dictionary<{key}, {value}>";
|
||||
|
||||
stateFields.Append($@"
|
||||
public {networkedType} {name} = default!;");
|
||||
@@ -575,12 +575,14 @@ public partial class {componentName}{deltaInterface}
|
||||
{deltaCompFields}
|
||||
|
||||
[System.Serializable, NetSerializable]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class {stateName} : IComponentState
|
||||
{{{stateFields}
|
||||
{cloneMethod}
|
||||
}}
|
||||
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class {componentName}_AutoNetworkSystem : EntitySystem
|
||||
{{
|
||||
public override void Initialize()
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace Robust.Shared.Maths
|
||||
/// <param name="dir"></param>
|
||||
public Angle(Vector2 dir)
|
||||
{
|
||||
dir = dir.Normalized();
|
||||
Theta = Math.Atan2(dir.Y, dir.X);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
[Dependency] protected readonly MetaDataSystem MetadataSys = default!;
|
||||
[Dependency] protected readonly SharedTransformSystem XformSystem = default!;
|
||||
|
||||
private const float AudioDespawnBuffer = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Default max range at which the sound can be heard.
|
||||
/// </summary>
|
||||
@@ -234,7 +236,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
{
|
||||
var timed = EnsureComp<TimedDespawnComponent>(entity.Value);
|
||||
var audioLength = GetAudioLength(component.FileName);
|
||||
timed.Lifetime = (float) audioLength.TotalSeconds + 0.01f;
|
||||
timed.Lifetime = (float) audioLength.TotalSeconds + AudioDespawnBuffer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -322,7 +324,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
|
||||
var despawn = AddComp<TimedDespawnComponent>(uid);
|
||||
// Don't want to clip audio too short due to imprecision.
|
||||
despawn.Lifetime = (float) length.Value.TotalSeconds + 0.01f;
|
||||
despawn.Lifetime = (float) length.Value.TotalSeconds + AudioDespawnBuffer;
|
||||
}
|
||||
|
||||
if (comp.Params.Variation != null && comp.Params.Variation.Value != 0f)
|
||||
|
||||
@@ -1349,10 +1349,10 @@ namespace Robust.Shared
|
||||
/// MaxLinVelocity is compared to the dot product of linearVelocity * frameTime.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Default is 35 m/s. Around half a tile per tick at 60 ticks per second.
|
||||
/// Default is 400 m/s in-line with Box2c. Box2d used 120m/s.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<float> MaxLinVelocity =
|
||||
CVarDef.Create("physics.maxlinvelocity", 35f, CVar.SERVER | CVar.REPLICATED);
|
||||
CVarDef.Create("physics.maxlinvelocity", 400f, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum angular velocity in full rotations per second.
|
||||
@@ -1364,7 +1364,6 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> MaxAngVelocity =
|
||||
CVarDef.Create("physics.maxangvelocity", 15f);
|
||||
|
||||
|
||||
/*
|
||||
* User interface
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,58 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Configuration
|
||||
{
|
||||
public static class CVarCommandUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a string into an object of the given type.
|
||||
/// </summary>
|
||||
/// <exception cref="FormatException">Thrown if the string could not be parsed into the given type.</exception>
|
||||
/// <exception cref="NotSupportedException">Thrown if the type is not supported.</exception>
|
||||
public static object ParseObject(Type type, string input)
|
||||
{
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
if (bool.TryParse(input, out var val))
|
||||
return val;
|
||||
|
||||
if (Parse.TryInt32(input, out var intVal))
|
||||
{
|
||||
if (intVal == 0) return false;
|
||||
if (intVal == 1) return true;
|
||||
}
|
||||
|
||||
throw new FormatException($"Could not parse bool value: {input}");
|
||||
}
|
||||
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
if (type == typeof(int))
|
||||
{
|
||||
return Parse.Int32(input);
|
||||
}
|
||||
|
||||
if (type == typeof(float))
|
||||
{
|
||||
return Parse.Float(input);
|
||||
}
|
||||
|
||||
if (type == typeof(long))
|
||||
{
|
||||
return long.Parse(input);
|
||||
}
|
||||
|
||||
if (type == typeof(ushort))
|
||||
{
|
||||
return ushort.Parse(input);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
internal sealed class CVarCommand : LocalizedCommands
|
||||
{
|
||||
@@ -51,7 +103,7 @@ namespace Robust.Shared.Configuration
|
||||
var type = _cfg.GetCVarType(name);
|
||||
try
|
||||
{
|
||||
var parsed = ParseObject(type, value);
|
||||
var parsed = CVarCommandUtil.ParseObject(type, value);
|
||||
_cfg.SetCVar(name, parsed);
|
||||
}
|
||||
catch (FormatException)
|
||||
@@ -95,50 +147,6 @@ namespace Robust.Shared.Configuration
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static object ParseObject(Type type, string input)
|
||||
{
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
if (bool.TryParse(input, out var val))
|
||||
return val;
|
||||
|
||||
if (Parse.TryInt32(input, out var intVal))
|
||||
{
|
||||
if (intVal == 0) return false;
|
||||
if (intVal == 1) return true;
|
||||
}
|
||||
|
||||
throw new FormatException($"Could not parse bool value: {input}");
|
||||
}
|
||||
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
if (type == typeof(int))
|
||||
{
|
||||
return Parse.Int32(input);
|
||||
}
|
||||
|
||||
if (type == typeof(float))
|
||||
{
|
||||
return Parse.Float(input);
|
||||
}
|
||||
|
||||
if (type == typeof(long))
|
||||
{
|
||||
return long.Parse(input);
|
||||
}
|
||||
|
||||
if (type == typeof(ushort))
|
||||
{
|
||||
return ushort.Parse(input);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CVarSubsCommand : LocalizedCommands
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class CommandBuffer
|
||||
_commandBuffer.AddFirst(command);
|
||||
}
|
||||
|
||||
public void Tick(byte tickRate)
|
||||
public void Tick(ushort tickRate)
|
||||
{
|
||||
_tickrate = tickRate;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console;
|
||||
|
||||
@@ -74,4 +75,14 @@ public enum CompletionOptionFlags
|
||||
/// (instead of adding a space to go to the next one).
|
||||
/// </summary>
|
||||
PartialCompletion = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Prevents suggestions containing spaces from being automatically wrapped in quotes.
|
||||
/// </summary>
|
||||
NoQuote = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Prevents suggestions from being escaped using <see cref="CommandParsing.Escape"/>.
|
||||
/// </summary>
|
||||
NoEscape = 1 << 2,
|
||||
}
|
||||
|
||||
@@ -197,6 +197,10 @@ public abstract partial class SharedContainerSystem
|
||||
{
|
||||
if (entity.Comp2 is { } physics)
|
||||
{
|
||||
// TODO CONTAINER
|
||||
// Is this actually needed?
|
||||
// I.e., shouldn't this just do a if (_timing.ApplyingState) return
|
||||
|
||||
// Here we intentionally don't dirty the physics comp. Client-side state handling will apply these same
|
||||
// changes. This also ensures that the server doesn't have to send the physics comp state to every
|
||||
// player for any entity inside of a container during init.
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
internal string GetPath(ResPath relPath)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()));
|
||||
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,7 +14,11 @@ namespace Robust.Shared.ContentPack
|
||||
/// The directory to use for user data.
|
||||
/// If null, a virtual temporary file system is used instead.
|
||||
/// </param>
|
||||
void Initialize(string? userData);
|
||||
/// <param name="hideUserDataDir">
|
||||
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
|
||||
/// <see cref="IResourceManager.UserData"/>.
|
||||
/// </param>
|
||||
void Initialize(string? userData, bool hideUserDataDir);
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a single stream as a content file. Useful for unit testing.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// The root path of this provider.
|
||||
/// Can be null if it's a virtual provider.
|
||||
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
|
||||
/// </summary>
|
||||
string? RootDir { get; }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
{
|
||||
@@ -63,5 +64,27 @@ namespace Robust.Shared.ContentPack
|
||||
!OperatingSystem.IsWindows()
|
||||
&& !OperatingSystem.IsMacOS();
|
||||
|
||||
|
||||
internal static string SafeGetResourcePath(string baseDir, ResPath path)
|
||||
{
|
||||
var relSysPath = path.ToRelativeSystemPath();
|
||||
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
|
||||
// better safe than sorry check
|
||||
if (!retPath.StartsWith(baseDir))
|
||||
{
|
||||
// Allow path to match if it's just missing the directory separator at the end.
|
||||
if (retPath != baseDir.TrimEnd(Path.DirectorySeparatorChar))
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return retPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
|
||||
public IWritableDirProvider UserData { get; private set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Initialize(string? userData)
|
||||
public virtual void Initialize(string? userData, bool hideRootDir)
|
||||
{
|
||||
Sawmill = _logManager.GetSawmill("res");
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -379,7 +379,13 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
if (root is DirLoader loader)
|
||||
{
|
||||
yield return new ResPath(loader.GetPath(new ResPath(@"/")));
|
||||
var rootDir = loader.GetPath(new ResPath(@"/"));
|
||||
|
||||
// TODO: GET RID OF THIS.
|
||||
// This code shouldn't be passing OS disk paths through ResPath.
|
||||
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
|
||||
|
||||
yield return new ResPath(rootDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,6 +467,8 @@ Types:
|
||||
IComponent: { All: True }
|
||||
IContainer: { All: True }
|
||||
ITypeDescriptorContext: { All: True }
|
||||
EditorBrowsableAttribute: { All: True }
|
||||
EditorBrowsableState: { All: True }
|
||||
System.Diagnostics.CodeAnalysis:
|
||||
AllowNullAttribute: { All: True }
|
||||
DisallowNullAttribute: { All: True }
|
||||
|
||||
@@ -10,17 +10,22 @@ namespace Robust.Shared.ContentPack
|
||||
/// <inheritdoc />
|
||||
internal sealed class WritableDirProvider : IWritableDirProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
private readonly bool _hideRootDir;
|
||||
|
||||
public string RootDir { get; }
|
||||
|
||||
string? IWritableDirProvider.RootDir => _hideRootDir ? null : RootDir;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="WritableDirProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="rootDir">Root file system directory to allow writing.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir)
|
||||
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
|
||||
{
|
||||
// FullName does not have a trailing separator, and we MUST have a separator.
|
||||
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
|
||||
_hideRootDir = hideRootDir;
|
||||
}
|
||||
|
||||
#region File Access
|
||||
@@ -119,7 +124,7 @@ namespace Robust.Shared.ContentPack
|
||||
throw new FileNotFoundException();
|
||||
|
||||
var dirInfo = new DirectoryInfo(GetFullPath(path));
|
||||
return new WritableDirProvider(dirInfo);
|
||||
return new WritableDirProvider(dirInfo, _hideRootDir);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -180,20 +185,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
path = path.Clean();
|
||||
|
||||
return GetFullPath(RootDir, path);
|
||||
}
|
||||
|
||||
private static string GetFullPath(string root, ResPath path)
|
||||
{
|
||||
var relPath = path.ToRelativeSystemPath();
|
||||
if (relPath.Contains("\\..") || relPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return Path.GetFullPath(Path.Combine(root, relPath));
|
||||
return PathHelpers.SafeGetResourcePath(RootDir, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,11 @@ namespace Robust.Shared.Enums
|
||||
/// <summary>
|
||||
/// Overlay will be rendered below grids, entities, and everything else. In world space.
|
||||
/// </summary>
|
||||
WorldSpaceBelowWorld = 1 << 8
|
||||
WorldSpaceBelowWorld = 1 << 8,
|
||||
|
||||
/// <summary>
|
||||
/// Called after GLClear but before FOV applied to the lighting buffer.
|
||||
/// </summary>
|
||||
BeforeLighting = 1 << 9,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,17 +40,17 @@ public interface IComponentDeltaState<TState> : IComponentDeltaState where TStat
|
||||
|
||||
void IComponentDeltaState.ApplyToFullState(IComponentState fullState)
|
||||
{
|
||||
if (fullState is TState state)
|
||||
ApplyToFullState(state);
|
||||
else
|
||||
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
|
||||
if (fullState is not TState state)
|
||||
throw new Exception($"Unexpected type. Expected {typeof(TState).Name} but got {fullState.GetType().Name}");
|
||||
|
||||
ApplyToFullState(state);
|
||||
}
|
||||
|
||||
IComponentState IComponentDeltaState.CreateNewFullState(IComponentState fullState)
|
||||
{
|
||||
if (fullState is TState state)
|
||||
return CreateNewFullState(state);
|
||||
else
|
||||
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
|
||||
if (fullState is not TState state)
|
||||
throw new Exception($"Unexpected type. Expected {typeof(TState).Name} but got {fullState.GetType().Name}");
|
||||
|
||||
return CreateNewFullState(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,12 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public abstract class BoundUserInterface : IDisposable
|
||||
{
|
||||
[Dependency] protected readonly IEntityManager EntMan = default!;
|
||||
[Dependency] protected internal readonly IEntityManager EntMan = default!;
|
||||
[Dependency] protected readonly ISharedPlayerManager PlayerManager = default!;
|
||||
protected readonly SharedUserInterfaceSystem UiSystem;
|
||||
|
||||
public bool IsOpened { get; protected set; }
|
||||
|
||||
public readonly Enum UiKey;
|
||||
public EntityUid Owner { get; }
|
||||
|
||||
@@ -28,13 +30,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
protected internal BoundUserInterfaceState? State { get; internal set; }
|
||||
|
||||
// Bandaid just for storage :)
|
||||
/// <summary>
|
||||
/// Defers state handling
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public virtual bool DeferredClose { get; } = true;
|
||||
|
||||
protected BoundUserInterface(EntityUid owner, Enum uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -48,8 +43,13 @@ namespace Robust.Shared.GameObjects
|
||||
/// Invoked when the UI is opened.
|
||||
/// Do all creation and opening of things like windows in here.
|
||||
/// </summary>
|
||||
[MustCallBase]
|
||||
protected internal virtual void Open()
|
||||
{
|
||||
if (IsOpened)
|
||||
return;
|
||||
|
||||
IsOpened = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -100,6 +100,10 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (!IsOpened)
|
||||
return;
|
||||
|
||||
IsOpened = false;
|
||||
UiSystem.CloseUi(Owner, UiKey, PlayerManager.LocalEntity, predicted: true);
|
||||
}
|
||||
|
||||
|
||||
104
Robust.Shared/GameObjects/EntityExt.cs
Normal file
104
Robust.Shared/GameObjects/EntityExt.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public static class EntityExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an Entity{T} to Entity{T?}, making it compatible with methods that take nullable components.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The component type. Must implement IComponent.</typeparam>
|
||||
/// <param name="ent">The source entity to convert.</param>
|
||||
/// <returns>An Entity{T?} with the same owner and component as the source entity.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Instead of:
|
||||
/// Entity{MyComponent?} nullable = (ent, ent.Comp);
|
||||
///
|
||||
/// // You can write:
|
||||
/// Entity{MyComponent?} nullable = ent.AsNullable();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static Entity<T?> AsNullable<T>(this Entity<T> ent) where T : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?> AsNullable<T1, T2>(this Entity<T1, T2> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?> AsNullable<T1, T2, T3>(this Entity<T1, T2, T3> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?, T4?> AsNullable<T1, T2, T3, T4>(this Entity<T1, T2, T3, T4> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
where T4 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?, T4?, T5?> AsNullable<T1, T2, T3, T4, T5>(this Entity<T1, T2, T3, T4, T5> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
where T4 : IComponent
|
||||
where T5 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?, T4?, T5?, T6?> AsNullable<T1, T2, T3, T4, T5, T6>(
|
||||
this Entity<T1, T2, T3, T4, T5, T6> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
where T4 : IComponent
|
||||
where T5 : IComponent
|
||||
where T6 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5, ent.Comp6);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?> AsNullable<T1, T2, T3, T4, T5, T6, T7>(
|
||||
this Entity<T1, T2, T3, T4, T5, T6, T7> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
where T4 : IComponent
|
||||
where T5 : IComponent
|
||||
where T6 : IComponent
|
||||
where T7 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5, ent.Comp6, ent.Comp7);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AsNullable{T}" />
|
||||
public static Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?, T8?> AsNullable<T1, T2, T3, T4, T5, T6, T7, T8>(
|
||||
this Entity<T1, T2, T3, T4, T5, T6, T7, T8> ent)
|
||||
where T1 : IComponent
|
||||
where T2 : IComponent
|
||||
where T3 : IComponent
|
||||
where T4 : IComponent
|
||||
where T5 : IComponent
|
||||
where T6 : IComponent
|
||||
where T7 : IComponent
|
||||
where T8 : IComponent
|
||||
{
|
||||
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5, ent.Comp6, ent.Comp7, ent.Comp8);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
@@ -38,11 +39,14 @@ public abstract partial class EntityManager
|
||||
Dirty(uid, comp, metadata);
|
||||
}
|
||||
|
||||
public void DirtyField<T>(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null)
|
||||
public virtual void DirtyField<T>(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
var compReg = ComponentFactory.GetRegistration(CompIdx.Index<T>());
|
||||
|
||||
// TODO
|
||||
// consider storing this on MetaDataComponent?
|
||||
// We alsready store other dirtying information there anyways, and avoids having to fetch the registration.
|
||||
if (!compReg.NetworkedFieldLookup.TryGetValue(fieldName, out var idx))
|
||||
{
|
||||
_sawmill.Error($"Tried to dirty delta field {fieldName} on {ToPrettyString(uid)} that isn't implemented.");
|
||||
@@ -54,6 +58,24 @@ public abstract partial class EntityManager
|
||||
comp.LastModifiedFields[idx] = curTick;
|
||||
Dirty(uid, comp, metadata);
|
||||
}
|
||||
|
||||
public virtual void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
var compReg = ComponentFactory.GetRegistration(CompIdx.Index<T>());
|
||||
|
||||
var curTick = _gameTiming.CurTick;
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (!compReg.NetworkedFieldLookup.TryGetValue(field, out var idx))
|
||||
_sawmill.Error($"Tried to dirty delta field {field} on {ToPrettyString(uid)} that isn't implemented.");
|
||||
else
|
||||
comp.LastModifiedFields[idx] = curTick;
|
||||
}
|
||||
|
||||
comp.LastFieldUpdate = curTick;
|
||||
Dirty(uid, comp, meta);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -197,17 +197,23 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var reg = _componentFactory.GetRegistration(name);
|
||||
|
||||
if (HasComponent(target, reg.Type))
|
||||
if (removeExisting)
|
||||
{
|
||||
if (!removeExisting)
|
||||
continue;
|
||||
|
||||
RemoveComponent(target, reg.Type, metadata);
|
||||
var comp = _componentFactory.GetComponent(reg);
|
||||
_serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true);
|
||||
AddComponentInternal(target, comp, reg, overwrite: true, metadata: metadata);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HasComponent(target, reg))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var comp = _componentFactory.GetComponent(reg);
|
||||
_serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true);
|
||||
AddComponent(target, comp, metadata: metadata);
|
||||
var comp = _componentFactory.GetComponent(reg);
|
||||
_serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true);
|
||||
AddComponentInternal(target, comp, reg, overwrite: false, metadata: metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +321,22 @@ namespace Robust.Shared.GameObjects
|
||||
AddComponentInternal(uid, component, overwrite, false, metadata);
|
||||
}
|
||||
|
||||
private void AddComponentInternal<T>(
|
||||
EntityUid uid,
|
||||
T component,
|
||||
ComponentRegistration compReg,
|
||||
bool overwrite = false,
|
||||
MetaDataComponent? metadata = null) where T : IComponent
|
||||
{
|
||||
if (!MetaQuery.Resolve(uid, ref metadata, false))
|
||||
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
|
||||
|
||||
DebugTools.Assert(component.Owner == default);
|
||||
component.Owner = uid;
|
||||
|
||||
AddComponentInternal(uid, component, compReg, overwrite, skipInit: false, metadata);
|
||||
}
|
||||
|
||||
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata) where T : IComponent
|
||||
{
|
||||
if (!MetaQuery.ResolveInternal(uid, ref metadata, false))
|
||||
@@ -731,6 +753,14 @@ namespace Robust.Shared.GameObjects
|
||||
return uid.HasValue && HasComponent<T>(uid.Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public bool HasComponent(EntityUid uid, ComponentRegistration reg)
|
||||
{
|
||||
var dict = _entTraitArray[reg.Idx.Value];
|
||||
return dict.TryGetValue(uid, out var comp) && !comp.Deleted;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
@@ -943,6 +973,23 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetComponent(EntityUid uid, ComponentRegistration reg, [NotNullWhen(true)] out IComponent? component)
|
||||
{
|
||||
var dict = _entTraitArray[reg.Idx.Value];
|
||||
if (dict.TryGetValue(uid, out var comp))
|
||||
{
|
||||
if (!comp.Deleted)
|
||||
{
|
||||
component = comp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
component = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetComponent(EntityUid uid, Type type, [NotNullWhen(true)] out IComponent? component)
|
||||
{
|
||||
@@ -1694,7 +1741,6 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true)
|
||||
{
|
||||
if (component != null)
|
||||
@@ -1717,7 +1763,7 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Resolve(ref Entity<TComp1?> entity, bool logMissing = true)
|
||||
{
|
||||
return Resolve(entity.Owner, ref entity.Comp, logMissing);
|
||||
|
||||
@@ -171,6 +171,13 @@ public partial class EntitySystem
|
||||
EntityManager.DirtyField(uid, component, fieldName, meta);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
EntityManager.DirtyFields(uid, comp, meta);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a component as dirty. This also implicitly dirties the entity this component belongs to.
|
||||
/// </summary>
|
||||
|
||||
@@ -176,6 +176,14 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>True if the entity has the component type, otherwise false.</returns>
|
||||
bool HasComponent<T>([NotNullWhen(true)] EntityUid? uid) where T : IComponent;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has a component type.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to check.</param>
|
||||
/// <param name="reg">The component registration to check for.</param>
|
||||
/// <returns>True if the entity has the component type, otherwise false.</returns>
|
||||
bool HasComponent(EntityUid uid, ComponentRegistration reg);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has a component type.
|
||||
/// </summary>
|
||||
@@ -294,6 +302,15 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>If the component existed in the entity.</returns>
|
||||
bool TryGetComponent<T>([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? component) where T : IComponent?;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to check.</param>
|
||||
/// <param name="reg">The component registration to check for.</param>
|
||||
/// <param name="component">Component of the specified type (if exists).</param>
|
||||
/// <returns>If the component existed in the entity.</returns>
|
||||
bool TryGetComponent(EntityUid uid, ComponentRegistration reg, [NotNullWhen(true)] out IComponent? component);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
/// </summary>
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public void SetEnabled(EntityUid uid, bool enabled, CollisionWakeComponent? component = null)
|
||||
{
|
||||
if (!_query.Resolve(uid, ref component) || component.Enabled == enabled)
|
||||
if (!_query.Resolve(uid, ref component, false) || component.Enabled == enabled)
|
||||
return;
|
||||
|
||||
component.Enabled = enabled;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -11,7 +9,6 @@ using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Shapes;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -77,14 +74,14 @@ public sealed partial class EntityLookupSystem
|
||||
/// <summary>
|
||||
/// Wrapper around the per-grid version.
|
||||
/// </summary>
|
||||
private void AddEntitiesIntersecting(MapId mapId,
|
||||
private void AddEntitiesIntersecting<T>(MapId mapId,
|
||||
HashSet<EntityUid> intersecting,
|
||||
IPhysShape shape,
|
||||
T shape,
|
||||
Transform shapeTransform,
|
||||
LookupFlags flags)
|
||||
LookupFlags flags) where T : IPhysShape
|
||||
{
|
||||
var worldAABB = shape.ComputeAABB(shapeTransform, 0);
|
||||
var state = new EntityQueryState(intersecting,
|
||||
var state = new EntityQueryState<T>(intersecting,
|
||||
shape,
|
||||
shapeTransform,
|
||||
_fixtures,
|
||||
@@ -96,7 +93,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
// Need to include maps
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid uid, MapGridComponent _, ref EntityQueryState state) =>
|
||||
static (EntityUid uid, MapGridComponent _, ref EntityQueryState<T> state) =>
|
||||
{
|
||||
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid);
|
||||
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
|
||||
@@ -112,19 +109,19 @@ public sealed partial class EntityLookupSystem
|
||||
AddContained(intersecting, flags);
|
||||
}
|
||||
|
||||
private void AddEntitiesIntersecting(
|
||||
private void AddEntitiesIntersecting<T>(
|
||||
EntityUid lookupUid,
|
||||
HashSet<EntityUid> intersecting,
|
||||
IPhysShape shape,
|
||||
T shape,
|
||||
Box2 localAABB,
|
||||
Transform localShapeTransform,
|
||||
LookupFlags flags,
|
||||
BroadphaseComponent? lookup = null)
|
||||
BroadphaseComponent? lookup = null) where T : IPhysShape
|
||||
{
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return;
|
||||
|
||||
var state = new EntityQueryState(
|
||||
var state = new EntityQueryState<T>(
|
||||
intersecting,
|
||||
shape,
|
||||
localShapeTransform,
|
||||
@@ -157,7 +154,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
return;
|
||||
|
||||
static bool PhysicsQuery(ref EntityQueryState state, in FixtureProxy value)
|
||||
static bool PhysicsQuery(ref EntityQueryState<T> state, in FixtureProxy value)
|
||||
{
|
||||
var sensors = (state.Flags & LookupFlags.Sensors) != 0x0;
|
||||
|
||||
@@ -179,7 +176,7 @@ public sealed partial class EntityLookupSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SundriesQuery(ref EntityQueryState state, in EntityUid value)
|
||||
static bool SundriesQuery(ref EntityQueryState<T> state, in EntityUid value)
|
||||
{
|
||||
var approx = (state.Flags & LookupFlags.Approximate) != 0x0;
|
||||
|
||||
@@ -227,14 +224,14 @@ public sealed partial class EntityLookupSystem
|
||||
/// <summary>
|
||||
/// Wrapper around the per-grid version.
|
||||
/// </summary>
|
||||
private bool AnyEntitiesIntersecting(MapId mapId,
|
||||
IPhysShape shape,
|
||||
private bool AnyEntitiesIntersecting<T>(MapId mapId,
|
||||
T shape,
|
||||
Transform shapeTransform,
|
||||
LookupFlags flags,
|
||||
EntityUid? ignored = null)
|
||||
EntityUid? ignored = null) where T : IPhysShape
|
||||
{
|
||||
var worldAABB = shape.ComputeAABB(shapeTransform, 0);
|
||||
var state = new AnyEntityQueryState(false,
|
||||
var state = new AnyEntityQueryState<T>(false,
|
||||
ignored,
|
||||
shape,
|
||||
shapeTransform,
|
||||
@@ -247,7 +244,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
// Need to include maps
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid uid, MapGridComponent _, ref AnyEntityQueryState state) =>
|
||||
static (EntityUid uid, MapGridComponent _, ref AnyEntityQueryState<T> state) =>
|
||||
{
|
||||
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid);
|
||||
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
|
||||
@@ -272,18 +269,18 @@ public sealed partial class EntityLookupSystem
|
||||
return state.Found;
|
||||
}
|
||||
|
||||
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
|
||||
IPhysShape shape,
|
||||
private bool AnyEntitiesIntersecting<T>(EntityUid lookupUid,
|
||||
T shape,
|
||||
Box2 localAABB,
|
||||
Transform shapeTransform,
|
||||
LookupFlags flags,
|
||||
EntityUid? ignored = null,
|
||||
BroadphaseComponent? lookup = null)
|
||||
BroadphaseComponent? lookup = null) where T : IPhysShape
|
||||
{
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return false;
|
||||
|
||||
var state = new AnyEntityQueryState(false,
|
||||
var state = new AnyEntityQueryState<T>(false,
|
||||
ignored,
|
||||
shape,
|
||||
shapeTransform,
|
||||
@@ -325,7 +322,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
return state.Found;
|
||||
|
||||
static bool PhysicsQuery(ref AnyEntityQueryState state, in FixtureProxy value)
|
||||
static bool PhysicsQuery(ref AnyEntityQueryState<T> state, in FixtureProxy value)
|
||||
{
|
||||
if (state.Ignored == value.Entity)
|
||||
return true;
|
||||
@@ -350,7 +347,7 @@ public sealed partial class EntityLookupSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool SundriesQuery(ref AnyEntityQueryState state, in EntityUid value)
|
||||
static bool SundriesQuery(ref AnyEntityQueryState<T> state, in EntityUid value)
|
||||
{
|
||||
if (state.Ignored == value)
|
||||
return true;
|
||||
@@ -409,9 +406,16 @@ public sealed partial class EntityLookupSystem
|
||||
var broadphaseInv = _transform.GetInvWorldMatrix(lookupUid);
|
||||
|
||||
var localBounds = broadphaseInv.TransformBounds(worldBounds);
|
||||
var shape = new Polygon(localBounds);
|
||||
var polygon = _physics.GetPooled(localBounds);
|
||||
var result = AnyEntitiesIntersecting(lookupUid,
|
||||
polygon,
|
||||
localBounds.CalcBoundingBox(),
|
||||
Physics.Transform.Empty,
|
||||
flags,
|
||||
ignored);
|
||||
|
||||
return AnyEntitiesIntersecting(lookupUid, shape, localBounds.CalcBoundingBox(), Physics.Transform.Empty, flags, ignored);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -454,8 +458,10 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return false;
|
||||
|
||||
var shape = new Polygon(worldAABB);
|
||||
return AnyEntitiesIntersecting(mapId, shape, Physics.Transform.Empty, flags);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var result = AnyEntitiesIntersecting(mapId, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
|
||||
@@ -469,8 +475,9 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
var shape = new Polygon(worldAABB);
|
||||
AddEntitiesIntersecting(mapId, intersecting, shape, Physics.Transform.Empty, flags);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
AddEntitiesIntersecting(mapId, intersecting, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -480,15 +487,18 @@ public sealed partial class EntityLookupSystem
|
||||
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
// Don't need to check contained entities as they have the same bounds as the parent.
|
||||
var shape = new Polygon(worldBounds);
|
||||
return AnyEntitiesIntersecting(mapId, shape, Physics.Transform.Empty, flags);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
var result = AnyEntitiesIntersecting(mapId, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
var shape = new Polygon(worldBounds);
|
||||
AddEntitiesIntersecting(mapId, intersecting, shape, Physics.Transform.Empty, flags);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
AddEntitiesIntersecting(mapId, intersecting, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return intersecting;
|
||||
}
|
||||
|
||||
@@ -703,12 +713,12 @@ public sealed partial class EntityLookupSystem
|
||||
return entities;
|
||||
}
|
||||
|
||||
public void GetEntitiesIntersecting(
|
||||
public void GetEntitiesIntersecting<T>(
|
||||
MapId mapId,
|
||||
IPhysShape shape,
|
||||
T shape,
|
||||
Transform transform,
|
||||
HashSet<EntityUid> entities,
|
||||
LookupFlags flags = LookupFlags.All)
|
||||
LookupFlags flags = LookupFlags.All) where T : IPhysShape
|
||||
{
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
@@ -751,10 +761,11 @@ public sealed partial class EntityLookupSystem
|
||||
return;
|
||||
|
||||
var localAABB = _transform.GetInvWorldMatrix(gridId).TransformBox(worldAABB);
|
||||
var shape = new Polygon(localAABB);
|
||||
var polygon = _physics.GetPooled(localAABB);
|
||||
|
||||
AddEntitiesIntersecting(gridId, intersecting, shape, localAABB, Physics.Transform.Empty, flags, lookup);
|
||||
AddEntitiesIntersecting(gridId, intersecting, polygon, localAABB, Physics.Transform.Empty, flags, lookup);
|
||||
AddContained(intersecting, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void GetEntitiesIntersecting(EntityUid gridId, Box2Rotated worldBounds, HashSet<EntityUid> intersecting, LookupFlags flags = DefaultFlags)
|
||||
@@ -763,10 +774,11 @@ public sealed partial class EntityLookupSystem
|
||||
return;
|
||||
|
||||
var localBounds = _transform.GetInvWorldMatrix(gridId).TransformBounds(worldBounds);
|
||||
var shape = new Polygon(localBounds);
|
||||
var polygon = _physics.GetPooled(localBounds);
|
||||
|
||||
AddEntitiesIntersecting(gridId, intersecting, shape, localBounds.CalcBoundingBox(), Physics.Transform.Empty, flags, lookup);
|
||||
AddEntitiesIntersecting(gridId, intersecting, polygon, localBounds.CalcBoundingBox(), Physics.Transform.Empty, flags, lookup);
|
||||
AddContained(intersecting, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -832,10 +844,10 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
#endregion
|
||||
|
||||
private record struct AnyEntityQueryState(
|
||||
private record struct AnyEntityQueryState<T>(
|
||||
bool Found,
|
||||
EntityUid? Ignored,
|
||||
IPhysShape Shape,
|
||||
T Shape,
|
||||
Transform Transform,
|
||||
FixtureSystem Fixtures,
|
||||
EntityLookupSystem Lookup,
|
||||
@@ -843,11 +855,11 @@ public sealed partial class EntityLookupSystem
|
||||
IManifoldManager Manifolds,
|
||||
EntityQuery<FixturesComponent> FixturesQuery,
|
||||
LookupFlags Flags
|
||||
);
|
||||
) where T : IPhysShape;
|
||||
|
||||
private readonly record struct EntityQueryState(
|
||||
private readonly record struct EntityQueryState<T>(
|
||||
HashSet<EntityUid> Intersecting,
|
||||
IPhysShape Shape,
|
||||
T Shape,
|
||||
Transform Transform,
|
||||
FixtureSystem Fixtures,
|
||||
EntityLookupSystem Lookup,
|
||||
@@ -855,5 +867,5 @@ public sealed partial class EntityLookupSystem
|
||||
IManifoldManager Manifolds,
|
||||
EntityQuery<FixturesComponent> FixturesQuery,
|
||||
LookupFlags Flags
|
||||
);
|
||||
) where T : IPhysShape;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public sealed partial class EntityLookupSystem
|
||||
/// <summary>
|
||||
/// Common method to determine if an entity overlaps the specified shape.
|
||||
/// </summary>
|
||||
private bool IsIntersecting(MapId mapId, EntityUid uid, TransformComponent xform, IPhysShape shape, Transform shapeTransform, Box2 worldAABB, LookupFlags flags)
|
||||
private bool IsIntersecting<TShape>(MapId mapId, EntityUid uid, TransformComponent xform, TShape shape, Transform shapeTransform, Box2 worldAABB, LookupFlags flags) where TShape : IPhysShape
|
||||
{
|
||||
var (entPos, entRot) = _transform.GetWorldPositionRotation(xform);
|
||||
|
||||
@@ -121,25 +121,27 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return;
|
||||
|
||||
var lookupPoly = new Polygon(localAABB);
|
||||
|
||||
AddEntitiesIntersecting(lookupUid, intersecting, lookupPoly, localAABB, Physics.Transform.Empty, flags, query, lookup);
|
||||
var polygon = _physics.GetPooled(localAABB);
|
||||
AddEntitiesIntersecting(lookupUid, intersecting, polygon, localAABB, Physics.Transform.Empty, flags, query, lookup);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
private void AddEntitiesIntersecting<T>(
|
||||
private void AddEntitiesIntersecting<T, TShape>(
|
||||
EntityUid lookupUid,
|
||||
HashSet<Entity<T>> intersecting,
|
||||
IPhysShape shape,
|
||||
TShape shape,
|
||||
Box2 localAABB,
|
||||
Transform localTransform,
|
||||
LookupFlags flags,
|
||||
EntityQuery<T> query,
|
||||
BroadphaseComponent? lookup = null) where T : IComponent
|
||||
BroadphaseComponent? lookup = null)
|
||||
where T : IComponent
|
||||
where TShape : IPhysShape
|
||||
{
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return;
|
||||
|
||||
var state = new QueryState<T>(
|
||||
var state = new QueryState<T, TShape>(
|
||||
intersecting,
|
||||
shape,
|
||||
localTransform,
|
||||
@@ -174,7 +176,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
return;
|
||||
|
||||
static bool PhysicsQuery(ref QueryState<T> state, in FixtureProxy value)
|
||||
static bool PhysicsQuery(ref QueryState<T, TShape> state, in FixtureProxy value)
|
||||
{
|
||||
if (!state.Sensors && !value.Fixture.Hard)
|
||||
return true;
|
||||
@@ -195,7 +197,7 @@ public sealed partial class EntityLookupSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SundriesQuery(ref QueryState<T> state, in EntityUid value)
|
||||
static bool SundriesQuery(ref QueryState<T, TShape> state, in EntityUid value)
|
||||
{
|
||||
if (!state.Query.TryGetComponent(value, out var comp))
|
||||
return true;
|
||||
@@ -250,22 +252,26 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return false;
|
||||
|
||||
var shape = new Polygon(localAABB);
|
||||
var polygon = _physics.GetPooled(localAABB);
|
||||
var (lookupPos, lookupRot) = _transform.GetWorldPositionRotation(lookupUid);
|
||||
var transform = new Transform(lookupPos, lookupRot);
|
||||
var result = AnyComponentsIntersecting(lookupUid, polygon, localAABB, transform, flags, query, ignored, lookup);
|
||||
_physics.ReturnPooled(polygon);
|
||||
|
||||
return AnyComponentsIntersecting(lookupUid, shape, localAABB, transform, flags, query, ignored, lookup);
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool AnyComponentsIntersecting<T>(
|
||||
private bool AnyComponentsIntersecting<T, TShape>(
|
||||
EntityUid lookupUid,
|
||||
IPhysShape shape,
|
||||
TShape shape,
|
||||
Box2 localAABB,
|
||||
Transform shapeTransform,
|
||||
LookupFlags flags,
|
||||
EntityQuery<T> query,
|
||||
EntityUid? ignored = null,
|
||||
BroadphaseComponent? lookup = null) where T : IComponent
|
||||
BroadphaseComponent? lookup = null)
|
||||
where T : IComponent
|
||||
where TShape : IPhysShape
|
||||
{
|
||||
/*
|
||||
* Unfortunately this is split from the other query as we can short-circuit here, hence the code duplication.
|
||||
@@ -275,7 +281,7 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return false;
|
||||
|
||||
var state = new AnyQueryState<T>(false,
|
||||
var state = new AnyQueryState<T, TShape>(false,
|
||||
ignored,
|
||||
shape,
|
||||
shapeTransform,
|
||||
@@ -317,7 +323,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
return state.Found;
|
||||
|
||||
static bool PhysicsQuery(ref AnyQueryState<T> state, in FixtureProxy value)
|
||||
static bool PhysicsQuery(ref AnyQueryState<T, TShape> state, in FixtureProxy value)
|
||||
{
|
||||
if (value.Entity == state.Ignored)
|
||||
return true;
|
||||
@@ -342,7 +348,7 @@ public sealed partial class EntityLookupSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool SundriesQuery(ref AnyQueryState<T> state, in EntityUid value)
|
||||
static bool SundriesQuery(ref AnyQueryState<T, TShape> state, in EntityUid value)
|
||||
{
|
||||
if (state.Ignored == value)
|
||||
return true;
|
||||
@@ -421,13 +427,14 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
public bool AnyComponentsIntersecting(Type type, MapId mapId, Box2 worldAABB, EntityUid? ignored = null, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var shape = new Polygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var transform = Physics.Transform.Empty;
|
||||
|
||||
return AnyComponentsIntersecting(type, mapId, shape, transform, ignored, flags);
|
||||
var result = AnyComponentsIntersecting(type, mapId, polygon, transform, ignored, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool AnyComponentsIntersecting(Type type, MapId mapId, IPhysShape shape, Transform shapeTransform, EntityUid? ignored = null, LookupFlags flags = DefaultFlags)
|
||||
public bool AnyComponentsIntersecting<T>(Type type, MapId mapId, T shape, Transform shapeTransform, EntityUid? ignored = null, LookupFlags flags = DefaultFlags) where T : IPhysShape
|
||||
{
|
||||
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(type));
|
||||
if (mapId == MapId.Nullspace)
|
||||
@@ -489,37 +496,40 @@ public sealed partial class EntityLookupSystem
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var shape = new Polygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var transform = Physics.Transform.Empty;
|
||||
|
||||
GetEntitiesIntersecting(type, mapId, shape, transform, intersecting, flags);
|
||||
GetEntitiesIntersecting(type, mapId, polygon, transform, intersecting, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void GetEntitiesIntersecting<T>(MapId mapId, Box2Rotated worldBounds, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
var shape = new Polygon(worldBounds);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
var shapeTransform = Physics.Transform.Empty;
|
||||
|
||||
GetEntitiesIntersecting(mapId, shape, shapeTransform, entities, flags);
|
||||
GetEntitiesIntersecting(mapId, polygon, shapeTransform, entities, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void GetEntitiesIntersecting<T>(MapId mapId, Box2 worldAABB, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
var shape = new Polygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var shapeTransform = Physics.Transform.Empty;
|
||||
|
||||
GetEntitiesIntersecting(mapId, shape, shapeTransform, entities, flags);
|
||||
GetEntitiesIntersecting(mapId, polygon, shapeTransform, entities, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPhysShape
|
||||
|
||||
public void GetEntitiesIntersecting(Type type, MapId mapId, IPhysShape shape, Transform shapeTransform, HashSet<Entity<IComponent>> intersecting, LookupFlags flags = DefaultFlags)
|
||||
public void GetEntitiesIntersecting<T>(Type type, MapId mapId, T shape, Transform shapeTransform, HashSet<Entity<IComponent>> intersecting, LookupFlags flags = DefaultFlags) where T : IPhysShape
|
||||
{
|
||||
DebugTools.Assert(typeof(IComponent).IsAssignableFrom(type));
|
||||
if (mapId == MapId.Nullspace)
|
||||
@@ -544,10 +554,10 @@ public sealed partial class EntityLookupSystem
|
||||
var query = EntityManager.GetEntityQuery(type);
|
||||
|
||||
// Get grid entities
|
||||
var state = new GridQueryState<IComponent>(intersecting, shape, shapeTransform, this, _physics, flags, query);
|
||||
var state = new GridQueryState<IComponent, T>(intersecting, shape, shapeTransform, this, _physics, flags, query);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid uid, MapGridComponent grid, ref GridQueryState<IComponent> state) =>
|
||||
static (EntityUid uid, MapGridComponent grid, ref GridQueryState<IComponent, T> state) =>
|
||||
{
|
||||
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid);
|
||||
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
|
||||
@@ -565,7 +575,9 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
}
|
||||
|
||||
public void GetEntitiesIntersecting<T>(MapId mapId, IPhysShape shape, Transform shapeTransform, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
|
||||
public void GetEntitiesIntersecting<T, TShape>(MapId mapId, TShape shape, Transform shapeTransform, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags)
|
||||
where T : IComponent
|
||||
where TShape : IPhysShape
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
@@ -588,10 +600,10 @@ public sealed partial class EntityLookupSystem
|
||||
var query = GetEntityQuery<T>();
|
||||
|
||||
// Get grid entities
|
||||
var state = new GridQueryState<T>(entities, shape, shapeTransform, this, _physics, flags, query);
|
||||
var state = new GridQueryState<T, TShape>(entities, shape, shapeTransform, this, _physics, flags, query);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid uid, MapGridComponent grid, ref GridQueryState<T> state) =>
|
||||
static (EntityUid uid, MapGridComponent grid, ref GridQueryState<T, TShape> state) =>
|
||||
{
|
||||
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid);
|
||||
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
|
||||
@@ -679,7 +691,9 @@ public sealed partial class EntityLookupSystem
|
||||
GetEntitiesInRange(mapId, shape, transform, entities, flags);
|
||||
}
|
||||
|
||||
public void GetEntitiesInRange<T>(MapId mapId, IPhysShape shape, Transform transform, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
|
||||
public void GetEntitiesInRange<T, TShape>(MapId mapId, TShape shape, Transform transform, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags)
|
||||
where T : IComponent
|
||||
where TShape : IPhysShape
|
||||
{
|
||||
DebugTools.Assert(shape.Radius > 0, "Range must be a positive float");
|
||||
|
||||
@@ -829,20 +843,21 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct GridQueryState<T>(
|
||||
private readonly record struct GridQueryState<T, TShape>(
|
||||
HashSet<Entity<T>> Intersecting,
|
||||
IPhysShape Shape,
|
||||
TShape Shape,
|
||||
Transform Transform,
|
||||
EntityLookupSystem Lookup,
|
||||
SharedPhysicsSystem Physics,
|
||||
LookupFlags Flags,
|
||||
EntityQuery<T> Query
|
||||
) where T : IComponent;
|
||||
) where T : IComponent
|
||||
where TShape : IPhysShape;
|
||||
|
||||
private record struct AnyQueryState<T>(
|
||||
private record struct AnyQueryState<T, TShape>(
|
||||
bool Found,
|
||||
EntityUid? Ignored,
|
||||
IPhysShape Shape,
|
||||
TShape Shape,
|
||||
Transform Transform,
|
||||
FixtureSystem Fixtures,
|
||||
SharedPhysicsSystem Physics,
|
||||
@@ -850,11 +865,12 @@ public sealed partial class EntityLookupSystem
|
||||
EntityQuery<T> Query,
|
||||
EntityQuery<FixturesComponent> FixturesQuery,
|
||||
LookupFlags Flags
|
||||
) where T : IComponent;
|
||||
) where T : IComponent
|
||||
where TShape : IPhysShape;
|
||||
|
||||
private readonly record struct QueryState<T>(
|
||||
private readonly record struct QueryState<T, TShape>(
|
||||
HashSet<Entity<T>> Intersecting,
|
||||
IPhysShape Shape,
|
||||
TShape Shape,
|
||||
Transform Transform,
|
||||
FixtureSystem Fixtures,
|
||||
SharedPhysicsSystem Physics,
|
||||
@@ -863,5 +879,6 @@ public sealed partial class EntityLookupSystem
|
||||
EntityQuery<FixturesComponent> FixturesQuery,
|
||||
bool Sensors,
|
||||
bool Approximate
|
||||
) where T : IComponent;
|
||||
) where T : IComponent
|
||||
where TShape : IPhysShape;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,6 @@ namespace Robust.Shared.GameObjects
|
||||
newFixtures.Add(($"grid_chunk-{bounds.Left}-{bounds.Bottom}", newFixture));
|
||||
}
|
||||
|
||||
var toRemove = new ValueList<(string Id, Fixture Fixture)>();
|
||||
// Check if we even need to issue an eventbus event
|
||||
var updated = false;
|
||||
|
||||
@@ -167,6 +166,13 @@ namespace Robust.Shared.GameObjects
|
||||
for (var i = newFixtures.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var fixture = newFixtures[i].Fixture;
|
||||
|
||||
// TODO GRIDS
|
||||
// Fix this
|
||||
// This **only** works if we assume the density is always the default (PhysicsConstants.DefaultDensity).
|
||||
// Hence, this always fails in SS14 because ShuttleSystem.OnGridFixtureChange changes the density.
|
||||
// So it constantly creats & destroys fixtures unnecessarily
|
||||
// AAAAA
|
||||
if (!oldFixture.Equals(fixture))
|
||||
continue;
|
||||
|
||||
@@ -175,21 +181,16 @@ namespace Robust.Shared.GameObjects
|
||||
break;
|
||||
}
|
||||
|
||||
if (existing)
|
||||
continue;
|
||||
|
||||
// Doesn't align with any new fixtures so delete
|
||||
if (existing) continue;
|
||||
|
||||
toRemove.Add((oldId, oldFixture));
|
||||
chunk.Fixtures.Remove(oldId);
|
||||
_fixtures.DestroyFixture(uid, oldId, oldFixture, false, body: body, manager: manager, xform: xform);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
foreach (var (id, fixture) in toRemove.Span)
|
||||
{
|
||||
// TODO add a DestroyFixture() override that takes in a list.
|
||||
// reduced broadphase lookups
|
||||
chunk.Fixtures.Remove(id);
|
||||
_fixtures.DestroyFixture(uid, id, fixture, false, body: body, manager: manager, xform: xform);
|
||||
}
|
||||
|
||||
if (newFixtures.Count > 0 || toRemove.Count > 0)
|
||||
if (newFixtures.Count > 0)
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
@@ -200,10 +201,11 @@ namespace Robust.Shared.GameObjects
|
||||
chunk.Fixtures.Add(id);
|
||||
var existingFixture = _fixtures.GetFixtureOrNull(uid, id, manager: manager);
|
||||
// Check if it's the same (otherwise remove anyway).
|
||||
// TODO GRIDS
|
||||
// wasn't this already checked?
|
||||
if (existingFixture?.Shape is PolygonShape poly &&
|
||||
poly.EqualsApprox((PolygonShape) fixture.Shape))
|
||||
{
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -245,13 +245,10 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
HashSet<MapChunk> modifiedChunks;
|
||||
|
||||
switch (args.Current)
|
||||
{
|
||||
case MapGridComponentDeltaState delta:
|
||||
{
|
||||
modifiedChunks = new();
|
||||
DebugTools.Assert(component.ChunkSize == delta.ChunkSize || component.Chunks.Count == 0,
|
||||
"Can't modify chunk size of an existing grid.");
|
||||
|
||||
@@ -261,7 +258,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
foreach (var (index, chunkData) in delta.ChunkData)
|
||||
{
|
||||
ApplyChunkData(uid, component, index, chunkData, modifiedChunks);
|
||||
ApplyChunkData(uid, component, index, chunkData);
|
||||
}
|
||||
|
||||
component.LastTileModifiedTick = delta.LastTileModifiedTick;
|
||||
@@ -269,7 +266,6 @@ public abstract partial class SharedMapSystem
|
||||
}
|
||||
case MapGridComponentState state:
|
||||
{
|
||||
modifiedChunks = new();
|
||||
DebugTools.Assert(component.ChunkSize == state.ChunkSize || component.Chunks.Count == 0,
|
||||
"Can't modify chunk size of an existing grid.");
|
||||
|
||||
@@ -279,12 +275,13 @@ public abstract partial class SharedMapSystem
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
if (!state.FullGridData.ContainsKey(index))
|
||||
ApplyChunkData(uid, component, index, ChunkDatum.Empty, modifiedChunks);
|
||||
ApplyChunkData(uid, component, index, ChunkDatum.Empty);
|
||||
}
|
||||
|
||||
foreach (var (index, data) in state.FullGridData)
|
||||
{
|
||||
ApplyChunkData(uid, component, index, new(data), modifiedChunks);
|
||||
DebugTools.Assert(!data.IsDeleted());
|
||||
ApplyChunkData(uid, component, index, data);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -309,15 +306,13 @@ public abstract partial class SharedMapSystem
|
||||
EntityUid uid,
|
||||
MapGridComponent component,
|
||||
Vector2i index,
|
||||
ChunkDatum data,
|
||||
HashSet<MapChunk> modifiedChunks)
|
||||
ChunkDatum data)
|
||||
{
|
||||
bool shapeChanged = false;
|
||||
var counter = 0;
|
||||
|
||||
if (data.IsDeleted())
|
||||
{
|
||||
if (!component.Chunks.TryGetValue(index, out var deletedChunk))
|
||||
if (!component.Chunks.Remove(index, out var deletedChunk))
|
||||
return;
|
||||
|
||||
// Deleted chunks still need to raise tile-changed events.
|
||||
@@ -326,7 +321,7 @@ public abstract partial class SharedMapSystem
|
||||
{
|
||||
for (ushort y = 0; y < component.ChunkSize; y++)
|
||||
{
|
||||
if (!deletedChunk.TrySetTile(x, y, Tile.Empty, out var oldTile, out var chunkShapeChanged))
|
||||
if (!deletedChunk.TrySetTile(x, y, Tile.Empty, out var oldTile, out _))
|
||||
continue;
|
||||
|
||||
var gridIndices = deletedChunk.ChunkTileToGridTile((x, y));
|
||||
@@ -335,14 +330,13 @@ public abstract partial class SharedMapSystem
|
||||
}
|
||||
}
|
||||
|
||||
component.Chunks.Remove(index);
|
||||
|
||||
// TODO is this required?
|
||||
modifiedChunks.Add(deletedChunk);
|
||||
return;
|
||||
}
|
||||
|
||||
var chunk = GetOrAddChunk(uid, component, index);
|
||||
chunk.Fixtures.Clear();
|
||||
chunk.Fixtures.UnionWith(data.Fixtures);
|
||||
|
||||
chunk.SuppressCollisionRegeneration = true;
|
||||
DebugTools.Assert(data.TileData.Any(x => !x.IsEmpty));
|
||||
DebugTools.Assert(data.TileData.Length == component.ChunkSize * component.ChunkSize);
|
||||
@@ -351,30 +345,20 @@ public abstract partial class SharedMapSystem
|
||||
for (ushort y = 0; y < component.ChunkSize; y++)
|
||||
{
|
||||
var tile = data.TileData[counter++];
|
||||
if (!chunk.TrySetTile(x, y, tile, out var oldTile, out var tileShapeChanged))
|
||||
if (!chunk.TrySetTile(x, y, tile, out var oldTile, out _))
|
||||
continue;
|
||||
|
||||
shapeChanged |= tileShapeChanged;
|
||||
var gridIndices = chunk.ChunkTileToGridTile((x, y));
|
||||
var newTileRef = new TileRef(uid, gridIndices, tile);
|
||||
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.Fixtures != null && !chunk.Fixtures.SetEquals(data.Fixtures))
|
||||
{
|
||||
chunk.Fixtures.Clear();
|
||||
|
||||
if (data.Fixtures != null)
|
||||
chunk.Fixtures.UnionWith(data.Fixtures);
|
||||
}
|
||||
// These should never refer to the same object
|
||||
DebugTools.AssertNotEqual(chunk.Fixtures, data.Fixtures);
|
||||
|
||||
chunk.CachedBounds = data.CachedBounds!.Value;
|
||||
chunk.SuppressCollisionRegeneration = false;
|
||||
if (shapeChanged)
|
||||
{
|
||||
modifiedChunks.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGridGetState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
|
||||
@@ -429,7 +413,15 @@ public abstract partial class SharedMapSystem
|
||||
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
|
||||
}
|
||||
}
|
||||
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, chunk.Fixtures, chunk.CachedBounds));
|
||||
|
||||
// The client needs to clone the fixture set instead of storing a reference.
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
var fixtures = chunk.Fixtures;
|
||||
if (_netManager.IsClient)
|
||||
fixtures = new(fixtures);
|
||||
|
||||
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, fixtures, chunk.CachedBounds));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,7 +458,15 @@ public abstract partial class SharedMapSystem
|
||||
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
|
||||
}
|
||||
}
|
||||
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, chunk.Fixtures, chunk.CachedBounds));
|
||||
|
||||
// The client needs to clone the fixture set instead of storing a reference.
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
var fixtures = chunk.Fixtures;
|
||||
if (_netManager.IsClient)
|
||||
fixtures = new(fixtures);
|
||||
|
||||
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, fixtures, chunk.CachedBounds));
|
||||
}
|
||||
|
||||
args.State = new MapGridComponentState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
|
||||
@@ -644,10 +644,12 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
foreach (var id in mapChunk.Fixtures)
|
||||
{
|
||||
mapChunk.Fixtures.Remove(id);
|
||||
_fixtures.DestroyFixture(uid, id, false, manager: manager, body: body, xform: xform);
|
||||
}
|
||||
|
||||
RemoveChunk(uid, grid, mapChunk.Indices);
|
||||
DebugTools.AssertEqual(mapChunk.Fixtures.Count, 0);
|
||||
removedChunks.Add(mapChunk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,8 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public bool AnchorEntity(Entity<TransformComponent> entity, Entity<MapGridComponent>? grid = null)
|
||||
{
|
||||
DebugTools.Assert(grid == null || grid.Value.Owner == entity.Comp.GridUid);
|
||||
DebugTools.Assert(grid == null || grid.Value.Owner == entity.Comp.GridUid,
|
||||
$"Tried to anchor entity {Name(entity)} to a grid ({grid!.Value.Owner}) different from its GridUid ({entity.Comp.GridUid})");
|
||||
|
||||
if (grid == null)
|
||||
{
|
||||
@@ -289,18 +290,26 @@ public abstract partial class SharedTransformSystem
|
||||
EntityUid uid,
|
||||
TransformComponent xform)
|
||||
{
|
||||
// Dont set pre-init, as the map grid component might not have been added yet.
|
||||
if (xform._gridInitialized || xform.LifeStage < ComponentLifeStage.Initializing)
|
||||
if (xform._gridInitialized)
|
||||
return;
|
||||
|
||||
xform._gridInitialized = true;
|
||||
DebugTools.Assert(xform.GridUid == null);
|
||||
if (_gridQuery.HasComponent(uid))
|
||||
{
|
||||
xform._gridUid = uid;
|
||||
xform._gridInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't set _gridInitialized to true unless the transform (and hence entity) is already being initialized,
|
||||
// as otherwise the current entity's grid component might just not have been added yet.
|
||||
//
|
||||
// We don't just return early, on the off chance that what is happening here is some convoluted entity
|
||||
// initialization pasta, where an an entity has been attached to an un-initialized entity on an already
|
||||
// initialized grid. In that case, the newly attached entity needs to be able to figure out the new grid id.
|
||||
// AFAIK this shouldn't happen anymore, but might as well keep this just in case.
|
||||
if (xform.LifeStage >= ComponentLifeStage.Initializing)
|
||||
xform._gridInitialized = true;
|
||||
|
||||
if (!xform._parent.IsValid())
|
||||
return;
|
||||
|
||||
@@ -726,6 +735,11 @@ public abstract partial class SharedTransformSystem
|
||||
{
|
||||
if (args.Current is TransformComponentState newState)
|
||||
{
|
||||
// TODO Delta-states
|
||||
// If the transform component ever gets delta states, then the client state manager needs to be updated.
|
||||
// Currently it explicitly looks for a "TransformComponentState" when determining an entity's parent for the
|
||||
// sake of sorting the states that need to be applied base on the transform hierarchy.
|
||||
|
||||
var parent = EnsureEntity<TransformComponent>(newState.ParentID, uid);
|
||||
var oldAnchored = xform.Anchored;
|
||||
|
||||
@@ -899,11 +913,11 @@ public abstract partial class SharedTransformSystem
|
||||
_mapManager.TryFindGridAt(mapUid, coordinates.Position, out var targetGrid, out _))
|
||||
{
|
||||
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
|
||||
SetCoordinates(entity, new EntityCoordinates(targetGrid, Vector2.Transform(coordinates.Position, invWorldMatrix)));
|
||||
SetCoordinates((entity.Owner, entity.Comp, MetaData(entity.Owner)), new EntityCoordinates(targetGrid, Vector2.Transform(coordinates.Position, invWorldMatrix)));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetCoordinates(entity, new EntityCoordinates(mapUid, coordinates.Position));
|
||||
SetCoordinates((entity.Owner, entity.Comp, MetaData(entity.Owner)), new EntityCoordinates(mapUid, coordinates.Position));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -316,6 +316,10 @@ namespace Robust.Shared.GameObjects
|
||||
/// Current parent entity of this entity.
|
||||
/// </summary>
|
||||
public readonly NetEntity ParentID;
|
||||
// TODO Delta-states
|
||||
// If the transform component ever gets delta states, then the client state manager needs to be updated.
|
||||
// Currently it explicitly looks for a "TransformComponentState" when determining an entity's parent for the
|
||||
// sake of sorting the states that need to be applied base on the transform hierarchy.
|
||||
|
||||
/// <summary>
|
||||
/// Current position offset of the entity.
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Collections;
|
||||
@@ -36,9 +37,9 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
private ActorRangeCheckJob _rangeJob;
|
||||
|
||||
/// <summary>
|
||||
/// Defer closing BUIs during state handling so client doesn't spam a BUI constantly during prediction.
|
||||
/// Defer BUIs during state handling so client doesn't spam a BUI constantly during prediction.
|
||||
/// </summary>
|
||||
private readonly List<BoundUserInterface> _queuedCloses = new();
|
||||
private readonly List<(BoundUserInterface Bui, bool value)> _queuedBuis = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -81,6 +82,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentShutdown>(OnActorShutdown);
|
||||
}
|
||||
|
||||
private void AddQueued(BoundUserInterface bui, bool value)
|
||||
{
|
||||
_queuedBuis.Add((bui, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the received message, and then pass it onto systems/components
|
||||
/// </summary>
|
||||
@@ -227,13 +233,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
if (ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
|
||||
{
|
||||
if (cBui.DeferredClose)
|
||||
_queuedCloses.Add(cBui);
|
||||
else
|
||||
{
|
||||
ent.Comp.ClientOpenInterfaces.Remove(key);
|
||||
cBui.Dispose();
|
||||
}
|
||||
AddQueued(cBui, false);
|
||||
}
|
||||
|
||||
if (ent.Comp.Actors.Count == 0)
|
||||
@@ -275,24 +275,18 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
// PlayerAttachedEvent will catch some of these.
|
||||
foreach (var (key, bui) in ent.Comp.ClientOpenInterfaces)
|
||||
{
|
||||
bui.Open();
|
||||
|
||||
if (ent.Comp.States.TryGetValue(key, out var state))
|
||||
{
|
||||
bui.UpdateState(state);
|
||||
bui.Update();
|
||||
}
|
||||
AddQueued(bui, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
|
||||
protected virtual void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
var actors = new List<EntityUid>();
|
||||
var ents = new ValueList<EntityUid>();
|
||||
foreach (var (key, acts) in ent.Comp.Actors)
|
||||
{
|
||||
actors.Clear();
|
||||
actors.AddRange(acts);
|
||||
foreach (var actor in actors)
|
||||
ents.Clear();
|
||||
ents.AddRange(acts);
|
||||
foreach (var actor in ents)
|
||||
{
|
||||
CloseUiInternal(ent!, key, actor);
|
||||
DebugTools.Assert(!acts.Contains(actor));
|
||||
@@ -301,7 +295,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
DebugTools.Assert(!ent.Comp.Actors.ContainsKey(key));
|
||||
}
|
||||
|
||||
DebugTools.Assert(ent.Comp.ClientOpenInterfaces.Values.All(x => _queuedCloses.Contains(x)));
|
||||
DebugTools.Assert(ent.Comp.ClientOpenInterfaces.Values.All(x => _queuedBuis.Contains((x, false))));
|
||||
}
|
||||
|
||||
private void OnUserInterfaceGetState(Entity<UserInterfaceComponent> ent, ref ComponentGetState args)
|
||||
@@ -322,12 +316,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
}
|
||||
case 1 << 2:
|
||||
{
|
||||
var state = new UserInterfaceStatesDeltaState()
|
||||
{
|
||||
States = new Dictionary<Enum, BoundUserInterfaceState>(ent.Comp.States),
|
||||
};
|
||||
var states = ent.Comp.States;
|
||||
|
||||
args.State = state;
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
if (_netManager.IsClient)
|
||||
states = new(states);
|
||||
|
||||
args.State = new UserInterfaceStatesDeltaState {States = states};
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -336,6 +332,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
var actors = new Dictionary<Enum, List<NetEntity>>();
|
||||
var dataCopy = new Dictionary<Enum, InterfaceData>(ent.Comp.Interfaces.Count);
|
||||
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
foreach (var (weh, a) in ent.Comp.Interfaces)
|
||||
{
|
||||
dataCopy[weh] = new InterfaceData(a);
|
||||
@@ -459,14 +457,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
}
|
||||
|
||||
var bui = ent.Comp.ClientOpenInterfaces[key];
|
||||
|
||||
if (bui.DeferredClose)
|
||||
_queuedCloses.Add(bui);
|
||||
else
|
||||
{
|
||||
ent.Comp.ClientOpenInterfaces.Remove(key);
|
||||
bui.Dispose();
|
||||
}
|
||||
AddQueued(bui, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +481,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
ent.Comp.States[key] = buiState;
|
||||
|
||||
if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
|
||||
if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui) || !cBui.IsOpened)
|
||||
continue;
|
||||
|
||||
cBui.State = buiState;
|
||||
@@ -523,9 +514,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
// Existing BUI just keep it.
|
||||
if (entity.Comp.ClientOpenInterfaces.TryGetValue(key, out var existing))
|
||||
{
|
||||
if (existing.DeferredClose)
|
||||
_queuedCloses.Remove(existing);
|
||||
|
||||
_queuedBuis.Remove((existing, false));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -541,10 +530,6 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
// Try-catch to try prevent error loops / bricked clients that constantly throw exceptions while applying game
|
||||
// states. E.g., stripping UI used to throw NREs in some instances while fetching the identity of unknown
|
||||
// entities.
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
var type = _reflection.LooseGetType(data.ClientType);
|
||||
var boundUserInterface = (BoundUserInterface) _factory.CreateInstance(type, [entity.Owner, key]);
|
||||
entity.Comp.ClientOpenInterfaces[key] = boundUserInterface;
|
||||
@@ -553,23 +538,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (!open)
|
||||
return;
|
||||
|
||||
boundUserInterface.Open();
|
||||
|
||||
if (entity.Comp.States.TryGetValue(key, out var buiState))
|
||||
{
|
||||
boundUserInterface.State = buiState;
|
||||
boundUserInterface.UpdateState(buiState);
|
||||
boundUserInterface.Update();
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(
|
||||
$"Caught exception while attempting to create a BUI {key} with type {data.ClientType} on entity {ToPrettyString(entity.Owner)}. Exception: {e}");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
AddQueued(boundUserInterface, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -685,6 +654,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the UI for the local client. Does nothing on server.
|
||||
/// </summary>
|
||||
public virtual void OpenUi(Entity<UserInterfaceComponent?> entity, Enum key, bool predicted = false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OpenUi(Entity<UserInterfaceComponent?> entity, Enum key, EntityUid? actor, bool predicted = false)
|
||||
{
|
||||
if (actor == null || !UIQuery.Resolve(entity.Owner, ref entity.Comp, false))
|
||||
@@ -722,6 +699,23 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
OpenUi(entity, key, actorEnt.Value, predicted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to return the saved position of a user interface.
|
||||
/// </summary>
|
||||
public virtual bool TryGetPosition(Entity<UserInterfaceComponent?> entity, Enum key, out Vector2 position)
|
||||
{
|
||||
position = Vector2.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a position for the BUI.
|
||||
/// </summary>
|
||||
protected virtual void SavePosition(BoundUserInterface bui)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a BUI state and networks it to all clients.
|
||||
/// </summary>
|
||||
@@ -755,8 +749,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
// Predict the change on client
|
||||
if (state != null && _netManager.IsClient && entity.Comp.ClientOpenInterfaces.TryGetValue(key, out var bui))
|
||||
{
|
||||
bui.UpdateState(state);
|
||||
bui.Update();
|
||||
if (bui.State?.Equals(state) != true)
|
||||
{
|
||||
bui.UpdateState(state);
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
|
||||
DirtyField(entity, nameof(UserInterfaceComponent.States));
|
||||
@@ -883,6 +880,33 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), message, key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the user's UIs that match the specified key.
|
||||
/// </summary>
|
||||
public void CloseUserUis<T>(Entity<UserInterfaceUserComponent?> actor) where T: Enum
|
||||
{
|
||||
if (!UserQuery.Resolve(actor.Owner, ref actor.Comp, false))
|
||||
return;
|
||||
|
||||
if (actor.Comp.OpenInterfaces.Count == 0)
|
||||
return;
|
||||
|
||||
var keys = new ValueList<Enum>();
|
||||
foreach (var (uid, enums) in actor.Comp.OpenInterfaces)
|
||||
{
|
||||
keys.Clear();
|
||||
keys.AddRange(enums);
|
||||
|
||||
foreach (var weh in keys)
|
||||
{
|
||||
if (weh is not T)
|
||||
continue;
|
||||
|
||||
CloseUiInternal(uid, weh, actor.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes all Uis for the actor.
|
||||
/// </summary>
|
||||
@@ -894,13 +918,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (actor.Comp.OpenInterfaces.Count == 0)
|
||||
return;
|
||||
|
||||
var enumCopy = new ValueList<Enum>();
|
||||
var keys = new ValueList<Enum>();
|
||||
|
||||
foreach (var (uid, enums) in actor.Comp.OpenInterfaces)
|
||||
{
|
||||
enumCopy.Clear();
|
||||
enumCopy.AddRange(enums);
|
||||
keys.Clear();
|
||||
keys.AddRange(enums);
|
||||
|
||||
foreach (var key in enumCopy)
|
||||
foreach (var key in keys)
|
||||
{
|
||||
CloseUiInternal(uid, key, actor.Owner);
|
||||
}
|
||||
@@ -1035,17 +1060,48 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
{
|
||||
if (_timing.IsFirstTimePredicted)
|
||||
{
|
||||
foreach (var bui in _queuedCloses)
|
||||
foreach (var (bui, open) in _queuedBuis)
|
||||
{
|
||||
if (UIQuery.TryComp(bui.Owner, out var uiComp))
|
||||
if (open)
|
||||
{
|
||||
uiComp.ClientOpenInterfaces.Remove(bui.UiKey);
|
||||
}
|
||||
bui.Open();
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
|
||||
bui.Dispose();
|
||||
if (UIQuery.TryComp(bui.Owner, out var uiComp))
|
||||
{
|
||||
if (uiComp.States.TryGetValue(bui.UiKey, out var buiState))
|
||||
{
|
||||
bui.State = buiState;
|
||||
bui.UpdateState(buiState);
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(
|
||||
$"Caught exception while attempting to create a BUI {bui.UiKey} with type {bui.GetType()} on entity {ToPrettyString(bui.Owner)}. Exception: {e}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// Close BUI
|
||||
else
|
||||
{
|
||||
if (UIQuery.TryComp(bui.Owner, out var uiComp))
|
||||
{
|
||||
uiComp.ClientOpenInterfaces.Remove(bui.UiKey);
|
||||
}
|
||||
|
||||
SavePosition(bui);
|
||||
bui.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_queuedCloses.Clear();
|
||||
_queuedBuis.Clear();
|
||||
}
|
||||
|
||||
var query = AllEntityQuery<ActiveUserInterfaceComponent, UserInterfaceComponent>();
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Shared.GameStates
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct ChunkDatum
|
||||
{
|
||||
public static readonly ChunkDatum Empty = new ChunkDatum();
|
||||
public static readonly ChunkDatum Empty = new();
|
||||
|
||||
public readonly HashSet<string>? Fixtures;
|
||||
|
||||
@@ -21,28 +21,12 @@ namespace Robust.Shared.GameStates
|
||||
|
||||
public readonly Box2i? CachedBounds;
|
||||
|
||||
[MemberNotNullWhen(false, nameof(TileData))]
|
||||
[MemberNotNullWhen(false, nameof(TileData), nameof(Fixtures))]
|
||||
public bool IsDeleted()
|
||||
{
|
||||
return TileData == null;
|
||||
}
|
||||
|
||||
internal ChunkDatum(ChunkDatum data)
|
||||
{
|
||||
if (data.TileData != null)
|
||||
{
|
||||
TileData = new Tile[data.TileData.Length];
|
||||
data.TileData.CopyTo(TileData, 0);
|
||||
}
|
||||
|
||||
if (data.Fixtures != null)
|
||||
{
|
||||
Fixtures = new HashSet<string>(data.Fixtures);
|
||||
}
|
||||
|
||||
CachedBounds = data.CachedBounds;
|
||||
}
|
||||
|
||||
private ChunkDatum(Tile[] tileData, HashSet<string> fixtures, Box2i cachedBounds)
|
||||
{
|
||||
TileData = tileData;
|
||||
|
||||
50
Robust.Shared/GameStates/SharedPvsOverrideSystem.cs
Normal file
50
Robust.Shared/GameStates/SharedPvsOverrideSystem.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Robust.Shared.GameStates;
|
||||
|
||||
public abstract class SharedPvsOverrideSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations,
|
||||
/// causing them to always be sent to all clients.
|
||||
/// </summary>
|
||||
public virtual void AddGlobalOverride(EntityUid uid)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an entity from the global overrides.
|
||||
/// </summary>
|
||||
public virtual void RemoveGlobalOverride(EntityUid uid)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations for a
|
||||
/// specific session.
|
||||
/// </summary>
|
||||
public virtual void AddSessionOverride(EntityUid uid, ICommonSession session)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an entity from a session's overrides.
|
||||
/// </summary>
|
||||
public virtual void RemoveSessionOverride(EntityUid uid, ICommonSession session)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the entity, all of its parents, and all of its children to ignore normal PVS range limitations,
|
||||
/// causing them to always be sent to all clients.
|
||||
/// </summary>
|
||||
public virtual void AddSessionOverrides(EntityUid uid, Filter filter)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,12 @@ namespace Robust.Shared.Localization
|
||||
/// </summary>
|
||||
CultureInfo? DefaultCulture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the culture has been loaded.
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
bool HasCulture(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// Load data for a culture.
|
||||
/// </summary>
|
||||
|
||||
@@ -337,6 +337,11 @@ namespace Robust.Shared.Localization
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasCulture(CultureInfo culture)
|
||||
{
|
||||
return _contexts.ContainsKey(culture);
|
||||
}
|
||||
|
||||
public void LoadCulture(CultureInfo culture)
|
||||
{
|
||||
var bundle = LinguiniBuilder.Builder()
|
||||
|
||||
@@ -319,7 +319,7 @@ namespace Robust.Shared.Map.Components
|
||||
if (data.IsDeleted())
|
||||
state.FullGridData.Remove(index);
|
||||
else
|
||||
state.FullGridData[index] = new(data);
|
||||
state.FullGridData[index] = data;
|
||||
}
|
||||
|
||||
state.LastTileModifiedTick = LastTileModifiedTick;
|
||||
@@ -327,14 +327,10 @@ namespace Robust.Shared.Map.Components
|
||||
|
||||
public MapGridComponentState CreateNewFullState(MapGridComponentState state)
|
||||
{
|
||||
var fullGridData = new Dictionary<Vector2i, ChunkDatum>(state.FullGridData.Count);
|
||||
if (ChunkData == null)
|
||||
return new(ChunkSize, state.FullGridData, state.LastTileModifiedTick);
|
||||
|
||||
foreach (var (key, value) in state.FullGridData)
|
||||
{
|
||||
fullGridData[key] = new(value);
|
||||
}
|
||||
|
||||
var newState = new MapGridComponentState(ChunkSize, fullGridData, LastTileModifiedTick);
|
||||
var newState = new MapGridComponentState(ChunkSize, state.FullGridData.ShallowClone(), LastTileModifiedTick);
|
||||
ApplyToFullState(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ public sealed partial class MapLightComponent : Component
|
||||
/// <summary>
|
||||
/// Ambient light. This is in linear-light, i.e. when providing a fixed colour, you must use Color.FromSrgb(Color.Black)!
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("ambientLightColor")]
|
||||
public Color AmbientLightColor { get; set; } = Color.FromSrgb(Color.Black);
|
||||
[DataField]
|
||||
public Color AmbientLightColor = Color.FromSrgb(Color.Black);
|
||||
}
|
||||
|
||||
@@ -77,11 +77,11 @@ namespace Robust.Shared.Map
|
||||
|
||||
#region MapId
|
||||
|
||||
public void FindGridsIntersecting(MapId mapId, IPhysShape shape, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = Approximate, bool includeMap = IncludeMap);
|
||||
public void FindGridsIntersecting<T>(MapId mapId, T shape, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = Approximate, bool includeMap = IncludeMap) where T : IPhysShape;
|
||||
|
||||
public void FindGridsIntersecting(MapId mapId, IPhysShape shape, Transform transform, GridCallback callback,
|
||||
bool approx = Approximate, bool includeMap = IncludeMap);
|
||||
public void FindGridsIntersecting<T>(MapId mapId, T shape, Transform transform, GridCallback callback,
|
||||
bool approx = Approximate, bool includeMap = IncludeMap) where T : IPhysShape;
|
||||
|
||||
public void FindGridsIntersecting(MapId mapId, Box2 worldAABB, GridCallback callback, bool approx = Approximate,
|
||||
bool includeMap = IncludeMap);
|
||||
@@ -107,11 +107,11 @@ namespace Robust.Shared.Map
|
||||
|
||||
#region MapEnt
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Transform transform, GridCallback callback,
|
||||
bool approx = Approximate, bool includeMap = IncludeMap);
|
||||
public void FindGridsIntersecting<T>(EntityUid mapEnt, T shape, Transform transform, GridCallback callback,
|
||||
bool approx = Approximate, bool includeMap = IncludeMap) where T : IPhysShape;
|
||||
|
||||
public void FindGridsIntersecting<TState>(EntityUid mapEnt, IPhysShape shape, Transform transform,
|
||||
ref TState state, GridCallback<TState> callback, bool approx = Approximate, bool includeMap = IncludeMap);
|
||||
public void FindGridsIntersecting<T, TState>(EntityUid mapEnt, T shape, Transform transform,
|
||||
ref TState state, GridCallback<TState> callback, bool approx = Approximate, bool includeMap = IncludeMap) where T : IPhysShape;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if any grids overlap the specified shapes.
|
||||
@@ -119,8 +119,8 @@ namespace Robust.Shared.Map
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, List<IPhysShape> shapes, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> entities, bool approx = Approximate, bool includeMap = IncludeMap);
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = Approximate, bool includeMap = IncludeMap);
|
||||
public void FindGridsIntersecting<T>(EntityUid mapEnt, T shape, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = Approximate, bool includeMap = IncludeMap) where T : IPhysShape;
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, GridCallback callback,
|
||||
bool approx = Approximate, bool includeMap = IncludeMap);
|
||||
|
||||
@@ -8,17 +8,16 @@ using Robust.Shared.Map.Enumerators;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Shapes;
|
||||
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
internal partial class MapManager
|
||||
{
|
||||
private bool IsIntersecting(
|
||||
private bool IsIntersecting<T>(
|
||||
ChunkEnumerator enumerator,
|
||||
IPhysShape shape,
|
||||
T shape,
|
||||
Transform shapeTransform,
|
||||
Entity<FixturesComponent> grid)
|
||||
Entity<FixturesComponent> grid) where T : IPhysShape
|
||||
{
|
||||
var gridTransform = _physics.GetPhysicsTransform(grid);
|
||||
|
||||
@@ -43,14 +42,14 @@ internal partial class MapManager
|
||||
|
||||
#region MapId
|
||||
|
||||
public void FindGridsIntersecting(MapId mapId, IPhysShape shape, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
public void FindGridsIntersecting<T>(MapId mapId, T shape, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt, shape, transform, ref grids, approx, includeMap);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(MapId mapId, IPhysShape shape, Transform transform, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
public void FindGridsIntersecting<T>(MapId mapId, T shape, Transform transform, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt, shape, transform, callback, includeMap, approx);
|
||||
@@ -100,18 +99,18 @@ internal partial class MapManager
|
||||
|
||||
#region MapEnt
|
||||
|
||||
public void FindGridsIntersecting(
|
||||
public void FindGridsIntersecting<T>(
|
||||
EntityUid mapEnt,
|
||||
IPhysShape shape,
|
||||
T shape,
|
||||
Transform transform,
|
||||
GridCallback callback,
|
||||
bool approx = IMapManager.Approximate,
|
||||
bool includeMap = IMapManager.IncludeMap)
|
||||
bool includeMap = IMapManager.IncludeMap) where T : IPhysShape
|
||||
{
|
||||
FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, callback, approx, includeMap);
|
||||
}
|
||||
|
||||
private void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Box2 worldAABB, Transform transform, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
private void FindGridsIntersecting<T>(EntityUid mapEnt, T shape, Box2 worldAABB, Transform transform, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape
|
||||
{
|
||||
// This is here so we don't double up on code.
|
||||
var state = callback;
|
||||
@@ -121,20 +120,27 @@ internal partial class MapManager
|
||||
approx: approx, includeMap: includeMap);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting<TState>(
|
||||
public void FindGridsIntersecting<T, TState>(
|
||||
EntityUid mapEnt,
|
||||
IPhysShape shape,
|
||||
T shape,
|
||||
Transform transform,
|
||||
ref TState state,
|
||||
GridCallback<TState> callback,
|
||||
bool approx = IMapManager.Approximate,
|
||||
bool includeMap = IMapManager.IncludeMap)
|
||||
bool includeMap = IMapManager.IncludeMap) where T : IPhysShape
|
||||
{
|
||||
FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, ref state, callback, approx, includeMap);
|
||||
}
|
||||
|
||||
private void FindGridsIntersecting<TState>(EntityUid mapEnt, IPhysShape shape, Box2 worldAABB, Transform transform,
|
||||
ref TState state, GridCallback<TState> callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
private void FindGridsIntersecting<T, TState>(
|
||||
EntityUid mapEnt,
|
||||
T shape,
|
||||
Box2 worldAABB,
|
||||
Transform transform,
|
||||
ref TState state,
|
||||
GridCallback<TState> callback,
|
||||
bool approx = IMapManager.Approximate,
|
||||
bool includeMap = IMapManager.IncludeMap) where T : IPhysShape
|
||||
{
|
||||
if (!_gridTreeQuery.TryGetComponent(mapEnt, out var gridTree))
|
||||
return;
|
||||
@@ -144,7 +150,7 @@ internal partial class MapManager
|
||||
callback(mapEnt, mapGrid, ref state);
|
||||
}
|
||||
|
||||
var gridState = new GridQueryState<TState>(
|
||||
var gridState = new GridQueryState<T, TState>(
|
||||
callback,
|
||||
state,
|
||||
worldAABB,
|
||||
@@ -156,8 +162,7 @@ internal partial class MapManager
|
||||
_transformSystem,
|
||||
approx);
|
||||
|
||||
|
||||
gridTree.Tree.Query(ref gridState, static (ref GridQueryState<TState> state, DynamicTree.Proxy proxy) =>
|
||||
gridTree.Tree.Query(ref gridState, static (ref GridQueryState<T, TState> state, DynamicTree.Proxy proxy) =>
|
||||
{
|
||||
// Even for approximate we'll check if any chunks roughly overlap.
|
||||
var data = state.Tree.GetUserData(proxy);
|
||||
@@ -198,14 +203,14 @@ internal partial class MapManager
|
||||
}
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
public void FindGridsIntersecting<T>(EntityUid mapEnt, T shape, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape
|
||||
{
|
||||
FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, ref grids, approx, includeMap);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, Box2 worldAABB, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
public void FindGridsIntersecting<T>(EntityUid mapEnt, T shape, Box2 worldAABB, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape
|
||||
{
|
||||
var state = grids;
|
||||
|
||||
@@ -219,39 +224,48 @@ internal partial class MapManager
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
FindGridsIntersecting(mapEnt, new Polygon(worldAABB), worldAABB, Transform.Empty, callback, approx, includeMap);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, callback, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting<TState>(EntityUid mapEnt, Box2 worldAABB, ref TState state, GridCallback<TState> callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
FindGridsIntersecting(mapEnt, new Polygon(worldAABB), worldAABB, Transform.Empty, ref state, callback, approx, includeMap);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, ref state, callback, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, ref List<Entity<MapGridComponent>> grids,
|
||||
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
FindGridsIntersecting(mapEnt, new Polygon(worldAABB), worldAABB, Transform.Empty, ref grids, approx, includeMap);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, ref grids, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, GridCallback callback, bool approx = IMapManager.Approximate,
|
||||
bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
var shape = new Polygon(worldBounds);
|
||||
FindGridsIntersecting(mapEnt, shape, worldBounds.CalcBoundingBox(), Transform.Empty, callback, approx, includeMap);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, callback, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting<TState>(EntityUid mapEnt, Box2Rotated worldBounds, ref TState state, GridCallback<TState> callback,
|
||||
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
var shape = new Polygon(worldBounds);
|
||||
FindGridsIntersecting(mapEnt, shape, worldBounds.CalcBoundingBox(), Transform.Empty, ref state, callback, approx, includeMap);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, ref state, callback, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, ref List<Entity<MapGridComponent>> grids,
|
||||
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
var shape = new Polygon(worldBounds);
|
||||
FindGridsIntersecting(mapEnt, shape, worldBounds.CalcBoundingBox(), Transform.Empty, ref grids, approx, includeMap);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, ref grids, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -342,22 +356,11 @@ internal partial class MapManager
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly record struct GridQueryState(
|
||||
GridCallback Callback,
|
||||
Box2 WorldAABB,
|
||||
IPhysShape Shape,
|
||||
Transform Transform,
|
||||
B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree,
|
||||
SharedMapSystem MapSystem,
|
||||
MapManager MapManager,
|
||||
SharedTransformSystem TransformSystem,
|
||||
bool Approximate);
|
||||
|
||||
private record struct GridQueryState<TState>(
|
||||
private record struct GridQueryState<T, TState>(
|
||||
GridCallback<TState> Callback,
|
||||
TState State,
|
||||
Box2 WorldAABB,
|
||||
IPhysShape Shape,
|
||||
T Shape,
|
||||
Transform Transform,
|
||||
B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree,
|
||||
SharedMapSystem MapSystem,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Map;
|
||||
@@ -16,9 +17,9 @@ public readonly struct Tile : IEquatable<Tile>, ISpanFormattable
|
||||
public readonly int TypeId;
|
||||
|
||||
/// <summary>
|
||||
/// Rendering flags.
|
||||
/// Custom flags for additional tile-data.
|
||||
/// </summary>
|
||||
public readonly TileRenderFlag Flags;
|
||||
public readonly byte Flags;
|
||||
|
||||
/// <summary>
|
||||
/// Variant of this tile to render.
|
||||
@@ -39,9 +40,9 @@ public readonly struct Tile : IEquatable<Tile>, ISpanFormattable
|
||||
/// Creates a new instance of a grid tile.
|
||||
/// </summary>
|
||||
/// <param name="typeId">Internal type ID.</param>
|
||||
/// <param name="flags">Flags used by toolbox's rendering.</param>
|
||||
/// <param name="flags">Custom tile flags for usage.</param>
|
||||
/// <param name="variant">The visual variant this tile is using.</param>
|
||||
public Tile(int typeId, TileRenderFlag flags = 0, byte variant = 0)
|
||||
public Tile(int typeId, byte flags = 0, byte variant = 0)
|
||||
{
|
||||
TypeId = typeId;
|
||||
Flags = flags;
|
||||
@@ -112,9 +113,19 @@ public readonly struct Tile : IEquatable<Tile>, ISpanFormattable
|
||||
return (TypeId.GetHashCode() * 397) ^ Flags.GetHashCode() ^ Variant.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public Tile WithVariant(byte variant)
|
||||
{
|
||||
return new Tile(TypeId, Flags, variant);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public Tile WithFlag(byte flag)
|
||||
{
|
||||
return new Tile(TypeId, flags: flag, variant: Variant);
|
||||
}
|
||||
}
|
||||
|
||||
public enum TileRenderFlag : byte
|
||||
{
|
||||
public sealed class TileFlagLayer {}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ internal sealed partial class CollisionManager
|
||||
/// <param name="xfA">The transform for the first shape.</param>
|
||||
/// <param name="xfB">The transform for the seconds shape.</param>
|
||||
/// <returns></returns>
|
||||
public bool TestOverlap(IPhysShape shapeA, int childIndexA, IPhysShape shapeB, int childIndexB, in Transform xfA, in Transform xfB)
|
||||
public bool TestOverlap<T, U>(T shapeA, int indexA, U shapeB, int indexB, in Transform xfA, in Transform xfB) where T : IPhysShape where U : IPhysShape
|
||||
{
|
||||
var input = new DistanceInput();
|
||||
|
||||
input.ProxyA.Set(shapeA, childIndexA);
|
||||
input.ProxyB.Set(shapeB, childIndexB);
|
||||
input.ProxyA.Set(shapeA, indexA);
|
||||
input.ProxyB.Set(shapeB, indexB);
|
||||
input.TransformA = xfA;
|
||||
input.TransformB = xfB;
|
||||
input.UseRadii = true;
|
||||
|
||||
@@ -24,6 +24,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -56,12 +57,12 @@ internal ref struct DistanceProxy
|
||||
/// must remain in scope while the proxy is in use.
|
||||
/// </summary>
|
||||
/// <param name="shape">The shape.</param>
|
||||
public void Set(IPhysShape shape, int index)
|
||||
internal void Set<T>(T shape, int index) where T : IPhysShape
|
||||
{
|
||||
switch (shape.ShapeType)
|
||||
{
|
||||
case ShapeType.Circle:
|
||||
PhysShapeCircle circle = (PhysShapeCircle) shape;
|
||||
var circle = Unsafe.As<PhysShapeCircle>(shape);
|
||||
Buffer._00 = circle.Position;
|
||||
Vertices = Buffer.AsSpan[..1];
|
||||
Radius = circle.Radius;
|
||||
@@ -70,20 +71,20 @@ internal ref struct DistanceProxy
|
||||
case ShapeType.Polygon:
|
||||
if (shape is Polygon poly)
|
||||
{
|
||||
Vertices = poly.Vertices;
|
||||
Vertices = poly.Vertices.AsSpan()[..poly.VertexCount];
|
||||
Radius = poly.Radius;
|
||||
}
|
||||
else
|
||||
{
|
||||
var polyShape = (PolygonShape) shape;
|
||||
Vertices = polyShape.Vertices;
|
||||
var polyShape = Unsafe.As<PolygonShape>(shape);
|
||||
Vertices = polyShape.Vertices.AsSpan()[..polyShape.VertexCount];
|
||||
Radius = polyShape.Radius;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ShapeType.Chain:
|
||||
ChainShape chain = (ChainShape) shape;
|
||||
var chain = Unsafe.As<ChainShape>(shape);
|
||||
Debug.Assert(0 <= index && index < chain.Vertices.Length);
|
||||
|
||||
Buffer._00 = chain.Vertices[index];
|
||||
@@ -93,7 +94,7 @@ internal ref struct DistanceProxy
|
||||
Radius = chain.Radius;
|
||||
break;
|
||||
case ShapeType.Edge:
|
||||
EdgeShape edge = (EdgeShape) shape;
|
||||
var edge = Unsafe.As<EdgeShape>(shape);
|
||||
|
||||
Buffer._00 = edge.Vertex1;
|
||||
Buffer._01 = edge.Vertex2;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user