Merge branch 'master' of https://github.com/space-wizards/space-station-14 into pr/34711
This commit is contained in:
45
.github/workflows/publish-testing.yml
vendored
Normal file
45
.github/workflows/publish-testing.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Publish Testing
|
||||
|
||||
concurrency:
|
||||
group: publish-testing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 10 * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Get Engine Tag
|
||||
run: |
|
||||
cd RobustToolbox
|
||||
git fetch --depth=1
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build Packaging
|
||||
run: dotnet build Content.Packaging --configuration Release --no-restore /m
|
||||
|
||||
- name: Package server
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
|
||||
|
||||
- name: Package client
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
- name: Publish version
|
||||
run: Tools/publish_multi_request.py --fork-id wizards-testing
|
||||
env:
|
||||
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}
|
||||
@@ -12,12 +12,12 @@ You want to handle the Build, Clean and Rebuild tasks to prevent missing task er
|
||||
If you want to learn more about these kinds of things, check out Microsoft's official documentation about MSBuild:
|
||||
https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
|
||||
-->
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Python>python3</Python>
|
||||
<Python Condition="'$(OS)'=='Windows_NT' Or '$(OS)'=='Windows'">py -3</Python>
|
||||
<ProjectGuid>{C899FCA4-7037-4E49-ABC2-44DE72487110}</ProjectGuid>
|
||||
<TargetFrameworkMoniker>.NETFramework, Version=v4.7.2</TargetFrameworkMoniker>
|
||||
<TargetFramework>net4.7.2</TargetFramework>
|
||||
<RestorePackages>false</RestorePackages>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -9,13 +9,14 @@ using Content.IntegrationTests.Pair;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
@@ -32,7 +33,6 @@ public class ComponentQueryBenchmark
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private MapId _mapId = new(10);
|
||||
private EntityQuery<ItemComponent> _itemQuery;
|
||||
private EntityQuery<ClothingComponent> _clothingQuery;
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
@@ -54,10 +54,10 @@ public class ComponentQueryBenchmark
|
||||
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
|
||||
_pair.Server.WaitPost(() =>
|
||||
{
|
||||
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
|
||||
if (!success)
|
||||
var map = new ResPath(Map);
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
if (!_entMan.System<MapLoaderSystem>().TryLoadMap(map, out _, out _, opts))
|
||||
throw new Exception("Map load failed");
|
||||
_pair.Server.MapMan.DoMapInitialize(_mapId);
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
_items = new EntityUid[_entMan.Count<ItemComponent>()];
|
||||
|
||||
@@ -6,12 +6,13 @@ using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Maps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
@@ -20,7 +21,7 @@ public class MapLoadBenchmark
|
||||
{
|
||||
private TestPair _pair = default!;
|
||||
private MapLoaderSystem _mapLoader = default!;
|
||||
private IMapManager _mapManager = default!;
|
||||
private SharedMapSystem _mapSys = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
@@ -36,7 +37,7 @@ public class MapLoadBenchmark
|
||||
.ToDictionary(x => x.ID, x => x.MapPath.ToString());
|
||||
|
||||
_mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
|
||||
_mapManager = server.ResolveDependency<IMapManager>();
|
||||
_mapSys = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedMapSystem>();
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
@@ -46,23 +47,25 @@ public class MapLoadBenchmark
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
|
||||
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Cog", "Convex"};
|
||||
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"};
|
||||
|
||||
[ParamsSource(nameof(MapsSource))]
|
||||
public string Map;
|
||||
|
||||
public Dictionary<string, string> Paths;
|
||||
private MapId _mapId;
|
||||
|
||||
[Benchmark]
|
||||
public async Task LoadMap()
|
||||
{
|
||||
var mapPath = Paths[Map];
|
||||
var mapPath = new ResPath(Paths[Map]);
|
||||
var server = _pair.Server;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var success = _mapLoader.TryLoad(new MapId(10), mapPath, out _);
|
||||
var success = _mapLoader.TryLoadMap(mapPath, out var map, out _);
|
||||
if (!success)
|
||||
throw new Exception("Map load failed");
|
||||
_mapId = map.Value.Comp.MapId;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,9 +73,7 @@ public class MapLoadBenchmark
|
||||
public void IterationCleanup()
|
||||
{
|
||||
var server = _pair.Server;
|
||||
server.WaitPost(() =>
|
||||
{
|
||||
_mapManager.DeleteMap(new MapId(10));
|
||||
}).Wait();
|
||||
server.WaitPost(() => _mapSys.DeleteMap(_mapId))
|
||||
.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,15 @@ using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Warps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
@@ -34,7 +36,6 @@ public class PvsBenchmark
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private MapId _mapId = new(10);
|
||||
private ICommonSession[] _players = default!;
|
||||
private EntityCoordinates[] _spawns = default!;
|
||||
public int _cycleOffset = 0;
|
||||
@@ -65,10 +66,10 @@ public class PvsBenchmark
|
||||
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
|
||||
await _pair.Server.WaitPost(() =>
|
||||
{
|
||||
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
|
||||
if (!success)
|
||||
var path = new ResPath(Map);
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
if (!_entMan.System<MapLoaderSystem>().TryLoadMap(path, out _, out _, opts))
|
||||
throw new Exception("Map load failed");
|
||||
_pair.Server.MapMan.DoMapInitialize(_mapId);
|
||||
});
|
||||
|
||||
// Get list of ghost warp positions
|
||||
|
||||
@@ -88,6 +88,7 @@ namespace Content.Client.Actions
|
||||
return;
|
||||
|
||||
component.Whitelist = state.Whitelist;
|
||||
component.Blacklist = state.Blacklist;
|
||||
component.CanTargetSelf = state.CanTargetSelf;
|
||||
BaseHandleState<EntityTargetActionComponent>(uid, component, state);
|
||||
}
|
||||
@@ -137,6 +138,7 @@ namespace Content.Client.Actions
|
||||
component.Priority = state.Priority;
|
||||
component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
|
||||
component.RaiseOnUser = state.RaiseOnUser;
|
||||
component.RaiseOnAction = state.RaiseOnAction;
|
||||
component.AutoPopulate = state.AutoPopulate;
|
||||
component.Temporary = state.Temporary;
|
||||
component.ItemIconStyle = state.ItemIconStyle;
|
||||
|
||||
@@ -11,6 +11,8 @@ public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new AtmosAlertsComputerWindow(this, Owner);
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly SpriteSystem _spriteSys = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
|
||||
|
||||
private GasTileOverlay _overlay = default!;
|
||||
|
||||
@@ -25,7 +26,7 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
|
||||
|
||||
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys);
|
||||
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys);
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Content.Client.Atmos.Overlays
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
|
||||
private readonly ShaderInstance _shader;
|
||||
@@ -46,10 +47,11 @@ namespace Content.Client.Atmos.Overlays
|
||||
|
||||
public const int GasOverlayZIndex = (int) Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else
|
||||
|
||||
public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys)
|
||||
public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys, SharedTransformSystem xformSys)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_mapManager = IoCManager.Resolve<IMapManager>();
|
||||
_xformSys = xformSys;
|
||||
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
|
||||
ZIndex = GasOverlayZIndex;
|
||||
|
||||
@@ -158,7 +160,8 @@ namespace Content.Client.Atmos.Overlays
|
||||
_fireFrameCounter,
|
||||
_shader,
|
||||
overlayQuery,
|
||||
xformQuery);
|
||||
xformQuery,
|
||||
_xformSys);
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(args.MapId);
|
||||
|
||||
@@ -180,7 +183,8 @@ namespace Content.Client.Atmos.Overlays
|
||||
int[] fireFrameCounter,
|
||||
ShaderInstance shader,
|
||||
EntityQuery<GasTileOverlayComponent> overlayQuery,
|
||||
EntityQuery<TransformComponent> xformQuery) state) =>
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
SharedTransformSystem xformSys) state) =>
|
||||
{
|
||||
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
|
||||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
|
||||
@@ -188,7 +192,7 @@ namespace Content.Client.Atmos.Overlays
|
||||
return true;
|
||||
}
|
||||
|
||||
var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
|
||||
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
|
||||
state.drawHandle.SetTransform(worldMatrix);
|
||||
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
|
||||
var localBounds = new Box2i(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
MinSize="280 160" Title="Temperature Control Unit">
|
||||
MinSize="280 160" Title="{Loc comp-space-heater-ui-title}">
|
||||
|
||||
<BoxContainer Name="VboxContainer" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
|
||||
|
||||
@@ -3,13 +3,15 @@ using Content.Shared.Buckle;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Rotation;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Buckle;
|
||||
|
||||
internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
{
|
||||
[Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
|
||||
[Dependency] private readonly IEyeManager _eye = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -17,6 +19,8 @@ internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
|
||||
SubscribeLocalEvent<BuckleComponent, BuckledEvent>(OnBuckledEvent);
|
||||
SubscribeLocalEvent<BuckleComponent, UnbuckledEvent>(OnUnbuckledEvent);
|
||||
}
|
||||
|
||||
private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args)
|
||||
@@ -28,13 +32,21 @@ internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
// This code is garbage, it doesn't work with rotated viewports. I need to finally get around to reworking
|
||||
// sprite rendering for entity layers & direction dependent sorting.
|
||||
|
||||
// Future notes:
|
||||
// Right now this doesn't handle: other grids, other grids rotating, the camera rotation changing, and many other fun rotation specific things
|
||||
// The entire thing should be a concern of the engine, or something engine helps to implement properly.
|
||||
// Give some of the sprite rotations their own drawdepth, maybe as an offset within the rsi, or something like this
|
||||
// And we won't ever need to set the draw depth manually
|
||||
|
||||
if (args.NewRotation == args.OldRotation)
|
||||
return;
|
||||
|
||||
if (!TryComp<SpriteComponent>(uid, out var strapSprite))
|
||||
return;
|
||||
|
||||
var isNorth = Transform(uid).LocalRotation.GetCardinalDir() == Direction.North;
|
||||
var angle = _xformSystem.GetWorldRotation(uid) + _eye.CurrentEye.Rotation; // Get true screen position, or close enough
|
||||
|
||||
var isNorth = angle.GetCardinalDir() == Direction.North;
|
||||
foreach (var buckledEntity in component.BuckledEntities)
|
||||
{
|
||||
if (!TryComp<BuckleComponent>(buckledEntity, out var buckle))
|
||||
@@ -45,6 +57,7 @@ internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
|
||||
if (isNorth)
|
||||
{
|
||||
// This will only assign if empty, it won't get overwritten by new depth on multiple calls, which do happen easily
|
||||
buckle.OriginalDrawDepth ??= buckledSprite.DrawDepth;
|
||||
buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
|
||||
}
|
||||
@@ -56,6 +69,42 @@ internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lower the draw depth of the buckled entity without needing for the strap entity to rotate/move.
|
||||
/// Only do so when the entity is facing screen-local north
|
||||
/// </summary>
|
||||
private void OnBuckledEvent(Entity<BuckleComponent> ent, ref BuckledEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(args.Strap, out var strapSprite))
|
||||
return;
|
||||
|
||||
if (!TryComp<SpriteComponent>(ent.Owner, out var buckledSprite))
|
||||
return;
|
||||
|
||||
var angle = _xformSystem.GetWorldRotation(args.Strap) + _eye.CurrentEye.Rotation; // Get true screen position, or close enough
|
||||
|
||||
if (angle.GetCardinalDir() != Direction.North)
|
||||
return;
|
||||
|
||||
ent.Comp.OriginalDrawDepth ??= buckledSprite.DrawDepth;
|
||||
buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Was the draw depth of the buckled entity lowered? Reset it upon unbuckling.
|
||||
/// </summary>
|
||||
private void OnUnbuckledEvent(Entity<BuckleComponent> ent, ref UnbuckledEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent.Owner, out var buckledSprite))
|
||||
return;
|
||||
|
||||
if (!ent.Comp.OriginalDrawDepth.HasValue)
|
||||
return;
|
||||
|
||||
buckledSprite.DrawDepth = ent.Comp.OriginalDrawDepth.Value;
|
||||
ent.Comp.OriginalDrawDepth = null;
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Content.Client.Clickable
|
||||
"/Textures/Logo",
|
||||
};
|
||||
|
||||
private const float Threshold = 0.25f;
|
||||
private const float Threshold = 0.1f;
|
||||
private const int ClickRadius = 2;
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
@@ -58,9 +58,7 @@
|
||||
StyleClasses="LabelBig" />
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Margin="0 0 0 5">
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-job'}:"
|
||||
FontColorOverride="DarkGray" />
|
||||
<Label Text=":"
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-job'}"
|
||||
FontColorOverride="DarkGray" />
|
||||
<TextureRect Name="PersonJobIcon"
|
||||
TextureScale="2 2"
|
||||
|
||||
@@ -5,17 +5,24 @@ namespace Content.Client.Dice;
|
||||
|
||||
public sealed class DiceSystem : SharedDiceSystem
|
||||
{
|
||||
protected override void UpdateVisuals(EntityUid uid, DiceComponent? die = null)
|
||||
public override void Initialize()
|
||||
{
|
||||
if (!Resolve(uid, ref die) || !TryComp(uid, out SpriteComponent? sprite))
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DiceComponent, AfterAutoHandleStateEvent>(OnDiceAfterHandleState);
|
||||
}
|
||||
|
||||
private void OnDiceAfterHandleState(Entity<DiceComponent> entity, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(entity, out var sprite))
|
||||
return;
|
||||
|
||||
// TODO maybe just move each diue to its own RSI?
|
||||
// TODO maybe just move each die to its own RSI?
|
||||
var state = sprite.LayerGetState(0).Name;
|
||||
if (state == null)
|
||||
return;
|
||||
|
||||
var prefix = state.Substring(0, state.IndexOf('_'));
|
||||
sprite.LayerSetState(0, $"{prefix}_{die.CurrentValue}");
|
||||
sprite.LayerSetState(0, $"{prefix}_{entity.Comp.CurrentValue}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@ public sealed class DoAfterSystem : SharedDoAfterSystem
|
||||
_overlay.RemoveOverlay<DoAfterOverlay>();
|
||||
}
|
||||
|
||||
#pragma warning disable RA0028 // No base call in overriden function
|
||||
public override void Update(float frameTime)
|
||||
#pragma warning restore RA0028 // No base call in overriden function
|
||||
{
|
||||
// Currently this only predicts do afters initiated by the player.
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Client.Wires.Visualizers;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
@@ -84,7 +85,8 @@ public sealed class AirlockSystem : SharedAirlockSystem
|
||||
if (!_appearanceSystem.TryGetData<DoorState>(uid, DoorVisuals.State, out var state, args.Component))
|
||||
state = DoorState.Closed;
|
||||
|
||||
if (_appearanceSystem.TryGetData<bool>(uid, DoorVisuals.Powered, out var powered, args.Component) && powered)
|
||||
if (_appearanceSystem.TryGetData<bool>(uid, PowerDeviceVisuals.Powered, out var powered, args.Component)
|
||||
&& powered)
|
||||
{
|
||||
boltedVisible = _appearanceSystem.TryGetData<bool>(uid, DoorVisuals.BoltLights, out var lights, args.Component)
|
||||
&& lights && (state == DoorState.Closed || state == DoorState.Welded);
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace Content.Client.GameTicking.Managers
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<TickerJoinLobbyEvent>(JoinLobby);
|
||||
SubscribeNetworkEvent<TickerJoinGameEvent>(JoinGame);
|
||||
SubscribeNetworkEvent<TickerConnectionStatusEvent>(ConnectionStatus);
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Content.Client.Ghost
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly PointLightSystem _pointLightSystem = default!;
|
||||
[Dependency] private readonly ContentEyeSystem _contentEye = default!;
|
||||
|
||||
public int AvailableGhostRoleCount { get; private set; }
|
||||
@@ -79,8 +80,27 @@ namespace Content.Client.Ghost
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup"), args.Performer);
|
||||
_contentEye.RequestToggleLight(uid, component);
|
||||
TryComp<PointLightComponent>(uid, out var light);
|
||||
|
||||
if (!component.DrawLight)
|
||||
{
|
||||
// normal lighting
|
||||
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-normal"), args.Performer);
|
||||
_contentEye.RequestEye(component.DrawFov, true);
|
||||
}
|
||||
else if (!light?.Enabled ?? false) // skip this option if we have no PointLightComponent
|
||||
{
|
||||
// enable personal light
|
||||
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-personal-light"), args.Performer);
|
||||
_pointLightSystem.SetEnabled(uid, true, light);
|
||||
}
|
||||
else
|
||||
{
|
||||
// fullbright mode
|
||||
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-fullbright"), args.Performer);
|
||||
_contentEye.RequestEye(component.DrawFov, false);
|
||||
_pointLightSystem.SetEnabled(uid, false, light);
|
||||
}
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Instruments.UI;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
@@ -13,7 +12,6 @@ namespace Content.Client.Instruments.UI
|
||||
public IEntityManager Entities => EntMan;
|
||||
[Dependency] public readonly IMidiManager MidiManager = default!;
|
||||
[Dependency] public readonly IFileDialogManager FileDialogManager = default!;
|
||||
[Dependency] public readonly IPlayerManager PlayerManager = default!;
|
||||
[Dependency] public readonly ILocalizationManager Loc = default!;
|
||||
|
||||
public readonly InstrumentSystem Instruments;
|
||||
@@ -41,6 +39,8 @@ namespace Content.Client.Instruments.UI
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_instrumentMenu = this.CreateWindow<InstrumentMenu>();
|
||||
_instrumentMenu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
dragSprite.DrawDepth = (int) DrawDepth.Overlays;
|
||||
if (!dragSprite.NoRotation)
|
||||
{
|
||||
Transform(_dragShadow.Value).WorldRotation = Transform(_draggedEntity.Value).WorldRotation;
|
||||
_transformSystem.SetWorldRotationNoLerp(_dragShadow.Value, _transformSystem.GetWorldRotation(_draggedEntity.Value));
|
||||
}
|
||||
|
||||
// drag initiated
|
||||
|
||||
11
Content.Client/ItemRecall/ItemRecallSystem.cs
Normal file
11
Content.Client/ItemRecall/ItemRecallSystem.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Content.Shared.ItemRecall;
|
||||
|
||||
namespace Content.Client.ItemRecall;
|
||||
|
||||
/// <summary>
|
||||
/// System for handling the ItemRecall ability for wizards.
|
||||
/// </summary>
|
||||
public sealed partial class ItemRecallSystem : SharedItemRecallSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -33,8 +33,13 @@ namespace Content.Client.Labels.UI
|
||||
_focused = false;
|
||||
LabelLineEdit.Text = _label;
|
||||
};
|
||||
}
|
||||
|
||||
// Give the editor keybard focus, since that's the only
|
||||
protected override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
// Give the editor keyboard focus, since that's the only
|
||||
// thing the user will want to be doing with this UI
|
||||
LabelLineEdit.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
|
||||
if (_entityManager.TryGetComponent<LatheComponent>(Entity, out var latheComponent))
|
||||
{
|
||||
if (!latheComponent.DynamicRecipes.Any())
|
||||
if (!latheComponent.DynamicPacks.Any())
|
||||
{
|
||||
ServerListButton.Visible = false;
|
||||
}
|
||||
|
||||
58
Content.Client/Light/AfterLightTargetOverlay.cs
Normal file
58
Content.Client/Light/AfterLightTargetOverlay.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
/// <summary>
|
||||
/// This exists just to copy <see cref="BeforeLightTargetOverlay"/> to the light render target
|
||||
/// </summary>
|
||||
public sealed class AfterLightTargetOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
|
||||
public const int ContentZIndex = LightBlurOverlay.ContentZIndex + 1;
|
||||
|
||||
public AfterLightTargetOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
ZIndex = ContentZIndex;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.Viewport;
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
if (viewport.Eye == null)
|
||||
return;
|
||||
|
||||
var lightOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var bounds = args.WorldBounds;
|
||||
|
||||
// at 1-1 render scale it's mostly fine but at 4x4 it's way too fkn big
|
||||
var newScale = viewport.RenderScale / 2f;
|
||||
|
||||
var localMatrix =
|
||||
viewport.LightRenderTarget.GetWorldToLocalMatrix(viewport.Eye, newScale);
|
||||
var diff = (lightOverlay.EnlargedLightTarget.Size - viewport.LightRenderTarget.Size);
|
||||
var halfDiff = diff / 2;
|
||||
|
||||
// Pixels -> Metres -> Half distance.
|
||||
// If we're zoomed in need to enlarge the bounds further.
|
||||
args.WorldHandle.RenderInRenderTarget(viewport.LightRenderTarget,
|
||||
() =>
|
||||
{
|
||||
// We essentially need to draw the cropped version onto the lightrendertarget.
|
||||
var subRegion = new UIBox2i(halfDiff.X,
|
||||
halfDiff.Y,
|
||||
viewport.LightRenderTarget.Size.X + halfDiff.X,
|
||||
viewport.LightRenderTarget.Size.Y + halfDiff.Y);
|
||||
|
||||
worldHandle.SetTransform(localMatrix);
|
||||
worldHandle.DrawTextureRectRegion(lightOverlay.EnlargedLightTarget.Texture, bounds, subRegion: subRegion);
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
51
Content.Client/Light/BeforeLightTargetOverlay.cs
Normal file
51
Content.Client/Light/BeforeLightTargetOverlay.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
/// <summary>
|
||||
/// Handles an enlarged lighting target so content can use large blur radii.
|
||||
/// </summary>
|
||||
public sealed class BeforeLightTargetOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
|
||||
public IRenderTexture EnlargedLightTarget = default!;
|
||||
public Box2Rotated EnlargedBounds;
|
||||
|
||||
/// <summary>
|
||||
/// In metres
|
||||
/// </summary>
|
||||
private float _skirting = 1.5f;
|
||||
|
||||
public const int ContentZIndex = -10;
|
||||
|
||||
public BeforeLightTargetOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
ZIndex = ContentZIndex;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
// Code is weird but I don't think engine should be enlarging the lighting render target arbitrarily either, maybe via cvar?
|
||||
// The problem is the blur has no knowledge of pixels outside the viewport so with a large enough blur radius you get sampling issues.
|
||||
var size = args.Viewport.LightRenderTarget.Size + (int) (_skirting * EyeManager.PixelsPerMeter);
|
||||
EnlargedBounds = args.WorldBounds.Enlarged(_skirting / 2f);
|
||||
|
||||
// This just exists to copy the lightrendertarget and write back to it.
|
||||
if (EnlargedLightTarget?.Size != size)
|
||||
{
|
||||
EnlargedLightTarget = _clyde
|
||||
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-copy");
|
||||
}
|
||||
|
||||
args.WorldHandle.RenderInRenderTarget(EnlargedLightTarget,
|
||||
() =>
|
||||
{
|
||||
}, _clyde.GetClearColor(args.MapUid));
|
||||
}
|
||||
}
|
||||
36
Content.Client/Light/EntitySystems/PlanetLightSystem.cs
Normal file
36
Content.Client/Light/EntitySystems/PlanetLightSystem.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
public sealed class PlanetLightSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GetClearColorEvent>(OnClearColor);
|
||||
|
||||
_overlayMan.AddOverlay(new BeforeLightTargetOverlay());
|
||||
_overlayMan.AddOverlay(new RoofOverlay(EntityManager));
|
||||
_overlayMan.AddOverlay(new TileEmissionOverlay(EntityManager));
|
||||
_overlayMan.AddOverlay(new LightBlurOverlay());
|
||||
_overlayMan.AddOverlay(new AfterLightTargetOverlay());
|
||||
}
|
||||
|
||||
private void OnClearColor(ref GetClearColorEvent ev)
|
||||
{
|
||||
ev.Color = Color.Transparent;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMan.RemoveOverlay<BeforeLightTargetOverlay>();
|
||||
_overlayMan.RemoveOverlay<RoofOverlay>();
|
||||
_overlayMan.RemoveOverlay<TileEmissionOverlay>();
|
||||
_overlayMan.RemoveOverlay<LightBlurOverlay>();
|
||||
_overlayMan.RemoveOverlay<AfterLightTargetOverlay>();
|
||||
}
|
||||
}
|
||||
9
Content.Client/Light/EntitySystems/RoofSystem.cs
Normal file
9
Content.Client/Light/EntitySystems/RoofSystem.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class RoofSystem : SharedRoofSystem
|
||||
{
|
||||
|
||||
}
|
||||
44
Content.Client/Light/LightBlurOverlay.cs
Normal file
44
Content.Client/Light/LightBlurOverlay.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
/// <summary>
|
||||
/// Essentially handles blurring for content-side light overlays.
|
||||
/// </summary>
|
||||
public sealed class LightBlurOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
|
||||
public const int ContentZIndex = TileEmissionOverlay.ContentZIndex + 1;
|
||||
|
||||
private IRenderTarget? _blurTarget;
|
||||
|
||||
public LightBlurOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
ZIndex = ContentZIndex;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.Viewport.Eye == null)
|
||||
return;
|
||||
|
||||
var beforeOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var size = beforeOverlay.EnlargedLightTarget.Size;
|
||||
|
||||
if (_blurTarget?.Size != size)
|
||||
{
|
||||
_blurTarget = _clyde
|
||||
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-blur");
|
||||
}
|
||||
|
||||
var target = beforeOverlay.EnlargedLightTarget;
|
||||
// Yeah that's all this does keep walkin.
|
||||
_clyde.BlurRenderTarget(args.Viewport, target, _blurTarget, args.Viewport.Eye, 14f * 2f);
|
||||
}
|
||||
}
|
||||
33
Content.Client/Light/LightCycleSystem.cs
Normal file
33
Content.Client/Light/LightCycleSystem.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared;
|
||||
using Content.Shared.Light.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class LightCycleSystem : SharedLightCycleSystem
|
||||
{
|
||||
[Dependency] private readonly ClientGameTicker _ticker = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
var mapQuery = AllEntityQuery<LightCycleComponent, MapLightComponent>();
|
||||
while (mapQuery.MoveNext(out var uid, out var cycle, out var map))
|
||||
{
|
||||
if (!cycle.Running)
|
||||
continue;
|
||||
|
||||
var time = (float) _timing.CurTime
|
||||
.Add(cycle.Offset)
|
||||
.Subtract(_ticker.RoundStartTimeSpan)
|
||||
.TotalSeconds;
|
||||
|
||||
var color = GetColor((uid, cycle), cycle.OriginalColor, time);
|
||||
map.AmbientLightColor = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Content.Client/Light/RoofOverlay.cs
Normal file
100
Content.Client/Light/RoofOverlay.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
public sealed class RoofOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly SharedTransformSystem _xformSystem;
|
||||
|
||||
private readonly HashSet<Entity<OccluderComponent>> _occluders = new();
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
public const int ContentZIndex = BeforeLightTargetOverlay.ContentZIndex + 1;
|
||||
|
||||
public RoofOverlay(IEntityManager entManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_lookup = _entManager.System<EntityLookupSystem>();
|
||||
_mapSystem = _entManager.System<SharedMapSystem>();
|
||||
_xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
ZIndex = ContentZIndex;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.Viewport.Eye == null)
|
||||
return;
|
||||
|
||||
var mapEnt = _mapSystem.GetMap(args.MapId);
|
||||
|
||||
if (!_entManager.TryGetComponent(mapEnt, out RoofComponent? roofComp) ||
|
||||
!_entManager.TryGetComponent(mapEnt, out MapGridComponent? grid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var viewport = args.Viewport;
|
||||
var eye = args.Viewport.Eye;
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var bounds = lightoverlay.EnlargedBounds;
|
||||
var target = lightoverlay.EnlargedLightTarget;
|
||||
|
||||
worldHandle.RenderInRenderTarget(target,
|
||||
() =>
|
||||
{
|
||||
var invMatrix = target.GetWorldToLocalMatrix(eye, viewport.RenderScale / 2f);
|
||||
|
||||
var gridMatrix = _xformSystem.GetWorldMatrix(mapEnt);
|
||||
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
||||
|
||||
worldHandle.SetTransform(matty);
|
||||
|
||||
var tileEnumerator = _mapSystem.GetTilesEnumerator(mapEnt, grid, bounds);
|
||||
|
||||
// Due to stencilling we essentially draw on unrooved tiles
|
||||
while (tileEnumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
if ((tileRef.Tile.Flags & (byte) TileFlag.Roof) == 0x0)
|
||||
{
|
||||
// Check if the tile is occluded in which case hide it anyway.
|
||||
// This is to avoid lit walls bleeding over to unlit tiles.
|
||||
_occluders.Clear();
|
||||
_lookup.GetLocalEntitiesIntersecting(mapEnt, tileRef.GridIndices, _occluders);
|
||||
var found = false;
|
||||
|
||||
foreach (var occluder in _occluders)
|
||||
{
|
||||
if (!occluder.Comp.Enabled)
|
||||
continue;
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
continue;
|
||||
}
|
||||
|
||||
var local = _lookup.GetLocalBounds(tileRef, grid.TileSize);
|
||||
worldHandle.DrawRect(local, roofComp.Color);
|
||||
}
|
||||
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
90
Content.Client/Light/TileEmissionOverlay.cs
Normal file
90
Content.Client/Light/TileEmissionOverlay.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
public sealed class TileEmissionOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
|
||||
private SharedMapSystem _mapSystem;
|
||||
private SharedTransformSystem _xformSystem;
|
||||
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
|
||||
private readonly EntityQuery<TransformComponent> _xformQuery;
|
||||
private readonly HashSet<Entity<TileEmissionComponent>> _entities = new();
|
||||
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public const int ContentZIndex = RoofOverlay.ContentZIndex + 1;
|
||||
|
||||
public TileEmissionOverlay(IEntityManager entManager)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_lookup = entManager.System<EntityLookupSystem>();
|
||||
_mapSystem = entManager.System<SharedMapSystem>();
|
||||
_xformSystem = entManager.System<SharedTransformSystem>();
|
||||
|
||||
_xformQuery = entManager.GetEntityQuery<TransformComponent>();
|
||||
ZIndex = ContentZIndex;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.Viewport.Eye == null)
|
||||
return;
|
||||
|
||||
var mapId = args.MapId;
|
||||
var worldHandle = args.WorldHandle;
|
||||
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var bounds = lightoverlay.EnlargedBounds;
|
||||
var target = lightoverlay.EnlargedLightTarget;
|
||||
var viewport = args.Viewport;
|
||||
|
||||
args.WorldHandle.RenderInRenderTarget(target,
|
||||
() =>
|
||||
{
|
||||
var invMatrix = target.GetWorldToLocalMatrix(viewport.Eye, viewport.RenderScale / 2f);
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(mapId, bounds, ref _grids, approx: true);
|
||||
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
var gridInvMatrix = _xformSystem.GetInvWorldMatrix(grid);
|
||||
var localBounds = gridInvMatrix.TransformBox(bounds);
|
||||
_entities.Clear();
|
||||
_lookup.GetLocalEntitiesIntersecting(grid.Owner, localBounds, _entities);
|
||||
|
||||
if (_entities.Count == 0)
|
||||
continue;
|
||||
|
||||
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
|
||||
|
||||
foreach (var ent in _entities)
|
||||
{
|
||||
var xform = _xformQuery.Comp(ent);
|
||||
|
||||
var tile = _mapSystem.LocalToTile(grid.Owner, grid, xform.Coordinates);
|
||||
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
||||
|
||||
worldHandle.SetTransform(matty);
|
||||
|
||||
// Yes I am fully aware this leads to overlap. If you really want to have alpha then you'll need
|
||||
// to turn the squares into polys.
|
||||
// Additionally no shadows so if you make it too big it's going to go through a 1x wall.
|
||||
var local = _lookup.GetLocalBounds(tile, grid.Comp.TileSize).Enlarged(ent.Comp.Range);
|
||||
worldHandle.DrawRect(local, ent.Comp.Color);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,6 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
|
||||
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
|
||||
[UISystemDependency] private readonly GuidebookSystem _guide = default!;
|
||||
[UISystemDependency] private readonly LoadoutSystem _loadouts = default!;
|
||||
|
||||
private CharacterSetupGui? _characterSetup;
|
||||
private HumanoidProfileEditor? _profileEditor;
|
||||
|
||||
@@ -1015,6 +1015,13 @@ namespace Content.Client.Lobby.UI
|
||||
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
||||
_loadoutWindow.OpenCenteredLeft();
|
||||
|
||||
_loadoutWindow.OnNameChanged += name =>
|
||||
{
|
||||
roleLoadout.EntityName = name;
|
||||
Profile = Profile.WithLoadout(roleLoadout);
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
|
||||
{
|
||||
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
|
||||
|
||||
@@ -5,17 +5,15 @@
|
||||
SetSize="800 800"
|
||||
MinSize="800 128">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<!--
|
||||
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
|
||||
<Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/>
|
||||
<PanelContainer HorizontalExpand="True" SetHeight="24">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<LineEdit Name="RoleNameEdit" ToolTip="{Loc 'loadout-name-edit-tooltip'}" VerticalExpand="True" HorizontalExpand="True"/>
|
||||
<LineEdit Name="RoleNameEdit" VerticalExpand="True" HorizontalExpand="True"/>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
-->
|
||||
<VerticalTabContainer Name="LoadoutGroupsContainer"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Player;
|
||||
@@ -13,6 +15,7 @@ namespace Content.Client.Lobby.UI.Loadouts;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LoadoutWindow : FancyWindow
|
||||
{
|
||||
public event Action<string>? OnNameChanged;
|
||||
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
|
||||
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
|
||||
|
||||
@@ -25,6 +28,23 @@ public sealed partial class LoadoutWindow : FancyWindow
|
||||
RobustXamlLoader.Load(this);
|
||||
Profile = profile;
|
||||
var protoManager = collection.Resolve<IPrototypeManager>();
|
||||
RoleNameEdit.IsValid = text => text.Length <= HumanoidCharacterProfile.MaxLoadoutNameLength;
|
||||
|
||||
// Hide if we can't edit the name.
|
||||
if (!proto.CanCustomizeName)
|
||||
{
|
||||
RoleNameBox.Visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = loadout.EntityName;
|
||||
|
||||
RoleNameEdit.ToolTip = Loc.GetString(
|
||||
"loadout-name-edit-tooltip",
|
||||
("max", HumanoidCharacterProfile.MaxLoadoutNameLength));
|
||||
RoleNameEdit.Text = name ?? string.Empty;
|
||||
RoleNameEdit.OnTextChanged += args => OnNameChanged?.Invoke(args.Text);
|
||||
}
|
||||
|
||||
// Hide if no groups
|
||||
if (proto.Groups.Count == 0)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Magic;
|
||||
using Content.Shared.Magic;
|
||||
using Content.Shared.Magic.Events;
|
||||
|
||||
namespace Content.Client.Magic;
|
||||
|
||||
@@ -102,7 +102,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
|
||||
if (!_mapManager.TryFindGridAt(mousePos, out var gridUid, out var grid))
|
||||
return;
|
||||
|
||||
StartDragging(gridUid, Vector2.Transform(mousePos.Position, Transform(gridUid).InvWorldMatrix));
|
||||
StartDragging(gridUid, Vector2.Transform(mousePos.Position, _transformSystem.GetInvWorldMatrix(gridUid)));
|
||||
}
|
||||
|
||||
if (!TryComp(_dragging, out TransformComponent? xform))
|
||||
@@ -117,11 +117,11 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var localToWorld = Vector2.Transform(_localPosition, xform.WorldMatrix);
|
||||
var localToWorld = Vector2.Transform(_localPosition, _transformSystem.GetWorldMatrix(xform));
|
||||
|
||||
if (localToWorld.EqualsApprox(mousePos.Position, 0.01f)) return;
|
||||
|
||||
var requestedGridOrigin = mousePos.Position - xform.WorldRotation.RotateVec(_localPosition);
|
||||
var requestedGridOrigin = mousePos.Position - _transformSystem.GetWorldRotation(xform).RotateVec(_localPosition);
|
||||
_lastMousePosition = new MapCoordinates(requestedGridOrigin, mousePos.MapId);
|
||||
|
||||
RaiseNetworkEvent(new GridDragRequestPosition()
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class NewsWriterBoundUserInterface : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<NewsWriterMenu>();
|
||||
|
||||
_menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed;
|
||||
|
||||
@@ -14,6 +14,8 @@ public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
EntityUid? gridUid = null;
|
||||
var stationName = string.Empty;
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace Content.Client.Nuke
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<NukeMenu>();
|
||||
|
||||
_menu.OnKeypadButtonPressed += i =>
|
||||
|
||||
@@ -32,6 +32,11 @@ public sealed class TargetOutlineSystem : EntitySystem
|
||||
/// </summary>
|
||||
public EntityWhitelist? Whitelist = null;
|
||||
|
||||
/// <summary>
|
||||
/// Blacklist that the target must satisfy.
|
||||
/// </summary>
|
||||
public EntityWhitelist? Blacklist = null;
|
||||
|
||||
/// <summary>
|
||||
/// Predicate the target must satisfy.
|
||||
/// </summary>
|
||||
@@ -93,15 +98,16 @@ public sealed class TargetOutlineSystem : EntitySystem
|
||||
RemoveHighlights();
|
||||
}
|
||||
|
||||
public void Enable(float range, bool checkObstructions, Func<EntityUid, bool>? predicate, EntityWhitelist? whitelist, CancellableEntityEventArgs? validationEvent)
|
||||
public void Enable(float range, bool checkObstructions, Func<EntityUid, bool>? predicate, EntityWhitelist? whitelist, EntityWhitelist? blacklist, CancellableEntityEventArgs? validationEvent)
|
||||
{
|
||||
Range = range;
|
||||
CheckObstruction = checkObstructions;
|
||||
Predicate = predicate;
|
||||
Whitelist = whitelist;
|
||||
Blacklist = blacklist;
|
||||
ValidationEvent = validationEvent;
|
||||
|
||||
_enabled = Predicate != null || Whitelist != null || ValidationEvent != null;
|
||||
_enabled = Predicate != null || Whitelist != null || Blacklist != null || ValidationEvent != null;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -10,8 +10,6 @@ namespace Content.Client.Pinpointer.UI;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StationMapBeaconControl : Control, IComparable<StationMapBeaconControl>
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public readonly EntityCoordinates BeaconPosition;
|
||||
public Action<EntityCoordinates>? OnPressed;
|
||||
public string? Label => BeaconNameLabel.Text;
|
||||
|
||||
@@ -12,6 +12,8 @@ public sealed class PowerMonitoringConsoleBoundUserInterface : BoundUserInterfac
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<PowerMonitoringWindow>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.SendPowerMonitoringConsoleMessageAction += SendPowerMonitoringConsoleMessage;
|
||||
|
||||
@@ -4,14 +4,18 @@ using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Power.Visualizers;
|
||||
|
||||
public sealed class CableVisualizerSystem : VisualizerSystem<CableVisualizerComponent>
|
||||
public sealed class CableVisualizerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CableVisualizerComponent, AppearanceChangeEvent>(OnAppearanceChange, after: new[] { typeof(SubFloorHideSystem) });
|
||||
}
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, CableVisualizerComponent component, ref AppearanceChangeEvent args)
|
||||
private void OnAppearanceChange(EntityUid uid, CableVisualizerComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
@@ -23,7 +27,7 @@ public sealed class CableVisualizerSystem : VisualizerSystem<CableVisualizerComp
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AppearanceSystem.TryGetData<WireVisDirFlags>(uid, WireVisVisuals.ConnectedMask, out var mask, args.Component))
|
||||
if (!_appearanceSystem.TryGetData<WireVisDirFlags>(uid, WireVisVisuals.ConnectedMask, out var mask, args.Component))
|
||||
mask = WireVisDirFlags.None;
|
||||
|
||||
args.Sprite.LayerSetState(0, $"{component.StatePrefix}{(int) mask}");
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
|
||||
if (_enableShuttlePosition)
|
||||
{
|
||||
_overlay = new EmergencyShuttleOverlay(EntityManager);
|
||||
_overlay = new EmergencyShuttleOverlay(EntityManager.TransformQuery, XformSystem);
|
||||
overlayManager.AddOverlay(_overlay);
|
||||
RaiseNetworkEvent(new EmergencyShuttleRequestPositionMessage());
|
||||
}
|
||||
@@ -57,23 +57,26 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
/// </summary>
|
||||
public sealed class EmergencyShuttleOverlay : Overlay
|
||||
{
|
||||
private IEntityManager _entManager;
|
||||
private readonly EntityQuery<TransformComponent> _transformQuery;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityUid? StationUid;
|
||||
public Box2? Position;
|
||||
|
||||
public EmergencyShuttleOverlay(IEntityManager entManager)
|
||||
public EmergencyShuttleOverlay(EntityQuery<TransformComponent> transformQuery, SharedTransformSystem transformSystem)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_transformQuery = transformQuery;
|
||||
_transformSystem = transformSystem;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (Position == null || !_entManager.TryGetComponent<TransformComponent>(StationUid, out var xform)) return;
|
||||
if (Position == null || !_transformQuery.TryGetComponent(StationUid, out var xform))
|
||||
return;
|
||||
|
||||
args.WorldHandle.SetTransform(xform.WorldMatrix);
|
||||
args.WorldHandle.SetTransform(_transformSystem.GetWorldMatrix(xform));
|
||||
args.WorldHandle.DrawRect(Position.Value, Color.Red.WithAlpha(100));
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
using System.Text;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.Shuttles.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DockObject : PanelContainer
|
||||
{
|
||||
[PublicAPI]
|
||||
public event Action? UndockPressed;
|
||||
|
||||
[PublicAPI]
|
||||
public event Action? ViewPressed;
|
||||
|
||||
public BoxContainer ContentsContainer => Contents;
|
||||
|
||||
public DockObject()
|
||||
|
||||
@@ -163,16 +163,6 @@ public sealed partial class DockingScreen : BoxContainer
|
||||
}
|
||||
|
||||
dockContainer.AddDock(dock, DockingControl);
|
||||
|
||||
dockContainer.ViewPressed += () =>
|
||||
{
|
||||
OnDockPress(dock);
|
||||
};
|
||||
|
||||
dockContainer.UndockPressed += () =>
|
||||
{
|
||||
UndockRequest?.Invoke(dock.Entity);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
@@ -24,6 +24,7 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IResourceManager _resManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
|
||||
private ContentSpriteControl _control = new();
|
||||
|
||||
@@ -42,12 +43,12 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
foreach (var queued in _control._queuedTextures)
|
||||
foreach (var queued in _control.QueuedTextures)
|
||||
{
|
||||
queued.Tcs.SetCanceled();
|
||||
}
|
||||
|
||||
_control._queuedTextures.Clear();
|
||||
_control.QueuedTextures.Clear();
|
||||
|
||||
_ui.RootControl.RemoveChild(_control);
|
||||
}
|
||||
@@ -103,7 +104,7 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
var texture = _clyde.CreateRenderTarget(new Vector2i(size.X, size.Y), new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "export");
|
||||
var tcs = new TaskCompletionSource(cancelToken);
|
||||
|
||||
_control._queuedTextures.Enqueue((texture, direction, entity, includeId, tcs));
|
||||
_control.QueuedTextures.Enqueue((texture, direction, entity, includeId, tcs));
|
||||
|
||||
await tcs.Task;
|
||||
}
|
||||
@@ -113,13 +114,21 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
if (!_adminManager.IsAdmin())
|
||||
return;
|
||||
|
||||
var target = ev.Target;
|
||||
Verb verb = new()
|
||||
{
|
||||
Text = Loc.GetString("export-entity-verb-get-data-text"),
|
||||
Category = VerbCategory.Debug,
|
||||
Act = () =>
|
||||
Act = async () =>
|
||||
{
|
||||
Export(ev.Target);
|
||||
try
|
||||
{
|
||||
await Export(target);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_runtimeLog.LogException(e, $"{nameof(ContentSpriteSystem)}.{nameof(Export)}");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -141,7 +150,7 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
Direction Direction,
|
||||
EntityUid Entity,
|
||||
bool IncludeId,
|
||||
TaskCompletionSource Tcs)> _queuedTextures = new();
|
||||
TaskCompletionSource Tcs)> QueuedTextures = new();
|
||||
|
||||
private ISawmill _sawmill;
|
||||
|
||||
@@ -155,7 +164,7 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
while (_queuedTextures.TryDequeue(out var queued))
|
||||
while (QueuedTextures.TryDequeue(out var queued))
|
||||
{
|
||||
if (queued.Tcs.Task.IsCanceled)
|
||||
continue;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -85,6 +86,9 @@ public sealed class StatusIconSystem : SharedStatusIconSystem
|
||||
if (data.HideOnStealth && TryComp<StealthComponent>(ent, out var stealth) && stealth.Enabled)
|
||||
return false;
|
||||
|
||||
if (TryComp<SpriteComponent>(ent, out var sprite) && !sprite.Visible)
|
||||
return false;
|
||||
|
||||
if (data.ShowTo != null && !_entityWhitelist.IsValid(data.ShowTo, viewer))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -74,10 +74,6 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
{
|
||||
containerBui.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
storageBui.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<StoreMenu>();
|
||||
if (EntMan.TryGetComponent<StoreComponent>(Owner, out var store))
|
||||
_menu.Title = Loc.GetString(store.Name);
|
||||
|
||||
@@ -21,6 +21,8 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new();
|
||||
|
||||
if (_type == SurveillanceCameraSetupUiKey.Router)
|
||||
|
||||
@@ -87,9 +87,6 @@ public sealed partial class DialogWindow : FancyWindow
|
||||
Prompts.AddChild(box);
|
||||
}
|
||||
|
||||
// Grab keyboard focus for the first dialog entry
|
||||
_promptLines[0].Item2.GrabKeyboardFocus();
|
||||
|
||||
OkButton.OnPressed += _ => Confirm();
|
||||
|
||||
CancelButton.OnPressed += _ =>
|
||||
@@ -110,6 +107,14 @@ public sealed partial class DialogWindow : FancyWindow
|
||||
OpenCentered();
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
// Grab keyboard focus for the first dialog entry
|
||||
_promptLines[0].Item2.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
private void Confirm()
|
||||
{
|
||||
var results = new Dictionary<string, string>();
|
||||
|
||||
@@ -7,7 +7,6 @@ using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -96,7 +95,6 @@ public sealed partial class FancyTree : Control
|
||||
private void LoadIcons()
|
||||
{
|
||||
IconColor = TryGetStyleProperty(StylePropertyIconColor, out Color color) ? color : Color.White;
|
||||
string? path;
|
||||
|
||||
if (!TryGetStyleProperty(StylePropertyIconExpanded, out IconExpanded))
|
||||
IconExpanded = _resCache.GetTexture(DefaultIconExpanded);
|
||||
|
||||
@@ -952,7 +952,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
var range = entityAction.CheckCanAccess ? action.Range : -1;
|
||||
|
||||
_interactionOutline?.SetEnabled(false);
|
||||
_targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, null);
|
||||
_targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, entityAction.Blacklist, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -56,19 +56,20 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
_window = UIManager.CreateWindow<CharacterWindow>();
|
||||
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
|
||||
|
||||
|
||||
_window.OnClose += DeactivateButton;
|
||||
_window.OnOpen += ActivateButton;
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenCharacterMenu,
|
||||
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
|
||||
.Register<CharacterUIController>();
|
||||
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
|
||||
.Register<CharacterUIController>();
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
{
|
||||
if (_window != null)
|
||||
{
|
||||
_window.Dispose();
|
||||
_window.Close();
|
||||
_window = null;
|
||||
}
|
||||
|
||||
@@ -105,18 +106,27 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
}
|
||||
|
||||
CharacterButton.OnPressed += CharacterButtonPressed;
|
||||
}
|
||||
|
||||
if (_window == null)
|
||||
private void DeactivateButton()
|
||||
{
|
||||
if (CharacterButton == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_window.OnClose += DeactivateButton;
|
||||
_window.OnOpen += ActivateButton;
|
||||
CharacterButton.Pressed = false;
|
||||
}
|
||||
|
||||
private void DeactivateButton() => CharacterButton!.Pressed = false;
|
||||
private void ActivateButton() => CharacterButton!.Pressed = true;
|
||||
private void ActivateButton()
|
||||
{
|
||||
if (CharacterButton == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CharacterButton.Pressed = true;
|
||||
}
|
||||
|
||||
private void CharacterUpdated(CharacterData data)
|
||||
{
|
||||
@@ -150,7 +160,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
|
||||
var objectiveLabel = new RichTextLabel
|
||||
{
|
||||
StyleClasses = {StyleNano.StyleClassTooltipActionTitle}
|
||||
StyleClasses = { StyleNano.StyleClassTooltipActionTitle }
|
||||
};
|
||||
objectiveLabel.SetMessage(objectiveText);
|
||||
|
||||
@@ -245,10 +255,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (CharacterButton != null)
|
||||
{
|
||||
CharacterButton.SetClickPressed(!_window.IsOpen);
|
||||
}
|
||||
CharacterButton?.SetClickPressed(!_window.IsOpen);
|
||||
|
||||
if (_window.IsOpen)
|
||||
{
|
||||
|
||||
@@ -38,11 +38,6 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[UISystemDependency] private readonly StorageSystem _storage = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Cached positions for opening nested storage.
|
||||
/// </summary>
|
||||
private readonly Dictionary<EntityUid, Vector2> _reservedStorage = new();
|
||||
|
||||
private readonly DragDropHelper<ItemGridPiece> _menuDragHelper;
|
||||
|
||||
public ItemGridPiece? DraggingGhost => _menuDragHelper.Dragged;
|
||||
@@ -133,11 +128,6 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
else
|
||||
{
|
||||
window.OpenCenteredLeft();
|
||||
|
||||
if (_reservedStorage.Remove(uid, out var pos))
|
||||
{
|
||||
LayoutContainer.SetPosition(window, pos);
|
||||
}
|
||||
}
|
||||
|
||||
return window;
|
||||
|
||||
@@ -3,8 +3,10 @@ using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Viewport;
|
||||
@@ -15,6 +17,7 @@ public sealed class ViewportUIController : UIController
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[UISystemDependency] private readonly SharedTransformSystem? _transformSystem = default!;
|
||||
public static readonly Vector2i ViewportSize = (EyeManager.PixelsPerMeter * 21, EyeManager.PixelsPerMeter * 15);
|
||||
public const int ViewportHeight = 15;
|
||||
private MainViewport? Viewport => UIManager.ActiveScreen?.GetWidget<MainViewport>();
|
||||
@@ -93,8 +96,11 @@ public sealed class ViewportUIController : UIController
|
||||
_entMan.TryGetComponent(ent, out EyeComponent? eye);
|
||||
|
||||
if (eye?.Eye == _eyeManager.CurrentEye
|
||||
&& _entMan.GetComponent<TransformComponent>(ent.Value).WorldPosition == default)
|
||||
return; // nothing to worry about, the player is just in null space... actually that is probably a problem?
|
||||
&& _entMan.GetComponent<TransformComponent>(ent.Value).MapID == MapId.Nullspace)
|
||||
{
|
||||
// nothing to worry about, the player is just in null space... actually that is probably a problem?
|
||||
return;
|
||||
}
|
||||
|
||||
// Currently, this shouldn't happen. This likely happened because the main eye was set to null. When this
|
||||
// does happen it can create hard to troubleshoot bugs, so lets print some helpful warnings:
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
using Content.Client.Ghost;
|
||||
using Content.Shared.Voting;
|
||||
|
||||
namespace Content.Client.Voting;
|
||||
|
||||
public sealed class VotingSystem : EntitySystem
|
||||
{
|
||||
|
||||
public event Action<VotePlayerListResponseEvent>? VotePlayerListResponse; //Provides a list of players elligble for vote actions
|
||||
|
||||
[Dependency] private readonly GhostSystem _ghostSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
@@ -170,7 +170,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
var targetCoordinates = xform.Coordinates;
|
||||
var targetLocalAngle = xform.LocalRotation;
|
||||
|
||||
return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range);
|
||||
return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range, overlapCheck: false);
|
||||
}
|
||||
|
||||
protected override void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform)
|
||||
|
||||
@@ -11,6 +11,8 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body
|
||||
{
|
||||
@@ -57,7 +59,6 @@ namespace Content.IntegrationTests.Tests.Body
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapLoader = entityManager.System<MapLoaderSystem>();
|
||||
var mapSys = entityManager.System<SharedMapSystem>();
|
||||
@@ -69,17 +70,13 @@ namespace Content.IntegrationTests.Tests.Body
|
||||
GridAtmosphereComponent relevantAtmos = default;
|
||||
var startingMoles = 0.0f;
|
||||
|
||||
var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml";
|
||||
var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml");
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapSys.CreateMap(out var mapId);
|
||||
Assert.That(mapLoader.TryLoad(mapId, testMapName, out var roots));
|
||||
|
||||
var query = entityManager.GetEntityQuery<MapGridComponent>();
|
||||
var grids = roots.Where(x => query.HasComponent(x));
|
||||
Assert.That(grids, Is.Not.Empty);
|
||||
grid = grids.First();
|
||||
Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt));
|
||||
grid = gridEnt!.Value.Owner;
|
||||
});
|
||||
|
||||
Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found.");
|
||||
@@ -148,18 +145,13 @@ namespace Content.IntegrationTests.Tests.Body
|
||||
RespiratorComponent respirator = null;
|
||||
EntityUid human = default;
|
||||
|
||||
var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml";
|
||||
var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml");
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapSys.CreateMap(out var mapId);
|
||||
|
||||
Assert.That(mapLoader.TryLoad(mapId, testMapName, out var ents), Is.True);
|
||||
var query = entityManager.GetEntityQuery<MapGridComponent>();
|
||||
grid = ents
|
||||
.Select<EntityUid, EntityUid?>(x => x)
|
||||
.FirstOrDefault((uid) => uid.HasValue && query.HasComponent(uid.Value), null);
|
||||
Assert.That(grid, Is.Not.Null);
|
||||
Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt));
|
||||
grid = gridEnt!.Value.Owner;
|
||||
});
|
||||
|
||||
Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found.");
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Body;
|
||||
|
||||
@@ -111,13 +112,12 @@ public sealed class SaveLoadReparentTest
|
||||
Is.Not.Empty
|
||||
);
|
||||
|
||||
const string mapPath = $"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml";
|
||||
var mapPath = new ResPath($"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml");
|
||||
|
||||
mapLoader.SaveMap(mapId, mapPath);
|
||||
maps.DeleteMap(mapId);
|
||||
Assert.That(mapLoader.TrySaveMap(mapId, mapPath));
|
||||
mapSys.DeleteMap(mapId);
|
||||
|
||||
mapSys.CreateMap(out mapId);
|
||||
Assert.That(mapLoader.TryLoad(mapId, mapPath, out _), Is.True);
|
||||
Assert.That(mapLoader.TryLoadMap(mapPath, out var map, out _), Is.True);
|
||||
|
||||
var query = EnumerateQueryEnumerator(
|
||||
entities.EntityQueryEnumerator<BodyComponent>()
|
||||
@@ -173,7 +173,7 @@ public sealed class SaveLoadReparentTest
|
||||
});
|
||||
}
|
||||
|
||||
maps.DeleteMap(mapId);
|
||||
entities.DeleteEntity(map);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.Nutrition.Components;
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Whitelist;
|
||||
@@ -104,51 +105,34 @@ public sealed class CargoTest
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var testMap = await pair.CreateTestMap();
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var protoManager = server.ProtoMan;
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapId = testMap.MapId;
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
var coord = new EntityCoordinates(grid.Owner, 0, 0);
|
||||
|
||||
var protoIds = protoManager.EnumeratePrototypes<EntityPrototype>()
|
||||
.Where(p => !p.Abstract)
|
||||
.Where(p => !pair.IsTestPrototype(p))
|
||||
.Where(p => !p.Components.ContainsKey("MapGrid")) // Grids are not for sale.
|
||||
.Select(p => p.ID)
|
||||
.Where(p => p.Components.ContainsKey("StaticPrice"))
|
||||
.ToList();
|
||||
|
||||
foreach (var proto in protoIds)
|
||||
{
|
||||
var ent = entManager.SpawnEntity(proto, coord);
|
||||
// Sanity check
|
||||
Assert.That(proto.TryGetComponent<StaticPriceComponent>(out var staticPriceComp, compFact), Is.True);
|
||||
|
||||
if (entManager.TryGetComponent<StackPriceComponent>(ent, out var stackpricecomp)
|
||||
&& stackpricecomp.Price > 0)
|
||||
if (proto.TryGetComponent<StackPriceComponent>(out var stackPriceComp, compFact) && stackPriceComp.Price > 0)
|
||||
{
|
||||
if (entManager.TryGetComponent<StaticPriceComponent>(ent, out var staticpricecomp))
|
||||
{
|
||||
Assert.That(staticpricecomp.Price, Is.EqualTo(0),
|
||||
$"The prototype {proto} has a StackPriceComponent and StaticPriceComponent whose values are not compatible with each other.");
|
||||
}
|
||||
Assert.That(staticPriceComp.Price, Is.EqualTo(0),
|
||||
$"The prototype {proto} has a StackPriceComponent and StaticPriceComponent whose values are not compatible with each other.");
|
||||
}
|
||||
|
||||
if (entManager.HasComponent<StackComponent>(ent))
|
||||
if (proto.HasComponent<StackComponent>(compFact))
|
||||
{
|
||||
if (entManager.TryGetComponent<StaticPriceComponent>(ent, out var staticpricecomp))
|
||||
{
|
||||
Assert.That(staticpricecomp.Price, Is.EqualTo(0),
|
||||
$"The prototype {proto} has a StackComponent and StaticPriceComponent whose values are not compatible with each other.");
|
||||
}
|
||||
Assert.That(staticPriceComp.Price, Is.EqualTo(0),
|
||||
$"The prototype {proto} has a StackComponent and StaticPriceComponent whose values are not compatible with each other.");
|
||||
}
|
||||
|
||||
entManager.DeleteEntity(ent);
|
||||
}
|
||||
mapManager.DeleteMap(mapId);
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Content.IntegrationTests.Tests
|
||||
.Where(p => !p.Abstract)
|
||||
.Where(p => !pair.IsTestPrototype(p))
|
||||
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
|
||||
.Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
|
||||
.Select(p => p.ID)
|
||||
.ToList();
|
||||
|
||||
@@ -101,6 +102,7 @@ namespace Content.IntegrationTests.Tests
|
||||
.Where(p => !p.Abstract)
|
||||
.Where(p => !pair.IsTestPrototype(p))
|
||||
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
|
||||
.Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
|
||||
.Select(p => p.ID)
|
||||
.ToList();
|
||||
foreach (var protoId in protoIds)
|
||||
@@ -341,6 +343,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"DebugExceptionInitialize",
|
||||
"DebugExceptionStartup",
|
||||
"GridFill",
|
||||
"RoomFill",
|
||||
"Map", // We aren't testing a map entity in this test
|
||||
"MapGrid",
|
||||
"Broadphase",
|
||||
|
||||
@@ -26,6 +26,7 @@ public sealed class LatheTest
|
||||
var compFactory = server.ResolveDependency<IComponentFactory>();
|
||||
var materialStorageSystem = server.System<SharedMaterialStorageSystem>();
|
||||
var whitelistSystem = server.System<EntityWhitelistSystem>();
|
||||
var latheSystem = server.System<SharedLatheSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
@@ -74,14 +75,14 @@ public sealed class LatheTest
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all the recipes assigned to this lathe
|
||||
var recipes = new List<ProtoId<LatheRecipePrototype>>();
|
||||
recipes.AddRange(latheComp.StaticRecipes);
|
||||
recipes.AddRange(latheComp.DynamicRecipes);
|
||||
// Collect all possible recipes assigned to this lathe
|
||||
var recipes = new HashSet<ProtoId<LatheRecipePrototype>>();
|
||||
latheSystem.AddRecipesFromPacks(recipes, latheComp.StaticPacks);
|
||||
latheSystem.AddRecipesFromPacks(recipes, latheComp.DynamicPacks);
|
||||
if (latheProto.TryGetComponent<EmagLatheRecipesComponent>(out var emagRecipesComp, compFactory))
|
||||
{
|
||||
recipes.AddRange(emagRecipesComp.EmagStaticRecipes);
|
||||
recipes.AddRange(emagRecipesComp.EmagDynamicRecipes);
|
||||
latheSystem.AddRecipesFromPacks(recipes, emagRecipesComp.EmagStaticPacks);
|
||||
latheSystem.AddRecipesFromPacks(recipes, emagRecipesComp.EmagDynamicPacks);
|
||||
}
|
||||
|
||||
// Check each recipe assigned to this lathe
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -29,7 +30,9 @@ public sealed class StaticFieldValidationTest
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(StringValid), protos), Is.Empty);
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayValid), protos), Is.Empty);
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdValid), protos), Is.Empty);
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTValid), protos), Is.Empty);
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayValid), protos), Is.Empty);
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTArrayValid), protos), Is.Empty);
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestValid), protos), Is.Empty);
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayValid), protos), Is.Empty);
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListValid), protos), Is.Empty);
|
||||
@@ -39,7 +42,9 @@ public sealed class StaticFieldValidationTest
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(StringInvalid), protos), Has.Count.EqualTo(1));
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayInvalid), protos), Has.Count.EqualTo(2));
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdInvalid), protos), Has.Count.EqualTo(1));
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTInvalid), protos), Has.Count.EqualTo(1));
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayInvalid), protos), Has.Count.EqualTo(2));
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTArrayInvalid), protos), Has.Count.EqualTo(2));
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestInvalid), protos), Has.Count.EqualTo(1));
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayInvalid), protos), Has.Count.EqualTo(2));
|
||||
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListInvalid), protos), Has.Count.EqualTo(2));
|
||||
@@ -88,24 +93,48 @@ public sealed class StaticFieldValidationTest
|
||||
public static EntProtoId Tag = "StaticFieldTestEnt";
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdTValid
|
||||
{
|
||||
public static EntProtoId<TransformComponent> Tag = "StaticFieldTestEnt";
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdInvalid
|
||||
{
|
||||
public static EntProtoId Tag = string.Empty;
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdTInvalid
|
||||
{
|
||||
public static EntProtoId<TransformComponent> Tag = string.Empty;
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdArrayValid
|
||||
{
|
||||
public static EntProtoId[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"];
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdTArrayValid
|
||||
{
|
||||
public static EntProtoId<TransformComponent>[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"];
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdArrayInvalid
|
||||
{
|
||||
public static EntProtoId[] Tag = [string.Empty, "StaticFieldTestEnt", string.Empty];
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdTArrayInvalid
|
||||
{
|
||||
public static EntProtoId<TransformComponent>[] Tag = [string.Empty, "StaticFieldTestEnt", string.Empty];
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class ProtoIdTestValid
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Minds;
|
||||
@@ -37,8 +38,8 @@ public sealed partial class MindTests
|
||||
Assert.That(pair.Client.EntMan.EntityCount, Is.EqualTo(0));
|
||||
|
||||
// Create a new map.
|
||||
int mapId = 1;
|
||||
await pair.Server.WaitPost(() => conHost.ExecuteCommand($"addmap {mapId}"));
|
||||
MapId mapId = default;
|
||||
await pair.Server.WaitPost(() => pair.Server.System<SharedMapSystem>().CreateMap(out mapId));
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
// Client is not attached to anything
|
||||
@@ -54,7 +55,7 @@ public sealed partial class MindTests
|
||||
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity));
|
||||
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
|
||||
var xform = pair.Client.Transform(pair.Client.AttachedEntity!.Value);
|
||||
Assert.That(xform.MapID, Is.EqualTo(new MapId(mapId)));
|
||||
Assert.That(xform.MapID, Is.EqualTo(mapId));
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ using Content.Server.Spawners.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -17,6 +16,9 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Station.Components;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
@@ -36,10 +38,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
private static readonly string[] Grids =
|
||||
{
|
||||
"/Maps/centcomm.yml",
|
||||
"/Maps/Shuttles/cargo.yml",
|
||||
"/Maps/Shuttles/emergency.yml",
|
||||
"/Maps/Shuttles/infiltrator.yml",
|
||||
"/Maps/centcomm.yml"
|
||||
};
|
||||
|
||||
private static readonly string[] DoNotMapWhitelist =
|
||||
@@ -65,14 +64,13 @@ namespace Content.IntegrationTests.Tests
|
||||
"Reach",
|
||||
"Train",
|
||||
"Oasis",
|
||||
"Cog",
|
||||
"Gate",
|
||||
"Amber",
|
||||
"Loop",
|
||||
"Plasma",
|
||||
"Elkridge",
|
||||
"Convex"
|
||||
|
||||
"Convex",
|
||||
"Relic"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -87,33 +85,72 @@ namespace Content.IntegrationTests.Tests
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapLoader = entManager.System<MapLoaderSystem>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
|
||||
var path = new ResPath(mapFile);
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
try
|
||||
{
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(mapLoader.TryLoad(mapId, mapFile, out var roots));
|
||||
Assert.That(roots.Where(uid => entManager.HasComponent<MapGridComponent>(uid)), Is.Not.Empty);
|
||||
#pragma warning restore NUnit2045
|
||||
Assert.That(mapLoader.TryLoadGrid(mapId, path, out var grid));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Failed to load map {mapFile}, was it saved as a map instead of a grid?", ex);
|
||||
}
|
||||
|
||||
try
|
||||
mapSystem.DeleteMap(mapId);
|
||||
});
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that shuttles are loadable and have been saved as grids and not maps.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ShuttlesLoadableTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var resMan = server.ResolveDependency<IResourceManager>();
|
||||
var mapLoader = entManager.System<MapLoaderSystem>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
|
||||
|
||||
var shuttleFolder = new ResPath("/Maps/Shuttles");
|
||||
var shuttles = resMan
|
||||
.ContentFindFiles(shuttleFolder)
|
||||
.Where(filePath =>
|
||||
filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
mapManager.DeleteMap(mapId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Failed to delete map {mapFile}", ex);
|
||||
}
|
||||
foreach (var path in shuttles)
|
||||
{
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
try
|
||||
{
|
||||
Assert.That(mapLoader.TryLoadGrid(mapId, path, out _),
|
||||
$"Failed to load shuttle {path}, was it saved as a map instead of a grid?");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Failed to load shuttle {path}, was it saved as a map instead of a grid?",
|
||||
ex);
|
||||
}
|
||||
mapSystem.DeleteMap(mapId);
|
||||
}
|
||||
});
|
||||
});
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
@@ -128,12 +165,15 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
var resourceManager = server.ResolveDependency<IResourceManager>();
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
|
||||
var mapFolder = new ResPath("/Maps");
|
||||
var maps = resourceManager
|
||||
.ContentFindFiles(mapFolder)
|
||||
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
|
||||
var v7Maps = new List<ResPath>();
|
||||
foreach (var map in maps)
|
||||
{
|
||||
var rootedPath = map.ToRootedPath();
|
||||
@@ -156,8 +196,15 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
var root = yamlStream.Documents[0].RootNode;
|
||||
var meta = root["meta"];
|
||||
var postMapInit = meta["postmapinit"].AsBool();
|
||||
var version = meta["format"].AsInt();
|
||||
|
||||
if (version >= 7)
|
||||
{
|
||||
v7Maps.Add(map);
|
||||
continue;
|
||||
}
|
||||
|
||||
var postMapInit = meta["postmapinit"].AsBool();
|
||||
Assert.That(postMapInit, Is.False, $"Map {map.Filename} was saved postmapinit");
|
||||
|
||||
// testing that maps have nothing with the DoNotMap entity category
|
||||
@@ -177,9 +224,58 @@ namespace Content.IntegrationTests.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var deps = server.ResolveDependency<IEntitySystemManager>().DependencyCollection;
|
||||
foreach (var map in v7Maps)
|
||||
{
|
||||
Assert.That(IsPreInit(map, loader, deps));
|
||||
}
|
||||
|
||||
// Check that the test actually does manage to catch post-init maps and isn't just blindly passing everything.
|
||||
// To that end, create a new post-init map and try verify it.
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
MapId id = default;
|
||||
await server.WaitPost(() => mapSys.CreateMap(out id, runMapInit: false));
|
||||
await server.WaitPost(() => server.EntMan.Spawn(null, new MapCoordinates(0, 0, id)));
|
||||
|
||||
// First check that a pre-init version passes
|
||||
var path = new ResPath($"{nameof(NoSavedPostMapInitTest)}.yml");
|
||||
Assert.That(loader.TrySaveMap(id, path));
|
||||
Assert.That(IsPreInit(path, loader, deps));
|
||||
|
||||
// and the post-init version fails.
|
||||
await server.WaitPost(() => mapSys.InitializeMap(id));
|
||||
Assert.That(loader.TrySaveMap(id, path));
|
||||
Assert.That(IsPreInit(path, loader, deps), Is.False);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
private bool IsPreInit(ResPath map, MapLoaderSystem loader, IDependencyCollection deps)
|
||||
{
|
||||
if (!loader.TryReadFile(map, out var data))
|
||||
{
|
||||
Assert.Fail($"Failed to read {map}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var reader = new EntityDeserializer(deps, data, DeserializationOptions.Default);
|
||||
if (!reader.TryProcessData())
|
||||
{
|
||||
Assert.Fail($"Failed to process {map}");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var mapId in reader.MapYamlIds)
|
||||
{
|
||||
var mapData = reader.YamlEntities[mapId];
|
||||
if (mapData.PostInit)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(GameMaps))]
|
||||
public async Task GameMapsLoadableTest(string mapProto)
|
||||
{
|
||||
@@ -196,16 +292,16 @@ namespace Content.IntegrationTests.Tests
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var ticker = entManager.EntitySysManager.GetEntitySystem<GameTicker>();
|
||||
var shuttleSystem = entManager.EntitySysManager.GetEntitySystem<ShuttleSystem>();
|
||||
var xformQuery = entManager.GetEntityQuery<TransformComponent>();
|
||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
MapId mapId;
|
||||
try
|
||||
{
|
||||
ticker.LoadGameMap(protoManager.Index<GameMapPrototype>(mapProto), mapId, null);
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
ticker.LoadGameMap(protoManager.Index<GameMapPrototype>(mapProto), out mapId, opts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -242,21 +338,17 @@ namespace Content.IntegrationTests.Tests
|
||||
if (entManager.TryGetComponent<StationEmergencyShuttleComponent>(station, out var stationEvac))
|
||||
{
|
||||
var shuttlePath = stationEvac.EmergencyShuttlePath;
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(mapLoader.TryLoad(shuttleMap, shuttlePath.ToString(), out var roots));
|
||||
EntityUid shuttle = default!;
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
shuttle = roots.First(uid => entManager.HasComponent<MapGridComponent>(uid));
|
||||
}, $"Failed to load {shuttlePath}");
|
||||
Assert.That(mapLoader.TryLoadGrid(shuttleMap, shuttlePath, out var shuttle),
|
||||
$"Failed to load {shuttlePath}");
|
||||
|
||||
Assert.That(
|
||||
shuttleSystem.TryFTLDock(shuttle,
|
||||
entManager.GetComponent<ShuttleComponent>(shuttle), targetGrid.Value),
|
||||
shuttleSystem.TryFTLDock(shuttle!.Value.Owner,
|
||||
entManager.GetComponent<ShuttleComponent>(shuttle!.Value.Owner),
|
||||
targetGrid.Value),
|
||||
$"Unable to dock {shuttlePath} to {mapProto}");
|
||||
#pragma warning restore NUnit2045
|
||||
}
|
||||
|
||||
mapManager.DeleteMap(shuttleMap);
|
||||
mapSystem.DeleteMap(shuttleMap);
|
||||
|
||||
if (entManager.HasComponent<StationJobsComponent>(station))
|
||||
{
|
||||
@@ -293,7 +385,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
try
|
||||
{
|
||||
mapManager.DeleteMap(mapId);
|
||||
mapSystem.DeleteMap(mapId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -357,11 +449,9 @@ namespace Content.IntegrationTests.Tests
|
||||
var server = pair.Server;
|
||||
|
||||
var mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var resourceManager = server.ResolveDependency<IResourceManager>();
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||
var mapSystem = server.System<SharedMapSystem>();
|
||||
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
|
||||
|
||||
var gameMaps = protoManager.EnumeratePrototypes<GameMapPrototype>().Select(o => o.MapPath).ToHashSet();
|
||||
@@ -372,7 +462,7 @@ namespace Content.IntegrationTests.Tests
|
||||
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
|
||||
var mapNames = new List<string>();
|
||||
var mapPaths = new List<ResPath>();
|
||||
foreach (var map in maps)
|
||||
{
|
||||
if (gameMaps.Contains(map))
|
||||
@@ -383,32 +473,46 @@ namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
continue;
|
||||
}
|
||||
mapNames.Add(rootedPath.ToString());
|
||||
mapPaths.Add(rootedPath);
|
||||
}
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var mapName in mapNames)
|
||||
// This bunch of files contains a random mixture of both map and grid files.
|
||||
// TODO MAPPING organize files
|
||||
var opts = MapLoadOptions.Default with
|
||||
{
|
||||
DeserializationOptions = DeserializationOptions.Default with
|
||||
{
|
||||
InitializeMaps = true,
|
||||
LogOrphanedGrids = false
|
||||
}
|
||||
};
|
||||
|
||||
HashSet<Entity<MapComponent>> maps;
|
||||
foreach (var path in mapPaths)
|
||||
{
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
try
|
||||
{
|
||||
Assert.That(mapLoader.TryLoad(mapId, mapName, out _));
|
||||
Assert.That(mapLoader.TryLoadGeneric(path, out maps, out _, opts));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Failed to load map {mapName}", ex);
|
||||
throw new Exception($"Failed to load map {path}", ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mapManager.DeleteMap(mapId);
|
||||
foreach (var map in maps)
|
||||
{
|
||||
server.EntMan.DeleteEntity(map);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Failed to delete map {mapName}", ex);
|
||||
throw new Exception($"Failed to delete map {path}", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -52,13 +52,16 @@ public sealed class ResearchTest
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.ResolveDependency<IEntityManager>();
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
|
||||
var latheSys = entMan.System<SharedLatheSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var allEnts = protoManager.EnumeratePrototypes<EntityPrototype>();
|
||||
var allLathes = new HashSet<LatheComponent>();
|
||||
var latheTechs = new HashSet<ProtoId<LatheRecipePrototype>>();
|
||||
foreach (var proto in allEnts)
|
||||
{
|
||||
if (proto.Abstract)
|
||||
@@ -69,30 +72,31 @@ public sealed class ResearchTest
|
||||
|
||||
if (!proto.TryGetComponent<LatheComponent>(out var lathe, compFact))
|
||||
continue;
|
||||
allLathes.Add(lathe);
|
||||
}
|
||||
|
||||
var latheTechs = new HashSet<string>();
|
||||
foreach (var lathe in allLathes)
|
||||
{
|
||||
if (lathe.DynamicRecipes == null)
|
||||
continue;
|
||||
latheSys.AddRecipesFromPacks(latheTechs, lathe.DynamicPacks);
|
||||
|
||||
foreach (var recipe in lathe.DynamicRecipes)
|
||||
{
|
||||
latheTechs.Add(recipe);
|
||||
}
|
||||
if (proto.TryGetComponent<EmagLatheRecipesComponent>(out var emag, compFact))
|
||||
latheSys.AddRecipesFromPacks(latheTechs, emag.EmagDynamicPacks);
|
||||
}
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// check that every recipe a tech adds can be made on some lathe
|
||||
var unlockedTechs = new HashSet<ProtoId<LatheRecipePrototype>>();
|
||||
foreach (var tech in protoManager.EnumeratePrototypes<TechnologyPrototype>())
|
||||
{
|
||||
unlockedTechs.UnionWith(tech.RecipeUnlocks);
|
||||
foreach (var recipe in tech.RecipeUnlocks)
|
||||
{
|
||||
Assert.That(latheTechs, Does.Contain(recipe), $"Recipe \"{recipe}\" cannot be unlocked on any lathes.");
|
||||
Assert.That(latheTechs, Does.Contain(recipe), $"Recipe '{recipe}' from tech '{tech.ID}' cannot be unlocked on any lathes.");
|
||||
}
|
||||
}
|
||||
|
||||
// now check that every dynamic recipe a lathe lists can be unlocked
|
||||
foreach (var recipe in latheTechs)
|
||||
{
|
||||
Assert.That(unlockedTechs, Does.Contain(recipe), $"Recipe '{recipe}' is dynamic on a lathe but cannot be unlocked by research.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Salvage;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests;
|
||||
@@ -24,7 +21,6 @@ public sealed class SalvageTest
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapLoader = entManager.System<MapLoaderSystem>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
@@ -34,13 +30,10 @@ public sealed class SalvageTest
|
||||
{
|
||||
foreach (var salvage in prototypeManager.EnumeratePrototypes<SalvageMapPrototype>())
|
||||
{
|
||||
var mapFile = salvage.MapPath;
|
||||
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
try
|
||||
{
|
||||
Assert.That(mapLoader.TryLoad(mapId, mapFile.ToString(), out var roots));
|
||||
Assert.That(roots.Where(uid => entManager.HasComponent<MapGridComponent>(uid)), Is.Not.Empty);
|
||||
Assert.That(mapLoader.TryLoadGrid(mapId, salvage.MapPath, out var grid));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -49,7 +42,7 @@ public sealed class SalvageTest
|
||||
|
||||
try
|
||||
{
|
||||
mapManager.DeleteMap(mapId);
|
||||
mapSystem.DeleteMap(mapId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.CCVar;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -16,7 +17,7 @@ namespace Content.IntegrationTests.Tests
|
||||
[Test]
|
||||
public async Task SaveLoadMultiGridMap()
|
||||
{
|
||||
const string mapPath = @"/Maps/Test/TestMap.yml";
|
||||
var mapPath = new ResPath("/Maps/Test/TestMap.yml");
|
||||
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
@@ -31,7 +32,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var dir = new ResPath(mapPath).Directory;
|
||||
var dir = mapPath.Directory;
|
||||
resManager.UserData.CreateDir(dir);
|
||||
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
@@ -39,23 +40,25 @@ namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
var mapGrid = mapManager.CreateGridEntity(mapId);
|
||||
xformSystem.SetWorldPosition(mapGrid, new Vector2(10, 10));
|
||||
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(1, (TileRenderFlag) 1, 255));
|
||||
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(typeId: 1, flags: 1, variant: 255));
|
||||
}
|
||||
{
|
||||
var mapGrid = mapManager.CreateGridEntity(mapId);
|
||||
xformSystem.SetWorldPosition(mapGrid, new Vector2(-8, -8));
|
||||
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(2, (TileRenderFlag) 1, 254));
|
||||
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(typeId: 2, flags: 1, variant: 254));
|
||||
}
|
||||
|
||||
Assert.Multiple(() => mapLoader.SaveMap(mapId, mapPath));
|
||||
Assert.Multiple(() => mapManager.DeleteMap(mapId));
|
||||
Assert.That(mapLoader.TrySaveMap(mapId, mapPath));
|
||||
mapSystem.DeleteMap(mapId);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
MapId newMap = default;
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(mapLoader.TryLoad(new MapId(10), mapPath, out _));
|
||||
Assert.That(mapLoader.TryLoadMap(mapPath, out var map, out _));
|
||||
newMap = map!.Value.Comp.MapId;
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
@@ -63,7 +66,7 @@ namespace Content.IntegrationTests.Tests
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
{
|
||||
if (!mapManager.TryFindGridAt(new MapId(10), new Vector2(10, 10), out var gridUid, out var mapGrid) ||
|
||||
if (!mapManager.TryFindGridAt(newMap, new Vector2(10, 10), out var gridUid, out var mapGrid) ||
|
||||
!sEntities.TryGetComponent<TransformComponent>(gridUid, out var gridXform))
|
||||
{
|
||||
Assert.Fail();
|
||||
@@ -73,11 +76,11 @@ namespace Content.IntegrationTests.Tests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(10, 10)));
|
||||
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(1, (TileRenderFlag) 1, 255)));
|
||||
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(typeId: 1, flags: 1, variant: 255)));
|
||||
});
|
||||
}
|
||||
{
|
||||
if (!mapManager.TryFindGridAt(new MapId(10), new Vector2(-8, -8), out var gridUid, out var mapGrid) ||
|
||||
if (!mapManager.TryFindGridAt(newMap, new Vector2(-8, -8), out var gridUid, out var mapGrid) ||
|
||||
!sEntities.TryGetComponent<TransformComponent>(gridUid, out var gridXform))
|
||||
{
|
||||
Assert.Fail();
|
||||
@@ -87,7 +90,7 @@ namespace Content.IntegrationTests.Tests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(-8, -8)));
|
||||
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(2, (TileRenderFlag) 1, 254)));
|
||||
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(typeId: 2, flags: 1, variant: 254)));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Events;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests that a map's yaml does not change when saved consecutively.
|
||||
/// Tests that a grid's yaml does not change when saved consecutively.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public sealed class SaveLoadSaveTest
|
||||
{
|
||||
[Test]
|
||||
public async Task SaveLoadSave()
|
||||
public async Task CreateSaveLoadSaveGrid()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
@@ -30,22 +30,21 @@ namespace Content.IntegrationTests.Tests
|
||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
|
||||
|
||||
var testSystem = server.System<SaveLoadSaveTestSystem>();
|
||||
testSystem.Enabled = true;
|
||||
|
||||
var rp1 = new ResPath("/save load save 1.yml");
|
||||
var rp2 = new ResPath("/save load save 2.yml");
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapSystem.CreateMap(out var mapId0);
|
||||
// TODO: Properly find the "main" station grid.
|
||||
var grid0 = mapManager.CreateGridEntity(mapId0);
|
||||
mapLoader.Save(grid0.Owner, "save load save 1.yml");
|
||||
entManager.RunMapInit(grid0.Owner, entManager.GetComponent<MetaDataComponent>(grid0));
|
||||
Assert.That(mapLoader.TrySaveGrid(grid0.Owner, rp1));
|
||||
mapSystem.CreateMap(out var mapId1);
|
||||
EntityUid grid1 = default!;
|
||||
#pragma warning disable NUnit2045
|
||||
Assert.That(mapLoader.TryLoad(mapId1, "save load save 1.yml", out var roots, new MapLoadOptions() { LoadMap = false }), $"Failed to load test map {TestMap}");
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
grid1 = roots.First(uid => entManager.HasComponent<MapGridComponent>(uid));
|
||||
});
|
||||
#pragma warning restore NUnit2045
|
||||
mapLoader.Save(grid1, "save load save 2.yml");
|
||||
Assert.That(mapLoader.TryLoadGrid(mapId1, rp1, out var grid1));
|
||||
Assert.That(mapLoader.TrySaveGrid(grid1!.Value, rp2));
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
@@ -54,14 +53,12 @@ namespace Content.IntegrationTests.Tests
|
||||
string one;
|
||||
string two;
|
||||
|
||||
var rp1 = new ResPath("/save load save 1.yml");
|
||||
await using (var stream = userData.Open(rp1, FileMode.Open))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
one = await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
var rp2 = new ResPath("/save load save 2.yml");
|
||||
await using (var stream = userData.Open(rp2, FileMode.Open))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
@@ -87,6 +84,7 @@ namespace Content.IntegrationTests.Tests
|
||||
TestContext.Error.WriteLine(twoTmp);
|
||||
}
|
||||
});
|
||||
testSystem.Enabled = false;
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
@@ -101,8 +99,12 @@ namespace Content.IntegrationTests.Tests
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
var mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var mapSystem = server.System<SharedMapSystem>();
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var testSystem = server.System<SaveLoadSaveTestSystem>();
|
||||
testSystem.Enabled = true;
|
||||
|
||||
var rp1 = new ResPath("/load save ticks save 1.yml");
|
||||
var rp2 = new ResPath("/load save ticks save 2.yml");
|
||||
|
||||
MapId mapId = default;
|
||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||
@@ -111,10 +113,10 @@ namespace Content.IntegrationTests.Tests
|
||||
// Load bagel.yml as uninitialized map, and save it to ensure it's up to date.
|
||||
server.Post(() =>
|
||||
{
|
||||
mapSystem.CreateMap(out mapId, runMapInit: false);
|
||||
mapManager.SetMapPaused(mapId, true);
|
||||
Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}");
|
||||
mapLoader.SaveMap(mapId, "load save ticks save 1.yml");
|
||||
var path = new ResPath(TestMap);
|
||||
Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}");
|
||||
mapId = map!.Value.Comp.MapId;
|
||||
Assert.That(mapLoader.TrySaveMap(mapId, rp1));
|
||||
});
|
||||
|
||||
// Run 5 ticks.
|
||||
@@ -122,7 +124,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapLoader.SaveMap(mapId, "/load save ticks save 2.yml");
|
||||
Assert.That(mapLoader.TrySaveMap(mapId, rp2));
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
@@ -131,13 +133,13 @@ namespace Content.IntegrationTests.Tests
|
||||
string one;
|
||||
string two;
|
||||
|
||||
await using (var stream = userData.Open(new ResPath("/load save ticks save 1.yml"), FileMode.Open))
|
||||
await using (var stream = userData.Open(rp1, FileMode.Open))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
one = await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
await using (var stream = userData.Open(new ResPath("/load save ticks save 2.yml"), FileMode.Open))
|
||||
await using (var stream = userData.Open(rp2, FileMode.Open))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
two = await reader.ReadToEndAsync();
|
||||
@@ -163,7 +165,8 @@ namespace Content.IntegrationTests.Tests
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitPost(() => mapManager.DeleteMap(mapId));
|
||||
testSystem.Enabled = false;
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
@@ -184,29 +187,31 @@ namespace Content.IntegrationTests.Tests
|
||||
var server = pair.Server;
|
||||
|
||||
var mapLoader = server.System<MapLoaderSystem>();
|
||||
var mapSystem = server.System<SharedMapSystem>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var userData = server.ResolveDependency<IResourceManager>().UserData;
|
||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
|
||||
var testSystem = server.System<SaveLoadSaveTestSystem>();
|
||||
testSystem.Enabled = true;
|
||||
|
||||
MapId mapId = default;
|
||||
const string fileA = "/load tick load a.yml";
|
||||
const string fileB = "/load tick load b.yml";
|
||||
MapId mapId1 = default;
|
||||
MapId mapId2 = default;
|
||||
var fileA = new ResPath("/load tick load a.yml");
|
||||
var fileB = new ResPath("/load tick load b.yml");
|
||||
string yamlA;
|
||||
string yamlB;
|
||||
|
||||
// Load & save the first map
|
||||
server.Post(() =>
|
||||
{
|
||||
mapSystem.CreateMap(out mapId, runMapInit: false);
|
||||
mapManager.SetMapPaused(mapId, true);
|
||||
Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}");
|
||||
mapLoader.SaveMap(mapId, fileA);
|
||||
var path = new ResPath(TestMap);
|
||||
Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}");
|
||||
mapId1 = map!.Value.Comp.MapId;
|
||||
Assert.That(mapLoader.TrySaveMap(mapId1, fileA));
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
await using (var stream = userData.Open(new ResPath(fileA), FileMode.Open))
|
||||
await using (var stream = userData.Open(fileA, FileMode.Open))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
yamlA = await reader.ReadToEndAsync();
|
||||
@@ -217,16 +222,15 @@ namespace Content.IntegrationTests.Tests
|
||||
// Load & save the second map
|
||||
server.Post(() =>
|
||||
{
|
||||
mapManager.DeleteMap(mapId);
|
||||
mapSystem.CreateMap(out mapId, runMapInit: false);
|
||||
mapManager.SetMapPaused(mapId, true);
|
||||
Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}");
|
||||
mapLoader.SaveMap(mapId, fileB);
|
||||
var path = new ResPath(TestMap);
|
||||
Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}");
|
||||
mapId2 = map!.Value.Comp.MapId;
|
||||
Assert.That(mapLoader.TrySaveMap(mapId2, fileB));
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
await using (var stream = userData.Open(new ResPath(fileB), FileMode.Open))
|
||||
await using (var stream = userData.Open(fileB, FileMode.Open))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
yamlB = await reader.ReadToEndAsync();
|
||||
@@ -234,8 +238,32 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
Assert.That(yamlA, Is.EqualTo(yamlB));
|
||||
|
||||
await server.WaitPost(() => mapManager.DeleteMap(mapId));
|
||||
testSystem.Enabled = false;
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId1));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId2));
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple system that modifies the data saved to a yaml file by removing the timestamp.
|
||||
/// Required by some tests that validate that re-saving a map does not modify it.
|
||||
/// </summary>
|
||||
private sealed class SaveLoadSaveTestSystem : EntitySystem
|
||||
{
|
||||
public bool Enabled;
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<AfterSerializationEvent>(OnAfterSave);
|
||||
}
|
||||
|
||||
private void OnAfterSave(AfterSerializationEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
// Remove timestamp.
|
||||
((MappingDataNode)ev.Node["meta"]).Remove("time");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ using System.Numerics;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Tests;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Shuttle;
|
||||
|
||||
@@ -106,8 +108,9 @@ public sealed class DockTest : ContentUnitTest
|
||||
{
|
||||
mapGrid = entManager.AddComponent<MapGridComponent>(map.MapUid);
|
||||
entManager.DeleteEntity(map.Grid);
|
||||
Assert.That(entManager.System<MapLoaderSystem>().TryLoad(otherMap.MapId, "/Maps/Shuttles/emergency.yml", out var rootUids));
|
||||
shuttle = rootUids[0];
|
||||
var path = new ResPath("/Maps/Shuttles/emergency.yml");
|
||||
Assert.That(entManager.System<MapLoaderSystem>().TryLoadGrid(otherMap.MapId, path, out var grid));
|
||||
shuttle = grid!.Value.Owner;
|
||||
|
||||
var dockingConfig = dockingSystem.GetDockingConfig(shuttle, map.MapUid);
|
||||
Assert.That(dockingConfig, Is.EqualTo(null));
|
||||
|
||||
2117
Content.Server.Database/Migrations/Postgres/20250211131539_LoadoutNames.Designer.cs
generated
Normal file
2117
Content.Server.Database/Migrations/Postgres/20250211131539_LoadoutNames.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class LoadoutNames : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "entity_name",
|
||||
table: "profile_role_loadout",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "entity_name",
|
||||
table: "profile_role_loadout");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -975,6 +975,11 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("EntityName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("entity_name");
|
||||
|
||||
b.Property<int>("ProfileId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("profile_id");
|
||||
|
||||
2041
Content.Server.Database/Migrations/Sqlite/20250211131517_LoadoutNames.Designer.cs
generated
Normal file
2041
Content.Server.Database/Migrations/Sqlite/20250211131517_LoadoutNames.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class LoadoutNames : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "entity_name",
|
||||
table: "profile_role_loadout",
|
||||
type: "TEXT",
|
||||
maxLength: 256,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "entity_name",
|
||||
table: "profile_role_loadout");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -921,6 +921,11 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_role_loadout_id");
|
||||
|
||||
b.Property<string>("EntityName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("entity_name");
|
||||
|
||||
b.Property<int>("ProfileId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_id");
|
||||
|
||||
@@ -480,6 +480,12 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
public string RoleName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Custom name of the role loadout if it supports it.
|
||||
/// </summary>
|
||||
[MaxLength(256)]
|
||||
public string? EntityName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Store the saved loadout groups. These may get validated and removed when loaded at runtime.
|
||||
/// </summary>
|
||||
|
||||
215
Content.Server/Administration/Commands/ChangeCvarCommand.cs
Normal file
215
Content.Server/Administration/Commands/ChangeCvarCommand.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Allows admins to change certain CVars. This is different than the "cvar" command which is host only and can change any CVar.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Possible todo for future, store default values for cvars, and allow resetting to default.
|
||||
/// </remarks>
|
||||
[AnyCommand]
|
||||
public sealed class ChangeCvarCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
|
||||
[Dependency] private readonly CVarControlManager _cVarControlManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Searches the list of cvars for a cvar that matches the search string.
|
||||
/// </summary>
|
||||
private void SearchCVars(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-changecvar-search-no-arguments"));
|
||||
return;
|
||||
}
|
||||
|
||||
var cvars = _cVarControlManager.GetAllRunnableCvars(shell);
|
||||
|
||||
var matches = cvars
|
||||
.Where(c =>
|
||||
c.Name.Contains(args[1], StringComparison.OrdinalIgnoreCase)
|
||||
|| c.ShortHelp?.Contains(args[1], StringComparison.OrdinalIgnoreCase) == true
|
||||
|| c.LongHelp?.Contains(args[1], StringComparison.OrdinalIgnoreCase) == true
|
||||
) // Might be very slow and stupid, but eh.
|
||||
.ToList();
|
||||
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-changecvar-search-no-matches"));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-changecvar-search-matches", ("count", matches.Count)));
|
||||
shell.WriteLine(string.Join("\n", matches.Select(FormatCVarFullHelp)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a CVar into a string for display.
|
||||
/// </summary>
|
||||
private string FormatCVarFullHelp(ChangableCVar cvar)
|
||||
{
|
||||
if (cvar.LongHelp != null && cvar.ShortHelp != null)
|
||||
{
|
||||
return $"{cvar.Name} - {cvar.LongHelp}";
|
||||
}
|
||||
|
||||
// There is no help, no one is coming. We are all doomed.
|
||||
return cvar.Name;
|
||||
}
|
||||
|
||||
public string Command => "changecvar";
|
||||
public string Description { get; } = Loc.GetString("cmd-changecvar-desc");
|
||||
public string Help { get; } = Loc.GetString("cmd-changecvar-help");
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-changecvar-no-arguments"));
|
||||
return;
|
||||
}
|
||||
|
||||
var cvars = _cVarControlManager.GetAllRunnableCvars(shell);
|
||||
|
||||
var cvar = args[0];
|
||||
if (cvar == "?")
|
||||
{
|
||||
if (cvars.Count == 0)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-changecvar-no-cvars"));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-changecvar-available-cvars"));
|
||||
shell.WriteLine(string.Join("\n", cvars.Select(FormatCVarFullHelp)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cvar == "search")
|
||||
{
|
||||
SearchCVars(shell, argStr, args);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_configurationManager.IsCVarRegistered(cvar)) // Might be a redunat check with the if statement below.
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-changecvar-cvar-not-registered", ("cvar", cvar)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cvars.All(c => c.Name != cvar))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-changecvar-cvar-not-allowed"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length == 1)
|
||||
{
|
||||
var value = _configurationManager.GetCVar<object>(cvar);
|
||||
shell.WriteLine(value.ToString()!);
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = args[1];
|
||||
var type = _configurationManager.GetCVarType(cvar);
|
||||
try
|
||||
{
|
||||
var parsed = CVarCommandUtil.ParseObject(type, value);
|
||||
// Value check, is it in the min/max range?
|
||||
var control = _cVarControlManager.GetCVar(cvar)!.Control; // Null check is done above.
|
||||
var allowed = true;
|
||||
if (control is { Min: not null, Max: not null })
|
||||
{
|
||||
switch (parsed) // This looks bad, and im not sorry.
|
||||
{
|
||||
case int intVal:
|
||||
{
|
||||
if (intVal < (int)control.Min || intVal > (int)control.Max)
|
||||
{
|
||||
allowed = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case float floatVal:
|
||||
{
|
||||
if (floatVal < (float)control.Min || floatVal > (float)control.Max)
|
||||
{
|
||||
allowed = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case long longVal:
|
||||
{
|
||||
if (longVal < (long)control.Min || longVal > (long)control.Max)
|
||||
{
|
||||
allowed = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ushort ushortVal:
|
||||
{
|
||||
if (ushortVal < (ushort)control.Min || ushortVal > (ushort)control.Max)
|
||||
{
|
||||
allowed = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-changecvar-value-out-of-range",
|
||||
("min", control.Min ?? "-∞"),
|
||||
("max", control.Max ?? "∞")));
|
||||
return;
|
||||
}
|
||||
|
||||
var oldValue = _configurationManager.GetCVar<object>(cvar);
|
||||
_configurationManager.SetCVar(cvar, parsed);
|
||||
_adminLogManager.Add(LogType.AdminCommands,
|
||||
LogImpact.High,
|
||||
$"{shell.Player!.Name} ({shell.Player!.UserId}) changed CVAR {cvar} from {oldValue.ToString()} to {parsed.ToString()}"
|
||||
);
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-changecvar-success", ("cvar", cvar), ("old", oldValue), ("value", parsed)));
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-cvar-parse-error", ("type", type)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
var cvars = _cVarControlManager.GetAllRunnableCvars(shell);
|
||||
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
cvars
|
||||
.Select(c => new CompletionOption(c.Name, c.ShortHelp ?? c.Name)),
|
||||
Loc.GetString("cmd-changecvar-arg-name"));
|
||||
}
|
||||
|
||||
var cvar = args[0];
|
||||
if (!_configurationManager.IsCVarRegistered(cvar))
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var type = _configurationManager.GetCVarType(cvar);
|
||||
return CompletionResult.FromHint($"<{type.Name}>");
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Maps;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -25,6 +23,7 @@ namespace Content.Server.Administration.Commands
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var gameTicker = entityManager.EntitySysManager.GetEntitySystem<GameTicker>();
|
||||
var mapSys = entityManager.EntitySysManager.GetEntitySystem<SharedMapSystem>();
|
||||
|
||||
if (args.Length is not (2 or 4 or 5))
|
||||
{
|
||||
@@ -32,29 +31,28 @@ namespace Content.Server.Administration.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (prototypeManager.TryIndex<GameMapPrototype>(args[1], out var gameMap))
|
||||
{
|
||||
if (!int.TryParse(args[0], out var mapId))
|
||||
return;
|
||||
|
||||
var loadOptions = new MapLoadOptions()
|
||||
{
|
||||
LoadMap = false,
|
||||
};
|
||||
|
||||
var stationName = args.Length == 5 ? args[4] : null;
|
||||
|
||||
if (args.Length >= 4 && int.TryParse(args[2], out var x) && int.TryParse(args[3], out var y))
|
||||
{
|
||||
loadOptions.Offset = new Vector2(x, y);
|
||||
}
|
||||
var grids = gameTicker.LoadGameMap(gameMap, new MapId(mapId), loadOptions, stationName);
|
||||
shell.WriteLine($"Loaded {grids.Count} grids.");
|
||||
}
|
||||
else
|
||||
if (!prototypeManager.TryIndex<GameMapPrototype>(args[1], out var gameMap))
|
||||
{
|
||||
shell.WriteError($"The given map prototype {args[0]} is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var mapId))
|
||||
return;
|
||||
|
||||
var stationName = args.Length == 5 ? args[4] : null;
|
||||
|
||||
Vector2? offset = null;
|
||||
if (args.Length >= 4)
|
||||
offset = new Vector2(int.Parse(args[2]), int.Parse(args[3]));
|
||||
|
||||
var id = new MapId(mapId);
|
||||
|
||||
var grids = mapSys.MapExists(id)
|
||||
? gameTicker.MergeGameMap(gameMap, id, stationName: stationName, offset: offset)
|
||||
: gameTicker.LoadGameMapWithId(gameMap, id, stationName: stationName, offset: offset);
|
||||
|
||||
shell.WriteLine($"Loaded {grids.Count} grids.");
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using System.Linq;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed class PersistenceSave : IConsoleCommand
|
||||
}
|
||||
|
||||
var mapLoader = _system.GetEntitySystem<MapLoaderSystem>();
|
||||
mapLoader.SaveMap(mapId, saveFilePath);
|
||||
mapLoader.TrySaveMap(mapId, new ResPath(saveFilePath));
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +118,9 @@ namespace Content.Server.Administration.Commands
|
||||
}
|
||||
|
||||
var xform = _entManager.GetComponent<TransformComponent>(playerEntity);
|
||||
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
xform.Coordinates = coords;
|
||||
xform.AttachToGridOrMap();
|
||||
xformSystem.AttachToGridOrMap(playerEntity, xform);
|
||||
if (_entManager.TryGetComponent(playerEntity, out PhysicsComponent? physics))
|
||||
{
|
||||
_entManager.System<SharedPhysicsSystem>().SetLinearVelocity(playerEntity, Vector2.Zero, body: physics);
|
||||
|
||||
125
Content.Server/Administration/Managers/CVarControlManager.cs
Normal file
125
Content.Server/Administration/Managers/CVarControlManager.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Content.Shared.CCVar.CVarAccess;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Content.Server.Administration.Managers;
|
||||
|
||||
/// <summary>
|
||||
/// Manages the control of CVars via the <see cref="Content.Shared.CCVar.CVarAccess.CVarControl"/> attribute.
|
||||
/// </summary>
|
||||
public sealed class CVarControlManager : IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
|
||||
[Dependency] private readonly ILogManager _logger = default!;
|
||||
|
||||
private readonly List<ChangableCVar> _changableCvars = new();
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_sawmill = _logger.GetSawmill("cvarcontrol");
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
RegisterCVars();
|
||||
}
|
||||
|
||||
private void RegisterCVars()
|
||||
{
|
||||
if (_changableCvars.Count != 0)
|
||||
{
|
||||
_sawmill.Warning("CVars already registered, overwriting.");
|
||||
_changableCvars.Clear();
|
||||
}
|
||||
|
||||
var validCvarsDefs = _reflectionManager.FindTypesWithAttribute<CVarDefsAttribute>();
|
||||
|
||||
foreach (var type in validCvarsDefs)
|
||||
{
|
||||
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy))
|
||||
{
|
||||
var allowed = field.GetCustomAttribute<CVarControl>();
|
||||
if (allowed == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var cvarDef = (CVarDef)field.GetValue(null)!;
|
||||
_changableCvars.Add(new ChangableCVar(cvarDef.Name, allowed, _localizationManager));
|
||||
}
|
||||
}
|
||||
|
||||
_sawmill.Info($"Registered {_changableCvars.Count} CVars.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all CVars that the player can change.
|
||||
/// </summary>
|
||||
public List<ChangableCVar> GetAllRunnableCvars(IConsoleShell shell)
|
||||
{
|
||||
// Not a player, running as server. We COULD return all cvars,
|
||||
// but a check later down the line will prevent it from anyways. Use the "cvar" command instead.
|
||||
if (shell.Player == null)
|
||||
return [];
|
||||
|
||||
return GetAllRunnableCvars(shell.Player);
|
||||
}
|
||||
|
||||
public List<ChangableCVar> GetAllRunnableCvars(ICommonSession session)
|
||||
{
|
||||
var adminData = _adminManager.GetAdminData(session);
|
||||
if (adminData == null)
|
||||
return []; // Not an admin
|
||||
|
||||
return _changableCvars
|
||||
.Where(cvar => adminData.HasFlag(cvar.Control.AdminFlags))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public ChangableCVar? GetCVar(string name)
|
||||
{
|
||||
return _changableCvars.FirstOrDefault(cvar => cvar.Name == name);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ChangableCVar
|
||||
{
|
||||
private const string LocPrefix = "changecvar";
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
// Holding a reference to the attribute might be skrunkly? Not sure how much mem it eats up.
|
||||
public CVarControl Control { get; }
|
||||
|
||||
public string? ShortHelp;
|
||||
public string? LongHelp;
|
||||
|
||||
public ChangableCVar(string name, CVarControl control, ILocalizationManager loc)
|
||||
{
|
||||
Name = name;
|
||||
Control = control;
|
||||
|
||||
if (loc.TryGetString($"{LocPrefix}-simple-{name.Replace('.', '_')}", out var simple))
|
||||
{
|
||||
ShortHelp = simple;
|
||||
}
|
||||
|
||||
if (loc.TryGetString($"{LocPrefix}-full-{name.Replace('.', '_')}", out var longHelp))
|
||||
{
|
||||
LongHelp = longHelp;
|
||||
}
|
||||
|
||||
// If one is set and the other is not, we throw
|
||||
if (ShortHelp == null && LongHelp != null || ShortHelp != null && LongHelp == null)
|
||||
{
|
||||
throw new InvalidOperationException("Short and long help must both be set or both be null.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Events;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Administration.Systems;
|
||||
|
||||
@@ -11,8 +10,7 @@ namespace Content.Server.Administration.Systems;
|
||||
/// </summary>
|
||||
public sealed class AdminTestArenaSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _map = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _loader = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
|
||||
|
||||
public const string ArenaMapPath = "/Maps/Test/admin_test_arena.yml";
|
||||
@@ -28,26 +26,24 @@ public sealed class AdminTestArenaSystem : EntitySystem
|
||||
{
|
||||
return (arenaMap, arenaGrid);
|
||||
}
|
||||
else
|
||||
{
|
||||
ArenaGrid[admin.UserId] = null;
|
||||
return (arenaMap, null);
|
||||
}
|
||||
}
|
||||
|
||||
ArenaMap[admin.UserId] = _mapManager.GetMapEntityId(_mapManager.CreateMap());
|
||||
_metaDataSystem.SetEntityName(ArenaMap[admin.UserId], $"ATAM-{admin.Name}");
|
||||
var grids = _map.LoadMap(Comp<MapComponent>(ArenaMap[admin.UserId]).MapId, ArenaMapPath);
|
||||
if (grids.Count != 0)
|
||||
{
|
||||
_metaDataSystem.SetEntityName(grids[0], $"ATAG-{admin.Name}");
|
||||
ArenaGrid[admin.UserId] = grids[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
ArenaGrid[admin.UserId] = null;
|
||||
return (arenaMap, null);
|
||||
}
|
||||
|
||||
return (ArenaMap[admin.UserId], ArenaGrid[admin.UserId]);
|
||||
var path = new ResPath(ArenaMapPath);
|
||||
if (!_loader.TryLoadMap(path, out var map, out var grids))
|
||||
throw new Exception($"Failed to load admin arena");
|
||||
|
||||
ArenaMap[admin.UserId] = map.Value.Owner;
|
||||
_metaDataSystem.SetEntityName(map.Value.Owner, $"ATAM-{admin.Name}");
|
||||
|
||||
var grid = grids.FirstOrNull();
|
||||
ArenaGrid[admin.UserId] = grid?.Owner;
|
||||
if (grid != null)
|
||||
_metaDataSystem.SetEntityName(grid.Value.Owner, $"ATAG-{admin.Name}");
|
||||
|
||||
return (map.Value.Owner, grid?.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public sealed partial class AdminVerbSystem
|
||||
var board = Spawn("ChessBoard", xform.Coordinates);
|
||||
var session = _tabletopSystem.EnsureSession(Comp<TabletopGameComponent>(board));
|
||||
xform.Coordinates = EntityCoordinates.FromMap(_mapManager, session.Position);
|
||||
xform.WorldRotation = Angle.Zero;
|
||||
_transformSystem.SetWorldRotationNoLerp((args.Target, xform), Angle.Zero);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = string.Join(": ", chessName, Loc.GetString("admin-smite-chess-dimension-description"))
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Content.Server.Administration.Systems
|
||||
mark.Text = Loc.GetString("toolshed-verb-mark");
|
||||
mark.Message = Loc.GetString("toolshed-verb-mark-description");
|
||||
mark.Category = VerbCategory.Admin;
|
||||
mark.Act = () => _toolshed.InvokeCommand(player, "=> $marked", Enumerable.Repeat(args.Target, 1), out _);
|
||||
mark.Act = () => _toolshed.InvokeCommand(player, "=> $marked", new List<EntityUid> {args.Target}, out _);
|
||||
mark.Impact = LogImpact.Low;
|
||||
args.Verbs.Add(mark);
|
||||
|
||||
@@ -370,7 +370,7 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
}
|
||||
|
||||
if (lawBoundComponent != null && target != null)
|
||||
if (lawBoundComponent != null && target != null && _adminManager.HasAdminFlag(player, AdminFlags.Moderator))
|
||||
{
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
|
||||
@@ -9,8 +9,7 @@ public sealed class MarkedCommand : ToolshedCommand
|
||||
[CommandImplementation]
|
||||
public IEnumerable<EntityUid> Marked(IInvocationContext ctx)
|
||||
{
|
||||
var res = (IEnumerable<EntityUid>?)ctx.ReadVar("marked");
|
||||
res ??= Array.Empty<EntityUid>();
|
||||
return res;
|
||||
var marked = ctx.ReadVar("marked") as IEnumerable<EntityUid>;
|
||||
return marked ?? Array.Empty<EntityUid>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,79 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Chat;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Announcements
|
||||
namespace Content.Server.Announcements;
|
||||
|
||||
[AdminCommand(AdminFlags.Moderator)]
|
||||
public sealed class AnnounceCommand : LocalizedEntityCommands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Moderator)]
|
||||
public sealed class AnnounceCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "announce";
|
||||
public string Description => "Send an in-game announcement.";
|
||||
public string Help => $"{Command} <sender> <message> or {Command} <message> to send announcement as CentCom.";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var chat = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>();
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
|
||||
if (args.Length == 0)
|
||||
public override string Command => "announce";
|
||||
public override string Description => Loc.GetString("cmd-announce-desc");
|
||||
public override string Help => Loc.GetString("cmd-announce-help", ("command", Command));
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
shell.WriteError(Loc.GetString("shell-need-minimum-one-argument"));
|
||||
return;
|
||||
case > 4:
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
var message = args[0];
|
||||
var sender = Loc.GetString("cmd-announce-sender");
|
||||
var color = Color.Gold;
|
||||
var sound = new SoundPathSpecifier("/Audio/Announcements/announce.ogg");
|
||||
|
||||
// Optional sender argument
|
||||
if (args.Length >= 2)
|
||||
sender = args[1];
|
||||
|
||||
// Optional color argument
|
||||
if (args.Length >= 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
shell.WriteError("Not enough arguments! Need at least 1.");
|
||||
color = Color.FromHex(args[2]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-invalid-color-hex"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length == 1)
|
||||
{
|
||||
chat.DispatchGlobalAnnouncement(args[0], colorOverride: Color.Gold);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Explicit IEnumerable<string> due to overload ambiguity on .NET 9
|
||||
var message = string.Join(' ', (IEnumerable<string>)new ArraySegment<string>(args, 1, args.Length-1));
|
||||
chat.DispatchGlobalAnnouncement(message, args[0], colorOverride: Color.Gold);
|
||||
}
|
||||
shell.WriteLine("Sent!");
|
||||
}
|
||||
|
||||
// Optional sound argument
|
||||
if (args.Length >= 4)
|
||||
sound = new SoundPathSpecifier(args[3]);
|
||||
|
||||
_chat.DispatchGlobalAnnouncement(message, sender, true, sound, color);
|
||||
shell.WriteLine(Loc.GetString("shell-command-success"));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return args.Length switch
|
||||
{
|
||||
1 => CompletionResult.FromHint(Loc.GetString("cmd-announce-arg-message")),
|
||||
2 => CompletionResult.FromHint(Loc.GetString("cmd-announce-arg-sender")),
|
||||
3 => CompletionResult.FromHint(Loc.GetString("cmd-announce-arg-color")),
|
||||
4 => CompletionResult.FromHintOptions(
|
||||
CompletionHelper.AudioFilePath(args[3], _proto, _res),
|
||||
Loc.GetString("cmd-announce-arg-sound")
|
||||
),
|
||||
_ => CompletionResult.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.Chat.Managers;
|
||||
using Content.Server.Jittering;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Anomaly;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Effects;
|
||||
@@ -24,8 +23,6 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly AnomalySystem _anomaly = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
|
||||
@@ -10,10 +10,8 @@ using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
@@ -25,11 +23,9 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
||||
[Dependency] private readonly AirAlarmSystem _airAlarmSystem = default!;
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly NavMapSystem _navMapSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly DeviceListSystem _deviceListSystem = default!;
|
||||
|
||||
private const float UpdateTime = 1.0f;
|
||||
|
||||
@@ -61,9 +61,9 @@ public sealed partial class AtmosphereSystem
|
||||
mixtures[5].Temperature = 5000f;
|
||||
|
||||
// 6: (Walk-In) Freezer
|
||||
mixtures[6].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard);
|
||||
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard);
|
||||
mixtures[6].Temperature = 235f; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings
|
||||
mixtures[6].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesFreezer);
|
||||
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesFreezer);
|
||||
mixtures[6].Temperature = Atmospherics.FreezerTemp; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings
|
||||
|
||||
// 7: Nitrogen (101kpa) for vox rooms
|
||||
mixtures[7].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellStandard);
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return;
|
||||
|
||||
// Used by ExperiencePressureDifference to correct push/throw directions from tile-relative to physics world.
|
||||
var gridWorldRotation = xforms.GetComponent(gridAtmosphere).WorldRotation;
|
||||
var gridWorldRotation = _transformSystem.GetWorldRotation(gridAtmosphere);
|
||||
|
||||
// If we're using monstermos, smooth out the yeet direction to follow the flow
|
||||
if (MonstermosEqualization)
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
if (tile.Hotspot.Bypassing)
|
||||
{
|
||||
tile.Hotspot.Volume = tile.Air.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate;
|
||||
tile.Hotspot.Volume = tile.Air.ReactionResults[(byte)GasReaction.Fire] * Atmospherics.FireGrowthRate;
|
||||
tile.Hotspot.Temperature = tile.Air.Temperature;
|
||||
}
|
||||
else
|
||||
@@ -187,7 +187,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
affected.Temperature = tile.Hotspot.Temperature;
|
||||
React(affected, tile);
|
||||
tile.Hotspot.Temperature = affected.Temperature;
|
||||
tile.Hotspot.Volume = affected.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate;
|
||||
tile.Hotspot.Volume = affected.ReactionResults[(byte)GasReaction.Fire] * Atmospherics.FireGrowthRate;
|
||||
Merge(tile.Air, affected);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,12 @@ using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Systems;
|
||||
@@ -37,6 +39,7 @@ namespace Content.Server.Atmos.Monitor.Systems;
|
||||
public sealed class AirAlarmSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AccessReaderSystem _access = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
|
||||
@@ -296,6 +299,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
addr = netConn.Address;
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} mode to {args.Mode}");
|
||||
SetMode(uid, addr, args.Mode, false);
|
||||
}
|
||||
else
|
||||
@@ -307,15 +311,26 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
private void OnUpdateAutoMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAutoModeMessage args)
|
||||
{
|
||||
component.AutoMode = args.Enabled;
|
||||
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} auto mode to {args.Enabled}");
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmThresholdMessage args)
|
||||
{
|
||||
if (AccessCheck(uid, args.Actor, component))
|
||||
{
|
||||
if (args.Gas != null)
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Gas} {args.Type} threshold using {ToPrettyString(uid)}");
|
||||
else
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Type} threshold using {ToPrettyString(uid)}");
|
||||
|
||||
SetThreshold(uid, args.Address, args.Type, args.Threshold, args.Gas);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args)
|
||||
@@ -323,6 +338,8 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
if (AccessCheck(uid, args.Actor, component)
|
||||
&& _deviceList.ExistsInDeviceList(uid, args.Address))
|
||||
{
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} settings using {ToPrettyString(uid)}");
|
||||
|
||||
SetDeviceData(uid, args.Address, args.Data);
|
||||
}
|
||||
else
|
||||
@@ -344,6 +361,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
case GasVentPumpData ventData:
|
||||
foreach (string addr in component.VentData.Keys)
|
||||
{
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to vent {addr}");
|
||||
SetData(uid, addr, args.Data);
|
||||
}
|
||||
break;
|
||||
@@ -351,6 +369,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
case GasVentScrubberData scrubberData:
|
||||
foreach (string addr in component.ScrubberData.Keys)
|
||||
{
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to scrubber {addr}");
|
||||
SetData(uid, addr, args.Data);
|
||||
}
|
||||
break;
|
||||
@@ -379,6 +398,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
if (!_access.IsAllowed(user.Value, uid, reader))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("air-alarm-ui-access-denied"), user.Value, user.Value);
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Low, $"{ToPrettyString(user)} attempted to access {ToPrettyString(uid)} without access");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Piping.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tag;
|
||||
@@ -25,6 +27,7 @@ namespace Content.Server.Atmos.Monitor.Systems;
|
||||
// a danger), and atmos (which triggers based on set thresholds).
|
||||
public sealed class AtmosMonitorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
||||
@@ -393,21 +396,74 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
if (!Resolve(uid, ref monitor))
|
||||
return;
|
||||
|
||||
// Used for logging after the switch statement
|
||||
string logPrefix = "";
|
||||
string logValueSuffix = "";
|
||||
AtmosAlarmThreshold? logPreviousThreshold = null;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case AtmosMonitorThresholdType.Pressure:
|
||||
logPrefix = "pressure";
|
||||
logValueSuffix = "kPa";
|
||||
logPreviousThreshold = monitor.PressureThreshold;
|
||||
|
||||
monitor.PressureThreshold = threshold;
|
||||
break;
|
||||
case AtmosMonitorThresholdType.Temperature:
|
||||
logPrefix = "temperature";
|
||||
logValueSuffix = "K";
|
||||
logPreviousThreshold = monitor.TemperatureThreshold;
|
||||
|
||||
monitor.TemperatureThreshold = threshold;
|
||||
break;
|
||||
case AtmosMonitorThresholdType.Gas:
|
||||
if (gas == null || monitor.GasThresholds == null)
|
||||
return;
|
||||
|
||||
logPrefix = ((Gas) gas).ToString();
|
||||
logValueSuffix = "kPa";
|
||||
monitor.GasThresholds.TryGetValue((Gas) gas, out logPreviousThreshold);
|
||||
|
||||
monitor.GasThresholds[(Gas) gas] = threshold;
|
||||
break;
|
||||
}
|
||||
|
||||
// Admin log each change separately rather than logging the whole state
|
||||
if (logPreviousThreshold != null)
|
||||
{
|
||||
if (threshold.Ignore != logPreviousThreshold.Ignore)
|
||||
{
|
||||
string enabled = threshold.Ignore ? "disabled" : "enabled";
|
||||
_adminLogger.Add(
|
||||
LogType.AtmosDeviceSetting,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(uid)} {logPrefix} thresholds {enabled}"
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var change in threshold.GetChanges(logPreviousThreshold))
|
||||
{
|
||||
if (change.Current.Enabled != change.Previous?.Enabled)
|
||||
{
|
||||
string enabled = change.Current.Enabled ? "enabled" : "disabled";
|
||||
_adminLogger.Add(
|
||||
LogType.AtmosDeviceSetting,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(uid)} {logPrefix} {change.Type} {enabled}"
|
||||
);
|
||||
}
|
||||
|
||||
if (change.Current.Value != change.Previous?.Value)
|
||||
{
|
||||
_adminLogger.Add(
|
||||
LogType.AtmosDeviceSetting,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(uid)} {logPrefix} {change.Type} changed from {change.Previous?.Value} {logValueSuffix} to {change.Current.Value} {logValueSuffix}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,8 +11,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class GasVentPumpComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies if the device is enabled by an air alarm. Does not indicate if the device is powered.
|
||||
/// By default, all air vents start enabled, whether linked to an alarm or not.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Enabled { get; set; } = false;
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsDirty { get; set; } = false;
|
||||
|
||||
@@ -9,8 +9,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
[Access(typeof(GasVentScrubberSystem))]
|
||||
public sealed partial class GasVentScrubberComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies if the device is enabled by an air alarm. Does not indicate if the device is powered.
|
||||
/// By default, all air scrubbers start enabled, whether linked to an alarm or not.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Enabled { get; set; } = false;
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
[DataField]
|
||||
public bool IsDirty { get; set; } = false;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user