mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
2 Commits
v0.8.74
...
prototype-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d49075a970 | ||
|
|
a67acd453c |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -11,7 +11,7 @@
|
||||
/Robust.Analyzers @PaulRitter
|
||||
/Robust.*/GameStates @PaulRitter
|
||||
/Robust.Shared/Analyzers @PaulRitter
|
||||
/Robust.*/Serialization @PaulRitter @DrSmugleaf
|
||||
/Robust.*/Serialization @PaulRitter
|
||||
/Robust.*/Prototypes @PaulRitter
|
||||
/Robust.Shared/GameObjects/ComponentDependencies @PaulRitter
|
||||
/Robust.*/Containers @PaulRitter
|
||||
|
||||
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
dotnet-version: 6.0.100
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -76,5 +76,3 @@ MSBuild/Robust.Custom.targets
|
||||
|
||||
|
||||
release/
|
||||
Robust.Docfx/*-site
|
||||
Robust.Docfx/api
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<PropertyGroup><Version>0.8.74</Version></PropertyGroup>
|
||||
<PropertyGroup><Version>0.8.52</Version></PropertyGroup>
|
||||
</Project>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 535 B |
@@ -6,9 +6,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="3.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="17.0.0" />
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="16.8.0" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
|
||||
<PackageReference Include="Pidgin" Version="2.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IPlayerManager _playMan = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _entityLookup = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
@@ -212,6 +213,7 @@ namespace Robust.Client
|
||||
{
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
_entityLookup.Startup();
|
||||
|
||||
_timing.ResetSimTime();
|
||||
_timing.Paused = false;
|
||||
@@ -222,6 +224,7 @@ namespace Robust.Client
|
||||
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
|
||||
_gameStates.Reset();
|
||||
_playMan.Shutdown();
|
||||
_entityLookup.Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
_mapManager.Shutdown();
|
||||
_discord.ClearPresence();
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Robust.Client
|
||||
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
|
||||
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
|
||||
IoCManager.Register<IEntityManager, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityLookup, EntityLookup>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
@@ -71,6 +72,7 @@ namespace Robust.Client
|
||||
IoCManager.Register<IStateManager, StateManager>();
|
||||
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
|
||||
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
|
||||
IoCManager.Register<IDebugDrawing, DebugDrawing>();
|
||||
IoCManager.Register<ILightManager, LightManager>();
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
|
||||
@@ -109,13 +109,13 @@ namespace Robust.Client.Console
|
||||
if (AvailableCommands.ContainsKey(commandName))
|
||||
{
|
||||
var playerManager = IoCManager.Resolve<IPlayerManager>();
|
||||
#if !DEBUG
|
||||
|
||||
if (!_conGroup.CanCommand(commandName) && playerManager.LocalPlayer?.Session.Status > SessionStatus.Connecting)
|
||||
{
|
||||
WriteError(null, $"Insufficient perms for command: {commandName}");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
var command1 = AvailableCommands[commandName];
|
||||
args.RemoveAt(0);
|
||||
var shell = new ConsoleShell(this, null);
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = EntitySystem.Get<DebugDrawingSystem>();
|
||||
var mgr = IoCManager.Resolve<IDebugDrawing>();
|
||||
mgr.DebugPositions = !mgr.DebugPositions;
|
||||
}
|
||||
}
|
||||
@@ -861,7 +861,7 @@ namespace Robust.Client.Console.Commands
|
||||
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
|
||||
var chunk = internalGrid.GetChunk(chunkIndex);
|
||||
|
||||
shell.WriteLine($"worldBounds: {internalGrid.CalcWorldAABB(chunk)} localBounds: {chunk.CachedBounds}");
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,21 +6,17 @@ using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of visual debug overlays for the client game.
|
||||
/// </summary>
|
||||
public sealed class DebugDrawingSystem : EntitySystem
|
||||
/// <inheritdoc />
|
||||
public sealed class DebugDrawing : IDebugDrawing
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
|
||||
private bool _debugPositions;
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public bool DebugPositions
|
||||
{
|
||||
get => _debugPositions;
|
||||
@@ -46,13 +42,13 @@ namespace Robust.Client.Debugging
|
||||
|
||||
private sealed class EntityPositionOverlay : Overlay
|
||||
{
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly IEntityLookup _lookup;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IEntityManager _entityManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityPositionOverlay(EntityLookupSystem lookup, IEyeManager eyeManager, IEntityManager entityManager)
|
||||
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager, IEntityManager entityManager)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_eyeManager = eyeManager;
|
||||
@@ -65,11 +61,13 @@ namespace Robust.Client.Debugging
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
|
||||
{
|
||||
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(entity);
|
||||
|
||||
var center = transform.WorldPosition;
|
||||
var worldRotation = transform.WorldRotation;
|
||||
|
||||
var xLine = worldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = worldRotation.RotateVec(Vector2.UnitY);
|
||||
13
Robust.Client/Debugging/IDebugDrawing.cs
Normal file
13
Robust.Client/Debugging/IDebugDrawing.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of visual debug overlays for the client game.
|
||||
/// </summary>
|
||||
public interface IDebugDrawing
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// </summary>
|
||||
bool DebugPositions { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
[Dependency] private readonly ITimerManager _timerManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IOverlayManagerInternal _overlayManager = default!;
|
||||
@@ -454,7 +455,6 @@ namespace Robust.Client
|
||||
private void Tick(FrameEventArgs frameEventArgs)
|
||||
{
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
|
||||
_console.CommandBufferExecute();
|
||||
_timerManager.UpdateTimers(frameEventArgs);
|
||||
_taskManager.ProcessPendingTasks();
|
||||
|
||||
@@ -470,6 +470,7 @@ namespace Robust.Client
|
||||
// The last real tick is the current tick! This way we won't be in "prediction" mode.
|
||||
_gameTiming.LastRealTick = _gameTiming.CurTick;
|
||||
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
|
||||
_lookup.Update();
|
||||
}
|
||||
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
|
||||
@@ -509,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.Warning;
|
||||
logManager.GetSawmill("loc").Level = LogLevel.Error;
|
||||
|
||||
#if DEBUG_ONLY_FCE_INFO
|
||||
#if DEBUG_ONLY_FCE_LOG
|
||||
@@ -574,6 +575,7 @@ namespace Robust.Client
|
||||
|
||||
_networkManager.Shutdown("Client shutting down");
|
||||
_midiManager.Shutdown();
|
||||
IoCManager.Resolve<IEntityLookup>().Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
_clyde.Shutdown();
|
||||
_clydeAudio.Shutdown();
|
||||
|
||||
@@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(OccluderComponent))]
|
||||
public sealed class ClientOccluderComponent : OccluderComponent
|
||||
internal sealed class ClientOccluderComponent : OccluderComponent
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
@@ -71,29 +71,13 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Occluding = OccluderDir.None;
|
||||
|
||||
if (Deleted)
|
||||
return;
|
||||
|
||||
// Content may want to override the default behavior for occlusion.
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(Owner);
|
||||
var ev = new OccluderDirectionsEvent
|
||||
if (Deleted || !_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
|
||||
{
|
||||
Component = xform,
|
||||
};
|
||||
|
||||
_entityManager.EventBus.RaiseLocalEvent(Owner, ref ev);
|
||||
|
||||
if (ev.Handled)
|
||||
{
|
||||
Occluding = ev.Directions;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!xform.Anchored)
|
||||
return;
|
||||
|
||||
var grid = _mapManager.GetGrid(xform.GridID);
|
||||
var position = xform.Coordinates;
|
||||
var grid = _mapManager.GetGrid(_entityManager.GetComponent<TransformComponent>(Owner).GridID);
|
||||
var position = _entityManager.GetComponent<TransformComponent>(Owner).Coordinates;
|
||||
void CheckDir(Direction dir, OccluderDir oclDir)
|
||||
{
|
||||
foreach (var neighbor in grid.GetInDir(position, dir))
|
||||
@@ -106,7 +90,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
var angle = xform.LocalRotation;
|
||||
var angle = _entityManager.GetComponent<TransformComponent>(Owner).LocalRotation;
|
||||
var dirRolling = angle.GetCardinalDir();
|
||||
// dirRolling starts at effective south
|
||||
|
||||
@@ -121,28 +105,15 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
CheckDir(dirRolling, OccluderDir.East);
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum OccluderDir : byte
|
||||
{
|
||||
None = 0,
|
||||
North = 1,
|
||||
East = 1 << 1,
|
||||
South = 1 << 2,
|
||||
West = 1 << 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised by occluders when trying to get occlusion directions.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct OccluderDirectionsEvent
|
||||
{
|
||||
public bool Handled = false;
|
||||
public OccluderDir Directions = OccluderDir.None;
|
||||
public TransformComponent Component = default!;
|
||||
|
||||
public OccluderDirectionsEvent() {}
|
||||
[Flags]
|
||||
internal enum OccluderDir : byte
|
||||
{
|
||||
None = 0,
|
||||
North = 1,
|
||||
East = 1 << 1,
|
||||
South = 1 << 2,
|
||||
West = 1 << 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +119,7 @@ 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>
|
||||
/// <returns>Index of the new layer.</returns>
|
||||
int LayerMapReserveBlank(object key);
|
||||
void LayerMapReserveBlank(object key);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a layer without texture (thus falling back to the error texture).
|
||||
@@ -146,8 +145,8 @@ namespace Robust.Client.GameObjects
|
||||
void RemoveLayer(int layer);
|
||||
void RemoveLayer(object layerKey);
|
||||
|
||||
void LayerSetShader(int layer, ShaderInstance shader, string? prototype = null);
|
||||
void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null);
|
||||
void LayerSetShader(int layer, ShaderInstance shader);
|
||||
void LayerSetShader(object layerKey, ShaderInstance shader);
|
||||
void LayerSetShader(int layer, string shaderName);
|
||||
void LayerSetShader(object layerKey, string shaderName);
|
||||
|
||||
@@ -218,8 +217,8 @@ namespace Robust.Client.GameObjects
|
||||
int GetLayerDirectionCount(ISpriteLayer layer);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the rotated sprite bounding box in world-space coordinates.
|
||||
/// Calculate sprite bounding box in world-space coordinates.
|
||||
/// </summary>
|
||||
Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, IEye? eye = null);
|
||||
Box2 CalculateBoundingBox(Vector2 worldPos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ 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.
|
||||
@@ -35,6 +37,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(Angle worldAngle);
|
||||
Box2 CalculateBoundingBox();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,16 +84,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var sprite in comp.SpriteTree.QueryAabb(localAABB))
|
||||
{
|
||||
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);
|
||||
|
||||
var worldPos = _entityManager.GetComponent<TransformComponent>(sprite.Owner).WorldPosition;
|
||||
var bounds = sprite.CalculateBoundingBox(worldPos);
|
||||
handle.DrawRect(bounds, Color.Red.WithAlpha(0.2f));
|
||||
handle.DrawRect(southIndicator, Color.Blue.WithAlpha(0.5f));
|
||||
handle.DrawRect(bounds.Scale(0.2f).Translated(-new Vector2(0f, bounds.Extents.Y)), Color.Blue.WithAlpha(0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ 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
|
||||
{
|
||||
@@ -32,8 +31,6 @@ 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;
|
||||
@@ -75,11 +72,7 @@ namespace Robust.Client.GameObjects
|
||||
public Vector2 Scale
|
||||
{
|
||||
get => scale;
|
||||
set
|
||||
{
|
||||
scale = value;
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
set => scale = value;
|
||||
}
|
||||
|
||||
[DataField("rotation")]
|
||||
@@ -90,11 +83,7 @@ namespace Robust.Client.GameObjects
|
||||
public Angle Rotation
|
||||
{
|
||||
get => rotation;
|
||||
set
|
||||
{
|
||||
rotation = value;
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
set => rotation = value;
|
||||
}
|
||||
|
||||
[DataField("offset")]
|
||||
@@ -108,18 +97,12 @@ namespace Robust.Client.GameObjects
|
||||
public Vector2 Offset
|
||||
{
|
||||
get => offset;
|
||||
set
|
||||
{
|
||||
offset = value;
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
set => offset = value;
|
||||
}
|
||||
|
||||
[DataField("color")]
|
||||
private Color color = Color.White;
|
||||
|
||||
public Matrix3 LocalMatrix = Matrix3.Identity;
|
||||
|
||||
[Animatable]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Color Color
|
||||
@@ -151,7 +134,113 @@ namespace Robust.Client.GameObjects
|
||||
Layers.Clear();
|
||||
foreach (var layerDatum in value)
|
||||
{
|
||||
AddLayer(layerDatum);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
@@ -247,13 +336,11 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(rsi))
|
||||
{
|
||||
var rsiPath = TextureRoot / rsi;
|
||||
if(resourceCache.TryGetResource(rsiPath, out RSIResource? resource))
|
||||
if(IoCManager.Resolve<IResourceCache>().TryGetResource(rsiPath, out RSIResource? resource))
|
||||
{
|
||||
BaseRSI = resource.RSI;
|
||||
}
|
||||
@@ -286,8 +373,6 @@ namespace Robust.Client.GameObjects
|
||||
LayerMap.Clear();
|
||||
LayerDatums = layerDatums;
|
||||
}
|
||||
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -305,7 +390,6 @@ namespace Robust.Client.GameObjects
|
||||
offset = other.offset;
|
||||
rotation = other.rotation;
|
||||
scale = other.scale;
|
||||
UpdateLocalMatrix();
|
||||
drawDepth = other.drawDepth;
|
||||
_screenLock = other._screenLock;
|
||||
_overrideDirection = other._overrideDirection;
|
||||
@@ -331,14 +415,9 @@ namespace Robust.Client.GameObjects
|
||||
RenderOrder = other.RenderOrder;
|
||||
}
|
||||
|
||||
internal void UpdateLocalMatrix()
|
||||
{
|
||||
LocalMatrix = Matrix3.CreateTransform(in offset, in rotation, in scale);
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrix()
|
||||
{
|
||||
return LocalMatrix;
|
||||
return Matrix3.CreateTransform(in offset, in rotation, in scale);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -383,17 +462,14 @@ namespace Robust.Client.GameObjects
|
||||
_layerMapShared = false;
|
||||
}
|
||||
|
||||
public int LayerMapReserveBlank(object key)
|
||||
public void LayerMapReserveBlank(object key)
|
||||
{
|
||||
if (LayerMapTryGet(key, out var index))
|
||||
if (LayerMapTryGet(key, out var _))
|
||||
{
|
||||
return index;
|
||||
return;
|
||||
}
|
||||
|
||||
index = AddBlankLayer();
|
||||
LayerMapSet(key, index);
|
||||
|
||||
return index;
|
||||
LayerMapSet(key, AddBlankLayer());
|
||||
}
|
||||
|
||||
public int AddBlankLayer(int? newIndex = null)
|
||||
@@ -402,19 +478,6 @@ 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);
|
||||
@@ -590,144 +653,7 @@ namespace Robust.Client.GameObjects
|
||||
RemoveLayer(layer);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
public void LayerSetShader(int layer, ShaderInstance? shader)
|
||||
{
|
||||
if (Layers.Count <= layer)
|
||||
{
|
||||
@@ -738,10 +664,9 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var theLayer = Layers[layer];
|
||||
theLayer.Shader = shader;
|
||||
theLayer.ShaderPrototype = prototype;
|
||||
}
|
||||
|
||||
public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null)
|
||||
public void LayerSetShader(object layerKey, ShaderInstance shader)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer))
|
||||
{
|
||||
@@ -750,7 +675,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
LayerSetShader(layer, shader, prototype);
|
||||
LayerSetShader(layer, shader);
|
||||
}
|
||||
|
||||
public void LayerSetShader(int layer, string shaderName)
|
||||
@@ -759,12 +684,10 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Shader prototype '{0}' does not exist. Trace:\n{1}", shaderName,
|
||||
Environment.StackTrace);
|
||||
|
||||
LayerSetShader(layer, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
LayerSetShader(layer, prototype.Instance(), shaderName);
|
||||
// This will set the shader to null if it does not exist.
|
||||
LayerSetShader(layer, prototype?.Instance());
|
||||
}
|
||||
|
||||
public void LayerSetShader(object layerKey, string shaderName)
|
||||
@@ -1225,7 +1148,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
Layers[layer].Offset = layerOffset;
|
||||
Layers[layer].SetOffset(layerOffset);
|
||||
}
|
||||
|
||||
public void LayerSetOffset(object layerKey, Vector2 layerOffset)
|
||||
@@ -1307,6 +1230,25 @@ 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
|
||||
@@ -1315,16 +1257,56 @@ namespace Robust.Client.GameObjects
|
||||
if (worldRotation.Theta < 0)
|
||||
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
|
||||
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)
|
||||
{
|
||||
layer.Render(drawingHandle, ref transform, angle, overrideDirection);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1360,6 +1342,27 @@ 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)
|
||||
@@ -1414,14 +1417,12 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
Visible = thestate.Visible;
|
||||
DrawDepth = thestate.DrawDepth;
|
||||
scale = thestate.Scale;
|
||||
rotation = thestate.Rotation;
|
||||
offset = thestate.Offset;
|
||||
UpdateLocalMatrix();
|
||||
Scale = thestate.Scale;
|
||||
Rotation = thestate.Rotation;
|
||||
Offset = thestate.Offset;
|
||||
Color = thestate.Color;
|
||||
RenderOrder = thestate.RenderOrder;
|
||||
|
||||
|
||||
if (thestate.BaseRsiPath != null && BaseRSI != null)
|
||||
{
|
||||
if (resourceCache.TryGetResource<RSIResource>(TextureRoot / thestate.BaseRsiPath, out var res))
|
||||
@@ -1437,8 +1438,43 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe optimize this to NOT fully clear the layers. (see LayerDatums setter function)
|
||||
LayerDatums = thestate.Layers;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueUpdateIsInert()
|
||||
@@ -1517,61 +1553,35 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, IEye? eye = null)
|
||||
public Box2 CalculateBoundingBox(Vector2 worldPos)
|
||||
{
|
||||
// fast check for empty sprites
|
||||
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;
|
||||
if (Layers.Count == 0)
|
||||
return new Box2(worldPos, worldPos);
|
||||
|
||||
// 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 highest topRight points from all layers
|
||||
var box = Layers[0].CalculateBoundingBox(relativeRotation);
|
||||
// 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();
|
||||
|
||||
for (int i = 1; i < Layers.Count; i++)
|
||||
{
|
||||
var layer = Layers[i];
|
||||
if (!layer.Visible) continue;
|
||||
var layerBB = layer.CalculateBoundingBox(relativeRotation);
|
||||
var layerBB = layer.CalculateBoundingBox();
|
||||
|
||||
box = box.Union(layerBB);
|
||||
}
|
||||
|
||||
// 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:
|
||||
// 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;
|
||||
|
||||
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);
|
||||
// move it all to world transform system (with sprite offset)
|
||||
var worldBB = spriteBB.Translated(Offset + worldPos);
|
||||
return worldBB;
|
||||
}
|
||||
|
||||
internal void UpdateBounds()
|
||||
@@ -1605,11 +1615,10 @@ namespace Robust.Client.GameObjects
|
||||
Flip = 3,
|
||||
}
|
||||
|
||||
public sealed class Layer : ISpriteLayer, ISerializationHooks
|
||||
public sealed class Layer : ISpriteLayer
|
||||
{
|
||||
[ViewVariables] private readonly SpriteComponent _parent;
|
||||
|
||||
[ViewVariables] public string? ShaderPrototype;
|
||||
[ViewVariables] public ShaderInstance? Shader;
|
||||
[ViewVariables] public Texture? Texture;
|
||||
|
||||
@@ -1619,37 +1628,11 @@ namespace Robust.Client.GameObjects
|
||||
[ViewVariables] public float AnimationTime;
|
||||
[ViewVariables] public int AnimationFrame;
|
||||
|
||||
public Matrix3 LocalMatrix = Matrix3.Identity;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Scale { get; set; } = Vector2.One;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
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;
|
||||
public Angle Rotation { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Visible = true;
|
||||
@@ -1669,7 +1652,6 @@ namespace Robust.Client.GameObjects
|
||||
if (_offset.EqualsApprox(value)) return;
|
||||
|
||||
_offset = value;
|
||||
UpdateLocalMatrix();
|
||||
_parent.UpdateBounds();
|
||||
}
|
||||
}
|
||||
@@ -1693,7 +1675,6 @@ 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;
|
||||
@@ -1701,26 +1682,14 @@ namespace Robust.Client.GameObjects
|
||||
AnimationTimeLeft = toClone.AnimationTimeLeft;
|
||||
AnimationTime = toClone.AnimationTime;
|
||||
AnimationFrame = toClone.AnimationFrame;
|
||||
_scale = toClone.Scale;
|
||||
_rotation = toClone.Rotation;
|
||||
_offset = toClone.Offset;
|
||||
UpdateLocalMatrix();
|
||||
Scale = toClone.Scale;
|
||||
Rotation = toClone.Rotation;
|
||||
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); }
|
||||
@@ -1732,7 +1701,7 @@ namespace Robust.Client.GameObjects
|
||||
Color = Color,
|
||||
Rotation = Rotation,
|
||||
Scale = Scale,
|
||||
Shader = ShaderPrototype,
|
||||
//todo Shader = Shader,
|
||||
State = State.Name,
|
||||
Visible = Visible,
|
||||
RsiPath = RSI?.Path?.ToString(),
|
||||
@@ -1761,7 +1730,7 @@ namespace Robust.Client.GameObjects
|
||||
set => SetAutoAnimated(value);
|
||||
}
|
||||
|
||||
public RSIDirection EffectiveDirection(Angle worldRotation)
|
||||
public RSI.State.Direction EffectiveDirection(Angle worldRotation)
|
||||
{
|
||||
if (State == default)
|
||||
{
|
||||
@@ -1782,16 +1751,22 @@ namespace Robust.Client.GameObjects
|
||||
return default;
|
||||
}
|
||||
|
||||
public RSIDirection EffectiveDirection(RSI.State state, Angle worldRotation,
|
||||
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,
|
||||
Direction? overrideDirection)
|
||||
{
|
||||
if (state.Directions == RSI.State.DirectionType.Dir1)
|
||||
{
|
||||
return RSIDirection.South;
|
||||
return RSI.State.Direction.South;
|
||||
}
|
||||
else
|
||||
{
|
||||
RSIDirection dir;
|
||||
RSI.State.Direction dir;
|
||||
if (overrideDirection != null)
|
||||
{
|
||||
dir = overrideDirection.Value.Convert(state.Directions);
|
||||
@@ -1874,7 +1849,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "State '{0}' does not exist in set RSI ({1}). Trace:\n{2}", State, rsi?.Path?.ToString() ?? "null",
|
||||
Logger.ErrorS(LogCategory, "State '{0}' does not exist in set RSI. Trace:\n{1}", State,
|
||||
Environment.StackTrace);
|
||||
Texture = null;
|
||||
}
|
||||
@@ -1923,6 +1898,11 @@ namespace Robust.Client.GameObjects
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetOffset(Vector2 offset)
|
||||
{
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Vector2i PixelSize
|
||||
{
|
||||
@@ -1943,121 +1923,10 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box2 CalculateBoundingBox(Angle angle)
|
||||
public Box2 CalculateBoundingBox()
|
||||
{
|
||||
// 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);
|
||||
// TODO: scale & rotation for layers is currently unimplemented.
|
||||
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2165,7 +2034,6 @@ namespace Robust.Client.GameObjects
|
||||
return results;
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
var icon = IconComponent.GetPrototypeIcon(prototype, resourceCache);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -7,7 +6,7 @@ using Robust.Shared.GameStates;
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
internal sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
{
|
||||
private readonly Queue<ClientAppearanceComponent> _queuedUpdates = new();
|
||||
|
||||
@@ -51,33 +50,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
if (!stateDiff) return;
|
||||
|
||||
component.AppearanceData = CloneAppearanceData(actualState.Data);
|
||||
component.AppearanceData = actualState.Data;
|
||||
MarkDirty(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take in an appearance data dictionary and attempt to clone it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As some appearance data values are not simple value-type objects, this is not just a shallow clone.
|
||||
/// </remarks>
|
||||
private Dictionary<object, object> CloneAppearanceData(Dictionary<object, object> data)
|
||||
{
|
||||
Dictionary<object, object> newDict = new(data.Count);
|
||||
|
||||
foreach (var (key, value) in data)
|
||||
{
|
||||
if (value.GetType().IsValueType)
|
||||
newDict[key] = value;
|
||||
else if (value is ICloneable cloneable)
|
||||
newDict[key] = cloneable.Clone();
|
||||
else
|
||||
throw new NotSupportedException("Invalid object in appearance data dictionary. Appearance data must be cloneable");
|
||||
}
|
||||
|
||||
return newDict;
|
||||
}
|
||||
|
||||
public override void MarkDirty(AppearanceComponent component)
|
||||
{
|
||||
if (component.AppearanceDirty)
|
||||
@@ -131,7 +107,7 @@ namespace Robust.Client.GameObjects
|
||||
[ByRefEvent]
|
||||
public struct AppearanceChangeEvent
|
||||
{
|
||||
public AppearanceComponent Component;
|
||||
public IReadOnlyDictionary<object, object> AppearanceData;
|
||||
public AppearanceComponent Component = default!;
|
||||
public IReadOnlyDictionary<object, object> AppearanceData = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace Robust.Client.GameObjects
|
||||
// This container is expecting an entity... but it got parented to some other entity???
|
||||
// Ah well, the sever should send a new container state that updates expected entities so just ignore it for now.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RemoveExpectedEntity(message.Entity);
|
||||
|
||||
@@ -210,101 +210,68 @@ namespace Robust.Client.GameObjects
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
var pointQuery = EntityManager.GetEntityQuery<PointLightComponent>();
|
||||
var spriteQuery = EntityManager.GetEntityQuery<SpriteComponent>();
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
{
|
||||
if (Deleted(toUpdate))
|
||||
if (EntityManager.Deleted(toUpdate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateEntityRecursively(toUpdate, xformQuery, pointQuery, spriteQuery);
|
||||
UpdateEntityRecursively(toUpdate);
|
||||
}
|
||||
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private void UpdateEntityRecursively(
|
||||
EntityUid entity,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PointLightComponent> pointQuery,
|
||||
EntityQuery<SpriteComponent> spriteQuery)
|
||||
private void UpdateEntityRecursively(EntityUid entity)
|
||||
{
|
||||
// Recursively go up parents and containers to see whether both sprites and lights need to be occluded
|
||||
// Could maybe optimise this more by checking nearest parent that has sprite / light and whether it's container
|
||||
// occluded but this probably isn't a big perf issue.
|
||||
var xform = xformQuery.GetComponent(entity);
|
||||
var parent = xform.ParentUid;
|
||||
var child = entity;
|
||||
var spriteOccluded = false;
|
||||
var lightOccluded = false;
|
||||
// TODO: Since we are recursing down,
|
||||
// we could cache ShowContents data here to speed it up for children.
|
||||
// Am lazy though.
|
||||
UpdateEntity(entity);
|
||||
|
||||
while (parent.IsValid() && !spriteOccluded && !lightOccluded)
|
||||
foreach (var child in EntityManager.GetComponent<TransformComponent>(entity).Children)
|
||||
{
|
||||
var parentXform = xformQuery.GetComponent(parent);
|
||||
if (TryComp<ContainerManagerComponent>(parent, out var manager) && manager.TryGetContainer(child, out var container))
|
||||
{
|
||||
spriteOccluded = spriteOccluded || !container.ShowContents;
|
||||
lightOccluded = lightOccluded || container.OccludesLight;
|
||||
}
|
||||
|
||||
child = parent;
|
||||
parent = parentXform.ParentUid;
|
||||
UpdateEntityRecursively(child.Owner);
|
||||
}
|
||||
|
||||
// Alright so
|
||||
// This is the CBT bit.
|
||||
// The issue is we need to go through the children and re-check whether they are or are not contained.
|
||||
// if they are contained then the occlusion values may need updating for all those children
|
||||
UpdateEntity(entity, xform, xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
|
||||
}
|
||||
|
||||
private void UpdateEntity(
|
||||
EntityUid entity,
|
||||
TransformComponent xform,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PointLightComponent> pointQuery,
|
||||
EntityQuery<SpriteComponent> spriteQuery,
|
||||
bool spriteOccluded,
|
||||
bool lightOccluded)
|
||||
private void UpdateEntity(EntityUid entity)
|
||||
{
|
||||
if (spriteQuery.TryGetComponent(entity, out var sprite))
|
||||
if (EntityManager.TryGetComponent(entity, out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.ContainerOccluded = spriteOccluded;
|
||||
}
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
if (pointQuery.TryGetComponent(entity, out var light))
|
||||
{
|
||||
light.ContainerOccluded = lightOccluded;
|
||||
}
|
||||
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
// Try to avoid TryComp if we already know stuff is occluded.
|
||||
if ((!spriteOccluded || !lightOccluded) && TryComp<ContainerManagerComponent>(entity, out var manager))
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
// Thank god it's by value and not by ref.
|
||||
var childSpriteOccluded = spriteOccluded;
|
||||
var childLightOccluded = lightOccluded;
|
||||
|
||||
// We already know either sprite or light is not occluding so need to check container.
|
||||
if (manager.TryGetContainer(child.Value, out var container))
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
|
||||
childLightOccluded = childLightOccluded || container.OccludesLight;
|
||||
sprite.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, childSpriteOccluded, childLightOccluded);
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (EntityManager.TryGetComponent(entity, out PointLightComponent? light))
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
light.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
|
||||
if (container.OccludesLight)
|
||||
{
|
||||
light.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Robust.Client.GameObjects
|
||||
if (_enabled)
|
||||
{
|
||||
_lightOverlay = new DebugLightOverlay(
|
||||
EntitySystem.Get<EntityLookupSystem>(),
|
||||
IoCManager.Resolve<IEntityLookup>(),
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IMapManager>(),
|
||||
Get<RenderingTreeSystem>());
|
||||
@@ -44,7 +44,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private sealed class DebugLightOverlay : Overlay
|
||||
{
|
||||
private EntityLookupSystem _lookup;
|
||||
private IEntityLookup _lookup;
|
||||
private IEyeManager _eyeManager;
|
||||
private IMapManager _mapManager;
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public DebugLightOverlay(EntityLookupSystem lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
|
||||
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_eyeManager = eyeManager;
|
||||
@@ -72,7 +72,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
foreach (var light in tree.LightTree)
|
||||
{
|
||||
var aabb = _lookup.GetWorldAABB(light.Owner);
|
||||
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
|
||||
if (!aabb.Intersects(worldBounds)) continue;
|
||||
|
||||
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
|
||||
|
||||
@@ -323,7 +323,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
private readonly IPlayerManager _playerManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly ShaderInstance _unshadedShader;
|
||||
private readonly EffectSystem _owner;
|
||||
@@ -377,9 +377,6 @@ namespace Robust.Client.GameObjects
|
||||
(attachedXform?.Coordinates ?? effect.Coordinates)
|
||||
.Offset(effect.AttachedOffset);
|
||||
|
||||
// If we've never seen the entity before then can't resolve coordinates.
|
||||
if (!coordinates.IsValid(_entityManager)) continue;
|
||||
|
||||
// ???
|
||||
var rotation = attachedXform?.WorldRotation ?? _entityManager.GetComponent<TransformComponent>(coordinates.EntityId).WorldRotation;
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
internal class GridRenderingSystem : EntitySystem
|
||||
{
|
||||
private readonly IClydeInternal _clyde;
|
||||
|
||||
public GridRenderingSystem(IClydeInternal clyde)
|
||||
{
|
||||
_clyde = clyde;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_clyde.RegisterGridEcsEvents();
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,11 @@ using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -25,8 +21,6 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly IPlayerCommandStates _cmdStates = new PlayerCommandStates();
|
||||
|
||||
@@ -114,43 +108,6 @@ namespace Robust.Client.GameObjects
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PlayerAttachSysMessage>(OnAttachedEntityChanged);
|
||||
|
||||
_conHost.RegisterCommand("incmd",
|
||||
"Inserts an input command into the simulation",
|
||||
"incmd <KeyFunction> <d|u KeyState> <wxPos> <wyPos>",
|
||||
GenerateInputCommand);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_conHost.UnregisterCommand("incmd");
|
||||
}
|
||||
|
||||
private void GenerateInputCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
if(localPlayer is null)
|
||||
return;
|
||||
|
||||
var pent = localPlayer.ControlledEntity;
|
||||
if(pent is null)
|
||||
return;
|
||||
|
||||
BoundKeyFunction keyFunction = new BoundKeyFunction(args[0]);
|
||||
BoundKeyState state = args[1] == "u" ? BoundKeyState.Up: BoundKeyState.Down;
|
||||
|
||||
var pxform = Transform(pent.Value);
|
||||
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
|
||||
var coords = EntityCoordinates.FromMap(EntityManager, pent.Value, new MapCoordinates(wPos, pxform.MapID));
|
||||
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
|
||||
|
||||
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
|
||||
coords, new ScreenCoordinates(0, 0, default), EntityUid.Invalid);
|
||||
|
||||
HandleInputCommand(localPlayer.Session, keyFunction, message);
|
||||
}
|
||||
|
||||
private void OnAttachedEntityChanged(PlayerAttachSysMessage message)
|
||||
|
||||
@@ -70,9 +70,8 @@ namespace Robust.Client.GameObjects
|
||||
UpdatesAfter.Add(typeof(TransformSystem));
|
||||
UpdatesAfter.Add(typeof(PhysicsSystem));
|
||||
|
||||
SubscribeLocalEvent<MapChangedEvent>(MapManagerOnMapCreated);
|
||||
|
||||
SubscribeLocalEvent<GridInitializeEvent>(MapManagerOnGridCreated);
|
||||
_mapManager.MapCreated += MapManagerOnMapCreated;
|
||||
_mapManager.OnGridCreated += MapManagerOnGridCreated;
|
||||
|
||||
// Due to how recursion works, this must be done.
|
||||
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
|
||||
@@ -114,18 +113,12 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void AnythingMoved(ref MoveEvent args)
|
||||
{
|
||||
var pointQuery = EntityManager.GetEntityQuery<PointLightComponent>();
|
||||
var spriteQuery = EntityManager.GetEntityQuery<SpriteComponent>();
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
AnythingMovedSubHandler(args.Sender, xformQuery, pointQuery, spriteQuery);
|
||||
AnythingMovedSubHandler(args.Sender, xforms);
|
||||
}
|
||||
|
||||
private void AnythingMovedSubHandler(
|
||||
EntityUid uid,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PointLightComponent> pointQuery,
|
||||
EntityQuery<SpriteComponent> spriteQuery)
|
||||
private void AnythingMovedSubHandler(EntityUid uid, EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
|
||||
if (!_checkedChildren.Add(uid) || EntityManager.HasComponent<RenderingTreeComponent>(uid)) return;
|
||||
@@ -134,19 +127,17 @@ namespace Robust.Client.GameObjects
|
||||
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
|
||||
// (Struct-based events ok though)
|
||||
// Ironically this was lagging the GC lolz
|
||||
if (spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite))
|
||||
QueueSpriteUpdate(sprite);
|
||||
|
||||
if (pointQuery.TryGetComponent(uid, out var light))
|
||||
if (EntityManager.TryGetComponent(uid, out PointLightComponent? light))
|
||||
QueueLightUpdate(light);
|
||||
|
||||
if (!xformQuery.TryGetComponent(uid, out var xform)) return;
|
||||
if (!xforms.TryGetComponent(uid, out var xform)) return;
|
||||
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform.ChildEntities)
|
||||
{
|
||||
AnythingMovedSubHandler(child.Value, xformQuery, pointQuery, spriteQuery);
|
||||
AnythingMovedSubHandler(child, xforms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +212,13 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_mapManager.MapCreated -= MapManagerOnMapCreated;
|
||||
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
|
||||
}
|
||||
|
||||
private void OnTreeRemove(EntityUid uid, RenderingTreeComponent component, ComponentRemove args)
|
||||
{
|
||||
foreach (var sprite in component.SpriteTree)
|
||||
@@ -237,9 +235,9 @@ namespace Robust.Client.GameObjects
|
||||
component.LightTree.Clear();
|
||||
}
|
||||
|
||||
private void MapManagerOnMapCreated(MapChangedEvent e)
|
||||
private void MapManagerOnMapCreated(object? sender, MapEventArgs e)
|
||||
{
|
||||
if (e.Destroyed || e.Map == MapId.Nullspace)
|
||||
if (e.Map == MapId.Nullspace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -247,9 +245,9 @@ namespace Robust.Client.GameObjects
|
||||
EntityManager.EnsureComponent<RenderingTreeComponent>(_mapManager.GetMapEntityId(e.Map));
|
||||
}
|
||||
|
||||
private void MapManagerOnGridCreated(GridInitializeEvent ev)
|
||||
private void MapManagerOnGridCreated(MapId mapId, GridId gridId)
|
||||
{
|
||||
EntityManager.EnsureComponent<RenderingTreeComponent>(_mapManager.GetGrid(ev.GridId).GridEntityId);
|
||||
EntityManager.EnsureComponent<RenderingTreeComponent>(_mapManager.GetGrid(gridId).GridEntityId);
|
||||
}
|
||||
|
||||
private RenderingTreeComponent? GetRenderTree(EntityUid entity, EntityQuery<TransformComponent> xforms)
|
||||
@@ -371,10 +369,13 @@ 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 SpriteAabbFunc(value, worldPos, worldRot, xforms);
|
||||
return tree == null ? bounds.CalcBoundingBox() : xforms.GetComponent(tree.Owner).InvWorldMatrix.TransformBox(bounds);
|
||||
}
|
||||
|
||||
private Box2 LightAabbFunc(in PointLightComponent value)
|
||||
@@ -391,7 +392,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private Box2 SpriteAabbFunc(SpriteComponent value, Vector2 worldPos, Angle worldRot, EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
var bounds = value.CalculateRotatedBoundingBox(worldPos, worldRot);
|
||||
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);
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed partial class SpriteSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
[Pure]
|
||||
public Texture Frame0(SpriteSpecifier specifier)
|
||||
{
|
||||
return RsiStateLike(specifier).Default;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public IRsiStateLike RsiStateLike(SpriteSpecifier specifier)
|
||||
{
|
||||
switch (specifier)
|
||||
{
|
||||
case SpriteSpecifier.Texture tex:
|
||||
return tex.GetTexture(_resourceCache);
|
||||
|
||||
case SpriteSpecifier.Rsi rsi:
|
||||
return GetState(rsi);
|
||||
|
||||
case SpriteSpecifier.EntityPrototype prototypeIcon:
|
||||
if (!_proto.TryIndex<EntityPrototype>(prototypeIcon.EntityPrototypeId, out var prototype))
|
||||
{
|
||||
Logger.Error("Failed to load PrototypeIcon {0}", prototypeIcon.EntityPrototypeId);
|
||||
return SpriteComponent.GetFallbackState(_resourceCache);
|
||||
}
|
||||
|
||||
return SpriteComponent.GetPrototypeIcon(prototype, _resourceCache);
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
var icon = IconComponent.GetPrototypeIcon(prototype, _resourceCache);
|
||||
if (icon != null) return icon;
|
||||
|
||||
if (!prototype.Components.ContainsKey("Sprite"))
|
||||
{
|
||||
return SpriteComponent.GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
var dummy = Spawn(prototype.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
|
||||
var result = spriteComponent.Icon ?? SpriteComponent.GetFallbackState(resourceCache);
|
||||
Del(dummy);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier)
|
||||
{
|
||||
if (_resourceCache.TryGetResource<RSIResource>(
|
||||
SharedSpriteComponent.TextureRoot / rsiSpecifier.RsiPath,
|
||||
out var theRsi) &&
|
||||
theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);
|
||||
return SpriteComponent.GetFallbackState(_resourceCache);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects
|
||||
/// Updates the layer animation for every visible sprite.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class SpriteSystem : EntitySystem
|
||||
public sealed class SpriteSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly IClientNetManager _network = default!;
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
@@ -331,6 +332,8 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
|
||||
|
||||
_lookup.Update();
|
||||
}
|
||||
|
||||
private void ResetPredictedEntities(GameTick curTick)
|
||||
@@ -421,6 +424,7 @@ namespace Robust.Client.GameStates
|
||||
var createdEntities = ApplyEntityStates(curState.EntityStates.Span, curState.EntityDeletions.Span,
|
||||
nextState != null ? nextState.EntityStates.Span : default);
|
||||
_players.ApplyPlayerStates(curState.PlayerStates.Value ?? Array.Empty<PlayerState>());
|
||||
_mapManager.ApplyGameStatePost(curState.MapData);
|
||||
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState));
|
||||
return createdEntities;
|
||||
|
||||
@@ -15,7 +15,6 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Log;
|
||||
using Vector2 = Robust.Shared.Maths.Vector2;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
@@ -194,10 +193,8 @@ namespace Robust.Client.Graphics.Audio
|
||||
{
|
||||
_didPositionWarning = true;
|
||||
Logger.WarningS("clyde.oal",
|
||||
"Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.",
|
||||
"Attempting to set position on audio source with multiple audio channels! Stream: '{0}'",
|
||||
_sourceStream.Name);
|
||||
// warning isn't enough, people just ignore it :(
|
||||
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo.");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -16,8 +18,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly Dictionary<GridId, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
|
||||
new();
|
||||
|
||||
private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
private int _verticesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
|
||||
private void _drawGrids(Viewport viewport, Box2Rotated worldBounds, IEye eye)
|
||||
{
|
||||
@@ -76,7 +78,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private void _updateChunkMesh(IMapGrid grid, MapChunk chunk)
|
||||
private void _updateChunkMesh(IMapGrid grid, IMapChunk chunk)
|
||||
{
|
||||
var data = _mapChunkData[grid.Index];
|
||||
|
||||
@@ -89,41 +91,25 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
|
||||
|
||||
var i = 0;
|
||||
var cSz = grid.ChunkSize;
|
||||
var cScaled = chunk.Indices * cSz;
|
||||
for (ushort x = 0; x < cSz; x++)
|
||||
foreach (var tile in chunk)
|
||||
{
|
||||
for (ushort y = 0; y < cSz; y++)
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.Tile);
|
||||
if (regionMaybe == null)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
if (tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
|
||||
|
||||
Box2 region;
|
||||
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
|
||||
{
|
||||
region = _tileDefinitionManager.ErrorTileRegion;
|
||||
}
|
||||
else
|
||||
{
|
||||
region = regionMaybe[tile.Variant];
|
||||
}
|
||||
|
||||
var gx = x + cScaled.X;
|
||||
var gy = y + cScaled.Y;
|
||||
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(gx, gy, region.Left, region.Bottom);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(gx + 1, gy, region.Right, region.Bottom);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(gx + 1, gy + 1, region.Right, region.Top);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(gx, gy + 1, region.Left, region.Top);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort)(i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
var region = regionMaybe.Value;
|
||||
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(tile.X, tile.Y, region.Left, region.Bottom);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(tile.X + 1, tile.Y, region.Right, region.Bottom);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(tile.X + 1, tile.Y + 1, region.Right, region.Top);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(tile.X, tile.Y + 1, region.Left, region.Top);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort) (i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
GL.BindVertexArray(datum.VAO);
|
||||
@@ -136,7 +122,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
datum.TileCount = i;
|
||||
}
|
||||
|
||||
private MapChunkData _initChunkBuffers(IMapGrid grid, MapChunk chunk)
|
||||
private MapChunkData _initChunkBuffers(IMapGrid grid, IMapChunk chunk)
|
||||
{
|
||||
var vao = GenVertexArray();
|
||||
BindVertexArray(vao);
|
||||
@@ -173,7 +159,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return datum;
|
||||
}
|
||||
|
||||
private bool _isChunkDirty(IMapGrid grid, MapChunk chunk)
|
||||
private bool _isChunkDirty(IMapGrid grid, IMapChunk chunk)
|
||||
{
|
||||
var data = _mapChunkData[grid.Index];
|
||||
return !data.TryGetValue(chunk.Indices, out var datum) || datum.Dirty;
|
||||
@@ -189,7 +175,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Don't need to set it if we don't have an entry since lack of an entry is treated as dirty.
|
||||
}
|
||||
|
||||
private void _updateOnGridModified(GridModifiedEvent args)
|
||||
private void _updateOnGridModified(object? sender, GridChangedEventArgs args)
|
||||
{
|
||||
foreach (var (pos, _) in args.Modified)
|
||||
{
|
||||
@@ -199,23 +185,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private void _updateTileMapOnUpdate(TileChangedEvent args)
|
||||
private void _updateTileMapOnUpdate(object? sender, TileChangedEventArgs args)
|
||||
{
|
||||
var grid = _mapManager.GetGrid(args.NewTile.GridIndex);
|
||||
var chunk = grid.GridTileToChunkIndices(new Vector2i(args.NewTile.X, args.NewTile.Y));
|
||||
_setChunkDirty(grid, chunk);
|
||||
}
|
||||
|
||||
private void _updateOnGridCreated(GridStartupEvent ev)
|
||||
private void _updateOnGridCreated(MapId mapId, GridId gridId)
|
||||
{
|
||||
var gridId = ev.GridId;
|
||||
Logger.DebugS("grid", $"Adding {gridId} to grid renderer");
|
||||
_mapChunkData.Add(gridId, new Dictionary<Vector2i, MapChunkData>());
|
||||
}
|
||||
|
||||
private void _updateOnGridRemoved(GridRemovalEvent ev)
|
||||
private void _updateOnGridRemoved(MapId mapId, GridId gridId)
|
||||
{
|
||||
var gridId = ev.GridId;
|
||||
Logger.DebugS("grid", $"Removing {gridId} from grid renderer");
|
||||
|
||||
var data = _mapChunkData[gridId];
|
||||
|
||||
@@ -24,17 +24,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
public ClydeDebugLayers DebugLayers { get; set; }
|
||||
|
||||
private readonly RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRotation, Box2 spriteScreenBB)>
|
||||
private readonly RefList<(SpriteComponent sprite, Matrix3 worldMatrix, Angle worldRotation, float yWorldPos)>
|
||||
_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();
|
||||
@@ -223,8 +217,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
|
||||
|
||||
var screenSize = viewport.Size;
|
||||
eye.GetViewMatrix(out var eyeMatrix, eye.Scale);
|
||||
|
||||
ProcessSpriteEntities(mapId, viewport, eye, worldBounds, _drawingSpriteList);
|
||||
ProcessSpriteEntities(mapId, eyeMatrix, worldBounds, _drawingSpriteList);
|
||||
|
||||
var worldOverlays = new List<Overlay>();
|
||||
|
||||
@@ -281,12 +276,30 @@ 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)
|
||||
{
|
||||
// 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();
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
@@ -309,14 +322,16 @@ 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.
|
||||
roundedPos = (Vector2i) entry.spriteScreenBB.Center;
|
||||
var flippedPos = new Vector2i(roundedPos.X, screenSize.Y - roundedPos.Y);
|
||||
var spritePos = spriteBB.Center;
|
||||
var screenPos = viewport.WorldToLocal(spritePos);
|
||||
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
|
||||
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
|
||||
flippedPos -= entityPostRenderTarget.Size / 2;
|
||||
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
|
||||
}
|
||||
}
|
||||
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in entry.worldPos);
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in worldPosition);
|
||||
|
||||
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
|
||||
{
|
||||
@@ -354,26 +369,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void ProcessSpriteEntities(MapId map, Viewport view, IEye eye, Box2Rotated worldBounds,
|
||||
RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> list)
|
||||
private void ProcessSpriteEntities(MapId map, Matrix3 eyeMatrix, Box2Rotated worldBounds,
|
||||
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> 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, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> state,
|
||||
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
|
||||
in SpriteComponent value) =>
|
||||
{
|
||||
var entity = value.Owner;
|
||||
@@ -381,10 +387,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
ref var entry = ref state.AllocAdd();
|
||||
entry.sprite = value;
|
||||
(entry.worldPos, entry.worldRot) = transform.GetWorldPositionRotation();
|
||||
|
||||
var spriteWorldBB = value.CalculateRotatedBoundingBox(entry.worldPos, entry.worldRot, eye);
|
||||
entry.spriteScreenBB = worldToLocal.TransformBox(spriteWorldBB);
|
||||
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;
|
||||
return true;
|
||||
|
||||
}, bounds, true);
|
||||
|
||||
@@ -514,14 +514,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var enlargedBounds = worldAABB.Enlarged(renderingTreeSystem.MaxLightRadius);
|
||||
|
||||
// Use worldbounds for this one as we only care if the light intersects our actual bounds
|
||||
var state = (this, worldAABB, count: 0, shadowCastingCount: 0);
|
||||
var state = (this, worldAABB, count: 0);
|
||||
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
|
||||
{
|
||||
var bounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
|
||||
|
||||
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldAABB, int count, int shadowCastingCount) state, in PointLightComponent light) =>
|
||||
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldAABB, int count) state, in PointLightComponent light) =>
|
||||
{
|
||||
if (state.count >= LightsToRenderListSize)
|
||||
{
|
||||
@@ -543,9 +543,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the light is a shadow casting light, keep a separate track of that
|
||||
if (light.CastShadows) state.shadowCastingCount++;
|
||||
|
||||
float distanceSquared = (state.worldAABB.Center - lightPos).LengthSquared;
|
||||
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
|
||||
|
||||
@@ -553,29 +550,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}, bounds);
|
||||
}
|
||||
|
||||
if (state.shadowCastingCount > _maxLightsPerScene)
|
||||
if (state.count > _maxLightsPerScene)
|
||||
{
|
||||
// There are too many lights casting shadows to fit in the scene.
|
||||
// There are too many lights to fit in the scene.
|
||||
// This check must occur before occluder expansion, or else bad things happen.
|
||||
|
||||
// First, partition the array based on whether the lights are shadow casting or not
|
||||
// (non shadow casting lights should be the first partition, shadow casting lights the second)
|
||||
// Sort lights by distance.
|
||||
Array.Sort(_lightsToRenderList, 0, state.count, Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
|
||||
{
|
||||
if (x.light.CastShadows && !y.light.CastShadows) return 1;
|
||||
else if (!x.light.CastShadows && y.light.CastShadows) return -1;
|
||||
else return 0;
|
||||
}));
|
||||
|
||||
// Next, sort just the shadow casting lights by distance.
|
||||
Array.Sort(_lightsToRenderList, state.count - state.shadowCastingCount, state.shadowCastingCount, Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
|
||||
{
|
||||
return x.distanceSquared.CompareTo(y.distanceSquared);
|
||||
}));
|
||||
|
||||
// Then effectively delete the furthest lights, by setting the end of the array to exclude N
|
||||
// number of shadow casting lights (where N is the number above the max number per scene.)
|
||||
state.count -= state.shadowCastingCount - _maxLightsPerScene;
|
||||
// Then effectively delete the furthest lights.
|
||||
state.count = _maxLightsPerScene;
|
||||
}
|
||||
|
||||
// When culling occluders later, we can't just remove any occluders outside the worldBounds.
|
||||
@@ -861,6 +846,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// 3D geometry used during depth projection.
|
||||
// 2D mask geometry used to apply wall bleed.
|
||||
|
||||
// TODO: This code probably does not work correctly with rotated camera.
|
||||
// TODO: Yes this function throws and index exception if you reach maxOccluders.
|
||||
|
||||
const int maxOccluders = 2048;
|
||||
|
||||
@@ -994,9 +994,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private sealed class SpriteDrawingOrderComparer : IComparer<int>
|
||||
{
|
||||
private readonly RefList<(SpriteComponent, Vector2, Angle, Box2)> _drawList;
|
||||
private readonly RefList<(SpriteComponent, Matrix3, Angle, float)> _drawList;
|
||||
|
||||
public SpriteDrawingOrderComparer(RefList<(SpriteComponent, Vector2, Angle, Box2)> drawList)
|
||||
public SpriteDrawingOrderComparer(RefList<(SpriteComponent, Matrix3, Angle, float)> drawList)
|
||||
{
|
||||
_drawList = drawList;
|
||||
}
|
||||
@@ -1019,8 +1019,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return cmp;
|
||||
}
|
||||
|
||||
// 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);
|
||||
cmp = _drawList[y].Item4.CompareTo(_drawList[x].Item4);
|
||||
|
||||
if (cmp != 0)
|
||||
{
|
||||
|
||||
@@ -437,13 +437,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_windowing!.WindowSetVisible(reg, visible);
|
||||
}
|
||||
|
||||
public void RunOnWindowThread(Action a)
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
|
||||
_windowing!.RunOnWindowThread(a);
|
||||
}
|
||||
|
||||
private abstract class WindowReg
|
||||
{
|
||||
public bool IsDisposed;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
/// <summary>
|
||||
/// Responsible for most things rendering on OpenGL mode.
|
||||
/// </summary>
|
||||
internal sealed partial class Clyde : IClydeInternal, IPostInjectInit, IEntityEventSubscriber
|
||||
internal sealed partial class Clyde : IClydeInternal, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
@@ -134,19 +134,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
_mapManager.TileChanged += _updateTileMapOnUpdate;
|
||||
_mapManager.OnGridCreated += _updateOnGridCreated;
|
||||
_mapManager.OnGridRemoved += _updateOnGridRemoved;
|
||||
_mapManager.GridChanged += _updateOnGridModified;
|
||||
|
||||
// This cvar does not modify the actual GL version requested or anything,
|
||||
// it overrides the version we detect to detect GL features.
|
||||
RegisterBlockCVars();
|
||||
}
|
||||
|
||||
public void RegisterGridEcsEvents()
|
||||
{
|
||||
_entityManager.EventBus.SubscribeEvent<TileChangedEvent>(EventSource.Local, this, _updateTileMapOnUpdate);
|
||||
_entityManager.EventBus.SubscribeEvent<GridStartupEvent>(EventSource.Local, this, _updateOnGridCreated);
|
||||
_entityManager.EventBus.SubscribeEvent<GridRemovalEvent>(EventSource.Local, this, _updateOnGridRemoved);
|
||||
_entityManager.EventBus.SubscribeEvent<GridModifiedEvent>(EventSource.Local, this, _updateOnGridModified);
|
||||
}
|
||||
|
||||
private void GLInitBindings(bool gles)
|
||||
{
|
||||
_glBindingsContext = _glContext!.BindingsContext;
|
||||
|
||||
@@ -76,11 +76,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return null;
|
||||
}
|
||||
|
||||
public void RegisterGridEcsEvents()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetWindowTitle(string title)
|
||||
{
|
||||
// Nada.
|
||||
@@ -251,11 +246,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void RunOnWindowThread(Action action)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
private sealed class DummyCursor : ICursor
|
||||
{
|
||||
public void Dispose()
|
||||
|
||||
@@ -118,10 +118,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case CmdWinCursorSet cmd:
|
||||
WinThreadWinCursorSet(cmd);
|
||||
break;
|
||||
|
||||
case CmdRunAction cmd:
|
||||
cmd.Action();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,11 +169,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void RunOnWindowThread(Action action)
|
||||
{
|
||||
SendCmd(new CmdRunAction(action));
|
||||
}
|
||||
|
||||
private abstract record CmdBase;
|
||||
|
||||
private sealed record CmdTerminate : CmdBase;
|
||||
@@ -254,10 +245,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private sealed record CmdCursorDestroy(
|
||||
ClydeHandle Cursor
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdRunAction(
|
||||
Action Action
|
||||
) : CmdBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
@@ -59,9 +58,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
void GLMakeContextCurrent(WindowReg? reg);
|
||||
void GLSwapInterval(int interval);
|
||||
unsafe void* GLGetProcAddress(string procName);
|
||||
|
||||
// Misc
|
||||
void RunOnWindowThread(Action a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ using Robust.Shared.Utility;
|
||||
using SharpFont;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -59,14 +58,6 @@ namespace Robust.Client.Graphics
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void ClearFontCache()
|
||||
{
|
||||
foreach (var fontInstance in _loadedInstances)
|
||||
{
|
||||
fontInstance.Value.ClearSizeData();
|
||||
}
|
||||
}
|
||||
|
||||
private ScaledFontData _generateScaledDatum(FontInstanceHandle instance, float scale)
|
||||
{
|
||||
var ftFace = instance.FaceHandle.Face;
|
||||
@@ -255,18 +246,6 @@ namespace Robust.Client.Graphics
|
||||
FaceHandle = faceHandle;
|
||||
}
|
||||
|
||||
public void ClearSizeData()
|
||||
{
|
||||
foreach (var scaleData in _scaledData)
|
||||
{
|
||||
foreach (var ownedTexture in scaleData.Value.AtlasTextures)
|
||||
{
|
||||
ownedTexture.Dispose();
|
||||
}
|
||||
}
|
||||
_scaledData.Clear();
|
||||
}
|
||||
|
||||
public Texture? GetCharTexture(Rune codePoint, float scale)
|
||||
{
|
||||
var glyph = GetGlyph(codePoint);
|
||||
|
||||
@@ -61,9 +61,5 @@ namespace Robust.Client.Graphics
|
||||
|
||||
/// <returns>Null if not running on X11.</returns>
|
||||
uint? GetX11WindowId();
|
||||
|
||||
void RegisterGridEcsEvents();
|
||||
|
||||
void RunOnWindowThread(Action action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
public interface IFontManager
|
||||
{
|
||||
public void ClearFontCache();
|
||||
|
||||
}
|
||||
|
||||
internal interface IFontManagerInternal : IFontManager
|
||||
{
|
||||
IFontFaceHandle Load(Stream stream);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -22,20 +22,11 @@ namespace Robust.Client.Map
|
||||
|
||||
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
|
||||
|
||||
private readonly Dictionary<ushort, Box2[]> _tileRegions = new();
|
||||
private readonly Dictionary<ushort, Box2> _tileRegions = new();
|
||||
|
||||
public Box2 ErrorTileRegion { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2[]? TileAtlasRegion(Tile tile)
|
||||
public Box2? TileAtlasRegion(Tile tile)
|
||||
{
|
||||
return TileAtlasRegion(tile.TypeId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2[]? TileAtlasRegion(ushort tileType)
|
||||
{
|
||||
if (_tileRegions.TryGetValue(tileType, out var region))
|
||||
if (_tileRegions.TryGetValue(tile.TypeId, out var region))
|
||||
{
|
||||
return region;
|
||||
}
|
||||
@@ -60,79 +51,39 @@ namespace Robust.Client.Map
|
||||
|
||||
const int tileSize = EyeManager.PixelsPerMeter;
|
||||
|
||||
var tileCount = defList.Select(x => (int)x.Variants).Sum() + 1;
|
||||
var dimensionX = (int) Math.Ceiling(Math.Sqrt(defList.Count));
|
||||
var dimensionY = (int) Math.Ceiling((float) defList.Count / dimensionX);
|
||||
|
||||
var dimensionX = (int) Math.Ceiling(Math.Sqrt(tileCount));
|
||||
var dimensionY = (int) Math.Ceiling((float) tileCount / dimensionX);
|
||||
var sheet = new Image<Rgba32>(dimensionX * tileSize, dimensionY * tileSize);
|
||||
|
||||
var imgWidth = dimensionX * tileSize;
|
||||
var imgHeight = dimensionY * tileSize;
|
||||
var sheet = new Image<Rgba32>(imgWidth, imgHeight);
|
||||
|
||||
// Add in the missing tile texture sprite as tile texture 0.
|
||||
for (var i = 0; i < defList.Count; i++)
|
||||
{
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
ErrorTileRegion = Box2.FromDimensions(
|
||||
0, (h - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
Image<Rgba32> image;
|
||||
using (var stream = _resourceCache.ContentFileRead("/Textures/noTile.png"))
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
var def = defList[i];
|
||||
var column = i % dimensionX;
|
||||
var row = i / dimensionX;
|
||||
|
||||
image.Blit(new UIBox2i(0, 0, tileSize, tileSize), sheet, Vector2i.Zero);
|
||||
}
|
||||
|
||||
if (imgWidth >= 2048 || imgHeight >= 2048)
|
||||
{
|
||||
// Sanity warning, some machines don't have textures larger than this and need multiple atlases.
|
||||
Logger.WarningS("clyde",
|
||||
$"Tile texture atlas is ({imgWidth} x {imgHeight}), larger than 2048 x 2048. If you really need {tileCount} tiles, file an issue on RobustToolbox.");
|
||||
}
|
||||
|
||||
var column = 1;
|
||||
var row = 0;
|
||||
foreach (var def in defList)
|
||||
{
|
||||
Image<Rgba32> image;
|
||||
using (var stream = _resourceCache.ContentFileRead(new ResourcePath(def.Path) / $"{def.SpriteName}.png"))
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
|
||||
if (image.Width != (tileSize * def.Variants) || image.Height != tileSize)
|
||||
if (image.Width != tileSize || image.Height != tileSize)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"Unable to load {new ResourcePath(def.Path) / $"{def.SpriteName}.png"}, due to being unable to use tile texture with a dimension other than {tileSize}x({tileSize} * Variants).");
|
||||
throw new NotSupportedException("Unable to use tiles with a dimension other than 32x32.");
|
||||
}
|
||||
|
||||
var regionList = new Box2[def.Variants];
|
||||
var point = new Vector2i(column * tileSize, row * tileSize);
|
||||
|
||||
for (var j = 0; j < def.Variants; j++)
|
||||
{
|
||||
var point = new Vector2i(column * tileSize, row * tileSize);
|
||||
image.Blit(new UIBox2i(0, 0, image.Width, image.Height), sheet, point);
|
||||
|
||||
var box = new UIBox2i(0, 0, tileSize, tileSize).Translated(new Vector2i(j * tileSize, 0));
|
||||
image.Blit(box, sheet, point);
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
|
||||
regionList[j] = Box2.FromDimensions(
|
||||
_tileRegions.Add(def.TileId,
|
||||
Box2.FromDimensions(
|
||||
point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
column++;
|
||||
|
||||
if (column >= dimensionX)
|
||||
{
|
||||
column = 0;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
_tileRegions.Add(def.TileId, regionList);
|
||||
tileSize / w, tileSize / h));
|
||||
}
|
||||
|
||||
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -10,8 +9,6 @@ namespace Robust.Client.Map
|
||||
/// </summary>
|
||||
internal interface IClydeTileDefinitionManager : ITileDefinitionManager
|
||||
{
|
||||
Box2 ErrorTileRegion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The texture atlas containing all the tiles.
|
||||
/// </summary>
|
||||
@@ -21,12 +18,6 @@ namespace Robust.Client.Map
|
||||
/// Gets the region inside the texture atlas to use to draw a tile.
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
Box2[]? TileAtlasRegion(Tile tile);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the region inside the texture atlas to use to draw a tile type.
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
Box2[]? TileAtlasRegion(ushort tileType);
|
||||
Box2? TileAtlasRegion(Tile tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Robust.Client.Physics
|
||||
SimulateWorld((float) diff.TotalSeconds, true);
|
||||
}
|
||||
|
||||
protected override void HandleMapCreated(MapChangedEvent eventArgs)
|
||||
protected override void HandleMapCreated(object? sender, MapEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Map == MapId.Nullspace) return;
|
||||
var mapUid = MapManager.GetMapEntityId(eventArgs.Map);
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
var mapId = MouseCoords.GetMapId(pManager.EntityManager);
|
||||
|
||||
var snapToEntities = EntitySystem.Get<EntityLookupSystem>().GetEntitiesInRange(MouseCoords, SnapToRange)
|
||||
var snapToEntities = IoCManager.Resolve<IEntityLookup>().GetEntitiesInRange(MouseCoords, SnapToRange)
|
||||
.Where(entity => pManager.EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent<TransformComponent>(entity).MapID == mapId)
|
||||
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager)).LengthSquared)
|
||||
.ToList();
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.Placement.Modes
|
||||
var topRight = new Vector2(CurrentTile.X + 0.99f, CurrentTile.Y + 0.99f);
|
||||
var box = new Box2(bottomLeft, topRight);
|
||||
|
||||
return !EntitySystem.Get<EntityLookupSystem>().AnyEntitiesIntersecting(map, box);
|
||||
return !IoCManager.Resolve<IEntityLookup>().AnyEntitiesIntersecting(map, box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Placement
|
||||
{
|
||||
public sealed partial class PlacementManager : IPlacementManager, IDisposable, IEntityEventSubscriber
|
||||
public sealed partial class PlacementManager : IPlacementManager, IDisposable
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager NetworkManager = default!;
|
||||
[Dependency] public readonly IPlayerManager PlayerManager = default!;
|
||||
@@ -185,7 +185,7 @@ namespace Robust.Client.Placement
|
||||
_modeDictionary.Add(type.Name, type);
|
||||
}
|
||||
|
||||
EntityManager.EventBus.SubscribeEvent<TileChangedEvent>(EventSource.Local, this, HandleTileChanged);
|
||||
MapManager.TileChanged += HandleTileChanged;
|
||||
|
||||
_drawOverlay = new PlacementOverlay(this);
|
||||
_overlayManager.AddOverlay(_drawOverlay);
|
||||
@@ -328,7 +328,7 @@ namespace Robust.Client.Placement
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTileChanged(TileChangedEvent args)
|
||||
private void HandleTileChanged(object? sender, TileChangedEventArgs args)
|
||||
{
|
||||
var coords = MapManager.GetGrid(args.NewTile.GridIndex).GridTileToLocal(args.NewTile.GridIndices);
|
||||
_pendingTileChanges.RemoveAll(c => c.Item1 == coords);
|
||||
|
||||
@@ -230,7 +230,7 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
using (var manifestFile = cache.ContentFileRead(manifestPath))
|
||||
{
|
||||
if (manifestFile.CanSeek && manifestFile.Length <= 4096)
|
||||
if (manifestFile.Length <= 4096)
|
||||
{
|
||||
// Most RSIs are actually tiny so if that's the case just load them into a stackalloc buffer.
|
||||
// Avoids a ton of allocations with stream reader etc
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.2" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.3" />
|
||||
<PackageReference Include="nfluidsynth" Version="0.3.1" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
@@ -25,9 +25,9 @@
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="3.8.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.8.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
||||
|
||||
<ProjectReference Include="..\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj" />
|
||||
</ItemGroup>
|
||||
@@ -53,7 +53,6 @@
|
||||
<Compile Update="UserInterface\CustomControls\DefaultWindow.xaml.cs">
|
||||
<DependentUpon>DefaultWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Remove="Debugging\IDebugDrawing.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
@@ -11,30 +9,6 @@ namespace Robust.Client.UserInterface.Controls
|
||||
Window = window;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable the UI autoscale system, this will scale down the UI for lower resolutions
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool AutoScale { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum resolution to start clamping autoscale to 1
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Vector2i AutoScaleUpperCutoff { get; set; } = new Vector2i(1080, 720);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum resolution to start clamping autos scale to autoscale minimum
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Vector2i AutoScaleLowerCutoff { get; set; } = new Vector2i(520, 520);
|
||||
|
||||
/// <summary>
|
||||
/// The minimum ui scale value that autoscale will scale to
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float AutoScaleMinimum { get; set; } = 0.5f;
|
||||
|
||||
public override float UIScale => UIScaleSet;
|
||||
internal float UIScaleSet { get; set; }
|
||||
public override IClydeWindow Window { get; }
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -165,11 +164,12 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
// macOS seems pretty annoying about having the file dialog opened from the main windowing thread.
|
||||
// So we are forced to execute this synchronously on the main windowing thread.
|
||||
// nativefiledialog doesn't provide any form of async API, so this WILL lock up half the client.
|
||||
// macOS seems pretty annoying about having the file dialog opened from the main thread.
|
||||
// So we are forced to execute this synchronously on the main thread.
|
||||
// Also I'm calling RunOnMainThread here to provide safety in case this is ran from a different thread.
|
||||
// nativefiledialog doesn't provide any form of async API, so this WILL lock up the client.
|
||||
var tcs = new TaskCompletionSource<string?>();
|
||||
_clyde.RunOnWindowThread(() => tcs.SetResult(action()));
|
||||
_taskManager.RunOnMainThread(() => tcs.SetResult(action()));
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
@@ -388,17 +388,4 @@ namespace Robust.Client.UserInterface
|
||||
SW_NFD_CANCEL,
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class OpenFileCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "testopenfile";
|
||||
public string Description => "";
|
||||
public string Help => "";
|
||||
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var stream = await IoCManager.Resolve<IFileDialogManager>().OpenFile();
|
||||
stream?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
@@ -13,7 +12,6 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
@@ -25,7 +23,6 @@ namespace Robust.Client.UserInterface
|
||||
internal sealed class UserInterfaceManager : IUserInterfaceManagerInternal
|
||||
{
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IFontManager _fontManager = default!;
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
@@ -954,37 +951,14 @@ namespace Robust.Client.UserInterface
|
||||
private void WindowContentScaleChanged(WindowContentScaleEventArgs args)
|
||||
{
|
||||
if (_windowsToRoot.TryGetValue(args.Window.Id, out var root))
|
||||
{
|
||||
UpdateUIScale(root);
|
||||
_fontManager.ClearFontCache();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private float CalculateAutoScale(WindowRoot root)
|
||||
{
|
||||
//Grab the OS UIScale or the value set through CVAR debug
|
||||
var osScale = _configurationManager.GetCVar(CVars.DisplayUIScale);
|
||||
osScale = osScale == 0f ? root.Window.ContentScale.X : osScale;
|
||||
var windowSize = root.Window.RenderTarget.Size;
|
||||
//Only run autoscale if it is enabled, otherwise default to just use OS UIScale
|
||||
if (!root.AutoScale && (windowSize.X <= 0 || windowSize.Y <= 0)) return osScale;
|
||||
var maxScaleRes = root.AutoScaleUpperCutoff;
|
||||
var minScaleRes = root.AutoScaleLowerCutoff;
|
||||
var autoScaleMin = root.AutoScaleMinimum;
|
||||
float scaleRatioX;
|
||||
float scaleRatioY;
|
||||
|
||||
//Calculate the scale ratios and clamp it between the maximums and minimums
|
||||
scaleRatioX = Math.Clamp(((float) windowSize.X - minScaleRes.X) / (maxScaleRes.X - minScaleRes.X) * osScale, autoScaleMin, osScale);
|
||||
scaleRatioY = Math.Clamp(((float) windowSize.Y - minScaleRes.Y) / (maxScaleRes.Y - minScaleRes.Y) * osScale, autoScaleMin, osScale);
|
||||
//Take the smallest UIScale value and use it for UI scaling
|
||||
return Math.Min(scaleRatioX, scaleRatioY);
|
||||
}
|
||||
|
||||
private void UpdateUIScale(WindowRoot root)
|
||||
{
|
||||
root.UIScaleSet = CalculateAutoScale(root);
|
||||
var newVal = _configurationManager.GetCVar(CVars.DisplayUIScale);
|
||||
root.UIScaleSet = newVal == 0f ? root.Window.ContentScale.X : newVal;
|
||||
|
||||
_propagateUIScaleChanged(root);
|
||||
root.InvalidateMeasure();
|
||||
}
|
||||
@@ -1003,7 +977,7 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
if (!_windowsToRoot.TryGetValue(windowResizedEventArgs.Window.Id, out var root))
|
||||
return;
|
||||
UpdateUIScale(root);
|
||||
|
||||
root.InvalidateMeasure();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace Robust.Client.Utility
|
||||
.Texture;
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IResourceCache cache)
|
||||
{
|
||||
if (cache.TryGetResource<RSIResource>(
|
||||
@@ -37,7 +36,6 @@ namespace Robust.Client.Utility
|
||||
return SpriteComponent.GetFallbackState(cache);
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static Texture Frame0(this SpriteSpecifier specifier)
|
||||
{
|
||||
return specifier.RsiStateLike().Default;
|
||||
@@ -82,7 +80,6 @@ namespace Robust.Client.Utility
|
||||
return specifier.RsiStateLike();
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static IRsiStateLike RsiStateLike(this SpriteSpecifier specifier)
|
||||
{
|
||||
var resC = IoCManager.Resolve<IResourceCache>();
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Add your introductions here!
|
||||
@@ -1,2 +0,0 @@
|
||||
- name: Example-article
|
||||
href: example-article.md
|
||||
@@ -1,214 +0,0 @@
|
||||
{
|
||||
"metadata": [
|
||||
{
|
||||
"src":
|
||||
[
|
||||
{
|
||||
"files": [
|
||||
"**.csproj"
|
||||
],
|
||||
"exclude": [
|
||||
"**/bin/**",
|
||||
"**/obj/**",
|
||||
"_site/**",
|
||||
"**.xaml"
|
||||
],
|
||||
"src": "../Robust.UnitTesting"
|
||||
}
|
||||
],
|
||||
"disableGitFeatures": false,
|
||||
"disableDefaultFilter": false,
|
||||
"dest": "api/Robust.UnitTesting"
|
||||
},
|
||||
{
|
||||
"src":
|
||||
[
|
||||
{
|
||||
"files": [
|
||||
"**.csproj"
|
||||
],
|
||||
"exclude": [
|
||||
"**/bin/**",
|
||||
"**/obj/**",
|
||||
"_site/**",
|
||||
"**.xaml"
|
||||
],
|
||||
"src": "../Robust.Shared"
|
||||
}
|
||||
],
|
||||
"disableGitFeatures": false,
|
||||
"disableDefaultFilter": false,
|
||||
"dest": "api/Robust.Shared"
|
||||
},
|
||||
{
|
||||
"src":
|
||||
[
|
||||
{
|
||||
"files": [
|
||||
"**.csproj"
|
||||
],
|
||||
"exclude": [
|
||||
"**/bin/**",
|
||||
"**/obj/**",
|
||||
"_site/**",
|
||||
"**.xaml"
|
||||
],
|
||||
"src": "../Robust.Shared.Maths"
|
||||
}
|
||||
],
|
||||
"disableGitFeatures": false,
|
||||
"disableDefaultFilter": false,
|
||||
"dest": "api/Robust.Shared.Maths"
|
||||
},
|
||||
{
|
||||
"src":
|
||||
[
|
||||
{
|
||||
"files": [
|
||||
"**.csproj"
|
||||
],
|
||||
"exclude": [
|
||||
"**/bin/**",
|
||||
"**/obj/**",
|
||||
"_site/**",
|
||||
"**.xaml"
|
||||
],
|
||||
"src": "../Robust.Shared.Scripting"
|
||||
}
|
||||
],
|
||||
"disableGitFeatures": false,
|
||||
"disableDefaultFilter": false,
|
||||
"dest": "api/Robust.Shared.Scripting"
|
||||
},
|
||||
{
|
||||
"src":
|
||||
[
|
||||
{
|
||||
"files": [
|
||||
"**.csproj"
|
||||
],
|
||||
"exclude": [
|
||||
"**/bin/**",
|
||||
"**/obj/**",
|
||||
"_site/**",
|
||||
"**.xaml"
|
||||
],
|
||||
"src": "../Robust.Client"
|
||||
}
|
||||
],
|
||||
"disableGitFeatures": false,
|
||||
"disableDefaultFilter": false,
|
||||
"dest": "api/Robust.Client"
|
||||
},
|
||||
{
|
||||
"src":
|
||||
[
|
||||
{
|
||||
"files": [
|
||||
"**.csproj"
|
||||
],
|
||||
"exclude": [
|
||||
"**/bin/**",
|
||||
"**/obj/**",
|
||||
"_site/**",
|
||||
"**.xaml"
|
||||
],
|
||||
"src": "../Robust.Client.WebView"
|
||||
}
|
||||
],
|
||||
"disableGitFeatures": false,
|
||||
"disableDefaultFilter": false,
|
||||
"dest": "api/Robust.Client.WebView"
|
||||
},
|
||||
{
|
||||
"src":
|
||||
[
|
||||
{
|
||||
"files": [
|
||||
"**.csproj"
|
||||
],
|
||||
"exclude": [
|
||||
"**/bin/**",
|
||||
"**/obj/**",
|
||||
"_site/**",
|
||||
"**.xaml"
|
||||
],
|
||||
"src": "../Robust.Server"
|
||||
}
|
||||
],
|
||||
"disableGitFeatures": false,
|
||||
"disableDefaultFilter": false,
|
||||
"dest": "api/Robust.Server"
|
||||
},
|
||||
{
|
||||
"src":
|
||||
[
|
||||
{
|
||||
"files": [
|
||||
"**.csproj"
|
||||
],
|
||||
"exclude": [
|
||||
"**/bin/**",
|
||||
"**/obj/**",
|
||||
"_site/**",
|
||||
"**.xaml"
|
||||
],
|
||||
"src": "../Robust.LoaderApi"
|
||||
}
|
||||
],
|
||||
"disableGitFeatures": false,
|
||||
"disableDefaultFilter": false,
|
||||
"dest": "api/Robust.LoaderApi/Robust.LoaderApi"
|
||||
}
|
||||
],
|
||||
"build": {
|
||||
"content": [
|
||||
{
|
||||
"files": [
|
||||
"api/**/**.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"articles/**.md",
|
||||
"articles/**/toc.yml",
|
||||
"toc.yml",
|
||||
"*.md"
|
||||
]
|
||||
}
|
||||
],
|
||||
"resource": [
|
||||
{
|
||||
"files": [
|
||||
"images/**",
|
||||
"favicon.ico",
|
||||
"icon.svg"
|
||||
]
|
||||
}
|
||||
],
|
||||
"overwrite": [
|
||||
{
|
||||
"files": [
|
||||
"apidoc/**.md"
|
||||
],
|
||||
"exclude": [
|
||||
"obj/**",
|
||||
"_site/**"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dest": "_robust-site",
|
||||
"globalMetadataFiles": [],
|
||||
"fileMetadataFiles": [],
|
||||
"template": [
|
||||
"default",
|
||||
"templates/darkfx"
|
||||
],
|
||||
"postProcessors": [],
|
||||
"markdownEngineName": "markdig",
|
||||
"noLangKeyword": false,
|
||||
"keepFileLink": false,
|
||||
"cleanupCacheHistory": false,
|
||||
"disableGitFeatures": false
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# RobustToolbox DocFX
|
||||

|
||||
|
||||
## Welcome to the RobustToolbox DocFX instance.
|
||||
### Click one of the tabs above to see documentation for that particular project/namespace
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Steffen Wilke
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,40 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<div class="hidden-sm col-md-2" role="complementary">
|
||||
<div class="sideaffix">
|
||||
{{^_disableContribution}}
|
||||
<div class="contribution">
|
||||
<ul class="nav">
|
||||
{{#docurl}}
|
||||
<li>
|
||||
<a href="{{docurl}}" class="contribution-link">{{__global.improveThisDoc}}</a>
|
||||
</li>
|
||||
{{/docurl}}
|
||||
{{#sourceurl}}
|
||||
<li>
|
||||
<a href="{{sourceurl}}" class="contribution-link">{{__global.viewSource}}</a>
|
||||
</li>
|
||||
{{/sourceurl}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/_disableContribution}}
|
||||
<div class="toggle-mode">
|
||||
<div class="icon">
|
||||
<i aria-hidden="true">☀</i>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="switch-style">
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
<div class="icon">
|
||||
<i aria-hidden="true">☾</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
|
||||
<h5>{{__global.inThisArticle}}</h5>
|
||||
<div></div>
|
||||
<!-- <p><a class="back-to-top" href="#top">Back to top</a><p> -->
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,29 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<footer>
|
||||
<div class="grad-bottom"></div>
|
||||
<div class="footer">
|
||||
<div class="container">
|
||||
<span class="pull-right">
|
||||
<a href="#top">Back to top</a>
|
||||
</span>
|
||||
<div class="pull-left">
|
||||
{{{_appFooter}}}
|
||||
{{^_appFooter}}<span>Generated by <strong>DocFX</strong></span>{{/_appFooter}}
|
||||
</div>
|
||||
<div class="toggle-mode pull-right visible-sm visible-xs">
|
||||
<div class="icon">
|
||||
<i aria-hidden="true">☀</i>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="switch-style-m">
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
<div class="icon">
|
||||
<i aria-hidden="true">☾</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="{{_rel}}styles/toggle-theme.js"></script>
|
||||
</footer>
|
||||
@@ -1,20 +0,0 @@
|
||||
{{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
|
||||
<meta name="generator" content="docfx {{_docfxVersion}}">
|
||||
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
|
||||
<link rel="shortcut icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
|
||||
<link rel="stylesheet" href="{{_rel}}styles/docfx.vendor.css">
|
||||
<link rel="stylesheet" href="{{_rel}}styles/docfx.css">
|
||||
<link rel="stylesheet" href="{{_rel}}styles/main.css">
|
||||
<meta property="docfx:navrel" content="{{_navRel}}">
|
||||
<meta property="docfx:tocrel" content="{{_tocRel}}">
|
||||
{{#_noindex}}<meta name="searchOption" content="noindex">{{/_noindex}}
|
||||
{{#_enableSearch}}<meta property="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
|
||||
{{#_enableNewTab}}<meta property="docfx:newtab" content="true">{{/_enableNewTab}}
|
||||
</head>
|
||||
@@ -1,470 +0,0 @@
|
||||
:root, body.dark-theme {
|
||||
--color-foreground: #ccd5dc;
|
||||
--color-navbar: #66666d;
|
||||
--color-breadcrumb: #999;
|
||||
--color-underline: #ddd;
|
||||
--color-toc-hover: #fff;
|
||||
--color-background: #2d2d30;
|
||||
--color-background-subnav: #333337;
|
||||
--color-background-dark: #1e1e1e;
|
||||
--color-background-table-alt: #212123;
|
||||
--color-background-quote: #69696e;
|
||||
}
|
||||
|
||||
body.light-theme {
|
||||
--color-foreground: #171717;
|
||||
--color-breadcrumb: #4a4a4a;
|
||||
--color-toc-hover: #4c4c4c;
|
||||
--color-background: #ffffff;
|
||||
--color-background-subnav: #f5f5f5;
|
||||
--color-background-dark: #ddd;
|
||||
--color-background-table-alt: #f9f9f9;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--color-foreground);
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
word-wrap: break-word;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.btn.focus, .btn:focus, .btn:hover {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 600;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 14px;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
article h1, article h2, article h3, article h4 {
|
||||
margin-top: 35px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
article h4 {
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--color-underline);
|
||||
}
|
||||
|
||||
.navbar-brand>img {
|
||||
color: var(--color-background);
|
||||
}
|
||||
|
||||
.navbar {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.subnav {
|
||||
border-top: 1px solid var(--color-underline);
|
||||
background-color: var(--color-background-subnav);
|
||||
}
|
||||
|
||||
.sidenav, .fixed_header, .toc {
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.navbar-inverse {
|
||||
background-color: var(--color-background-dark);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>li>a, .navbar-inverse .navbar-text {
|
||||
color: var(--color-navbar);
|
||||
background-color: var(--color-background-dark);
|
||||
border-bottom: 3px solid transparent;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>li>a:focus, .navbar-inverse .navbar-nav>li>a:hover {
|
||||
color: var(--color-foreground);
|
||||
background-color: var(--color-background-dark);
|
||||
border-bottom: 3px solid var(--color-background-subnav);
|
||||
transition: all ease 0.25s;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>.active>a, .navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover {
|
||||
color: var(--color-foreground);
|
||||
background-color: var(--color-background-dark);
|
||||
border-bottom: 3px solid var(--color-foreground);
|
||||
transition: all ease 0.25s;
|
||||
}
|
||||
|
||||
.navbar-form .form-control {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.light-theme .navbar-brand svg {
|
||||
filter: brightness(20%);
|
||||
}
|
||||
|
||||
.toc .level1>li {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.toc .nav>li>a {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.sidefilter {
|
||||
background-color: var(--color-background);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.sidefilter {
|
||||
background-color: var(--color-background);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.toc-filter {
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.toc-filter>input {
|
||||
border: none;
|
||||
border-radius: unset;
|
||||
background-color: var(--color-background-subnav);
|
||||
padding: 5px 0 5px 20px;
|
||||
font-size: 90%
|
||||
}
|
||||
|
||||
.toc-filter>.clear-icon {
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
.toc-filter>input:focus {
|
||||
color: var(--color-foreground);
|
||||
transition: all ease 0.25s;
|
||||
}
|
||||
|
||||
.toc-filter>.filter-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidetoc>.toc {
|
||||
background-color: var(--color-background);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.sidetoc {
|
||||
background-color: var(--color-background);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.alert {
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
padding: 10px 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.alert>p {
|
||||
margin-bottom: 0;
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid;
|
||||
background-color: var(--color-background-dark);
|
||||
}
|
||||
|
||||
.alert>h5 {
|
||||
padding: 10px 15px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border-top: 2px solid;
|
||||
background-color: var(--color-background-dark);
|
||||
border-radius: none;
|
||||
}
|
||||
|
||||
.alert>ul {
|
||||
margin-bottom: 0;
|
||||
padding: 5px 40px;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
color: #f57f17;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 9.5px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
background-color: var(--color-background-dark);
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
code {
|
||||
background: var(--color-background-dark) !important;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.toc .nav>li.active>.expand-stub::before, .toc .nav>li.in>.expand-stub::before, .toc .nav>li.in.active>.expand-stub::before, .toc .nav>li.filtered>.expand-stub::before {
|
||||
content: "▾";
|
||||
}
|
||||
|
||||
.toc .nav>li>.expand-stub::before, .toc .nav>li.active>.expand-stub::before {
|
||||
content: "▸";
|
||||
}
|
||||
|
||||
.affix ul ul>li>a:before {
|
||||
content: "|";
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background-color: var(--color-background-subnav);
|
||||
}
|
||||
|
||||
.breadcrumb .label.label-primary {
|
||||
background: #444;
|
||||
border-radius: 0;
|
||||
font-weight: normal;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
#breadcrumb .breadcrumb>li a {
|
||||
border-radius: 0;
|
||||
font-weight: normal;
|
||||
font-size: 85%;
|
||||
display: inline;
|
||||
padding: 0 .6em 0;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
color: var(--color-breadcrumb);
|
||||
}
|
||||
|
||||
#breadcrumb .breadcrumb>li a:hover {
|
||||
color: var(--color-foreground);
|
||||
transition: all ease 0.25s;
|
||||
}
|
||||
|
||||
.breadcrumb>li+li:before {
|
||||
content: "⯈";
|
||||
font-size: 75%;
|
||||
color: var(--color-background-dark);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.light-theme .breadcrumb>li+li:before {
|
||||
color: var(--color-foreground)
|
||||
}
|
||||
|
||||
.toc .level1>li {
|
||||
font-weight: 600;
|
||||
font-size: 130%;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: none;
|
||||
background-color: var(--color-background-dark);
|
||||
padding: 15px 0;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.toc .nav>li>a:hover, .toc .nav>li>a:focus {
|
||||
color: var(--color-toc-hover);
|
||||
transition: all ease 0.1s;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background-color: var(--color-background-subnav);
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
input#search-query:focus {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.table-bordered, .table-bordered>tbody>tr>td, .table-bordered>tbody>tr>th, .table-bordered>tfoot>tr>td, .table-bordered>tfoot>tr>th, .table-bordered>thead>tr>td, .table-bordered>thead>tr>th {
|
||||
border: 1px solid var(--color-background-dark);
|
||||
}
|
||||
|
||||
.table-striped>tbody>tr:nth-of-type(odd) {
|
||||
background-color: var(--color-background-table-alt);
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 10px 20px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 110%;
|
||||
border-left: 5px solid var(--color-background-quote);
|
||||
color: var(--color-background-quote);
|
||||
}
|
||||
|
||||
.pagination>.disabled>a, .pagination>.disabled>a:focus, .pagination>.disabled>a:hover, .pagination>.disabled>span, .pagination>.disabled>span:focus, .pagination>.disabled>span:hover {
|
||||
background-color: var(--color-background-subnav);
|
||||
border-color: var(--color-background-subnav);
|
||||
}
|
||||
|
||||
.breadcrumb>li, .pagination {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.tabGroup a[role="tab"] {
|
||||
border-bottom: 2px solid var(--color-background-dark);
|
||||
}
|
||||
|
||||
.tabGroup a[role="tab"][aria-selected="true"] {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.tabGroup section[role="tabpanel"] {
|
||||
border: 1px solid var(--color-background-dark);
|
||||
}
|
||||
|
||||
.sideaffix > div.contribution > ul > li > a.contribution-link:hover {
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
left: 4px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #337ab7;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px #337ab7;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(19px);
|
||||
-ms-transform: translateX(19px);
|
||||
transform: translateX(19px);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.toggle-mode .icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.toggle-mode .icon i {
|
||||
font-style: normal;
|
||||
font-size: 17px;
|
||||
display: inline-block;
|
||||
padding-right: 7px;
|
||||
padding-left: 7px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) {
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
.sidefilter {
|
||||
width: 18%;
|
||||
}
|
||||
.sidetoc {
|
||||
width: 18%;
|
||||
}
|
||||
.article.grid-right {
|
||||
margin-left: 19%;
|
||||
}
|
||||
.sideaffix {
|
||||
width: 11.5%;
|
||||
}
|
||||
.affix ul>li.active>a {
|
||||
white-space: initial;
|
||||
}
|
||||
.affix ul>li>a {
|
||||
width: 99%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
const sw = document.getElementById("switch-style"), sw_mobile = document.getElementById("switch-style-m"), b = document.body;
|
||||
if (b) {
|
||||
function toggleTheme(target, dark) {
|
||||
target.classList.toggle("dark-theme", dark)
|
||||
target.classList.toggle("light-theme", !dark)
|
||||
}
|
||||
|
||||
function switchEventListener() {
|
||||
toggleTheme(b, this.checked);
|
||||
if (window.localStorage) {
|
||||
this.checked ? localStorage.setItem("theme", "dark-theme") : localStorage.setItem("theme", "light-theme")
|
||||
}
|
||||
}
|
||||
|
||||
var isDarkTheme = !window.localStorage || !window.localStorage.getItem("theme") || window.localStorage && localStorage.getItem("theme") === "dark-theme";
|
||||
|
||||
if(sw && sw_mobile){
|
||||
sw.checked = isDarkTheme;
|
||||
sw_mobile.checked = isDarkTheme;
|
||||
|
||||
sw.addEventListener("change", switchEventListener);
|
||||
sw_mobile.addEventListener("change", switchEventListener);
|
||||
|
||||
// sync state between switches
|
||||
sw.addEventListener("change", function() {
|
||||
sw_mobile.checked = this.checked;
|
||||
});
|
||||
|
||||
sw_mobile.addEventListener("change", function() {
|
||||
sw.checked = this.checked;
|
||||
});
|
||||
}
|
||||
|
||||
toggleTheme(b, isDarkTheme);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
- name: Articles
|
||||
href: articles/
|
||||
|
||||
# Client
|
||||
- name: Robust.Client
|
||||
href: api/Robust.Client/
|
||||
homepage: api/Robust.Client/Robust.Client.html
|
||||
- name: Robust.Client.WebView
|
||||
href: api/Robust.Client.WebView/
|
||||
|
||||
#Loader Api
|
||||
- name: Robust.LoaderApi
|
||||
href: api/Robust.LoaderApi/Robust.LoaderApi/
|
||||
|
||||
# Server
|
||||
- name: Robust.Server
|
||||
href: api/Robust.Server/
|
||||
|
||||
# Shared
|
||||
- name: Robust.Shared
|
||||
href: api/Robust.Shared/
|
||||
- name: Robust.Shared.Maths
|
||||
href: api/Robust.Shared.Maths/
|
||||
- name: Robust.Shared.Scripting
|
||||
href: api/Robust.Shared.Scripting/
|
||||
|
||||
# Unit testing
|
||||
- name: Robust.UnitTesting
|
||||
href: api/Robust.UnitTesting/
|
||||
@@ -66,17 +66,18 @@ namespace Robust.Server
|
||||
|
||||
[Dependency] private readonly IConfigurationManagerInternal _config = default!;
|
||||
[Dependency] private readonly IServerEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IGameTiming _time = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resources = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ITimerManager _timerManager = default!;
|
||||
[Dependency] private readonly ITimerManager timerManager = default!;
|
||||
[Dependency] private readonly IServerGameStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IServerNetManager _network = default!;
|
||||
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
[Dependency] private readonly IRuntimeLog runtimeLog = default!;
|
||||
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
|
||||
[Dependency] private readonly IWatchdogApi _watchdogApi = default!;
|
||||
[Dependency] private readonly HubManager _hubManager = default!;
|
||||
@@ -126,7 +127,7 @@ namespace Robust.Server
|
||||
else
|
||||
Logger.InfoS("srv", $"{reason}, shutting down...");
|
||||
|
||||
_shutdownReason = reason ?? "Shutting down";
|
||||
_shutdownReason = reason;
|
||||
|
||||
if (_mainLoop != null) _mainLoop.Running = false;
|
||||
if (_logHandler != null)
|
||||
@@ -343,6 +344,7 @@ namespace Robust.Server
|
||||
_consoleHost.Initialize();
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
IoCManager.Resolve<IEntityLookup>().Startup();
|
||||
_stateManager.Initialize();
|
||||
|
||||
var reg = _entityManager.ComponentFactory.GetRegistration<TransformComponent>();
|
||||
@@ -601,6 +603,7 @@ namespace Robust.Server
|
||||
_network.Shutdown($"Server shutting down: {_shutdownReason}");
|
||||
|
||||
// shutdown entities
|
||||
IoCManager.Resolve<IEntityLookup>().Shutdown();
|
||||
_entityManager.Cleanup();
|
||||
|
||||
if (_config.GetCVar(CVars.LogRuntimeLog))
|
||||
@@ -611,7 +614,7 @@ namespace Robust.Server
|
||||
Directory.CreateDirectory(relPath);
|
||||
var pathToWrite = Path.Combine(relPath,
|
||||
"Runtime-" + DateTime.Now.ToString("yyyy-MM-dd-THH-mm-ss") + ".txt");
|
||||
File.WriteAllText(pathToWrite, _runtimeLog.Display(), EncodingHelpers.UTF8);
|
||||
File.WriteAllText(pathToWrite, runtimeLog.Display(), EncodingHelpers.UTF8);
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.ProcessExit -= ProcessExiting;
|
||||
@@ -656,8 +659,7 @@ namespace Robust.Server
|
||||
|
||||
using (TickUsage.WithLabels("Timers").NewTimer())
|
||||
{
|
||||
_consoleHost.CommandBufferExecute();
|
||||
_timerManager.UpdateTimers(frameEventArgs);
|
||||
timerManager.UpdateTimers(frameEventArgs);
|
||||
}
|
||||
|
||||
using (TickUsage.WithLabels("AsyncTasks").NewTimer())
|
||||
@@ -668,6 +670,8 @@ namespace Robust.Server
|
||||
// Pass Histogram into the IEntityManager.Update so it can do more granular measuring.
|
||||
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false, TickUsage);
|
||||
|
||||
_lookup.Update();
|
||||
|
||||
using (TickUsage.WithLabels("PostEngine").NewTimer())
|
||||
{
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
|
||||
|
||||
@@ -311,7 +311,7 @@ namespace Robust.Server.Bql
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var radius = (float)(double)arguments[0];
|
||||
var entityLookup = EntitySystem.Get<EntityLookupSystem>();
|
||||
var entityLookup = IoCManager.Resolve<IEntityLookup>();
|
||||
|
||||
// TODO: Make this a foreach and reduce LINQ chain because it'll allocate a LOT
|
||||
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
|
||||
|
||||
@@ -29,13 +29,14 @@ 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")
|
||||
{
|
||||
mapMgr.AddUninitializedMap(mapId);
|
||||
pauseMgr.AddUninitializedMap(mapId);
|
||||
}
|
||||
|
||||
shell.WriteLine($"Map with ID {mapId} created.");
|
||||
@@ -218,11 +219,7 @@ namespace Robust.Server.Console.Commands
|
||||
}
|
||||
|
||||
IoCManager.Resolve<IMapLoader>().LoadMap(mapId, args[1]);
|
||||
|
||||
if (mapManager.MapExists(mapId))
|
||||
shell.WriteLine($"Map {mapId} has been loaded from {args[1]}.");
|
||||
else
|
||||
shell.WriteError($"Error while loading map from {args[1]}.");
|
||||
shell.WriteLine($"Map {mapId} has been loaded from {args[1]}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +318,7 @@ 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));
|
||||
@@ -331,13 +329,13 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (mapManager.IsMapInitialized(mapId))
|
||||
if (pauseManager.IsMapInitialized(mapId))
|
||||
{
|
||||
shell.WriteError("Map is already initialized!");
|
||||
return;
|
||||
}
|
||||
|
||||
mapManager.DoMapInitialize(mapId);
|
||||
pauseManager.DoMapInitialize(mapId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,16 +348,17 @@ 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, mapManager.IsMapInitialized(mapId),
|
||||
mapManager.IsMapPaused(mapId),
|
||||
mapManager.GetMapEntityId(mapId),
|
||||
string.Join(",", mapManager.GetAllMapGrids(mapId).Select(grid => grid.Index)));
|
||||
mapId, pauseManager.IsMapInitialized(mapId),
|
||||
pauseManager.IsMapPaused(mapId),
|
||||
string.Join(",", mapManager.GetAllMapGrids(mapId).Select(grid => grid.Index)),
|
||||
mapManager.GetMapEntityId(mapId));
|
||||
}
|
||||
|
||||
shell.WriteLine(msg.ToString());
|
||||
@@ -381,7 +380,7 @@ namespace Robust.Server.Console.Commands
|
||||
foreach (var grid in mapManager.GetAllGrids().OrderBy(grid => grid.Index.Value))
|
||||
{
|
||||
msg.AppendFormat("{0}: map: {1}, ent: {2}, pos: {3} \n",
|
||||
grid.Index, grid.ParentMapId, grid.GridEntityId, grid.WorldPosition);
|
||||
grid.Index, grid.ParentMapId, grid.WorldPosition, grid.GridEntityId);
|
||||
}
|
||||
|
||||
shell.WriteLine(msg.ToString());
|
||||
|
||||
@@ -109,8 +109,9 @@ namespace Robust.Server.Console.Commands
|
||||
private void SetupPlayer(MapId mapId, IConsoleShell shell, IPlayerSession? player, IMapManager mapManager)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
mapManager.SetMapPaused(mapId, false);
|
||||
var mapUid = mapManager.GetMapEntityIdOrThrow(mapId);
|
||||
var pauseManager = IoCManager.Resolve<IPauseManager>();
|
||||
pauseManager.SetMapPaused(mapId, false);
|
||||
var mapUid = IoCManager.Resolve<IMapManager>().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;
|
||||
|
||||
public sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
internal sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using JetBrains.Annotations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Physics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -37,7 +37,7 @@ namespace Robust.Server.GameObjects
|
||||
collideComp.BodyType = BodyType.Static;
|
||||
}
|
||||
|
||||
protected override void HandleMapCreated(MapChangedEvent eventArgs)
|
||||
protected override void HandleMapCreated(object? sender, MapEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Map == MapId.Nullspace) return;
|
||||
var mapUid = MapManager.GetMapEntityIdOrThrow(eventArgs.Map);
|
||||
|
||||
@@ -5,31 +5,30 @@ namespace Robust.Server.GameStates;
|
||||
|
||||
public struct ChunkIndicesEnumerator
|
||||
{
|
||||
private Vector2i _bottomLeft;
|
||||
private Vector2i _topRight;
|
||||
private Vector2i _topLeft;
|
||||
private Vector2i _bottomRight;
|
||||
|
||||
private int _x;
|
||||
private int _y;
|
||||
|
||||
public ChunkIndicesEnumerator(Vector2 viewPos, float range, float chunkSize)
|
||||
public ChunkIndicesEnumerator(Box2 viewBox, float chunkSize)
|
||||
{
|
||||
_bottomLeft = ((viewPos - range) / chunkSize).Floored();
|
||||
// Also floor this as we get the whole chunk anyway.
|
||||
_topRight = ((viewPos + range) / chunkSize).Floored();
|
||||
_topLeft = (viewBox.TopLeft / chunkSize).Floored();
|
||||
_bottomRight = (viewBox.BottomRight / chunkSize).Floored();
|
||||
|
||||
_x = _bottomLeft.X;
|
||||
_y = _bottomLeft.Y;
|
||||
_x = _topLeft.X;
|
||||
_y = _bottomRight.Y;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out Vector2i? chunkIndices)
|
||||
{
|
||||
if (_y > _topRight.Y)
|
||||
if (_y > _topLeft.Y)
|
||||
{
|
||||
_x++;
|
||||
_y = _bottomLeft.Y;
|
||||
_y = _bottomRight.Y;
|
||||
}
|
||||
|
||||
if (_x > _topRight.X)
|
||||
if (_x > _bottomRight.X)
|
||||
{
|
||||
chunkIndices = null;
|
||||
return false;
|
||||
|
||||
@@ -37,6 +37,7 @@ public interface IPVSCollection
|
||||
public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : IComparable<TIndex>, IEquatable<TIndex>
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector2i GetChunkIndices(Vector2 coordinates)
|
||||
@@ -80,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="IIndexLocation"/>s of all <see cref="TIndex"/>.
|
||||
/// An index containing the <see cref="IndexLocation"/>s of all <see cref="TIndex"/>.
|
||||
/// </summary>
|
||||
private readonly Dictionary<TIndex, IIndexLocation> _indexLocations = new();
|
||||
private readonly Dictionary<TIndex, IndexLocation> _indexLocations = new();
|
||||
|
||||
/// <summary>
|
||||
/// Buffer of all locationchanges since the last process call
|
||||
/// </summary>
|
||||
private readonly Dictionary<TIndex, IIndexLocation> _locationChangeBuffer = new();
|
||||
private readonly Dictionary<TIndex, IndexLocation> _locationChangeBuffer = new();
|
||||
/// <summary>
|
||||
/// Buffer of all indexremovals since the last process call
|
||||
/// </summary>
|
||||
@@ -102,7 +103,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
{
|
||||
var changedIndices = new HashSet<TIndex>(_locationChangeBuffer.Keys);
|
||||
|
||||
var changedChunkLocations = new HashSet<IIndexLocation>();
|
||||
var changedChunkLocations = new HashSet<IndexLocation>();
|
||||
foreach (var (index, tick) in _removalBuffer)
|
||||
{
|
||||
//changes dont need to be computed if we are removing the index anyways
|
||||
@@ -153,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, IIndexLocation location)
|
||||
private void AddIndexInternal(TIndex index, IndexLocation location)
|
||||
{
|
||||
switch (location)
|
||||
{
|
||||
@@ -185,7 +186,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
_indexLocations.Add(index, location);
|
||||
}
|
||||
|
||||
private IIndexLocation? RemoveIndexInternal(TIndex index)
|
||||
private IndexLocation? RemoveIndexInternal(TIndex index)
|
||||
{
|
||||
// the index might be gone due to disconnects/grid-/map-deletions
|
||||
if (!_indexLocations.TryGetValue(index, out var location))
|
||||
@@ -351,14 +352,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
var gridId = coordinates.GetGridId(_entityManager);
|
||||
if (gridId != GridId.Invalid)
|
||||
{
|
||||
var gridIndices = GetChunkIndices(coordinates.Position);
|
||||
var gridIndices = GetChunkIndices(_mapManager.GetGrid(gridId).LocalToGrid(coordinates));
|
||||
UpdateIndex(index, gridId, gridIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
return;
|
||||
}
|
||||
|
||||
var mapCoordinates = coordinates.ToMap(_entityManager);
|
||||
var mapIndices = GetChunkIndices(coordinates.Position);
|
||||
UpdateIndex(index, mapCoordinates.MapId, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
var mapId = coordinates.GetMapId(_entityManager);
|
||||
var mapIndices = GetChunkIndices(coordinates.ToMapPos(_entityManager));
|
||||
UpdateIndex(index, mapId, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -391,7 +392,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
RegisterUpdate(index, new MapChunkLocation(mapId, chunkIndices));
|
||||
}
|
||||
|
||||
private void RegisterUpdate(TIndex index, IIndexLocation location)
|
||||
private void RegisterUpdate(TIndex index, IndexLocation location)
|
||||
{
|
||||
if(_indexLocations.TryGetValue(index, out var oldLocation) && oldLocation == location) return;
|
||||
|
||||
@@ -399,78 +400,14 @@ 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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,174 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
public sealed class RobustTree<T> where T : notnull
|
||||
{
|
||||
private Dictionary<T, TreeNode> _nodeIndex = new();
|
||||
|
||||
private Dictionary<T, T> _parents = new();
|
||||
public readonly HashSet<T> RootNodes = new();
|
||||
|
||||
private ObjectPool<HashSet<T>> _pool;
|
||||
|
||||
public RobustTree(ObjectPool<HashSet<T>>? pool = null)
|
||||
{
|
||||
_pool = pool ?? new DefaultObjectPool<HashSet<T>>(new PVSSystem.SetPolicy<T>());
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var value in _nodeIndex.Values)
|
||||
{
|
||||
if(value.Children != null)
|
||||
_pool.Return(value.Children);
|
||||
}
|
||||
_nodeIndex.Clear();
|
||||
_parents.Clear();
|
||||
RootNodes.Clear();
|
||||
}
|
||||
|
||||
public TreeNode this[T index] => _nodeIndex[index];
|
||||
|
||||
public void Remove(T value, bool mend = false)
|
||||
{
|
||||
if (!_nodeIndex.TryGetValue(value, out var node))
|
||||
throw new InvalidOperationException("Node doesnt exist.");
|
||||
|
||||
|
||||
if (RootNodes.Contains(value))
|
||||
{
|
||||
if (node.Children != null)
|
||||
{
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
_parents.Remove(child);
|
||||
RootNodes.Add(child);
|
||||
}
|
||||
_pool.Return(node.Children);
|
||||
}
|
||||
RootNodes.Remove(value);
|
||||
_nodeIndex.Remove(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_parents.TryGetValue(value, out var parent))
|
||||
{
|
||||
if (node.Children != null)
|
||||
{
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
if (mend)
|
||||
{
|
||||
_parents[child] = parent;
|
||||
var children = _nodeIndex[parent].Children;
|
||||
if (children == null)
|
||||
{
|
||||
children = _pool.Get();
|
||||
_nodeIndex[parent] = _nodeIndex[parent].WithChildren(children);
|
||||
}
|
||||
children.Add(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
_parents.Remove(child);
|
||||
RootNodes.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
_pool.Return(node.Children);
|
||||
}
|
||||
_parents.Remove(value);
|
||||
_nodeIndex.Remove(value);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Node neither had a parent nor was a RootNode.");
|
||||
}
|
||||
|
||||
public TreeNode Set(T rootNode)
|
||||
{
|
||||
//root node, for now
|
||||
if (_nodeIndex.TryGetValue(rootNode, out var node))
|
||||
{
|
||||
if(!RootNodes.Contains(rootNode))
|
||||
throw new InvalidOperationException("Node already exists as non-root node.");
|
||||
return node;
|
||||
}
|
||||
|
||||
node = new TreeNode(rootNode);
|
||||
_nodeIndex.Add(rootNode, node);
|
||||
RootNodes.Add(rootNode);
|
||||
return node;
|
||||
}
|
||||
|
||||
public TreeNode Set(T child, T parent)
|
||||
{
|
||||
if (!_nodeIndex.TryGetValue(parent, out var parentNode))
|
||||
parentNode = Set(parent);
|
||||
|
||||
if (parentNode.Children == null)
|
||||
{
|
||||
_nodeIndex[parent] = parentNode = parentNode.WithChildren(_pool.Get());
|
||||
}
|
||||
|
||||
if (_nodeIndex.TryGetValue(child, out var existingNode))
|
||||
{
|
||||
if (RootNodes.Contains(child))
|
||||
{
|
||||
parentNode.Children!.Add(existingNode.Value);
|
||||
RootNodes.Remove(child);
|
||||
_parents.Add(child, parent);
|
||||
return existingNode;
|
||||
}
|
||||
|
||||
if (!_parents.TryGetValue(child, out var previousParent) || _nodeIndex.TryGetValue(previousParent, out var previousParentNode))
|
||||
throw new InvalidOperationException("Could not find old parent for non-root node.");
|
||||
|
||||
previousParentNode.Children?.Remove(existingNode.Value);
|
||||
parentNode.Children!.Add(existingNode.Value);
|
||||
_parents[child] = parent;
|
||||
return existingNode;
|
||||
}
|
||||
|
||||
existingNode = new TreeNode(child);
|
||||
_nodeIndex.Add(child, existingNode);
|
||||
parentNode.Children!.Add(existingNode.Value);
|
||||
_parents.Add(child, parent);
|
||||
return existingNode;
|
||||
}
|
||||
|
||||
public readonly struct TreeNode : IEquatable<TreeNode>
|
||||
{
|
||||
public readonly T Value;
|
||||
public readonly HashSet<T>? Children;
|
||||
|
||||
public TreeNode(T value, HashSet<T>? children = null)
|
||||
{
|
||||
Value = value;
|
||||
Children = children;
|
||||
}
|
||||
|
||||
public bool Equals(TreeNode other)
|
||||
{
|
||||
return Value.Equals(other.Value) && Children?.Equals(other.Children) == true;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is TreeNode other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Value, Children);
|
||||
}
|
||||
|
||||
public TreeNode WithChildren(HashSet<T> children)
|
||||
{
|
||||
return new TreeNode(Value, children);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
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;
|
||||
@@ -128,37 +125,6 @@ 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!;
|
||||
EntityQuery<TransformComponent> transformQuery = default!;
|
||||
HashSet<int>[] playerChunks = default!;
|
||||
EntityUid[][] viewerEntities = default!;
|
||||
(Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[] 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> metadata, RobustTree<EntityUid> tree)?[chunksCount];
|
||||
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);
|
||||
|
||||
@@ -169,9 +135,11 @@ namespace Robust.Server.GameStates
|
||||
|
||||
for (var j = start; j < end; ++j)
|
||||
{
|
||||
var session = players[j];
|
||||
|
||||
try
|
||||
{
|
||||
SendStateUpdate(j);
|
||||
SendStateUpdate(session);
|
||||
}
|
||||
catch (Exception e) // Catch EVERY exception
|
||||
{
|
||||
@@ -180,10 +148,8 @@ namespace Robust.Server.GameStates
|
||||
}
|
||||
});
|
||||
|
||||
void SendStateUpdate(int sessionIndex)
|
||||
void SendStateUpdate(IPlayerSession session)
|
||||
{
|
||||
var session = players[sessionIndex];
|
||||
|
||||
// KILL IT WITH FIRE
|
||||
if(mainThread != Thread.CurrentThread)
|
||||
IoCManager.InitThread(new DependencyCollection(parentDeps), true);
|
||||
@@ -195,10 +161,7 @@ namespace Robust.Server.GameStates
|
||||
DebugTools.Assert("Why does this channel not have an entry?");
|
||||
}
|
||||
|
||||
var (entStates, deletions) = _pvs.CullingEnabled
|
||||
? _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick, chunkCache,
|
||||
playerChunks[sessionIndex], metadataQuery, transformQuery, viewerEntities[sessionIndex])
|
||||
: _pvs.GetAllEntityStates(session, lastAck, _gameTiming.CurTick);
|
||||
var (entStates, deletions) = _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick);
|
||||
var playerStates = _playerManager.GetPlayerStates(lastAck);
|
||||
var mapData = _mapManager.GetStateData(lastAck);
|
||||
|
||||
@@ -209,6 +172,8 @@ namespace Robust.Server.GameStates
|
||||
|
||||
InterlockedHelper.Min(ref oldestAckValue, lastAck.Value);
|
||||
|
||||
DebugTools.Assert(state.MapData?.CreatedMaps is null || (state.MapData?.CreatedMaps is not null && state.EntityStates.HasContents), "Sending new maps, but no entity state.");
|
||||
|
||||
// actually send the state
|
||||
var stateUpdateMessage = _networkManager.CreateNetMessage<MsgState>();
|
||||
stateUpdateMessage.State = state;
|
||||
@@ -229,8 +194,6 @@ 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,6 +24,7 @@ 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;
|
||||
@@ -43,6 +44,7 @@ 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;
|
||||
@@ -52,7 +54,7 @@ namespace Robust.Server.Maps
|
||||
{
|
||||
var grid = _mapManager.GetGrid(gridId);
|
||||
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _prototypeManager);
|
||||
context.RegisterGrid(grid);
|
||||
var root = context.Serialize();
|
||||
var document = new YamlDocument(root);
|
||||
@@ -98,7 +100,7 @@ namespace Robust.Server.Maps
|
||||
throw new InvalidDataException("Cannot instance map with multiple grids as blueprint.");
|
||||
}
|
||||
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
|
||||
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
|
||||
context.Deserialize();
|
||||
grid = context.Grids[0];
|
||||
@@ -118,7 +120,7 @@ namespace Robust.Server.Maps
|
||||
_serverEntityManager.GetComponent<MetaDataComponent>(entity).EntityLifeStage = EntityLifeStage.MapInitialized;
|
||||
}
|
||||
}
|
||||
else if (_mapManager.IsMapInitialized(mapId))
|
||||
else if (_pauseManager.IsMapInitialized(mapId))
|
||||
{
|
||||
foreach (var entity in context.Entities)
|
||||
{
|
||||
@@ -126,7 +128,7 @@ namespace Robust.Server.Maps
|
||||
}
|
||||
}
|
||||
|
||||
if (_mapManager.IsMapPaused(mapId))
|
||||
if (_pauseManager.IsMapPaused(mapId))
|
||||
{
|
||||
foreach (var entity in context.Entities)
|
||||
{
|
||||
@@ -139,7 +141,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, _prototypeManager);
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _prototypeManager);
|
||||
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
|
||||
{
|
||||
context.RegisterGrid(grid);
|
||||
@@ -205,7 +207,7 @@ namespace Robust.Server.Maps
|
||||
|
||||
LoadedMapData?.Invoke(data.Stream, resPath.ToString());
|
||||
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
|
||||
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
|
||||
context.Deserialize();
|
||||
|
||||
@@ -224,12 +226,12 @@ 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;
|
||||
private readonly Dictionary<GridId, int> GridIDMap = new();
|
||||
public readonly List<MapGrid> Grids = new();
|
||||
private readonly List<GridId> _readGridIndices = new();
|
||||
|
||||
private readonly Dictionary<EntityUid, int> EntityUidMap = new();
|
||||
private readonly Dictionary<int, EntityUid> UidEntityMap = new();
|
||||
@@ -258,11 +260,12 @@ namespace Robust.Server.Maps
|
||||
public bool MapIsPostInit { get; private set; }
|
||||
|
||||
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
|
||||
IServerEntityManagerInternal entities, IPrototypeManager prototypeManager)
|
||||
IServerEntityManagerInternal entities, IPauseManager pauseManager, IPrototypeManager prototypeManager)
|
||||
{
|
||||
_mapManager = maps;
|
||||
_tileDefinitionManager = tileDefs;
|
||||
_serverEntityManager = entities;
|
||||
_pauseManager = pauseManager;
|
||||
_prototypeManager = prototypeManager;
|
||||
|
||||
RootNode = new YamlMappingNode();
|
||||
@@ -280,12 +283,13 @@ namespace Robust.Server.Maps
|
||||
|
||||
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
|
||||
IServerEntityManagerInternal entities,
|
||||
IPrototypeManager prototypeManager,
|
||||
IPauseManager pauseManager, IPrototypeManager prototypeManager,
|
||||
YamlMappingNode node, MapId targetMapId, MapLoadOptions options)
|
||||
{
|
||||
_mapManager = maps;
|
||||
_tileDefinitionManager = tileDefs;
|
||||
_serverEntityManager = entities;
|
||||
_pauseManager = pauseManager;
|
||||
_loadOptions = options;
|
||||
|
||||
RootNode = node;
|
||||
@@ -315,8 +319,9 @@ namespace Robust.Server.Maps
|
||||
// Create the new map.
|
||||
AllocMap();
|
||||
|
||||
// Maps grid section indices to GridIds, for deserializing GridIds on entities.
|
||||
ReadGridSectionIndices();
|
||||
// Load grids.
|
||||
ReadTileMapSection();
|
||||
ReadGridSection();
|
||||
|
||||
// Entities are first allocated. This allows us to know the future UID of all entities on the map before
|
||||
// even ExposeData is loaded. This allows us to resolve serialized EntityUid instances correctly.
|
||||
@@ -325,11 +330,8 @@ namespace Robust.Server.Maps
|
||||
// Actually instance components and run ExposeData on them.
|
||||
FinishEntitiesLoad();
|
||||
|
||||
// Load grids.
|
||||
ReadTileMapSection();
|
||||
|
||||
// Reads the grid section, allocates MapGrids, and maps them to their respective MapGridComponents.
|
||||
ReadGridSection();
|
||||
// Finish binding MapGrids to their respective MapGridComponents.
|
||||
BindGridComponents();
|
||||
|
||||
// Clear the net tick numbers so that components from prototypes (not modified by map)
|
||||
// aren't sent over the wire initially.
|
||||
@@ -477,20 +479,18 @@ namespace Robust.Server.Maps
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadGridSection()
|
||||
private void BindGridComponents()
|
||||
{
|
||||
// There were no new grids, nothing to do here.
|
||||
if(_readGridIndices.Count == 0)
|
||||
if(Grids.Count == 0)
|
||||
return;
|
||||
|
||||
// MapGrids already contain their assigned GridId from their ctor, and the MapComponents just got deserialized.
|
||||
// Now we need to actually bind the MapGrids to their components so that you can resolve GridId -> EntityUid
|
||||
// After doing this, it should be 100% safe to use the MapManager API like normal.
|
||||
|
||||
var yamlGrids = RootNode.GetNode<YamlSequenceNode>("grids");
|
||||
|
||||
// get ents that the grids will bind to
|
||||
var gridComps = new Dictionary<GridId, MapGridComponent>(_readGridIndices.Count);
|
||||
var gridComps = new Dictionary<GridId, MapGridComponent>(Grids.Count);
|
||||
|
||||
// linear search for new grid comps
|
||||
foreach (var tuple in _entitiesToDeserialize)
|
||||
@@ -507,54 +507,15 @@ namespace Robust.Server.Maps
|
||||
gridComps[gridComp.GridIndex] = gridComp;
|
||||
}
|
||||
|
||||
for (var index = 0; index < _readGridIndices.Count; index++)
|
||||
// Actually bind them
|
||||
foreach (var mapGrid in Grids)
|
||||
{
|
||||
// Here is where the implicit index pairing magic happens from the yaml.
|
||||
var gridIndex = _readGridIndices[index];
|
||||
var yamlGrid = (YamlMappingNode)yamlGrids.Children[index];
|
||||
|
||||
// designed to throw if something is broken, every grid must map to an ent
|
||||
var gridComp = gridComps[gridIndex];
|
||||
|
||||
DebugTools.Assert(gridComp.GridIndex == gridIndex);
|
||||
|
||||
YamlMappingNode yamlGridInfo = (YamlMappingNode)yamlGrid["settings"];
|
||||
YamlSequenceNode yamlGridChunks = (YamlSequenceNode)yamlGrid["chunks"];
|
||||
|
||||
var grid = AllocateMapGrid(gridComp, yamlGridInfo);
|
||||
|
||||
foreach (var chunkNode in yamlGridChunks.Cast<YamlMappingNode>())
|
||||
{
|
||||
YamlGridSerializer.DeserializeChunk(_mapManager, grid, chunkNode, _tileMap!, _tileDefinitionManager);
|
||||
}
|
||||
|
||||
Grids.Add(grid); // Grids are kept in index order
|
||||
var gridComp = gridComps[mapGrid.Index];
|
||||
_mapManager.BindGrid(gridComp, mapGrid);
|
||||
}
|
||||
}
|
||||
|
||||
private static MapGrid AllocateMapGrid(MapGridComponent gridComp, YamlMappingNode yamlGridInfo)
|
||||
{
|
||||
// sane defaults
|
||||
ushort csz = 16;
|
||||
ushort tsz = 1;
|
||||
|
||||
foreach (var kvInfo in yamlGridInfo)
|
||||
{
|
||||
var key = kvInfo.Key.ToString();
|
||||
var val = kvInfo.Value.ToString();
|
||||
if (key == "chunksize")
|
||||
csz = ushort.Parse(val);
|
||||
else if (key == "tilesize")
|
||||
tsz = ushort.Parse(val);
|
||||
else if (key == "snapsize")
|
||||
continue; // obsolete
|
||||
}
|
||||
|
||||
var grid = gridComp.AllocMapGrid(csz, tsz);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private void AttachMapEntities()
|
||||
{
|
||||
var mapEntity = _mapManager.GetMapEntityIdOrThrow(TargetMap);
|
||||
@@ -621,15 +582,21 @@ namespace Robust.Server.Maps
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadGridSectionIndices()
|
||||
private void ReadGridSection()
|
||||
{
|
||||
// sets up the mapping so the serializer can properly deserialize GridIds.
|
||||
|
||||
var yamlGrids = RootNode.GetNode<YamlSequenceNode>("grids");
|
||||
|
||||
for (var i = 0; i < yamlGrids.Children.Count; i++)
|
||||
foreach (var yamlGrid in yamlGrids)
|
||||
{
|
||||
_readGridIndices.Add(_mapManager.GenerateGridId(null));
|
||||
var grid = YamlGridSerializer.DeserializeGrid(
|
||||
_mapManager, TargetMap,
|
||||
(YamlMappingNode) yamlGrid["settings"],
|
||||
(YamlSequenceNode) yamlGrid["chunks"],
|
||||
_tileMap!,
|
||||
_tileDefinitionManager
|
||||
);
|
||||
|
||||
Grids.Add(grid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,7 +612,7 @@ namespace Robust.Server.Maps
|
||||
|
||||
if (!MapIsPostInit)
|
||||
{
|
||||
_mapManager.AddUninitializedMap(TargetMap);
|
||||
_pauseManager.AddUninitializedMap(TargetMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -752,7 +719,7 @@ namespace Robust.Server.Maps
|
||||
var isPostInit = false;
|
||||
foreach (var grid in Grids)
|
||||
{
|
||||
if (_mapManager.IsMapInitialized(grid.ParentMapId))
|
||||
if (_pauseManager.IsMapInitialized(grid.ParentMapId))
|
||||
{
|
||||
isPostInit = true;
|
||||
break;
|
||||
@@ -961,13 +928,6 @@ namespace Robust.Server.Maps
|
||||
return true;
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
public class MapLoadException : Exception
|
||||
{
|
||||
public MapLoadException(string? message)
|
||||
: base(message) { }
|
||||
}
|
||||
|
||||
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
bool skipHook,
|
||||
@@ -976,18 +936,19 @@ namespace Robust.Server.Maps
|
||||
// This is the code that deserializes the Grids index into the GridId. This has to happen between Grid allocation
|
||||
// and when grids are bound to their entities.
|
||||
|
||||
if (node.Value == "null")
|
||||
{
|
||||
throw new MapLoadException($"Error in map file: found local grid ID '{node.Value}' which does not exist.");
|
||||
}
|
||||
if (node.Value == "null") return new DeserializedValue<GridId>(GridId.Invalid);
|
||||
|
||||
var val = int.Parse(node.Value);
|
||||
if (val >= _readGridIndices.Count)
|
||||
if (val >= Grids.Count)
|
||||
{
|
||||
throw new MapLoadException($"Error in map file: found local grid ID '{val}' which does not exist.");
|
||||
Logger.ErrorS("map", "Error in map file: found local grid ID '{0}' which does not exist.", val);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DeserializedValue<GridId>(Grids[val].Index);
|
||||
}
|
||||
|
||||
return new DeserializedValue<GridId>(_readGridIndices[val]);
|
||||
return new DeserializedValue<GridId>(GridId.Invalid);
|
||||
}
|
||||
|
||||
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(ISerializationManager serializationManager,
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Server.Maps
|
||||
return gridn;
|
||||
}
|
||||
|
||||
private static YamlNode SerializeChunk(MapChunk chunk)
|
||||
private static YamlNode SerializeChunk(IMapChunk chunk)
|
||||
{
|
||||
var root = new YamlMappingNode();
|
||||
var value = new YamlScalarNode($"{chunk.X},{chunk.Y}");
|
||||
@@ -51,7 +51,7 @@ namespace Robust.Server.Maps
|
||||
return root;
|
||||
}
|
||||
|
||||
private static string SerializeTiles(MapChunk chunk)
|
||||
private static string SerializeTiles(IMapChunk chunk)
|
||||
{
|
||||
// number of bytes written per tile, because sizeof(Tile) is useless.
|
||||
const int structSize = 4;
|
||||
@@ -68,8 +68,7 @@ namespace Robust.Server.Maps
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
writer.Write(tile.TypeId);
|
||||
writer.Write((byte)tile.Flags);
|
||||
writer.Write(tile.Variant);
|
||||
writer.Write(tile.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,10 +76,38 @@ namespace Robust.Server.Maps
|
||||
return Convert.ToBase64String(barr);
|
||||
}
|
||||
|
||||
public static void DeserializeChunk(IMapManager mapMan, IMapGridInternal grid,
|
||||
YamlMappingNode chunkData,
|
||||
IReadOnlyDictionary<ushort, string> tileDefMapping,
|
||||
public static MapGrid DeserializeGrid(IMapManagerInternal mapMan, MapId mapId, YamlMappingNode info,
|
||||
YamlSequenceNode chunks, IReadOnlyDictionary<ushort, string> tileDefMapping,
|
||||
ITileDefinitionManager tileDefinitionManager)
|
||||
{
|
||||
ushort csz = 0;
|
||||
ushort tsz = 0;
|
||||
float sgsz = 0.0f;
|
||||
|
||||
foreach (var kvInfo in info)
|
||||
{
|
||||
var key = kvInfo.Key.ToString();
|
||||
var val = kvInfo.Value.ToString();
|
||||
if (key == "chunksize")
|
||||
csz = ushort.Parse(val);
|
||||
else if (key == "tilesize")
|
||||
tsz = ushort.Parse(val);
|
||||
else if (key == "snapsize")
|
||||
sgsz = float.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
//TODO: Pass in options
|
||||
var grid = mapMan.CreateUnboundGrid(mapId);
|
||||
|
||||
foreach (var chunkNode in chunks.Cast<YamlMappingNode>())
|
||||
{
|
||||
DeserializeChunk(mapMan, grid, chunkNode, tileDefMapping, tileDefinitionManager);
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private static void DeserializeChunk(IMapManager mapMan, IMapGridInternal grid, YamlMappingNode chunkData, IReadOnlyDictionary<ushort, string> tileDefMapping, ITileDefinitionManager tileDefinitionManager)
|
||||
{
|
||||
var indNode = chunkData["ind"];
|
||||
var tileNode = chunkData["tiles"];
|
||||
@@ -102,13 +129,12 @@ namespace Robust.Server.Maps
|
||||
for (ushort x = 0; x < grid.ChunkSize; x++)
|
||||
{
|
||||
var id = reader.ReadUInt16();
|
||||
var flags = (TileRenderFlag)reader.ReadByte();
|
||||
var variant = reader.ReadByte();
|
||||
var data = reader.ReadUInt16();
|
||||
|
||||
var defName = tileDefMapping[id];
|
||||
id = tileDefinitionManager[defName].TileId;
|
||||
|
||||
var tile = new Tile(id, flags, variant);
|
||||
var tile = new Tile(id, data);
|
||||
chunk.SetTile(x, y, tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace Robust.Server.Placement
|
||||
{
|
||||
EntityCoordinates start = msg.EntityCoordinates;
|
||||
Vector2 rectSize = msg.RectSize;
|
||||
foreach (EntityUid entity in EntitySystem.Get<EntityLookupSystem>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
|
||||
foreach (EntityUid entity in IoCManager.Resolve<IEntityLookup>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
|
||||
new Box2(start.Position, start.Position + rectSize)))
|
||||
{
|
||||
if (_entityManager.Deleted(entity) || _entityManager.HasComponent<IMapGridComponent>(entity) || _entityManager.HasComponent<ActorComponent>(entity))
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
</PropertyGroup>
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
|
||||
<!-- -->
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.7" />
|
||||
<PackageReference Include="prometheus-net" Version="4.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Primitives" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Primitives" Version="5.0.0" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace Robust.Server
|
||||
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
|
||||
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
|
||||
IoCManager.Register<IEntityManager, ServerEntityManager>();
|
||||
IoCManager.Register<IEntityLookup, EntityLookup>();
|
||||
IoCManager.Register<IEntityNetworkManager, ServerEntityManager>();
|
||||
IoCManager.Register<IServerEntityNetworkManager, ServerEntityManager>();
|
||||
IoCManager.Register<IMapLoader, MapLoader>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -135,17 +135,5 @@ namespace Robust.Shared.Maths
|
||||
{
|
||||
return $"({Left}, {Bottom}, {Right}, {Top})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies each side of the box by the scalar.
|
||||
/// </summary>
|
||||
public Box2i Scale(int scalar)
|
||||
{
|
||||
return new Box2i(
|
||||
Left * scalar,
|
||||
Bottom * scalar,
|
||||
Right * scalar,
|
||||
Top * scalar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
|
||||
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -130,12 +130,6 @@ 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>
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="3.8.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.8.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -128,13 +128,13 @@ namespace Robust.Shared
|
||||
/// The amount of new entities that can be sent to a client in a single game state, under PVS.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> NetPVSNewEntityBudget =
|
||||
CVarDef.Create("net.pvs_new_budget", 20, CVar.ARCHIVE | CVar.REPLICATED);
|
||||
CVarDef.Create("net.pvs_new_budget", 20, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// The amount of entered entities that can be sent to a client in a single game state, under PVS.
|
||||
/// The amount of entity updates that can be sent to a client in a single game state, under PVS.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> NetPVSEntityBudget =
|
||||
CVarDef.Create("net.pvs_budget", 50, CVar.ARCHIVE | CVar.REPLICATED);
|
||||
CVarDef.Create("net.pvs_budget", 50, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// Log late input messages from clients.
|
||||
@@ -1006,17 +1006,6 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<bool> ResTexturePreloadCache =
|
||||
CVarDef.Create("res.texture_preload_cache", true, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Override seekability of resource streams returned by ResourceManager.
|
||||
/// See <see cref="ContentPack.StreamSeekMode"/> for int values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is intended to be a debugging tool primarily.
|
||||
/// Non-default seek modes WILL result in worse performance.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> ResStreamSeekMode =
|
||||
CVarDef.Create("res.stream_seek_mode", (int)ContentPack.StreamSeekMode.None);
|
||||
|
||||
/*
|
||||
* DEBUG
|
||||
*/
|
||||
|
||||
@@ -20,16 +20,6 @@ namespace Robust.Shared.Configuration
|
||||
/// </summary>
|
||||
void SetupNetworking();
|
||||
|
||||
/// <summary>
|
||||
/// Get a replicated client CVar for a specific client.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">CVar type.</typeparam>
|
||||
/// <param name="channel">channel of the connected client.</param>
|
||||
/// <param name="definition">The CVar.</param>
|
||||
/// <returns>Replicated CVar of the client.</returns>
|
||||
public T GetClientCVar<T>(INetChannel channel, CVarDef<T> definition) where T : notnull =>
|
||||
GetClientCVar<T>(channel, definition.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Get a replicated client CVar for a specific client.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Robust.Shared.Console;
|
||||
|
||||
public sealed class CommandBuffer
|
||||
{
|
||||
private const string DelayMarker = "-DELAY-";
|
||||
|
||||
private int _tickrate = 0;
|
||||
private int _delay = 0;
|
||||
|
||||
private readonly LinkedList<string> _commandBuffer = new();
|
||||
|
||||
public void Append(string command)
|
||||
{
|
||||
_commandBuffer.AddLast(command);
|
||||
}
|
||||
|
||||
public void Insert(string command)
|
||||
{
|
||||
_commandBuffer.AddFirst(command);
|
||||
}
|
||||
|
||||
public void Tick(byte tickRate)
|
||||
{
|
||||
_tickrate = tickRate;
|
||||
|
||||
if (_delay > 0)
|
||||
{
|
||||
_delay -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetCommand([MaybeNullWhen(false)]out string command)
|
||||
{
|
||||
var next = _commandBuffer.First;
|
||||
|
||||
if (next is null) // nothing to do here
|
||||
{
|
||||
command = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (next.Value.Equals(DelayMarker))
|
||||
{
|
||||
if (_delay == 0) // just finished
|
||||
{
|
||||
_commandBuffer.RemoveFirst();
|
||||
return TryGetCommand(out command);
|
||||
}
|
||||
else // currently counting down delay
|
||||
{
|
||||
command = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (next.Value.StartsWith("wait "))
|
||||
{
|
||||
var sTicks = next.Value.Substring(5);
|
||||
_commandBuffer.RemoveFirst();
|
||||
if (string.IsNullOrWhiteSpace(sTicks) || !int.TryParse(sTicks, out var ticks)) // messed up command
|
||||
{
|
||||
return TryGetCommand(out command);
|
||||
}
|
||||
|
||||
// Setup Timing
|
||||
_commandBuffer.AddFirst(DelayMarker);
|
||||
_delay = ticks;
|
||||
|
||||
command = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// normal command
|
||||
_commandBuffer.RemoveFirst();
|
||||
command = next.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ namespace Robust.Shared.Console.Commands
|
||||
continue;
|
||||
}
|
||||
|
||||
shell.ConsoleHost.AppendCommand(line);
|
||||
shell.ExecuteCommand(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
@@ -18,16 +17,13 @@ namespace Robust.Shared.Console
|
||||
protected const string SawmillName = "con";
|
||||
|
||||
[Dependency] protected readonly ILogManager LogManager = default!;
|
||||
[Dependency] private readonly IReflectionManager ReflectionManager = default!;
|
||||
[Dependency] protected readonly IReflectionManager ReflectionManager = default!;
|
||||
[Dependency] protected readonly INetManager NetManager = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _typeFactory = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
[ViewVariables]
|
||||
protected readonly Dictionary<string, IConsoleCommand> AvailableCommands = new();
|
||||
|
||||
private readonly CommandBuffer _commandBuffer = new CommandBuffer();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsServer => NetManager.IsServer;
|
||||
|
||||
@@ -120,36 +116,6 @@ namespace Robust.Shared.Console
|
||||
ExecuteCommand(null, command);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AppendCommand(string command)
|
||||
{
|
||||
_commandBuffer.Append(command);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void InsertCommand(string command)
|
||||
{
|
||||
_commandBuffer.Insert(command);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CommandBufferExecute()
|
||||
{
|
||||
_commandBuffer.Tick(_timing.TickRate);
|
||||
|
||||
while (_commandBuffer.TryGetCommand(out var cmd))
|
||||
{
|
||||
try
|
||||
{
|
||||
ExecuteCommand(cmd);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LocalShell.WriteError(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A console command that was registered inline through <see cref="IConsoleHost"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -81,35 +81,11 @@ namespace Robust.Shared.Console
|
||||
IConsoleShell GetSessionShell(ICommonSession session);
|
||||
|
||||
/// <summary>
|
||||
/// Execute a command string immediately on the local shell, bypassing the command buffer completely.
|
||||
/// Execute a command string on the local shell.
|
||||
/// </summary>
|
||||
/// <param name="command">Command string to execute.</param>
|
||||
void ExecuteCommand(string command);
|
||||
|
||||
/// <summary>
|
||||
/// Appends a command into the end of the command buffer on the local shell.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This command will be ran *sometime* in the future, depending on how many waits are in the buffer.
|
||||
/// </remarks>
|
||||
/// <param name="command">Command string to execute.</param>
|
||||
void AppendCommand(string command);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a command into the front of the command buffer on the local shell.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This command will preempt the next command executed in the command buffer.
|
||||
/// </remarks>
|
||||
/// <param name="command">Command string to execute.</param>
|
||||
void InsertCommand(string command);
|
||||
|
||||
/// <summary>
|
||||
/// Processes any contents of the command buffer on the local shell. This needs to be called regularly (once a tick),
|
||||
/// inside the simulation. Pausing the server should prevent the buffer from being processed.
|
||||
/// </summary>
|
||||
void CommandBufferExecute();
|
||||
|
||||
/// <summary>
|
||||
/// Executes a command string on this specific session shell. If the command does not exist, the command will be forwarded
|
||||
/// to the
|
||||
|
||||
@@ -81,7 +81,6 @@ namespace Robust.Shared.Containers
|
||||
// spatially move the object to the location of the container. If you don't want this functionality, the
|
||||
// calling code can save the local position before calling this function, and apply it afterwords.
|
||||
transform.LocalPosition = Vector2.Zero;
|
||||
transform.LocalRotation = Angle.Zero;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -209,43 +209,6 @@ namespace Robust.Shared.Containers
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the top-most container in the hierarchy for this entity, if it exists.
|
||||
/// </summary>
|
||||
public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, [NotNullWhen(true)] out IContainer? container)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
return TryGetOuterContainer(uid, xform, out container, xformQuery);
|
||||
}
|
||||
|
||||
public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform,
|
||||
[NotNullWhen(true)] out IContainer? container, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
container = null;
|
||||
|
||||
if (!uid.IsValid())
|
||||
return false;
|
||||
|
||||
var conQuery = EntityManager.GetEntityQuery<ContainerManagerComponent>();
|
||||
var child = uid;
|
||||
var parent = xform.ParentUid;
|
||||
|
||||
while (parent.IsValid())
|
||||
{
|
||||
if (conQuery.TryGetComponent(parent, out var conManager) &&
|
||||
conManager.TryGetContainer(child, out var parentContainer))
|
||||
{
|
||||
container = parentContainer;
|
||||
}
|
||||
|
||||
var parentXform = xformQuery.GetComponent(parent);
|
||||
child = parent;
|
||||
parent = parentXform.ParentUid;
|
||||
}
|
||||
|
||||
return container != null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Eject entities from their parent container if the parent change is done by the transform only.
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Robust.Shared.ContentPack
|
||||
var fullStopwatch = Stopwatch.StartNew();
|
||||
|
||||
var resolver = CreateResolver();
|
||||
using var peReader = ModLoader.MakePEReader(assembly, leaveOpen: true);
|
||||
using var peReader = new PEReader(assembly, PEStreamOptions.LeaveOpen);
|
||||
var reader = peReader.GetMetadataReader();
|
||||
|
||||
var asmName = reader.GetString(reader.GetAssemblyDefinition().Name);
|
||||
@@ -853,13 +853,13 @@ namespace Robust.Shared.ContentPack
|
||||
continue;
|
||||
}
|
||||
|
||||
return ModLoader.MakePEReader(File.OpenRead(path));
|
||||
return ReaderFromStream(File.OpenRead(path));
|
||||
}
|
||||
|
||||
var extraStream = _parent.ExtraRobustLoader?.Invoke(dllName);
|
||||
if (extraStream != null)
|
||||
{
|
||||
return ModLoader.MakePEReader(extraStream);
|
||||
return ReaderFromStream(extraStream);
|
||||
}
|
||||
|
||||
foreach (var resLoadPath in _resLoadPaths)
|
||||
@@ -867,7 +867,7 @@ namespace Robust.Shared.ContentPack
|
||||
try
|
||||
{
|
||||
var path = resLoadPath / dllName;
|
||||
return ModLoader.MakePEReader(_parent._res.ContentFileRead(path));
|
||||
return ReaderFromStream(_parent._res.ContentFileRead(path));
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
@@ -877,6 +877,23 @@ namespace Robust.Shared.ContentPack
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PEReader ReaderFromStream(Stream stream)
|
||||
{
|
||||
if (OperatingSystem.IsLinux() && stream is FileStream)
|
||||
{
|
||||
// PEReader is bugged on Linux and not properly thread safe when doing memory mapping.
|
||||
// As such, we never pass it a file stream so it uses a different, non-bugged code path.
|
||||
// See https://github.com/dotnet/runtime/issues/60545
|
||||
var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
stream.Dispose();
|
||||
stream = ms;
|
||||
}
|
||||
|
||||
return new PEReader(stream);
|
||||
}
|
||||
|
||||
public PEReader? Resolve(string simpleName)
|
||||
{
|
||||
return _dictionary.GetOrAdd(simpleName, ResolveCore);
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack;
|
||||
|
||||
/// <summary>
|
||||
/// A content root stored in memory, backed by a dictionary.
|
||||
/// </summary>
|
||||
public sealed class MemoryContentRoot : IContentRoot, IDisposable
|
||||
{
|
||||
private readonly Dictionary<ResourcePath, byte[]> _files = new();
|
||||
|
||||
private readonly ReaderWriterLockSlim _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a file to the content root, or updates it if that path already exists.
|
||||
/// </summary>
|
||||
/// <param name="relPath">The relative path of the file.</param>
|
||||
/// <param name="data">The data byte array to store in the content root. Stored as is, without being copied or cloned.</param>
|
||||
public void AddOrUpdateFile(ResourcePath relPath, byte[] data)
|
||||
{
|
||||
// Just in case, we ensure it's a clean relative path.
|
||||
relPath = relPath.Clean().ToRelativePath();
|
||||
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_files[relPath] = data;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a file from this content root.
|
||||
/// </summary>
|
||||
/// <param name="relPath">The relative path to the file.</param>
|
||||
/// <returns></returns>
|
||||
public bool RemoveFile(ResourcePath relPath)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
return _files.Remove(relPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetFile(ResourcePath relPath, [NotNullWhen(true)] out Stream? stream)
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (!_files.TryGetValue(relPath, out var data))
|
||||
{
|
||||
stream = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Non-writable stream, as this needs to be thread-safe.
|
||||
stream = new MemoryStream(data, false);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ResourcePath> FindFiles(ResourcePath path)
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
foreach (var (file, _) in _files)
|
||||
{
|
||||
if (file.TryRelativeTo(path, out _))
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> GetRelativeFilePaths()
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
foreach (var (file, _) in _files)
|
||||
{
|
||||
yield return file.ToString();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates all files and their resource paths on this content root.
|
||||
/// </summary>
|
||||
/// <remarks>Do not modify or keep around the returned byte array, it's meant to be read-only.</remarks>
|
||||
public IEnumerable<(ResourcePath relPath, byte[] data)> GetAllFiles()
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
foreach (var (p, d) in _files)
|
||||
{
|
||||
yield return (p, d);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Mount()
|
||||
{
|
||||
// Nada. We don't need to perform any special logic here.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_lock.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
private static (string[] refs, string name) GetAssemblyReferenceData(Stream stream)
|
||||
{
|
||||
using var reader = ModLoader.MakePEReader(stream);
|
||||
using var reader = new PEReader(stream);
|
||||
var metaReader = reader.GetMetadataReader();
|
||||
|
||||
var name = metaReader.GetString(metaReader.GetAssemblyDefinition().Name);
|
||||
@@ -383,13 +383,5 @@ namespace Robust.Shared.ContentPack
|
||||
EngineModuleDirectories = _engineModuleDirectories.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
internal static PEReader MakePEReader(Stream stream, bool leaveOpen=false)
|
||||
{
|
||||
if (!stream.CanSeek)
|
||||
stream = leaveOpen ? stream.CopyToMemoryStream() : stream.ConsumeToMemoryStream();
|
||||
|
||||
return new PEReader(stream, leaveOpen ? PEStreamOptions.LeaveOpen : default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Robust.Shared.ContentPack;
|
||||
|
||||
internal sealed class NonSeekableStream : Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
|
||||
public NonSeekableStream(Stream baseStream)
|
||||
{
|
||||
_baseStream = baseStream;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
_baseStream.Dispose();
|
||||
}
|
||||
|
||||
public override ValueTask DisposeAsync()
|
||||
{
|
||||
return _baseStream.DisposeAsync();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return _baseStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
return _baseStream.Read(buffer);
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
return _baseStream.ReadByte();
|
||||
}
|
||||
|
||||
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return _baseStream.ReadAsync(buffer, cancellationToken);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_baseStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
_baseStream.Write(buffer);
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
_baseStream.WriteByte(value);
|
||||
}
|
||||
|
||||
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return _baseStream.WriteAsync(buffer, cancellationToken);
|
||||
}
|
||||
|
||||
public override bool CanRead => _baseStream.CanRead;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => _baseStream.CanWrite;
|
||||
// .NET mingles seekability and exposing length.
|
||||
// This makes absolutely no sense but ok.
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,6 @@ namespace Robust.Shared.ContentPack
|
||||
private readonly List<(ResourcePath prefix, IContentRoot root)> _contentRoots =
|
||||
new();
|
||||
|
||||
private StreamSeekMode _streamSeekMode;
|
||||
|
||||
// Special file names on Windows like serial ports.
|
||||
private static readonly Regex BadPathSegmentRegex =
|
||||
new("^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$", RegexOptions.IgnoreCase);
|
||||
@@ -47,8 +45,6 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
UserData = new VirtualWritableDirProvider();
|
||||
}
|
||||
|
||||
_config.OnValueChanged(CVars.ResStreamSeekMode, i => _streamSeekMode = (StreamSeekMode)i, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -195,9 +191,8 @@ namespace Robust.Shared.ContentPack
|
||||
continue;
|
||||
}
|
||||
|
||||
if (root.TryGetFile(relative, out var stream))
|
||||
if (root.TryGetFile(relative, out fileStream))
|
||||
{
|
||||
fileStream = WrapStream(stream);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -211,35 +206,6 @@ namespace Robust.Shared.ContentPack
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply <see cref="_streamSeekMode"/> to the provided stream.
|
||||
/// </summary>
|
||||
private Stream WrapStream(Stream stream)
|
||||
{
|
||||
switch (_streamSeekMode)
|
||||
{
|
||||
case StreamSeekMode.None:
|
||||
return stream;
|
||||
|
||||
case StreamSeekMode.ForceSeekable:
|
||||
if (stream.CanSeek)
|
||||
return stream;
|
||||
|
||||
var ms = new MemoryStream(stream.CopyToArray(), writable: false);
|
||||
stream.Dispose();
|
||||
return ms;
|
||||
|
||||
case StreamSeekMode.ForceNonSeekable:
|
||||
if (!stream.CanSeek)
|
||||
return stream;
|
||||
|
||||
return new NonSeekableStream(stream);
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContentFileExists(string path)
|
||||
{
|
||||
|
||||
@@ -689,7 +689,6 @@ Types:
|
||||
CancellationToken: { All: True }
|
||||
CancellationTokenSource: { All: True }
|
||||
Interlocked: { All: True }
|
||||
Monitor: { All: True }
|
||||
System.Web:
|
||||
HttpUtility:
|
||||
Methods:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user