mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
26 Commits
prototype-
...
v0.8.57
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
918bbd3f01 | ||
|
|
a1441d5051 | ||
|
|
58d6189c40 | ||
|
|
58defed1d2 | ||
|
|
27a94384d0 | ||
|
|
4b8f5815db | ||
|
|
d830eef435 | ||
|
|
e7c4bf7341 | ||
|
|
162e646404 | ||
|
|
93049fcacf | ||
|
|
f25ad8ece9 | ||
|
|
1aaf3b9250 | ||
|
|
11f6cff6df | ||
|
|
c2ea57c95a | ||
|
|
f61ae5da6b | ||
|
|
8ae9a5f2da | ||
|
|
3f7e89e006 | ||
|
|
cee4e4d62e | ||
|
|
14a3783760 | ||
|
|
b4607f7b1f | ||
|
|
5a28c16cae | ||
|
|
9e8bf861ea | ||
|
|
4cfb9210d0 | ||
|
|
681f77a796 | ||
|
|
2a7f1cbf48 | ||
|
|
c4e63cfdc7 |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<PropertyGroup><Version>0.8.52</Version></PropertyGroup>
|
||||
<PropertyGroup><Version>0.8.57</Version></PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -510,7 +510,7 @@ namespace Robust.Client
|
||||
logManager.GetSawmill("discord").Level = LogLevel.Warning;
|
||||
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
|
||||
logManager.GetSawmill("szr").Level = LogLevel.Info;
|
||||
logManager.GetSawmill("loc").Level = LogLevel.Error;
|
||||
logManager.GetSawmill("loc").Level = LogLevel.Warning;
|
||||
|
||||
#if DEBUG_ONLY_FCE_INFO
|
||||
#if DEBUG_ONLY_FCE_LOG
|
||||
|
||||
@@ -119,7 +119,8 @@ namespace Robust.Client.GameObjects
|
||||
/// This is useful to allow layer map configs to be defined in prototypes,
|
||||
/// while still allowing code to create configs if they're absent.
|
||||
/// </remarks>
|
||||
void LayerMapReserveBlank(object key);
|
||||
/// <returns>Index of the new layer.</returns>
|
||||
int LayerMapReserveBlank(object key);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a layer without texture (thus falling back to the error texture).
|
||||
@@ -145,8 +146,8 @@ namespace Robust.Client.GameObjects
|
||||
void RemoveLayer(int layer);
|
||||
void RemoveLayer(object layerKey);
|
||||
|
||||
void LayerSetShader(int layer, ShaderInstance shader);
|
||||
void LayerSetShader(object layerKey, ShaderInstance shader);
|
||||
void LayerSetShader(int layer, ShaderInstance shader, string? prototype = null);
|
||||
void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null);
|
||||
void LayerSetShader(int layer, string shaderName);
|
||||
void LayerSetShader(object layerKey, string shaderName);
|
||||
|
||||
@@ -217,8 +218,8 @@ namespace Robust.Client.GameObjects
|
||||
int GetLayerDirectionCount(ISpriteLayer layer);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate sprite bounding box in world-space coordinates.
|
||||
/// Calculate the rotated sprite bounding box in world-space coordinates.
|
||||
/// </summary>
|
||||
Box2 CalculateBoundingBox(Vector2 worldPos);
|
||||
Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, IEye? eye = null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
RSI.State.Direction EffectiveDirection(Angle worldRotation);
|
||||
|
||||
Vector2 LocalToLayer(Vector2 localPos);
|
||||
|
||||
/// <summary>
|
||||
/// Layer size in pixels.
|
||||
/// Don't account layer scale or sprite world transform.
|
||||
@@ -37,6 +35,6 @@ namespace Robust.Client.GameObjects
|
||||
/// Calculate layer bounding box in sprite local-space coordinates.
|
||||
/// </summary>
|
||||
/// <returns>Bounding box in sprite local-space coordinates.</returns>
|
||||
Box2 CalculateBoundingBox();
|
||||
Box2 CalculateBoundingBox(Angle worldAngle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,10 +84,16 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var sprite in comp.SpriteTree.QueryAabb(localAABB))
|
||||
{
|
||||
var worldPos = _entityManager.GetComponent<TransformComponent>(sprite.Owner).WorldPosition;
|
||||
var bounds = sprite.CalculateBoundingBox(worldPos);
|
||||
var (worldPos, worldRot) = _entityManager.GetComponent<TransformComponent>(sprite.Owner).GetWorldPositionRotation();
|
||||
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot);
|
||||
|
||||
// Get scaled down bounds used to indicate the "south" of a sprite.
|
||||
var localBound = bounds.Box;
|
||||
var smallLocal = localBound.Scale(0.2f).Translated(-new Vector2(0f, localBound.Extents.Y));
|
||||
var southIndicator = new Box2Rotated(smallLocal, bounds.Rotation, bounds.Origin);
|
||||
|
||||
handle.DrawRect(bounds, Color.Red.WithAlpha(0.2f));
|
||||
handle.DrawRect(bounds.Scale(0.2f).Translated(-new Vector2(0f, bounds.Extents.Y)), Color.Blue.WithAlpha(0.5f));
|
||||
handle.DrawRect(southIndicator, Color.Blue.WithAlpha(0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
using RSIDirection = Robust.Client.Graphics.RSI.State.Direction;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -31,6 +32,8 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IResourceCache resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager prototypes = default!;
|
||||
[Dependency] private readonly IEntityManager entities = default!;
|
||||
[Dependency] private readonly IReflectionManager reflection = default!;
|
||||
[Dependency] private readonly IEyeManager eyeManager = default!;
|
||||
|
||||
[DataField("visible")]
|
||||
private bool _visible = true;
|
||||
@@ -72,7 +75,11 @@ namespace Robust.Client.GameObjects
|
||||
public Vector2 Scale
|
||||
{
|
||||
get => scale;
|
||||
set => scale = value;
|
||||
set
|
||||
{
|
||||
scale = value;
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("rotation")]
|
||||
@@ -83,7 +90,11 @@ namespace Robust.Client.GameObjects
|
||||
public Angle Rotation
|
||||
{
|
||||
get => rotation;
|
||||
set => rotation = value;
|
||||
set
|
||||
{
|
||||
rotation = value;
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("offset")]
|
||||
@@ -97,12 +108,18 @@ namespace Robust.Client.GameObjects
|
||||
public Vector2 Offset
|
||||
{
|
||||
get => offset;
|
||||
set => offset = value;
|
||||
set
|
||||
{
|
||||
offset = value;
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("color")]
|
||||
private Color color = Color.White;
|
||||
|
||||
public Matrix3 LocalMatrix = Matrix3.Identity;
|
||||
|
||||
[Animatable]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Color Color
|
||||
@@ -134,113 +151,7 @@ namespace Robust.Client.GameObjects
|
||||
Layers.Clear();
|
||||
foreach (var layerDatum in value)
|
||||
{
|
||||
var anyTextureAttempted = false;
|
||||
var layer = new Layer(this);
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
|
||||
{
|
||||
var path = TextureRoot / layerDatum.RsiPath;
|
||||
|
||||
if (IoCManager.Resolve<IResourceCache>().TryGetResource(path, out RSIResource? resource))
|
||||
{
|
||||
layer.RSI = resource.RSI;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.State))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
var theRsi = layer.RSI ?? BaseRSI;
|
||||
if (theRsi == null)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
|
||||
layerDatum.State);
|
||||
}
|
||||
else
|
||||
{
|
||||
var stateid = new RSI.StateId(layerDatum.State);
|
||||
layer.State = stateid;
|
||||
if (theRsi.TryGetState(stateid, out var state))
|
||||
{
|
||||
// Always use south because this layer will be cached in the serializer.
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
|
||||
stateid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
if (layer.State.IsValid)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Cannot specify 'texture' on a layer if it has an RSI state specified."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.Texture =
|
||||
IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
|
||||
{
|
||||
if (IoCManager.Resolve<IPrototypeManager>().TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
|
||||
{
|
||||
layer.Shader = prototype.Instance();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Shader prototype '{0}' does not exist.",
|
||||
layerDatum.Shader);
|
||||
}
|
||||
}
|
||||
|
||||
layer.Color = layerDatum.Color;
|
||||
layer.Rotation = layerDatum.Rotation;
|
||||
layer._offset = layerDatum.Offset;
|
||||
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
||||
layer.Visible = anyTextureAttempted && layerDatum.Visible;
|
||||
layer.Scale = layerDatum.Scale;
|
||||
|
||||
Layers.Add(layer);
|
||||
|
||||
if (layerDatum.MapKeys != null)
|
||||
{
|
||||
var index = Layers.Count - 1;
|
||||
foreach (var keyString in layerDatum.MapKeys)
|
||||
{
|
||||
object key;
|
||||
if (IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
|
||||
if (LayerMap.ContainsKey(key))
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
LayerMap.Add(key, index);
|
||||
}
|
||||
}
|
||||
AddLayer(layerDatum);
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
@@ -336,11 +247,13 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(rsi))
|
||||
{
|
||||
var rsiPath = TextureRoot / rsi;
|
||||
if(IoCManager.Resolve<IResourceCache>().TryGetResource(rsiPath, out RSIResource? resource))
|
||||
if(resourceCache.TryGetResource(rsiPath, out RSIResource? resource))
|
||||
{
|
||||
BaseRSI = resource.RSI;
|
||||
}
|
||||
@@ -373,6 +286,8 @@ namespace Robust.Client.GameObjects
|
||||
LayerMap.Clear();
|
||||
LayerDatums = layerDatums;
|
||||
}
|
||||
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -390,6 +305,7 @@ namespace Robust.Client.GameObjects
|
||||
offset = other.offset;
|
||||
rotation = other.rotation;
|
||||
scale = other.scale;
|
||||
UpdateLocalMatrix();
|
||||
drawDepth = other.drawDepth;
|
||||
_screenLock = other._screenLock;
|
||||
_overrideDirection = other._overrideDirection;
|
||||
@@ -415,9 +331,14 @@ namespace Robust.Client.GameObjects
|
||||
RenderOrder = other.RenderOrder;
|
||||
}
|
||||
|
||||
internal void UpdateLocalMatrix()
|
||||
{
|
||||
LocalMatrix = Matrix3.CreateTransform(in offset, in rotation, in scale);
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrix()
|
||||
{
|
||||
return Matrix3.CreateTransform(in offset, in rotation, in scale);
|
||||
return LocalMatrix;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -462,14 +383,17 @@ namespace Robust.Client.GameObjects
|
||||
_layerMapShared = false;
|
||||
}
|
||||
|
||||
public void LayerMapReserveBlank(object key)
|
||||
public int LayerMapReserveBlank(object key)
|
||||
{
|
||||
if (LayerMapTryGet(key, out var _))
|
||||
if (LayerMapTryGet(key, out var index))
|
||||
{
|
||||
return;
|
||||
return index;
|
||||
}
|
||||
|
||||
LayerMapSet(key, AddBlankLayer());
|
||||
index = AddBlankLayer();
|
||||
LayerMapSet(key, index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public int AddBlankLayer(int? newIndex = null)
|
||||
@@ -478,6 +402,19 @@ namespace Robust.Client.GameObjects
|
||||
return AddLayer(layer, newIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new layer based on some <see cref="PrototypeLayerData"/>.
|
||||
/// </summary>
|
||||
public int AddLayer(PrototypeLayerData layerDatum, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this);
|
||||
|
||||
var index = AddLayer(layer, newIndex);
|
||||
|
||||
LayerSetData(index, layerDatum);
|
||||
return index;
|
||||
}
|
||||
|
||||
public int AddLayer(string texturePath, int? newIndex = null)
|
||||
{
|
||||
return AddLayer(new ResourcePath(texturePath), newIndex);
|
||||
@@ -653,7 +590,144 @@ namespace Robust.Client.GameObjects
|
||||
RemoveLayer(layer);
|
||||
}
|
||||
|
||||
public void LayerSetShader(int layer, ShaderInstance? shader)
|
||||
/// <summary>
|
||||
/// Fills in a layer's values using some <see cref="PrototypeLayerData"/>.
|
||||
/// </summary>
|
||||
public void LayerSetData(int index, PrototypeLayerData layerDatum)
|
||||
{
|
||||
if (Layers.Count <= index)
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Layer with index '{0}' does not exist, cannot set layer data! Trace:\n{1}",
|
||||
index, Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
var layer = Layers[index];
|
||||
|
||||
var anyTextureAttempted = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
|
||||
{
|
||||
var path = TextureRoot / layerDatum.RsiPath;
|
||||
|
||||
if (resourceCache.TryGetResource(path, out RSIResource? resource))
|
||||
{
|
||||
layer.RSI = resource.RSI;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.State))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
var theRsi = layer.RSI ?? BaseRSI;
|
||||
if (theRsi == null)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
|
||||
layerDatum.State);
|
||||
}
|
||||
else
|
||||
{
|
||||
var stateid = new RSI.StateId(layerDatum.State);
|
||||
layer.State = stateid;
|
||||
if (theRsi.TryGetState(stateid, out var state))
|
||||
{
|
||||
// Always use south because this layer will be cached in the serializer.
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
|
||||
stateid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
if (layer.State.IsValid)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Cannot specify 'texture' on a layer if it has an RSI state specified."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.Texture =
|
||||
resourceCache.GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
|
||||
{
|
||||
if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
|
||||
{
|
||||
layer.ShaderPrototype = layerDatum.Shader;
|
||||
layer.Shader = prototype.Instance();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Shader prototype '{0}' does not exist.",
|
||||
layerDatum.Shader);
|
||||
}
|
||||
}
|
||||
|
||||
if (layerDatum.MapKeys != null)
|
||||
{
|
||||
foreach (var keyString in layerDatum.MapKeys)
|
||||
{
|
||||
object key;
|
||||
if (reflection.TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
|
||||
if (LayerMap.TryGetValue(key, out var mappedIndex))
|
||||
{
|
||||
if (mappedIndex != index)
|
||||
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
_layerMapEnsurePrivate();
|
||||
LayerMap[key] = index;
|
||||
}
|
||||
}
|
||||
|
||||
layer.Color = layerDatum.Color;
|
||||
layer._rotation = layerDatum.Rotation;
|
||||
layer._offset = layerDatum.Offset;
|
||||
layer._scale = layerDatum.Scale;
|
||||
layer.UpdateLocalMatrix();
|
||||
|
||||
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
||||
layer.Visible = anyTextureAttempted && layerDatum.Visible;
|
||||
}
|
||||
|
||||
public void LayerSetData(object layerKey, PrototypeLayerData data)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer))
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Layer with key '{0}' does not exist, cannot set shader! Trace:\n{1}",
|
||||
layerKey, Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
LayerSetData(layer, data);
|
||||
}
|
||||
|
||||
public void LayerSetShader(int layer, ShaderInstance? shader, string? prototype = null)
|
||||
{
|
||||
if (Layers.Count <= layer)
|
||||
{
|
||||
@@ -664,9 +738,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var theLayer = Layers[layer];
|
||||
theLayer.Shader = shader;
|
||||
theLayer.ShaderPrototype = prototype;
|
||||
}
|
||||
|
||||
public void LayerSetShader(object layerKey, ShaderInstance shader)
|
||||
public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer))
|
||||
{
|
||||
@@ -675,7 +750,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
LayerSetShader(layer, shader);
|
||||
LayerSetShader(layer, shader, prototype);
|
||||
}
|
||||
|
||||
public void LayerSetShader(int layer, string shaderName)
|
||||
@@ -684,10 +759,12 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Shader prototype '{0}' does not exist. Trace:\n{1}", shaderName,
|
||||
Environment.StackTrace);
|
||||
|
||||
LayerSetShader(layer, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will set the shader to null if it does not exist.
|
||||
LayerSetShader(layer, prototype?.Instance());
|
||||
LayerSetShader(layer, prototype.Instance(), shaderName);
|
||||
}
|
||||
|
||||
public void LayerSetShader(object layerKey, string shaderName)
|
||||
@@ -1148,7 +1225,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
Layers[layer].SetOffset(layerOffset);
|
||||
Layers[layer].Offset = layerOffset;
|
||||
}
|
||||
|
||||
public void LayerSetOffset(object layerKey, Vector2 layerOffset)
|
||||
@@ -1230,25 +1307,6 @@ namespace Robust.Client.GameObjects
|
||||
RenderInternal(drawingHandle, eyeRotation, worldRotation, worldPosition, overrideDir);
|
||||
}
|
||||
|
||||
private void CalcModelMatrix(int numDirs, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
|
||||
{
|
||||
Angle angle;
|
||||
|
||||
if (_screenLock)
|
||||
{
|
||||
// Negate the eye rotation in the model matrix, so that later when the view matrix is applied the
|
||||
// sprite will be locked upright to the screen
|
||||
angle = new Angle(-eyeRotation.Theta);
|
||||
}
|
||||
else
|
||||
{
|
||||
angle = CalcRectWorldAngle(worldRotation + eyeRotation, numDirs) - eyeRotation;
|
||||
}
|
||||
|
||||
var sWorldRotation = angle;
|
||||
modelMatrix = Matrix3.CreateTransform(in worldPosition, in sWorldRotation);
|
||||
}
|
||||
|
||||
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
|
||||
{
|
||||
// Reduce the angles to fix math shenanigans
|
||||
@@ -1257,56 +1315,16 @@ namespace Robust.Client.GameObjects
|
||||
if (worldRotation.Theta < 0)
|
||||
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
|
||||
|
||||
// sprite matrix, WITHOUT offset.
|
||||
// offset is applied after sprite numDirs snapping/rotation correction
|
||||
// --> apply at same time as layer offset
|
||||
var spriteMatrix = Matrix3.CreateTransform(Vector2.Zero, rotation, scale);
|
||||
// worldRotation + eyeRotation should be the angle of the entity on-screen. If no-rot is enabled this is just set to zero.
|
||||
// However, at some point later the eye-matrix is applied separately, so we subtract -eye rotation for now:
|
||||
var entityMatrix = Matrix3.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation);
|
||||
|
||||
Matrix3.Multiply(ref LocalMatrix, ref entityMatrix, out var transform);
|
||||
|
||||
var angle = worldRotation + eyeRotation; // angle on-screen. Used to decide the direction of 4/8 directional RSIs
|
||||
foreach (var layer in Layers)
|
||||
{
|
||||
if (!layer.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var numDirs = GetLayerDirectionCount(layer);
|
||||
var layerRotation = worldRotation + layer.Rotation;
|
||||
var layerPosition = worldPosition + layerRotation.RotateVec(layer._offset + offset);
|
||||
|
||||
CalcModelMatrix(numDirs, eyeRotation, layerRotation, layerPosition, out var modelMatrix);
|
||||
Matrix3.Multiply(ref spriteMatrix, ref modelMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
RenderLayer(drawingHandle, layer, eyeRotation, layerRotation, overrideDirection);
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderLayer(DrawingHandleWorld drawingHandle, Layer layer, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection)
|
||||
{
|
||||
var texture = GetRenderTexture(layer, worldRotation + eyeRotation, overrideDirection);
|
||||
|
||||
if (layer.Shader != null)
|
||||
{
|
||||
drawingHandle.UseShader(layer.Shader);
|
||||
}
|
||||
|
||||
var layerColor = color * layer.Color;
|
||||
|
||||
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter);
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(position, textureSize);
|
||||
|
||||
// TODO: Implement layer-specific rotation and scale.
|
||||
// Apply these directly to the box.
|
||||
// Oh and when you do update Layer.LocalToLayer so content doesn't break.
|
||||
|
||||
// handle.Modulate changes the color
|
||||
// drawingHandle.SetTransform() is set above, turning the quad into local space vertices
|
||||
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
|
||||
|
||||
if (layer.Shader != null)
|
||||
{
|
||||
drawingHandle.UseShader(null);
|
||||
layer.Render(drawingHandle, ref transform, angle, overrideDirection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1342,27 +1360,6 @@ namespace Robust.Client.GameObjects
|
||||
};
|
||||
}
|
||||
|
||||
private Texture GetRenderTexture(Layer layer, Angle worldRotation, Direction? overrideDirection)
|
||||
{
|
||||
var texture = layer.Texture;
|
||||
|
||||
if (layer.State.IsValid)
|
||||
{
|
||||
// Pull texture from RSI state instead.
|
||||
var rsi = layer.RSI ?? BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
|
||||
{
|
||||
state = GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
var layerSpecificDir = layer.EffectiveDirection(state, worldRotation, overrideDirection);
|
||||
texture = state.GetFrame(layerSpecificDir, layer.AnimationFrame);
|
||||
}
|
||||
|
||||
texture ??= resourceCache.GetFallback<TextureResource>().Texture;
|
||||
return texture;
|
||||
}
|
||||
|
||||
public void FrameUpdate(float delta)
|
||||
{
|
||||
foreach (var t in Layers)
|
||||
@@ -1417,12 +1414,14 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
Visible = thestate.Visible;
|
||||
DrawDepth = thestate.DrawDepth;
|
||||
Scale = thestate.Scale;
|
||||
Rotation = thestate.Rotation;
|
||||
Offset = thestate.Offset;
|
||||
scale = thestate.Scale;
|
||||
rotation = thestate.Rotation;
|
||||
offset = thestate.Offset;
|
||||
UpdateLocalMatrix();
|
||||
Color = thestate.Color;
|
||||
RenderOrder = thestate.RenderOrder;
|
||||
|
||||
|
||||
if (thestate.BaseRsiPath != null && BaseRSI != null)
|
||||
{
|
||||
if (resourceCache.TryGetResource<RSIResource>(TextureRoot / thestate.BaseRsiPath, out var res))
|
||||
@@ -1438,43 +1437,8 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe optimize this to NOT full clear.
|
||||
Layers.Clear();
|
||||
for (var i = 0; i < thestate.Layers.Count; i++)
|
||||
{
|
||||
var netlayer = thestate.Layers[i];
|
||||
var layer = new Layer(this)
|
||||
{
|
||||
// These are easy so do them here.
|
||||
Scale = netlayer.Scale,
|
||||
Rotation = netlayer.Rotation,
|
||||
Visible = netlayer.Visible,
|
||||
Color = netlayer.Color
|
||||
};
|
||||
Layers.Add(layer);
|
||||
|
||||
// Using the public API to handle errors.
|
||||
// Probably slow as crap.
|
||||
// Who am I kidding, DEFINITELY.
|
||||
if (netlayer.Shader != null)
|
||||
{
|
||||
LayerSetShader(i, netlayer.Shader);
|
||||
}
|
||||
|
||||
if (netlayer.RsiPath != null)
|
||||
{
|
||||
LayerSetRSI(i, netlayer.RsiPath);
|
||||
}
|
||||
|
||||
if (netlayer.TexturePath != null)
|
||||
{
|
||||
LayerSetTexture(i, netlayer.TexturePath);
|
||||
}
|
||||
else if (netlayer.State != null)
|
||||
{
|
||||
LayerSetState(i, netlayer.State);
|
||||
}
|
||||
}
|
||||
// Maybe optimize this to NOT fully clear the layers. (see LayerDatums setter function)
|
||||
LayerDatums = thestate.Layers;
|
||||
}
|
||||
|
||||
private void QueueUpdateIsInert()
|
||||
@@ -1553,35 +1517,61 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box2 CalculateBoundingBox(Vector2 worldPos)
|
||||
public Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, IEye? eye = null)
|
||||
{
|
||||
// fast check for empty sprites
|
||||
if (Layers.Count == 0)
|
||||
return new Box2(worldPos, worldPos);
|
||||
if (!Visible || Layers.Count == 0)
|
||||
{
|
||||
return new Box2Rotated(new Box2(worldPosition, worldPosition), Angle.Zero, worldPosition);
|
||||
}
|
||||
|
||||
// We need to modify world rotation so that it lies between 0 and 2pi.
|
||||
// This matters for 4 or 8 directional sprites deciding which quadrant (octant?) they lie in.
|
||||
// the 0->2pi convention is set by the sprite-rendering code that selects the layers.
|
||||
// See RenderInternal().
|
||||
|
||||
worldRotation = worldRotation.Reduced();
|
||||
if (worldRotation.Theta < 0)
|
||||
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
|
||||
|
||||
eye ??= eyeManager.CurrentEye;
|
||||
|
||||
// Need relative angle on screen for determining the sprite rsi direction.
|
||||
Angle relativeRotation = NoRotation
|
||||
? Angle.Zero
|
||||
: worldRotation + eye.Rotation;
|
||||
|
||||
// we need to calculate bounding box taking into account all nested layers
|
||||
// because layers can have offsets, scale or rotation we need to calculate a new BB
|
||||
// based on lowest bottomLeft and hightest topRight points from all layers
|
||||
var box = Layers[0].CalculateBoundingBox();
|
||||
// because layers can have offsets, scale or rotation, we need to calculate a new BB
|
||||
// based on lowest bottomLeft and highest topRight points from all layers
|
||||
var box = Layers[0].CalculateBoundingBox(relativeRotation);
|
||||
|
||||
for (int i = 1; i < Layers.Count; i++)
|
||||
{
|
||||
var layer = Layers[i];
|
||||
var layerBB = layer.CalculateBoundingBox();
|
||||
if (!layer.Visible) continue;
|
||||
var layerBB = layer.CalculateBoundingBox(relativeRotation);
|
||||
|
||||
box = box.Union(layerBB);
|
||||
}
|
||||
|
||||
// apply sprite transformations and calculate sprite bounding box
|
||||
// we can optimize it a bit, if sprite doesn't have rotation
|
||||
var spriteBox = box.Scale(Scale);
|
||||
var spriteHasRotation = !Rotation.EqualsApprox(Angle.Zero);
|
||||
var spriteBB = spriteHasRotation ?
|
||||
new Box2Rotated(spriteBox, Rotation).CalcBoundingBox() : spriteBox;
|
||||
// Next, what we do is take the box2 and apply the sprite's transform, and then the entity's transform. We
|
||||
// could do this via Matrix3.TransformBox, but that only yields bounding boxes. So instead we manually
|
||||
// transform our box by the combination of these matrices:
|
||||
|
||||
// move it all to world transform system (with sprite offset)
|
||||
var worldBB = spriteBB.Translated(Offset + worldPos);
|
||||
return worldBB;
|
||||
if (Scale != Vector2.One)
|
||||
box = box.Scale(Scale);
|
||||
|
||||
var adjustedOffset = NoRotation
|
||||
? (-eye.Rotation).RotateVec(Offset)
|
||||
: worldRotation.RotateVec(Offset);
|
||||
|
||||
Vector2 position = adjustedOffset + worldPosition;
|
||||
Angle finalRotation = NoRotation
|
||||
? Rotation - eye.Rotation
|
||||
: Rotation + worldRotation;
|
||||
|
||||
return new Box2Rotated(box.Translated(position), finalRotation, position);
|
||||
}
|
||||
|
||||
internal void UpdateBounds()
|
||||
@@ -1615,10 +1605,11 @@ namespace Robust.Client.GameObjects
|
||||
Flip = 3,
|
||||
}
|
||||
|
||||
public sealed class Layer : ISpriteLayer
|
||||
public sealed class Layer : ISpriteLayer, ISerializationHooks
|
||||
{
|
||||
[ViewVariables] private readonly SpriteComponent _parent;
|
||||
|
||||
[ViewVariables] public string? ShaderPrototype;
|
||||
[ViewVariables] public ShaderInstance? Shader;
|
||||
[ViewVariables] public Texture? Texture;
|
||||
|
||||
@@ -1628,11 +1619,37 @@ namespace Robust.Client.GameObjects
|
||||
[ViewVariables] public float AnimationTime;
|
||||
[ViewVariables] public int AnimationFrame;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Scale { get; set; } = Vector2.One;
|
||||
public Matrix3 LocalMatrix = Matrix3.Identity;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Angle Rotation { get; set; }
|
||||
public Vector2 Scale
|
||||
{
|
||||
get => _scale;
|
||||
set
|
||||
{
|
||||
if (_scale.EqualsApprox(value)) return;
|
||||
|
||||
_scale = value;
|
||||
UpdateLocalMatrix();
|
||||
_parent.UpdateBounds();
|
||||
}
|
||||
}
|
||||
internal Vector2 _scale = Vector2.One;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Angle Rotation
|
||||
{
|
||||
get => _rotation;
|
||||
set
|
||||
{
|
||||
if (_rotation.EqualsApprox(value)) return;
|
||||
|
||||
_rotation = value;
|
||||
UpdateLocalMatrix();
|
||||
_parent.UpdateBounds();
|
||||
}
|
||||
}
|
||||
internal Angle _rotation = Angle.Zero;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Visible = true;
|
||||
@@ -1652,6 +1669,7 @@ namespace Robust.Client.GameObjects
|
||||
if (_offset.EqualsApprox(value)) return;
|
||||
|
||||
_offset = value;
|
||||
UpdateLocalMatrix();
|
||||
_parent.UpdateBounds();
|
||||
}
|
||||
}
|
||||
@@ -1675,6 +1693,7 @@ namespace Robust.Client.GameObjects
|
||||
if (toClone.Shader != null)
|
||||
{
|
||||
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
|
||||
ShaderPrototype = toClone.ShaderPrototype;
|
||||
}
|
||||
Texture = toClone.Texture;
|
||||
RSI = toClone.RSI;
|
||||
@@ -1682,14 +1701,26 @@ namespace Robust.Client.GameObjects
|
||||
AnimationTimeLeft = toClone.AnimationTimeLeft;
|
||||
AnimationTime = toClone.AnimationTime;
|
||||
AnimationFrame = toClone.AnimationFrame;
|
||||
Scale = toClone.Scale;
|
||||
Rotation = toClone.Rotation;
|
||||
_scale = toClone.Scale;
|
||||
_rotation = toClone.Rotation;
|
||||
_offset = toClone.Offset;
|
||||
UpdateLocalMatrix();
|
||||
Visible = toClone.Visible;
|
||||
Color = toClone.Color;
|
||||
DirOffset = toClone.DirOffset;
|
||||
AutoAnimated = toClone.AutoAnimated;
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
|
||||
internal void UpdateLocalMatrix()
|
||||
{
|
||||
LocalMatrix = Matrix3.CreateTransform(in _offset, in _rotation, in _scale);
|
||||
}
|
||||
|
||||
RSI? ISpriteLayer.Rsi { get => RSI; set => SetRsi(value); }
|
||||
RSI.StateId ISpriteLayer.RsiState { get => State; set => SetState(value); }
|
||||
Texture? ISpriteLayer.Texture { get => Texture; set => SetTexture(value); }
|
||||
@@ -1701,7 +1732,7 @@ namespace Robust.Client.GameObjects
|
||||
Color = Color,
|
||||
Rotation = Rotation,
|
||||
Scale = Scale,
|
||||
//todo Shader = Shader,
|
||||
Shader = ShaderPrototype,
|
||||
State = State.Name,
|
||||
Visible = Visible,
|
||||
RsiPath = RSI?.Path?.ToString(),
|
||||
@@ -1730,7 +1761,7 @@ namespace Robust.Client.GameObjects
|
||||
set => SetAutoAnimated(value);
|
||||
}
|
||||
|
||||
public RSI.State.Direction EffectiveDirection(Angle worldRotation)
|
||||
public RSIDirection EffectiveDirection(Angle worldRotation)
|
||||
{
|
||||
if (State == default)
|
||||
{
|
||||
@@ -1751,22 +1782,16 @@ namespace Robust.Client.GameObjects
|
||||
return default;
|
||||
}
|
||||
|
||||
public Vector2 LocalToLayer(Vector2 localPos)
|
||||
{
|
||||
// TODO: scale & rotation for layers is currently unimplemented.
|
||||
return localPos;
|
||||
}
|
||||
|
||||
public RSI.State.Direction EffectiveDirection(RSI.State state, Angle worldRotation,
|
||||
public RSIDirection EffectiveDirection(RSI.State state, Angle worldRotation,
|
||||
Direction? overrideDirection)
|
||||
{
|
||||
if (state.Directions == RSI.State.DirectionType.Dir1)
|
||||
{
|
||||
return RSI.State.Direction.South;
|
||||
return RSIDirection.South;
|
||||
}
|
||||
else
|
||||
{
|
||||
RSI.State.Direction dir;
|
||||
RSIDirection dir;
|
||||
if (overrideDirection != null)
|
||||
{
|
||||
dir = overrideDirection.Value.Convert(state.Directions);
|
||||
@@ -1898,11 +1923,6 @@ namespace Robust.Client.GameObjects
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetOffset(Vector2 offset)
|
||||
{
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Vector2i PixelSize
|
||||
{
|
||||
@@ -1923,10 +1943,121 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box2 CalculateBoundingBox()
|
||||
public Box2 CalculateBoundingBox(Angle angle)
|
||||
{
|
||||
// TODO: scale & rotation for layers is currently unimplemented.
|
||||
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
|
||||
// Other than some special cases for simple layers, this will basically just apply the matrix obtained
|
||||
// via GetLayerDrawMatrix() to this layer's bounding box.
|
||||
|
||||
var rsiState = GetActualState();
|
||||
|
||||
var dir = (rsiState == null || rsiState.Directions == RSI.State.DirectionType.Dir1)
|
||||
? RSIDirection.South
|
||||
: angle.ToRsiDirection(rsiState.Directions);
|
||||
|
||||
// special case for simple layers. The vast majority of layers are like this.
|
||||
if (_rotation == Angle.Zero)
|
||||
{
|
||||
var textureSize = PixelSize / EyeManager.PixelsPerMeter;
|
||||
|
||||
// this switch block is basically an explicit version of the `rsiDirectionMatrix` in `GetLayerDrawMatrix()`.
|
||||
var box = dir switch
|
||||
{
|
||||
// No rotation:
|
||||
RSIDirection.South or RSIDirection.North => Box2.CenteredAround(Offset, textureSize),
|
||||
// rotate 90 degrees:
|
||||
RSIDirection.East or RSIDirection.West => Box2.CenteredAround(Offset, (textureSize.Y, textureSize.X)),
|
||||
// rotated 45 degrees (any 45 degree rotated rectangle has a square bounding box with sides of length (x+y)/sqrt(2) )
|
||||
_ => Box2.CenteredAround(Offset, Vector2.One * (textureSize.X + textureSize.Y) / MathF.Sqrt(2))
|
||||
};
|
||||
|
||||
return _scale == Vector2.One ? box : box.Scale(_scale);
|
||||
}
|
||||
|
||||
// Welp we have some non-zero _rotation, so lets just apply the generalized layer transform and get the bounding box from where;
|
||||
GetLayerDrawMatrix(dir, out var layerDrawMatrix);
|
||||
return layerDrawMatrix.TransformBox(Box2.CentredAroundZero(PixelSize / EyeManager.PixelsPerMeter));
|
||||
}
|
||||
|
||||
internal RSI.State? GetActualState()
|
||||
{
|
||||
if (!State.IsValid)
|
||||
return null;
|
||||
|
||||
// Pull texture from RSI state
|
||||
var rsi = RSI ?? _parent.BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(State, out var rsiState))
|
||||
{
|
||||
rsiState = GetFallbackState(_parent.resourceCache);
|
||||
}
|
||||
|
||||
return rsiState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the apparent rotation of an entity on screen (world + eye rotation), get layer's matrix for drawing &
|
||||
/// relevant RSI direction.
|
||||
/// </summary>
|
||||
public void GetLayerDrawMatrix(RSIDirection dir, out Matrix3 layerDrawMatrix)
|
||||
{
|
||||
if (_parent.NoRotation)
|
||||
layerDrawMatrix = LocalMatrix;
|
||||
else
|
||||
{
|
||||
var rsiDirectionMatrix = Matrix3.CreateTransform(Vector2.Zero, -dir.Convert().ToAngle());
|
||||
Matrix3.Multiply(ref rsiDirectionMatrix, ref LocalMatrix, out layerDrawMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, Angle angle, Direction? overrideDirection)
|
||||
{
|
||||
if (!Visible)
|
||||
return;
|
||||
|
||||
var rsiState = GetActualState();
|
||||
|
||||
var dir = (rsiState == null || rsiState.Directions == RSI.State.DirectionType.Dir1)
|
||||
? RSIDirection.South
|
||||
: angle.ToRsiDirection(rsiState.Directions);
|
||||
|
||||
// Set the drawing transform for this layer
|
||||
GetLayerDrawMatrix(dir, out var layerMatrix);
|
||||
Matrix3.Multiply(ref layerMatrix, ref 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.
|
||||
if (overrideDirection != null && rsiState != null)
|
||||
dir = overrideDirection.Value.Convert(rsiState.Directions);
|
||||
dir = dir.OffsetRsiDir(DirOffset);
|
||||
|
||||
// Get the correct directional texture from the state, and draw it!
|
||||
var texture = GetRenderTexture(rsiState, dir);
|
||||
RenderTexture(drawingHandle, texture);
|
||||
}
|
||||
|
||||
private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture)
|
||||
{
|
||||
if (Shader != null)
|
||||
drawingHandle.UseShader(Shader);
|
||||
|
||||
var layerColor = _parent.color * Color;
|
||||
|
||||
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter);
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(position, textureSize);
|
||||
|
||||
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
|
||||
|
||||
if (Shader != null)
|
||||
drawingHandle.UseShader(null);
|
||||
}
|
||||
|
||||
private Texture GetRenderTexture(RSI.State? state, RSIDirection dir)
|
||||
{
|
||||
if (state == null)
|
||||
return Texture ?? _parent.resourceCache.GetFallback<TextureResource>().Texture;
|
||||
|
||||
return state.GetFrame(dir, AnimationFrame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.GameStates;
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
public sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
{
|
||||
private readonly Queue<ClientAppearanceComponent> _queuedUpdates = new();
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
private readonly IPlayerManager _playerManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
|
||||
private readonly ShaderInstance _unshadedShader;
|
||||
private readonly EffectSystem _owner;
|
||||
|
||||
@@ -369,13 +369,10 @@ namespace Robust.Client.GameObjects
|
||||
private Box2 SpriteAabbFunc(in SpriteComponent value)
|
||||
{
|
||||
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
var xform = xforms.GetComponent(value.Owner);
|
||||
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
|
||||
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
|
||||
var tree = GetRenderTree(value.Owner, xforms);
|
||||
|
||||
return tree == null ? bounds.CalcBoundingBox() : xforms.GetComponent(tree.Owner).InvWorldMatrix.TransformBox(bounds);
|
||||
return SpriteAabbFunc(value, worldPos, worldRot, xforms);
|
||||
}
|
||||
|
||||
private Box2 LightAabbFunc(in PointLightComponent value)
|
||||
@@ -392,7 +389,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private Box2 SpriteAabbFunc(SpriteComponent value, Vector2 worldPos, Angle worldRot, EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
|
||||
var bounds = value.CalculateRotatedBoundingBox(worldPos, worldRot);
|
||||
var tree = GetRenderTree(value.Owner, xforms);
|
||||
|
||||
return tree == null ? bounds.CalcBoundingBox() : xforms.GetComponent(tree.Owner).InvWorldMatrix.TransformBox(bounds);
|
||||
|
||||
@@ -24,11 +24,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
public ClydeDebugLayers DebugLayers { get; set; }
|
||||
|
||||
private readonly RefList<(SpriteComponent sprite, Matrix3 worldMatrix, Angle worldRotation, float yWorldPos)>
|
||||
private readonly RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRotation, Box2 spriteScreenBB)>
|
||||
_drawingSpriteList
|
||||
=
|
||||
new();
|
||||
|
||||
// TODO allow this scale to be passed with PostShader as variable
|
||||
/// <summary>
|
||||
/// Some shaders that enlarge the final sprite, like emission or highlight effects, need to use a slightly larger render target.
|
||||
/// </summary>
|
||||
public static float PostShadeScale = 1.25f;
|
||||
|
||||
public void Render()
|
||||
{
|
||||
CheckTransferringScreenshots();
|
||||
@@ -217,9 +223,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
|
||||
|
||||
var screenSize = viewport.Size;
|
||||
eye.GetViewMatrix(out var eyeMatrix, eye.Scale);
|
||||
|
||||
ProcessSpriteEntities(mapId, eyeMatrix, worldBounds, _drawingSpriteList);
|
||||
ProcessSpriteEntities(mapId, viewport, eye, worldBounds, _drawingSpriteList);
|
||||
|
||||
var worldOverlays = new List<Overlay>();
|
||||
|
||||
@@ -276,30 +281,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
break;
|
||||
}
|
||||
|
||||
var matrix = entry.worldMatrix;
|
||||
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
|
||||
|
||||
RenderTexture? entityPostRenderTarget = null;
|
||||
Vector2i roundedPos = default;
|
||||
if (entry.sprite.PostShader != null)
|
||||
{
|
||||
// calculate world bounding box
|
||||
var spriteBB = entry.sprite.CalculateBoundingBox(worldPosition);
|
||||
var spriteLB = spriteBB.BottomLeft;
|
||||
var spriteRT = spriteBB.TopRight;
|
||||
|
||||
// finally we can calculate screen bounding in pixels
|
||||
var screenLB = viewport.WorldToLocal(spriteLB);
|
||||
var screenRT = viewport.WorldToLocal(spriteRT);
|
||||
|
||||
// we need to scale RT a for effects like emission or highlight
|
||||
// scale can be passed with PostShader as variable in future
|
||||
var postShadeScale = 1.25f;
|
||||
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
|
||||
|
||||
// Rotate the vector by the eye angle, otherwise the bounding box will be incorrect
|
||||
screenSpriteSize = (Vector2i) eye.Rotation.RotateVec(screenSpriteSize).Rounded();
|
||||
screenSpriteSize.Y = -screenSpriteSize.Y;
|
||||
// get the size of the sprite on screen, scaled slightly to allow for shaders that increase the final sprite size.
|
||||
var screenSpriteSize = (Vector2i) (entry.spriteScreenBB.Size * PostShadeScale).Rounded();
|
||||
|
||||
// I'm not 100% sure why it works, but without it post-shader
|
||||
// can be lower or upper by 1px than original sprite depending on sprite rotation or scale
|
||||
@@ -322,16 +309,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Calculate viewport so that the entity thinks it's drawing to the same position,
|
||||
// which is necessary for light application,
|
||||
// but it's ACTUALLY drawing into the center of the render target.
|
||||
var spritePos = spriteBB.Center;
|
||||
var screenPos = viewport.WorldToLocal(spritePos);
|
||||
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
|
||||
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
|
||||
roundedPos = (Vector2i) entry.spriteScreenBB.Center;
|
||||
var flippedPos = new Vector2i(roundedPos.X, screenSize.Y - roundedPos.Y);
|
||||
flippedPos -= entityPostRenderTarget.Size / 2;
|
||||
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
|
||||
}
|
||||
}
|
||||
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in worldPosition);
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in entry.worldPos);
|
||||
|
||||
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
|
||||
{
|
||||
@@ -369,17 +354,26 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void ProcessSpriteEntities(MapId map, Matrix3 eyeMatrix, Box2Rotated worldBounds,
|
||||
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
|
||||
private void ProcessSpriteEntities(MapId map, Viewport view, IEye eye, Box2Rotated worldBounds,
|
||||
RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> list)
|
||||
{
|
||||
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
// Construct a matrix equivalent for Viewport.WorldToLocal()
|
||||
eye.GetViewMatrix(out var viewMatrix, view.RenderScale);
|
||||
var uiProjmatrix = Matrix3.Identity;
|
||||
uiProjmatrix.R0C0 = EyeManager.PixelsPerMeter;
|
||||
uiProjmatrix.R1C1 = -EyeManager.PixelsPerMeter;
|
||||
uiProjmatrix.R0C2 = view.Size.X / 2f;
|
||||
uiProjmatrix.R1C2 = view.Size.Y / 2f;
|
||||
var worldToLocal = viewMatrix * uiProjmatrix;
|
||||
|
||||
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
|
||||
{
|
||||
var bounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
|
||||
|
||||
comp.SpriteTree.QueryAabb(ref list, (
|
||||
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
|
||||
ref RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> state,
|
||||
in SpriteComponent value) =>
|
||||
{
|
||||
var entity = value.Owner;
|
||||
@@ -387,12 +381,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
ref var entry = ref state.AllocAdd();
|
||||
entry.sprite = value;
|
||||
Vector2 worldPos;
|
||||
(worldPos, entry.worldRot, entry.matrix) = transform.GetWorldPositionRotationMatrix();
|
||||
var eyePos = eyeMatrix.Transform(worldPos);
|
||||
// Didn't use the bounds from the query as that has to be re-calculated (and is probably more expensive than this).
|
||||
var bounds = value.CalculateBoundingBox(eyePos);
|
||||
entry.yWorldPos = eyePos.Y - bounds.Extents.Y;
|
||||
(entry.worldPos, entry.worldRot) = transform.GetWorldPositionRotation();
|
||||
|
||||
var spriteWorldBB = value.CalculateRotatedBoundingBox(entry.worldPos, entry.worldRot, eye);
|
||||
entry.spriteScreenBB = worldToLocal.TransformBox(spriteWorldBB);
|
||||
return true;
|
||||
|
||||
}, bounds, true);
|
||||
|
||||
@@ -994,9 +994,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private sealed class SpriteDrawingOrderComparer : IComparer<int>
|
||||
{
|
||||
private readonly RefList<(SpriteComponent, Matrix3, Angle, float)> _drawList;
|
||||
private readonly RefList<(SpriteComponent, Vector2, Angle, Box2)> _drawList;
|
||||
|
||||
public SpriteDrawingOrderComparer(RefList<(SpriteComponent, Matrix3, Angle, float)> drawList)
|
||||
public SpriteDrawingOrderComparer(RefList<(SpriteComponent, Vector2, Angle, Box2)> drawList)
|
||||
{
|
||||
_drawList = drawList;
|
||||
}
|
||||
@@ -1019,7 +1019,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = _drawList[y].Item4.CompareTo(_drawList[x].Item4);
|
||||
// compare the top of the sprite's BB for y-sorting. Because screen coordinates are flipped, the "top" of the BB is actually the "bottom".
|
||||
cmp = _drawList[x].Item4.Top.CompareTo(_drawList[y].Item4.Top);
|
||||
|
||||
if (cmp != 0)
|
||||
{
|
||||
|
||||
@@ -29,14 +29,13 @@ namespace Robust.Server.Console.Commands
|
||||
var mapId = new MapId(int.Parse(args[0]));
|
||||
|
||||
var mapMgr = IoCManager.Resolve<IMapManager>();
|
||||
var pauseMgr = IoCManager.Resolve<IPauseManager>();
|
||||
|
||||
if (!mapMgr.MapExists(mapId))
|
||||
{
|
||||
mapMgr.CreateMap(mapId);
|
||||
if (args.Length >= 2 && args[1] == "false")
|
||||
{
|
||||
pauseMgr.AddUninitializedMap(mapId);
|
||||
mapMgr.AddUninitializedMap(mapId);
|
||||
}
|
||||
|
||||
shell.WriteLine($"Map with ID {mapId} created.");
|
||||
@@ -318,7 +317,6 @@ namespace Robust.Server.Console.Commands
|
||||
}
|
||||
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var pauseManager = IoCManager.Resolve<IPauseManager>();
|
||||
|
||||
var arg = args[0];
|
||||
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
|
||||
@@ -329,13 +327,13 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (pauseManager.IsMapInitialized(mapId))
|
||||
if (mapManager.IsMapInitialized(mapId))
|
||||
{
|
||||
shell.WriteError("Map is already initialized!");
|
||||
return;
|
||||
}
|
||||
|
||||
pauseManager.DoMapInitialize(mapId);
|
||||
mapManager.DoMapInitialize(mapId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,15 +346,14 @@ namespace Robust.Server.Console.Commands
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var pauseManager = IoCManager.Resolve<IPauseManager>();
|
||||
|
||||
var msg = new StringBuilder();
|
||||
|
||||
foreach (var mapId in mapManager.GetAllMapIds().OrderBy(id => id.Value))
|
||||
{
|
||||
msg.AppendFormat("{0}: init: {1}, paused: {2}, ent: {3}, grids: {4}\n",
|
||||
mapId, pauseManager.IsMapInitialized(mapId),
|
||||
pauseManager.IsMapPaused(mapId),
|
||||
mapId, mapManager.IsMapInitialized(mapId),
|
||||
mapManager.IsMapPaused(mapId),
|
||||
string.Join(",", mapManager.GetAllMapGrids(mapId).Select(grid => grid.Index)),
|
||||
mapManager.GetMapEntityId(mapId));
|
||||
}
|
||||
|
||||
@@ -109,9 +109,8 @@ namespace Robust.Server.Console.Commands
|
||||
private void SetupPlayer(MapId mapId, IConsoleShell shell, IPlayerSession? player, IMapManager mapManager)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
var pauseManager = IoCManager.Resolve<IPauseManager>();
|
||||
pauseManager.SetMapPaused(mapId, false);
|
||||
var mapUid = IoCManager.Resolve<IMapManager>().GetMapEntityIdOrThrow(mapId);
|
||||
mapManager.SetMapPaused(mapId, false);
|
||||
var mapUid = mapManager.GetMapEntityIdOrThrow(mapId);
|
||||
IoCManager.Resolve<IEntityManager>().GetComponent<SharedPhysicsMapComponent>(mapUid).Gravity = new Vector2(0, -9.8f);
|
||||
|
||||
return;
|
||||
|
||||
@@ -3,7 +3,7 @@ using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
internal sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
public sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -5,30 +5,31 @@ namespace Robust.Server.GameStates;
|
||||
|
||||
public struct ChunkIndicesEnumerator
|
||||
{
|
||||
private Vector2i _topLeft;
|
||||
private Vector2i _bottomRight;
|
||||
private Vector2i _bottomLeft;
|
||||
private Vector2i _topRight;
|
||||
|
||||
private int _x;
|
||||
private int _y;
|
||||
|
||||
public ChunkIndicesEnumerator(Box2 viewBox, float chunkSize)
|
||||
public ChunkIndicesEnumerator(Vector2 viewPos, float range, float chunkSize)
|
||||
{
|
||||
_topLeft = (viewBox.TopLeft / chunkSize).Floored();
|
||||
_bottomRight = (viewBox.BottomRight / chunkSize).Floored();
|
||||
_bottomLeft = ((viewPos - range) / chunkSize).Floored();
|
||||
// Also floor this as we get the whole chunk anyway.
|
||||
_topRight = ((viewPos + range) / chunkSize).Floored();
|
||||
|
||||
_x = _topLeft.X;
|
||||
_y = _bottomRight.Y;
|
||||
_x = _bottomLeft.X;
|
||||
_y = _bottomLeft.Y;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out Vector2i? chunkIndices)
|
||||
{
|
||||
if (_y > _topLeft.Y)
|
||||
if (_y > _topRight.Y)
|
||||
{
|
||||
_x++;
|
||||
_y = _bottomRight.Y;
|
||||
_y = _bottomLeft.Y;
|
||||
}
|
||||
|
||||
if (_x > _bottomRight.X)
|
||||
if (_x > _topRight.X)
|
||||
{
|
||||
chunkIndices = null;
|
||||
return false;
|
||||
|
||||
@@ -81,14 +81,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
private readonly List<(GameTick tick, TIndex index)> _deletionHistory = new();
|
||||
|
||||
/// <summary>
|
||||
/// An index containing the <see cref="IndexLocation"/>s of all <see cref="TIndex"/>.
|
||||
/// An index containing the <see cref="IIndexLocation"/>s of all <see cref="TIndex"/>.
|
||||
/// </summary>
|
||||
private readonly Dictionary<TIndex, IndexLocation> _indexLocations = new();
|
||||
private readonly Dictionary<TIndex, IIndexLocation> _indexLocations = new();
|
||||
|
||||
/// <summary>
|
||||
/// Buffer of all locationchanges since the last process call
|
||||
/// </summary>
|
||||
private readonly Dictionary<TIndex, IndexLocation> _locationChangeBuffer = new();
|
||||
private readonly Dictionary<TIndex, IIndexLocation> _locationChangeBuffer = new();
|
||||
/// <summary>
|
||||
/// Buffer of all indexremovals since the last process call
|
||||
/// </summary>
|
||||
@@ -103,7 +103,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
{
|
||||
var changedIndices = new HashSet<TIndex>(_locationChangeBuffer.Keys);
|
||||
|
||||
var changedChunkLocations = new HashSet<IndexLocation>();
|
||||
var changedChunkLocations = new HashSet<IIndexLocation>();
|
||||
foreach (var (index, tick) in _removalBuffer)
|
||||
{
|
||||
//changes dont need to be computed if we are removing the index anyways
|
||||
@@ -154,7 +154,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
|
||||
public HashSet<TIndex>.Enumerator GetElementsForSession(ICommonSession session) => _localOverrides[session].GetEnumerator();
|
||||
|
||||
private void AddIndexInternal(TIndex index, IndexLocation location)
|
||||
private void AddIndexInternal(TIndex index, IIndexLocation location)
|
||||
{
|
||||
switch (location)
|
||||
{
|
||||
@@ -186,7 +186,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
_indexLocations.Add(index, location);
|
||||
}
|
||||
|
||||
private IndexLocation? RemoveIndexInternal(TIndex index)
|
||||
private IIndexLocation? RemoveIndexInternal(TIndex index)
|
||||
{
|
||||
// the index might be gone due to disconnects/grid-/map-deletions
|
||||
if (!_indexLocations.TryGetValue(index, out var location))
|
||||
@@ -392,7 +392,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
RegisterUpdate(index, new MapChunkLocation(mapId, chunkIndices));
|
||||
}
|
||||
|
||||
private void RegisterUpdate(TIndex index, IndexLocation location)
|
||||
private void RegisterUpdate(TIndex index, IIndexLocation location)
|
||||
{
|
||||
if(_indexLocations.TryGetValue(index, out var oldLocation) && oldLocation == location) return;
|
||||
|
||||
@@ -400,14 +400,78 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IndexLocations
|
||||
|
||||
private abstract record IndexLocation;
|
||||
private record MapChunkLocation(MapId MapId, Vector2i ChunkIndices) : IndexLocation;
|
||||
private record GridChunkLocation(GridId GridId, Vector2i ChunkIndices) : IndexLocation;
|
||||
private record GlobalOverride : IndexLocation;
|
||||
private record LocalOverride(ICommonSession Session) : IndexLocation;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region IndexLocations
|
||||
|
||||
public interface IIndexLocation {};
|
||||
|
||||
public interface IChunkIndexLocation{ };
|
||||
|
||||
public struct MapChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatable<MapChunkLocation>
|
||||
{
|
||||
public MapChunkLocation(MapId mapId, Vector2i chunkIndices)
|
||||
{
|
||||
MapId = mapId;
|
||||
ChunkIndices = chunkIndices;
|
||||
}
|
||||
|
||||
public MapId MapId { get; init; }
|
||||
public Vector2i ChunkIndices { get; init; }
|
||||
|
||||
public bool Equals(MapChunkLocation other)
|
||||
{
|
||||
return MapId.Equals(other.MapId) && ChunkIndices.Equals(other.ChunkIndices);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is MapChunkLocation other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(MapId, ChunkIndices);
|
||||
}
|
||||
}
|
||||
|
||||
public struct GridChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatable<GridChunkLocation>
|
||||
{
|
||||
public GridChunkLocation(GridId gridId, Vector2i chunkIndices)
|
||||
{
|
||||
GridId = gridId;
|
||||
ChunkIndices = chunkIndices;
|
||||
}
|
||||
|
||||
public GridId GridId { get; init; }
|
||||
public Vector2i ChunkIndices { get; init; }
|
||||
|
||||
public bool Equals(GridChunkLocation other)
|
||||
{
|
||||
return GridId.Equals(other.GridId) && ChunkIndices.Equals(other.ChunkIndices);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is GridChunkLocation other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(GridId, ChunkIndices);
|
||||
}
|
||||
}
|
||||
|
||||
public struct GlobalOverride : IIndexLocation { }
|
||||
|
||||
public struct LocalOverride : IIndexLocation
|
||||
{
|
||||
public LocalOverride(ICommonSession session)
|
||||
{
|
||||
Session = session;
|
||||
}
|
||||
|
||||
public ICommonSession Session { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -35,7 +35,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Is view culling enabled, or will we send the whole map?
|
||||
/// </summary>
|
||||
private bool _cullingEnabled;
|
||||
public bool CullingEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// How many new entities we can send per tick (dont wanna nuke the clients mailbox).
|
||||
@@ -56,17 +56,28 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
/// All <see cref="Robust.Shared.GameObjects.EntityUid"/>s a <see cref="ICommonSession"/> saw last iteration.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ICommonSession, Dictionary<EntityUid, PVSEntityVisiblity>> _playerVisibleSets = new();
|
||||
/// <summary>
|
||||
/// All <see cref="Robust.Shared.GameObjects.EntityUid"/>s a <see cref="ICommonSession"/> saw along its entire connection.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ICommonSession, HashSet<EntityUid>> _playerSeenSets = new();
|
||||
|
||||
private PVSCollection<EntityUid> _entityPvsCollection = default!;
|
||||
public PVSCollection<EntityUid> EntityPVSCollection => _entityPvsCollection;
|
||||
private readonly List<IPVSCollection> _pvsCollections = new();
|
||||
|
||||
private readonly ObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>> _visSetPool =
|
||||
new DefaultObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>>(
|
||||
new DefaultPooledObjectPolicy<Dictionary<EntityUid, PVSEntityVisiblity>>(), MaxVisPoolSize);
|
||||
private readonly ObjectPool<HashSet<EntityUid>> _viewerEntsPool
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new DefaultPooledObjectPolicy<HashSet<EntityUid>>(), MaxVisPoolSize);
|
||||
private readonly ObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>> _visSetPool
|
||||
= new DefaultObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>>(
|
||||
new DictPolicy<EntityUid, PVSEntityVisiblity>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<HashSet<EntityUid>> _uidSetPool
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<Dictionary<EntityUid, MetaDataComponent>> _chunkCachePool =
|
||||
new DefaultObjectPool<Dictionary<EntityUid, MetaDataComponent>>(
|
||||
new DictPolicy<EntityUid, MetaDataComponent>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<HashSet<int>> _playerChunkPool =
|
||||
new DefaultObjectPool<HashSet<int>>(new SetPolicy<int>(), MaxVisPoolSize);
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -121,7 +132,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
private void SetPvs(bool value)
|
||||
{
|
||||
_cullingEnabled = value;
|
||||
CullingEnabled = value;
|
||||
}
|
||||
|
||||
private void OnNewEntityBudgetChanged(int obj)
|
||||
@@ -260,115 +271,213 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
#endregion
|
||||
|
||||
public (List<EntityState>? updates, List<EntityUid>? deletions) CalculateEntityStates(ICommonSession session,
|
||||
GameTick fromTick, GameTick toTick)
|
||||
public (List<(uint, IChunkIndexLocation)> , HashSet<int>[], EntityUid[][] viewers) GetChunks(IPlayerSession[] sessions)
|
||||
{
|
||||
var chunkList = new List<(uint, IChunkIndexLocation)>();
|
||||
var playerChunks = new HashSet<int>[sessions.Length];
|
||||
var eyeQuery = EntityManager.GetEntityQuery<EyeComponent>();
|
||||
var transformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var viewerEntities = new EntityUid[sessions.Length][];
|
||||
|
||||
// Keep track of the index of each chunk we use for a faster index lookup.
|
||||
var mapIndices = new Dictionary<uint, Dictionary<MapChunkLocation, int>>(4);
|
||||
var gridIndices = new Dictionary<uint, Dictionary<GridChunkLocation, int>>(4);
|
||||
|
||||
for (int i = 0; i < sessions.Length; i++)
|
||||
{
|
||||
var session = sessions[i];
|
||||
playerChunks[i] = _playerChunkPool.Get();
|
||||
|
||||
var viewers = GetSessionViewers(session);
|
||||
viewerEntities[i] = new EntityUid[viewers.Count];
|
||||
viewers.CopyTo(viewerEntities[i]);
|
||||
|
||||
foreach (var eyeEuid in viewers)
|
||||
{
|
||||
var (viewPos, range, mapId) = CalcViewBounds(in eyeEuid, transformQuery);
|
||||
|
||||
uint visMask = EyeComponent.DefaultVisibilityMask;
|
||||
if (eyeQuery.TryGetComponent(eyeEuid, out var eyeComp))
|
||||
visMask = eyeComp.VisibilityMask;
|
||||
|
||||
// Get the nyoom dictionary for index lookups.
|
||||
if (!mapIndices.TryGetValue(visMask, out var mapDict))
|
||||
{
|
||||
mapDict = new Dictionary<MapChunkLocation, int>(32);
|
||||
mapIndices[visMask] = mapDict;
|
||||
}
|
||||
|
||||
var mapChunkEnumerator = new ChunkIndicesEnumerator(viewPos, range, ChunkSize);
|
||||
|
||||
while (mapChunkEnumerator.MoveNext(out var chunkIndices))
|
||||
{
|
||||
var chunkLocation = new MapChunkLocation(mapId, chunkIndices.Value);
|
||||
var entry = (visMask, chunkLocation);
|
||||
|
||||
if (mapDict.TryGetValue(chunkLocation, out var indexOf))
|
||||
{
|
||||
playerChunks[i].Add(indexOf);
|
||||
}
|
||||
else
|
||||
{
|
||||
playerChunks[i].Add(chunkList.Count);
|
||||
mapDict.Add(chunkLocation, chunkList.Count);
|
||||
chunkList.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the nyoom dictionary for index lookups.
|
||||
if (!gridIndices.TryGetValue(visMask, out var gridDict))
|
||||
{
|
||||
gridDict = new Dictionary<GridChunkLocation, int>(32);
|
||||
gridIndices[visMask] = gridDict;
|
||||
}
|
||||
|
||||
_mapManager.FindGridsIntersectingEnumerator(mapId, new Box2(viewPos - range, viewPos + range), out var gridEnumerator, true);
|
||||
while (gridEnumerator.MoveNext(out var mapGrid))
|
||||
{
|
||||
var localPos = transformQuery.GetComponent(mapGrid.GridEntityId).InvWorldMatrix.Transform(viewPos);
|
||||
|
||||
var gridChunkEnumerator =
|
||||
new ChunkIndicesEnumerator(localPos, range, ChunkSize);
|
||||
|
||||
while (gridChunkEnumerator.MoveNext(out var gridChunkIndices))
|
||||
{
|
||||
var chunkLocation = new GridChunkLocation(mapGrid.Index, gridChunkIndices.Value);
|
||||
var entry = (visMask, chunkLocation);
|
||||
|
||||
if (gridDict.TryGetValue(chunkLocation, out var indexOf))
|
||||
{
|
||||
playerChunks[i].Add(indexOf);
|
||||
}
|
||||
else
|
||||
{
|
||||
playerChunks[i].Add(chunkList.Count);
|
||||
gridDict.Add(chunkLocation, chunkList.Count);
|
||||
chunkList.Add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_uidSetPool.Return(viewers);
|
||||
}
|
||||
|
||||
return (chunkList, playerChunks, viewerEntities);
|
||||
}
|
||||
|
||||
public Dictionary<EntityUid, MetaDataComponent>? CalculateChunk(IChunkIndexLocation chunkLocation, uint visMask, EntityQuery<TransformComponent> transform, EntityQuery<MetaDataComponent> metadata)
|
||||
{
|
||||
var chunk = chunkLocation switch
|
||||
{
|
||||
GridChunkLocation gridChunkLocation => _entityPvsCollection.TryGetChunk(gridChunkLocation.GridId,
|
||||
gridChunkLocation.ChunkIndices, out var gridChunk)
|
||||
? gridChunk
|
||||
: null,
|
||||
MapChunkLocation mapChunkLocation => _entityPvsCollection.TryGetChunk(mapChunkLocation.MapId,
|
||||
mapChunkLocation.ChunkIndices, out var mapChunk)
|
||||
? mapChunk
|
||||
: null
|
||||
};
|
||||
if (chunk == null) return null;
|
||||
var chunkSet = _chunkCachePool.Get();
|
||||
foreach (var uid in chunk)
|
||||
{
|
||||
AddToChunkSetRecursively(in uid, visMask, chunkSet, transform, metadata);
|
||||
}
|
||||
|
||||
return chunkSet;
|
||||
}
|
||||
|
||||
public void ReturnToPool(Dictionary<EntityUid, MetaDataComponent>?[] chunkCache, HashSet<int>[] playerChunks)
|
||||
{
|
||||
foreach (var chunk in chunkCache)
|
||||
{
|
||||
if(chunk == null) continue;
|
||||
_chunkCachePool.Return(chunk);
|
||||
}
|
||||
|
||||
foreach (var playerChunk in playerChunks)
|
||||
{
|
||||
_playerChunkPool.Return(playerChunk);
|
||||
}
|
||||
}
|
||||
|
||||
private bool AddToChunkSetRecursively(in EntityUid uid, uint visMask, Dictionary<EntityUid, MetaDataComponent> set, EntityQuery<TransformComponent> transform,
|
||||
EntityQuery<MetaDataComponent> metadata)
|
||||
{
|
||||
//are we valid?
|
||||
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
|
||||
if (!uid.IsValid()) return false;
|
||||
|
||||
if (set.ContainsKey(uid)) return false;
|
||||
|
||||
var mComp = metadata.GetComponent(uid);
|
||||
|
||||
// TODO: Don't need to know about parents so no longer need to use bool for this method.
|
||||
// If the eye is missing ANY layer this entity or any of its parents belongs to, it is considered invisible.
|
||||
if ((visMask & mComp.VisibilityMask) != mComp.VisibilityMask)
|
||||
return false;
|
||||
|
||||
var parent = transform.GetComponent(uid).ParentUid;
|
||||
|
||||
if (parent.IsValid() && //is it not a worldentity?
|
||||
!set.ContainsKey(parent) && //was the parent not yet added to toSend?
|
||||
!AddToChunkSetRecursively(in parent, visMask, set, transform, metadata)) //did we just fail to add the parent?
|
||||
return false; //we failed? suppose we dont get added either
|
||||
|
||||
//todo paul i want it to crash here if it gets added double bc that shouldnt happen and will add alot of unneeded cycles, make this a simpl assignment at some point maybe idk
|
||||
set.Add(uid, mComp);
|
||||
return true;
|
||||
}
|
||||
|
||||
public (List<EntityState>? updates, List<EntityUid>? deletions) CalculateEntityStates(IPlayerSession session,
|
||||
GameTick fromTick, GameTick toTick, Dictionary<EntityUid, MetaDataComponent>?[] chunkCache, HashSet<int> chunkIndices, EntityQuery<MetaDataComponent> mQuery, EntityUid[] viewerEntities)
|
||||
{
|
||||
DebugTools.Assert(session.Status == SessionStatus.InGame);
|
||||
var newEntitiesSent = 0;
|
||||
var entitiesSent = 0;
|
||||
|
||||
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
|
||||
if (!_cullingEnabled)
|
||||
{
|
||||
var allStates = GetAllEntityStates(session, fromTick, toTick);
|
||||
return (allStates, deletions);
|
||||
}
|
||||
|
||||
var playerVisibleSet = _playerVisibleSets[session];
|
||||
var visibleEnts = _visSetPool.Get();
|
||||
var seenSet = _playerSeenSets[session];
|
||||
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
|
||||
|
||||
visibleEnts.Clear();
|
||||
|
||||
var eyeQuery = EntityManager.GetEntityQuery<EyeComponent>();
|
||||
var transformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
var globalOverridesEnumerator = _entityPvsCollection.GlobalOverridesEnumerator;
|
||||
while(globalOverridesEnumerator.MoveNext())
|
||||
var globalEnumerator = _entityPvsCollection.GlobalOverridesEnumerator;
|
||||
while (globalEnumerator.MoveNext())
|
||||
{
|
||||
var uid = globalOverridesEnumerator.Current;
|
||||
//todo paul reenable budgetcheck here once you fix mapmanager
|
||||
TryAddToVisibleEnts(
|
||||
in uid,
|
||||
seenSet,
|
||||
playerVisibleSet,
|
||||
visibleEnts,
|
||||
fromTick,
|
||||
ref newEntitiesSent,
|
||||
ref entitiesSent,
|
||||
metadataQuery,
|
||||
transformQuery,
|
||||
dontSkip: true);
|
||||
var uid = globalEnumerator.Current;
|
||||
TryAddToVisibleEnts(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, mQuery, dontSkip: true);
|
||||
}
|
||||
globalOverridesEnumerator.Dispose();
|
||||
globalEnumerator.Dispose();
|
||||
|
||||
var localOverridesEnumerator = _entityPvsCollection.GetElementsForSession(session);
|
||||
while (localOverridesEnumerator.MoveNext())
|
||||
var localEnumerator = _entityPvsCollection.GetElementsForSession(session);
|
||||
while (localEnumerator.MoveNext())
|
||||
{
|
||||
var uid = localOverridesEnumerator.Current;
|
||||
//todo paul reenable budgetcheck here once you fix mapmanager
|
||||
TryAddToVisibleEnts(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, dontSkip: true);
|
||||
var uid = localEnumerator.Current;
|
||||
TryAddToVisibleEnts(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, mQuery, dontSkip: true);
|
||||
}
|
||||
localOverridesEnumerator.Dispose();
|
||||
localEnumerator.Dispose();
|
||||
|
||||
var expandEvent = new ExpandPvsEvent((IPlayerSession) session, new List<EntityUid>());
|
||||
RaiseLocalEvent(ref expandEvent);
|
||||
foreach (var entityUid in expandEvent.Entities)
|
||||
foreach (var viewerEntity in viewerEntities)
|
||||
{
|
||||
TryAddToVisibleEnts(in entityUid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery);
|
||||
TryAddToVisibleEnts(in viewerEntity, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, mQuery, dontSkip: true);
|
||||
}
|
||||
|
||||
var viewers = GetSessionViewers(session);
|
||||
|
||||
foreach (var eyeEuid in viewers)
|
||||
foreach (var i in chunkIndices)
|
||||
{
|
||||
var (viewBox, mapId) = CalcViewBounds(in eyeEuid, transformQuery);
|
||||
|
||||
uint visMask = EyeComponent.DefaultVisibilityMask;
|
||||
if (eyeQuery.TryGetComponent(eyeEuid, out var eyeComp))
|
||||
visMask = eyeComp.VisibilityMask;
|
||||
|
||||
//todo at some point just register the viewerentities as localoverrides
|
||||
TryAddToVisibleEnts(in eyeEuid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, visMask, dontSkip: true);
|
||||
|
||||
var mapChunkEnumerator = new ChunkIndicesEnumerator(viewBox, ChunkSize);
|
||||
|
||||
while (mapChunkEnumerator.MoveNext(out var chunkIndices))
|
||||
var chunk = chunkCache[i];
|
||||
if(chunk == null) continue;
|
||||
foreach (var (uid, metadata) in chunk)
|
||||
{
|
||||
if(_entityPvsCollection.TryGetChunk(mapId, chunkIndices.Value, out var chunk))
|
||||
{
|
||||
foreach (var index in chunk)
|
||||
{
|
||||
TryAddToVisibleEnts(in index, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, visMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
TryAddToVisibleEnts(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, mQuery, metadata);
|
||||
|
||||
_mapManager.FindGridsIntersectingEnumerator(mapId, viewBox, out var gridEnumerator, true);
|
||||
while (gridEnumerator.MoveNext(out var mapGrid))
|
||||
{
|
||||
var gridXform = transformQuery.GetComponent(mapGrid.GridEntityId);
|
||||
|
||||
var gridChunkEnumerator =
|
||||
new ChunkIndicesEnumerator(gridXform.InvWorldMatrix.TransformBox(viewBox), ChunkSize);
|
||||
|
||||
while (gridChunkEnumerator.MoveNext(out var gridChunkIndices))
|
||||
{
|
||||
if (_entityPvsCollection.TryGetChunk(mapGrid.Index, gridChunkIndices.Value, out var chunk))
|
||||
{
|
||||
foreach (var index in chunk)
|
||||
{
|
||||
TryAddToVisibleEnts(in index, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, visMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewers.Clear();
|
||||
_viewerEntsPool.Return(viewers);
|
||||
|
||||
var entityStates = new List<EntityState>();
|
||||
|
||||
foreach (var (entityUid, visiblity) in visibleEnts)
|
||||
@@ -411,7 +520,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private bool TryAddToVisibleEnts(
|
||||
private void TryAddToVisibleEnts(
|
||||
in EntityUid uid,
|
||||
HashSet<EntityUid> seenSet,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
|
||||
@@ -420,40 +529,16 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
ref int newEntitiesSent,
|
||||
ref int totalEnteredEntities,
|
||||
EntityQuery<MetaDataComponent> metadataQuery,
|
||||
EntityQuery<TransformComponent> transformQuery,
|
||||
uint? visMask = null,
|
||||
bool dontSkip = false,
|
||||
bool trustParent = false)
|
||||
MetaDataComponent? metaDataComponent = null,
|
||||
bool dontSkip = false)
|
||||
{
|
||||
//are we valid yet?
|
||||
//are we valid?
|
||||
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
|
||||
if (!uid.IsValid()) return false;
|
||||
if (!uid.IsValid()) return;
|
||||
|
||||
//did we already get added?
|
||||
if (toSend.ContainsKey(uid)) return true;
|
||||
|
||||
var metadata = metadataQuery.GetComponent(uid);
|
||||
|
||||
// if we are invisible, we are not going into the visSet, so don't worry about parents, and children are not going in
|
||||
if (visMask != null)
|
||||
{
|
||||
// TODO: Don't need to know about parents so no longer need to use bool for this method.
|
||||
|
||||
// If the eye is missing ANY layer this entity or any of its parents belongs to, it is considered invisible.
|
||||
if ((visMask & metadata.VisibilityMask) != metadata.VisibilityMask)
|
||||
return false;
|
||||
}
|
||||
|
||||
var parent = transformQuery.GetComponent(uid).ParentUid;
|
||||
|
||||
if (!trustParent && //do we have it on good authority the parent exists?
|
||||
parent.IsValid() && //is it not a worldentity?
|
||||
!toSend.ContainsKey(parent) && //was the parent not yet added to toSend?
|
||||
!TryAddToVisibleEnts(in parent, seenSet, previousVisibleEnts, toSend, fromTick, ref newEntitiesSent, ref totalEnteredEntities, metadataQuery, transformQuery, visMask)) //did we just fail to add the parent?
|
||||
return false; //we failed? suppose we dont get added either
|
||||
|
||||
//did we already get added through the parent call?
|
||||
if (toSend.ContainsKey(uid)) return true;
|
||||
//todo paul can this happen?
|
||||
if (toSend.ContainsKey(uid)) return;
|
||||
|
||||
//are we new?
|
||||
var @new = !seenSet.Contains(uid);
|
||||
@@ -462,7 +547,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
if (entered)
|
||||
{
|
||||
if (!dontSkip && totalEnteredEntities >= _entityBudget)
|
||||
return false;
|
||||
return;
|
||||
|
||||
totalEnteredEntities++;
|
||||
}
|
||||
@@ -471,7 +556,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
{
|
||||
//we just entered pvs, do we still have enough budget to send us?
|
||||
if(!dontSkip && newEntitiesSent >= _newEntityBudget)
|
||||
return false;
|
||||
return;
|
||||
|
||||
newEntitiesSent++;
|
||||
seenSet.Add(uid);
|
||||
@@ -480,29 +565,33 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
if (entered)
|
||||
{
|
||||
toSend.Add(uid, PVSEntityVisiblity.Entered);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (metadata.EntityLastModifiedTick < fromTick)
|
||||
metaDataComponent ??= metadataQuery.GetComponent(uid);
|
||||
|
||||
if (metaDataComponent.EntityLastModifiedTick < fromTick)
|
||||
{
|
||||
//entity has been sent before and hasnt been updated since
|
||||
toSend.Add(uid, PVSEntityVisiblity.StayedUnchanged);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
//add us
|
||||
toSend.Add(uid, PVSEntityVisiblity.StayedChanged);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entity states that have been modified after and including the provided tick.
|
||||
/// </summary>
|
||||
private List<EntityState>? GetAllEntityStates(ICommonSession player, GameTick fromTick, GameTick toTick)
|
||||
public (List<EntityState>? updates, List<EntityUid>? deletions) GetAllEntityStates(ICommonSession player, GameTick fromTick, GameTick toTick)
|
||||
{
|
||||
List<EntityState> stateEntities;
|
||||
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
|
||||
// no point sending an empty collection
|
||||
if (deletions.Count == 0) deletions = default;
|
||||
|
||||
stateEntities = new List<EntityState>();
|
||||
var stateEntities = new List<EntityState>();
|
||||
var seenEnts = new HashSet<EntityUid>();
|
||||
var slowPath = false;
|
||||
var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
@@ -544,7 +633,9 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
if (!slowPath)
|
||||
{
|
||||
return stateEntities.Count == 0 ? default : stateEntities;
|
||||
if (stateEntities.Count == 0) stateEntities = default;
|
||||
|
||||
return (stateEntities, deletions);
|
||||
}
|
||||
|
||||
stateEntities = new List<EntityState>(EntityManager.EntityCount);
|
||||
@@ -559,7 +650,9 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
|
||||
// no point sending an empty collection
|
||||
return stateEntities.Count == 0 ? default : stateEntities;
|
||||
if (stateEntities.Count == 0) stateEntities = default;
|
||||
|
||||
return (stateEntities, deletions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -618,7 +711,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
private HashSet<EntityUid> GetSessionViewers(ICommonSession session)
|
||||
{
|
||||
var viewers = _viewerEntsPool.Get();
|
||||
var viewers = _uidSetPool.Get();
|
||||
if (session.Status != SessionStatus.InGame)
|
||||
return viewers;
|
||||
|
||||
@@ -638,14 +731,38 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
|
||||
// Read Safe
|
||||
private (Box2 view, MapId mapId) CalcViewBounds(in EntityUid euid, EntityQuery<TransformComponent> transformQuery)
|
||||
private (Vector2 worldPos, float range, MapId mapId) CalcViewBounds(in EntityUid euid, EntityQuery<TransformComponent> transformQuery)
|
||||
{
|
||||
var xform = transformQuery.GetComponent(euid);
|
||||
return (xform.WorldPosition, _viewSize / 2f, xform.MapID);
|
||||
}
|
||||
|
||||
var view = Box2.UnitCentered.Scale(_viewSize).Translated(xform.WorldPosition);
|
||||
var map = xform.MapID;
|
||||
public sealed class SetPolicy<T> : PooledObjectPolicy<HashSet<T>>
|
||||
{
|
||||
public override HashSet<T> Create()
|
||||
{
|
||||
return new HashSet<T>();
|
||||
}
|
||||
|
||||
return (view, map);
|
||||
public override bool Return(HashSet<T> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DictPolicy<T1, T2> : PooledObjectPolicy<Dictionary<T1, T2>> where T1 : notnull
|
||||
{
|
||||
public override Dictionary<T1, T2> Create()
|
||||
{
|
||||
return new Dictionary<T1, T2>();
|
||||
}
|
||||
|
||||
public override bool Return(Dictionary<T1, T2> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -125,6 +128,35 @@ namespace Robust.Server.GameStates
|
||||
// people not in the game don't get states
|
||||
var players = _playerManager.ServerSessions.Where(o => o.Status == SessionStatus.InGame).ToArray();
|
||||
|
||||
//todo paul oh my god make this less shit
|
||||
EntityQuery<MetaDataComponent> metadataQuery = default!;
|
||||
HashSet<int>[] playerChunks = default!;
|
||||
EntityUid[][] viewerEntities = default!;
|
||||
Dictionary<EntityUid, MetaDataComponent>?[] chunkCache = default!;
|
||||
|
||||
if (_pvs.CullingEnabled)
|
||||
{
|
||||
List<(uint, IChunkIndexLocation)> chunks;
|
||||
(chunks, playerChunks, viewerEntities) = _pvs.GetChunks(players);
|
||||
const int ChunkBatchSize = 2;
|
||||
var chunksCount = chunks.Count;
|
||||
var chunkBatches = (int) MathF.Ceiling((float) chunksCount / ChunkBatchSize);
|
||||
chunkCache = new Dictionary<EntityUid, MetaDataComponent>?[chunks.Count];
|
||||
var transformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
metadataQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
|
||||
Parallel.For(0, chunkBatches, i =>
|
||||
{
|
||||
var start = i * ChunkBatchSize;
|
||||
var end = Math.Min(start + ChunkBatchSize, chunksCount);
|
||||
|
||||
for (var j = start; j < end; ++j)
|
||||
{
|
||||
var (visMask, chunkIndexLocation) = chunks[j];
|
||||
chunkCache[j] = _pvs.CalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const int BatchSize = 2;
|
||||
var batches = (int) MathF.Ceiling((float) players.Length / BatchSize);
|
||||
|
||||
@@ -135,11 +167,9 @@ namespace Robust.Server.GameStates
|
||||
|
||||
for (var j = start; j < end; ++j)
|
||||
{
|
||||
var session = players[j];
|
||||
|
||||
try
|
||||
{
|
||||
SendStateUpdate(session);
|
||||
SendStateUpdate(j);
|
||||
}
|
||||
catch (Exception e) // Catch EVERY exception
|
||||
{
|
||||
@@ -148,8 +178,10 @@ namespace Robust.Server.GameStates
|
||||
}
|
||||
});
|
||||
|
||||
void SendStateUpdate(IPlayerSession session)
|
||||
void SendStateUpdate(int sessionIndex)
|
||||
{
|
||||
var session = players[sessionIndex];
|
||||
|
||||
// KILL IT WITH FIRE
|
||||
if(mainThread != Thread.CurrentThread)
|
||||
IoCManager.InitThread(new DependencyCollection(parentDeps), true);
|
||||
@@ -161,7 +193,10 @@ namespace Robust.Server.GameStates
|
||||
DebugTools.Assert("Why does this channel not have an entry?");
|
||||
}
|
||||
|
||||
var (entStates, deletions) = _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick);
|
||||
var (entStates, deletions) = _pvs.CullingEnabled
|
||||
? _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick, chunkCache,
|
||||
playerChunks[sessionIndex], metadataQuery, viewerEntities[sessionIndex])
|
||||
: _pvs.GetAllEntityStates(session, lastAck, _gameTiming.CurTick);
|
||||
var playerStates = _playerManager.GetPlayerStates(lastAck);
|
||||
var mapData = _mapManager.GetStateData(lastAck);
|
||||
|
||||
@@ -194,6 +229,8 @@ namespace Robust.Server.GameStates
|
||||
_networkManager.ServerSendMessage(stateUpdateMessage, channel);
|
||||
}
|
||||
|
||||
if(_pvs.CullingEnabled)
|
||||
_pvs.ReturnToPool(chunkCache, playerChunks);
|
||||
_pvs.Cleanup(_playerManager.ServerSessions);
|
||||
var oldestAck = new GameTick(oldestAckValue);
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
@@ -44,7 +43,6 @@ namespace Robust.Server.Maps
|
||||
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IServerEntityManagerInternal _serverEntityManager = default!;
|
||||
[Dependency] private readonly IPauseManager _pauseManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public event Action<YamlStream, string>? LoadedMapData;
|
||||
@@ -54,7 +52,7 @@ namespace Robust.Server.Maps
|
||||
{
|
||||
var grid = _mapManager.GetGrid(gridId);
|
||||
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _prototypeManager);
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
|
||||
context.RegisterGrid(grid);
|
||||
var root = context.Serialize();
|
||||
var document = new YamlDocument(root);
|
||||
@@ -100,7 +98,7 @@ namespace Robust.Server.Maps
|
||||
throw new InvalidDataException("Cannot instance map with multiple grids as blueprint.");
|
||||
}
|
||||
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
|
||||
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
|
||||
context.Deserialize();
|
||||
grid = context.Grids[0];
|
||||
@@ -120,7 +118,7 @@ namespace Robust.Server.Maps
|
||||
_serverEntityManager.GetComponent<MetaDataComponent>(entity).EntityLifeStage = EntityLifeStage.MapInitialized;
|
||||
}
|
||||
}
|
||||
else if (_pauseManager.IsMapInitialized(mapId))
|
||||
else if (_mapManager.IsMapInitialized(mapId))
|
||||
{
|
||||
foreach (var entity in context.Entities)
|
||||
{
|
||||
@@ -128,7 +126,7 @@ namespace Robust.Server.Maps
|
||||
}
|
||||
}
|
||||
|
||||
if (_pauseManager.IsMapPaused(mapId))
|
||||
if (_mapManager.IsMapPaused(mapId))
|
||||
{
|
||||
foreach (var entity in context.Entities)
|
||||
{
|
||||
@@ -141,7 +139,7 @@ namespace Robust.Server.Maps
|
||||
public void SaveMap(MapId mapId, string yamlPath)
|
||||
{
|
||||
Logger.InfoS("map", $"Saving map {mapId} to {yamlPath}");
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _prototypeManager);
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
|
||||
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
|
||||
{
|
||||
context.RegisterGrid(grid);
|
||||
@@ -207,7 +205,7 @@ namespace Robust.Server.Maps
|
||||
|
||||
LoadedMapData?.Invoke(data.Stream, resPath.ToString());
|
||||
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
|
||||
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
|
||||
context.Deserialize();
|
||||
|
||||
@@ -226,7 +224,6 @@ namespace Robust.Server.Maps
|
||||
private readonly IMapManagerInternal _mapManager;
|
||||
private readonly ITileDefinitionManager _tileDefinitionManager;
|
||||
private readonly IServerEntityManagerInternal _serverEntityManager;
|
||||
private readonly IPauseManager _pauseManager;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
|
||||
private readonly MapLoadOptions? _loadOptions;
|
||||
@@ -260,12 +257,11 @@ namespace Robust.Server.Maps
|
||||
public bool MapIsPostInit { get; private set; }
|
||||
|
||||
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
|
||||
IServerEntityManagerInternal entities, IPauseManager pauseManager, IPrototypeManager prototypeManager)
|
||||
IServerEntityManagerInternal entities, IPrototypeManager prototypeManager)
|
||||
{
|
||||
_mapManager = maps;
|
||||
_tileDefinitionManager = tileDefs;
|
||||
_serverEntityManager = entities;
|
||||
_pauseManager = pauseManager;
|
||||
_prototypeManager = prototypeManager;
|
||||
|
||||
RootNode = new YamlMappingNode();
|
||||
@@ -283,13 +279,12 @@ namespace Robust.Server.Maps
|
||||
|
||||
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
|
||||
IServerEntityManagerInternal entities,
|
||||
IPauseManager pauseManager, IPrototypeManager prototypeManager,
|
||||
IPrototypeManager prototypeManager,
|
||||
YamlMappingNode node, MapId targetMapId, MapLoadOptions options)
|
||||
{
|
||||
_mapManager = maps;
|
||||
_tileDefinitionManager = tileDefs;
|
||||
_serverEntityManager = entities;
|
||||
_pauseManager = pauseManager;
|
||||
_loadOptions = options;
|
||||
|
||||
RootNode = node;
|
||||
@@ -612,7 +607,7 @@ namespace Robust.Server.Maps
|
||||
|
||||
if (!MapIsPostInit)
|
||||
{
|
||||
_pauseManager.AddUninitializedMap(TargetMap);
|
||||
_mapManager.AddUninitializedMap(TargetMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -719,7 +714,7 @@ namespace Robust.Server.Maps
|
||||
var isPostInit = false;
|
||||
foreach (var grid in Grids)
|
||||
{
|
||||
if (_pauseManager.IsMapInitialized(grid.ParentMapId))
|
||||
if (_mapManager.IsMapInitialized(grid.ParentMapId))
|
||||
{
|
||||
isPostInit = true;
|
||||
break;
|
||||
|
||||
@@ -130,6 +130,12 @@ namespace Robust.Shared.Maths
|
||||
return new((int) MathF.Floor(X), (int) MathF.Floor(Y));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Vector2i Ceiled()
|
||||
{
|
||||
return new((int) MathF.Ceiling(X), (int) MathF.Ceiling(Y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts a vector from another, returning a new vector.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class IgnorePauseComponent : Component
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
protected override void OnAdd()
|
||||
{
|
||||
base.OnAdd();
|
||||
_entMan.GetComponent<MetaDataComponent>(Owner).EntityPaused = false;
|
||||
IoCManager.Resolve<IEntityManager>().GetComponent<MetaDataComponent>(Owner).EntityPaused = false;
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
if (IoCManager.Resolve<IPauseManager>().IsMapPaused(_entMan.GetComponent<TransformComponent>(Owner).MapID))
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (IoCManager.Resolve<IMapManager>().IsMapPaused(entMan.GetComponent<TransformComponent>(Owner).MapID))
|
||||
{
|
||||
_entMan.GetComponent<MetaDataComponent>(Owner).EntityPaused = true;
|
||||
entMan.GetComponent<MetaDataComponent>(Owner).EntityPaused = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,9 +147,11 @@ namespace Robust.Shared.GameObjects
|
||||
get => _entityPaused;
|
||||
set
|
||||
{
|
||||
if (_entityPaused == value)
|
||||
return;
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
if (_entityPaused == value || value && entMan.HasComponent<IgnorePauseComponent>(Owner))
|
||||
if (value && entMan.HasComponent<IgnorePauseComponent>(Owner))
|
||||
return;
|
||||
|
||||
_entityPaused = value;
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Robust.Shared.GameObjects
|
||||
[DataField("color")]
|
||||
public Color Color = Color.White;
|
||||
[DataField("map")]
|
||||
public List<string>? MapKeys;
|
||||
public HashSet<string>? MapKeys;
|
||||
|
||||
public static PrototypeLayerData New()
|
||||
{
|
||||
|
||||
@@ -155,12 +155,18 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_parent.IsValid())
|
||||
var parent = _parent;
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
var rotation = _localRotation;
|
||||
|
||||
while (parent.IsValid())
|
||||
{
|
||||
return Parent!.WorldRotation + _localRotation;
|
||||
var parentXform = xformQuery.GetComponent(parent);
|
||||
rotation += parentXform._localRotation;
|
||||
parent = parentXform.ParentUid;
|
||||
}
|
||||
|
||||
return _localRotation;
|
||||
return rotation;
|
||||
}
|
||||
set
|
||||
{
|
||||
@@ -211,15 +217,21 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_parent.IsValid())
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
var parent = _parent;
|
||||
var myMatrix = _localMatrix;
|
||||
|
||||
while (parent.IsValid())
|
||||
{
|
||||
var parentMatrix = Parent!.WorldMatrix;
|
||||
var myMatrix = GetLocalMatrix();
|
||||
var parentXform = xformQuery.GetComponent(parent);
|
||||
var parentMatrix = parentXform._localMatrix;
|
||||
parent = parentXform.ParentUid;
|
||||
|
||||
Matrix3.Multiply(ref myMatrix, ref parentMatrix, out var result);
|
||||
return result;
|
||||
myMatrix = result;
|
||||
}
|
||||
|
||||
return GetLocalMatrix();
|
||||
return myMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,15 +242,21 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_parent.IsValid())
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
var parent = _parent;
|
||||
var myMatrix = _invLocalMatrix;
|
||||
|
||||
while (parent.IsValid())
|
||||
{
|
||||
var matP = Parent!.InvWorldMatrix;
|
||||
var myMatrix = GetLocalMatrixInv();
|
||||
Matrix3.Multiply(ref matP, ref myMatrix, out var result);
|
||||
return result;
|
||||
var parentXform = xformQuery.GetComponent(parent);
|
||||
var parentMatrix = parentXform._invLocalMatrix;
|
||||
parent = parentXform.ParentUid;
|
||||
|
||||
Matrix3.Multiply(ref parentMatrix, ref myMatrix, out var result);
|
||||
myMatrix = result;
|
||||
}
|
||||
|
||||
return GetLocalMatrixInv();
|
||||
return myMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,15 +332,16 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (!sameParent)
|
||||
{
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
changedParent = true;
|
||||
var newParent = _entMan.GetComponent<TransformComponent>(value.EntityId);
|
||||
var newParent = xformQuery.GetComponent(value.EntityId);
|
||||
|
||||
DebugTools.Assert(newParent != this,
|
||||
$"Can't parent a {nameof(TransformComponent)} to itself.");
|
||||
|
||||
// That's already our parent, don't bother attaching again.
|
||||
|
||||
var oldParent = Parent;
|
||||
var oldParent = _parent.IsValid() ? xformQuery.GetComponent(_parent) : null;
|
||||
var uid = Owner;
|
||||
oldParent?._children.Remove(uid);
|
||||
newParent._children.Add(uid);
|
||||
@@ -343,7 +362,8 @@ namespace Robust.Shared.GameObjects
|
||||
// This may not in fact be the right thing.
|
||||
if (changedParent || !DeferUpdates)
|
||||
RebuildMatrices();
|
||||
Dirty();
|
||||
|
||||
Dirty(_entMan);
|
||||
|
||||
if (!DeferUpdates)
|
||||
{
|
||||
@@ -507,7 +527,7 @@ namespace Robust.Shared.GameObjects
|
||||
// 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 IMapComponent every iteration.
|
||||
static MapId FindMapIdAndSet(TransformComponent p, IEntityManager entMan)
|
||||
static MapId FindMapIdAndSet(TransformComponent p, IEntityManager entMan, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
if (p._mapIdInitialized)
|
||||
{
|
||||
@@ -517,7 +537,7 @@ namespace Robust.Shared.GameObjects
|
||||
MapId value;
|
||||
if (p._parent.IsValid())
|
||||
{
|
||||
value = FindMapIdAndSet((TransformComponent) p.Parent!, entMan);
|
||||
value = FindMapIdAndSet(xformQuery.GetComponent(p._parent), entMan, xformQuery);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -537,9 +557,11 @@ namespace Robust.Shared.GameObjects
|
||||
return value;
|
||||
}
|
||||
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (!_mapIdInitialized)
|
||||
{
|
||||
FindMapIdAndSet(this, _entMan);
|
||||
FindMapIdAndSet(this, _entMan, xformQuery);
|
||||
|
||||
_mapIdInitialized = true;
|
||||
}
|
||||
@@ -728,17 +750,27 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
var oldMapId = MapID;
|
||||
|
||||
//Set Paused state
|
||||
var mapPaused = _mapManager.IsMapPaused(newMapId);
|
||||
var metaData = _entMan.GetComponent<MetaDataComponent>(Owner);
|
||||
metaData.EntityPaused = mapPaused;
|
||||
|
||||
MapID = newMapId;
|
||||
MapIdChanged(oldMapId);
|
||||
UpdateChildMapIdsRecursive(MapID, _entMan);
|
||||
UpdateChildMapIdsRecursive(MapID, _entMan, mapPaused);
|
||||
}
|
||||
|
||||
private void UpdateChildMapIdsRecursive(MapId newMapId, IEntityManager entMan)
|
||||
private void UpdateChildMapIdsRecursive(MapId newMapId, IEntityManager entMan, bool mapPaused)
|
||||
{
|
||||
var xforms = _entMan.GetEntityQuery<TransformComponent>();
|
||||
var metaEnts = _entMan.GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
foreach (var child in _children)
|
||||
{
|
||||
//Set Paused state
|
||||
var metaData = metaEnts.GetComponent(child);
|
||||
metaData.EntityPaused = mapPaused;
|
||||
|
||||
var concrete = xforms.GetComponent(child);
|
||||
var old = concrete.MapID;
|
||||
|
||||
@@ -747,7 +779,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (concrete.ChildCount != 0)
|
||||
{
|
||||
concrete.UpdateChildMapIdsRecursive(newMapId, entMan);
|
||||
concrete.UpdateChildMapIdsRecursive(newMapId, entMan, mapPaused);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -800,14 +832,14 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var parent = _parent;
|
||||
var worldRot = _localRotation;
|
||||
var worldMatrix = GetLocalMatrix();
|
||||
var worldMatrix = _localMatrix;
|
||||
|
||||
// By doing these all at once we can elide multiple IsValid + GetComponent calls
|
||||
while (parent.IsValid())
|
||||
{
|
||||
var xform = xforms.GetComponent(parent);
|
||||
worldRot += xform.LocalRotation;
|
||||
var parentMatrix = xform.GetLocalMatrix();
|
||||
var parentMatrix = xform._localMatrix;
|
||||
Matrix3.Multiply(ref worldMatrix, ref parentMatrix, out var result);
|
||||
worldMatrix = result;
|
||||
parent = xform.ParentUid;
|
||||
@@ -861,8 +893,8 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var parent = _parent;
|
||||
var worldRot = _localRotation;
|
||||
var invMatrix = GetLocalMatrixInv();
|
||||
var worldMatrix = GetLocalMatrix();
|
||||
var invMatrix = _invLocalMatrix;
|
||||
var worldMatrix = _localMatrix;
|
||||
|
||||
// By doing these all at once we can elide multiple IsValid + GetComponent calls
|
||||
while (parent.IsValid())
|
||||
@@ -870,11 +902,11 @@ namespace Robust.Shared.GameObjects
|
||||
var xform = xformQuery.GetComponent(parent);
|
||||
worldRot += xform.LocalRotation;
|
||||
|
||||
var parentMatrix = xform.GetLocalMatrix();
|
||||
var parentMatrix = xform._localMatrix;
|
||||
Matrix3.Multiply(ref worldMatrix, ref parentMatrix, out var result);
|
||||
worldMatrix = result;
|
||||
|
||||
var parentInvMatrix = xform.GetLocalMatrixInv();
|
||||
var parentInvMatrix = xform._invLocalMatrix;
|
||||
Matrix3.Multiply(ref parentInvMatrix, ref invMatrix, out var invResult);
|
||||
invMatrix = invResult;
|
||||
|
||||
@@ -1005,16 +1037,6 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrix()
|
||||
{
|
||||
return _localMatrix;
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrixInv()
|
||||
{
|
||||
return _invLocalMatrix;
|
||||
}
|
||||
|
||||
private void RebuildMatrices()
|
||||
{
|
||||
var pos = _localPosition;
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Robust.Shared.GameObjects
|
||||
[IoC.Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
|
||||
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[IoC.Dependency] private readonly IPauseManager _pauseManager = default!;
|
||||
|
||||
#endregion Dependencies
|
||||
|
||||
@@ -438,7 +437,7 @@ namespace Robust.Shared.GameObjects
|
||||
StartEntity(entity);
|
||||
|
||||
// If the map we're initializing the entity on is initialized, run map init on it.
|
||||
if (_pauseManager.IsMapInitialized(mapId))
|
||||
if (_mapManager.IsMapInitialized(mapId))
|
||||
entity.RunMapInit();
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -3,7 +3,6 @@ using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -172,7 +171,7 @@ namespace Robust.Shared.GameObjects
|
||||
set
|
||||
{
|
||||
if (MetaData is {} metaData)
|
||||
metaData.EntityName = value;
|
||||
metaData.EntityDescription = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
internal abstract class SharedAppearanceSystem : EntitySystem
|
||||
public abstract class SharedAppearanceSystem : EntitySystem
|
||||
{
|
||||
public virtual void MarkDirty(AppearanceComponent component) {}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Robust.Shared.GameObjects
|
||||
IoCManager.Resolve<IIslandManager>().Initialize();
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.AutoClearForces, OnAutoClearChange, true);
|
||||
configManager.OnValueChanged(CVars.AutoClearForces, OnAutoClearChange);
|
||||
}
|
||||
|
||||
private void HandlePhysicsMapInit(EntityUid uid, SharedPhysicsMapComponent component, ComponentInit args)
|
||||
@@ -81,6 +81,7 @@ namespace Robust.Shared.GameObjects
|
||||
component.ContactManager = new();
|
||||
component.ContactManager.Initialize();
|
||||
component.ContactManager.MapId = component.MapId;
|
||||
component.AutoClearForces = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.AutoClearForces);
|
||||
|
||||
component.ContactManager.KinematicControllerCollision += KinematicControllerCollision;
|
||||
}
|
||||
|
||||
@@ -1,30 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Linguini.Bundle.Errors;
|
||||
using Linguini.Syntax.Parser.Error;
|
||||
|
||||
namespace Robust.Shared.Localization
|
||||
namespace Robust.Shared.Localization;
|
||||
|
||||
internal static class LocHelper
|
||||
{
|
||||
internal static class LocHelper
|
||||
public static string FormatCompileErrors(this ParseError self, ReadOnlyMemory<char> resource,
|
||||
string? newLine = null)
|
||||
{
|
||||
public static string FormatCompileErrors(this ParseError self, ReadOnlyMemory<char> resource)
|
||||
ErrorSpan span = new(self.Row, self.Slice!.Value.Start.Value, self.Slice.Value.End.Value,
|
||||
self.Position.Start.Value, self.Position.End.Value);
|
||||
return FormatErrors(self.Message, span, resource, newLine);
|
||||
}
|
||||
|
||||
private static string FormatErrors(string message, ErrorSpan span, ReadOnlyMemory<char> resource, string? newLine)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var errContext = resource.Slice(span.StartSpan, span.EndSpan - span.StartSpan).ToString();
|
||||
var lines = new List<ReadOnlyMemory<char>>(5);
|
||||
var currLineOffset = 0;
|
||||
var lastStart = 0;
|
||||
for (var i = 0; i < span.StartMark - span.StartSpan; i++)
|
||||
{
|
||||
ErrorSpan span = new(self.Row, self.Slice!.Value.Start.Value, self.Slice.Value.End.Value,
|
||||
self.Position.Start.Value, self.Position.End.Value);
|
||||
return FormatErrors(self.Message, span, resource);
|
||||
switch (errContext[i])
|
||||
{
|
||||
// Reset current line so that mark aligns with the reported error
|
||||
// We cheat here a bit, since we both `\r\n` and `\n` end with '\n'
|
||||
case '\n':
|
||||
if (i > 0 && errContext[i - 1] == '\r')
|
||||
{
|
||||
lines.Add(resource.Slice(lastStart, currLineOffset - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add(resource.Slice(lastStart, currLineOffset));
|
||||
}
|
||||
|
||||
lastStart = currLineOffset + 1;
|
||||
currLineOffset = 0;
|
||||
break;
|
||||
default:
|
||||
currLineOffset++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatErrors(string message, ErrorSpan span, ReadOnlyMemory<char> resource)
|
||||
lines.Add(resource.Slice(lastStart, resource.Length - lastStart));
|
||||
|
||||
|
||||
var lastLine = $"{span.Row + lines.Count - 1}".Length;
|
||||
for (var index = 0; index < lines.Count; index++)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var row = $" {span.Row} ";
|
||||
var errContext = resource.Slice(span.StartSpan, span.EndSpan - span.StartSpan).ToString();
|
||||
sb.Append(row).Append('|')
|
||||
.AppendLine(errContext);
|
||||
sb.Append(' ', row.Length).Append('|')
|
||||
.Append(' ', span.StartMark - span.StartSpan - 1).Append('^', span.EndMark - span.StartMark)
|
||||
.AppendLine($" {message}");
|
||||
return sb.ToString();
|
||||
var line = lines[index];
|
||||
|
||||
sb.Append(newLine ?? Environment.NewLine).Append(' ').Append($"{span.Row + index}".PadLeft(lastLine))
|
||||
.Append(" |").Append(line);
|
||||
}
|
||||
|
||||
sb.Append(newLine ?? Environment.NewLine)
|
||||
.Append(' ', currLineOffset + lastLine + 3)
|
||||
.Append('^', span.EndMark - span.StartMark)
|
||||
.Append($" {message}");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Shared.Localization
|
||||
|
||||
if (!TryGetString(messageId, out var msg))
|
||||
{
|
||||
_logSawmill.Warning("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
|
||||
_logSawmill.Debug("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
|
||||
msg = messageId;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Robust.Shared.Localization
|
||||
|
||||
if (!TryGetString(messageId, out var msg, args0))
|
||||
{
|
||||
_logSawmill.Warning("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
|
||||
_logSawmill.Debug("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
|
||||
msg = messageId;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
@@ -13,6 +15,14 @@ namespace Robust.Shared.Map
|
||||
|
||||
internal readonly int Value;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="GridId"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should NOT be used in regular code, and is only public for special/legacy
|
||||
/// cases. Generally you should only use this for parsing a GridId in console commands
|
||||
/// and immediately check if the grid actually exists in the <see cref="IMapManager"/>.
|
||||
/// </remarks>
|
||||
public GridId(int value)
|
||||
{
|
||||
Value = value;
|
||||
@@ -57,6 +67,30 @@ namespace Robust.Shared.Map
|
||||
return self.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="GridId"/> is an alias of the <see cref="EntityUid"/> that
|
||||
/// holds the <see cref="IMapGridComponent"/>, so it can be implicitly converted.
|
||||
/// </summary>
|
||||
public static implicit operator EntityUid(GridId self)
|
||||
{
|
||||
// If this throws, you are either using an unallocated gridId,
|
||||
// or using it after the grid was freed. Both of these are bugs.
|
||||
return IoCManager.Resolve<IMapManager>().GetGridEuid(self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="GridId"/> is an alias of the <see cref="EntityUid"/> that
|
||||
/// holds the <see cref="IMapGridComponent"/>.
|
||||
/// </summary>
|
||||
public static implicit operator GridId(EntityUid euid)
|
||||
{
|
||||
// If this throws, you are using an EntityUid that isn't a grid.
|
||||
// This would raise the question, "Why does your code think this entity is a grid?".
|
||||
// Grid-ness is defined by the entity having an IMapGridComponent,
|
||||
// was the component removed without you knowing?
|
||||
return IoCManager.Resolve<IMapManager>().GetGridComp(euid).GridIndex;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
|
||||
@@ -3,13 +3,14 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <summary>
|
||||
/// This manages all of the grids in the world.
|
||||
/// </summary>
|
||||
public interface IMapManager
|
||||
public interface IMapManager : IPauseManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The default <see cref="MapId" /> that is always available. Equivalent to SS13 Null space.
|
||||
|
||||
@@ -2,101 +2,121 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Timing
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
internal sealed class PauseManager : IPauseManager, IPostInjectInit
|
||||
internal partial class MapManager
|
||||
{
|
||||
[Dependency] private readonly IConsoleHost _conhost = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _entityLookup = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
[ViewVariables] private readonly HashSet<MapId> _pausedMaps = new();
|
||||
[ViewVariables] private readonly HashSet<MapId> _unInitializedMaps = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetMapPaused(MapId mapId, bool paused)
|
||||
{
|
||||
if(!MapExists(mapId))
|
||||
throw new ArgumentException("That map does not exist.");
|
||||
|
||||
if (paused)
|
||||
{
|
||||
_pausedMaps.Add(mapId);
|
||||
|
||||
foreach (var entity in _entityLookup.GetEntitiesInMap(mapId))
|
||||
{
|
||||
_entityManager.GetComponent<MetaDataComponent>(entity).EntityPaused = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_pausedMaps.Remove(mapId);
|
||||
}
|
||||
|
||||
foreach (var entity in _entityLookup.GetEntitiesInMap(mapId))
|
||||
{
|
||||
_entityManager.GetComponent<MetaDataComponent>(entity).EntityPaused = false;
|
||||
}
|
||||
var mapEnt = GetMapEntityId(mapId);
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var metaQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
RecursiveSetPaused(mapEnt, paused, in xformQuery, in metaQuery);
|
||||
}
|
||||
|
||||
private static void RecursiveSetPaused(EntityUid entity, bool paused,
|
||||
in EntityQuery<TransformComponent> xformQuery,
|
||||
in EntityQuery<MetaDataComponent> metaQuery)
|
||||
{
|
||||
metaQuery.GetComponent(entity).EntityPaused = paused;
|
||||
|
||||
foreach (var child in xformQuery.GetComponent(entity)._children)
|
||||
{
|
||||
RecursiveSetPaused(child, paused, in xformQuery, in metaQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.");
|
||||
|
||||
_unInitializedMaps.Remove(mapId);
|
||||
|
||||
foreach (var entity in IoCManager.Resolve<IEntityLookup>().GetEntitiesInMap(mapId).ToArray())
|
||||
{
|
||||
entity.RunMapInit();
|
||||
var mapEnt = GetMapEntityId(mapId);
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var metaQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
// MapInit could have deleted this entity.
|
||||
if(_entityManager.TryGetComponent(entity, out MetaDataComponent? meta))
|
||||
meta.EntityPaused = false;
|
||||
RecursiveDoMapInit(mapEnt, in xformQuery, in metaQuery);
|
||||
}
|
||||
|
||||
private static void RecursiveDoMapInit(EntityUid entity,
|
||||
in EntityQuery<TransformComponent> xformQuery,
|
||||
in EntityQuery<MetaDataComponent> metaQuery)
|
||||
{
|
||||
// RunMapInit can modify the TransformTree
|
||||
// ToArray caches deleted euids, we check here if they still exist.
|
||||
if(!metaQuery.TryGetComponent(entity, out var meta))
|
||||
return;
|
||||
|
||||
entity.RunMapInit();
|
||||
meta.EntityPaused = false;
|
||||
|
||||
foreach (var child in xformQuery.GetComponent(entity)._children.ToArray())
|
||||
{
|
||||
RecursiveDoMapInit(child, in xformQuery, in metaQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DoGridMapInitialize(IMapGrid grid)
|
||||
{
|
||||
DoGridMapInitialize(grid.Index);
|
||||
// NOP
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DoGridMapInitialize(GridId gridId)
|
||||
{
|
||||
var mapId = _mapManager.GetGrid(gridId).ParentMapId;
|
||||
|
||||
foreach (var entity in _entityLookup.GetEntitiesInMap(mapId))
|
||||
{
|
||||
if (_entityManager.GetComponent<TransformComponent>(entity).GridID != gridId)
|
||||
continue;
|
||||
|
||||
entity.RunMapInit();
|
||||
_entityManager.GetComponent<MetaDataComponent>(entity).EntityPaused = false;
|
||||
}
|
||||
// NOP
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddUninitializedMap(MapId mapId)
|
||||
{
|
||||
_unInitializedMaps.Add(mapId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMapPaused(MapId mapId)
|
||||
{
|
||||
return _pausedMaps.Contains(mapId) || _unInitializedMaps.Contains(mapId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsGridPaused(IMapGrid grid)
|
||||
{
|
||||
return IsMapPaused(grid.ParentMapId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsGridPaused(GridId gridId)
|
||||
{
|
||||
if (_mapManager.TryGetGrid(gridId, out var grid))
|
||||
if (TryGetGrid(gridId, out var grid))
|
||||
{
|
||||
return IsGridPaused(grid);
|
||||
}
|
||||
@@ -105,15 +125,18 @@ namespace Robust.Shared.Timing
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMapInitialized(MapId mapId)
|
||||
{
|
||||
return !_unInitializedMaps.Contains(mapId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PostInject()
|
||||
/// <summary>
|
||||
/// Initializes the map pausing system.
|
||||
/// </summary>
|
||||
private void InitializeMapPausing()
|
||||
{
|
||||
_mapManager.MapDestroyed += (_, args) =>
|
||||
MapDestroyed += (_, args) =>
|
||||
{
|
||||
_pausedMaps.Remove(args.Map);
|
||||
_unInitializedMaps.Add(args.Map);
|
||||
@@ -130,10 +153,9 @@ namespace Robust.Shared.Timing
|
||||
return;
|
||||
}
|
||||
|
||||
string? arg = args[0];
|
||||
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
|
||||
var mapId = new MapId(int.Parse(args[0], CultureInfo.InvariantCulture));
|
||||
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
if (!MapExists(mapId))
|
||||
{
|
||||
shell.WriteError("That map does not exist.");
|
||||
return;
|
||||
@@ -147,10 +169,9 @@ namespace Robust.Shared.Timing
|
||||
"querymappaused <map ID>",
|
||||
(shell, _, args) =>
|
||||
{
|
||||
string? arg = args[0];
|
||||
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
|
||||
var mapId = new MapId(int.Parse(args[0], CultureInfo.InvariantCulture));
|
||||
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
if (!MapExists(mapId))
|
||||
{
|
||||
shell.WriteError("That map does not exist.");
|
||||
return;
|
||||
@@ -170,10 +191,9 @@ namespace Robust.Shared.Timing
|
||||
return;
|
||||
}
|
||||
|
||||
string? arg = args[0];
|
||||
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
|
||||
var mapId = new MapId(int.Parse(args[0], CultureInfo.InvariantCulture));
|
||||
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
if (!MapExists(mapId))
|
||||
{
|
||||
shell.WriteLine("That map does not exist.");
|
||||
return;
|
||||
@@ -1,3 +1,4 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -7,20 +8,25 @@ using Robust.Shared.Utility;
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
/// <inheritdoc cref="IMapManager" />
|
||||
[Virtual]
|
||||
internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
|
||||
{
|
||||
[field: Dependency] public IGameTiming GameTiming { get; } = default!;
|
||||
[field: Dependency] public IEntityManager EntityManager { get; } = default!;
|
||||
|
||||
[Dependency] private readonly IConsoleHost _conhost = default!;
|
||||
[Dependency] private readonly IEntityLookup _entityLookup = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
InitializeGridTrees();
|
||||
#if DEBUG
|
||||
DebugTools.Assert(!_dbgGuardInit);
|
||||
DebugTools.Assert(!_dbgGuardRunning);
|
||||
_dbgGuardInit = true;
|
||||
#endif
|
||||
InitializeGridTrees();
|
||||
InitializeMapPausing();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Handle storage and loading of YAML prototypes.
|
||||
/// </summary>
|
||||
public interface IPrototypeManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain variant.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the variant of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IPrototype"/> by ID.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
T Index<T>(string id) where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IPrototype"/> by ID.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the ID does not exist or the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IPrototype Index(Type type, string id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a prototype of type <typeparamref name="T"/> with the specified <param name="id"/> exists.
|
||||
/// </summary>
|
||||
bool HasIndex<T>(string id) where T : class, IPrototype;
|
||||
|
||||
bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
|
||||
bool TryIndex(Type type, string id, [NotNullWhen(true)] out IPrototype? prototype);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a prototype variant <param name="variant"/> exists.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <returns>Whether the prototype variant exists.</returns>
|
||||
bool HasVariant(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Type for a prototype variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <returns>The specified prototype Type.</returns>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown when the specified prototype variant isn't registered or doesn't exist.
|
||||
/// </exception>
|
||||
Type GetVariantType(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the Type for a prototype variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <param name="prototype">The specified prototype Type, or null.</param>
|
||||
/// <returns>Whether the prototype type was found and <see cref="prototype"/> isn't null.</returns>
|
||||
bool TryGetVariantType(string variant, [NotNullWhen(true)] out Type? prototype);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="variant"></param>
|
||||
/// <returns></returns>
|
||||
bool TryGetVariantFrom(Type type, [NotNullWhen(true)] out string? variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="prototype">The prototype in question.</param>
|
||||
/// <param name="variant">Identifier for the prototype variant, or null.</param>
|
||||
/// <returns>Whether the prototype variant was successfully retrieved.</returns>
|
||||
bool TryGetVariantFrom(IPrototype prototype, [NotNullWhen(true)] out string? variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant, or null.</param>
|
||||
/// <typeparam name="T">The prototype in question.</typeparam>
|
||||
/// <returns>Whether the prototype variant was successfully retrieved.</returns>
|
||||
bool TryGetVariantFrom<T>([NotNullWhen(true)] out string? variant) where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Load prototypes from files in a directory, recursively.
|
||||
/// </summary>
|
||||
List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false);
|
||||
|
||||
Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path);
|
||||
|
||||
HashSet<IPrototype> LoadFromString(string str, bool overwrite = false, string actionMessage="");
|
||||
|
||||
void RemoveString(string prototypes);
|
||||
|
||||
/// <summary>
|
||||
/// Clear out all prototypes and reset to a blank slate.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Syncs all inter-prototype data. Call this when operations adding new prototypes are done.
|
||||
/// </summary>
|
||||
void Resync();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a specific prototype name to be ignored.
|
||||
/// </summary>
|
||||
void RegisterIgnore(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a single prototype class type into the manager.
|
||||
/// </summary>
|
||||
/// <param name="protoClass">A prototype class type that implements IPrototype. This type also
|
||||
/// requires a <see cref="PrototypeAttribute"/> with a non-empty class string.</param>
|
||||
void RegisterType(Type protoClass);
|
||||
|
||||
event Action<YamlStream, string>? LoadedData;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when prototype are reloaded. The event args contain the modified prototypes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does NOT fire on initial prototype load.
|
||||
/// </remarks>
|
||||
event Action<PrototypesReloadedEventArgs> PrototypesReloaded;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Quick attribute to give the prototype its type string.
|
||||
/// To prevent needing to instantiate it because interfaces can't declare statics.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
[BaseTypeRequired(typeof(IPrototype))]
|
||||
[MeansImplicitUse]
|
||||
[MeansDataDefinition]
|
||||
public sealed class PrototypeAttribute : Attribute
|
||||
{
|
||||
private readonly string type;
|
||||
public string Type => type;
|
||||
public readonly int LoadPriority;
|
||||
public readonly string LoadBefore;
|
||||
public readonly string LoadAfter;
|
||||
|
||||
public PrototypeAttribute(string type, int loadPriority = 1, string loadBefore="" ,string loadAfter= "")
|
||||
{
|
||||
this.type = type;
|
||||
LoadPriority = loadPriority;
|
||||
LoadBefore = loadBefore;
|
||||
LoadAfter = loadAfter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
@@ -26,8 +25,175 @@ using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle storage and loading of YAML prototypes.
|
||||
/// </summary>
|
||||
public interface IPrototypeManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain variant.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the variant of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IPrototype"/> by ID.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
T Index<T>(string id) where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IPrototype"/> by ID.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the ID does not exist or the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IPrototype Index(Type type, string id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a prototype of type <typeparamref name="T"/> with the specified <param name="id"/> exists.
|
||||
/// </summary>
|
||||
bool HasIndex<T>(string id) where T : class, IPrototype;
|
||||
bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
|
||||
bool TryIndex(Type type, string id, [NotNullWhen(true)] out IPrototype? prototype);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a prototype variant <param name="variant"/> exists.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <returns>Whether the prototype variant exists.</returns>
|
||||
bool HasVariant(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Type for a prototype variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <returns>The specified prototype Type.</returns>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown when the specified prototype variant isn't registered or doesn't exist.
|
||||
/// </exception>
|
||||
Type GetVariantType(string variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the Type for a prototype variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant.</param>
|
||||
/// <param name="prototype">The specified prototype Type, or null.</param>
|
||||
/// <returns>Whether the prototype type was found and <see cref="prototype"/> isn't null.</returns>
|
||||
bool TryGetVariantType(string variant, [NotNullWhen(true)] out Type? prototype);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="variant"></param>
|
||||
/// <returns></returns>
|
||||
bool TryGetVariantFrom(Type type, [NotNullWhen(true)] out string? variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="prototype">The prototype in question.</param>
|
||||
/// <param name="variant">Identifier for the prototype variant, or null.</param>
|
||||
/// <returns>Whether the prototype variant was successfully retrieved.</returns>
|
||||
bool TryGetVariantFrom(IPrototype prototype, [NotNullWhen(true)] out string? variant);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get a prototype's variant.
|
||||
/// </summary>
|
||||
/// <param name="variant">Identifier for the prototype variant, or null.</param>
|
||||
/// <typeparam name="T">The prototype in question.</typeparam>
|
||||
/// <returns>Whether the prototype variant was successfully retrieved.</returns>
|
||||
bool TryGetVariantFrom<T>([NotNullWhen(true)] out string? variant) where T : class, IPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// Load prototypes from files in a directory, recursively.
|
||||
/// </summary>
|
||||
List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false);
|
||||
|
||||
Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path);
|
||||
|
||||
List<IPrototype> LoadFromStream(TextReader stream, bool overwrite = false);
|
||||
|
||||
List<IPrototype> LoadString(string str, bool overwrite = false);
|
||||
|
||||
void RemoveString(string prototypes);
|
||||
|
||||
/// <summary>
|
||||
/// Clear out all prototypes and reset to a blank slate.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Syncs all inter-prototype data. Call this when operations adding new prototypes are done.
|
||||
/// </summary>
|
||||
void Resync();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a specific prototype name to be ignored.
|
||||
/// </summary>
|
||||
void RegisterIgnore(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a single prototype class type into the manager.
|
||||
/// </summary>
|
||||
/// <param name="protoClass">A prototype class type that implements IPrototype. This type also
|
||||
/// requires a <see cref="PrototypeAttribute"/> with a non-empty class string.</param>
|
||||
void RegisterType(Type protoClass);
|
||||
|
||||
event Action<YamlStream, string>? LoadedData;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when prototype are reloaded. The event args contain the modified prototypes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does NOT fire on initial prototype load.
|
||||
/// </remarks>
|
||||
event Action<PrototypesReloadedEventArgs> PrototypesReloaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quick attribute to give the prototype its type string.
|
||||
/// To prevent needing to instantiate it because interfaces can't declare statics.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
[BaseTypeRequired(typeof(IPrototype))]
|
||||
[MeansImplicitUse]
|
||||
[MeansDataDefinition]
|
||||
public sealed class PrototypeAttribute : Attribute
|
||||
{
|
||||
private readonly string type;
|
||||
public string Type => type;
|
||||
public readonly int LoadPriority = 1;
|
||||
|
||||
public PrototypeAttribute(string type, int loadPriority = 1)
|
||||
{
|
||||
this.type = type;
|
||||
LoadPriority = loadPriority;
|
||||
}
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
public class PrototypeManager : IPrototypeManager
|
||||
@@ -38,12 +204,11 @@ namespace Robust.Shared.Prototypes
|
||||
[Dependency] protected readonly ITaskManager TaskManager = default!;
|
||||
[Dependency] private readonly ISerializationManager _serializationManager = default!;
|
||||
|
||||
private readonly Dictionary<string, Type> _types = new();
|
||||
private readonly Dictionary<string, Type> _prototypeTypes = new();
|
||||
private readonly Dictionary<Type, int> _prototypePriorities = new();
|
||||
|
||||
private bool _initialized;
|
||||
private bool _hasEverBeenReloaded;
|
||||
private int mappingErrors;
|
||||
|
||||
#region IPrototypeManager members
|
||||
|
||||
@@ -51,13 +216,8 @@ namespace Robust.Shared.Prototypes
|
||||
private readonly Dictionary<Type, Dictionary<string, DeserializationResult>> _prototypeResults = new();
|
||||
private readonly Dictionary<Type, PrototypeInheritanceTree> _inheritanceTrees = new();
|
||||
|
||||
private readonly HashSet<Type> LoadBeforeList = new ();
|
||||
private readonly HashSet<Type> LoadNormalList = new ();
|
||||
private readonly HashSet<Type> LoadAfterList = new ();
|
||||
|
||||
private readonly HashSet<ErrorNode> ErrorNodes = new ();
|
||||
|
||||
private readonly HashSet<string> _ignoredPrototypeTypes = new();
|
||||
|
||||
public virtual void Initialize()
|
||||
{
|
||||
if (_initialized)
|
||||
@@ -65,9 +225,8 @@ namespace Robust.Shared.Prototypes
|
||||
throw new InvalidOperationException($"{nameof(PrototypeManager)} has already been initialized.");
|
||||
}
|
||||
|
||||
mappingErrors = 0;
|
||||
ReloadTypes();
|
||||
_initialized = true;
|
||||
ReloadPrototypeTypes();
|
||||
}
|
||||
|
||||
public IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype
|
||||
@@ -124,8 +283,7 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
mappingErrors = 0;
|
||||
_types.Clear();
|
||||
_prototypeTypes.Clear();
|
||||
_prototypes.Clear();
|
||||
_prototypeResults.Clear();
|
||||
_inheritanceTrees.Clear();
|
||||
@@ -139,7 +297,7 @@ namespace Robust.Shared.Prototypes
|
||||
protected void ReloadPrototypes(IEnumerable<ResourcePath> filePaths)
|
||||
{
|
||||
#if !FULL_RELEASE
|
||||
var changed = filePaths.SelectMany(f => LoadFromFile(f.ToRootedPath(), true)).ToList();
|
||||
var changed = filePaths.SelectMany(f => LoadFile(f.ToRootedPath(), true)).ToList();
|
||||
ReloadPrototypes(changed);
|
||||
#endif
|
||||
}
|
||||
@@ -216,7 +374,6 @@ namespace Robust.Shared.Prototypes
|
||||
#endif
|
||||
}
|
||||
|
||||
#region Inheritance Tree
|
||||
public void Resync()
|
||||
{
|
||||
var trees = _inheritanceTrees.Keys.ToList();
|
||||
@@ -285,6 +442,174 @@ namespace Robust.Shared.Prototypes
|
||||
_prototypes[type][id] = (IPrototype) populatedRes.RawValue!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new List<IPrototype>();
|
||||
|
||||
_hasEverBeenReloaded = true;
|
||||
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
|
||||
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
|
||||
|
||||
foreach (var resourcePath in streams)
|
||||
{
|
||||
var filePrototypes = LoadFile(resourcePath, overwrite);
|
||||
changedPrototypes.AddRange(filePrototypes);
|
||||
}
|
||||
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
public Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path)
|
||||
{
|
||||
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
|
||||
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
|
||||
|
||||
var dict = new Dictionary<string, HashSet<ErrorNode>>();
|
||||
foreach (var resourcePath in streams)
|
||||
{
|
||||
using var reader = ReadFile(resourcePath);
|
||||
|
||||
if (reader == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
|
||||
for (var i = 0; i < yamlStream.Documents.Count; i++)
|
||||
{
|
||||
var rootNode = (YamlSequenceNode) yamlStream.Documents[i].RootNode;
|
||||
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
|
||||
{
|
||||
var type = node.GetNode("type").AsString();
|
||||
if (!_prototypeTypes.ContainsKey(type))
|
||||
{
|
||||
if (_ignoredPrototypeTypes.Contains(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new PrototypeLoadException($"Unknown prototype type: '{type}'");
|
||||
}
|
||||
|
||||
var mapping = node.ToDataNodeCast<MappingDataNode>();
|
||||
mapping.Remove("type");
|
||||
var errorNodes = _serializationManager.ValidateNode(_prototypeTypes[type], mapping).GetErrors()
|
||||
.ToHashSet();
|
||||
if (errorNodes.Count == 0) continue;
|
||||
if (!dict.TryGetValue(resourcePath.ToString(), out var hashSet))
|
||||
dict[resourcePath.ToString()] = new HashSet<ErrorNode>();
|
||||
dict[resourcePath.ToString()].UnionWith(errorNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private StreamReader? ReadFile(ResourcePath file, bool @throw = true)
|
||||
{
|
||||
var retries = 0;
|
||||
|
||||
// This might be shit-code, but its pjb-responded-idk-when-asked shit-code.
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reader = new StreamReader(Resources.ContentFileRead(file), EncodingHelpers.UTF8);
|
||||
return reader;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (retries > 10)
|
||||
{
|
||||
if (@throw)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
Logger.Error($"Error reloading prototypes in file {file}.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
retries++;
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HashSet<IPrototype> LoadFile(ResourcePath file, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new HashSet<IPrototype>();
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = ReadFile(file, !overwrite);
|
||||
|
||||
if (reader == null)
|
||||
{
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
|
||||
LoadedData?.Invoke(yamlStream, file.ToString());
|
||||
|
||||
for (var i = 0; i < yamlStream.Documents.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentPrototypes = LoadFromDocument(yamlStream.Documents[i], overwrite);
|
||||
changedPrototypes.UnionWith(documentPrototypes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("eng", $"Exception whilst loading prototypes from {file}#{i}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
var sawmill = Logger.GetSawmill("eng");
|
||||
sawmill.Error("YamlException whilst loading prototypes from {0}: {1}", file, e.Message);
|
||||
}
|
||||
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
public List<IPrototype> LoadFromStream(TextReader stream, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new List<IPrototype>();
|
||||
_hasEverBeenReloaded = true;
|
||||
var yaml = new YamlStream();
|
||||
yaml.Load(stream);
|
||||
|
||||
for (var i = 0; i < yaml.Documents.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentPrototypes = LoadFromDocument(yaml.Documents[i], overwrite);
|
||||
changedPrototypes.AddRange(documentPrototypes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new PrototypeLoadException($"Failed to load prototypes from document#{i}", e);
|
||||
}
|
||||
}
|
||||
|
||||
LoadedData?.Invoke(yaml, "anonymous prototypes YAML stream");
|
||||
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
public List<IPrototype> LoadString(string str, bool overwrite = false)
|
||||
{
|
||||
return LoadFromStream(new StringReader(str), overwrite);
|
||||
}
|
||||
|
||||
public void RemoveString(string prototypes)
|
||||
{
|
||||
var reader = new StringReader(prototypes);
|
||||
@@ -298,7 +623,7 @@ namespace Robust.Shared.Prototypes
|
||||
foreach (var node in root.Cast<YamlMappingNode>())
|
||||
{
|
||||
var typeString = node.GetNode("type").AsString();
|
||||
var type = _types[typeString];
|
||||
var type = _prototypeTypes[typeString];
|
||||
|
||||
var id = node.GetNode("id").AsString();
|
||||
|
||||
@@ -311,203 +636,62 @@ namespace Robust.Shared.Prototypes
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path)
|
||||
{
|
||||
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
|
||||
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
|
||||
|
||||
var dict = new Dictionary<string, HashSet<ErrorNode>>();
|
||||
|
||||
foreach (var resourcePath in streams)
|
||||
{
|
||||
LoadFromFile(resourcePath, false, true);
|
||||
|
||||
if (!ErrorNodes.Any())
|
||||
continue;
|
||||
|
||||
if (!dict.TryGetValue(resourcePath.ToString(), out var hashSet))
|
||||
dict[resourcePath.ToString()] = new HashSet<ErrorNode>();
|
||||
|
||||
dict[resourcePath.ToString()].UnionWith(ErrorNodes);
|
||||
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
#region Prototype Loading
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Loads Prototypes from a path.
|
||||
/// </summary>
|
||||
/// <param name="path">path to files to load as prototype.</param>
|
||||
/// <param name="overwrite">Overwrite if prototype already is loaded and exists</param>
|
||||
/// <returns>HashSet of Loaded Prototypes</returns>
|
||||
public List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new List<IPrototype>();
|
||||
|
||||
_hasEverBeenReloaded = true;
|
||||
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
|
||||
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
|
||||
|
||||
foreach (var resourcePath in streams)
|
||||
{
|
||||
var filePrototypes = LoadFromFile(resourcePath, overwrite);
|
||||
changedPrototypes.AddRange(filePrototypes);
|
||||
}
|
||||
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads Prototypes from a file.
|
||||
/// </summary>
|
||||
/// <param name="file">file to load as prototype.</param>
|
||||
/// <param name="overwrite">Overwrite if prototype already is loaded and exists</param>
|
||||
/// /// <param name="validateMapping">toggle true to receive a count of total mapping errors</param>
|
||||
/// <returns>HashSet of Loaded Prototypes</returns>
|
||||
public HashSet<IPrototype> LoadFromFile(ResourcePath file, bool overwrite = false, bool validateMapping = false)
|
||||
{
|
||||
HashSet<IPrototype> LoadedPrototypes = new();
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var reader = new StreamReader(Resources.ContentFileRead(file), EncodingHelpers.UTF8);
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
|
||||
LoadedPrototypes = LoadFromDocument(yamlStream, overwrite, validateMapping, actionMessage: file.ToString());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.Error($"Error loading prototypes in file {file}.", e);
|
||||
}
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
var sawmill = Logger.GetSawmill("eng");
|
||||
sawmill.Error("Caught YamlException whilst loading prototypes from a File {0}: {1}", file.Filename, e.Message);
|
||||
}
|
||||
|
||||
return LoadedPrototypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads Prototypes from a string.
|
||||
/// </summary>
|
||||
/// <param name="str">Input string to load as prototype.</param>
|
||||
/// <param name="overwrite">Overwrite if prototype already is loaded and exists</param>
|
||||
/// <param name="actionMessage">String that will be included in the LoadedData Event</param>
|
||||
/// <returns>HashSet of Loaded Prototypes</returns>
|
||||
public HashSet<IPrototype> LoadFromString(string str, bool overwrite = false, string actionMessage = "")
|
||||
{
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(str));
|
||||
|
||||
return LoadFromDocument(yamlStream, overwrite, actionMessage: actionMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads YAML Prototypes via a Yaml Stream
|
||||
/// </summary>
|
||||
/// <param name="yamlStream">YamlStream to process</param>
|
||||
/// <param name="mappingErrors">a count of mapping errors. is 0 until you toggle validateMapping on.</param>
|
||||
/// <param name="validateMapping">toggle true to receive a count of total mapping errors</param>
|
||||
/// <param name="overwrite">Overwrite if prototype already is loaded and exists.</param>
|
||||
/// <param name="actionMessage">String that will be included in the LoadedData Event</param>
|
||||
/// <returns>HashSet of Loaded Prototypes</returns>
|
||||
/// <exception cref="PrototypeLoadException">Thrown when Prototype failed to load</exception>
|
||||
private HashSet<IPrototype> LoadFromDocument(YamlStream yamlStream, bool overwrite = false,
|
||||
bool validateMapping = false, string actionMessage = "")
|
||||
{
|
||||
var loadedPrototypes = new HashSet<IPrototype>();
|
||||
|
||||
|
||||
for (var i = 0; i < yamlStream.Documents.Count(); i++)
|
||||
{
|
||||
var prototypeDocument = yamlStream.Documents[i];
|
||||
var rootNode = (YamlSequenceNode) prototypeDocument.RootNode;
|
||||
|
||||
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
|
||||
{
|
||||
var type = node.GetNode("type").AsString();
|
||||
if (!_types.ContainsKey(type))
|
||||
{
|
||||
if (_ignoredPrototypeTypes.Contains(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new PrototypeLoadException($"Unknown prototype type: '{type}'");
|
||||
}
|
||||
|
||||
var prototypeType = _types[type];
|
||||
|
||||
var res = _serializationManager.Read(prototypeType, node.ToDataNode(), skipHook: true);
|
||||
var prototype = (IPrototype) res.RawValue!;
|
||||
|
||||
if (!overwrite && _prototypes[prototypeType].ContainsKey(prototype.ID))
|
||||
{
|
||||
throw new PrototypeLoadException($"Duplicate ID: '{prototype.ID}'");
|
||||
}
|
||||
|
||||
_prototypeResults[prototypeType][prototype.ID] = res;
|
||||
if (prototype is IInheritingPrototype inheritingPrototype)
|
||||
{
|
||||
_inheritanceTrees[prototypeType].AddId(prototype.ID, inheritingPrototype.Parent, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//we call it here since it wont get called when pushing inheritance
|
||||
res.CallAfterDeserializationHook();
|
||||
}
|
||||
|
||||
_prototypes[prototypeType][prototype.ID] = prototype;
|
||||
loadedPrototypes.Add(prototype);
|
||||
|
||||
if (validateMapping)
|
||||
{
|
||||
var mapping = node.ToDataNodeCast<MappingDataNode>();
|
||||
mapping.Remove("type");
|
||||
var errorNodes = _serializationManager.ValidateNode(_types[type], mapping).GetErrors()
|
||||
.ToHashSet();
|
||||
mappingErrors = errorNodes.Count;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
LoadedData?.Invoke(yamlStream, actionMessage);
|
||||
return loadedPrototypes;
|
||||
}
|
||||
#endregion
|
||||
#endregion IPrototypeManager members
|
||||
|
||||
private void ReloadTypes()
|
||||
private void ReloadPrototypeTypes()
|
||||
{
|
||||
Clear();
|
||||
foreach (var type in _reflectionManager.GetAllChildren<IPrototype>())
|
||||
{
|
||||
var prototypeAttributes = (PrototypeAttribute?) Attribute.GetCustomAttribute(type, typeof(PrototypeAttribute));
|
||||
|
||||
if ( prototypeAttributes!.LoadBefore != string.Empty)
|
||||
LoadBeforeList.Add(type);
|
||||
else if (prototypeAttributes!.LoadAfter != string.Empty)
|
||||
LoadAfterList.Add(type);
|
||||
else
|
||||
LoadNormalList.Add(type);
|
||||
|
||||
RegisterType(type);
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<IPrototype> LoadFromDocument(YamlDocument document, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new HashSet<IPrototype>();
|
||||
var rootNode = (YamlSequenceNode) document.RootNode;
|
||||
|
||||
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
|
||||
{
|
||||
var type = node.GetNode("type").AsString();
|
||||
if (!_prototypeTypes.ContainsKey(type))
|
||||
{
|
||||
if (_ignoredPrototypeTypes.Contains(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new PrototypeLoadException($"Unknown prototype type: '{type}'");
|
||||
}
|
||||
|
||||
var prototypeType = _prototypeTypes[type];
|
||||
var res = _serializationManager.Read(prototypeType, node.ToDataNode(), skipHook: true);
|
||||
var prototype = (IPrototype) res.RawValue!;
|
||||
|
||||
if (!overwrite && _prototypes[prototypeType].ContainsKey(prototype.ID))
|
||||
{
|
||||
throw new PrototypeLoadException($"Duplicate ID: '{prototype.ID}'");
|
||||
}
|
||||
|
||||
_prototypeResults[prototypeType][prototype.ID] = res;
|
||||
if (prototype is IInheritingPrototype inheritingPrototype)
|
||||
{
|
||||
_inheritanceTrees[prototypeType].AddId(prototype.ID, inheritingPrototype.Parent, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//we call it here since it wont get called when pushing inheritance
|
||||
res.CallAfterDeserializationHook();
|
||||
}
|
||||
|
||||
_prototypes[prototypeType][prototype.ID] = prototype;
|
||||
changedPrototypes.Add(prototype);
|
||||
}
|
||||
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
public bool HasIndex<T>(string id) where T : class, IPrototype
|
||||
{
|
||||
@@ -539,19 +723,19 @@ namespace Robust.Shared.Prototypes
|
||||
/// <inheritdoc />
|
||||
public bool HasVariant(string variant)
|
||||
{
|
||||
return _types.ContainsKey(variant);
|
||||
return _prototypeTypes.ContainsKey(variant);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type GetVariantType(string variant)
|
||||
{
|
||||
return _types[variant];
|
||||
return _prototypeTypes[variant];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetVariantType(string variant, [NotNullWhen(true)] out Type? prototype)
|
||||
{
|
||||
return _types.TryGetValue(variant, out prototype);
|
||||
return _prototypeTypes.TryGetValue(variant, out prototype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -609,14 +793,14 @@ namespace Robust.Shared.Prototypes
|
||||
"No " + nameof(PrototypeAttribute) + " to give it a type string.");
|
||||
}
|
||||
|
||||
if (_types.ContainsKey(attribute.Type))
|
||||
if (_prototypeTypes.ContainsKey(attribute.Type))
|
||||
{
|
||||
throw new InvalidImplementationException(type,
|
||||
typeof(IPrototype),
|
||||
$"Duplicate prototype type ID: {attribute.Type}. Current: {_types[attribute.Type]}");
|
||||
$"Duplicate prototype type ID: {attribute.Type}. Current: {_prototypeTypes[attribute.Type]}");
|
||||
}
|
||||
|
||||
_types[attribute.Type] = type;
|
||||
_prototypeTypes[attribute.Type] = type;
|
||||
_prototypePriorities[type] = attribute.LoadPriority;
|
||||
|
||||
if (typeof(IPrototype).IsAssignableFrom(type))
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="9.1.4" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Linguini.Bundle" Version="0.1.2" />
|
||||
<PackageReference Include="Linguini.Bundle" Version="0.1.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lidgren.Network\Lidgren.Network.csproj" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Shared
|
||||
IoCManager.Register<ILocalizationManager, LocalizationManager>();
|
||||
IoCManager.Register<ILocalizationManagerInternal, LocalizationManager>();
|
||||
IoCManager.Register<ILogManager, LogManager>();
|
||||
IoCManager.Register<IPauseManager, PauseManager>();
|
||||
IoCManager.Register<IPauseManager, NetworkedMapManager>();
|
||||
IoCManager.Register<IModLoader, ModLoader>();
|
||||
IoCManager.Register<IModLoaderInternal, ModLoader>();
|
||||
IoCManager.Register<INetManager, NetManager>();
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Shared.Timing
|
||||
{
|
||||
[Obsolete("Use the same functions on IMapManager.")]
|
||||
public interface IPauseManager
|
||||
{
|
||||
void SetMapPaused(MapId mapId, bool paused);
|
||||
|
||||
void DoMapInitialize(MapId mapId);
|
||||
|
||||
[Obsolete("This function does nothing, per-grid pausing isn't a thing anymore.")]
|
||||
void DoGridMapInitialize(GridId gridId);
|
||||
|
||||
[Obsolete("This function does nothing, per-grid pausing isn't a thing anymore.")]
|
||||
void DoGridMapInitialize(IMapGrid grid);
|
||||
|
||||
void AddUninitializedMap(MapId mapId);
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterPrototypes(protoMan => protoMan.LoadFromString(Prototypes))
|
||||
.RegisterPrototypes(protoMan => protoMan.LoadString(Prototypes))
|
||||
.InitializeInstance();
|
||||
|
||||
// Adds the map with id 1, and spawns entity 1 as the map entity.
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
var manager = IoCManager.Resolve<IPrototypeManager>();
|
||||
manager.RegisterType(typeof(EntityPrototype));
|
||||
manager.LoadFromString(PROTOTYPES);
|
||||
manager.LoadFromStream(new StringReader(PROTOTYPES));
|
||||
manager.Resync();
|
||||
|
||||
// build the net dream
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Robust.UnitTesting.Server.GameObjects
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
var manager = IoCManager.Resolve<IPrototypeManager>();
|
||||
manager.RegisterType(typeof(EntityPrototype));
|
||||
manager.LoadFromString(PROTOTYPES);
|
||||
manager.LoadFromStream(new StringReader(PROTOTYPES));
|
||||
manager.Resync();
|
||||
|
||||
//NOTE: The grids have not moved, so we can assert worldpos == localpos for the test
|
||||
|
||||
@@ -207,7 +207,7 @@ namespace Robust.UnitTesting.Server
|
||||
container.Register<IIslandManager, IslandManager>();
|
||||
container.Register<IManifoldManager, CollisionManager>();
|
||||
container.Register<IMapManagerInternal, MapManager>();
|
||||
container.RegisterInstance<IPauseManager>(new Mock<IPauseManager>().Object); // TODO: get timing working similar to RobustIntegrationTest
|
||||
container.Register<IPauseManager, MapManager>();
|
||||
container.Register<IPhysicsManager, PhysicsManager>();
|
||||
|
||||
_diFactory?.Invoke(container);
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterPrototypes(protoMan => protoMan.LoadFromString(PROTOTYPE))
|
||||
.RegisterPrototypes(protoMan => protoMan.LoadString(PROTOTYPE))
|
||||
.InitializeInstance();
|
||||
|
||||
var mapManager = sim.Resolve<IMapManager>();
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
|
||||
.NewSimulation()
|
||||
.RegisterPrototypes(f=>
|
||||
{
|
||||
f.LoadFromString(Prototypes);
|
||||
f.LoadString(Prototypes);
|
||||
})
|
||||
.InitializeInstance();
|
||||
|
||||
|
||||
98
Robust.UnitTesting/Shared/Localization/FormatErrorTest.cs
Normal file
98
Robust.UnitTesting/Shared/Localization/FormatErrorTest.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using Linguini.Syntax.Parser.Error;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Localization;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable]
|
||||
public sealed class TestFormatErrors
|
||||
{
|
||||
private const string Res1 = "err1 = $user)";
|
||||
private const string Res2 = "a = b {{\r\n err = x";
|
||||
private const string Res2Lf = "a = b {{\n err = x";
|
||||
|
||||
#region ExpectedCrlf
|
||||
|
||||
private const string Expect1Wide1Crlf = "\r\n 99 |err1 = $user)\r\n ^ Unbalanced closing brace";
|
||||
|
||||
private const string Expect2Wide1Crlf = "\r\n 99 |err1 = $user)\r\n ^ Unbalanced closing brace";
|
||||
|
||||
private const string Expect1Wide4Crlf =
|
||||
"\r\n 99 |err1 = $user)\r\n ^^^^ Expected a message field for \"x\"";
|
||||
|
||||
private const string Expect2Wide4Crlf = "\r\n 99 |err1 = $user)\r\n ^^^^ Expected a message field for \"x\"";
|
||||
|
||||
|
||||
private const string ExpectMulti1Wide1Crlf =
|
||||
"\r\n 99 |a = b {{\r\n 100 | err = x\r\n ^ Unbalanced closing brace";
|
||||
|
||||
private const string ExpectMulti1Wide3Crlf =
|
||||
"\r\n 99 |a = b {{\r\n 100 | err = x\r\n ^^^ Expected a message field for \"x\"";
|
||||
|
||||
#endregion
|
||||
|
||||
[Test]
|
||||
[TestCase(Expect1Wide1Crlf, Res1, 7)]
|
||||
[TestCase(Expect2Wide1Crlf, Res1, 0)]
|
||||
[TestCase(Expect1Wide4Crlf, Res1, 8, 12)]
|
||||
[TestCase(Expect2Wide4Crlf, Res1, 0, 4)]
|
||||
[TestCase(ExpectMulti1Wide1Crlf, Res2, 12)]
|
||||
[TestCase(ExpectMulti1Wide3Crlf, Res2, 12, 15)]
|
||||
[TestCase(ExpectMulti1Wide1Crlf, Res2Lf, 11)]
|
||||
[TestCase(ExpectMulti1Wide3Crlf, Res2Lf, 11, 14)]
|
||||
public void TestSingleLineTestCrlf(string expected, string resource, int start, int? end = null)
|
||||
{
|
||||
var err = ParseError.UnbalancedClosingBrace(start, 99);
|
||||
if (end != null)
|
||||
{
|
||||
err = ParseError.ExpectedMessageField("x".AsMemory(), start, end.Value, 99);
|
||||
}
|
||||
|
||||
err.Slice = new Range(0, resource.Length);
|
||||
var actual = err.FormatCompileErrors(resource.AsMemory(), "\r\n");
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
#region ExpectedLf
|
||||
|
||||
private const string Expect1Wide1Lf = "\n 99 |err1 = $user)\n ^ Unbalanced closing brace";
|
||||
|
||||
private const string Expect2Wide1Lf = "\n 99 |err1 = $user)\n ^ Unbalanced closing brace";
|
||||
|
||||
private const string Expect1Wide4Lf =
|
||||
"\n 99 |err1 = $user)\n ^^^^ Expected a message field for \"x\"";
|
||||
|
||||
private const string Expect2Wide4Lf = "\n 99 |err1 = $user)\n ^^^^ Expected a message field for \"x\"";
|
||||
|
||||
|
||||
private const string ExpectMulti1Wide1Lf = "\n 99 |a = b {{\n 100 | err = x\n ^ Unbalanced closing brace";
|
||||
|
||||
private const string ExpectMulti1Wide3Lf =
|
||||
"\n 99 |a = b {{\n 100 | err = x\n ^^^ Expected a message field for \"x\"";
|
||||
|
||||
#endregion
|
||||
|
||||
[Test]
|
||||
[TestCase(Expect1Wide1Lf, Res1, 7)]
|
||||
[TestCase(Expect2Wide1Lf, Res1, 0)]
|
||||
[TestCase(Expect1Wide4Lf, Res1, 8, 12)]
|
||||
[TestCase(Expect2Wide4Lf, Res1, 0, 4)]
|
||||
[TestCase(ExpectMulti1Wide1Lf, Res2, 12)]
|
||||
[TestCase(ExpectMulti1Wide3Lf, Res2, 12, 15)]
|
||||
[TestCase(ExpectMulti1Wide1Lf, Res2Lf, 11)]
|
||||
[TestCase(ExpectMulti1Wide3Lf, Res2Lf, 11, 14)]
|
||||
public void TestSingleLineTestLf(string expected, string resource, int start, int? end = null)
|
||||
{
|
||||
var err = ParseError.UnbalancedClosingBrace(start, 99);
|
||||
if (end != null)
|
||||
{
|
||||
err = ParseError.ExpectedMessageField("x".AsMemory(), start, end.Value, 99);
|
||||
}
|
||||
|
||||
err.Slice = new Range(0, resource.Length);
|
||||
var actual = err.FormatCompileErrors(resource.AsMemory(), "\n");
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
prototypeManager.RegisterType(typeof(EntityPrototype));
|
||||
prototypeManager.LoadFromString(PROTOTYPES);
|
||||
prototypeManager.LoadFromStream(new StringReader(PROTOTYPES));
|
||||
prototypeManager.Resync();
|
||||
|
||||
var factory = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
@@ -8,44 +8,37 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using MapGrid = Robust.Shared.Map.MapGrid;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
[TestFixture, TestOf(typeof(MapGrid))]
|
||||
sealed class MapGrid_Tests : RobustUnitTest
|
||||
sealed class MapGrid_Tests
|
||||
{
|
||||
protected override void OverrideIoC()
|
||||
private static ISimulation SimulationFactory()
|
||||
{
|
||||
base.OverrideIoC();
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.InitializeInstance();
|
||||
|
||||
var mock = new Mock<IEntitySystemManager>();
|
||||
var broady = new BroadPhaseSystem();
|
||||
var physics = new PhysicsSystem();
|
||||
mock.Setup(m => m.GetEntitySystem<SharedBroadphaseSystem>()).Returns(broady);
|
||||
mock.Setup(m => m.GetEntitySystem<SharedPhysicsSystem>()).Returns(physics);
|
||||
|
||||
IoCManager.RegisterInstance<IEntitySystemManager>(mock.Object, true);
|
||||
}
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().GenerateNetIds();
|
||||
return sim;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetTileRefCoords()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
var sim = SimulationFactory();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
|
||||
grid.SetTile(new Vector2i(-9, -1), new Tile(1, 2));
|
||||
|
||||
var result = grid.GetTileRef(new Vector2i(-9, -1));
|
||||
|
||||
Assert.That(grid.ChunkCount, Is.EqualTo(1));
|
||||
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1)));
|
||||
Assert.That(result, Is.EqualTo(new TileRef(new MapId(5), new GridId(1), new Vector2i(-9,-1), new Tile(1, 2))));
|
||||
Assert.That(result, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9,-1), new Tile(1, 2))));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,7 +47,11 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
[Test]
|
||||
public void BoundsExpansion()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
var sim = SimulationFactory();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
grid.WorldPosition = new Vector2(3, 5);
|
||||
|
||||
grid.SetTile(new Vector2i(-1, -2), new Tile(1));
|
||||
grid.SetTile(new Vector2i(1, 2), new Tile(1));
|
||||
@@ -74,7 +71,11 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
[Test]
|
||||
public void BoundsContract()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
var sim = SimulationFactory();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
grid.WorldPosition = new Vector2(3, 5);
|
||||
|
||||
grid.SetTile(new Vector2i(-1, -2), new Tile(1));
|
||||
grid.SetTile(new Vector2i(1, 2), new Tile(1));
|
||||
@@ -93,7 +94,10 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
[Test]
|
||||
public void GridTileToChunkIndices()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
var sim = SimulationFactory();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
|
||||
var result = grid.GridTileToChunkIndices(new Vector2i(-9, -1));
|
||||
|
||||
@@ -106,7 +110,10 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
[Test]
|
||||
public void ToLocalCentered()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
var sim = SimulationFactory();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
|
||||
var result = grid.GridTileToLocal(new Vector2i(0, 0)).Position;
|
||||
|
||||
@@ -117,7 +124,10 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
[Test]
|
||||
public void TryGetTileRefNoTile()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
var sim = SimulationFactory();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
|
||||
var foundTile = grid.TryGetTileRef(new Vector2i(-9, -1), out var tileRef);
|
||||
|
||||
@@ -129,7 +139,11 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
[Test]
|
||||
public void TryGetTileRefTileExists()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
var sim = SimulationFactory();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
|
||||
grid.SetTile(new Vector2i(-9, -1), new Tile(1, 2));
|
||||
|
||||
var foundTile = grid.TryGetTileRef(new Vector2i(-9, -1), out var tileRef);
|
||||
@@ -137,13 +151,17 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
Assert.That(foundTile, Is.True);
|
||||
Assert.That(grid.ChunkCount, Is.EqualTo(1));
|
||||
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1)));
|
||||
Assert.That(tileRef, Is.EqualTo(new TileRef(new MapId(5), new GridId(1), new Vector2i(-9, -1), new Tile(1, 2))));
|
||||
Assert.That(tileRef, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9, -1), new Tile(1, 2))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PointCollidesWithGrid()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
var sim = SimulationFactory();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
|
||||
grid.SetTile(new Vector2i(19, 23), new Tile(1));
|
||||
|
||||
var result = grid.CollidesWithGrid(new Vector2i(19, 23));
|
||||
@@ -154,31 +172,16 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
[Test]
|
||||
public void PointNotCollideWithGrid()
|
||||
{
|
||||
var grid = MapGridFactory(new GridId(1));
|
||||
var sim = SimulationFactory();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
|
||||
|
||||
grid.SetTile(new Vector2i(19, 23), new Tile(1));
|
||||
|
||||
var result = grid.CollidesWithGrid(new Vector2i(19, 24));
|
||||
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
private static IMapGridInternal MapGridFactory(GridId id)
|
||||
{
|
||||
var mapId = new MapId(5);
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
if(mapMan.MapExists(mapId))
|
||||
mapMan.DeleteMap(mapId);
|
||||
|
||||
mapMan.CreateMap(mapId);
|
||||
|
||||
if(mapMan.GridExists(id))
|
||||
mapMan.DeleteGrid(id);
|
||||
|
||||
var newGrid = mapMan.CreateGrid(mapId, id, 8);
|
||||
newGrid.WorldPosition = new Vector2(3, 5);
|
||||
|
||||
return (IMapGridInternal)newGrid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
272
Robust.UnitTesting/Shared/Map/MapPauseTests.cs
Normal file
272
Robust.UnitTesting/Shared/Map/MapPauseTests.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
// ReSharper disable AccessToStaticMemberViaDerivedType
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Map;
|
||||
|
||||
[TestFixture]
|
||||
internal sealed class MapPauseTests
|
||||
{
|
||||
private static ISimulation SimulationFactory()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(factory => factory.RegisterClass<IgnorePauseComponent>())
|
||||
.InitializeInstance();
|
||||
|
||||
return sim;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When an entity is on a paused map, it does not get returned by an EntityQuery.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Paused_NotIncluded_NotInQuery()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
// arrange
|
||||
var mapId = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(mapId, true);
|
||||
|
||||
entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
|
||||
var query = entMan.EntityQuery<TransformComponent>(false).ToList();
|
||||
|
||||
// 0 ents, map and the spawned one are not returned
|
||||
Assert.That(query.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When an entity is on an unpaused map, it is returned by an EntityQuery.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnPaused_NotIncluded_InQuery()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
// arrange
|
||||
var mapId = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(mapId, false);
|
||||
|
||||
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
|
||||
var query = entMan.EntityQuery<TransformComponent>(false).ToList();
|
||||
|
||||
// 2 ents, map and the spawned one
|
||||
Assert.That(query.Count, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When an entity is on a paused map, it is get returned by an EntityQuery when included.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Paused_Included_InQuery()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
// arrange
|
||||
var mapId = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(mapId, true);
|
||||
|
||||
entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
|
||||
var query = entMan.EntityQuery<TransformComponent>(true).ToList();
|
||||
|
||||
// 2 ents, map and the spawned one are returned because includePaused
|
||||
Assert.That(query.Count, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new child entity added to a paused map will be created paused.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Paused_AddEntity_IsPaused()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
// arrange
|
||||
var mapId = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(mapId, true);
|
||||
|
||||
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
|
||||
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
|
||||
Assert.That(metaData.EntityPaused, Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new child entity added to an unpaused map will be created unpaused.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnPaused_AddEntity_IsNotPaused()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
// arrange
|
||||
var mapId = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(mapId, false);
|
||||
|
||||
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
|
||||
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
|
||||
Assert.That(metaData.EntityPaused, Is.False);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a new grid is added to a paused map, the grid becomes paused.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Paused_AddGrid_GridPaused()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
// arrange
|
||||
var mapId = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(mapId, true);
|
||||
|
||||
// act
|
||||
var newGrid = mapMan.CreateGrid(mapId);
|
||||
|
||||
// assert
|
||||
Assert.That(mapMan.IsMapPaused(mapId), Is.True);
|
||||
Assert.That(mapMan.IsGridPaused(newGrid.GridEntityId), Is.True);
|
||||
|
||||
var metaData = entMan.GetComponent<MetaDataComponent>(newGrid.GridEntityId);
|
||||
Assert.That(metaData.EntityPaused, Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a tree of entities are teleported from a paused map
|
||||
/// to an unpaused map, all of the entities in the tree are unpaused.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Paused_TeleportBetweenMaps_Unpaused()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
// arrange
|
||||
var map1 = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(map1, true);
|
||||
|
||||
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, map1));
|
||||
var xform = entMan.GetComponent<TransformComponent>(newEnt);
|
||||
|
||||
var map2 = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(map2, false);
|
||||
|
||||
// Act
|
||||
xform.ParentUid = mapMan.GetMapEntityId(map2);
|
||||
|
||||
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
|
||||
Assert.That(metaData.EntityPaused, Is.False);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a tree of entities are teleported from an unpaused map
|
||||
/// to a paused map, all of the entitites in the tree are paused.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Unpaused_TeleportBetweenMaps_IsPaused()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
// arrange
|
||||
var map1 = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(map1, false);
|
||||
|
||||
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, map1));
|
||||
var xform = entMan.GetComponent<TransformComponent>(newEnt);
|
||||
|
||||
var map2 = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(map2, true);
|
||||
|
||||
// Act
|
||||
xform.ParentUid = mapMan.GetMapEntityId(map2);
|
||||
|
||||
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
|
||||
Assert.That(metaData.EntityPaused, Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a paused map is unpaused, all of the entities on the map are unpaused.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Paused_UnpauseMap_UnpausedEntities()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
var mapId = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(mapId, true);
|
||||
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
|
||||
mapMan.SetMapPaused(mapId, false);
|
||||
|
||||
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
|
||||
Assert.That(metaData.EntityPaused, Is.False);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When an unpaused map is paused, all of the entities on the map are paused.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Unpaused_PauseMap_PausedEntities()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
var mapId = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(mapId, false);
|
||||
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
|
||||
mapMan.SetMapPaused(mapId, true);
|
||||
|
||||
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
|
||||
Assert.That(metaData.EntityPaused, Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An entity that has set IgnorePause will not be paused when the map is paused.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void IgnorePause_PauseMap_NotPaused()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
|
||||
var mapId = mapMan.CreateMap();
|
||||
mapMan.SetMapPaused(mapId, false);
|
||||
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
|
||||
entMan.AddComponent<IgnorePauseComponent>(newEnt);
|
||||
mapMan.SetMapPaused(mapId, true);
|
||||
|
||||
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
|
||||
Assert.That(metaData.EntityPaused, Is.False);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -48,7 +47,7 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
_prototypes = (PrototypeManager) IoCManager.Resolve<IPrototypeManager>();
|
||||
_prototypes.RegisterType(typeof(EntityPrototype));
|
||||
_prototypes.LoadFromString(InitialPrototypes);
|
||||
_prototypes.LoadString(InitialPrototypes);
|
||||
_prototypes.Resync();
|
||||
|
||||
_maps = IoCManager.Resolve<IMapManager>();
|
||||
@@ -76,8 +75,8 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
Assert.That(entityComponent.Value, Is.EqualTo(5));
|
||||
Assert.False(IoCManager.Resolve<IEntityManager>().HasComponent<HotReloadTestTwoComponent>(entity));
|
||||
|
||||
var changedPrototypes = _prototypes.LoadFromString(ReloadedPrototypes, true);
|
||||
_prototypes.ReloadPrototypes(changedPrototypes.ToList());
|
||||
var changedPrototypes = _prototypes.LoadString(ReloadedPrototypes, true);
|
||||
_prototypes.ReloadPrototypes(changedPrototypes);
|
||||
|
||||
Assert.True(reloaded);
|
||||
reloaded = false;
|
||||
@@ -88,8 +87,8 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
// New components are added
|
||||
Assert.True(IoCManager.Resolve<IEntityManager>().HasComponent<HotReloadTestTwoComponent>(entity));
|
||||
|
||||
changedPrototypes = _prototypes.LoadFromString(InitialPrototypes, true);
|
||||
_prototypes.ReloadPrototypes(changedPrototypes.ToList());
|
||||
changedPrototypes = _prototypes.LoadString(InitialPrototypes, true);
|
||||
_prototypes.ReloadPrototypes(changedPrototypes);
|
||||
|
||||
Assert.True(reloaded);
|
||||
reloaded = false;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
manager = IoCManager.Resolve<IPrototypeManager>();
|
||||
manager.RegisterType(typeof(EntityPrototype));
|
||||
manager.LoadFromString(DOCUMENT);
|
||||
manager.LoadString(DOCUMENT);
|
||||
manager.Resync();
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
[Test]
|
||||
public void TestLoadString()
|
||||
{
|
||||
manager.LoadFromString(LoadStringDocument);
|
||||
manager.LoadString(LoadStringDocument);
|
||||
manager.Resync();
|
||||
|
||||
var prototype = manager.Index<EntityPrototype>(LoadStringTestDummyId);
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Robust.UnitTesting.Shared.Serialization
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
prototypeManager.RegisterType(typeof(EntityPrototype));
|
||||
prototypeManager.LoadFromString(Prototypes);
|
||||
prototypeManager.LoadString(Prototypes);
|
||||
prototypeManager.Resync();
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
@@ -46,7 +46,7 @@ entitiesImmutableList:
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
protoMan.RegisterType(typeof(EntityPrototype));
|
||||
protoMan.LoadFromString(Prototypes);
|
||||
protoMan.LoadString(Prototypes);
|
||||
protoMan.Resync();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user