mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3caffa04da | ||
|
|
06b377d1d5 | ||
|
|
41fb191dda | ||
|
|
d4bcc1dc05 | ||
|
|
84dcd658aa | ||
|
|
a634d6bd04 | ||
|
|
36f9df3079 | ||
|
|
824c018a69 | ||
|
|
4b6b688c72 | ||
|
|
71df25b251 | ||
|
|
be14a3c249 | ||
|
|
3c2a4d5c79 | ||
|
|
44180b3ee0 | ||
|
|
bb0e77e937 | ||
|
|
684b9bc852 | ||
|
|
9f3db6693e | ||
|
|
40d869948d | ||
|
|
5c97b15849 | ||
|
|
3d8a9a41fa | ||
|
|
92fc8722da | ||
|
|
73f6555624 | ||
|
|
2ac7bc3ce4 | ||
|
|
05cb4bb1c9 | ||
|
|
a393efc87a | ||
|
|
4d47cfa1a6 | ||
|
|
2b1d755d9f |
@@ -57,7 +57,7 @@
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,83 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 260.2.2
|
||||
|
||||
|
||||
## 260.2.1
|
||||
|
||||
|
||||
## 260.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add `StringBuilder.Insert(int, string)` to sandbox.
|
||||
* Add the WorldNormal to the StartCollideEvent.
|
||||
|
||||
|
||||
## 260.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `ComponentFactory` is now exposed to `EntitySystem` as `Factory`
|
||||
|
||||
### Other
|
||||
|
||||
* Cleanup warnings in PLacementManager
|
||||
* Cleanup warnings in Clide.Sprite
|
||||
|
||||
## 260.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Fix / change `StartCollideEvent.WorldPoint` to return all points for the collision which may be up to 2 instead of 1.
|
||||
|
||||
### New features
|
||||
|
||||
* Add SpriteSystem dependency to VisualizerSystem.
|
||||
* Add Vertical property to progress bars
|
||||
* Add some `EntProtoId` overloads for group entity spawn methods.
|
||||
|
||||
|
||||
## 259.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* TileChangedEvent now has an array of tile changed entries rather than raising an individual event for every single tile changed.
|
||||
|
||||
### Other
|
||||
|
||||
* `Entity<T>` methods were marked as `readonly` as appropriate.
|
||||
|
||||
|
||||
## 258.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix static physics bodies not generating contacts if they spawn onto sleeping bodies.
|
||||
|
||||
|
||||
## 258.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `IMarkupTag` and related methods in `MarkupTagManager` have been obsoleted and should be replaced with the new `IMarkupTagHandler` interface. Various engine tags (e.g., `BoldTag`, `ColorTag`, etc) no longer implement the old interface.
|
||||
|
||||
### New features
|
||||
|
||||
* Add IsValidPath to ResPath and make some minor performance improvements.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* OutputPanel and RichTextLabel now remove controls associated with rich text tags when the text is updated.
|
||||
* Fix `SpriteComponent.Visible` datafield not being read from yaml.
|
||||
* Fix container state handling not forcing inserts.
|
||||
|
||||
### Other
|
||||
|
||||
* `SpriteSystem.LayerMapReserve()` no longer throws an exception if the specified layer already exists. This makes it behave like the obsoleted `SpriteComponent.LayerMapReserveBlank()`.
|
||||
|
||||
|
||||
## 257.0.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -387,7 +387,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace Robust.Client.GameObjects
|
||||
[RegisterComponent]
|
||||
public sealed partial class SpriteComponent : Component, IComponentDebug, ISerializationHooks, IComponentTreeEntry<SpriteComponent>, IAnimationProperties
|
||||
{
|
||||
#region ECSd
|
||||
public const string LogCategory = "go.comp.sprite";
|
||||
|
||||
[Dependency] private readonly IResourceCache resourceCache = default!;
|
||||
@@ -59,12 +58,13 @@ namespace Robust.Client.GameObjects
|
||||
[DataField] // TODO Sprite access restrict.
|
||||
public bool GranularLayersRendering = false;
|
||||
|
||||
[DataField]
|
||||
[DataField("visible")]
|
||||
internal bool _visible = true;
|
||||
|
||||
// VV convenience variable to examine layer objects using layer keys
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
[ViewVariables]
|
||||
private Dictionary<object, Layer> _mappedLayers => LayerMap.ToDictionary(x => x.Key, x => Layers[x.Value]);
|
||||
private Dictionary<object, Layer> MappedLayers => LayerMap.ToDictionary(x => x.Key, x => Layers[x.Value]);
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Visible
|
||||
@@ -93,7 +93,7 @@ namespace Robust.Client.GameObjects
|
||||
set => Sys.SetDrawDepth((Owner, this), value);
|
||||
}
|
||||
|
||||
[DataField]
|
||||
[DataField("scale")] // Explicit name, in case this field ever gets renamed
|
||||
internal Vector2 scale = Vector2.One;
|
||||
|
||||
/// <summary>
|
||||
@@ -108,7 +108,7 @@ namespace Robust.Client.GameObjects
|
||||
set => Sys.SetScale((Owner, this), value);
|
||||
}
|
||||
|
||||
[DataField]
|
||||
[DataField("rotation")] // Explicit name, in case this field ever gets renamed
|
||||
internal Angle rotation = Angle.Zero;
|
||||
|
||||
[Animatable]
|
||||
@@ -120,7 +120,7 @@ namespace Robust.Client.GameObjects
|
||||
set => Sys.SetRotation((Owner, this), value);
|
||||
}
|
||||
|
||||
[DataField]
|
||||
[DataField("offset")] // Explicit name, in case this field ever gets renamed
|
||||
internal Vector2 offset = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
@@ -135,7 +135,7 @@ namespace Robust.Client.GameObjects
|
||||
set => Sys.SetOffset((Owner, this), value);
|
||||
}
|
||||
|
||||
[DataField]
|
||||
[DataField("color")] // Explicit name, in case this field ever gets renamed
|
||||
internal Color color = Color.White;
|
||||
|
||||
[Animatable]
|
||||
@@ -1052,8 +1052,6 @@ namespace Robust.Client.GameObjects
|
||||
return Sys.CalculateBounds((Owner, this), worldPosition, worldRotation, eyeRot);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Enum to "offset" a cardinal direction.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Shared.Containers.ContainerManagerComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -58,7 +57,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!RemoveExpectedEntity(meta.NetEntity, out var container))
|
||||
return;
|
||||
|
||||
Insert((uid, TransformQuery.GetComponent(uid), MetaQuery.GetComponent(uid), null), container);
|
||||
Insert((uid, TransformQuery.GetComponent(uid), MetaQuery.GetComponent(uid), null), container, force: true);
|
||||
}
|
||||
|
||||
public override void ShutdownContainer(BaseContainer container)
|
||||
@@ -232,7 +231,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
Insert(message.Entity, container);
|
||||
Insert(message.Entity, container, force: true);
|
||||
}
|
||||
|
||||
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
|
||||
|
||||
@@ -213,32 +213,30 @@ public sealed partial class SpriteSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new blank layer and map the given key to it.
|
||||
/// Ensures that a layer with the given key exists and return the layer's index.
|
||||
/// If the layer does not yet exist, this will create and add a blank layer.
|
||||
/// </summary>
|
||||
public int LayerMapReserve(Entity<SpriteComponent?> sprite, Enum key)
|
||||
{
|
||||
if (!_query.Resolve(sprite.Owner, ref sprite.Comp))
|
||||
return -1;
|
||||
|
||||
if (LayerExists(sprite, key))
|
||||
throw new Exception("Layer already exists");
|
||||
if (LayerMapTryGet(sprite, key, out var layerIndex, false))
|
||||
return layerIndex;
|
||||
|
||||
var layer = AddBlankLayer(sprite!);
|
||||
LayerMapSet(sprite, key, layer.Index);
|
||||
return layer.Index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A create a new blank layer and map the given key to it. If possible, it is preferred to use an enum key.
|
||||
/// string keys mainly exist to make it easier to define custom layer keys in yaml.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="LayerMapReserve(Entity{SpriteComponent?},System.Enum)"/>
|
||||
public int LayerMapReserve(Entity<SpriteComponent?> sprite, string key)
|
||||
{
|
||||
if (!_query.Resolve(sprite.Owner, ref sprite.Comp))
|
||||
return -1;
|
||||
|
||||
if (LayerExists(sprite, key))
|
||||
throw new Exception("Layer already exists");
|
||||
if (LayerMapTryGet(sprite, key, out var layerIndex, false))
|
||||
return layerIndex;
|
||||
|
||||
var layer = AddBlankLayer(sprite!);
|
||||
LayerMapSet(sprite, key, layer.Index);
|
||||
|
||||
@@ -11,6 +11,7 @@ public abstract class VisualizerSystem<T> : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly AppearanceSystem AppearanceSystem = default!;
|
||||
[Dependency] protected readonly AnimationPlayerSystem AnimationSystem = default!;
|
||||
[Dependency] protected readonly SpriteSystem SpriteSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -412,8 +412,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void _updateTileMapOnUpdate(ref TileChangedEvent args)
|
||||
{
|
||||
var gridData = _mapChunkData.GetOrNew(args.Entity);
|
||||
if (gridData.TryGetValue(args.ChunkIndex, out var data))
|
||||
data.Dirty = true;
|
||||
foreach (var change in args.Changes)
|
||||
{
|
||||
if (gridData.TryGetValue(change.ChunkIndex, out var data))
|
||||
data.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void _updateOnGridCreated(GridStartupEvent ev)
|
||||
|
||||
@@ -153,7 +153,7 @@ internal partial class Clyde
|
||||
|
||||
// special casing angle = n*pi/2 to avoid box rotation & bounding calculations doesn't seem to give significant speedups.
|
||||
data.SpriteScreenBB = TransformCenteredBox(
|
||||
data.Sprite.Bounds,
|
||||
_spriteSystem.GetLocalBounds((data.Uid, data.Sprite)),
|
||||
finalRotation,
|
||||
pos + batch.PreScaleViewOffset,
|
||||
batch.ViewScale);
|
||||
|
||||
@@ -10,6 +10,7 @@ internal sealed partial class Clyde
|
||||
private MapSystem _mapSystem = default!;
|
||||
private LightTreeSystem _lightTreeSystem = default!;
|
||||
private TransformSystem _transformSystem = default!;
|
||||
private SpriteSystem _spriteSystem = default!;
|
||||
private SpriteTreeSystem _spriteTreeSystem = default!;
|
||||
private ClientOccluderSystem _occluderSystem = default!;
|
||||
|
||||
@@ -24,6 +25,7 @@ internal sealed partial class Clyde
|
||||
_mapSystem = _entitySystemManager.GetEntitySystem<MapSystem>();
|
||||
_lightTreeSystem = _entitySystemManager.GetEntitySystem<LightTreeSystem>();
|
||||
_transformSystem = _entitySystemManager.GetEntitySystem<TransformSystem>();
|
||||
_spriteSystem = _entitySystemManager.GetEntitySystem<SpriteSystem>();
|
||||
_spriteTreeSystem = _entitySystemManager.GetEntitySystem<SpriteTreeSystem>();
|
||||
_occluderSystem = _entitySystemManager.GetEntitySystem<ClientOccluderSystem>();
|
||||
}
|
||||
@@ -33,6 +35,7 @@ internal sealed partial class Clyde
|
||||
_mapSystem = null!;
|
||||
_lightTreeSystem = null!;
|
||||
_transformSystem = null!;
|
||||
_spriteSystem = null!;
|
||||
_spriteTreeSystem = null!;
|
||||
_occluderSystem = null!;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
private SharedMapSystem Maps => EntityManager.System<SharedMapSystem>();
|
||||
private SharedTransformSystem XformSystem => EntityManager.System<SharedTransformSystem>();
|
||||
private SpriteSystem Sprite => EntityManager.System<SpriteSystem>();
|
||||
|
||||
/// <summary>
|
||||
/// How long before a pending tile change is dropped.
|
||||
@@ -359,12 +360,15 @@ namespace Robust.Client.Placement
|
||||
|
||||
private void HandleTileChanged(ref TileChangedEvent args)
|
||||
{
|
||||
var coords = Maps.GridTileToLocal(
|
||||
args.NewTile.GridUid,
|
||||
EntityManager.GetComponent<MapGridComponent>(args.NewTile.GridUid),
|
||||
args.NewTile.GridIndices);
|
||||
foreach (var change in args.Changes)
|
||||
{
|
||||
var coords = Maps.GridTileToLocal(
|
||||
args.Entity,
|
||||
args.Entity.Comp,
|
||||
change.GridIndices);
|
||||
|
||||
_pendingTileChanges.RemoveAll(c => c.Item1 == coords);
|
||||
_pendingTileChanges.RemoveAll(c => c.Item1 == coords);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -708,11 +712,11 @@ namespace Robust.Client.Placement
|
||||
CurrentPlacementOverlayEntity = null;
|
||||
}
|
||||
|
||||
private SpriteComponent SetupPlacementOverlayEntity()
|
||||
private Entity<SpriteComponent> SetupPlacementOverlayEntity()
|
||||
{
|
||||
EnsureNoPlacementOverlayEntity();
|
||||
CurrentPlacementOverlayEntity = EntityManager.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
return EntityManager.EnsureComponent<SpriteComponent>(CurrentPlacementOverlayEntity.Value);
|
||||
return (CurrentPlacementOverlayEntity.Value, EntityManager.EnsureComponent<SpriteComponent>(CurrentPlacementOverlayEntity.Value));
|
||||
}
|
||||
|
||||
private void PreparePlacement(string templateName)
|
||||
@@ -729,10 +733,16 @@ namespace Robust.Client.Placement
|
||||
EntityManager.GetComponent<MetaDataComponent>(CurrentPlacementOverlayEntity.Value));
|
||||
}
|
||||
|
||||
public void PreparePlacementSprite(SpriteComponent sprite)
|
||||
public void PreparePlacementSprite(Entity<SpriteComponent> sprite)
|
||||
{
|
||||
var sc = SetupPlacementOverlayEntity();
|
||||
sc.CopyFrom(sprite);
|
||||
Sprite.CopySprite(sprite.AsNullable(), sc.AsNullable());
|
||||
}
|
||||
|
||||
[Obsolete("Use the Entity<SpriteComponent> overload.")]
|
||||
public void PreparePlacementSprite(SpriteComponent sprite)
|
||||
{
|
||||
PreparePlacementSprite((sprite.Owner, sprite));
|
||||
}
|
||||
|
||||
public void PreparePlacementTexList(List<IDirectionalTextureProvider>? texs, bool noRot, EntityPrototype? prototype)
|
||||
@@ -743,27 +753,27 @@ namespace Robust.Client.Placement
|
||||
// This one covers most cases (including Construction)
|
||||
foreach (var v in texs)
|
||||
{
|
||||
if (v is RSI.State)
|
||||
if (v is RSI.State st)
|
||||
{
|
||||
var st = (RSI.State) v;
|
||||
sc.AddLayer(st.StateId, st.RSI);
|
||||
Sprite.AddRsiLayer(sc.AsNullable(), st.StateId, st.RSI);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback
|
||||
sc.AddLayer(v.Default);
|
||||
Sprite.AddTextureLayer(sc.AsNullable(), v.Default);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sc.AddLayer(new ResPath("/Textures/Interface/tilebuildoverlay.png"));
|
||||
Sprite.AddTextureLayer(sc.AsNullable(), new ResPath("/Textures/Interface/tilebuildoverlay.png"));
|
||||
}
|
||||
sc.NoRotation = noRot;
|
||||
|
||||
sc.Comp.NoRotation = noRot;
|
||||
|
||||
if (prototype != null && prototype.TryGetComponent<SpriteComponent>("Sprite", out var spriteComp))
|
||||
{
|
||||
sc.Scale = spriteComp.Scale;
|
||||
Sprite.SetScale(sc.AsNullable(), spriteComp.Scale);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -771,7 +781,7 @@ namespace Robust.Client.Placement
|
||||
private void PreparePlacementTile()
|
||||
{
|
||||
var sc = SetupPlacementOverlayEntity();
|
||||
sc.AddLayer(new ResPath("/Textures/Interface/tilebuildoverlay.png"));
|
||||
Sprite.AddTextureLayer(sc.AsNullable(), new ResPath("/Textures/Interface/tilebuildoverlay.png"));
|
||||
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ namespace Robust.Client.Player
|
||||
{
|
||||
if (_client.RunLevel != ClientRunLevel.SinglePlayerGame)
|
||||
Sawmill.Warning($"Attaching local player to an entity {EntManager.ToPrettyString(uid)} without an eye. This eye will not be netsynced and may cause issues.");
|
||||
var eye = (EyeComponent) Factory.GetComponent(typeof(EyeComponent));
|
||||
var eye = Factory.GetComponent<EyeComponent>();
|
||||
eye.NetSyncEnabled = false;
|
||||
EntManager.AddComponent(uid.Value, eye);
|
||||
}
|
||||
|
||||
@@ -95,6 +95,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public void Clear()
|
||||
{
|
||||
_firstLine = true;
|
||||
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
entry.RemoveControls();
|
||||
}
|
||||
|
||||
_entries.Clear();
|
||||
_totalContentHeight = 0;
|
||||
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
|
||||
@@ -104,6 +110,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public void RemoveEntry(Index index)
|
||||
{
|
||||
var entry = _entries[index];
|
||||
entry.RemoveControls();
|
||||
_entries.RemoveAt(index.GetOffset(_entries.Count));
|
||||
|
||||
var font = _getFont();
|
||||
@@ -189,6 +196,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (entryOffset > contentBox.Height)
|
||||
{
|
||||
entry.HideControls();
|
||||
|
||||
// We know that every subsequent entry will also fail the test, but we also need to
|
||||
// hide all the controls, so we cannot simply break out of the loop
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,27 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private StyleBox? _backgroundStyleBoxOverride;
|
||||
private StyleBox? _foregroundStyleBoxOverride;
|
||||
|
||||
private bool _vertical;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the progress bar is oriented vertically.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A vertical progress bar fills from bottom to top.
|
||||
/// </remarks>
|
||||
public bool Vertical
|
||||
{
|
||||
get => _vertical;
|
||||
set
|
||||
{
|
||||
if (_vertical != value)
|
||||
{
|
||||
_vertical = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public StyleBox? BackgroundStyleBoxOverride
|
||||
{
|
||||
get => _backgroundStyleBoxOverride;
|
||||
@@ -70,11 +91,23 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
return;
|
||||
}
|
||||
var minSize = fg.MinimumSize;
|
||||
var size = PixelWidth * GetAsRatio() - minSize.X;
|
||||
if (size > 0)
|
||||
|
||||
if (_vertical)
|
||||
{
|
||||
fg.Draw(handle, UIBox2.FromDimensions(0, 0, minSize.X + size, PixelHeight), UIScale);
|
||||
var size = PixelHeight * GetAsRatio();
|
||||
if (size > 0)
|
||||
{
|
||||
fg.Draw(handle, UIBox2.FromDimensions(0, PixelHeight - size, PixelWidth, size), UIScale);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var minSize = fg.MinimumSize;
|
||||
var size = PixelWidth * GetAsRatio() - minSize.X;
|
||||
if (size > 0)
|
||||
{
|
||||
fg.Draw(handle, UIBox2.FromDimensions(0, 0, minSize.X + size, PixelHeight), UIScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -15,8 +17,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
[Dependency] private readonly MarkupTagManager _tagManager = default!;
|
||||
|
||||
private FormattedMessage? _message;
|
||||
private RichTextEntry _entry;
|
||||
private RichTextEntry? _entry;
|
||||
private float _lineHeightScale = 1;
|
||||
private bool _lineHeightOverride;
|
||||
|
||||
@@ -40,19 +41,26 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public string? Text
|
||||
{
|
||||
get => _message?.ToMarkup();
|
||||
get => _entry?.Message.ToMarkup();
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
_message?.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
SetMessage(FormattedMessage.FromMarkupPermissive(value));
|
||||
Clear();
|
||||
else
|
||||
SetMessage(FormattedMessage.FromMarkupPermissive(value));
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_entry?.RemoveControls();
|
||||
_entry = null;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
public IEnumerable<Control> Controls => _entry?.Controls?.Values ?? Enumerable.Empty<Control>();
|
||||
public IReadOnlyList<MarkupNode> Nodes => _entry?.Message.Nodes ?? Array.Empty<MarkupNode>();
|
||||
|
||||
public RichTextLabel()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -61,8 +69,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public void SetMessage(FormattedMessage message, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
_message = message;
|
||||
_entry = new RichTextEntry(_message, this, _tagManager, tagsAllowed, defaultColor);
|
||||
_entry?.RemoveControls();
|
||||
_entry = new RichTextEntry(message, this, _tagManager, tagsAllowed, defaultColor);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
@@ -73,31 +81,31 @@ namespace Robust.Client.UserInterface.Controls
|
||||
SetMessage(msg, tagsAllowed, defaultColor);
|
||||
}
|
||||
|
||||
public string? GetMessage() => _message?.ToMarkup();
|
||||
public string? GetMessage() => _entry?.Message.ToMarkup();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of the currently used formatted message.
|
||||
/// </summary>
|
||||
public FormattedMessage? GetFormattedMessage() => _entry == null ? null : new FormattedMessage(_entry.Value.Message);
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
if (_message == null)
|
||||
{
|
||||
if (_entry == null)
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
var font = _getFont();
|
||||
_entry.Update(_tagManager, font, availableSize.X * UIScale, UIScale, LineHeightScale);
|
||||
|
||||
return new Vector2(_entry.Width / UIScale, _entry.Height / UIScale);
|
||||
// _entry is nullable struct.
|
||||
// cannot just call _entry.Value.Update() as that doesn't actually update _entry.
|
||||
_entry = _entry.Value.Update(_tagManager, font, availableSize.X * UIScale, UIScale, LineHeightScale);
|
||||
|
||||
return new Vector2(_entry.Value.Width / UIScale, _entry.Value.Height / UIScale);
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (_message == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_entry.Draw(_tagManager, handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale);
|
||||
_entry?.Draw(_tagManager, handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class BoldItalicTag : IMarkupTag
|
||||
public sealed class BoldItalicTag : IMarkupTagHandler
|
||||
{
|
||||
public const string BoldItalicFont = "DefaultBoldItalic";
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class BoldTag : IMarkupTag
|
||||
public sealed class BoldTag : IMarkupTagHandler
|
||||
{
|
||||
public const string BoldFont = "DefaultBold";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class BulletTag : IMarkupTag
|
||||
public sealed class BulletTag : IMarkupTagHandler
|
||||
{
|
||||
public string Name => "bullet";
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Robust.Client.UserInterface.RichText;
|
||||
/// <summary>
|
||||
/// Colors the text inside its opening and closing nodes
|
||||
/// </summary>
|
||||
public sealed class ColorTag : IMarkupTag
|
||||
public sealed class ColorTag : IMarkupTagHandler
|
||||
{
|
||||
public static readonly Color DefaultColor = new(200, 200, 200);
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class CommandLinkTag : IMarkupTag
|
||||
public sealed class CommandLinkTag : IMarkupTagHandler
|
||||
{
|
||||
[Dependency] private readonly IClientConsoleHost _clientConsoleHost = default!;
|
||||
|
||||
public string Name => "cmdlink";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetControl(MarkupNode node, [NotNullWhen(true)] out Control? control)
|
||||
public bool TryCreateControl(MarkupNode node, [NotNullWhen(true)] out Control? control)
|
||||
{
|
||||
if (!node.Value.TryGetString(out var text)
|
||||
|| !node.Attributes.TryGetValue("command", out var commandParameter)
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.UserInterface.RichText;
|
||||
/// Applies the font provided as the tags parameter to the markup drawing context.
|
||||
/// Definitely not save for user supplied markup
|
||||
/// </summary>
|
||||
public sealed class FontTag : IMarkupTag
|
||||
public sealed class FontTag : IMarkupTagHandler
|
||||
{
|
||||
public const string DefaultFont = "Default";
|
||||
public const int DefaultSize = 12;
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class HeadingTag : IMarkupTag
|
||||
public sealed class HeadingTag : IMarkupTagHandler
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public interface IMarkupTag
|
||||
/// <summary>
|
||||
/// Classes that implement this interface will be instantiated by <see cref="MarkupTagManager"/> and used to handle
|
||||
/// the parsing and behaviour of markup tags. Note that each class is only ever instantiated once by the tag manager,
|
||||
/// and wil be used to handle all tags of that kind, and thus should not contain state information relevant to a
|
||||
/// specific tag.
|
||||
/// </summary>
|
||||
public interface IMarkupTagHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// The string used as the tags name when writing rich text
|
||||
@@ -54,17 +61,32 @@ public interface IMarkupTag
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called inside the constructor of <see cref="RichTextEntry"/> to
|
||||
/// supply a control that gets rendered inline before this tags children<br/>
|
||||
/// Text continues to the right of the control until the next line and then continues bellow it
|
||||
/// Called inside the constructor of <see cref="RichTextEntry"/> to supply a control that gets rendered inline
|
||||
/// before this tags children. The returned control must be new instance to avoid issues with shallow cloning
|
||||
/// <see cref="FormattedMessage"/> nodes. Text continues to the right of the control until the next line and
|
||||
/// then continues bellow it.
|
||||
/// </summary>
|
||||
/// <param name="node">The markup node containing the parameter and attributes</param>
|
||||
/// <param name="control">A UI control for placing in line with this tags children</param>
|
||||
/// <returns>true if this tag supplies a control</returns>
|
||||
public bool TryCreateControl(MarkupNode node, [NotNullWhen(true)] out Control? control)
|
||||
{
|
||||
control = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use IMarkupTagHandler")]
|
||||
public interface IMarkupTag : IMarkupTagHandler
|
||||
{
|
||||
bool IMarkupTagHandler.TryCreateControl(MarkupNode node, [NotNullWhen(true)] out Control? control)
|
||||
{
|
||||
return TryGetControl(node, out control);
|
||||
}
|
||||
|
||||
public bool TryGetControl(MarkupNode node, [NotNullWhen(true)] out Control? control)
|
||||
{
|
||||
control = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class ItalicTag : IMarkupTag
|
||||
public sealed class ItalicTag : IMarkupTagHandler
|
||||
{
|
||||
public const string ItalicFont = "DefaultItalic";
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ public sealed class MarkupTagManager
|
||||
/// <summary>
|
||||
/// Tags defined in engine need to be instantiated here because of sandboxing
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, IMarkupTag> _markupTagTypes = new IMarkupTag[] {
|
||||
private readonly Dictionary<string, IMarkupTagHandler> _markupTagTypes = new IMarkupTagHandler[] {
|
||||
new BoldItalicTag(),
|
||||
new BoldTag(),
|
||||
new BulletTag(),
|
||||
@@ -44,13 +44,13 @@ public sealed class MarkupTagManager
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
foreach (var type in _reflectionManager.GetAllChildren<IMarkupTag>())
|
||||
foreach (var type in _reflectionManager.GetAllChildren<IMarkupTagHandler>())
|
||||
{
|
||||
//Prevent tags defined inside engine from being instantiated
|
||||
if (_engineTypes.Contains(type))
|
||||
continue;
|
||||
|
||||
var instance = (IMarkupTag)_sandboxHelper.CreateInstance(type);
|
||||
var instance = (IMarkupTagHandler)_sandboxHelper.CreateInstance(type);
|
||||
_markupTagTypes[instance.Name.ToLower()] = instance;
|
||||
}
|
||||
|
||||
@@ -60,22 +60,48 @@ public sealed class MarkupTagManager
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use GetMarkupTagHandler")]
|
||||
public IMarkupTag? GetMarkupTag(string name)
|
||||
{
|
||||
return _markupTagTypes.GetValueOrDefault(name) as IMarkupTag;
|
||||
}
|
||||
|
||||
public IMarkupTagHandler? GetMarkupTagHandler(string name)
|
||||
{
|
||||
return _markupTagTypes.GetValueOrDefault(name);
|
||||
}
|
||||
|
||||
public bool TryGetMarkupTag(string name, Type[]? tagsAllowed, [NotNullWhen(true)] out IMarkupTag? tag)
|
||||
/// <summary>
|
||||
/// Attempt to get the tag handler with the corresponding name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the tag, as specified by <see cref="IMarkupTag.Name"/></param>
|
||||
/// <param name="tagsAllowed">List of allowed tag types. If null, all types are allowed.</param>
|
||||
/// <param name="handler">The instance responsible for handling tags of this type.</param>
|
||||
/// <returns></returns>
|
||||
public bool TryGetMarkupTagHandler(string name, Type[]? tagsAllowed, [NotNullWhen(true)] out IMarkupTagHandler? handler)
|
||||
{
|
||||
if (_markupTagTypes.TryGetValue(name, out var markupTag)
|
||||
// Using a whitelist prevents new tags from sneaking in.
|
||||
&& (tagsAllowed == null || Array.IndexOf(tagsAllowed, markupTag.GetType()) != -1))
|
||||
{
|
||||
tag = markupTag;
|
||||
handler = markupTag;
|
||||
return true;
|
||||
}
|
||||
|
||||
tag = null;
|
||||
handler = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
[Obsolete("Use TryGetMarkupTagHandler")]
|
||||
public bool TryGetMarkupTag(string name, Type[]? tagsAllowed, [NotNullWhen(true)] out IMarkupTag? tag)
|
||||
{
|
||||
if (!TryGetMarkupTagHandler(name, tagsAllowed, out var handler) || handler is not IMarkupTag cast)
|
||||
{
|
||||
tag = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
tag = cast;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by <see cref="OutputPanel"/> and <see cref="RichTextLabel"/> to handle rich text layout.
|
||||
/// Note that if this text is ever removed or modified without removing the owning control,
|
||||
/// then <see cref="RemoveControls"/> should be called to ensure that any controls that were added by this
|
||||
/// entry are also removed.
|
||||
/// </summary>
|
||||
internal struct RichTextEntry
|
||||
{
|
||||
@@ -36,7 +39,7 @@ namespace Robust.Client.UserInterface
|
||||
/// </summary>
|
||||
public ValueList<int> LineBreaks;
|
||||
|
||||
private readonly Dictionary<int, Control>? _tagControls;
|
||||
public readonly Dictionary<int, Control>? Controls;
|
||||
|
||||
public RichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
@@ -56,15 +59,35 @@ namespace Robust.Client.UserInterface
|
||||
if (node.Name == null)
|
||||
continue;
|
||||
|
||||
if (!tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag) || !tag.TryGetControl(node, out var control))
|
||||
if (!tagManager.TryGetMarkupTagHandler(node.Name, _tagsAllowed, out var handler) || !handler.TryCreateControl(node, out var control))
|
||||
continue;
|
||||
|
||||
// Markup tag handler instances are shared across controls. We need to ensure that the hanlder doesn't
|
||||
// store state information and return the same control for each rich text entry.
|
||||
DebugTools.Assert(handler.TryCreateControl(node, out var other) && other != control);
|
||||
|
||||
parent.Children.Add(control);
|
||||
tagControls ??= new Dictionary<int, Control>();
|
||||
tagControls.Add(nodeIndex, control);
|
||||
}
|
||||
|
||||
_tagControls = tagControls;
|
||||
Controls = tagControls;
|
||||
}
|
||||
|
||||
// TODO RICH TEXT
|
||||
// Somehow ensure that this **has** to be called when removing rich text from some control.
|
||||
/// <summary>
|
||||
/// Remove all owned controls from their parents.
|
||||
/// </summary>
|
||||
public readonly void RemoveControls()
|
||||
{
|
||||
if (Controls == null)
|
||||
return;
|
||||
|
||||
foreach (var ctrl in Controls.Values)
|
||||
{
|
||||
ctrl.Orphan();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,7 +97,7 @@ namespace Robust.Client.UserInterface
|
||||
/// <param name="maxSizeX">The maximum horizontal size of the container of this entry.</param>
|
||||
/// <param name="uiScale"></param>
|
||||
/// <param name="lineHeightScale"></param>
|
||||
public void Update(MarkupTagManager tagManager, Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1)
|
||||
public RichTextEntry Update(MarkupTagManager tagManager, Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1)
|
||||
{
|
||||
// This method is gonna suck due to complexity.
|
||||
// Bear with me here.
|
||||
@@ -112,10 +135,10 @@ namespace Robust.Client.UserInterface
|
||||
continue;
|
||||
|
||||
if (ProcessMetric(ref this, metrics, out breakLine))
|
||||
return;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
|
||||
if (Controls == null || !Controls.TryGetValue(nodeIndex, out var control))
|
||||
continue;
|
||||
|
||||
control.Measure(new Vector2(Width, Height));
|
||||
@@ -128,12 +151,14 @@ namespace Robust.Client.UserInterface
|
||||
desiredSize.Y);
|
||||
|
||||
if (ProcessMetric(ref this, controlMetrics, out breakLine))
|
||||
return;
|
||||
return this;
|
||||
}
|
||||
|
||||
Width = wordWrap.FinalizeText(out breakLine);
|
||||
CheckLineBreak(ref this, breakLine);
|
||||
|
||||
return this;
|
||||
|
||||
bool ProcessRune(ref RichTextEntry src, Rune rune, out int? outBreakLine)
|
||||
{
|
||||
wordWrap.NextRune(rune, out breakLine, out var breakNewLine, out var skip);
|
||||
@@ -166,9 +191,10 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
internal readonly void HideControls()
|
||||
{
|
||||
if (_tagControls == null)
|
||||
if (Controls == null)
|
||||
return;
|
||||
foreach (var control in _tagControls.Values)
|
||||
|
||||
foreach (var control in Controls.Values)
|
||||
{
|
||||
control.Visible = false;
|
||||
}
|
||||
@@ -220,7 +246,7 @@ namespace Robust.Client.UserInterface
|
||||
globalBreakCounter += 1;
|
||||
}
|
||||
|
||||
if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
|
||||
if (Controls == null || !Controls.TryGetValue(nodeIndex, out var control))
|
||||
continue;
|
||||
|
||||
// Controls may have been previously hidden via HideControls due to being "out-of frame".
|
||||
@@ -243,7 +269,7 @@ namespace Robust.Client.UserInterface
|
||||
return node.Value.StringValue ?? "";
|
||||
|
||||
//Skip the node if there is no markup tag for it.
|
||||
if (!tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag))
|
||||
if (!tagManager.TryGetMarkupTagHandler(node.Name, _tagsAllowed, out var tag))
|
||||
return "";
|
||||
|
||||
if (!node.Closing)
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace Robust.Server
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir);
|
||||
_resources.Initialize(dataDir, hideUserDataDir: false);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
@@ -60,9 +60,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
internal string GetPath(ResPath relPath)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()))
|
||||
// Sanitise platform-specific path and standardize it for engine use.
|
||||
.Replace(Path.DirectorySeparatorChar, '/');
|
||||
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,7 +14,11 @@ namespace Robust.Shared.ContentPack
|
||||
/// The directory to use for user data.
|
||||
/// If null, a virtual temporary file system is used instead.
|
||||
/// </param>
|
||||
void Initialize(string? userData);
|
||||
/// <param name="hideUserDataDir">
|
||||
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
|
||||
/// <see cref="IResourceManager.UserData"/>.
|
||||
/// </param>
|
||||
void Initialize(string? userData, bool hideUserDataDir);
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a single stream as a content file. Useful for unit testing.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// The root path of this provider.
|
||||
/// Can be null if it's a virtual provider.
|
||||
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
|
||||
/// </summary>
|
||||
string? RootDir { get; }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
{
|
||||
@@ -63,5 +64,27 @@ namespace Robust.Shared.ContentPack
|
||||
!OperatingSystem.IsWindows()
|
||||
&& !OperatingSystem.IsMacOS();
|
||||
|
||||
|
||||
internal static string SafeGetResourcePath(string baseDir, ResPath path)
|
||||
{
|
||||
var relSysPath = path.ToRelativeSystemPath();
|
||||
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
|
||||
// better safe than sorry check
|
||||
if (!retPath.StartsWith(baseDir))
|
||||
{
|
||||
// Allow path to match if it's just missing the directory separator at the end.
|
||||
if (retPath != baseDir.TrimEnd(Path.DirectorySeparatorChar))
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return retPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
|
||||
public IWritableDirProvider UserData { get; private set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Initialize(string? userData)
|
||||
public virtual void Initialize(string? userData, bool hideRootDir)
|
||||
{
|
||||
Sawmill = _logManager.GetSawmill("res");
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -379,6 +379,10 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
var rootDir = loader.GetPath(new ResPath(@"/"));
|
||||
|
||||
// TODO: GET RID OF THIS.
|
||||
// This code shouldn't be passing OS disk paths through ResPath.
|
||||
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
|
||||
|
||||
yield return new ResPath(rootDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,6 +875,7 @@ Types:
|
||||
- "System.Text.StringBuilder Insert(int, object)"
|
||||
- "System.Text.StringBuilder Insert(int, sbyte)"
|
||||
- "System.Text.StringBuilder Insert(int, short)"
|
||||
- "System.Text.StringBuilder Insert(int, string)"
|
||||
- "System.Text.StringBuilder Insert(int, string, int)"
|
||||
- "System.Text.StringBuilder Insert(int, System.Decimal)"
|
||||
- "System.Text.StringBuilder Insert(int, System.ReadOnlySpan`1<char>)"
|
||||
|
||||
@@ -10,17 +10,22 @@ namespace Robust.Shared.ContentPack
|
||||
/// <inheritdoc />
|
||||
internal sealed class WritableDirProvider : IWritableDirProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
private readonly bool _hideRootDir;
|
||||
|
||||
public string RootDir { get; }
|
||||
|
||||
string? IWritableDirProvider.RootDir => _hideRootDir ? null : RootDir;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="WritableDirProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="rootDir">Root file system directory to allow writing.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir)
|
||||
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
|
||||
{
|
||||
// FullName does not have a trailing separator, and we MUST have a separator.
|
||||
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
|
||||
_hideRootDir = hideRootDir;
|
||||
}
|
||||
|
||||
#region File Access
|
||||
@@ -119,7 +124,7 @@ namespace Robust.Shared.ContentPack
|
||||
throw new FileNotFoundException();
|
||||
|
||||
var dirInfo = new DirectoryInfo(GetFullPath(path));
|
||||
return new WritableDirProvider(dirInfo);
|
||||
return new WritableDirProvider(dirInfo, _hideRootDir);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -180,20 +185,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
path = path.Clean();
|
||||
|
||||
return GetFullPath(RootDir, path);
|
||||
}
|
||||
|
||||
private static string GetFullPath(string root, ResPath path)
|
||||
{
|
||||
var relPath = path.ToRelativeSystemPath();
|
||||
if (relPath.Contains("\\..") || relPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return Path.GetFullPath(Path.Combine(root, relPath));
|
||||
return PathHelpers.SafeGetResourcePath(RootDir, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,11 +273,11 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public IComponent GetComponent(Type componentType)
|
||||
{
|
||||
if (!_types.ContainsKey(componentType))
|
||||
if (!_types.TryGetValue(componentType, out var value))
|
||||
{
|
||||
throw new InvalidOperationException($"{componentType} is not a registered component.");
|
||||
}
|
||||
return _typeFactory.CreateInstanceUnchecked<IComponent>(_types[componentType].Type);
|
||||
return _typeFactory.CreateInstanceUnchecked<IComponent>(value.Type);
|
||||
}
|
||||
|
||||
public IComponent GetComponent(CompIdx componentType)
|
||||
@@ -287,11 +287,11 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public T GetComponent<T>() where T : IComponent, new()
|
||||
{
|
||||
if (!_types.ContainsKey(typeof(T)))
|
||||
if (!_types.TryGetValue(typeof(T), out var reg))
|
||||
{
|
||||
throw new InvalidOperationException($"{typeof(T)} is not a registered component.");
|
||||
}
|
||||
return _typeFactory.CreateInstanceUnchecked<T>(_types[typeof(T)].Type);
|
||||
return _typeFactory.CreateInstanceUnchecked<T>(reg.Type);
|
||||
}
|
||||
|
||||
public IComponent GetComponent(ComponentRegistration reg)
|
||||
|
||||
@@ -150,6 +150,7 @@ namespace Robust.Shared.GameObjects
|
||||
[ViewVariables, Access(typeof(EntityManager), Other = AccessPermissions.ReadExecute)]
|
||||
public EntityLifeStage EntityLifeStage { get; internal set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public MetaDataFlags Flags
|
||||
{
|
||||
get => _flags;
|
||||
|
||||
@@ -11,7 +11,7 @@ public record struct Entity<T> : IFluentEntityUid, IAsType<EntityUid>
|
||||
{
|
||||
public EntityUid Owner;
|
||||
public T Comp;
|
||||
EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
readonly EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
|
||||
public Entity(EntityUid owner, T comp)
|
||||
{
|
||||
@@ -48,9 +48,9 @@ public record struct Entity<T> : IFluentEntityUid, IAsType<EntityUid>
|
||||
}
|
||||
|
||||
|
||||
public override int GetHashCode() => Owner.GetHashCode();
|
||||
public Entity<T?> AsNullable() => new(Owner, Comp);
|
||||
public EntityUid AsType() => Owner;
|
||||
public override readonly int GetHashCode() => Owner.GetHashCode();
|
||||
public readonly Entity<T?> AsNullable() => new(Owner, Comp);
|
||||
public readonly EntityUid AsType() => Owner;
|
||||
}
|
||||
|
||||
[NotYamlSerializable]
|
||||
@@ -60,7 +60,7 @@ public record struct Entity<T1, T2> : IFluentEntityUid, IAsType<EntityUid>
|
||||
public EntityUid Owner;
|
||||
public T1 Comp1;
|
||||
public T2 Comp2;
|
||||
EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
readonly EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
|
||||
public Entity(EntityUid owner, T1 comp1, T2 comp2)
|
||||
{
|
||||
@@ -119,9 +119,9 @@ public record struct Entity<T1, T2> : IFluentEntityUid, IAsType<EntityUid>
|
||||
return new Entity<T1>(ent.Owner, ent.Comp1);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => Owner.GetHashCode();
|
||||
public Entity<T1?, T2?> AsNullable() => new(Owner, Comp1, Comp2);
|
||||
public EntityUid AsType() => Owner;
|
||||
public override readonly int GetHashCode() => Owner.GetHashCode();
|
||||
public readonly Entity<T1?, T2?> AsNullable() => new(Owner, Comp1, Comp2);
|
||||
public readonly EntityUid AsType() => Owner;
|
||||
}
|
||||
|
||||
[NotYamlSerializable]
|
||||
@@ -132,7 +132,7 @@ public record struct Entity<T1, T2, T3> : IFluentEntityUid, IAsType<EntityUid>
|
||||
public T1 Comp1;
|
||||
public T2 Comp2;
|
||||
public T3 Comp3;
|
||||
EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
readonly EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
|
||||
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3)
|
||||
{
|
||||
@@ -226,9 +226,9 @@ public record struct Entity<T1, T2, T3> : IFluentEntityUid, IAsType<EntityUid>
|
||||
|
||||
#endregion
|
||||
|
||||
public override int GetHashCode() => Owner.GetHashCode();
|
||||
public Entity<T1?, T2?, T3?> AsNullable() => new(Owner, Comp1, Comp2, Comp3);
|
||||
public EntityUid AsType() => Owner;
|
||||
public override readonly int GetHashCode() => Owner.GetHashCode();
|
||||
public readonly Entity<T1?, T2?, T3?> AsNullable() => new(Owner, Comp1, Comp2, Comp3);
|
||||
public readonly EntityUid AsType() => Owner;
|
||||
}
|
||||
|
||||
[NotYamlSerializable]
|
||||
@@ -240,7 +240,7 @@ public record struct Entity<T1, T2, T3, T4> : IFluentEntityUid, IAsType<EntityUi
|
||||
public T2 Comp2;
|
||||
public T3 Comp3;
|
||||
public T4 Comp4;
|
||||
EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
readonly EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
|
||||
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3, T4 comp4)
|
||||
{
|
||||
@@ -357,9 +357,9 @@ public record struct Entity<T1, T2, T3, T4> : IFluentEntityUid, IAsType<EntityUi
|
||||
|
||||
#endregion
|
||||
|
||||
public override int GetHashCode() => Owner.GetHashCode();
|
||||
public Entity<T1?, T2?, T3?, T4?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4);
|
||||
public EntityUid AsType() => Owner;
|
||||
public override readonly int GetHashCode() => Owner.GetHashCode();
|
||||
public readonly Entity<T1?, T2?, T3?, T4?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4);
|
||||
public readonly EntityUid AsType() => Owner;
|
||||
}
|
||||
|
||||
[NotYamlSerializable]
|
||||
@@ -372,7 +372,7 @@ public record struct Entity<T1, T2, T3, T4, T5> : IFluentEntityUid, IAsType<Enti
|
||||
public T3 Comp3;
|
||||
public T4 Comp4;
|
||||
public T5 Comp5;
|
||||
EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
readonly EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
|
||||
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3, T4 comp4, T5 comp5)
|
||||
{
|
||||
@@ -512,9 +512,9 @@ public record struct Entity<T1, T2, T3, T4, T5> : IFluentEntityUid, IAsType<Enti
|
||||
|
||||
#endregion
|
||||
|
||||
public override int GetHashCode() => Owner.GetHashCode();
|
||||
public Entity<T1?, T2?, T3?, T4?, T5?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5);
|
||||
public EntityUid AsType() => Owner;
|
||||
public override readonly int GetHashCode() => Owner.GetHashCode();
|
||||
public readonly Entity<T1?, T2?, T3?, T4?, T5?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5);
|
||||
public readonly EntityUid AsType() => Owner;
|
||||
}
|
||||
|
||||
[NotYamlSerializable]
|
||||
@@ -528,7 +528,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6> : IFluentEntityUid, IAsType<
|
||||
public T4 Comp4;
|
||||
public T5 Comp5;
|
||||
public T6 Comp6;
|
||||
EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
readonly EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
|
||||
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3, T4 comp4, T5 comp5, T6 comp6)
|
||||
{
|
||||
@@ -691,9 +691,9 @@ public record struct Entity<T1, T2, T3, T4, T5, T6> : IFluentEntityUid, IAsType<
|
||||
|
||||
#endregion
|
||||
|
||||
public override int GetHashCode() => Owner.GetHashCode();
|
||||
public Entity<T1?, T2?, T3?, T4?, T5?, T6?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6);
|
||||
public EntityUid AsType() => Owner;
|
||||
public override readonly int GetHashCode() => Owner.GetHashCode();
|
||||
public readonly Entity<T1?, T2?, T3?, T4?, T5?, T6?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6);
|
||||
public readonly EntityUid AsType() => Owner;
|
||||
}
|
||||
|
||||
[NotYamlSerializable]
|
||||
@@ -708,7 +708,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6, T7> : IFluentEntityUid, IAsT
|
||||
public T5 Comp5;
|
||||
public T6 Comp6;
|
||||
public T7 Comp7;
|
||||
EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
readonly EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
|
||||
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3, T4 comp4, T5 comp5, T6 comp6, T7 comp7)
|
||||
{
|
||||
@@ -894,9 +894,9 @@ public record struct Entity<T1, T2, T3, T4, T5, T6, T7> : IFluentEntityUid, IAsT
|
||||
|
||||
#endregion
|
||||
|
||||
public override int GetHashCode() => Owner.GetHashCode();
|
||||
public Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7);
|
||||
public EntityUid AsType() => Owner;
|
||||
public override readonly int GetHashCode() => Owner.GetHashCode();
|
||||
public readonly Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7);
|
||||
public readonly EntityUid AsType() => Owner;
|
||||
}
|
||||
|
||||
[NotYamlSerializable]
|
||||
@@ -912,7 +912,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6, T7, T8> : IFluentEntityUid,
|
||||
public T6 Comp6;
|
||||
public T7 Comp7;
|
||||
public T8 Comp8;
|
||||
EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
readonly EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
|
||||
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3, T4 comp4, T5 comp5, T6 comp6, T7 comp7, T8 comp8)
|
||||
{
|
||||
@@ -1121,7 +1121,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6, T7, T8> : IFluentEntityUid,
|
||||
|
||||
#endregion
|
||||
|
||||
public override int GetHashCode() => Owner.GetHashCode();
|
||||
public Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?, T8?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8);
|
||||
public EntityUid AsType() => Owner;
|
||||
public override readonly int GetHashCode() => Owner.GetHashCode();
|
||||
public readonly Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?, T8?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8);
|
||||
public readonly EntityUid AsType() => Owner;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,16 @@ public partial class EntityManager
|
||||
return ents;
|
||||
}
|
||||
|
||||
public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params EntProtoId[] protoNames)
|
||||
{
|
||||
var ents = new EntityUid[protoNames.Length];
|
||||
for (var i = 0; i < protoNames.Length; i++)
|
||||
{
|
||||
ents[i] = SpawnAttachedTo(protoNames[i], coordinates);
|
||||
}
|
||||
return ents;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames)
|
||||
{
|
||||
@@ -74,6 +84,14 @@ public partial class EntityManager
|
||||
return ents;
|
||||
}
|
||||
|
||||
public void SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames)
|
||||
{
|
||||
foreach (var protoName in protoNames)
|
||||
{
|
||||
SpawnAttachedTo(protoName, coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
|
||||
{
|
||||
if (!coordinates.IsValid(this))
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly IReplayRecordingManager _replayMan = default!;
|
||||
[Dependency] protected readonly ILocalizationManager Loc = default!;
|
||||
|
||||
protected IComponentFactory Factory => EntityManager.ComponentFactory;
|
||||
|
||||
public ISawmill Log { get; private set; } = default!;
|
||||
|
||||
protected virtual string SawmillName
|
||||
|
||||
@@ -22,6 +22,8 @@ public partial interface IEntityManager
|
||||
EntityUid[] SpawnEntities(MapCoordinates coordinates, params string?[] protoNames);
|
||||
EntityUid[] SpawnEntities(MapCoordinates coordinates, string? prototype, int count);
|
||||
EntityUid[] SpawnEntities(MapCoordinates coordinates, List<string?> protoNames);
|
||||
void SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames);
|
||||
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params EntProtoId[] protoNames);
|
||||
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames);
|
||||
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params string?[] protoNames);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -833,7 +834,7 @@ public abstract partial class SharedMapSystem
|
||||
}
|
||||
|
||||
var offset = chunk.GridTileToChunkTile(gridIndices);
|
||||
SetChunkTile(uid, grid, chunk, (ushort)offset.X, (ushort)offset.Y, tile);
|
||||
SetChunkTile(uid, grid, chunk, (ushort)offset.X, (ushort)offset.Y, tile, out _);
|
||||
}
|
||||
|
||||
public void SetTiles(EntityUid uid, MapGridComponent grid, List<(Vector2i GridIndices, Tile Tile)> tiles)
|
||||
@@ -842,6 +843,11 @@ public abstract partial class SharedMapSystem
|
||||
return;
|
||||
|
||||
var modified = new HashSet<MapChunk>(Math.Max(1, tiles.Count / grid.ChunkSize));
|
||||
var tileChanges = new ValueList<TileChangedEntry>(tiles.Count);
|
||||
|
||||
// Suppress sending out events for each tile changed
|
||||
// We're going to send them all out together at the end
|
||||
MapManager.SuppressOnTileChanged = true;
|
||||
|
||||
foreach (var (gridIndices, tile) in tiles)
|
||||
{
|
||||
@@ -859,8 +865,11 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
var offset = chunk.GridTileToChunkTile(gridIndices);
|
||||
chunk.SuppressCollisionRegeneration = true;
|
||||
if (SetChunkTile(uid, grid, chunk, (ushort)offset.X, (ushort)offset.Y, tile))
|
||||
if (SetChunkTile(uid, grid, chunk, (ushort)offset.X, (ushort)offset.Y, tile, out var oldTile))
|
||||
{
|
||||
modified.Add(chunk);
|
||||
tileChanges.Add(new TileChangedEntry(tile, oldTile, offset, gridIndices));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var chunk in modified)
|
||||
@@ -869,6 +878,13 @@ public abstract partial class SharedMapSystem
|
||||
}
|
||||
|
||||
RegenerateCollision(uid, grid, modified);
|
||||
|
||||
// Notify of all tile changes in one event
|
||||
var ev = new TileChangedEvent((uid, grid), tileChanges.ToArray());
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
|
||||
// Back to normal
|
||||
MapManager.SuppressOnTileChanged = false;
|
||||
}
|
||||
|
||||
public TilesEnumerator GetLocalTilesEnumerator(EntityUid uid, MapGridComponent grid, Box2 aabb,
|
||||
|
||||
@@ -14,9 +14,9 @@ public abstract partial class SharedMapSystem
|
||||
/// <param name="xIndex">The X tile index relative to the chunk.</param>
|
||||
/// <param name="yIndex">The Y tile index relative to the chunk.</param>
|
||||
/// <param name="tile">The new tile to insert.</param>
|
||||
internal bool SetChunkTile(EntityUid uid, MapGridComponent grid, MapChunk chunk, ushort xIndex, ushort yIndex, Tile tile)
|
||||
internal bool SetChunkTile(EntityUid uid, MapGridComponent grid, MapChunk chunk, ushort xIndex, ushort yIndex, Tile tile, out Tile oldTile)
|
||||
{
|
||||
if (!chunk.TrySetTile(xIndex, yIndex, tile, out var oldTile, out var shapeChanged))
|
||||
if (!chunk.TrySetTile(xIndex, yIndex, tile, out oldTile, out var shapeChanged))
|
||||
return false;
|
||||
|
||||
var tileIndices = new Vector2i(xIndex, yIndex);
|
||||
|
||||
@@ -173,45 +173,61 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for when a single tile on a grid is changed locally or remotely.
|
||||
/// Raised directed at the grid when tiles are changed locally or remotely.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct TileChangedEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of this class.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="TileChangedEvent(Entity{MapGridComponent}, Tile, Tile, Vector2i, Vector2i)"/>
|
||||
public TileChangedEvent(Entity<MapGridComponent> entity, TileRef newTile, Tile oldTile, Vector2i chunkIndex)
|
||||
: this(entity, newTile.Tile, oldTile, chunkIndex, newTile.GridIndices) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of this event for a single changed tile.
|
||||
/// </summary>
|
||||
/// <param name="entity">The grid entity containing the changed tile(s)</param>
|
||||
/// <param name="newTile">New tile that replaced the old one.</param>
|
||||
/// <param name="oldTile">Old tile that was replaced.</param>
|
||||
/// <param name="chunkIndex">The index of the grid-chunk that this tile belongs to.</param>
|
||||
/// <param name="gridIndices">The positional indices of this tile on the grid.</param>
|
||||
public TileChangedEvent(Entity<MapGridComponent> entity, Tile newTile, Tile oldTile, Vector2i chunkIndex, Vector2i gridIndices)
|
||||
{
|
||||
Entity = entity;
|
||||
NewTile = newTile;
|
||||
OldTile = oldTile;
|
||||
ChunkIndex = chunkIndex;
|
||||
Changes = [new TileChangedEntry(newTile, oldTile, chunkIndex, gridIndices)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Was the tile previously empty or is it now empty.
|
||||
/// Creates a new instance of this event for multiple changed tiles.
|
||||
/// </summary>
|
||||
public bool EmptyChanged => OldTile.IsEmpty != NewTile.Tile.IsEmpty;
|
||||
public TileChangedEvent(Entity<MapGridComponent> entity, TileChangedEntry[] changes)
|
||||
{
|
||||
Entity = entity;
|
||||
Changes = changes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity of the grid with the tile-change. TileRef stores the GridId.
|
||||
/// Entity of the grid with the tile-change. TileRef stores the GridId.
|
||||
/// </summary>
|
||||
public readonly Entity<MapGridComponent> Entity;
|
||||
|
||||
/// <summary>
|
||||
/// New tile that replaced the old one.
|
||||
/// An array of all the tiles that were changed.
|
||||
/// </summary>
|
||||
public readonly TileRef NewTile;
|
||||
public readonly TileChangedEntry[] Changes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data about a single tile that was changed as part of a <see cref="TileChangedEvent"/>.
|
||||
/// </summary>
|
||||
/// <param name="NewTile">New tile that replaced the old one.</param>
|
||||
/// <param name="OldTile">Old tile that was replaced.</param>
|
||||
/// <param name="ChunkIndex">The index of the grid-chunk that this tile belongs to.</param>
|
||||
/// <param name="GridIndices">The positional indices of this tile on the grid.</param>
|
||||
public readonly record struct TileChangedEntry(Tile NewTile, Tile OldTile, Vector2i ChunkIndex, Vector2i GridIndices)
|
||||
{
|
||||
/// <summary>
|
||||
/// Old tile that was replaced.
|
||||
/// Was the tile previously empty or is it now empty.
|
||||
/// </summary>
|
||||
public readonly Tile OldTile;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the grid-chunk that this tile belongs to.
|
||||
/// </summary>
|
||||
public readonly Vector2i ChunkIndex;
|
||||
public bool EmptyChanged => OldTile.IsEmpty != NewTile.IsEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,11 +70,14 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void MapManagerOnTileChanged(ref TileChangedEvent e)
|
||||
{
|
||||
if(e.NewTile.Tile != Tile.Empty)
|
||||
return;
|
||||
foreach (var change in e.Changes)
|
||||
{
|
||||
if(change.NewTile != Tile.Empty)
|
||||
continue;
|
||||
|
||||
// TODO optimize this for when multiple tiles get empties simultaneously (e.g., explosions).
|
||||
DeparentAllEntsOnTile(e.NewTile.GridUid, e.NewTile.GridIndices);
|
||||
// TODO optimize this for when multiple tiles get empties simultaneously (e.g., explosions).
|
||||
DeparentAllEntsOnTile(e.Entity, change.GridIndices);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Events;
|
||||
|
||||
@@ -20,9 +20,16 @@ public readonly struct StartCollideEvent
|
||||
|
||||
public readonly Fixture OurFixture;
|
||||
public readonly Fixture OtherFixture;
|
||||
public readonly Vector2 WorldPoint;
|
||||
|
||||
public StartCollideEvent(
|
||||
internal readonly FixedArray2<Vector2> _worldPoints;
|
||||
|
||||
public readonly int PointCount;
|
||||
public readonly Vector2 WorldNormal;
|
||||
|
||||
public Vector2[] WorldPoints => _worldPoints.AsSpan[..PointCount].ToArray();
|
||||
|
||||
|
||||
internal StartCollideEvent(
|
||||
EntityUid ourEntity,
|
||||
EntityUid otherEntity,
|
||||
string ourFixtureId,
|
||||
@@ -31,7 +38,9 @@ public readonly struct StartCollideEvent
|
||||
Fixture otherFixture,
|
||||
PhysicsComponent ourBody,
|
||||
PhysicsComponent otherBody,
|
||||
Vector2 worldPoint)
|
||||
FixedArray2<Vector2> worldPoints,
|
||||
int pointCount,
|
||||
Vector2 worldNormal)
|
||||
{
|
||||
OurEntity = ourEntity;
|
||||
OtherEntity = otherEntity;
|
||||
@@ -39,8 +48,10 @@ public readonly struct StartCollideEvent
|
||||
OtherFixtureId = otherFixtureId;
|
||||
OurFixture = ourFixture;
|
||||
OtherFixture = otherFixture;
|
||||
WorldPoint = worldPoint;
|
||||
OtherBody = otherBody;
|
||||
OurBody = ourBody;
|
||||
_worldPoints = worldPoints;
|
||||
PointCount = pointCount;
|
||||
WorldNormal = worldNormal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +294,14 @@ public abstract partial class SharedPhysicsSystem
|
||||
DebugTools.Assert(!fixB.Contacts.ContainsKey(fixA));
|
||||
fixB.Contacts.Add(fixA, contact);
|
||||
bodB.Contacts.AddLast(contact.BodyBNode);
|
||||
|
||||
// If it's a spawned static ent then need to wake any contacting entities.
|
||||
// The issue is that static ents can never be awake and if it spawns on an asleep entity never gets a contact.
|
||||
// Checking only bodyA should be okay because if bodyA is the other ent (i.e. dynamic / kinematic) then it should already be awake.
|
||||
if (bodyA.BodyType == BodyType.Static && !bodyB.Awake)
|
||||
{
|
||||
WakeBody(uidB, body: bodyB);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -543,7 +551,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
|
||||
var status = ArrayPool<ContactStatus>.Shared.Rent(index);
|
||||
var worldPoints = ArrayPool<Vector2>.Shared.Rent(index);
|
||||
var worldPoints = ArrayPool<FixedArray4<Vector2>>.Shared.Rent(index);
|
||||
|
||||
// Update contacts all at once.
|
||||
BuildManifolds(contacts, index, status, worldPoints);
|
||||
@@ -578,9 +586,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
var uidA = contact.EntityA;
|
||||
var uidB = contact.EntityB;
|
||||
var worldPoint = worldPoints[i];
|
||||
var points = new FixedArray2<Vector2>(worldPoint._00, worldPoint._01);
|
||||
var worldNormal = worldPoint._02;
|
||||
|
||||
var ev1 = new StartCollideEvent(uidA, uidB, contact.FixtureAId, contact.FixtureBId, fixtureA, fixtureB, bodyA, bodyB, worldPoint);
|
||||
var ev2 = new StartCollideEvent(uidB, uidA, contact.FixtureBId, contact.FixtureAId, fixtureB, fixtureA, bodyB, bodyA, worldPoint);
|
||||
var ev1 = new StartCollideEvent(uidA, uidB, contact.FixtureAId, contact.FixtureBId, fixtureA, fixtureB, bodyA, bodyB, points, contact.Manifold.PointCount, worldNormal);
|
||||
var ev2 = new StartCollideEvent(uidB, uidA, contact.FixtureBId, contact.FixtureAId, fixtureB, fixtureA, bodyB, bodyA, points, contact.Manifold.PointCount, worldNormal);
|
||||
|
||||
RaiseLocalEvent(uidA, ref ev1, true);
|
||||
RaiseLocalEvent(uidB, ref ev2, true);
|
||||
@@ -618,10 +628,10 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
ArrayPool<Contact>.Shared.Return(contacts);
|
||||
ArrayPool<ContactStatus>.Shared.Return(status);
|
||||
ArrayPool<Vector2>.Shared.Return(worldPoints);
|
||||
ArrayPool<FixedArray4<Vector2>>.Shared.Return(worldPoints);
|
||||
}
|
||||
|
||||
private void BuildManifolds(Contact[] contacts, int count, ContactStatus[] status, Vector2[] worldPoints)
|
||||
private void BuildManifolds(Contact[] contacts, int count, ContactStatus[] status, FixedArray4<Vector2>[] worldPoints)
|
||||
{
|
||||
if (count == 0)
|
||||
return;
|
||||
@@ -664,7 +674,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
public Contact[] Contacts;
|
||||
public ContactStatus[] Status;
|
||||
public Vector2[] WorldPoints;
|
||||
public FixedArray4<Vector2>[] WorldPoints;
|
||||
public bool[] Wake;
|
||||
|
||||
public void Execute(int index)
|
||||
@@ -673,7 +683,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateContact(Contact[] contacts, int index, ContactStatus[] status, bool[] wake, Vector2[] worldPoints)
|
||||
private void UpdateContact(Contact[] contacts, int index, ContactStatus[] status, bool[] wake, FixedArray4<Vector2>[] worldPoints)
|
||||
{
|
||||
var contact = contacts[index];
|
||||
|
||||
@@ -698,7 +708,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
if (contactStatus == ContactStatus.StartTouching)
|
||||
{
|
||||
worldPoints[index] = Physics.Transform.Mul(bodyATransform, contacts[index].Manifold.LocalPoint);
|
||||
var points = new FixedArray4<Vector2>();
|
||||
contact.GetWorldManifold(bodyATransform, bodyBTransform, out var worldNormal, points.AsSpan);
|
||||
// Use the 3rd Vector2 as the world normal, 4th is blank.
|
||||
points._02 = worldNormal;
|
||||
worldPoints[index] = points;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public readonly struct ResPath : IEquatable<ResPath>
|
||||
public ResPath(string canonPath)
|
||||
{
|
||||
// Paths should never have non-standardised directory separators passed in, the caller should have already sanitised it.
|
||||
DebugTools.Assert(!canonPath.Contains('\\'));
|
||||
DebugTools.Assert(IsValidPath(canonPath));
|
||||
CanonPath = canonPath;
|
||||
}
|
||||
|
||||
@@ -77,6 +77,21 @@ public readonly struct ResPath : IEquatable<ResPath>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the given string paths contains any non-standard directory separators.
|
||||
/// </summary>
|
||||
public static bool IsValidPath(string path) => !path.Contains('\\');
|
||||
|
||||
/// <summary>
|
||||
/// Check whether a string is a valid path (<see cref="IsValidPath"/>) and corresponds to a simple file name.
|
||||
/// </summary>
|
||||
public static bool IsValidFilename([NotNullWhen(true)] string? filename)
|
||||
=> !string.IsNullOrEmpty(filename)
|
||||
&& IsValidPath(filename)
|
||||
&& !filename.Contains('/')
|
||||
&& filename != "."
|
||||
&& filename != "..";
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the path is equal to "."
|
||||
/// </summary>
|
||||
@@ -106,7 +121,7 @@ public readonly struct ResPath : IEquatable<ResPath>
|
||||
}
|
||||
|
||||
var ind = CanonPath.Length > 1 && CanonPath[^1] == '/'
|
||||
? CanonPath[..^1].LastIndexOf('/')
|
||||
? CanonPath.LastIndexOf('/', CanonPath.Length - 2)
|
||||
: CanonPath.LastIndexOf('/');
|
||||
return ind switch
|
||||
{
|
||||
@@ -206,7 +221,7 @@ public readonly struct ResPath : IEquatable<ResPath>
|
||||
// it's a filename
|
||||
// Uses +1 to skip `/` found in or starts from beginning of string
|
||||
// if we found nothing (ind == -1)
|
||||
var ind = CanonPath[..^1].LastIndexOf('/') + 1;
|
||||
var ind = CanonPath.LastIndexOf('/', CanonPath.Length - 2) + 1;
|
||||
return CanonPath[^1] == '/'
|
||||
? CanonPath[ind .. ^1] // Omit last `/`
|
||||
: CanonPath[ind..];
|
||||
@@ -242,7 +257,6 @@ public readonly struct ResPath : IEquatable<ResPath>
|
||||
return CanonPath.GetHashCode();
|
||||
}
|
||||
|
||||
|
||||
public static bool operator ==(ResPath left, ResPath right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
@@ -283,7 +297,7 @@ public readonly struct ResPath : IEquatable<ResPath>
|
||||
}
|
||||
|
||||
// Avoid double separators
|
||||
if (left.CanonPath.EndsWith("/"))
|
||||
if (left.CanonPath.EndsWith('/'))
|
||||
{
|
||||
return new ResPath(left.CanonPath + right.CanonPath);
|
||||
}
|
||||
@@ -475,7 +489,7 @@ public readonly struct ResPath : IEquatable<ResPath>
|
||||
/// </summary>
|
||||
public string ToRelativeSystemPath()
|
||||
{
|
||||
return ToRelativePath().ChangeSeparator(SystemSeparatorStr);
|
||||
return ToRelativePath().ChangeSeparator(SystemSeparator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -495,14 +509,19 @@ public readonly struct ResPath : IEquatable<ResPath>
|
||||
/// </summary>
|
||||
public string ChangeSeparator(string newSeparator)
|
||||
{
|
||||
if (newSeparator is "." or "\0")
|
||||
{
|
||||
throw new ArgumentException("New separator can't be `.` or `NULL`");
|
||||
}
|
||||
if (newSeparator.Length != 1)
|
||||
throw new InvalidOperationException("new separator must be a single character.");
|
||||
return ChangeSeparator(newSeparator[0]);
|
||||
}
|
||||
|
||||
return newSeparator == "/"
|
||||
? CanonPath
|
||||
: CanonPath.Replace("/", newSeparator);
|
||||
/// <inheritdoc cref="ChangeSeparator(string)"/>
|
||||
public string ChangeSeparator(char newSeparator)
|
||||
{
|
||||
if (newSeparator is '.' or '\0')
|
||||
throw new ArgumentException("New separator can't be `.` or `NULL`");
|
||||
|
||||
// String.Replace() already checks if newSeparator == '/'
|
||||
return CanonPath.Replace('/', newSeparator);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ public sealed class GenericEntityPrint
|
||||
{
|
||||
public EntityUid Owner;
|
||||
{{fields.ToString().TrimEnd()}}
|
||||
EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
readonly EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
|
||||
public Entity(EntityUid owner{{parameters}})
|
||||
{
|
||||
@@ -212,9 +212,9 @@ public sealed class GenericEntityPrint
|
||||
}
|
||||
{{castRegion}}
|
||||
|
||||
public override int GetHashCode() => Owner.GetHashCode();
|
||||
public Entity<{{nullableGenerics}}> AsNullable() => new(Owner{{selfAccess}});
|
||||
public EntityUid AsType() => Owner;
|
||||
public override readonly int GetHashCode() => Owner.GetHashCode();
|
||||
public readonly Entity<{{nullableGenerics}}> AsNullable() => new(Owner{{selfAccess}});
|
||||
public readonly EntityUid AsType() => Owner;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Numerics;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
@@ -17,6 +18,61 @@ namespace Robust.UnitTesting.Shared.Physics;
|
||||
[TestFixture]
|
||||
public sealed class Broadphase_Test
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests that spawned static ents properly collide with entities in range.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestStaticSpawn()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.InitializeInstance();
|
||||
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var fixtureSystem = entManager.System<FixtureSystem>();
|
||||
var physicsSystem = entManager.System<SharedPhysicsSystem>();
|
||||
|
||||
var (mapEnt, mapId) = sim.CreateMap();
|
||||
|
||||
var dynamicEnt = entManager.SpawnAtPosition(null, new EntityCoordinates(mapEnt, Vector2.Zero));
|
||||
var dynamicBody = entManager.AddComponent<PhysicsComponent>(dynamicEnt);
|
||||
physicsSystem.SetBodyType(dynamicEnt, BodyType.Dynamic, body: dynamicBody);
|
||||
|
||||
fixtureSystem.TryCreateFixture(dynamicEnt, new PhysShapeCircle(1f), "fix1", collisionMask: 10);
|
||||
physicsSystem.WakeBody(dynamicEnt, body: dynamicBody);
|
||||
|
||||
Assert.That(dynamicBody.Awake);
|
||||
|
||||
physicsSystem.SetAwake((dynamicEnt, dynamicBody), false);
|
||||
Assert.That(!dynamicBody.Awake);
|
||||
|
||||
// Clear move buffer
|
||||
entManager.System<SharedBroadphaseSystem>().FindNewContacts(
|
||||
entManager.GetComponent<PhysicsMapComponent>(mapEnt),
|
||||
entManager.GetComponent<MapComponent>(mapEnt).MapId);
|
||||
|
||||
var staticEnt = entManager.SpawnAtPosition(null, new EntityCoordinates(mapEnt, Vector2.Zero));
|
||||
var staticBody = entManager.AddComponent<PhysicsComponent>(staticEnt);
|
||||
physicsSystem.SetBodyType(staticEnt, BodyType.Static, body: staticBody);
|
||||
|
||||
fixtureSystem.TryCreateFixture(staticEnt, new PhysShapeCircle(1f), "fix1", collisionLayer: 10);
|
||||
physicsSystem.SetCanCollide(staticEnt, true);
|
||||
|
||||
Assert.That(!staticBody.Awake);
|
||||
Assert.That(staticBody.ContactCount, Is.EqualTo(0));
|
||||
|
||||
entManager.System<SharedBroadphaseSystem>().FindNewContacts(
|
||||
entManager.GetComponent<PhysicsMapComponent>(mapEnt),
|
||||
entManager.GetComponent<MapComponent>(mapEnt).MapId);
|
||||
|
||||
Assert.That(staticBody.ContactCount, Is.EqualTo(1));
|
||||
|
||||
physicsSystem.CollideContacts();
|
||||
|
||||
// Make sure it's actually marked as touching and not just "well it's in range right".
|
||||
Assert.That(staticBody.Contacts.First!.Value.IsTouching, Is.EqualTo(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we reparent a sundries entity to another broadphase does it correctly update.
|
||||
/// </summary>
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var fixtureSystem = server.ResolveDependency<IEntitySystemManager>()
|
||||
.GetEntitySystem<FixtureSystem>();
|
||||
var physicsSystem = server.ResolveDependency<IEntitySystemManager>()
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Robust.UnitTesting.Shared.Resources
|
||||
_testDir = Directory.CreateDirectory(_testDirPath);
|
||||
var subDir = Path.Combine(_testDirPath, "writable");
|
||||
|
||||
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir));
|
||||
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir), hideRootDir: false);
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
|
||||
@@ -36,13 +36,17 @@ public sealed class ResPathTest
|
||||
[TestCase("x/y/z", ExpectedResult = "z")]
|
||||
[TestCase("/bar", ExpectedResult = "bar")]
|
||||
[TestCase("bar/", ExpectedResult = "bar")] // Trailing / gets trimmed.
|
||||
[TestCase("/foo/bar/", ExpectedResult = "bar")]
|
||||
// These next two tests are the current behaviour. I don't know if this is how it should behave, these tests just
|
||||
// ensure that it doesn't change unintentionally
|
||||
[TestCase("/foo/bar//", ExpectedResult = "")]
|
||||
[TestCase("/foo/bar///", ExpectedResult = "")]
|
||||
public string FilenameTest(string input)
|
||||
{
|
||||
var resPathFilename = new ResPath(input).Filename;
|
||||
return resPathFilename;
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
[TestCase(@"", ExpectedResult = @".")]
|
||||
[TestCase(@".", ExpectedResult = @".")]
|
||||
@@ -66,6 +70,11 @@ public sealed class ResPathTest
|
||||
[TestCase(@"/foo/bar/x", ExpectedResult = @"/foo/bar")]
|
||||
[TestCase(@"/foo/bar.txt", ExpectedResult = @"/foo")]
|
||||
[TestCase(@"/bar.txt", ExpectedResult = @"/")]
|
||||
// These next three tests are the current behaviour. I don't know if this is how it should behave, these tests just
|
||||
// ensure that it doesn't change unintentionally
|
||||
[TestCase(@"/foo/bar//", ExpectedResult = "/foo/bar")]
|
||||
[TestCase(@"/foo/bar///", ExpectedResult = "/foo/bar/")]
|
||||
[TestCase(@"/foo/bar////", ExpectedResult = "/foo/bar//")]
|
||||
public string DirectoryTest(string path)
|
||||
{
|
||||
var resPathDirectory = new ResPath(path).Directory.ToString();
|
||||
@@ -73,8 +82,7 @@ public sealed class ResPathTest
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(@"a/b/c", "👏", ExpectedResult = "a👏b👏c")]
|
||||
[TestCase(@"/a/b/c", "👏", ExpectedResult = "👏a👏b👏c")]
|
||||
[TestCase(@"a/b/c", "\\", ExpectedResult = @"a\b\c")]
|
||||
[TestCase(@"/a/b/c", "\\", ExpectedResult = @"\a\b\c")]
|
||||
public string ChangeSeparatorTest(string input, string separator)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user