mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d926d0f97 | ||
|
|
4cbbb6f848 | ||
|
|
d9a4e0d628 | ||
|
|
d282c46d44 | ||
|
|
3330d96177 | ||
|
|
4033d96327 | ||
|
|
6e0205d1a8 | ||
|
|
7cd95351c3 | ||
|
|
2a102f048f | ||
|
|
16bab1bc03 | ||
|
|
123d0ae6ac | ||
|
|
d72de032fa | ||
|
|
0fdba836ee | ||
|
|
eb63809999 | ||
|
|
4c3c74865c | ||
|
|
b624f5b70f | ||
|
|
6566a7658a | ||
|
|
9e3e1cc929 | ||
|
|
4e87d93009 | ||
|
|
1031ae4cc5 | ||
|
|
73da147b88 | ||
|
|
0ab59d70b1 | ||
|
|
8e8470ac7e | ||
|
|
15f94bd094 | ||
|
|
68888c4370 | ||
|
|
19f87dfbb3 | ||
|
|
68e5b6924d | ||
|
|
9f913cd2d9 | ||
|
|
ec37d1c137 | ||
|
|
ea58924495 | ||
|
|
c5aa735506 | ||
|
|
f5a6e52c7f | ||
|
|
d5c4981648 | ||
|
|
8c6170661d |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,13 +54,101 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 218.2.3
|
||||
## 220.2.2
|
||||
|
||||
|
||||
## 218.2.2
|
||||
## 220.2.1
|
||||
|
||||
|
||||
## 218.2.1
|
||||
## 220.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* RSIs can now specify load parameters, mimicking the ones from `.png.yml`. Currently only disabling sRGB is supported.
|
||||
* Added a second UV channel to Clyde's vertex format. On regular batched sprite draws, this goes 0 -> 1 across the sprite quad.
|
||||
* Added a new `CopyToShaderParameters` system for `SpriteComponent` layers.
|
||||
|
||||
|
||||
## 220.1.0
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix client-side replay exceptions due to dropped states when recording.
|
||||
|
||||
### Other
|
||||
|
||||
* Remove IP + HWId from ViewVariables.
|
||||
* Close BUIs upon disconnect.
|
||||
|
||||
|
||||
## 220.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Refactor UserInterfaceSystem.
|
||||
- The API has been significantly cleaned up and standardised, most noticeably callers don't need to worry about TryGetUi and can rely on either HasUi, SetUiState, CloseUi, or OpenUi to handle their code as appropriate.
|
||||
- Interface data is now stored via key rather than as a flat list which is a breaking change for YAML.
|
||||
- BoundUserInterfaces can now be completely handled via Shared code. Existing Server-side callers will behave similarly to before.
|
||||
- BoundUserInterfaces now properly close in many more situations, additionally they are now attached to the entity so reconnecting can re-open them and they can be serialized properly.
|
||||
|
||||
|
||||
## 219.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add SetMapCoordinates to TransformSystem.
|
||||
* Improve YAML Linter and validation of static fields.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix DebugCoordsPanel freezing when hovering a control.
|
||||
|
||||
### Other
|
||||
|
||||
* Optimise physics networking to not dirty every tick of movement.
|
||||
|
||||
|
||||
## 219.1.3
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix map-loader not pausing pre-init maps when not actively overwriting an existing map.
|
||||
|
||||
|
||||
## 219.1.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix map-loader not map-initialising grids when loading into a post-init map.
|
||||
|
||||
|
||||
## 219.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix map-loader not map-initialising maps when overwriting a post-init map.
|
||||
|
||||
|
||||
## 219.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added a new optional arguments to various entity spawning methods, including a new argument to set the entity's rotation.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes map initialisation not always initialising all entities on a map.
|
||||
|
||||
### Other
|
||||
|
||||
* The default value of the `auth.mode` cvar has changed
|
||||
|
||||
|
||||
## 219.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Move most IMapManager functionality to SharedMapSystem.
|
||||
|
||||
|
||||
## 218.2.0
|
||||
|
||||
@@ -26,9 +26,8 @@ public partial class AddRemoveComponentBenchmark
|
||||
.InitializeInstance();
|
||||
|
||||
_entityManager = _simulation.Resolve<IEntityManager>();
|
||||
|
||||
var coords = new MapCoordinates(0, 0, new MapId(1));
|
||||
_simulation.AddMap(coords.MapId);
|
||||
var map = _simulation.CreateMap().Uid;
|
||||
var coords = new EntityCoordinates(map, default);
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
|
||||
@@ -29,8 +29,8 @@ public partial class ComponentIteratorBenchmark
|
||||
|
||||
Comps = new A[N+2];
|
||||
|
||||
var coords = new MapCoordinates(0, 0, new MapId(1));
|
||||
_simulation.AddMap(coords.MapId);
|
||||
var map = _simulation.CreateMap().MapId;
|
||||
var coords = new MapCoordinates(default, map);
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
|
||||
@@ -31,8 +31,8 @@ public partial class GetComponentBenchmark
|
||||
|
||||
Comps = new A[N+2];
|
||||
|
||||
var coords = new MapCoordinates(0, 0, new MapId(1));
|
||||
_simulation.AddMap(coords.MapId);
|
||||
var map = _simulation.CreateMap().Uid;
|
||||
var coords = new EntityCoordinates(map, default);
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
|
||||
@@ -29,10 +29,9 @@ public partial class SpawnDeleteEntityBenchmark
|
||||
.InitializeInstance();
|
||||
|
||||
_entityManager = _simulation.Resolve<IEntityManager>();
|
||||
|
||||
_mapCoords = new MapCoordinates(0, 0, new MapId(1));
|
||||
var uid = _simulation.AddMap(_mapCoords.MapId);
|
||||
_entCoords = new EntityCoordinates(uid, 0, 0);
|
||||
var (map, mapId) = _simulation.CreateMap();
|
||||
_mapCoords = new MapCoordinates(default, mapId);
|
||||
_entCoords = new EntityCoordinates(map, 0, 0);
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
|
||||
@@ -91,8 +91,7 @@ public class RecursiveMoveBenchmark : RobustIntegrationTest
|
||||
// Set up map and spawn player
|
||||
server.WaitPost(() =>
|
||||
{
|
||||
var mapId = mapMan.CreateMap();
|
||||
var map = mapMan.GetMapEntityId(mapId);
|
||||
var map = server.ResolveDependency<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var gridComp = mapMan.CreateGridEntity(mapId);
|
||||
var grid = gridComp.Owner;
|
||||
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
|
||||
|
||||
@@ -74,11 +74,13 @@ public sealed class AudioOverlay : Overlay
|
||||
output.Clear();
|
||||
output.AppendLine("Audio Source");
|
||||
output.AppendLine("Runtime:");
|
||||
output.AppendLine($"- Distance: {_audio.GetAudioDistance(distance.Length()):0.00}");
|
||||
output.AppendLine($"- Occlusion: {posOcclusion:0.0000}");
|
||||
output.AppendLine("Params:");
|
||||
output.AppendLine($"- RolloffFactor: {comp.RolloffFactor:0.0000}");
|
||||
output.AppendLine($"- Volume: {comp.Volume:0.0000}");
|
||||
output.AppendLine($"- Reference distance: {comp.ReferenceDistance}");
|
||||
output.AppendLine($"- Max distance: {comp.MaxDistance}");
|
||||
output.AppendLine($"- Reference distance: {comp.ReferenceDistance:0.00}");
|
||||
output.AppendLine($"- Max distance: {comp.MaxDistance:0.00}");
|
||||
var outputText = output.ToString().Trim();
|
||||
var dimensions = screenHandle.GetDimensions(_font, outputText, 1f);
|
||||
var buffer = new Vector2(3f, 3f);
|
||||
|
||||
@@ -388,7 +388,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
var distance = delta.Length();
|
||||
|
||||
// Out of range so just clip it for us.
|
||||
if (distance > component.MaxDistance)
|
||||
if (GetAudioDistance(distance) > component.MaxDistance)
|
||||
{
|
||||
// Still keeps the source playing, just with no volume.
|
||||
component.Gain = 0f;
|
||||
|
||||
@@ -285,6 +285,7 @@ namespace Robust.Client
|
||||
/// <summary>
|
||||
/// Enumeration of the run levels of the BaseClient.
|
||||
/// </summary>
|
||||
/// <seealso cref="ClientRunLevelExt"/>
|
||||
public enum ClientRunLevel : byte
|
||||
{
|
||||
Error = 0,
|
||||
@@ -315,6 +316,21 @@ namespace Robust.Client
|
||||
SinglePlayerGame,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper functions for working with <see cref="ClientRunLevel"/>.
|
||||
/// </summary>
|
||||
public static class ClientRunLevelExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if a <see cref="ClientRunLevel"/> is <see cref="ClientRunLevel.InGame"/>
|
||||
/// or <see cref="ClientRunLevel.SinglePlayerGame"/>.
|
||||
/// </summary>
|
||||
public static bool IsInGameLike(this ClientRunLevel runLevel)
|
||||
{
|
||||
return runLevel is ClientRunLevel.InGame or ClientRunLevel.SinglePlayerGame;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for when something changed with the player.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Animations;
|
||||
@@ -28,6 +29,7 @@ using static Robust.Client.ComponentTrees.SpriteTreeSystem;
|
||||
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -770,15 +772,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
foreach (var keyString in layerDatum.MapKeys)
|
||||
{
|
||||
object key;
|
||||
if (reflection.TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
var key = ParseKey(keyString);
|
||||
|
||||
if (LayerMap.TryGetValue(key, out var mappedIndex))
|
||||
{
|
||||
@@ -804,9 +798,30 @@ namespace Robust.Client.GameObjects
|
||||
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
||||
layer.Visible = layerDatum.Visible ?? layer.Visible;
|
||||
|
||||
if (layerDatum.CopyToShaderParameters is { } copyParameters)
|
||||
{
|
||||
layer.CopyToShaderParameters = new CopyToShaderParameters(ParseKey(copyParameters.LayerKey))
|
||||
{
|
||||
ParameterTexture = copyParameters.ParameterTexture,
|
||||
ParameterUV = copyParameters.ParameterUV
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.CopyToShaderParameters = null;
|
||||
}
|
||||
|
||||
RebuildBounds();
|
||||
}
|
||||
|
||||
private object ParseKey(string keyString)
|
||||
{
|
||||
if (reflection.TryParseEnumReference(keyString, out var @enum))
|
||||
return @enum;
|
||||
|
||||
return keyString;
|
||||
}
|
||||
|
||||
public void LayerSetData(object layerKey, PrototypeLayerData data)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer, true))
|
||||
@@ -1635,6 +1650,9 @@ namespace Robust.Client.GameObjects
|
||||
[ViewVariables]
|
||||
public LayerRenderingStrategy RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public CopyToShaderParameters? CopyToShaderParameters;
|
||||
|
||||
public Layer(SpriteComponent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
@@ -2007,8 +2025,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// Set the drawing transform for this layer
|
||||
GetLayerDrawMatrix(dir, out var layerMatrix);
|
||||
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
// The direction used to draw the sprite can differ from the one that the angle would naively suggest,
|
||||
// due to direction overrides or offsets.
|
||||
@@ -2018,7 +2034,41 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// Get the correct directional texture from the state, and draw it!
|
||||
var texture = GetRenderTexture(_actualState, dir);
|
||||
RenderTexture(drawingHandle, texture);
|
||||
|
||||
if (CopyToShaderParameters == null)
|
||||
{
|
||||
// Set the drawing transform for this layer
|
||||
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
RenderTexture(drawingHandle, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multiple atrocities to god being committed right here.
|
||||
var otherLayerIdx = _parent.LayerMap[CopyToShaderParameters.LayerKey!];
|
||||
var otherLayer = _parent.Layers[otherLayerIdx];
|
||||
if (otherLayer.Shader is not { } shader)
|
||||
{
|
||||
// No shader set apparently..?
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shader.Mutable)
|
||||
otherLayer.Shader = shader = shader.Duplicate();
|
||||
|
||||
var clydeTexture = Clyde.RenderHandle.ExtractTexture(texture, null, out var csr);
|
||||
var sr = Clyde.RenderHandle.WorldTextureBoundsToUV(clydeTexture, csr);
|
||||
|
||||
if (CopyToShaderParameters.ParameterTexture is { } paramTexture)
|
||||
shader.SetParameter(paramTexture, clydeTexture);
|
||||
|
||||
if (CopyToShaderParameters.ParameterUV is { } paramUV)
|
||||
{
|
||||
var uv = new Vector4(sr.Left, sr.Bottom, sr.Right, sr.Top);
|
||||
shader.SetParameter(paramUV, uv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture)
|
||||
@@ -2096,6 +2146,17 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiated version of <see cref="PrototypeCopyToShaderParameters"/>.
|
||||
/// Has <see cref="LayerKey"/> actually resolved to a a real key.
|
||||
/// </summary>
|
||||
public sealed class CopyToShaderParameters(object layerKey)
|
||||
{
|
||||
public object LayerKey = layerKey;
|
||||
public string? ParameterTexture;
|
||||
public string? ParameterUV;
|
||||
}
|
||||
|
||||
void IAnimationProperties.SetAnimatableProperty(string name, object value)
|
||||
{
|
||||
if (!name.StartsWith("layer/"))
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.Physics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
@@ -16,6 +13,17 @@ public sealed class MapSystem : SharedMapSystem
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
protected override MapId GetNextMapId()
|
||||
{
|
||||
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
|
||||
var id = new MapId(--LastMapId);
|
||||
while (MapManager.MapExists(id))
|
||||
{
|
||||
id = new MapId(--LastMapId);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -27,9 +35,4 @@ public sealed class MapSystem : SharedMapSystem
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +1,8 @@
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using System;
|
||||
using UserInterfaceComponent = Robust.Shared.GameObjects.UserInterfaceComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
|
||||
}
|
||||
|
||||
private void MessageReceived(BoundUIWrapMessage ev)
|
||||
{
|
||||
var uid = GetEntity(ev.Entity);
|
||||
|
||||
if (!TryComp<UserInterfaceComponent>(uid, out var cmp))
|
||||
return;
|
||||
|
||||
var uiKey = ev.UiKey;
|
||||
var message = ev.Message;
|
||||
message.Session = _playerManager.LocalSession!;
|
||||
message.Entity = GetNetEntity(uid);
|
||||
message.UiKey = uiKey;
|
||||
|
||||
// Raise as object so the correct type is used.
|
||||
RaiseLocalEvent(uid, (object)message, true);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case OpenBoundInterfaceMessage _:
|
||||
TryOpenUi(uid, uiKey, cmp);
|
||||
break;
|
||||
|
||||
case CloseBoundInterfaceMessage _:
|
||||
TryCloseUi(message.Session, uid, uiKey, remoteCall: true, uiComp: cmp);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (cmp.OpenInterfaces.TryGetValue(uiKey, out var bui))
|
||||
bui.InternalReceiveMessage(message);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryOpenUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? uiComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref uiComp))
|
||||
return false;
|
||||
|
||||
if (uiComp.OpenInterfaces.ContainsKey(uiKey))
|
||||
return false;
|
||||
|
||||
var data = uiComp.MappedInterfaceData[uiKey];
|
||||
|
||||
// TODO: This type should be cached, but I'm too lazy.
|
||||
var type = _reflectionManager.LooseGetType(data.ClientType);
|
||||
var boundInterface =
|
||||
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new object[] {uid, uiKey});
|
||||
|
||||
boundInterface.Open();
|
||||
uiComp.OpenInterfaces[uiKey] = boundInterface;
|
||||
|
||||
if (_playerManager.LocalSession is { } playerSession)
|
||||
{
|
||||
uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession);
|
||||
RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1329,23 +1329,8 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (comp, cur, next) in _compStateWork.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(comp, ref handleState);
|
||||
}
|
||||
#pragma warning disable CS0168 // Variable is declared but never used
|
||||
catch (Exception e)
|
||||
#pragma warning restore CS0168 // Variable is declared but never used
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
|
||||
#else
|
||||
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(comp, ref handleState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
("aPos", 0),
|
||||
("tCoord", 1),
|
||||
("modulate", 2)
|
||||
("tCoord2", 2),
|
||||
("modulate", 3)
|
||||
};
|
||||
|
||||
private const int UniIModUV = 0;
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
foreach (var (grid, chunks) in _mapChunkData)
|
||||
{
|
||||
var gridComp = _mapManager.GetGridComp(grid);
|
||||
var gridComp = _entityManager.GetComponent<MapGridComponent>(grid);
|
||||
foreach (var (index, chunk) in chunks)
|
||||
{
|
||||
if (!chunk.Dirty || gridComp.Chunks.ContainsKey(index))
|
||||
|
||||
@@ -251,10 +251,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
|
||||
{
|
||||
var mapId = eye.Position.MapId;
|
||||
if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId))
|
||||
{
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
|
||||
var worldOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceEntities);
|
||||
|
||||
@@ -23,9 +23,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
// Colour Modulation.
|
||||
GL.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
|
||||
// Texture Coords (2).
|
||||
GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(2);
|
||||
// Colour Modulation.
|
||||
GL.VertexAttribPointer(3, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 6 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(3);
|
||||
}
|
||||
|
||||
// NOTE: This is:
|
||||
@@ -37,6 +40,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
public readonly Vector2 Position;
|
||||
public readonly Vector2 TextureCoordinates;
|
||||
public readonly Vector2 TextureCoordinates2;
|
||||
// Note that this color is in linear space.
|
||||
public readonly Color Modulate;
|
||||
|
||||
@@ -48,6 +52,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Modulate = modulate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(Vector2 position, Vector2 textureCoordinates, Vector2 textureCoordinates2, Color modulate)
|
||||
{
|
||||
Position = position;
|
||||
TextureCoordinates = textureCoordinates;
|
||||
TextureCoordinates2 = textureCoordinates2;
|
||||
Modulate = modulate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(float x, float y, float u, float v, float r, float g, float b, float a)
|
||||
: this(new Vector2(x, y), new Vector2(u, v), new Color(r, g, b, a))
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
private RenderHandle _renderHandle = default!;
|
||||
|
||||
private sealed class RenderHandle : IRenderHandle
|
||||
internal sealed class RenderHandle : IRenderHandle
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
private readonly IEntityManager _entities;
|
||||
@@ -88,16 +88,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var clydeTexture = ExtractTexture(texture, in subRegion, out var csr);
|
||||
|
||||
var (w, h) = clydeTexture.Size;
|
||||
var sr = new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
|
||||
var sr = WorldTextureBoundsToUV(clydeTexture, csr);
|
||||
|
||||
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
|
||||
}
|
||||
|
||||
internal static Box2 WorldTextureBoundsToUV(ClydeTexture texture, UIBox2 csr)
|
||||
{
|
||||
var (w, h) = texture.Size;
|
||||
return new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a subRegion (px) into texture coords (0-1) of a given texture (cells of the textureAtlas).
|
||||
/// </summary>
|
||||
private static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
|
||||
internal static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
|
||||
{
|
||||
if (texture is AtlasTexture atlas)
|
||||
{
|
||||
|
||||
@@ -578,10 +578,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// TODO: split batch if necessary.
|
||||
var vIdx = BatchVertexIndex;
|
||||
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, modulate);
|
||||
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, modulate);
|
||||
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, modulate);
|
||||
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, modulate);
|
||||
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, new Vector2(0, 0), modulate);
|
||||
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, new Vector2(1, 0), modulate);
|
||||
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, new Vector2(1, 1), modulate);
|
||||
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, new Vector2(0, 1), modulate);
|
||||
BatchVertexIndex += 4;
|
||||
QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx);
|
||||
|
||||
|
||||
@@ -601,7 +601,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ClydeTexture : OwnedTexture
|
||||
internal sealed class ClydeTexture : OwnedTexture
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
public readonly bool IsSrgb;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
varying highp vec2 UV;
|
||||
varying highp vec2 UV2;
|
||||
varying highp vec2 Pos;
|
||||
varying highp vec4 VtxModulate;
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
/*layout (location = 0)*/ attribute vec2 aPos;
|
||||
// Texture coordinates.
|
||||
/*layout (location = 1)*/ attribute vec2 tCoord;
|
||||
/*layout (location = 2)*/ attribute vec2 tCoord2;
|
||||
// Colour modulation.
|
||||
/*layout (location = 2)*/ attribute vec4 modulate;
|
||||
/*layout (location = 3)*/ attribute vec4 modulate;
|
||||
|
||||
varying vec2 UV;
|
||||
varying vec2 UV2;
|
||||
varying vec2 Pos;
|
||||
varying vec4 VtxModulate;
|
||||
|
||||
@@ -36,5 +38,6 @@ void main()
|
||||
gl_Position = vec4(VERTEX, 0.0, 1.0);
|
||||
Pos = (VERTEX + 1.0) / 2.0;
|
||||
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
|
||||
UV2 = tCoord2;
|
||||
VtxModulate = zFromSrgb(modulate);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
varying highp vec2 UV;
|
||||
varying highp vec2 UV2;
|
||||
|
||||
uniform sampler2D lightMap;
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
/*layout (location = 0)*/ attribute vec2 aPos;
|
||||
// Texture coordinates.
|
||||
/*layout (location = 1)*/ attribute vec2 tCoord;
|
||||
/*layout (location = 2)*/ attribute vec2 tCoord2;
|
||||
// Colour modulation.
|
||||
/*layout (location = 2)*/ attribute vec4 modulate;
|
||||
/*layout (location = 3)*/ attribute vec4 modulate;
|
||||
|
||||
varying vec2 UV;
|
||||
varying vec2 UV2;
|
||||
|
||||
// Maybe we should merge these CPU side.
|
||||
// idk yet.
|
||||
@@ -40,6 +42,7 @@ void main()
|
||||
vec2 VERTEX = aPos;
|
||||
|
||||
UV = tCoord;
|
||||
UV2 = tCoord2;
|
||||
|
||||
// [SHADER_CODE]
|
||||
|
||||
|
||||
@@ -114,43 +114,12 @@ namespace Robust.Client.Graphics
|
||||
DrawPrimitives(primitiveTopology, White, indices, drawVertices);
|
||||
}
|
||||
|
||||
private static void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
|
||||
private void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
|
||||
{
|
||||
if (input.Length == 0)
|
||||
return;
|
||||
|
||||
if (input.Length != output.Length)
|
||||
Color colorLinear = Color.FromSrgb(color);
|
||||
for (var i = 0; i < output.Length; i++)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid lengths!");
|
||||
}
|
||||
|
||||
var colorLinear = Color.FromSrgb(color);
|
||||
var colorVec = Unsafe.As<Color, Vector128<float>>(ref colorLinear);
|
||||
var uvVec = Vector128.Create(0, 0, 0.5f, 0.5f);
|
||||
var maskVec = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0, 0).AsSingle();
|
||||
|
||||
var simdVectors = (nuint)(input.Length / 2);
|
||||
ref readonly var srcBase = ref Unsafe.As<Vector2, float>(ref Unsafe.AsRef(in input[0]));
|
||||
ref var dstBase = ref Unsafe.As<DrawVertexUV2DColor, float>(ref output[0]);
|
||||
|
||||
for (nuint i = 0; i < simdVectors; i++)
|
||||
{
|
||||
var positions = Vector128.LoadUnsafe(in srcBase, i * 4);
|
||||
|
||||
var posColorLower = (positions & maskVec) | uvVec;
|
||||
var posColorUpper = (Vector128.Shuffle(positions, Vector128.Create(2, 3, 0, 0)) & maskVec) | uvVec;
|
||||
|
||||
posColorLower.StoreUnsafe(ref dstBase, i * 16);
|
||||
colorVec.StoreUnsafe(ref dstBase, i * 16 + 4);
|
||||
posColorUpper.StoreUnsafe(ref dstBase, i * 16 + 8);
|
||||
colorVec.StoreUnsafe(ref dstBase, i * 16 + 12);
|
||||
}
|
||||
|
||||
var lastPos = (int)simdVectors * 2;
|
||||
if (lastPos != output.Length)
|
||||
{
|
||||
// Odd number of vertices. Handle the last manually.
|
||||
output[lastPos] = new DrawVertexUV2DColor(input[lastPos], new Vector2(0.5f, 0.5f), colorLinear);
|
||||
output[i] = new DrawVertexUV2DColor(input[i], new Vector2(0.5f, 0.5f), colorLinear);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +237,8 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
public Vector2 Position;
|
||||
public Vector2 UV;
|
||||
public Vector2 UV2;
|
||||
|
||||
/// <summary>
|
||||
/// Modulation colour for this vertex.
|
||||
/// Note that this color is in linear space.
|
||||
|
||||
@@ -17,7 +17,7 @@ using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
[Prototype("shader")]
|
||||
public sealed class ShaderPrototype : IPrototype, ISerializationHooks
|
||||
public sealed partial class ShaderPrototype : IPrototype, ISerializationHooks
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
|
||||
@@ -88,7 +88,6 @@ public sealed partial class ReplayLoadManager
|
||||
if (initMessages != null)
|
||||
UpdateMessages(initMessages, uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
|
||||
UpdateMessages(messages[0], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
|
||||
ProcessQueue(GameTick.MaxValue, detachQueue, detached);
|
||||
|
||||
var entSpan = state0.EntityStates.Value;
|
||||
Dictionary<NetEntity, EntityState> entStates = new(entSpan.Count);
|
||||
@@ -98,6 +97,8 @@ public sealed partial class ReplayLoadManager
|
||||
entStates.Add(entState.NetEntity, modifiedState);
|
||||
}
|
||||
|
||||
ProcessQueue(GameTick.MaxValue, detachQueue, detached, entStates);
|
||||
|
||||
await callback(0, states.Count, LoadingState.ProcessingFiles, true);
|
||||
var playerSpan = state0.PlayerStates.Value;
|
||||
Dictionary<NetUserId, SessionState> playerStates = new(playerSpan.Count);
|
||||
@@ -144,7 +145,7 @@ public sealed partial class ReplayLoadManager
|
||||
UpdatePlayerStates(curState.PlayerStates.Span, playerStates);
|
||||
UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached);
|
||||
UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase);
|
||||
ProcessQueue(curState.ToSequence, detachQueue, detached);
|
||||
ProcessQueue(curState.ToSequence, detachQueue, detached, entStates);
|
||||
UpdateDeletions(curState.EntityDeletions, entStates, detached);
|
||||
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
|
||||
ticksSinceLastCheckpoint++;
|
||||
@@ -176,14 +177,28 @@ public sealed partial class ReplayLoadManager
|
||||
private void ProcessQueue(
|
||||
GameTick curTick,
|
||||
Dictionary<GameTick, List<NetEntity>> detachQueue,
|
||||
HashSet<NetEntity> detached)
|
||||
HashSet<NetEntity> detached,
|
||||
Dictionary<NetEntity, EntityState> entStates)
|
||||
{
|
||||
foreach (var (tick, ents) in detachQueue)
|
||||
{
|
||||
if (tick > curTick)
|
||||
continue;
|
||||
detachQueue.Remove(tick);
|
||||
detached.UnionWith(ents);
|
||||
|
||||
foreach (var e in ents)
|
||||
{
|
||||
if (entStates.ContainsKey(e))
|
||||
detached.Add(e);
|
||||
else
|
||||
{
|
||||
// AFAIK this should only happen if the client skipped over some ticks, probably due to packet loss
|
||||
// I.e., entity was created on tick n, then leaves PVS range on the tick n+1
|
||||
// If the n-th tick gets dropped, the client only ever receives the pvs-leave message.
|
||||
// In that case we should just ignore it.
|
||||
_sawmill.Debug($"Received a PVS detach msg for entity {e} before it was received?");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,13 +79,14 @@ internal sealed partial class ReplayPlaybackManager
|
||||
if (checkpoint.DetachedStates == null)
|
||||
return;
|
||||
|
||||
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length); ;
|
||||
var metas = _entMan.GetEntityQuery<MetaDataComponent>();
|
||||
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length);
|
||||
foreach (var es in checkpoint.DetachedStates)
|
||||
{
|
||||
var uid = _entMan.GetEntity(es.NetEntity);
|
||||
if (metas.TryGetComponent(uid, out var meta) && !meta.EntityDeleted)
|
||||
if (_entMan.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
{
|
||||
DebugTools.Assert(!meta.EntityDeleted);
|
||||
continue;
|
||||
}
|
||||
|
||||
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?
|
||||
.FirstOrDefault(c => c.NetID == _metaId).State;
|
||||
@@ -93,18 +94,16 @@ internal sealed partial class ReplayPlaybackManager
|
||||
if (metaState == null)
|
||||
throw new MissingMetadataException(es.NetEntity);
|
||||
|
||||
_entMan.CreateEntityUninitialized(metaState.PrototypeId, uid);
|
||||
meta = metas.GetComponent(uid);
|
||||
uid = _entMan.CreateEntity(metaState.PrototypeId, out meta);
|
||||
|
||||
// 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.
|
||||
_entMan.ClearNetEntity(meta.NetEntity);
|
||||
_entMan.SetNetEntity(uid.Value, es.NetEntity, meta);
|
||||
|
||||
_entMan.SetNetEntity(uid, es.NetEntity, meta);
|
||||
|
||||
_entMan.InitializeEntity(uid, meta);
|
||||
_entMan.StartEntity(uid);
|
||||
_entMan.InitializeEntity(uid.Value, meta);
|
||||
_entMan.StartEntity(uid.Value);
|
||||
meta.LastStateApplied = checkpoint.Tick;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -142,6 +143,26 @@ namespace Robust.Client.ResourceManagement
|
||||
}
|
||||
});
|
||||
|
||||
// Do not meta-atlas RSIs with custom load parameters.
|
||||
var atlasList = rsiList.Where(x => x.LoadParameters == TextureLoadParameters.Default).ToArray();
|
||||
var nonAtlasList = rsiList.Where(x => x.LoadParameters != TextureLoadParameters.Default).ToArray();
|
||||
|
||||
foreach (var data in nonAtlasList)
|
||||
{
|
||||
if (data.Bad)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
RSIResource.LoadTexture(Clyde, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
|
||||
data.Bad = true;
|
||||
}
|
||||
}
|
||||
|
||||
// This combines individual RSI atlases into larger atlases to reduce draw batches. currently this is a VERY
|
||||
// lazy bundling and is not at all compact, its basically an atlas of RSI atlases. Really what this should
|
||||
// try to do is to have each RSI write directly to the atlas, rather than having each RSI write to its own
|
||||
@@ -155,7 +176,7 @@ namespace Robust.Client.ResourceManagement
|
||||
// TODO allow RSIs to opt out (useful for very big & rare RSIs)
|
||||
// TODO combine with (non-rsi) texture atlas?
|
||||
|
||||
Array.Sort(rsiList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0));
|
||||
Array.Sort(atlasList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0));
|
||||
|
||||
// Each RSI sub atlas has a different size.
|
||||
// Even if we iterate through them once to estimate total area, I have NFI how to sanely estimate an optimal square-texture size.
|
||||
@@ -167,9 +188,9 @@ namespace Robust.Client.ResourceManagement
|
||||
Vector2i offset = default;
|
||||
int finalized = -1;
|
||||
int atlasCount = 0;
|
||||
for (int i = 0; i < rsiList.Length; i++)
|
||||
for (int i = 0; i < atlasList.Length; i++)
|
||||
{
|
||||
var rsi = rsiList[i];
|
||||
var rsi = atlasList[i];
|
||||
if (rsi.Bad)
|
||||
continue;
|
||||
|
||||
@@ -200,14 +221,14 @@ namespace Robust.Client.ResourceManagement
|
||||
var height = offset.Y + deltaY;
|
||||
var croppedSheet = new Image<Rgba32>(maxSize, height);
|
||||
sheet.Blit(new UIBox2i(0, 0, maxSize, height), croppedSheet, default);
|
||||
FinalizeMetaAtlas(rsiList.Length - 1, croppedSheet);
|
||||
FinalizeMetaAtlas(atlasList.Length - 1, croppedSheet);
|
||||
|
||||
void FinalizeMetaAtlas(int toIndex, Image<Rgba32> sheet)
|
||||
{
|
||||
var atlas = Clyde.LoadTextureFromImage(sheet);
|
||||
for (int i = finalized + 1; i <= toIndex; i++)
|
||||
{
|
||||
var rsi = rsiList[i];
|
||||
var rsi = atlasList[i];
|
||||
rsi.AtlasTexture = atlas;
|
||||
}
|
||||
|
||||
@@ -255,9 +276,10 @@ namespace Robust.Client.ResourceManagement
|
||||
}
|
||||
|
||||
sawmill.Debug(
|
||||
"Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountErrored} errored) in {LoadTime}",
|
||||
"Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountNotAtlas} not atlassed, {CountErrored} errored) in {LoadTime}",
|
||||
rsiList.Length,
|
||||
atlasCount,
|
||||
nonAtlasList.Length,
|
||||
errors,
|
||||
sw.Elapsed);
|
||||
|
||||
|
||||
@@ -40,17 +40,21 @@ namespace Robust.Client.ResourceManagement
|
||||
var loadStepData = new LoadStepData {Path = path};
|
||||
var manager = dependencies.Resolve<IResourceManager>();
|
||||
LoadPreTexture(manager, loadStepData);
|
||||
|
||||
loadStepData.AtlasTexture = dependencies.Resolve<IClyde>().LoadTextureFromImage(
|
||||
loadStepData.AtlasSheet,
|
||||
loadStepData.Path.ToString());
|
||||
|
||||
LoadTexture(dependencies.Resolve<IClyde>(), loadStepData);
|
||||
LoadPostTexture(loadStepData);
|
||||
LoadFinish(dependencies.Resolve<IResourceCacheInternal>(), loadStepData);
|
||||
|
||||
loadStepData.AtlasSheet.Dispose();
|
||||
}
|
||||
|
||||
internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData)
|
||||
{
|
||||
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
|
||||
loadStepData.AtlasSheet,
|
||||
loadStepData.Path.ToString(),
|
||||
loadStepData.LoadParameters);
|
||||
}
|
||||
|
||||
internal static void LoadPreTexture(IResourceManager manager, LoadStepData data)
|
||||
{
|
||||
var manifestPath = data.Path / "meta.json";
|
||||
@@ -178,6 +182,7 @@ namespace Robust.Client.ResourceManagement
|
||||
data.FrameSize = frameSize;
|
||||
data.DimX = dimensionX;
|
||||
data.CallbackOffsets = callbackOffsets;
|
||||
data.LoadParameters = metadata.LoadParameters;
|
||||
}
|
||||
|
||||
internal static void LoadPostTexture(LoadStepData data)
|
||||
@@ -380,6 +385,7 @@ namespace Robust.Client.ResourceManagement
|
||||
public Texture AtlasTexture = default!;
|
||||
public Vector2i AtlasOffset;
|
||||
public RSI Rsi = default!;
|
||||
public TextureLoadParameters LoadParameters;
|
||||
}
|
||||
|
||||
internal struct StateReg
|
||||
|
||||
@@ -5,7 +5,6 @@ using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controllers;
|
||||
|
||||
// Notices your UIController, *UwU Whats this?*
|
||||
/// <summary>
|
||||
/// Each <see cref="UIController"/> is instantiated as a singleton by <see cref="UserInterfaceManager"/>
|
||||
/// <see cref="UIController"/> can use <see cref="DependencyAttribute"/> for regular IoC dependencies
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IClyde _displayManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IBaseClient _baseClient = default!;
|
||||
|
||||
private readonly StringBuilder _textBuilder = new();
|
||||
private readonly char[] _textBuffer = new char[1024];
|
||||
@@ -58,30 +59,36 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
|
||||
_textBuilder.Clear();
|
||||
|
||||
var isInGame = _baseClient.RunLevel.IsInGameLike();
|
||||
var mouseScreenPos = _inputManager.MouseScreenPosition;
|
||||
var screenSize = _displayManager.ScreenSize;
|
||||
var screenScale = _displayManager.MainWindow.ContentScale;
|
||||
|
||||
EntityCoordinates mouseGridPos;
|
||||
TileRef tile;
|
||||
EntityCoordinates mouseGridPos = default;
|
||||
TileRef tile = default;
|
||||
MapCoordinates mouseWorldMap = default;
|
||||
|
||||
var mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
|
||||
if (mouseWorldMap == MapCoordinates.Nullspace)
|
||||
return;
|
||||
|
||||
var mapSystem = _entityManager.System<SharedMapSystem>();
|
||||
var xformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
|
||||
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
|
||||
if (isInGame)
|
||||
{
|
||||
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
|
||||
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
|
||||
mouseWorldMap.Position);
|
||||
tile = new TileRef(EntityUid.Invalid, mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);
|
||||
mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
|
||||
if (mouseWorldMap != MapCoordinates.Nullspace)
|
||||
{
|
||||
var mapSystem = _entityManager.System<SharedMapSystem>();
|
||||
var xformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
|
||||
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
|
||||
{
|
||||
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
|
||||
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
|
||||
mouseWorldMap.Position);
|
||||
tile = new TileRef(EntityUid.Invalid,
|
||||
mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var controlHovered = UserInterfaceManager.CurrentlyHovered;
|
||||
@@ -95,32 +102,37 @@ Mouse Pos:
|
||||
{tile}
|
||||
GUI: {controlHovered}");
|
||||
|
||||
_textBuilder.AppendLine("\nAttached NetEntity:");
|
||||
var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid;
|
||||
|
||||
if (controlledEntity == EntityUid.Invalid)
|
||||
if (isInGame)
|
||||
{
|
||||
_textBuilder.AppendLine("No attached netentity.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var entityTransform = _entityManager.GetComponent<TransformComponent>(controlledEntity);
|
||||
var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform);
|
||||
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
|
||||
var xformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
|
||||
var playerCoordinates = entityTransform.Coordinates;
|
||||
var playerRotation = xformSystem.GetWorldRotation(entityTransform);
|
||||
var gridRotation = entityTransform.GridUid != null
|
||||
? xformSystem.GetWorldRotation(entityTransform.GridUid.Value)
|
||||
: Angle.Zero;
|
||||
_textBuilder.AppendLine("\nAttached NetEntity:");
|
||||
var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid;
|
||||
|
||||
_textBuilder.Append($@" Screen: {playerScreen}
|
||||
if (controlledEntity == EntityUid.Invalid)
|
||||
{
|
||||
_textBuilder.AppendLine("No attached netentity.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var entityTransform = _entityManager.GetComponent<TransformComponent>(controlledEntity);
|
||||
var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform);
|
||||
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
|
||||
|
||||
var playerCoordinates = entityTransform.Coordinates;
|
||||
var playerRotation = xformSystem.GetWorldRotation(entityTransform);
|
||||
var gridRotation = entityTransform.GridUid != null
|
||||
? xformSystem.GetWorldRotation(entityTransform.GridUid.Value)
|
||||
: Angle.Zero;
|
||||
|
||||
_textBuilder.Append($@" Screen: {playerScreen}
|
||||
{playerWorldOffset}
|
||||
{_entityManager.GetNetCoordinates(playerCoordinates)}
|
||||
Rotation: {playerRotation.Degrees:F2}°
|
||||
NEntId: {_entityManager.GetNetEntity(controlledEntity)}
|
||||
Grid NEntId: {_entityManager.GetNetEntity(entityTransform.GridUid)}
|
||||
Grid Rotation: {gridRotation.Degrees:F2}°");
|
||||
}
|
||||
}
|
||||
|
||||
_contents.TextMemory = FormatHelpers.BuilderToMemory(_textBuilder, _textBuffer);
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.Utility;
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
[Prototype("font")]
|
||||
public sealed class FontPrototype : IPrototype
|
||||
public sealed partial class FontPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -18,7 +16,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Client.UserInterface.Themes;
|
||||
|
||||
[Prototype("uiTheme")]
|
||||
public sealed class UITheme : IPrototype
|
||||
public sealed partial class UITheme : IPrototype
|
||||
{
|
||||
private IResourceCache? _cache;
|
||||
private IUserInterfaceManager? _uiMan;
|
||||
|
||||
@@ -70,6 +70,11 @@ namespace Robust.Server.Console.Commands
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
if (!_map.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError($"map {args[0]} does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell.Player == null)
|
||||
{
|
||||
@@ -110,13 +115,6 @@ namespace Robust.Server.Console.Commands
|
||||
|
||||
private void SetupPlayer(MapId mapId, IConsoleShell shell)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
{
|
||||
_map.CreateMap(mapId);
|
||||
}
|
||||
|
||||
_map.SetMapPaused(mapId, false);
|
||||
var mapUid = _map.GetMapEntityIdOrThrow(mapId);
|
||||
_ent.System<Gravity2DController>().SetGravity(mapUid, new Vector2(0, -9.8f));
|
||||
|
||||
@@ -49,7 +49,6 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
private ISawmill _logLoader = default!;
|
||||
private ISawmill _logWriter = default!;
|
||||
|
||||
private static readonly MapLoadOptions DefaultLoadOptions = new();
|
||||
private const int MapFormatVersion = 6;
|
||||
private const int BackwardsVersion = 2;
|
||||
|
||||
@@ -132,7 +131,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
public bool TryLoad(MapId mapId, string path, [NotNullWhen(true)] out IReadOnlyList<EntityUid>? rootUids,
|
||||
MapLoadOptions? options = null)
|
||||
{
|
||||
options ??= DefaultLoadOptions;
|
||||
options ??= new();
|
||||
|
||||
var resPath = new ResPath(path).ToRootedPath();
|
||||
|
||||
@@ -658,11 +657,13 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
// We just need to cache the old mapuid and point to the new mapuid.
|
||||
|
||||
if (HasComp<MapComponent>(rootNode))
|
||||
if (TryComp(rootNode, out MapComponent? mapComp))
|
||||
{
|
||||
// If map exists swap out
|
||||
if (_mapManager.MapExists(data.TargetMap))
|
||||
if (_mapSystem.TryGetMap(data.TargetMap, out var existing))
|
||||
{
|
||||
data.Options.DoMapInit |= _mapSystem.IsInitialized(data.TargetMap);
|
||||
data.MapIsPaused = _mapSystem.IsPaused(existing.Value);
|
||||
// Map exists but we also have a map file with stuff on it soooo swap out the old map.
|
||||
if (data.Options.LoadMap)
|
||||
{
|
||||
@@ -675,26 +676,28 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
data.Options.Rotation = Angle.Zero;
|
||||
}
|
||||
|
||||
_mapManager.SetMapEntity(data.TargetMap, rootNode);
|
||||
Del(existing);
|
||||
EnsureComp<LoadedMapComponent>(rootNode);
|
||||
|
||||
mapComp.MapId = data.TargetMap;
|
||||
DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing);
|
||||
}
|
||||
// Otherwise just ignore the map in the file.
|
||||
else
|
||||
{
|
||||
var oldRootUid = data.Entities[0];
|
||||
var newRootUid = _mapManager.GetMapEntityId(data.TargetMap);
|
||||
data.Entities[0] = newRootUid;
|
||||
data.Entities[0] = existing.Value;
|
||||
|
||||
foreach (var ent in data.Entities)
|
||||
{
|
||||
if (ent == newRootUid)
|
||||
if (ent == existing)
|
||||
continue;
|
||||
|
||||
var xform = xformQuery.GetComponent(ent);
|
||||
|
||||
if (!xform.ParentUid.IsValid() || xform.ParentUid.Equals(oldRootUid))
|
||||
{
|
||||
_transform.SetParent(ent, xform, newRootUid);
|
||||
_transform.SetParent(ent, xform, existing.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,16 +706,9 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're loading a file with a map then swap out the entityuid
|
||||
// TODO: Mapmanager nonsense
|
||||
var AAAAA = _mapManager.CreateMap(data.TargetMap);
|
||||
|
||||
if (!data.MapIsPostInit)
|
||||
{
|
||||
_mapManager.AddUninitializedMap(data.TargetMap);
|
||||
}
|
||||
|
||||
_mapManager.SetMapEntity(data.TargetMap, rootNode);
|
||||
data.MapIsPaused = !data.MapIsPostInit;
|
||||
mapComp.MapId = data.TargetMap;
|
||||
DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing);
|
||||
EnsureComp<LoadedMapComponent>(rootNode);
|
||||
|
||||
// Nothing should have invalid uid except for the root node.
|
||||
@@ -721,17 +717,15 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
else
|
||||
{
|
||||
// No map file root, in that case create a new map / get the one we're loading onto.
|
||||
var mapNode = _mapManager.GetMapEntityId(data.TargetMap);
|
||||
|
||||
if (!mapNode.IsValid())
|
||||
if (!_mapSystem.TryGetMap(data.TargetMap, out var mapNode))
|
||||
{
|
||||
// Map doesn't exist so we'll start it up now so we can re-attach the preinit entities to it for later.
|
||||
_mapManager.CreateMap(data.TargetMap);
|
||||
_mapManager.AddUninitializedMap(data.TargetMap);
|
||||
mapNode = _mapManager.GetMapEntityId(data.TargetMap);
|
||||
DebugTools.Assert(mapNode.IsValid());
|
||||
mapNode = _mapSystem.CreateMap(data.TargetMap, false);
|
||||
}
|
||||
|
||||
data.Options.DoMapInit |= _mapSystem.IsInitialized(data.TargetMap);
|
||||
data.MapIsPaused = _mapSystem.IsPaused(mapNode.Value);
|
||||
|
||||
// If anything has an invalid parent (e.g. it's some form of root node) then parent it to the map.
|
||||
foreach (var ent in data.Entities)
|
||||
{
|
||||
@@ -743,12 +737,11 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
|
||||
if (!xform.ParentUid.IsValid())
|
||||
{
|
||||
_transform.SetParent(ent, xform, mapNode);
|
||||
_transform.SetParent(ent, xform, mapNode.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.MapIsPaused = _mapManager.IsMapPaused(data.TargetMap);
|
||||
_logLoader.Debug($"Swapped out root node in {_stopwatch.Elapsed}");
|
||||
}
|
||||
|
||||
@@ -896,7 +889,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
{
|
||||
EntityManager.SetLifeStage(metadata, EntityLifeStage.MapInitialized);
|
||||
}
|
||||
else if (_mapManager.IsMapInitialized(data.TargetMap))
|
||||
else if (data.Options.DoMapInit)
|
||||
{
|
||||
_serverEntityManager.RunMapInit(uid, metadata);
|
||||
}
|
||||
@@ -964,7 +957,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
// Yes, post-init maps do not have EntityLifeStage >= EntityLifeStage.MapInitialized
|
||||
bool postInit;
|
||||
if (TryComp(uid, out MapComponent? mapComp))
|
||||
postInit = !mapComp.MapPreInit;
|
||||
postInit = mapComp.MapInitialized;
|
||||
else
|
||||
postInit = metadata.EntityLifeStage >= EntityLifeStage.MapInitialized;
|
||||
|
||||
@@ -1098,17 +1091,17 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSaveable(EntityUid uid, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> transformQuery)
|
||||
private bool IsSaveable(EntityUid uid)
|
||||
{
|
||||
// Don't serialize things parented to un savable things.
|
||||
// For example clothes inside a person.
|
||||
while (uid.IsValid())
|
||||
{
|
||||
var meta = metaQuery.GetComponent(uid);
|
||||
var meta = MetaData(uid);
|
||||
|
||||
if (meta.EntityDeleted || meta.EntityPrototype?.MapSavable == false) break;
|
||||
|
||||
uid = transformQuery.GetComponent(uid).ParentUid;
|
||||
uid = Transform(uid).ParentUid;
|
||||
}
|
||||
|
||||
// If we manage to get up to the map (root node) then it's saveable.
|
||||
@@ -1123,7 +1116,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
EntityQuery<TransformComponent> transformQuery,
|
||||
EntityQuery<MapSaveIdComponent> saveCompQuery)
|
||||
{
|
||||
if (!IsSaveable(uid, metaQuery, transformQuery))
|
||||
if (!IsSaveable(uid))
|
||||
return;
|
||||
|
||||
entities.Add(uid);
|
||||
|
||||
@@ -5,9 +5,9 @@ using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Events;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
@@ -18,6 +18,16 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
private bool _deleteEmptyGrids;
|
||||
|
||||
protected override MapId GetNextMapId()
|
||||
{
|
||||
var id = new MapId(++LastMapId);
|
||||
while (MapManager.MapExists(id))
|
||||
{
|
||||
id = new MapId(++LastMapId);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
protected override void UpdatePvsChunks(Entity<TransformComponent, MetaDataComponent> grid)
|
||||
{
|
||||
_pvs.GridParentChanged(grid);
|
||||
@@ -31,11 +41,6 @@ namespace Robust.Server.GameObjects
|
||||
Subs.CVar(_cfg, CVars.GameDeleteEmptyGrids, SetGridDeletion, true);
|
||||
}
|
||||
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
}
|
||||
|
||||
private void SetGridDeletion(bool value)
|
||||
{
|
||||
_deleteEmptyGrids = value;
|
||||
|
||||
@@ -1,416 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using System.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
[Dependency] private readonly TransformSystem _xformSys = default!;
|
||||
|
||||
private EntityQuery<IgnoreUIRangeComponent> _ignoreUIRangeQuery;
|
||||
|
||||
private readonly List<ICommonSession> _sessionCache = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<BoundUIWrapMessage>(OnMessageReceived);
|
||||
_playerMan.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
|
||||
_ignoreUIRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_playerMan.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args)
|
||||
{
|
||||
if (args.NewStatus != SessionStatus.Disconnected)
|
||||
return;
|
||||
|
||||
if (!OpenInterfaces.TryGetValue(args.Session, out var buis))
|
||||
return;
|
||||
|
||||
foreach (var bui in buis.ToArray())
|
||||
{
|
||||
CloseShared(bui, args.Session);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var query = AllEntityQuery<ActiveUserInterfaceComponent, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var activeUis, out var xform))
|
||||
{
|
||||
foreach (var ui in activeUis.Interfaces)
|
||||
{
|
||||
CheckRange(uid, activeUis, ui, xform, xformQuery);
|
||||
|
||||
if (!ui.StateDirty)
|
||||
continue;
|
||||
|
||||
ui.StateDirty = false;
|
||||
|
||||
foreach (var (player, state) in ui.PlayerStateOverrides)
|
||||
{
|
||||
RaiseNetworkEvent(state, player.Channel);
|
||||
}
|
||||
|
||||
if (ui.LastStateMsg == null)
|
||||
continue;
|
||||
|
||||
foreach (var session in ui.SubscribedSessions)
|
||||
{
|
||||
if (!ui.PlayerStateOverrides.ContainsKey(session))
|
||||
RaiseNetworkEvent(ui.LastStateMsg, session.Channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the subscribed clients are still in range of the interface.
|
||||
/// </summary>
|
||||
private void CheckRange(EntityUid uid, ActiveUserInterfaceComponent activeUis, PlayerBoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
|
||||
{
|
||||
if (ui.InteractionRange <= 0)
|
||||
return;
|
||||
|
||||
// We have to cache the set of sessions because Unsubscribe modifies the original.
|
||||
_sessionCache.Clear();
|
||||
_sessionCache.AddRange(ui.SubscribedSessions);
|
||||
|
||||
var uiPos = _xformSys.GetWorldPosition(transform, query);
|
||||
var uiMap = transform.MapID;
|
||||
|
||||
foreach (var session in _sessionCache)
|
||||
{
|
||||
// The component manages the set of sessions, so this invalid session should be removed soon.
|
||||
if (!query.TryGetComponent(session.AttachedEntity, out var xform))
|
||||
continue;
|
||||
|
||||
if (_ignoreUIRangeQuery.HasComponent(session.AttachedEntity))
|
||||
continue;
|
||||
|
||||
// Handle pluggable BoundUserInterfaceCheckRangeEvent
|
||||
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, ui, session);
|
||||
RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true);
|
||||
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Pass)
|
||||
continue;
|
||||
|
||||
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Fail)
|
||||
{
|
||||
CloseUi(ui, session, activeUis);
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(checkRangeEvent.Result == BoundUserInterfaceRangeResult.Default);
|
||||
|
||||
if (uiMap != xform.MapID)
|
||||
{
|
||||
CloseUi(ui, session, activeUis);
|
||||
continue;
|
||||
}
|
||||
|
||||
var distanceSquared = (uiPos - _xformSys.GetWorldPosition(xform, query)).LengthSquared();
|
||||
if (distanceSquared > ui.InteractionRangeSqrd)
|
||||
CloseUi(ui, session, activeUis);
|
||||
}
|
||||
}
|
||||
|
||||
#region Get BUI
|
||||
|
||||
public bool HasUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
return ui.Interfaces.ContainsKey(uiKey);
|
||||
}
|
||||
|
||||
public PlayerBoundUserInterface GetUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!Resolve(uid, ref ui))
|
||||
throw new InvalidOperationException($"Cannot get {typeof(PlayerBoundUserInterface)} from an entity without {typeof(UserInterfaceComponent)}!");
|
||||
|
||||
return ui.Interfaces[uiKey];
|
||||
}
|
||||
|
||||
public PlayerBoundUserInterface? GetUiOrNull(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
return TryGetUi(uid, uiKey, out var bui, ui)
|
||||
? bui
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return UIs a session has open.
|
||||
/// Null if empty.
|
||||
/// </summary>
|
||||
public List<PlayerBoundUserInterface>? GetAllUIsForSession(ICommonSession session)
|
||||
{
|
||||
OpenInterfaces.TryGetValue(session, out var value);
|
||||
return value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public bool IsUiOpen(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
return bui.SubscribedSessions.Count > 0;
|
||||
}
|
||||
|
||||
public bool SessionHasOpenUi(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
return bui.SubscribedSessions.Contains(session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a state. This can be used for stateful UI updating.
|
||||
/// This state is sent to all clients, and automatically sent to all new clients when they open the UI.
|
||||
/// Pretty much how NanoUI did it back in ye olde BYOND.
|
||||
/// </summary>
|
||||
/// <param name="state">
|
||||
/// The state object that will be sent to all current and future client.
|
||||
/// This can be null.
|
||||
/// </param>
|
||||
/// <param name="session">
|
||||
/// The player session to send this new state to.
|
||||
/// Set to null for sending it to every subscribed player session.
|
||||
/// </param>
|
||||
public bool TrySetUiState(EntityUid uid,
|
||||
Enum uiKey,
|
||||
BoundUserInterfaceState state,
|
||||
ICommonSession? session = null,
|
||||
UserInterfaceComponent? ui = null,
|
||||
bool clearOverrides = true)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
SetUiState(bui, state, session, clearOverrides);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a state. This can be used for stateful UI updating.
|
||||
/// This state is sent to all clients, and automatically sent to all new clients when they open the UI.
|
||||
/// Pretty much how NanoUI did it back in ye olde BYOND.
|
||||
/// </summary>
|
||||
/// <param name="state">
|
||||
/// The state object that will be sent to all current and future client.
|
||||
/// This can be null.
|
||||
/// </param>
|
||||
/// <param name="session">
|
||||
/// The player session to send this new state to.
|
||||
/// Set to null for sending it to every subscribed player session.
|
||||
/// </param>
|
||||
public void SetUiState(PlayerBoundUserInterface bui, BoundUserInterfaceState state, ICommonSession? session = null, bool clearOverrides = true)
|
||||
{
|
||||
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), new UpdateBoundStateMessage(state), bui.UiKey);
|
||||
if (session == null)
|
||||
{
|
||||
bui.LastStateMsg = msg;
|
||||
if (clearOverrides)
|
||||
bui.PlayerStateOverrides.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
bui.PlayerStateOverrides[session] = msg;
|
||||
}
|
||||
|
||||
bui.StateDirty = true;
|
||||
}
|
||||
|
||||
#region Close
|
||||
protected override void CloseShared(PlayerBoundUserInterface bui, ICommonSession session, ActiveUserInterfaceComponent? activeUis = null)
|
||||
{
|
||||
var owner = bui.Owner;
|
||||
bui._subscribedSessions.Remove(session);
|
||||
bui.PlayerStateOverrides.Remove(session);
|
||||
|
||||
if (OpenInterfaces.TryGetValue(session, out var buis))
|
||||
buis.Remove(bui);
|
||||
|
||||
RaiseLocalEvent(owner, new BoundUIClosedEvent(bui.UiKey, owner, session));
|
||||
|
||||
if (bui._subscribedSessions.Count == 0)
|
||||
DeactivateInterface(bui.Owner, bui, activeUis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes this all interface for any clients that have any open.
|
||||
/// </summary>
|
||||
public bool TryCloseAll(EntityUid uid, Shared.GameObjects.ActiveUserInterfaceComponent? aui = null)
|
||||
{
|
||||
if (!Resolve(uid, ref aui, false))
|
||||
return false;
|
||||
|
||||
foreach (var ui in aui.Interfaces)
|
||||
{
|
||||
CloseAll(ui);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes this specific interface for any clients that have it open.
|
||||
/// </summary>
|
||||
public bool TryCloseAll(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
CloseAll(bui);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes this interface for any clients that have it open.
|
||||
/// </summary>
|
||||
public void CloseAll(PlayerBoundUserInterface bui)
|
||||
{
|
||||
foreach (var session in bui.SubscribedSessions.ToArray())
|
||||
{
|
||||
CloseUi(bui, session);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SendMessage
|
||||
|
||||
/// <summary>
|
||||
/// Send a BUI message to all connected player sessions.
|
||||
/// </summary>
|
||||
public bool TrySendUiMessage(EntityUid uid, Enum uiKey, BoundUserInterfaceMessage message, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
SendUiMessage(bui, message);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a BUI message to all connected player sessions.
|
||||
/// </summary>
|
||||
public void SendUiMessage(PlayerBoundUserInterface bui, BoundUserInterfaceMessage message)
|
||||
{
|
||||
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey);
|
||||
foreach (var session in bui.SubscribedSessions)
|
||||
{
|
||||
RaiseNetworkEvent(msg, session.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a BUI message to a specific player session.
|
||||
/// </summary>
|
||||
public bool TrySendUiMessage(EntityUid uid, Enum uiKey, BoundUserInterfaceMessage message, ICommonSession session, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
return TrySendUiMessage(bui, message, session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a BUI message to a specific player session.
|
||||
/// </summary>
|
||||
public bool TrySendUiMessage(PlayerBoundUserInterface bui, BoundUserInterfaceMessage message, ICommonSession session)
|
||||
{
|
||||
if (!bui.SubscribedSessions.Contains(session))
|
||||
return false;
|
||||
|
||||
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey), session.Channel);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised by <see cref="UserInterfaceSystem"/> to check whether an interface is still accessible by its user.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
[PublicAPI]
|
||||
public struct BoundUserInterfaceCheckRangeEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity owning the UI being checked for.
|
||||
/// </summary>
|
||||
public readonly EntityUid Target;
|
||||
|
||||
/// <summary>
|
||||
/// The UI itself.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public readonly PlayerBoundUserInterface UserInterface;
|
||||
|
||||
/// <summary>
|
||||
/// The player for which the UI is being checked.
|
||||
/// </summary>
|
||||
public readonly ICommonSession Player;
|
||||
|
||||
/// <summary>
|
||||
/// The result of the range check.
|
||||
/// </summary>
|
||||
public BoundUserInterfaceRangeResult Result;
|
||||
|
||||
public BoundUserInterfaceCheckRangeEvent(
|
||||
EntityUid target,
|
||||
PlayerBoundUserInterface userInterface,
|
||||
ICommonSession player)
|
||||
{
|
||||
Target = target;
|
||||
UserInterface = userInterface;
|
||||
Player = player;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible results for a <see cref="BoundUserInterfaceCheckRangeEvent"/>.
|
||||
/// </summary>
|
||||
public enum BoundUserInterfaceRangeResult : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Run built-in range check.
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Range check passed, UI is accessible.
|
||||
/// </summary>
|
||||
Pass,
|
||||
|
||||
/// <summary>
|
||||
/// Range check failed, UI is inaccessible.
|
||||
/// </summary>
|
||||
Fail
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Robust.Server.GameObjects
|
||||
StartEntity(entity);
|
||||
}
|
||||
|
||||
private protected override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
||||
internal override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
||||
{
|
||||
if (prototypeName == null)
|
||||
return base.CreateEntity(prototypeName, out metadata, context);
|
||||
|
||||
@@ -53,5 +53,7 @@ namespace Robust.Server.Maps
|
||||
/// This should be set to false if you want to load a map file onto an existing map and do not wish to overwrite the existing entity.
|
||||
/// </remarks>
|
||||
public bool LoadMap { get; set; } = true;
|
||||
|
||||
public bool DoMapInit = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Shared.Audio;
|
||||
/// to allow the server to know audio lengths without shipping the large audio files themselves.
|
||||
/// </summary>
|
||||
[Prototype(ProtoName)]
|
||||
public sealed class AudioMetadataPrototype : IPrototype
|
||||
public sealed partial class AudioMetadataPrototype : IPrototype
|
||||
{
|
||||
public const string ProtoName = "audioMetadata";
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Robust.Shared.Audio;
|
||||
/// This can be used by <see cref="Content.Shared.Audio.SharedContentAudioSystem"/> to apply an audio preset.
|
||||
/// </summary>
|
||||
[Prototype("audioPreset")]
|
||||
public sealed class AudioPresetPrototype : IPrototype
|
||||
public sealed partial class AudioPresetPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Shared.Audio;
|
||||
|
||||
[Prototype("soundCollection")]
|
||||
public sealed class SoundCollectionPrototype : IPrototype
|
||||
public sealed partial class SoundCollectionPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
|
||||
@@ -851,7 +851,7 @@ namespace Robust.Shared
|
||||
/// See the documentation of the <see cref="Network.AuthMode"/> enum for values.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> AuthMode =
|
||||
CVarDef.Create("auth.mode", (int) Network.AuthMode.Optional, CVar.SERVERONLY);
|
||||
CVarDef.Create("auth.mode", (int) Network.AuthMode.Required, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Allow unauthenticated localhost connections, even if the auth mode is set to required.
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Robust.Shared.Console.Commands;
|
||||
|
||||
sealed class AddMapCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IMapManagerInternal _map = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public override string Command => "addmap";
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
@@ -24,11 +25,8 @@ sealed class AddMapCommand : LocalizedCommands
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
{
|
||||
_map.CreateMap(mapId);
|
||||
if (args.Length >= 2 && args[1] == "false")
|
||||
{
|
||||
_map.AddUninitializedMap(mapId);
|
||||
}
|
||||
var init = args.Length < 2 || !bool.Parse(args[1]);
|
||||
_entMan.System<SharedMapSystem>().CreateMap(mapId, runMapInit: init);
|
||||
|
||||
shell.WriteLine($"Map with ID {mapId} created.");
|
||||
return;
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\explorer.exe",
|
||||
FileName = "explorer.exe",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
|
||||
@@ -29,9 +29,50 @@ public sealed partial class PrototypeLayerData
|
||||
[DataField("map")] public HashSet<string>? MapKeys;
|
||||
[DataField("renderingStrategy")] public LayerRenderingStrategy? RenderingStrategy;
|
||||
|
||||
/// <summary>
|
||||
/// If set, indicates that this sprite layer should instead be used to copy into shader parameters on another layer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If set, this sprite layer is not rendered. Instead, the "result" of rendering it (exact sprite layer and such)
|
||||
/// are copied into the shader parameters of another object,
|
||||
/// specified by the <see cref="PrototypeCopyToShaderParameters"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The specified layer must have a shader set. When it does, the shader's
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Note that sprite layers are processed in-order, so to avoid 1-frame delays,
|
||||
/// the layer doing the copying should occur BEFORE the layer being copied into.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[DataField] public PrototypeCopyToShaderParameters? CopyToShaderParameters;
|
||||
|
||||
[DataField] public bool Cycle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores parameters for <see cref="PrototypeLayerData.CopyToShaderParameters"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable, DataDefinition]
|
||||
public sealed partial class PrototypeCopyToShaderParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The map key of the layer that will have its shader modified.
|
||||
/// </summary>
|
||||
[DataField(required: true)] public string LayerKey;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the shader parameter that will receive the actual selected texture.
|
||||
/// </summary>
|
||||
[DataField] public string? ParameterTexture;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the shader parameter that will receive UVs to select the sprite in <see cref="ParameterTexture"/>.
|
||||
/// </summary>
|
||||
[DataField] public string? ParameterUV;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum LayerRenderingStrategy
|
||||
{
|
||||
|
||||
@@ -101,7 +101,6 @@ namespace Robust.Shared.GameObjects
|
||||
internal bool _mapIdInitialized;
|
||||
internal bool _gridInitialized;
|
||||
|
||||
// TODO: Cache this.
|
||||
/// <summary>
|
||||
/// The EntityUid of the map which this object is on, if any.
|
||||
/// </summary>
|
||||
|
||||
@@ -41,14 +41,14 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Invoked when the server uses <c>SetState</c>.
|
||||
/// </summary>
|
||||
protected virtual void UpdateState(BoundUserInterfaceState state)
|
||||
protected internal virtual void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the server sends an arbitrary message.
|
||||
/// </summary>
|
||||
protected virtual void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
protected internal virtual void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
UiSystem.TryCloseUi(_playerManager.LocalSession, Owner, UiKey);
|
||||
UiSystem.CloseUi(Owner, UiKey, _playerManager.LocalEntity, predicted: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -65,7 +65,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public void SendMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
UiSystem.SendUiMessage(this, message);
|
||||
UiSystem.ClientSendUiMessage(Owner, UiKey, message);
|
||||
}
|
||||
|
||||
public void SendPredictedMessage(BoundUserInterfaceMessage message)
|
||||
@@ -73,20 +73,6 @@ namespace Robust.Shared.GameObjects
|
||||
UiSystem.SendPredictedUiMessage(this, message);
|
||||
}
|
||||
|
||||
internal void InternalReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case UpdateBoundStateMessage updateBoundStateMessage:
|
||||
State = updateBoundStateMessage.State;
|
||||
UpdateState(State);
|
||||
break;
|
||||
default:
|
||||
ReceiveMessage(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
~BoundUserInterface()
|
||||
{
|
||||
Dispose(false);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Lets any entities with this component ignore user interface range checks that would normally
|
||||
/// close the UI automatically.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class IgnoreUIRangeComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an entity-bound interface that can be opened by multiple players at once.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class PlayerBoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
public float InteractionRange;
|
||||
|
||||
[ViewVariables]
|
||||
public float InteractionRangeSqrd => InteractionRange * InteractionRange;
|
||||
|
||||
[ViewVariables]
|
||||
public Enum UiKey { get; }
|
||||
[ViewVariables]
|
||||
public EntityUid Owner { get; }
|
||||
|
||||
internal readonly HashSet<ICommonSession> _subscribedSessions = new();
|
||||
[ViewVariables]
|
||||
internal BoundUIWrapMessage? LastStateMsg;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool RequireInputValidation;
|
||||
|
||||
[ViewVariables]
|
||||
internal bool StateDirty;
|
||||
|
||||
[ViewVariables]
|
||||
internal readonly Dictionary<ICommonSession, BoundUIWrapMessage> PlayerStateOverrides =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// All of the sessions currently subscribed to this UserInterface.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IReadOnlySet<ICommonSession> SubscribedSessions => _subscribedSessions;
|
||||
|
||||
public PlayerBoundUserInterface(PrototypeData data, EntityUid owner)
|
||||
{
|
||||
RequireInputValidation = data.RequireInputValidation;
|
||||
UiKey = data.UiKey;
|
||||
Owner = owner;
|
||||
|
||||
InteractionRange = data.InteractionRange;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ActiveUserInterfaceComponent : Component
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class ActiveUserInterfaceComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public HashSet<PlayerBoundUserInterface> Interfaces = new();
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class ServerBoundUserInterfaceMessage
|
||||
{
|
||||
[ViewVariables]
|
||||
public BoundUserInterfaceMessage Message { get; }
|
||||
[ViewVariables]
|
||||
public ICommonSession Session { get; }
|
||||
[PublicAPI]
|
||||
public sealed class ServerBoundUserInterfaceMessage
|
||||
{
|
||||
[ViewVariables]
|
||||
public BoundUserInterfaceMessage Message { get; }
|
||||
[ViewVariables]
|
||||
public ICommonSession Session { get; }
|
||||
|
||||
public ServerBoundUserInterfaceMessage(BoundUserInterfaceMessage message, ICommonSession session)
|
||||
{
|
||||
Message = message;
|
||||
Session = session;
|
||||
}
|
||||
public ServerBoundUserInterfaceMessage(BoundUserInterfaceMessage message, ICommonSession session)
|
||||
{
|
||||
Message = message;
|
||||
Session = session;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,36 +8,51 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedUserInterfaceSystem))]
|
||||
public sealed partial class UserInterfaceComponent : Component
|
||||
{
|
||||
// TODO: Obviously clean this shit up, I just moved it into shared.
|
||||
/// <summary>
|
||||
/// The currently open interfaces. Used clientside to store the UI.
|
||||
/// </summary>
|
||||
[ViewVariables, Access(Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.ReadWriteExecute)]
|
||||
public readonly Dictionary<Enum, BoundUserInterface> ClientOpenInterfaces = new();
|
||||
|
||||
[ViewVariables] public readonly Dictionary<Enum, BoundUserInterface> OpenInterfaces = new();
|
||||
|
||||
[ViewVariables] public readonly Dictionary<Enum, PlayerBoundUserInterface> Interfaces = new();
|
||||
|
||||
public Dictionary<Enum, PrototypeData> MappedInterfaceData = new();
|
||||
[DataField]
|
||||
internal Dictionary<Enum, InterfaceData> Interfaces = new();
|
||||
|
||||
/// <summary>
|
||||
/// Loaded on Init from serialized data.
|
||||
/// Actors that currently have interfaces open.
|
||||
/// </summary>
|
||||
[DataField("interfaces")] internal List<PrototypeData> InterfaceData = new();
|
||||
[DataField]
|
||||
public Dictionary<Enum, List<EntityUid>> Actors = new();
|
||||
|
||||
/// <summary>
|
||||
/// Legacy data, new BUIs should be using comp states.
|
||||
/// </summary>
|
||||
public Dictionary<Enum, BoundUserInterfaceState> States = new();
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class UserInterfaceComponentState(
|
||||
Dictionary<Enum, List<NetEntity>> actors,
|
||||
Dictionary<Enum, BoundUserInterfaceState> states)
|
||||
: IComponentState
|
||||
{
|
||||
public Dictionary<Enum, List<NetEntity>> Actors = actors;
|
||||
|
||||
public Dictionary<Enum, BoundUserInterfaceState> States = states;
|
||||
}
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class PrototypeData
|
||||
public sealed partial class InterfaceData
|
||||
{
|
||||
[DataField("key", required: true)]
|
||||
public Enum UiKey { get; private set; } = default!;
|
||||
|
||||
[DataField("type", required: true)]
|
||||
public string ClientType { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum range before a BUI auto-closes. A non-positive number means there is no limit.
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
[DataField]
|
||||
public float InteractionRange = 2f;
|
||||
|
||||
// TODO BUI move to content?
|
||||
@@ -48,7 +63,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <remarks>
|
||||
/// Avoids requiring each system to individually validate client inputs. However, perhaps some BUIs are supposed to be bypass accessibility checks
|
||||
/// </remarks>
|
||||
[DataField("requireInputValidation")]
|
||||
[DataField]
|
||||
public bool RequireInputValidation = true;
|
||||
}
|
||||
|
||||
@@ -56,18 +71,12 @@ namespace Robust.Shared.GameObjects
|
||||
/// Raised whenever the server receives a BUI message from a client relating to a UI that requires input
|
||||
/// validation.
|
||||
/// </summary>
|
||||
public sealed class BoundUserInterfaceMessageAttempt : CancellableEntityEventArgs
|
||||
public sealed class BoundUserInterfaceMessageAttempt(EntityUid actor, EntityUid target, Enum uiKey)
|
||||
: CancellableEntityEventArgs
|
||||
{
|
||||
public readonly ICommonSession Sender;
|
||||
public readonly EntityUid Target;
|
||||
public readonly Enum UiKey;
|
||||
|
||||
public BoundUserInterfaceMessageAttempt(ICommonSession sender, EntityUid target, Enum uiKey)
|
||||
{
|
||||
Sender = sender;
|
||||
Target = target;
|
||||
UiKey = uiKey;
|
||||
}
|
||||
public readonly EntityUid Actor = actor;
|
||||
public readonly EntityUid Target = target;
|
||||
public readonly Enum UiKey = uiKey;
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
@@ -104,7 +113,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// Only set when the message is raised as a directed event.
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
public ICommonSession Session = default!;
|
||||
public EntityUid Actor = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,17 +129,6 @@ namespace Robust.Shared.GameObjects
|
||||
public NetEntity Entity { get; set; } = NetEntity.Invalid;
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
internal sealed class UpdateBoundStateMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly BoundUserInterfaceState State;
|
||||
|
||||
public UpdateBoundStateMessage(BoundUserInterfaceState state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
internal sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
@@ -142,59 +140,38 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
internal abstract class BaseBoundUIWrapMessage : EntityEventArgs
|
||||
internal abstract class BaseBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey)
|
||||
: EntityEventArgs
|
||||
{
|
||||
public readonly NetEntity Entity;
|
||||
public readonly BoundUserInterfaceMessage Message;
|
||||
public readonly Enum UiKey;
|
||||
|
||||
public BaseBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey)
|
||||
{
|
||||
Message = message;
|
||||
UiKey = uiKey;
|
||||
Entity = entity;
|
||||
}
|
||||
public readonly NetEntity Entity = entity;
|
||||
public readonly BoundUserInterfaceMessage Message = message;
|
||||
public readonly Enum UiKey = uiKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper message raised from client to server.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class BoundUIWrapMessage : BaseBoundUIWrapMessage
|
||||
{
|
||||
public BoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) : base(entity, message, uiKey)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper message raised from client to server.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class PredictedBoundUIWrapMessage : BaseBoundUIWrapMessage
|
||||
{
|
||||
public PredictedBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) : base(entity, message, uiKey)
|
||||
{
|
||||
}
|
||||
}
|
||||
internal sealed class BoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey)
|
||||
: BaseBoundUIWrapMessage(entity, message, uiKey);
|
||||
|
||||
public sealed class BoundUIOpenedEvent : BaseLocalBoundUserInterfaceEvent
|
||||
{
|
||||
public BoundUIOpenedEvent(Enum uiKey, EntityUid uid, ICommonSession session)
|
||||
public BoundUIOpenedEvent(Enum uiKey, EntityUid uid, EntityUid actor)
|
||||
{
|
||||
UiKey = uiKey;
|
||||
Entity = uid;
|
||||
Session = session;
|
||||
Actor = actor;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BoundUIClosedEvent : BaseLocalBoundUserInterfaceEvent
|
||||
{
|
||||
public BoundUIClosedEvent(Enum uiKey, EntityUid uid, ICommonSession session)
|
||||
public BoundUIClosedEvent(Enum uiKey, EntityUid uid, EntityUid actor)
|
||||
{
|
||||
UiKey = uiKey;
|
||||
Entity = uid;
|
||||
Session = session;
|
||||
Actor = actor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Stores data about this entity and what BUIs they have open.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class UserInterfaceUserComponent : Component
|
||||
{
|
||||
public override bool SessionSpecific => true;
|
||||
|
||||
[DataField]
|
||||
public Dictionary<EntityUid, List<Enum>> OpenInterfaces = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class UserInterfaceUserComponentState : IComponentState
|
||||
{
|
||||
public Dictionary<NetEntity, List<Enum>> OpenInterfaces = new();
|
||||
}
|
||||
@@ -1013,6 +1013,11 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal variant of <see cref="GetComponents"/> that directly returns the actual component set.
|
||||
/// </summary>
|
||||
internal IReadOnlyCollection<IComponent> GetComponentsInternal(EntityUid uid) => _entCompIndex[uid];
|
||||
|
||||
/// <inheritdoc />
|
||||
public int ComponentCount(EntityUid uid)
|
||||
{
|
||||
|
||||
@@ -5,16 +5,17 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public partial class EntityManager
|
||||
{
|
||||
// This method will soon be marked as obsolete.
|
||||
// This method will soon(TM) be marked as obsolete.
|
||||
public EntityUid SpawnEntity(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
=> SpawnAttachedTo(protoName, coordinates, overrides);
|
||||
|
||||
// This method will soon be marked as obsolete.
|
||||
// This method will soon(TM) be marked as obsolete.
|
||||
public EntityUid SpawnEntity(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
=> Spawn(protoName, coordinates, overrides);
|
||||
|
||||
@@ -83,12 +84,16 @@ public partial class EntityManager
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null)
|
||||
=> Spawn(protoName, MapCoordinates.Nullspace, overrides);
|
||||
|
||||
public virtual EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
public EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true)
|
||||
{
|
||||
var entity = CreateEntityUninitialized(protoName, coordinates, overrides);
|
||||
var entity = CreateEntityUninitialized(protoName, MapCoordinates.Nullspace, overrides);
|
||||
InitializeAndStartEntity(entity, doMapInit);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!)
|
||||
{
|
||||
var entity = CreateEntityUninitialized(protoName, coordinates, overrides, rotation);
|
||||
InitializeAndStartEntity(entity, coordinates.MapId);
|
||||
return entity;
|
||||
}
|
||||
@@ -117,7 +122,8 @@ public partial class EntityManager
|
||||
return true;
|
||||
}
|
||||
|
||||
uid = Spawn(protoName, overrides);
|
||||
var doMapInit = _mapSystem.IsInitialized(xform.MapUid);
|
||||
uid = Spawn(protoName, overrides, doMapInit);
|
||||
if (_containers.Insert(uid.Value, container))
|
||||
return true;
|
||||
|
||||
@@ -141,7 +147,8 @@ public partial class EntityManager
|
||||
if (!containerComp.Containers.TryGetValue(containerId, out var container))
|
||||
return false;
|
||||
|
||||
uid = Spawn(protoName, overrides);
|
||||
var doMapInit = _mapSystem.IsInitialized(TransformQuery.GetComponent(containerUid).MapUid);
|
||||
uid = Spawn(protoName, overrides, doMapInit);
|
||||
|
||||
if (_containers.Insert(uid.Value, container))
|
||||
return true;
|
||||
@@ -157,7 +164,8 @@ public partial class EntityManager
|
||||
if (!xform.ParentUid.IsValid())
|
||||
return Spawn(protoName);
|
||||
|
||||
var uid = Spawn(protoName, overrides);
|
||||
var doMapInit = _mapSystem.IsInitialized(xform.MapUid);
|
||||
var uid = Spawn(protoName, overrides, doMapInit);
|
||||
_xforms.DropNextTo(uid, target);
|
||||
return uid;
|
||||
}
|
||||
@@ -182,16 +190,16 @@ public partial class EntityManager
|
||||
ContainerManagerComponent? containerComp = null,
|
||||
ComponentRegistry? overrides = null)
|
||||
{
|
||||
var uid = Spawn(protoName, overrides);
|
||||
inserted = true;
|
||||
xform ??= TransformQuery.GetComponent(containerUid);
|
||||
var doMapInit = _mapSystem.IsInitialized(xform.MapUid);
|
||||
var uid = Spawn(protoName, overrides, doMapInit);
|
||||
|
||||
if ((containerComp == null && !TryGetComponent(containerUid, out containerComp))
|
||||
|| !containerComp.Containers.TryGetValue(containerId, out var container)
|
||||
|| !_containers.Insert(uid, container))
|
||||
{
|
||||
|
||||
inserted = false;
|
||||
xform ??= TransformQuery.GetComponent(containerUid);
|
||||
if (xform.ParentUid.IsValid())
|
||||
_xforms.DropNextTo(uid, (containerUid, xform));
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Profiling;
|
||||
@@ -297,14 +298,13 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!)
|
||||
{
|
||||
var newEntity = CreateEntity(prototypeName, out _, overrides);
|
||||
var transform = TransformQuery.GetComponent(newEntity);
|
||||
|
||||
if (coordinates.MapId == MapId.Nullspace)
|
||||
{
|
||||
DebugTools.Assert(_mapManager.GetMapEntityId(coordinates.MapId) == EntityUid.Invalid);
|
||||
transform._parent = EntityUid.Invalid;
|
||||
transform.Anchored = false;
|
||||
return newEntity;
|
||||
@@ -323,7 +323,7 @@ namespace Robust.Shared.GameObjects
|
||||
else
|
||||
{
|
||||
coords = new EntityCoordinates(mapEnt, coordinates.Position);
|
||||
_xforms.SetCoordinates(newEntity, transform, coords, null, newParent: mapXform);
|
||||
_xforms.SetCoordinates(newEntity, transform, coords, rotation, newParent: mapXform);
|
||||
}
|
||||
|
||||
return newEntity;
|
||||
@@ -776,7 +776,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Allocates an entity and loads components but does not do initialization.
|
||||
/// </summary>
|
||||
private protected virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
||||
internal virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
||||
{
|
||||
if (prototypeName == null)
|
||||
return AllocEntity(out metadata);
|
||||
@@ -821,15 +821,22 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null)
|
||||
{
|
||||
var doMapInit = _mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID);
|
||||
InitializeAndStartEntity(entity, doMapInit);
|
||||
}
|
||||
|
||||
public void InitializeAndStartEntity(Entity<MetaDataComponent?> entity, bool doMapInit)
|
||||
{
|
||||
if (!MetaQuery.Resolve(entity.Owner, ref entity.Comp))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var meta = MetaQuery.GetComponent(entity);
|
||||
InitializeEntity(entity, meta);
|
||||
StartEntity(entity);
|
||||
InitializeEntity(entity.Owner, entity.Comp);
|
||||
StartEntity(entity.Owner);
|
||||
|
||||
// If the map we're initializing the entity on is initialized, run map init on it.
|
||||
if (_mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID))
|
||||
RunMapInit(entity, meta);
|
||||
if (doMapInit)
|
||||
RunMapInit(entity.Owner, entity.Comp);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -859,7 +866,7 @@ namespace Robust.Shared.GameObjects
|
||||
DebugTools.Assert(meta.EntityLifeStage == EntityLifeStage.Initialized, $"Expected entity {ToPrettyString(entity)} to be initialized, was {meta.EntityLifeStage}");
|
||||
SetLifeStage(meta, EntityLifeStage.MapInitialized);
|
||||
|
||||
EventBus.RaiseLocalEvent(entity, MapInitEventInstance, false);
|
||||
EventBus.RaiseLocalEvent(entity, MapInitEventInstance);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -5,8 +5,10 @@ using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
@@ -699,32 +701,32 @@ public partial class EntitySystem
|
||||
|
||||
#region Entity Spawning
|
||||
|
||||
// This method will be obsoleted soon.
|
||||
// This method will be obsoleted soon(TM).
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityUid Spawn(string? prototype, EntityCoordinates coordinates)
|
||||
{
|
||||
return ((IEntityManager)EntityManager).SpawnEntity(prototype, coordinates);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.Spawn(string?, MapCoordinates, ComponentRegistry?)" />
|
||||
/// <inheritdoc cref="IEntityManager.Spawn(string?, MapCoordinates, ComponentRegistry?, Angle)" />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityUid Spawn(string? prototype, MapCoordinates coordinates)
|
||||
=> EntityManager.Spawn(prototype, coordinates);
|
||||
protected EntityUid Spawn(string? prototype, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
|
||||
=> EntityManager.Spawn(prototype, coordinates, overrides, rotation);
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.Spawn(string?, ComponentRegistry?)" />
|
||||
/// <inheritdoc cref="IEntityManager.Spawn(string?, ComponentRegistry?, bool)" />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityUid Spawn(string? prototype = null)
|
||||
=> EntityManager.Spawn(prototype);
|
||||
protected EntityUid Spawn(string? prototype = null, ComponentRegistry? overrides = null, bool doMapInit = true)
|
||||
=> EntityManager.Spawn(prototype, overrides, doMapInit);
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.SpawnAttachedTo" />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates)
|
||||
=> EntityManager.SpawnAttachedTo(prototype, coordinates);
|
||||
protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
=> EntityManager.SpawnAttachedTo(prototype, coordinates, overrides);
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.SpawnAtPosition" />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityUid SpawnAtPosition(string? prototype, EntityCoordinates coordinates)
|
||||
=> EntityManager.SpawnAtPosition(prototype, coordinates);
|
||||
protected EntityUid SpawnAtPosition(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
=> EntityManager.SpawnAtPosition(prototype, coordinates, overrides);
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TrySpawnInContainer" />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
@@ -27,12 +28,12 @@ public partial interface IEntityManager
|
||||
/// <summary>
|
||||
/// Spawns an entity in nullspace.
|
||||
/// </summary>
|
||||
EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null);
|
||||
EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true);
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an entity at a specific world position. The entity will either be parented to the map or a grid.
|
||||
/// </summary>
|
||||
EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null);
|
||||
EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!);
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an entity and then parents it to the entity that the given entity coordinates are relative to.
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -76,10 +77,12 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
|
||||
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null);
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!);
|
||||
|
||||
void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null);
|
||||
|
||||
void InitializeAndStartEntity(Entity<MetaDataComponent?> entity, bool doMapInit);
|
||||
|
||||
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);
|
||||
|
||||
void StartEntity(EntityUid entity);
|
||||
|
||||
@@ -1,68 +1,188 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract partial class SharedMapSystem
|
||||
{
|
||||
protected int LastMapId;
|
||||
|
||||
private void InitializeMap()
|
||||
{
|
||||
SubscribeLocalEvent<MapComponent, ComponentAdd>(OnMapAdd);
|
||||
SubscribeLocalEvent<MapComponent, ComponentInit>(OnMapInit);
|
||||
SubscribeLocalEvent<MapComponent, ComponentAdd>(OnComponentAdd);
|
||||
SubscribeLocalEvent<MapComponent, ComponentInit>(OnCompInit);
|
||||
SubscribeLocalEvent<MapComponent, ComponentStartup>(OnCompStartup);
|
||||
SubscribeLocalEvent<MapComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<MapComponent, ComponentShutdown>(OnMapRemoved);
|
||||
SubscribeLocalEvent<MapComponent, ComponentHandleState>(OnMapHandleState);
|
||||
SubscribeLocalEvent<MapComponent, ComponentGetState>(OnMapGetState);
|
||||
}
|
||||
|
||||
public bool MapExists([NotNullWhen(true)] MapId? mapId)
|
||||
{
|
||||
return mapId != null && Maps.ContainsKey(mapId.Value);
|
||||
}
|
||||
|
||||
public EntityUid GetMap(MapId mapId)
|
||||
{
|
||||
return Maps[mapId];
|
||||
}
|
||||
|
||||
public bool TryGetMap([NotNullWhen(true)] MapId? mapId, [NotNullWhen(true)] out EntityUid? uid)
|
||||
{
|
||||
if (mapId == null || !Maps.TryGetValue(mapId.Value, out var map))
|
||||
{
|
||||
uid = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
uid = map;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnMapHandleState(EntityUid uid, MapComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not MapComponentState state)
|
||||
return;
|
||||
|
||||
component.MapId = state.MapId;
|
||||
|
||||
if (!MapManager.MapExists(state.MapId))
|
||||
if (component.MapId == MapId.Nullspace)
|
||||
{
|
||||
var mapInternal = (IMapManagerInternal)MapManager;
|
||||
mapInternal.CreateMap(state.MapId, uid);
|
||||
if (state.MapId == MapId.Nullspace)
|
||||
throw new Exception($"Received invalid map state? {ToPrettyString(uid)}");
|
||||
|
||||
component.MapId = state.MapId;
|
||||
Maps.Add(component.MapId, uid);
|
||||
RecursiveMapIdUpdate(uid, uid, component.MapId);
|
||||
}
|
||||
|
||||
DebugTools.AssertEqual(component.MapId, state.MapId);
|
||||
component.LightingEnabled = state.LightingEnabled;
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
component.MapInitialized = state.Initialized;
|
||||
|
||||
xformQuery.GetComponent(uid).ChangeMapId(state.MapId, xformQuery);
|
||||
if (LifeStage(uid) >= EntityLifeStage.Initialized)
|
||||
SetPaused(uid, state.MapPaused);
|
||||
else
|
||||
component.MapPaused = state.MapPaused;
|
||||
}
|
||||
|
||||
MapManager.SetMapPaused(state.MapId, state.MapPaused);
|
||||
private void RecursiveMapIdUpdate(EntityUid uid, EntityUid mapUid, MapId mapId)
|
||||
{
|
||||
// This is required only in the event where an entity becomes a map AFTER children have already been attached to it.
|
||||
// AFAIK, this currently only happens when the client applies entity states out of order (i.e., ignoring transform hierarchy),
|
||||
// which itself only happens if PVS is disabled.
|
||||
// TODO MAPS remove this
|
||||
|
||||
var xform = Transform(uid);
|
||||
xform.MapUid = mapUid;
|
||||
xform.MapID = mapId;
|
||||
xform._mapIdInitialized = true;
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
RecursiveMapIdUpdate(child, mapUid, mapId);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMapGetState(EntityUid uid, MapComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new MapComponentState(component.MapId, component.LightingEnabled, component.MapPaused);
|
||||
args.State = new MapComponentState(component.MapId, component.LightingEnabled, component.MapPaused, component.MapInitialized);
|
||||
}
|
||||
|
||||
protected abstract void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args);
|
||||
protected abstract MapId GetNextMapId();
|
||||
|
||||
private void OnMapInit(EntityUid uid, MapComponent component, ComponentInit args)
|
||||
private void OnComponentAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
// ordered startups when
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
EnsureComp<GridTreeComponent>(uid);
|
||||
EnsureComp<MovedGridsComponent>(uid);
|
||||
}
|
||||
|
||||
private void OnCompInit(EntityUid uid, MapComponent component, ComponentInit args)
|
||||
{
|
||||
if (component.MapId == MapId.Nullspace)
|
||||
component.MapId = GetNextMapId();
|
||||
|
||||
DebugTools.AssertEqual(component.MapId.IsClientSide, IsClientSide(uid));
|
||||
if (!Maps.TryAdd(component.MapId, uid))
|
||||
{
|
||||
if (Maps[component.MapId] != uid)
|
||||
throw new Exception($"Attempted to initialize a map {ToPrettyString(uid)} with a duplicate map id {component.MapId}");
|
||||
}
|
||||
|
||||
var msg = new MapChangedEvent(uid, component.MapId, true);
|
||||
RaiseLocalEvent(uid, msg, true);
|
||||
}
|
||||
|
||||
private void OnCompStartup(EntityUid uid, MapComponent component, ComponentStartup args)
|
||||
{
|
||||
if (component.MapPaused)
|
||||
RecursiveSetPaused(uid, true);
|
||||
}
|
||||
|
||||
private void OnMapRemoved(EntityUid uid, MapComponent component, ComponentShutdown args)
|
||||
{
|
||||
DebugTools.Assert(component.MapId != MapId.Nullspace);
|
||||
Log.Info($"Deleting map {component.MapId}");
|
||||
|
||||
var iMap = (IMapManagerInternal)MapManager;
|
||||
iMap.RemoveMapId(component.MapId);
|
||||
Maps.Remove(component.MapId);
|
||||
|
||||
var msg = new MapChangedEvent(uid, component.MapId, false);
|
||||
RaiseLocalEvent(uid, msg, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new map, automatically assigning a map id.
|
||||
/// </summary>
|
||||
public EntityUid CreateMap(out MapId mapId, bool runMapInit = true)
|
||||
{
|
||||
mapId = GetNextMapId();
|
||||
var uid = CreateMap(mapId, runMapInit);
|
||||
return uid;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CreateMap(out Robust.Shared.Map.MapId,bool)"/>
|
||||
public EntityUid CreateMap(bool runMapInit = true) => CreateMap(out _, runMapInit);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new map with the specified map id.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">Throws if an invalid or already existing map id is provided.</exception>
|
||||
public EntityUid CreateMap(MapId mapId, bool runMapInit = true)
|
||||
{
|
||||
if (Maps.ContainsKey(mapId))
|
||||
throw new ArgumentException($"Map with id {mapId} already exists");
|
||||
|
||||
if (mapId == MapId.Nullspace)
|
||||
throw new ArgumentException($"Cannot create a null-space map");
|
||||
|
||||
if (_netManager.IsServer && mapId.IsClientSide)
|
||||
throw new ArgumentException($"Attempted to create a client-side map on the server?");
|
||||
|
||||
if (_netManager.IsClient && _netManager.IsConnected && !mapId.IsClientSide)
|
||||
throw new ArgumentException($"Attempted to create a client-side map entity with a non client-side map ID?");
|
||||
|
||||
var uid = EntityManager.CreateEntityUninitialized(null);
|
||||
var map = _factory.GetComponent<MapComponent>();
|
||||
map.MapId = mapId;
|
||||
AddComp(uid, map);
|
||||
|
||||
// Give the entity a name, mainly for debugging. Content can always override this with a localized name.
|
||||
var meta = MetaData(uid);
|
||||
_meta.SetEntityName(uid, $"Map Entity", meta);
|
||||
|
||||
// Initialize components. this should add the map id to the collections.
|
||||
EntityManager.InitializeComponents(uid, meta);
|
||||
EntityManager.StartComponents(uid);
|
||||
DebugTools.Assert(Maps[mapId] == uid);
|
||||
|
||||
if (runMapInit)
|
||||
InitializeMap((uid, map));
|
||||
else
|
||||
SetPaused((uid, map), true);
|
||||
|
||||
return uid;
|
||||
}
|
||||
}
|
||||
|
||||
83
Robust.Shared/GameObjects/Systems/SharedMapSystem.MapInit.cs
Normal file
83
Robust.Shared/GameObjects/Systems/SharedMapSystem.MapInit.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract partial class SharedMapSystem
|
||||
{
|
||||
public bool IsInitialized(MapId mapId)
|
||||
{
|
||||
if (mapId == MapId.Nullspace)
|
||||
return true; // Nullspace is always initialized
|
||||
|
||||
if(!Maps.TryGetValue(mapId, out var uid))
|
||||
throw new ArgumentException($"Map {mapId} does not exist.");
|
||||
|
||||
return IsInitialized(uid);
|
||||
}
|
||||
public bool IsInitialized(EntityUid? map)
|
||||
{
|
||||
if (map == null)
|
||||
return true; // Nullspace is always initialized
|
||||
|
||||
return IsInitialized(map.Value);
|
||||
}
|
||||
|
||||
public bool IsInitialized(Entity<MapComponent?> map)
|
||||
{
|
||||
if (!_mapQuery.Resolve(map, ref map.Comp))
|
||||
return false;
|
||||
|
||||
return map.Comp.MapInitialized;
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, MapComponent component, MapInitEvent args)
|
||||
{
|
||||
DebugTools.Assert(!component.MapInitialized);
|
||||
component.MapInitialized = true;
|
||||
EntityManager.Dirty(uid, component);
|
||||
}
|
||||
|
||||
public void InitializeMap(MapId mapId, bool unpause = true)
|
||||
{
|
||||
if(!Maps.TryGetValue(mapId, out var uid))
|
||||
throw new ArgumentException($"Map {mapId} does not exist.");
|
||||
|
||||
InitializeMap(uid, unpause);
|
||||
}
|
||||
|
||||
public void InitializeMap(Entity<MapComponent?> map, bool unpause = true)
|
||||
{
|
||||
if (!_mapQuery.Resolve(map, ref map.Comp))
|
||||
return;
|
||||
|
||||
if (map.Comp.MapInitialized)
|
||||
throw new ArgumentException($"Map {ToPrettyString(map)} is already initialized.");
|
||||
|
||||
RecursiveMapInit(map.Owner);
|
||||
|
||||
if (unpause)
|
||||
SetPaused(map, false);
|
||||
}
|
||||
|
||||
private void RecursiveMapInit(EntityUid entity)
|
||||
{
|
||||
var toInitialize = new List<EntityUid> {entity};
|
||||
for (var i = 0; i < toInitialize.Count; i++)
|
||||
{
|
||||
var uid = toInitialize[i];
|
||||
// toInitialize might contain deleted entities.
|
||||
if(!_metaQuery.TryComp(uid, out var meta))
|
||||
continue;
|
||||
|
||||
if (meta.EntityLifeStage == EntityLifeStage.MapInitialized)
|
||||
continue;
|
||||
|
||||
toInitialize.AddRange(Transform(uid)._children);
|
||||
EntityManager.RunMapInit(uid, meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Robust.Shared/GameObjects/Systems/SharedMapSystem.Pause.cs
Normal file
60
Robust.Shared/GameObjects/Systems/SharedMapSystem.Pause.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract partial class SharedMapSystem
|
||||
{
|
||||
public bool IsPaused(MapId mapId)
|
||||
{
|
||||
if (mapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
if(!Maps.TryGetValue(mapId, out var uid))
|
||||
throw new ArgumentException($"Map {mapId} does not exist.");
|
||||
|
||||
return IsPaused(uid);
|
||||
}
|
||||
|
||||
public bool IsPaused(Entity<MapComponent?> map)
|
||||
{
|
||||
if (!_mapQuery.Resolve(map, ref map.Comp))
|
||||
return false;
|
||||
|
||||
return map.Comp.MapPaused;
|
||||
}
|
||||
|
||||
public void SetPaused(MapId mapId, bool paused)
|
||||
{
|
||||
if(!Maps.TryGetValue(mapId, out var uid))
|
||||
throw new ArgumentException($"Map {mapId} does not exist.");
|
||||
|
||||
SetPaused(uid, paused);
|
||||
}
|
||||
|
||||
public void SetPaused(Entity<MapComponent?> map, bool paused)
|
||||
{
|
||||
if (!_mapQuery.Resolve(map, ref map.Comp))
|
||||
return;
|
||||
|
||||
if (map.Comp.MapPaused == paused)
|
||||
return;
|
||||
|
||||
map.Comp.MapPaused = paused;
|
||||
if (map.Comp.LifeStage < ComponentLifeStage.Initializing)
|
||||
return;
|
||||
|
||||
Dirty(map);
|
||||
RecursiveSetPaused(map, paused);
|
||||
}
|
||||
|
||||
private void RecursiveSetPaused(EntityUid entity, bool paused)
|
||||
{
|
||||
_meta.SetEntityPaused(entity, paused);
|
||||
foreach (var child in Transform(entity)._children)
|
||||
{
|
||||
RecursiveSetPaused(child, paused);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,17 +20,23 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly FixtureSystem _fixtures = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly MetaDataSystem _meta = default!;
|
||||
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
internal Dictionary<MapId, EntityUid> Maps { get; } = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_mapQuery = GetEntityQuery<MapComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
InitializeMap();
|
||||
|
||||
@@ -199,47 +199,34 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
#region Component Lifetime
|
||||
|
||||
private (EntityUid?, MapId) InitializeMapUid(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
if (xform._mapIdInitialized)
|
||||
return (xform.MapUid, xform.MapID);
|
||||
|
||||
if (xform.ParentUid.IsValid())
|
||||
{
|
||||
(xform.MapUid, xform.MapID) = InitializeMapUid(xform.ParentUid, Transform(xform.ParentUid));
|
||||
}
|
||||
else if (_mapQuery.TryComp(uid, out var mapComp))
|
||||
{
|
||||
DebugTools.AssertNotEqual(mapComp.MapId, MapId.Nullspace);
|
||||
xform.MapUid = uid;
|
||||
xform.MapID = mapComp.MapId;
|
||||
}
|
||||
else
|
||||
{
|
||||
xform.MapUid = null;
|
||||
xform.MapID = MapId.Nullspace;
|
||||
}
|
||||
|
||||
xform._mapIdInitialized = true;
|
||||
return (xform.MapUid, xform.MapID);
|
||||
}
|
||||
|
||||
private void OnCompInit(EntityUid uid, TransformComponent component, ComponentInit args)
|
||||
{
|
||||
// Children MAY be initialized here before their parents are.
|
||||
// We do this whole dance to handle this recursively,
|
||||
// setting _mapIdInitialized along the way to avoid going to the MapComponent every iteration.
|
||||
static MapId FindMapIdAndSet(EntityUid uid, TransformComponent xform, IEntityManager entMan, EntityQuery<TransformComponent> xformQuery, IMapManager mapManager)
|
||||
{
|
||||
if (xform._mapIdInitialized)
|
||||
return xform.MapID;
|
||||
|
||||
MapId value;
|
||||
|
||||
if (xform.ParentUid.IsValid())
|
||||
{
|
||||
value = FindMapIdAndSet(xform.ParentUid, xformQuery.GetComponent(xform.ParentUid), entMan, xformQuery, mapManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
// second level node, terminates recursion up the branch of the tree
|
||||
if (entMan.TryGetComponent(uid, out MapComponent? mapComp))
|
||||
{
|
||||
value = mapComp.MapId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We allow entities to be spawned directly into null-space.
|
||||
value = MapId.Nullspace;
|
||||
}
|
||||
}
|
||||
|
||||
xform.MapUid = value == MapId.Nullspace ? null : mapManager.GetMapEntityId(value);
|
||||
xform.MapID = value;
|
||||
xform._mapIdInitialized = true;
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!component._mapIdInitialized)
|
||||
{
|
||||
FindMapIdAndSet(uid, component, EntityManager, XformQuery, _mapManager);
|
||||
component._mapIdInitialized = true;
|
||||
}
|
||||
InitializeMapUid(uid, component);
|
||||
|
||||
// Has to be done if _parent is set from ExposeData.
|
||||
if (component.ParentUid.IsValid())
|
||||
@@ -522,6 +509,8 @@ public abstract partial class SharedTransformSystem
|
||||
throw new InvalidOperationException($"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(uid)}, new parent: {ToPrettyString(value.EntityId)}");
|
||||
}
|
||||
|
||||
InitializeMapUid(value.EntityId, newParent);
|
||||
|
||||
// Check for recursive/circular transform hierarchies.
|
||||
if (xform.MapUid == newParent.MapUid)
|
||||
{
|
||||
@@ -881,6 +870,28 @@ public abstract partial class SharedTransformSystem
|
||||
return GetMapCoordinates(entity.Comp);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetMapCoordinates(EntityUid entity, MapCoordinates coordinates)
|
||||
{
|
||||
var xform = XformQuery.GetComponent(entity);
|
||||
SetMapCoordinates((entity, xform), coordinates);
|
||||
}
|
||||
|
||||
public void SetMapCoordinates(Entity<TransformComponent> entity, MapCoordinates coordinates)
|
||||
{
|
||||
var mapUid = _map.GetMap(coordinates.MapId);
|
||||
if (!_gridQuery.HasComponent(entity) &&
|
||||
_mapManager.TryFindGridAt(mapUid, coordinates.Position, out var targetGrid, out _))
|
||||
{
|
||||
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
|
||||
SetCoordinates(entity, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(coordinates.Position)));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetCoordinates(entity, new EntityCoordinates(mapUid, coordinates.Position));
|
||||
}
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
protected EntityQuery<TransformComponent> XformQuery;
|
||||
@@ -50,6 +51,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
|
||||
_mapQuery = GetEntityQuery<MapComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
XformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
@@ -8,7 +9,7 @@ namespace Robust.Shared.Graphics;
|
||||
/// Flags for loading of textures.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public struct TextureLoadParameters
|
||||
public struct TextureLoadParameters : IEquatable<TextureLoadParameters>
|
||||
{
|
||||
/// <summary>
|
||||
/// The default sampling parameters for the texture.
|
||||
@@ -41,4 +42,29 @@ public struct TextureLoadParameters
|
||||
SampleParameters = TextureSampleParameters.Default,
|
||||
Srgb = true
|
||||
};
|
||||
}
|
||||
|
||||
public bool Equals(TextureLoadParameters other)
|
||||
{
|
||||
return SampleParameters.Equals(other.SampleParameters) && Srgb == other.Srgb;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is TextureLoadParameters other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(SampleParameters, Srgb);
|
||||
}
|
||||
|
||||
public static bool operator ==(TextureLoadParameters left, TextureLoadParameters right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TextureLoadParameters left, TextureLoadParameters right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Robust.Shared.Graphics;
|
||||
/// with different sampling parameters than the base texture.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public struct TextureSampleParameters
|
||||
public struct TextureSampleParameters : IEquatable<TextureSampleParameters>
|
||||
{
|
||||
// NOTE: If somebody is gonna add support for 3D/1D textures, change this doc comment.
|
||||
// See the note on this page for why: https://www.khronos.org/opengl/wiki/Sampler_Object#Filtering
|
||||
@@ -62,4 +62,29 @@ public struct TextureSampleParameters
|
||||
Filter = false,
|
||||
WrapMode = TextureWrapMode.None
|
||||
};
|
||||
}
|
||||
|
||||
public bool Equals(TextureSampleParameters other)
|
||||
{
|
||||
return Filter == other.Filter && WrapMode == other.WrapMode;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is TextureSampleParameters other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Filter, (int) WrapMode);
|
||||
}
|
||||
|
||||
public static bool operator ==(TextureSampleParameters left, TextureSampleParameters right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TextureSampleParameters left, TextureSampleParameters right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,35 +12,29 @@ namespace Robust.Shared.Map.Components
|
||||
public sealed partial class MapComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("lightingEnabled")]
|
||||
[DataField]
|
||||
public bool LightingEnabled { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public MapId MapId { get; internal set; } = MapId.Nullspace;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool MapPaused { get; set; } = false;
|
||||
[DataField, Access(typeof(SharedMapSystem), typeof(MapManager))]
|
||||
public bool MapPaused;
|
||||
|
||||
//TODO replace MapPreInit with the map's entity life stage
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool MapPreInit { get; set; } = false;
|
||||
[DataField, Access(typeof(SharedMapSystem), typeof(MapManager))]
|
||||
public bool MapInitialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialized state of a <see cref="MapGridComponentState"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class MapComponentState : ComponentState
|
||||
public sealed class MapComponentState(MapId mapId, bool lightingEnabled, bool paused, bool init)
|
||||
: ComponentState
|
||||
{
|
||||
public MapId MapId;
|
||||
public bool LightingEnabled;
|
||||
public bool MapPaused;
|
||||
|
||||
public MapComponentState(MapId mapId, bool lightingEnabled, bool paused)
|
||||
{
|
||||
MapId = mapId;
|
||||
LightingEnabled = lightingEnabled;
|
||||
MapPaused = paused;
|
||||
}
|
||||
public MapId MapId = mapId;
|
||||
public bool LightingEnabled = lightingEnabled;
|
||||
public bool MapPaused = paused;
|
||||
public bool Initialized = init;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,6 +344,9 @@ namespace Robust.Shared.Map
|
||||
var mapCoordinates = ToMap(entityManager, transformSystem);
|
||||
var otherMapCoordinates = otherCoordinates.ToMap(entityManager, transformSystem);
|
||||
|
||||
if (mapCoordinates.MapId != otherMapCoordinates.MapId)
|
||||
return false;
|
||||
|
||||
return mapCoordinates.InRange(otherMapCoordinates, range);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ namespace Robust.Shared.Map
|
||||
public const bool Approximate = false;
|
||||
public const bool IncludeMap = true;
|
||||
|
||||
[Obsolete("Use EntityQuery<MapGridComponent>")]
|
||||
IEnumerable<MapGridComponent> GetAllGrids();
|
||||
|
||||
/// <summary>
|
||||
/// Should the OnTileChanged event be suppressed? This is useful for initially loading the map
|
||||
/// so that you don't spam an event for each of the million station tiles.
|
||||
@@ -42,16 +39,7 @@ namespace Robust.Shared.Map
|
||||
|
||||
void Restart();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new map.
|
||||
/// </summary>
|
||||
/// <param name="mapId">
|
||||
/// If provided, the new map will use this ID. If not provided, a new ID will be selected automatically.
|
||||
/// </param>
|
||||
/// <returns>The new map.</returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Throw if an explicit ID for the map or default grid is passed and a map or grid with the specified ID already exists, respectively.
|
||||
/// </exception>
|
||||
[Obsolete("Use MapSystem")]
|
||||
MapId CreateMap(MapId? mapId = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -59,24 +47,12 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="mapId">The map ID to check existence of.</param>
|
||||
/// <returns>True if the map exists, false otherwise.</returns>
|
||||
bool MapExists(MapId mapId);
|
||||
bool MapExists([NotNullWhen(true)] MapId? mapId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new entity, then sets it as the map entity.
|
||||
/// </summary>
|
||||
/// <returns>Newly created entity.</returns>
|
||||
EntityUid CreateNewMapEntity(MapId mapId);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the MapEntity(root node) for a given map. If an entity is already set, it will be deleted
|
||||
/// before the new one is set.
|
||||
/// </summary>
|
||||
/// <param name="updateChildren">Should we re-parent children from the old map to the new one, or delete them.</param>
|
||||
void SetMapEntity(MapId mapId, EntityUid newMapEntityId, bool updateChildren = true);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the map entity ID for a given map.
|
||||
/// Returns the map entity ID for a given map, or an invalid entity Id if the map does not exist.
|
||||
/// </summary>
|
||||
[Obsolete("Use TryGetMap")]
|
||||
EntityUid GetMapEntityId(MapId mapId);
|
||||
|
||||
/// <summary>
|
||||
@@ -93,6 +69,7 @@ namespace Robust.Shared.Map
|
||||
MapGridComponent CreateGrid(MapId currentMapId, in GridCreateOptions options);
|
||||
MapGridComponent CreateGrid(MapId currentMapId);
|
||||
Entity<MapGridComponent> CreateGridEntity(MapId currentMapId, GridCreateOptions? options = null);
|
||||
Entity<MapGridComponent> CreateGridEntity(EntityUid map, GridCreateOptions? options = null);
|
||||
|
||||
[Obsolete("Use GetComponent<MapGridComponent>(uid)")]
|
||||
MapGridComponent GetGrid(EntityUid gridId);
|
||||
@@ -233,17 +210,13 @@ namespace Robust.Shared.Map
|
||||
|
||||
#endregion
|
||||
|
||||
void DeleteGrid(EntityUid euid);
|
||||
|
||||
bool HasMapEntity(MapId mapId);
|
||||
[Obsolete("Just delete the grid entity")]
|
||||
void DeleteGrid(EntityUid euid);
|
||||
|
||||
bool IsGrid(EntityUid uid);
|
||||
bool IsMap(EntityUid uid);
|
||||
|
||||
[Obsolete("Whatever this is used for, it is a terrible idea. Create a new map and get it's MapId.")]
|
||||
MapId NextMapId();
|
||||
MapGridComponent GetGridComp(EntityUid euid);
|
||||
|
||||
//
|
||||
// Pausing functions
|
||||
//
|
||||
@@ -252,14 +225,15 @@ namespace Robust.Shared.Map
|
||||
|
||||
void DoMapInitialize(MapId mapId);
|
||||
|
||||
// TODO rename this to actually be descriptive or just remove it.
|
||||
[Obsolete("Use CreateMap's runMapInit argument")]
|
||||
void AddUninitializedMap(MapId mapId);
|
||||
|
||||
[Pure]
|
||||
[Obsolete("Use MapSystem")]
|
||||
bool IsMapPaused(MapId mapId);
|
||||
|
||||
[Pure]
|
||||
[Obsolete("Use MapSystem")]
|
||||
bool IsMapInitialized(MapId mapId);
|
||||
|
||||
}
|
||||
|
||||
public struct GridCreateOptions
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal interface IMapManagerInternal : IMapManager
|
||||
{
|
||||
IGameTiming GameTiming { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Raises the OnTileChanged event.
|
||||
/// </summary>
|
||||
/// <param name="tileRef">A reference to the new tile.</param>
|
||||
/// <param name="oldTile">The old tile that got replaced.</param>
|
||||
void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk);
|
||||
|
||||
MapId CreateMap(MapId? mapId, EntityUid euid);
|
||||
|
||||
void RemoveMapId(MapId mapId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,5 +53,7 @@ namespace Robust.Shared.Map
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
|
||||
public bool IsClientSide => Value < 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,32 +12,15 @@ using Robust.Shared.Utility;
|
||||
namespace Robust.Shared.Map;
|
||||
internal partial class MapManager
|
||||
{
|
||||
[Obsolete("Use GetComponent<MapGridComponent>(uid)")]
|
||||
public MapGridComponent GetGridComp(EntityUid euid)
|
||||
{
|
||||
return EntityManager.GetComponent<MapGridComponent>(euid);
|
||||
}
|
||||
|
||||
[Obsolete("Use EntityQuery instead.")]
|
||||
public IEnumerable<MapGridComponent> GetAllGrids()
|
||||
{
|
||||
var compQuery = EntityManager.AllEntityQueryEnumerator<MapGridComponent>();
|
||||
|
||||
while (compQuery.MoveNext(out var comp))
|
||||
{
|
||||
yield return comp;
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once MethodOverloadWithOptionalParameter
|
||||
public MapGridComponent CreateGrid(MapId currentMapId, ushort chunkSize = 16)
|
||||
{
|
||||
return CreateGrid(currentMapId, chunkSize, default);
|
||||
return CreateGrid(GetMapEntityIdOrThrow(currentMapId), chunkSize, default);
|
||||
}
|
||||
|
||||
public MapGridComponent CreateGrid(MapId currentMapId, in GridCreateOptions options)
|
||||
{
|
||||
return CreateGrid(currentMapId, options.ChunkSize, default);
|
||||
return CreateGrid(GetMapEntityIdOrThrow(currentMapId), options.ChunkSize, default);
|
||||
}
|
||||
|
||||
public MapGridComponent CreateGrid(MapId currentMapId)
|
||||
@@ -46,18 +29,19 @@ internal partial class MapManager
|
||||
}
|
||||
|
||||
public Entity<MapGridComponent> CreateGridEntity(MapId currentMapId, GridCreateOptions? options = null)
|
||||
{
|
||||
return CreateGridEntity(GetMapEntityIdOrThrow(currentMapId), options);
|
||||
}
|
||||
|
||||
public Entity<MapGridComponent> CreateGridEntity(EntityUid map, GridCreateOptions? options = null)
|
||||
{
|
||||
options ??= GridCreateOptions.Default;
|
||||
return CreateGrid(currentMapId, options.Value.ChunkSize, default);
|
||||
return CreateGrid(map, options.Value.ChunkSize, default);
|
||||
}
|
||||
|
||||
[Obsolete("Use GetComponent<MapGridComponent>(uid)")]
|
||||
public MapGridComponent GetGrid(EntityUid gridId)
|
||||
{
|
||||
DebugTools.Assert(gridId.IsValid());
|
||||
|
||||
return GetGridComp(gridId);
|
||||
}
|
||||
=> EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
|
||||
[Obsolete("Use HasComponent<MapGridComponent>(uid)")]
|
||||
public bool IsGrid(EntityUid uid)
|
||||
@@ -108,10 +92,6 @@ internal partial class MapManager
|
||||
|
||||
public virtual void DeleteGrid(EntityUid euid)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugTools.Assert(_dbgGuardRunning);
|
||||
#endif
|
||||
|
||||
// Possible the grid was already deleted / is invalid
|
||||
if (!EntityManager.TryGetComponent<MapGridComponent>(euid, out var iGrid))
|
||||
{
|
||||
@@ -141,10 +121,6 @@ internal partial class MapManager
|
||||
/// <param name="oldTile">The old tile that got replaced.</param>
|
||||
public void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugTools.Assert(_dbgGuardRunning);
|
||||
#endif
|
||||
|
||||
if (SuppressOnTileChanged)
|
||||
return;
|
||||
|
||||
@@ -153,7 +129,7 @@ internal partial class MapManager
|
||||
EntityManager.EventBus.RaiseLocalEvent(euid, ref ev, true);
|
||||
}
|
||||
|
||||
protected Entity<MapGridComponent> CreateGrid(MapId currentMapId, ushort chunkSize, EntityUid forcedGridEuid)
|
||||
protected Entity<MapGridComponent> CreateGrid(EntityUid map, ushort chunkSize, EntityUid forcedGridEuid)
|
||||
{
|
||||
var gridEnt = EntityManager.CreateEntityUninitialized(null, forcedGridEuid);
|
||||
|
||||
@@ -166,8 +142,7 @@ internal partial class MapManager
|
||||
//are applied. After they are applied the parent may be different, but the MapId will
|
||||
//be the same. This causes TransformComponent.ParentUid of a grid to be unsafe to
|
||||
//use in transform states anytime before the state parent is properly set.
|
||||
var fallbackParentEuid = GetMapEntityIdOrThrow(currentMapId);
|
||||
EntityManager.GetComponent<TransformComponent>(gridEnt).AttachParent(fallbackParentEuid);
|
||||
EntityManager.GetComponent<TransformComponent>(gridEnt).AttachParent(map);
|
||||
|
||||
var meta = EntityManager.GetComponent<MetaDataComponent>(gridEnt);
|
||||
EntityManager.System<MetaDataSystem>().SetEntityName(gridEnt, $"grid", meta);
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
internal partial class MapManager
|
||||
{
|
||||
public void RemoveMapId(MapId mapId)
|
||||
{
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
_mapEntities.Remove(mapId);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -27,131 +28,42 @@ public sealed class MapEventArgs : EventArgs
|
||||
|
||||
internal partial class MapManager
|
||||
{
|
||||
private readonly Dictionary<MapId, EntityUid> _mapEntities = new();
|
||||
private MapId _highestMapId = MapId.Nullspace;
|
||||
private Dictionary<MapId, EntityUid> _mapEntities => _mapSystem.Maps;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DeleteMap(MapId mapId)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugTools.Assert(_dbgGuardRunning);
|
||||
#endif
|
||||
|
||||
if (!_mapEntities.TryGetValue(mapId, out var ent) || !ent.IsValid())
|
||||
throw new InvalidOperationException($"Attempted to delete nonexistent map '{mapId}'");
|
||||
|
||||
EntityManager.DeleteEntity(ent);
|
||||
DebugTools.Assert(!_mapEntities.ContainsKey(mapId));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapId CreateMap(MapId? mapId = null)
|
||||
{
|
||||
return ((IMapManagerInternal) this).CreateMap(mapId, default);
|
||||
if (mapId != null)
|
||||
{
|
||||
_mapSystem.CreateMap(mapId.Value);
|
||||
return mapId.Value;
|
||||
}
|
||||
|
||||
_mapSystem.CreateMap(out var map);
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool MapExists(MapId mapId)
|
||||
public bool MapExists([NotNullWhen(true)] MapId? mapId)
|
||||
{
|
||||
return _mapEntities.ContainsKey(mapId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public EntityUid CreateNewMapEntity(MapId mapId)
|
||||
{
|
||||
DebugTools.Assert(mapId != MapId.Nullspace);
|
||||
#if DEBUG
|
||||
DebugTools.Assert(_dbgGuardRunning);
|
||||
#endif
|
||||
|
||||
var newEntity = EntityManager.CreateEntityUninitialized(null);
|
||||
SetMapEntity(mapId, newEntity);
|
||||
|
||||
EntityManager.InitializeComponents(newEntity);
|
||||
EntityManager.StartComponents(newEntity);
|
||||
|
||||
return newEntity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetMapEntity(MapId mapId, EntityUid newMapEntity, bool updateChildren = true)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugTools.Assert(_dbgGuardRunning);
|
||||
#endif
|
||||
|
||||
if (!_mapEntities.ContainsKey(mapId))
|
||||
throw new InvalidOperationException($"Map {mapId} does not exist.");
|
||||
|
||||
foreach (var kvEntity in _mapEntities)
|
||||
{
|
||||
if (kvEntity.Value == newMapEntity)
|
||||
{
|
||||
if (mapId == kvEntity.Key)
|
||||
return;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Entity {newMapEntity} is already the root node of another map {kvEntity.Key}.");
|
||||
}
|
||||
}
|
||||
|
||||
MapComponent? mapComp;
|
||||
// If this is being done as part of maploader then we want to copy the preinit state across mainly.
|
||||
bool preInit = false;
|
||||
bool paused = false;
|
||||
|
||||
// remove existing graph
|
||||
if (_mapEntities.TryGetValue(mapId, out var oldEntId))
|
||||
{
|
||||
if (EntityManager.TryGetComponent(oldEntId, out mapComp))
|
||||
{
|
||||
preInit = mapComp.MapPreInit;
|
||||
paused = mapComp.MapPaused;
|
||||
}
|
||||
|
||||
EntityManager.System<SharedTransformSystem>().ReparentChildren(oldEntId, newMapEntity);
|
||||
|
||||
//Note: EntityUid.Invalid gets passed in here
|
||||
//Note: This prevents setting a subgraph as the root, since the subgraph will be deleted
|
||||
EntityManager.DeleteEntity(oldEntId);
|
||||
}
|
||||
|
||||
var raiseEvent = false;
|
||||
|
||||
// re-use or add map component
|
||||
if (!EntityManager.TryGetComponent(newMapEntity, out mapComp))
|
||||
mapComp = EntityManager.AddComponent<MapComponent>(newMapEntity);
|
||||
else
|
||||
{
|
||||
raiseEvent = true;
|
||||
|
||||
if (mapComp.MapId != mapId)
|
||||
{
|
||||
_sawmill.Warning($"Setting map {mapId} root to entity {newMapEntity}, but entity thinks it is root node of map {mapComp.MapId}.");
|
||||
}
|
||||
}
|
||||
|
||||
_sawmill.Debug($"Setting map {mapId} entity to {newMapEntity}");
|
||||
|
||||
// set as new map entity
|
||||
mapComp.MapPreInit = preInit;
|
||||
mapComp.MapPaused = paused;
|
||||
|
||||
mapComp.MapId = mapId;
|
||||
_mapEntities[mapId] = newMapEntity;
|
||||
|
||||
// Yeah this sucks but I just want to save maps for now, deal.
|
||||
if (raiseEvent)
|
||||
{
|
||||
var ev = new MapChangedEvent(newMapEntity, mapId, true);
|
||||
EntityManager.EventBus.RaiseLocalEvent(newMapEntity, ev, true);
|
||||
}
|
||||
return _mapSystem.MapExists(mapId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public EntityUid GetMapEntityId(MapId mapId)
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var entId))
|
||||
return entId;
|
||||
if (_mapSystem.TryGetMap(mapId, out var entId))
|
||||
return entId.Value;
|
||||
|
||||
return EntityUid.Invalid;
|
||||
}
|
||||
@@ -161,13 +73,12 @@ internal partial class MapManager
|
||||
/// </summary>
|
||||
public EntityUid GetMapEntityIdOrThrow(MapId mapId)
|
||||
{
|
||||
return _mapEntities[mapId];
|
||||
return _mapSystem.GetMap(mapId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasMapEntity(MapId mapId)
|
||||
public bool TryGetMap([NotNullWhen(true)] MapId? mapId, [NotNullWhen(true)] out EntityUid? uid)
|
||||
{
|
||||
return _mapEntities.ContainsKey(mapId);
|
||||
return _mapSystem.TryGetMap(mapId, out uid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -181,67 +92,4 @@ internal partial class MapManager
|
||||
{
|
||||
return EntityManager.HasComponent<MapComponent>(uid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapId NextMapId()
|
||||
{
|
||||
return _highestMapId = new MapId(_highestMapId.Value + 1);
|
||||
}
|
||||
|
||||
MapId IMapManagerInternal.CreateMap(MapId? mapId, EntityUid entityUid)
|
||||
{
|
||||
if (mapId == MapId.Nullspace)
|
||||
throw new InvalidOperationException("Attempted to create a null-space map.");
|
||||
|
||||
#if DEBUG
|
||||
DebugTools.Assert(_dbgGuardRunning);
|
||||
#endif
|
||||
|
||||
var actualId = mapId ?? new MapId(_highestMapId.Value + 1);
|
||||
|
||||
if (MapExists(actualId))
|
||||
throw new InvalidOperationException($"A map with ID {actualId} already exists");
|
||||
|
||||
if (_highestMapId.Value < actualId.Value)
|
||||
_highestMapId = actualId;
|
||||
|
||||
_sawmill.Info($"Creating new map {actualId}");
|
||||
|
||||
if (actualId != MapId.Nullspace) // nullspace isn't bound to an entity
|
||||
{
|
||||
Entity<MapComponent> result = default;
|
||||
var query = EntityManager.AllEntityQueryEnumerator<MapComponent>();
|
||||
while (query.MoveNext(out var uid, out var map))
|
||||
{
|
||||
if (map.MapId != actualId)
|
||||
continue;
|
||||
|
||||
result = (uid, map);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != default)
|
||||
{
|
||||
DebugTools.Assert(mapId != null);
|
||||
_mapEntities.Add(actualId, result);
|
||||
_sawmill.Debug($"Rebinding map {actualId} to entity {result.Owner}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var newEnt = EntityManager.CreateEntityUninitialized(null, entityUid);
|
||||
_mapEntities.Add(actualId, newEnt);
|
||||
|
||||
var mapComp = EntityManager.AddComponent<MapComponent>(newEnt);
|
||||
mapComp.MapId = actualId;
|
||||
var meta = EntityManager.GetComponent<MetaDataComponent>(newEnt);
|
||||
EntityManager.System<MetaDataSystem>().SetEntityName(newEnt, $"map {actualId}", meta);
|
||||
EntityManager.Dirty(newEnt, mapComp, meta);
|
||||
EntityManager.InitializeComponents(newEnt, meta);
|
||||
EntityManager.StartComponents(newEnt);
|
||||
_sawmill.Debug($"Binding map {actualId} to entity {newEnt}");
|
||||
}
|
||||
}
|
||||
|
||||
return actualId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
@@ -8,145 +7,44 @@ namespace Robust.Shared.Map
|
||||
{
|
||||
internal partial class MapManager
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void SetMapPaused(MapId mapId, bool paused)
|
||||
{
|
||||
if(mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
if(!MapExists(mapId))
|
||||
throw new ArgumentException("That map does not exist.");
|
||||
|
||||
var mapUid = GetMapEntityId(mapId);
|
||||
var mapComp = EntityManager.GetComponent<MapComponent>(mapUid);
|
||||
|
||||
if (mapComp.MapPaused == paused)
|
||||
return;
|
||||
|
||||
mapComp.MapPaused = paused;
|
||||
EntityManager.Dirty(mapUid, mapComp);
|
||||
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var metaQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
var metaSystem = EntityManager.EntitySysManager.GetEntitySystem<MetaDataSystem>();
|
||||
|
||||
RecursiveSetPaused(mapUid, paused, in xformQuery, in metaQuery, in metaSystem);
|
||||
_mapSystem.SetPaused(mapId, paused);
|
||||
}
|
||||
|
||||
private static void RecursiveSetPaused(EntityUid entity, bool paused,
|
||||
in EntityQuery<TransformComponent> xformQuery,
|
||||
in EntityQuery<MetaDataComponent> metaQuery,
|
||||
in MetaDataSystem system)
|
||||
public void SetMapPaused(EntityUid uid, bool paused)
|
||||
{
|
||||
system.SetEntityPaused(entity, paused, metaQuery.GetComponent(entity));
|
||||
var xform = xformQuery.GetComponent(entity);
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
RecursiveSetPaused(child, paused, in xformQuery, in metaQuery, in system);
|
||||
}
|
||||
_mapSystem.SetPaused(uid, paused);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DoMapInitialize(MapId mapId)
|
||||
{
|
||||
if(!MapExists(mapId))
|
||||
throw new ArgumentException("That map does not exist.");
|
||||
|
||||
if (IsMapInitialized(mapId))
|
||||
throw new ArgumentException("That map is already initialized.");
|
||||
|
||||
var mapEnt = GetMapEntityId(mapId);
|
||||
var mapComp = EntityManager.GetComponent<MapComponent>(mapEnt);
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var metaQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
var metaSystem = EntityManager.EntitySysManager.GetEntitySystem<MetaDataSystem>();
|
||||
|
||||
mapComp.MapPreInit = false;
|
||||
mapComp.MapPaused = false;
|
||||
EntityManager.Dirty(mapEnt, mapComp);
|
||||
|
||||
RecursiveDoMapInit(mapEnt, in xformQuery, in metaQuery, in metaSystem);
|
||||
_mapSystem.InitializeMap(mapId);
|
||||
}
|
||||
|
||||
private void RecursiveDoMapInit(EntityUid entity,
|
||||
in EntityQuery<TransformComponent> xformQuery,
|
||||
in EntityQuery<MetaDataComponent> metaQuery,
|
||||
in MetaDataSystem system)
|
||||
public bool IsMapInitialized(MapId mapId)
|
||||
{
|
||||
// RunMapInit can modify the TransformTree
|
||||
// ToArray caches deleted euids, we check here if they still exist.
|
||||
if(!metaQuery.TryGetComponent(entity, out var meta))
|
||||
return;
|
||||
|
||||
EntityManager.RunMapInit(entity, meta);
|
||||
system.SetEntityPaused(entity, false, meta);
|
||||
|
||||
foreach (var child in xformQuery.GetComponent(entity)._children.ToArray())
|
||||
{
|
||||
RecursiveDoMapInit(child, in xformQuery, in metaQuery, in system);
|
||||
}
|
||||
return _mapSystem.IsInitialized(mapId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddUninitializedMap(MapId mapId)
|
||||
{
|
||||
SetMapPreInit(mapId);
|
||||
}
|
||||
|
||||
private bool CheckMapPause(MapId mapId)
|
||||
{
|
||||
if(mapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
var mapEuid = GetMapEntityId(mapId);
|
||||
|
||||
if (!EntityManager.TryGetComponent<MapComponent>(mapEuid, out var map))
|
||||
return false;
|
||||
|
||||
return map.MapPaused;
|
||||
}
|
||||
|
||||
private void SetMapPreInit(MapId mapId)
|
||||
{
|
||||
if(mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var mapEuid = GetMapEntityId(mapId);
|
||||
var mapComp = EntityManager.GetComponent<MapComponent>(mapEuid);
|
||||
mapComp.MapPreInit = true;
|
||||
}
|
||||
|
||||
private bool CheckMapPreInit(MapId mapId)
|
||||
{
|
||||
if(mapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
var mapEuid = GetMapEntityId(mapId);
|
||||
|
||||
if (!EntityManager.TryGetComponent<MapComponent>(mapEuid, out var map))
|
||||
return false;
|
||||
|
||||
return map.MapPreInit;
|
||||
var ent = GetMapEntityId(mapId);
|
||||
EntityManager.GetComponent<MapComponent>(ent).MapInitialized = false;
|
||||
var meta = EntityManager.GetComponent<MetaDataComponent>(ent);
|
||||
((EntityManager)EntityManager).SetLifeStage(meta, EntityLifeStage.Initialized);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMapPaused(MapId mapId)
|
||||
{
|
||||
if(mapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
var mapEuid = GetMapEntityId(mapId);
|
||||
|
||||
if (!EntityManager.TryGetComponent<MapComponent>(mapEuid, out var map))
|
||||
return false;
|
||||
|
||||
return map.MapPaused || map.MapPreInit;
|
||||
return _mapSystem.IsPaused(mapId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMapInitialized(MapId mapId)
|
||||
public bool IsMapPaused(EntityUid uid)
|
||||
{
|
||||
return !CheckMapPreInit(mapId);
|
||||
return _mapSystem.IsPaused(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,13 +3,9 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
@@ -23,60 +19,36 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
|
||||
|
||||
[Dependency] private readonly IConsoleHost _conhost = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
private ISawmill _sawmill => _mapSystem.Log;
|
||||
|
||||
private FixtureSystem _fixtureSystem = default!;
|
||||
private SharedMapSystem _mapSystem = default!;
|
||||
private SharedPhysicsSystem _physics = default!;
|
||||
private SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
private EntityQuery<FixturesComponent> _fixturesQuery;
|
||||
private EntityQuery<GridTreeComponent> _gridTreeQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
_fixturesQuery = EntityManager.GetEntityQuery<FixturesComponent>();
|
||||
_gridTreeQuery = EntityManager.GetEntityQuery<GridTreeComponent>();
|
||||
_gridQuery = EntityManager.GetEntityQuery<MapGridComponent>();
|
||||
_physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
|
||||
_xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
_sawmill = Logger.GetSawmill("map");
|
||||
|
||||
#if DEBUG
|
||||
DebugTools.Assert(!_dbgGuardInit);
|
||||
DebugTools.Assert(!_dbgGuardRunning);
|
||||
_dbgGuardInit = true;
|
||||
#endif
|
||||
InitializeMapPausing();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Startup()
|
||||
{
|
||||
_fixtureSystem = EntityManager.System<FixtureSystem>();
|
||||
_physics = EntityManager.System<SharedPhysicsSystem>();
|
||||
_transformSystem = EntityManager.System<SharedTransformSystem>();
|
||||
_mapSystem = EntityManager.System<SharedMapSystem>();
|
||||
|
||||
#if DEBUG
|
||||
DebugTools.Assert(_dbgGuardInit);
|
||||
_dbgGuardRunning = true;
|
||||
#endif
|
||||
|
||||
_sawmill.Debug("Starting...");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Shutdown()
|
||||
{
|
||||
#if DEBUG
|
||||
DebugTools.Assert(_dbgGuardInit);
|
||||
#endif
|
||||
_sawmill.Debug("Stopping...");
|
||||
|
||||
// TODO: AllEntityQuery instead???
|
||||
@@ -102,9 +74,4 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
|
||||
EntityManager.DeleteEntity(uid);
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private bool _dbgGuardInit;
|
||||
private bool _dbgGuardRunning;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace Robust.Shared.Network
|
||||
public bool IsConnected => _connection.Status == NetConnectionStatus.Connected;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public IPEndPoint RemoteEndPoint => _connection.RemoteEndPoint;
|
||||
|
||||
/// <summary>
|
||||
@@ -100,7 +99,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{RemoteEndPoint}/{UserId}";
|
||||
return $"{ConnectionId}/{UserId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Network
|
||||
@@ -10,11 +11,13 @@ namespace Robust.Shared.Network
|
||||
{
|
||||
[ViewVariables]
|
||||
public NetUserId UserId { get; }
|
||||
|
||||
[ViewVariables]
|
||||
public string UserName { get; }
|
||||
|
||||
[ViewVariables]
|
||||
public string? PatronTier { get; init; }
|
||||
[ViewVariables]
|
||||
|
||||
public ImmutableArray<byte> HWId { get; init; }
|
||||
|
||||
public NetUserData(NetUserId userId, string userName)
|
||||
@@ -22,5 +25,18 @@ namespace Robust.Shared.Network
|
||||
UserId = userId;
|
||||
UserName = userName;
|
||||
}
|
||||
|
||||
public sealed override string ToString()
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
stringBuilder.Append("NetUserData"); // type name
|
||||
stringBuilder.Append(" { ");
|
||||
if ((this with { HWId = default }).PrintMembers(stringBuilder))
|
||||
{
|
||||
stringBuilder.Append(' ');
|
||||
}
|
||||
stringBuilder.Append('}');
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +181,6 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
// Owner field shouldn't be required, fixtures on other entities shouldn't be getting compared to each other.
|
||||
// This is mainly here because it might've intruded some physics bugs, so this is here just in case.
|
||||
DebugTools.Assert(Owner == other.Owner);
|
||||
return Equivalent(other) && Owner == other.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,55 +313,60 @@ public partial class SharedPhysicsSystem
|
||||
Dirty(uid, body);
|
||||
}
|
||||
|
||||
public void SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
public bool SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (body.BodyType == BodyType.Static)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (value * value > 0.0f)
|
||||
{
|
||||
if (!WakeBody(uid, manager: manager, body: body))
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// CloseToPercent tolerance needs to be small enough such that an angular velocity just above
|
||||
// sleep-tolerance can damp down to sleeping.
|
||||
|
||||
if (MathHelper.CloseToPercent(body.AngularVelocity, value, 0.00001f))
|
||||
return;
|
||||
return false;
|
||||
|
||||
body.AngularVelocity = value;
|
||||
|
||||
if (dirty)
|
||||
Dirty(uid, body);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to set the body to collidable, wake it, then move it.
|
||||
/// </summary>
|
||||
public void SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true, bool wakeBody = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
public bool SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true, bool wakeBody = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (body.BodyType == BodyType.Static) return;
|
||||
if (body.BodyType == BodyType.Static)
|
||||
return false;
|
||||
|
||||
if (wakeBody && Vector2.Dot(velocity, velocity) > 0.0f)
|
||||
{
|
||||
if (!WakeBody(uid, manager: manager, body: body))
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (body.LinearVelocity.EqualsApprox(velocity, 0.0000001f))
|
||||
return;
|
||||
return false;
|
||||
|
||||
body.LinearVelocity = velocity;
|
||||
|
||||
if (dirty)
|
||||
Dirty(uid, body);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetAngularDamping(EntityUid uid, PhysicsComponent body, float value, bool dirty = true)
|
||||
|
||||
@@ -660,13 +660,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
});
|
||||
|
||||
// Update data sequentially
|
||||
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
for (var i = 0; i < actualIslands.Length; i++)
|
||||
{
|
||||
var island = actualIslands[i];
|
||||
|
||||
UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities, xformQuery, metaQuery);
|
||||
UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities, xformQuery);
|
||||
SleepBodies(in island, sleepStatus);
|
||||
}
|
||||
|
||||
@@ -1001,8 +999,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
float[] angles,
|
||||
Vector2[] linearVelocities,
|
||||
float[] angularVelocities,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<MetaDataComponent> metaQuery)
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
foreach (var (joint, error) in island.BrokenJoints)
|
||||
{
|
||||
@@ -1035,21 +1032,22 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
|
||||
var linVelocity = linearVelocities[offset + i];
|
||||
var physicsDirtied = false;
|
||||
|
||||
if (!float.IsNaN(linVelocity.X) && !float.IsNaN(linVelocity.Y))
|
||||
{
|
||||
SetLinearVelocity(uid, linVelocity, false, body: body);
|
||||
physicsDirtied |= SetLinearVelocity(uid, linVelocity, false, body: body);
|
||||
}
|
||||
|
||||
var angVelocity = angularVelocities[offset + i];
|
||||
|
||||
if (!float.IsNaN(angVelocity))
|
||||
{
|
||||
SetAngularVelocity(uid, angVelocity, false, body: body);
|
||||
physicsDirtied |= SetAngularVelocity(uid, angVelocity, false, body: body);
|
||||
}
|
||||
|
||||
// TODO: Should check if the values update.
|
||||
Dirty(uid, body, metaQuery.GetComponent(uid));
|
||||
if (physicsDirtied)
|
||||
Dirty(uid, body);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -200,12 +200,13 @@ internal abstract partial class SharedPlayerManager
|
||||
if (EntManager.EnsureComponent<ActorComponent>(uid, out var actor))
|
||||
{
|
||||
// component already existed.
|
||||
DebugTools.AssertNotNull(actor.PlayerSession);
|
||||
if (!force)
|
||||
return false;
|
||||
|
||||
kicked = actor.PlayerSession;
|
||||
Detach(kicked);
|
||||
|
||||
if (kicked != null)
|
||||
Detach(kicked);
|
||||
}
|
||||
|
||||
if (_netMan.IsServer)
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Robust.Shared.Prototypes;
|
||||
/// Prototype that represents game entities.
|
||||
/// </summary>
|
||||
[Prototype("entityCategory")]
|
||||
public sealed class EntityCategoryPrototype : IPrototype
|
||||
public sealed partial class EntityCategoryPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
@@ -22,4 +22,4 @@ public sealed class EntityCategoryPrototype : IPrototype
|
||||
/// </summary>
|
||||
[DataField("description")]
|
||||
public string? Description { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
@@ -272,7 +273,8 @@ public interface IPrototypeManager
|
||||
out Dictionary<Type, HashSet<string>> prototypes);
|
||||
|
||||
/// <summary>
|
||||
/// This method uses reflection to validate that prototype id fields correspond to valid prototypes.
|
||||
/// This method uses reflection to validate that all static prototype id fields correspond to valid prototypes.
|
||||
/// This will validate all known to <see cref="IReflectionManager"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will validate any field that has either a <see cref="ValidatePrototypeIdAttribute{T}"/> attribute, or a
|
||||
@@ -280,7 +282,12 @@ public interface IPrototypeManager
|
||||
/// </remarks>
|
||||
/// <param name="prototypes">A collection prototypes to use for validation. Any prototype not in this collection
|
||||
/// will be considered invalid.</param>
|
||||
List<string> ValidateFields(Dictionary<Type, HashSet<string>> prototypes);
|
||||
List<string> ValidateStaticFields(Dictionary<Type, HashSet<string>> prototypes);
|
||||
|
||||
/// <summary>
|
||||
/// This is a variant of <see cref="ValidateStaticFields(System.Collections.Generic.Dictionary{System.Type,System.Collections.Generic.HashSet{string}})"/> that only validates a single type.
|
||||
/// </summary>
|
||||
List<string> ValidateStaticFields(Type type, Dictionary<Type, HashSet<string>> prototypes);
|
||||
|
||||
/// <summary>
|
||||
/// This method will serialize all loaded prototypes into yaml and then validate them. This can be used to ensure
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Utility;
|
||||
using BindingFlags = System.Reflection.BindingFlags;
|
||||
|
||||
@@ -13,35 +12,41 @@ namespace Robust.Shared.Prototypes;
|
||||
public partial class PrototypeManager
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public List<string> ValidateFields(Dictionary<Type, HashSet<string>> prototypes)
|
||||
public List<string> ValidateStaticFields(Dictionary<Type, HashSet<string>> prototypes)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
foreach (var type in _reflectionManager.FindAllTypes())
|
||||
{
|
||||
// TODO validate public static fields on abstract classes that have no implementations?
|
||||
if (!type.IsAbstract)
|
||||
ValidateType(type, errors, prototypes);
|
||||
ValidateStaticFieldsInternal(type, errors, prototypes);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate all fields defined on this type and all base types.
|
||||
/// </summary>
|
||||
private void ValidateType(Type type, List<string> errors, Dictionary<Type, HashSet<string>> prototypes)
|
||||
/// <inheritdoc/>
|
||||
public List<string> ValidateStaticFields(Type type, Dictionary<Type, HashSet<string>> prototypes)
|
||||
{
|
||||
object? instance = null;
|
||||
Type? baseType = type;
|
||||
var errors = new List<string>();
|
||||
ValidateStaticFieldsInternal(type, errors, prototypes);
|
||||
return errors;
|
||||
}
|
||||
|
||||
var flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public |
|
||||
BindingFlags.DeclaredOnly;
|
||||
/// <summary>
|
||||
/// Validate all static fields defined on this type and all base types.
|
||||
/// </summary>
|
||||
private void ValidateStaticFieldsInternal(Type type, List<string> errors, Dictionary<Type, HashSet<string>> prototypes)
|
||||
{
|
||||
var baseType = type;
|
||||
var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly;
|
||||
|
||||
while (baseType != null)
|
||||
{
|
||||
foreach (var field in baseType.GetFields(flags))
|
||||
{
|
||||
ValidateField(field, type, ref instance, errors, prototypes);
|
||||
DebugTools.Assert(field.IsStatic);
|
||||
ValidateStaticField(field, type, errors, prototypes);
|
||||
}
|
||||
|
||||
// We need to get the fields on the base type separately in order to get the private fields
|
||||
@@ -49,92 +54,110 @@ public partial class PrototypeManager
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateField(
|
||||
private void ValidateStaticField(
|
||||
FieldInfo field,
|
||||
Type type,
|
||||
ref object? instance,
|
||||
List<string> errors,
|
||||
Dictionary<Type, HashSet<string>> prototypes)
|
||||
{
|
||||
DebugTools.Assert(field.IsStatic);
|
||||
DebugTools.Assert(!field.HasCustomAttribute<DataFieldAttribute>(), "Datafields should not be static");
|
||||
|
||||
// Is this even a prototype id related field?
|
||||
if (!TryGetFieldPrototype(field, out var proto, out var canBeNull, out var canBeEmpty))
|
||||
if (!TryGetFieldPrototype(field, out var proto))
|
||||
return;
|
||||
|
||||
if (!TryGetFieldValue(field, type, ref instance, errors, out var value))
|
||||
return;
|
||||
|
||||
var id = value?.ToString();
|
||||
|
||||
if (id == null)
|
||||
if (!prototypes.TryGetValue(proto, out var validIds))
|
||||
{
|
||||
if (!canBeNull)
|
||||
errors.Add($"Prototype id field failed validation. Fields should not be null. Field: {field.Name} in {type.FullName}");
|
||||
errors.Add($"Prototype id field failed validation. Unknown prototype kind {proto.Name}. Field: {field.Name} in {type.FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
if (!TryGetIds(field, proto, out var ids))
|
||||
{
|
||||
if (!canBeEmpty)
|
||||
errors.Add($"Prototype id field failed validation. Non-optional non-nullable data-fields must have a default value. Field: {field.Name} in {type.FullName}");
|
||||
TryGetIds(field, proto, out _);
|
||||
DebugTools.Assert($"Failed to get ids, despite resolving the field into a prototype kind?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prototypes.TryGetValue(proto, out var ids))
|
||||
foreach (var id in ids)
|
||||
{
|
||||
errors.Add($"Prototype id field failed validation. Unknown prototype kind. Field: {field.Name} in {type.FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ids.Contains(id))
|
||||
{
|
||||
errors.Add($"Prototype id field failed validation. Unknown prototype: {id}. Field: {field.Name} in {type.FullName}");
|
||||
if (!validIds.Contains(id))
|
||||
errors.Add($"Prototype id field failed validation. Unknown prototype: {id} of type {proto.Name}. Field: {field.Name} in {type.FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of some field. If this is not a static field, this will create instance of the object in order to
|
||||
/// validate default field values.
|
||||
/// Extract prototype ids from a string, IEnumerable{string}, EntProtoId, IEnumerable{EntProtoId}, ProtoId{T}, or IEnumerable{ProtoId{T}} field.
|
||||
/// </summary>
|
||||
private bool TryGetFieldValue(FieldInfo field, Type type, ref object? instance, List<string> errors, out object? value)
|
||||
private bool TryGetIds(FieldInfo field, Type proto, [NotNullWhen(true)] out string[]? ids)
|
||||
{
|
||||
value = null;
|
||||
ids = null;
|
||||
var value = field.GetValue(null);
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
if (field.IsStatic || instance != null)
|
||||
if (value is string str)
|
||||
{
|
||||
value = field.GetValue(instance);
|
||||
ids = [str];
|
||||
return true;
|
||||
}
|
||||
|
||||
var constructor = type.GetConstructor(
|
||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
|
||||
Type.EmptyTypes);
|
||||
|
||||
// TODO handle parameterless record constructors.
|
||||
// Figure out how ISerializationManager does it, or just re-use that code somehow.
|
||||
// In the meantime, record data fields need an explicit parameterless ctor.
|
||||
|
||||
if (constructor == null)
|
||||
if (value is IEnumerable<string> strEnum)
|
||||
{
|
||||
errors.Add($"Prototype id field failed validation. Could not create instance to validate default value. Field: {field.Name} in {type.FullName}");
|
||||
return false;
|
||||
ids = strEnum.ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
instance = constructor.Invoke(Array.Empty<object>());
|
||||
value = field.GetValue(instance);
|
||||
if (value is EntProtoId protoId)
|
||||
{
|
||||
ids = [protoId];
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (value is IEnumerable<EntProtoId> protoIdEnum)
|
||||
{
|
||||
ids = protoIdEnum.Select(x=> x.Id).ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ProtoId<>))
|
||||
{
|
||||
ids = [value.ToString()!];
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var iface in field.FieldType.GetInterfaces())
|
||||
{
|
||||
if (!iface.IsGenericType)
|
||||
continue;
|
||||
|
||||
if (iface.GetGenericTypeDefinition() != typeof(IEnumerable<>))
|
||||
continue;
|
||||
|
||||
var enumType = iface.GetGenericArguments().Single();
|
||||
if (!enumType.IsGenericType)
|
||||
continue;
|
||||
|
||||
if (enumType.GetGenericTypeDefinition() != typeof(ProtoId<>))
|
||||
continue;
|
||||
|
||||
ids = GetIdsMethod.MakeGenericMethod(proto).Invoke(null, [value]) as string[];
|
||||
return ids != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetFieldPrototype(
|
||||
FieldInfo field,
|
||||
[NotNullWhen(true)] out Type? proto,
|
||||
out bool canBeNull,
|
||||
out bool canBeEmpty)
|
||||
private static MethodInfo GetIdsMethod = typeof(PrototypeManager).GetMethod(nameof(GetIds), BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
private static string[] GetIds<T>(IEnumerable<ProtoId<T>> enumerable) where T : class, IPrototype
|
||||
{
|
||||
proto = null;
|
||||
canBeNull = false;
|
||||
canBeEmpty = false;
|
||||
return enumerable.Select(x => x.Id).ToArray();
|
||||
}
|
||||
|
||||
private bool TryGetFieldPrototype(FieldInfo field, [NotNullWhen(true)] out Type? proto)
|
||||
{
|
||||
// Validate anything with the attribute
|
||||
var attrib = field.GetCustomAttribute(typeof(ValidatePrototypeIdAttribute<>), false);
|
||||
if (attrib != null)
|
||||
{
|
||||
@@ -142,46 +165,40 @@ public partial class PrototypeManager
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!field.TryGetCustomAttribute(out DataFieldAttribute? dataField))
|
||||
return false;
|
||||
if (TryGetPrototypeFromType(field.FieldType, out proto))
|
||||
return true;
|
||||
|
||||
var fieldType = field.FieldType;
|
||||
canBeEmpty = dataField.Required;
|
||||
DebugTools.Assert(!field.IsStatic);
|
||||
|
||||
// Resolve nullable structs
|
||||
if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
// Allow validating arrays or lists.
|
||||
foreach (var iface in field.FieldType.GetInterfaces().Where(x => x.IsGenericType))
|
||||
{
|
||||
fieldType = fieldType.GetGenericArguments().Single();
|
||||
canBeNull = true;
|
||||
if (iface.GetGenericTypeDefinition() != typeof(IEnumerable<>))
|
||||
continue;
|
||||
|
||||
var enumType = iface.GetGenericArguments().Single();
|
||||
if (TryGetPrototypeFromType(enumType, out proto))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fieldType == typeof(EntProtoId))
|
||||
proto = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetPrototypeFromType(Type type, [NotNullWhen(true)] out Type? proto)
|
||||
{
|
||||
if (type == typeof(EntProtoId))
|
||||
{
|
||||
proto = typeof(EntityPrototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ProtoId<>))
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>))
|
||||
{
|
||||
proto = field.FieldType.GetGenericArguments().Single();
|
||||
proto = type.GetGenericArguments().Single();
|
||||
DebugTools.Assert(proto != typeof(EntityPrototype), "Use EntProtoId instead of ProtoId<EntityPrototype>");
|
||||
return true;
|
||||
}
|
||||
|
||||
// As far as I know there is no way to check for the nullability of a string field, so we will assume that null
|
||||
// values imply that the field itself is properly marked as nullable.
|
||||
canBeNull = true;
|
||||
|
||||
if (dataField.CustomTypeSerializer == null)
|
||||
return false;
|
||||
|
||||
if (!dataField.CustomTypeSerializer.IsGenericType)
|
||||
return false;
|
||||
|
||||
if (dataField.CustomTypeSerializer.GetGenericTypeDefinition() != typeof(PrototypeIdSerializer<>))
|
||||
return false;
|
||||
|
||||
proto = dataField.CustomTypeSerializer.GetGenericArguments().First();
|
||||
return true;
|
||||
proto = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public partial class PrototypeManager
|
||||
var mapping = node.ToDataNodeCast<MappingDataNode>();
|
||||
var id = mapping.Get<ValueDataNode>("id").Value;
|
||||
|
||||
var data = new PrototypeValidationData(mapping, resourcePath.ToString());
|
||||
var data = new PrototypeValidationData(id, mapping, resourcePath.ToString());
|
||||
mapping.Remove("type");
|
||||
|
||||
if (prototypes.GetOrNew(type).TryAdd(id, data))
|
||||
@@ -65,10 +65,14 @@ public partial class PrototypeManager
|
||||
}
|
||||
}
|
||||
|
||||
var ctx = new YamlValidationContext();
|
||||
var errors = new List<ErrorNode>();
|
||||
foreach (var (type, instances) in prototypes)
|
||||
{
|
||||
foreach (var data in instances.Values)
|
||||
var defaultErrorOccurred = false;
|
||||
foreach (var (id, data) in instances)
|
||||
{
|
||||
errors.Clear();
|
||||
EnsurePushed(data, instances, type);
|
||||
if (data.Mapping.TryGet("abstract", out ValueDataNode? abstractNode)
|
||||
&& bool.Parse(abstractNode.Value))
|
||||
@@ -76,9 +80,25 @@ public partial class PrototypeManager
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = _serializationManager.ValidateNode(type, data.Mapping).GetErrors().ToHashSet();
|
||||
if (result.Count > 0)
|
||||
dict.GetOrNew(data.File).UnionWith(result);
|
||||
// Validate yaml directly
|
||||
errors.AddRange(_serializationManager.ValidateNode(type, data.Mapping).GetErrors());
|
||||
if (errors.Count > 0)
|
||||
dict.GetOrNew(data.File).UnionWith(errors);
|
||||
|
||||
// Create instance & re-serialize it, to validate the default values of data-fields. We still validate
|
||||
// the yaml directly just in case reading & writing the fields somehow modifies their values.
|
||||
try
|
||||
{
|
||||
var instance = _serializationManager.Read(type, data.Mapping, ctx);
|
||||
var mapping = _serializationManager.WriteValue(type, instance, alwaysWrite: true, ctx);
|
||||
errors.AddRange(_serializationManager.ValidateNode(type, mapping, ctx).GetErrors());
|
||||
if (errors.Count > 0)
|
||||
dict.GetOrNew(data.File).UnionWith(errors);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add(new ErrorNode(new ValueDataNode(), $"Caught Exception while validating {type} prototype {id}. Exception: {ex}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,12 +172,17 @@ public partial class PrototypeManager
|
||||
|
||||
private sealed class PrototypeValidationData
|
||||
{
|
||||
public readonly string Id;
|
||||
public MappingDataNode Mapping;
|
||||
public readonly string File;
|
||||
public bool Pushed;
|
||||
|
||||
public PrototypeValidationData(MappingDataNode mapping, string file)
|
||||
public string[]? Parents;
|
||||
public MappingDataNode[]? ParentMappings;
|
||||
|
||||
public PrototypeValidationData(string id, MappingDataNode mapping, string file)
|
||||
{
|
||||
Id = id;
|
||||
File = file;
|
||||
Mapping = mapping;
|
||||
}
|
||||
@@ -176,23 +201,22 @@ public partial class PrototypeManager
|
||||
if (!data.Mapping.TryGet(ParentDataFieldAttribute.Name, out var parentNode))
|
||||
return;
|
||||
|
||||
var parents = _serializationManager.Read<string[]>(parentNode, notNullableOverride: true);
|
||||
var parentNodes = new MappingDataNode[parents.Length];
|
||||
DebugTools.AssertNull(data.Parents);
|
||||
DebugTools.AssertNull(data.ParentMappings);
|
||||
data.Parents = _serializationManager.Read<string[]>(parentNode, notNullableOverride: true);
|
||||
data.ParentMappings = new MappingDataNode[data.Parents.Length];
|
||||
|
||||
foreach (var parentId in parents)
|
||||
var i = 0;
|
||||
foreach (var parentId in data.Parents)
|
||||
{
|
||||
var parent = prototypes[parentId];
|
||||
EnsurePushed(parent, prototypes, type);
|
||||
|
||||
for (var i = 0; i < parents.Length; i++)
|
||||
{
|
||||
parentNodes[i] = parent.Mapping;
|
||||
}
|
||||
data.ParentMappings[i++] = parent.Mapping;
|
||||
}
|
||||
|
||||
data.Mapping = _serializationManager.PushCompositionWithGenericNode(
|
||||
type,
|
||||
parentNodes,
|
||||
data.ParentMappings,
|
||||
data.Mapping);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Robust.Shared.Prototypes;
|
||||
/// Tile alias prototypes, unlike tile prototypes, are implemented here, as they're really just fed to TileDefinitionManager.
|
||||
/// </summary>
|
||||
[Prototype("tileAlias")]
|
||||
public sealed class TileAliasPrototype : IPrototype
|
||||
public sealed partial class TileAliasPrototype : IPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// The target tile ID to alias to.
|
||||
|
||||
62
Robust.Shared/Prototypes/YamlValidationContext.cs
Normal file
62
Robust.Shared/Prototypes/YamlValidationContext.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
internal sealed class YamlValidationContext : ISerializationContext, ITypeSerializer<EntityUid, ValueDataNode>
|
||||
{
|
||||
public SerializationManager.SerializerProvider SerializerProvider { get; } = new();
|
||||
public bool WritingReadingPrototypes => true;
|
||||
|
||||
public YamlValidationContext()
|
||||
{
|
||||
SerializerProvider.RegisterSerializer(this);
|
||||
}
|
||||
|
||||
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(ISerializationManager serializationManager,
|
||||
ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
|
||||
{
|
||||
if (node.Value == "null" || node.Value == "invalid")
|
||||
return new ValidatedValueNode(node);
|
||||
|
||||
return new ErrorNode(node, "Prototypes should not contain EntityUids", true);
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, EntityUid value,
|
||||
IDependencyCollection dependencies, bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
if (!value.Valid)
|
||||
return new ValueDataNode("invalid");
|
||||
|
||||
return new ValueDataNode(value.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
EntityUid ITypeReader<EntityUid, ValueDataNode>.Read(ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context, ISerializationManager.InstantiationDelegate<EntityUid>? _)
|
||||
{
|
||||
if (node.Value == "invalid")
|
||||
return EntityUid.Invalid;
|
||||
|
||||
return EntityUid.Parse(node.Value);
|
||||
}
|
||||
|
||||
[MustUseReturnValue]
|
||||
public EntityUid Copy(ISerializationManager serializationManager, EntityUid source, EntityUid target,
|
||||
bool skipHook,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return new((int)source);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -93,7 +94,17 @@ internal static class RsiLoading
|
||||
states[stateI] = new StateMetadata(stateName, dirValue, delays);
|
||||
}
|
||||
|
||||
return new RsiMetadata(size, states);
|
||||
var textureParams = TextureLoadParameters.Default;
|
||||
if (manifestJson.Load is { } load)
|
||||
{
|
||||
textureParams = new TextureLoadParameters
|
||||
{
|
||||
SampleParameters = TextureSampleParameters.Default,
|
||||
Srgb = load.Srgb
|
||||
};
|
||||
}
|
||||
|
||||
return new RsiMetadata(size, states, textureParams);
|
||||
}
|
||||
|
||||
public static void Warmup()
|
||||
@@ -103,16 +114,11 @@ internal static class RsiLoading
|
||||
JsonSerializer.Deserialize<RsiJsonMetadata>(warmupJson, SerializerOptions);
|
||||
}
|
||||
|
||||
internal sealed class RsiMetadata
|
||||
internal sealed class RsiMetadata(Vector2i size, StateMetadata[] states, TextureLoadParameters loadParameters)
|
||||
{
|
||||
public readonly Vector2i Size;
|
||||
public readonly StateMetadata[] States;
|
||||
|
||||
public RsiMetadata(Vector2i size, StateMetadata[] states)
|
||||
{
|
||||
Size = size;
|
||||
States = states;
|
||||
}
|
||||
public readonly Vector2i Size = size;
|
||||
public readonly StateMetadata[] States = states;
|
||||
public readonly TextureLoadParameters LoadParameters = loadParameters;
|
||||
}
|
||||
|
||||
internal sealed class StateMetadata
|
||||
@@ -134,10 +140,13 @@ internal static class RsiLoading
|
||||
|
||||
// To be directly deserialized.
|
||||
[UsedImplicitly]
|
||||
private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States);
|
||||
private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States, RsiJsonLoad? Load);
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed record StateJsonMetadata(string Name, int? Directions, float[][]? Delays);
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed record RsiJsonLoad(bool Srgb = true);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
|
||||
@@ -4,8 +4,9 @@ using Robust.Shared.Prototypes;
|
||||
namespace Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// This attribute should be used on string fields to validate that they correspond to a valid YAML prototype id.
|
||||
/// If the field needs to be have a default value.
|
||||
/// This attribute should be used on static string or string collection fields to validate that they correspond to
|
||||
/// valid YAML prototype ids. This attribute is not required for static <see cref="ProtoId{T}"/> and
|
||||
/// <see cref="EntProtoId"/> fields, as they automatically get validated.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class ValidatePrototypeIdAttribute<T> : Attribute where T : IPrototype
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace Robust.Shared.Serialization.Manager.Definition
|
||||
nodeVariable),
|
||||
call,
|
||||
dfa.Required
|
||||
? ExpressionUtils.ThrowExpression<RequiredFieldNotMappedException>(fieldDefinition.FieldType, tagConst)
|
||||
? ExpressionUtils.ThrowExpression<RequiredFieldNotMappedException>(fieldDefinition.FieldType, tagConst, typeof(T))
|
||||
: AssignIfNotDefaultExpression(i, targetParam, Expression.Constant(DefaultValues[i], fieldDefinition.FieldType))
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Robust.Shared.Serialization.Manager.Exceptions;
|
||||
|
||||
public sealed class RequiredFieldNotMappedException : Exception
|
||||
{
|
||||
public RequiredFieldNotMappedException(Type type, string field) : base($"Required field {field} of type {type} wasn't mapped.")
|
||||
public RequiredFieldNotMappedException(Type type, string field, Type dataDef) : base($"Required field {field} of type {type} in {dataDef} wasn't mapped.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
|
||||
|
||||
/// <summary>
|
||||
/// Checks that a string corresponds to a valid prototype id. Note that any data fields using this serializer will
|
||||
/// also be validated by <see cref="IPrototypeManager.ValidateFields"/>
|
||||
/// also be validated by <see cref="IPrototypeManager.ValidateStaticFields"/>
|
||||
/// </summary>
|
||||
[Virtual]
|
||||
public class PrototypeIdSerializer<TPrototype> : ITypeValidator<string, ValueDataNode> where TPrototype : class, IPrototype
|
||||
|
||||
@@ -13,22 +13,18 @@ namespace Robust.UnitTesting.Client.GameObjects.Components
|
||||
[TestOf(typeof(TransformComponent))]
|
||||
public sealed class TransformComponentTests
|
||||
{
|
||||
private static readonly MapId TestMapId = new(1);
|
||||
|
||||
private static (ISimulation, EntityUid gridA, EntityUid gridB) SimulationFactory()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.InitializeInstance();
|
||||
|
||||
var mapId = sim.Resolve<IEntityManager>().System<SharedMapSystem>().CreateMap();
|
||||
var mapManager = sim.Resolve<IMapManager>();
|
||||
|
||||
// Adds the map with id 1, and spawns entity 1 as the map entity.
|
||||
mapManager.CreateMap(TestMapId);
|
||||
|
||||
// Adds two grids to use in tests.
|
||||
var gridA = mapManager.CreateGridEntity(TestMapId);
|
||||
var gridB = mapManager.CreateGridEntity(TestMapId);
|
||||
var gridA = mapManager.CreateGridEntity(mapId);
|
||||
var gridB = mapManager.CreateGridEntity(mapId);
|
||||
|
||||
return (sim, gridA, gridB);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed partial class ComponentMapInitTest
|
||||
var sim = simFactory.InitializeInstance();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var mapManager = sim.Resolve<IMapManager>();
|
||||
var mapId = mapManager.CreateMap();
|
||||
sim.Resolve<IEntityManager>().System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
|
||||
var ent = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
Assert.That(entManager.GetComponent<MetaDataComponent>(ent).EntityLifeStage, Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -19,14 +18,15 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
[TestFixture, Parallelizable]
|
||||
public sealed partial class ContainerTest
|
||||
{
|
||||
private static EntityCoordinates _coords;
|
||||
|
||||
private static ISimulation SimulationFactory()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.InitializeInstance();
|
||||
|
||||
// Adds the map with id 1, and spawns entity 1 as the map entity.
|
||||
sim.AddMap(1);
|
||||
var map = sim.CreateMap();
|
||||
_coords = new EntityCoordinates(map.Item1, default);
|
||||
|
||||
return sim;
|
||||
}
|
||||
@@ -37,8 +37,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
var sim = SimulationFactory();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
|
||||
var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var entity = sim.SpawnEntity(null,_coords);
|
||||
|
||||
var container = containerSys.MakeContainer<Container>(entity, "dummy");
|
||||
|
||||
@@ -74,9 +73,8 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
var sim = SimulationFactory();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
|
||||
var owner = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var inserted = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var owner = sim.SpawnEntity(null,_coords);
|
||||
var inserted = sim.SpawnEntity(null,_coords);
|
||||
var transform = entManager.GetComponent<TransformComponent>(inserted);
|
||||
|
||||
var container = containerSys.MakeContainer<Container>(owner, "dummy");
|
||||
@@ -104,11 +102,10 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
var sim = SimulationFactory();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
|
||||
var owner = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var inserted = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var owner = sim.SpawnEntity(null,_coords);
|
||||
var inserted = sim.SpawnEntity(null,_coords);
|
||||
var transform = entManager.GetComponent<TransformComponent>(inserted);
|
||||
var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var entity = sim.SpawnEntity(null,_coords);
|
||||
|
||||
var container = containerSys.MakeContainer<Container>(owner, "dummy");
|
||||
Assert.That(containerSys.Insert(inserted, container), Is.True);
|
||||
@@ -132,8 +129,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
|
||||
var coordinates = new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0));
|
||||
var coordinates =_coords;
|
||||
var entityOne = sim.SpawnEntity(null, coordinates);
|
||||
var entityTwo = sim.SpawnEntity(null, coordinates);
|
||||
var entityThree = sim.SpawnEntity(null, coordinates);
|
||||
@@ -165,8 +161,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
|
||||
var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var entity = sim.SpawnEntity(null,_coords);
|
||||
var container = containerSys.MakeContainer<Container>(entity, "dummy");
|
||||
|
||||
Assert.That(containerSys.Insert(entity, container), Is.False);
|
||||
@@ -178,9 +173,8 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
|
||||
var mapEnt = EntityUid.FirstUid;
|
||||
var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var mapEnt = new EntityUid(1);
|
||||
var entity = sim.SpawnEntity(null,_coords);
|
||||
var container = containerSys.MakeContainer<Container>(entity, "dummy");
|
||||
|
||||
Assert.That(containerSys.Insert(mapEnt, container), Is.False);
|
||||
@@ -194,7 +188,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
|
||||
var grid = sim.Resolve<IMapManager>().CreateGridEntity(new MapId(1)).Owner;
|
||||
var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var entity = sim.SpawnEntity(null,_coords);
|
||||
var container = containerSys.MakeContainer<Container>(entity, "dummy");
|
||||
|
||||
Assert.That(containerSys.Insert(grid, container), Is.False);
|
||||
@@ -207,10 +201,9 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
var sim = SimulationFactory();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
|
||||
var containerEntity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var containerEntity = sim.SpawnEntity(null,_coords);
|
||||
var container = containerSys.MakeContainer<Container>(containerEntity, "dummy");
|
||||
var insertEntity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var insertEntity = sim.SpawnEntity(null,_coords);
|
||||
|
||||
var result = containerSys.Insert(insertEntity, container);
|
||||
|
||||
@@ -230,10 +223,9 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
|
||||
var containerEntity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var containerEntity = sim.SpawnEntity(null,_coords);
|
||||
var container = containerSys.MakeContainer<Container>(containerEntity, "dummy");
|
||||
var insertEntity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var insertEntity = sim.SpawnEntity(null,_coords);
|
||||
|
||||
var result = containerSys.Remove(insertEntity, container);
|
||||
|
||||
@@ -245,12 +237,11 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var containerSys = sim.Resolve<IEntitySystemManager>().GetEntitySystem<ContainerSystem>();
|
||||
|
||||
var entity1 = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var entity1 = sim.SpawnEntity(null,_coords);
|
||||
var container1 = containerSys.MakeContainer<Container>(entity1, "dummy");
|
||||
var entity2 = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var entity2 = sim.SpawnEntity(null,_coords);
|
||||
var container2 = containerSys.MakeContainer<Container>(entity2, "dummy");
|
||||
var transferEntity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var transferEntity = sim.SpawnEntity(null,_coords);
|
||||
containerSys.Insert(transferEntity, container1);
|
||||
|
||||
var result = containerSys.Insert(transferEntity, container2);
|
||||
@@ -266,10 +257,9 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
var sim = SimulationFactory();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var containerSys = entManager.System<ContainerSystem>();
|
||||
|
||||
var entity = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var entity = sim.SpawnEntity(null,_coords);
|
||||
var container = containerSys.MakeContainer<Container>(entity, "dummy");
|
||||
var childEnt = sim.SpawnEntity(null, new EntityCoordinates(EntityUid.FirstUid, new Vector2(0, 0)));
|
||||
var childEnt = sim.SpawnEntity(null,_coords);
|
||||
|
||||
container.OccludesLight = true;
|
||||
container.ShowContents = true;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user