Merge branch 'master' into fix-conflict-40263

This commit is contained in:
Errant
2025-09-10 19:52:34 +02:00
committed by GitHub
1274 changed files with 36202 additions and 13752 deletions

3
.github/CODEOWNERS vendored
View File

@@ -26,6 +26,9 @@
/Content.*/Trigger/ @slarticodefast /Content.*/Trigger/ @slarticodefast
/Content.*/Stunnable/ @Princess-Cheeseballs
/Content.*/Nutrition/ @Princess-Cheeseballs
# SKREEEE # SKREEEE
/Content.*.Database/ @PJB3005 @DrSmugleaf /Content.*.Database/ @PJB3005 @DrSmugleaf
/Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @crazybrain23 /Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @crazybrain23

View File

@@ -34,7 +34,7 @@ jobs:
run: dotnet build Content.Packaging --configuration Release --no-restore /m run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server - name: Package server
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64 run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
- name: Package client - name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release run: dotnet run --project Content.Packaging client --no-wipe-release

View File

@@ -41,7 +41,7 @@ jobs:
run: dotnet build Content.Packaging --configuration Release --no-restore /m run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server - name: Package server
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64 run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
- name: Package client - name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release run: dotnet run --project Content.Packaging client --no-wipe-release

View File

@@ -60,7 +60,7 @@ jobs:
run: dotnet build Content.Packaging --configuration Release --no-restore /m run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server - name: Package server
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64 run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
- name: Package client - name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release run: dotnet run --project Content.Packaging client --no-wipe-release

View File

@@ -1,17 +1,19 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Installs git hooks, updates them, updates submodules, that kind of thing. """
Installs git hooks, updates them, updates submodules, that kind of thing.
"""
import subprocess
import sys
import os import os
import shutil import shutil
import subprocess
import sys
import time import time
from pathlib import Path from pathlib import Path
from typing import List from typing import List
SOLUTION_PATH = Path("..") / "SpaceStation14.sln" SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
# If this doesn't match the saved version we overwrite them all. # If this doesn't match the saved version we overwrite them all.
CURRENT_HOOKS_VERSION = "2" CURRENT_HOOKS_VERSION = "3"
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet" QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
@@ -25,12 +27,10 @@ def run_command(command: List[str], capture: bool = False) -> subprocess.Complet
sys.stdout.flush() sys.stdout.flush()
completed = None
if capture: if capture:
completed = subprocess.run(command, cwd="..", stdout=subprocess.PIPE) completed = subprocess.run(command, stdout=subprocess.PIPE, text=True)
else: else:
completed = subprocess.run(command, cwd="..") completed = subprocess.run(command)
if completed.returncode != 0: if completed.returncode != 0:
print("Error: command exited with code {}!".format(completed.returncode)) print("Error: command exited with code {}!".format(completed.returncode))
@@ -43,7 +43,7 @@ def update_submodules():
Updates all submodules. Updates all submodules.
""" """
if ('GITHUB_ACTIONS' in os.environ): if 'GITHUB_ACTIONS' in os.environ:
return return
if os.path.isfile("DISABLE_SUBMODULE_AUTOUPDATE"): if os.path.isfile("DISABLE_SUBMODULE_AUTOUPDATE"):
@@ -76,22 +76,21 @@ def install_hooks():
print("No hooks change detected.") print("No hooks change detected.")
return return
with open("INSTALLED_HOOKS_VERSION", "w") as f:
f.write(CURRENT_HOOKS_VERSION)
print("Hooks need updating.") print("Hooks need updating.")
hooks_target_dir = Path("..")/".git"/"hooks" hooks_target_dir = Path(run_command(["git", "rev-parse", "--git-path", "hooks"], True).stdout.strip())
hooks_source_dir = Path("hooks") hooks_source_dir = Path("hooks")
# Clear entire tree since we need to kill deleted files too. # Clear entire tree since we need to kill deleted files too.
for filename in os.listdir(str(hooks_target_dir)): for filename in os.listdir(hooks_target_dir):
os.remove(str(hooks_target_dir/filename)) os.remove(hooks_target_dir / filename)
for filename in os.listdir(str(hooks_source_dir)): for filename in os.listdir(hooks_source_dir):
print("Copying hook {}".format(filename)) print("Copying hook {}".format(filename))
shutil.copy2(str(hooks_source_dir/filename), shutil.copy2(hooks_source_dir / filename, hooks_target_dir / filename)
str(hooks_target_dir/filename))
with open("INSTALLED_HOOKS_VERSION", "w") as f:
f.write(CURRENT_HOOKS_VERSION)
def reset_solution(): def reset_solution():
@@ -107,8 +106,7 @@ def reset_solution():
def check_for_zip_download(): def check_for_zip_download():
# Check if .git exists, # Check if .git exists,
cur_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) if run_command(["git", "rev-parse"]).returncode != 0:
if not os.path.isdir(os.path.join(cur_dir, ".git")):
print("It appears that you downloaded this repository directly from GitHub. (Using the .zip download option) \n" print("It appears that you downloaded this repository directly from GitHub. (Using the .zip download option) \n"
"When downloading straight from GitHub, it leaves out important information that git needs to function. " "When downloading straight from GitHub, it leaves out important information that git needs to function. "
"Such as information to download the engine or even the ability to even be able to create contributions. \n" "Such as information to download the engine or even the ability to even be able to create contributions. \n"

View File

@@ -1,10 +1,10 @@
#!/bin/bash #!/bin/bash
gitroot=`git rev-parse --show-toplevel` gitroot=$(git rev-parse --show-toplevel)
cd "$gitroot/BuildChecker" cd "$gitroot/BuildChecker" || exit
if [[ `uname` == MINGW* || `uname` == CYGWIN* ]]; then if [[ $(uname) == MINGW* || $(uname) == CYGWIN* ]]; then
# Windows # Windows
py -3 git_helper.py --quiet py -3 git_helper.py --quiet
else else

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# Just call post-checkout since it does the same thing. # Just call post-checkout since it does the same thing.
gitroot=`git rev-parse --show-toplevel` gitroot=$(git rev-parse --git-path hooks)
bash "$gitroot/.git/hooks/post-checkout" bash "$gitroot/post-checkout"

View File

@@ -0,0 +1,174 @@
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos.Components;
using Content.Shared.CCVar;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Benchmarks;
/// <summary>
/// Spawns N number of entities with a <see cref="DeltaPressureComponent"/> and
/// simulates them for a number of ticks M.
/// </summary>
[Virtual]
[GcServer(true)]
//[MemoryDiagnoser]
//[ThreadingDiagnoser]
public class DeltaPressureBenchmark
{
/// <summary>
/// Number of entities (windows, really) to spawn with a <see cref="DeltaPressureComponent"/>.
/// </summary>
[Params(1, 10, 100, 1000, 5000, 10000, 50000, 100000)]
public int EntityCount;
/// <summary>
/// Number of entities that each parallel processing job will handle.
/// </summary>
// [Params(1, 10, 100, 1000, 5000, 10000)] For testing how multithreading parameters affect performance (THESE TESTS TAKE 16+ HOURS TO RUN)
[Params(10)]
public int BatchSize;
/// <summary>
/// Number of entities to process per iteration in the DeltaPressure
/// processing loop.
/// </summary>
// [Params(100, 1000, 5000, 10000, 50000)]
[Params(1000)]
public int EntitiesPerIteration;
private readonly EntProtoId _windowProtoId = "Window";
private readonly EntProtoId _wallProtoId = "WallPlastitaniumIndestructible";
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private SharedMapSystem _map = default!;
private IRobustRandom _random = default!;
private IConfigurationManager _cvar = default!;
private ITileDefinitionManager _tileDefMan = default!;
private AtmosphereSystem _atmospereSystem = default!;
private Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>
_testEnt;
[GlobalSetup]
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
var mapdata = await _pair.CreateTestMap();
_entMan = server.ResolveDependency<IEntityManager>();
_map = _entMan.System<SharedMapSystem>();
_random = server.ResolveDependency<IRobustRandom>();
_cvar = server.ResolveDependency<IConfigurationManager>();
_tileDefMan = server.ResolveDependency<ITileDefinitionManager>();
_atmospereSystem = _entMan.System<AtmosphereSystem>();
_random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
_cvar.SetCVar(CCVars.DeltaPressureParallelToProcessPerIteration, EntitiesPerIteration);
_cvar.SetCVar(CCVars.DeltaPressureParallelBatchSize, BatchSize);
var plating = _tileDefMan["Plating"].TileId;
/*
Basically, we want to have a 5-wide grid of tiles.
Edges are walled, and the length of the grid is determined by N + 2.
Windows should only touch the top and bottom walls, and each other.
*/
var length = EntityCount + 2; // ensures we can spawn exactly N windows between side walls
const int height = 5;
await server.WaitPost(() =>
{
// Fill required tiles (extend grid) with plating
for (var x = 0; x < length; x++)
{
for (var y = 0; y < height; y++)
{
_map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
}
}
// Spawn perimeter walls and windows row in the middle (y = 2)
const int midY = height / 2;
for (var x = 0; x < length; x++)
{
for (var y = 0; y < height; y++)
{
var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);
var isPerimeter = x == 0 || x == length - 1 || y == 0 || y == height - 1;
if (isPerimeter)
{
_entMan.SpawnEntity(_wallProtoId, coords);
continue;
}
// Spawn windows only on the middle row, spanning interior (excluding side walls)
if (y == midY)
{
_entMan.SpawnEntity(_windowProtoId, coords);
}
}
}
});
// Next we run the fixgridatmos command to ensure that we have some air on our grid.
// Wait a little bit as well.
// TODO: Unhardcode command magic string when fixgridatmos is an actual command we can ref and not just
// a stamp-on in AtmosphereSystem.
await _pair.WaitCommand("fixgridatmos " + mapdata.Grid.Owner, 1);
var uid = mapdata.Grid.Owner;
_testEnt = new Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>(
uid,
_entMan.GetComponent<GridAtmosphereComponent>(uid),
_entMan.GetComponent<GasTileOverlayComponent>(uid),
_entMan.GetComponent<MapGridComponent>(uid),
_entMan.GetComponent<TransformComponent>(uid));
}
[Benchmark]
public async Task PerformFullProcess()
{
await _pair.Server.WaitPost(() =>
{
while (!_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure)) { }
});
}
[Benchmark]
public async Task PerformSingleRunProcess()
{
await _pair.Server.WaitPost(() =>
{
_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure);
});
}
[GlobalCleanup]
public async Task CleanupAsync()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
}

View File

@@ -47,7 +47,7 @@ public class MapLoadBenchmark
PoolManager.Shutdown(); PoolManager.Shutdown();
} }
public static readonly string[] MapsSource = { "Empty", "Saltern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"}; public static string[] MapsSource { get; } = { "Empty", "Saltern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"};
[ParamsSource(nameof(MapsSource))] [ParamsSource(nameof(MapsSource))]
public string Map; public string Map;

View File

@@ -29,7 +29,7 @@ namespace Content.Client.Access.UI
foreach (var access in accessLevels) foreach (var access in accessLevels)
{ {
if (!protoManager.TryIndex(access, out var accessLevel)) if (!protoManager.Resolve(access, out var accessLevel))
{ {
continue; continue;
} }

View File

@@ -57,7 +57,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
foreach (var accessGroup in _accessGroups) foreach (var accessGroup in _accessGroups)
{ {
if (!_protoManager.TryIndex(accessGroup, out var accessGroupProto)) if (!_protoManager.Resolve(accessGroup, out var accessGroupProto))
continue; continue;
_groupedAccessLevels.Add(accessGroupProto, new()); _groupedAccessLevels.Add(accessGroupProto, new());
@@ -65,13 +65,13 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
// Ensure that the 'general' access group is added to handle // Ensure that the 'general' access group is added to handle
// misc. access levels that aren't associated with any group // misc. access levels that aren't associated with any group
if (_protoManager.TryIndex(GeneralAccessGroup, out var generalAccessProto)) if (_protoManager.Resolve(GeneralAccessGroup, out var generalAccessProto))
_groupedAccessLevels.TryAdd(generalAccessProto, new()); _groupedAccessLevels.TryAdd(generalAccessProto, new());
// Assign known access levels with their associated groups // Assign known access levels with their associated groups
foreach (var accessLevel in _accessLevels) foreach (var accessLevel in _accessLevels)
{ {
if (!_protoManager.TryIndex(accessLevel, out var accessLevelProto)) if (!_protoManager.Resolve(accessLevel, out var accessLevelProto))
continue; continue;
var assigned = false; var assigned = false;

View File

@@ -4,6 +4,7 @@ using Content.Shared.Access.Systems;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using Content.Shared.CrewManifest; using Content.Shared.CrewManifest;
using Content.Shared.Roles;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using static Content.Shared.Access.Components.IdCardConsoleComponent; using static Content.Shared.Access.Components.IdCardConsoleComponent;
@@ -74,7 +75,7 @@ namespace Content.Client.Access.UI
_window?.UpdateState(castState); _window?.UpdateState(castState);
} }
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, string newJobPrototype) public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, ProtoId<JobPrototype> newJobPrototype)
{ {
if (newFullName.Length > _maxNameLength) if (newFullName.Length > _maxNameLength)
newFullName = newFullName[.._maxNameLength]; newFullName = newFullName[.._maxNameLength];

View File

@@ -123,7 +123,7 @@ namespace Content.Client.Access.UI
foreach (var group in job.AccessGroups) foreach (var group in job.AccessGroups)
{ {
if (!_prototypeManager.TryIndex(group, out AccessGroupPrototype? groupPrototype)) if (!_prototypeManager.Resolve(group, out AccessGroupPrototype? groupPrototype))
{ {
continue; continue;
} }

View File

@@ -316,8 +316,9 @@ public sealed partial class BanPanel : DefaultWindow
}; };
// This is adding the icon before the role name // This is adding the icon before the role name
// Yeah, this is sus, but having to split the functions up and stuff is worse imo. // TODO: This should not be using raw strings for prototypes as it means it won't be validated at all.
if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype) && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto)) // I know the ban manager is doing the same thing, but that should not leak into UI code.
if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype) && _protoMan.Resolve(jobPrototype.Icon, out var iconProto))
{ {
var jobIconTexture = new TextureRect var jobIconTexture = new TextureRect
{ {

View File

@@ -0,0 +1,40 @@
using Robust.Client.Graphics;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.Anomaly;
/// <summary>
/// This component creates and handles the drawing of a ScreenTexture to be used on the Anomaly Scanner
/// for an indicator of Anomaly Severity.
/// </summary>
/// <remarks>
/// In the future I would like to make this a more generic "DynamicTextureComponent" that can contain a dictionary
/// of texture components like "Bar(offset, size, minimumValue, maximumValue, AppearanceKey, LayerMapKey)" that can
/// just draw a bar or other basic drawn element that will show up on a texture layer.
/// </remarks>
[RegisterComponent]
[Access(typeof(AnomalyScannerSystem))]
public sealed partial class AnomalyScannerScreenComponent : Component
{
/// <summary>
/// This is the texture drawn as a layer on the Anomaly Scanner device.
/// </summary>
public OwnedTexture? ScreenTexture;
/// <summary>
/// A small buffer that we can reuse to draw the severity bar.
/// </summary>
public Rgba32[]? BarBuf;
/// <summary>
/// The position of the top-left of the severity bar in pixels.
/// </summary>
[DataField(readOnly: true)]
public Vector2i Offset = new Vector2i(12, 17);
/// <summary>
/// The width and height of the severity bar in pixels.
/// </summary>
[DataField(readOnly: true)]
public Vector2i Size = new Vector2i(10, 3);
}

View File

@@ -0,0 +1,110 @@
using System.Numerics;
using Content.Shared.Anomaly;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.Anomaly;
/// <inheritdoc cref="SharedAnomalyScannerSystem"/>
public sealed class AnomalyScannerSystem : SharedAnomalyScannerSystem
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
private const float MaxHueDegrees = 360f;
private const float GreenHueDegrees = 110f;
private const float RedHueDegrees = 0f;
private const float GreenHue = GreenHueDegrees / MaxHueDegrees;
private const float RedHue = RedHueDegrees / MaxHueDegrees;
// Just an array to initialize the pixels of a new OwnedTexture
private static readonly Rgba32[] EmptyTexture = new Rgba32[32*32];
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AnomalyScannerScreenComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<AnomalyScannerScreenComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<AnomalyScannerScreenComponent, AppearanceChangeEvent>(OnScannerAppearanceChanged);
}
private void OnComponentInit(Entity<AnomalyScannerScreenComponent> ent, ref ComponentInit args)
{
if(!_sprite.TryGetLayer(ent.Owner, AnomalyScannerVisualLayers.Base, out var layer, true))
return;
// Allocate the OwnedTexture
ent.Comp.ScreenTexture = _clyde.CreateBlankTexture<Rgba32>(layer.PixelSize);
if (layer.PixelSize.X < ent.Comp.Offset.X + ent.Comp.Size.X ||
layer.PixelSize.Y < ent.Comp.Offset.Y + ent.Comp.Size.Y)
{
// If the bar doesn't fit, just bail here, ScreenTexture and BarBuf will remain null, and appearance updates
// will do nothing.
DebugTools.Assert(false, "AnomalyScannerScreenComponent: Bar does not fit within sprite");
return;
}
// Initialize the texture
ent.Comp.ScreenTexture.SetSubImage((0, 0), layer.PixelSize, new ReadOnlySpan<Rgba32>(EmptyTexture));
// Initialize bar drawing buffer
ent.Comp.BarBuf = new Rgba32[ent.Comp.Size.X * ent.Comp.Size.Y];
}
private void OnComponentStartup(Entity<AnomalyScannerScreenComponent> ent, ref ComponentStartup args)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
_sprite.LayerSetTexture((ent, sprite), AnomalyScannerVisualLayers.Screen, ent.Comp.ScreenTexture);
}
private void OnScannerAppearanceChanged(Entity<AnomalyScannerScreenComponent> ent, ref AppearanceChangeEvent args)
{
if (args.Sprite is null || ent.Comp.ScreenTexture is null || ent.Comp.BarBuf is null)
return;
args.AppearanceData.TryGetValue(AnomalyScannerVisuals.AnomalySeverity, out var severityObj);
if (severityObj is not float severity)
severity = 0;
// Get the bar length
var barLength = (int)(severity * ent.Comp.Size.X);
// Calculate the bar color
// Hue "angle" of two colors to interpolate between depending on severity
// Just a lerp from Green hue at severity = 0.5 to Red hue at 1.0
var hue = Math.Clamp(2*GreenHue * (1 - severity), RedHue, GreenHue);
var color = new Rgba32(Color.FromHsv(new Vector4(hue, 1f, 1f, 1f)).RGBA);
var transparent = new Rgba32(0, 0, 0, 255);
for(var y = 0; y < ent.Comp.Size.Y; y++)
{
for (var x = 0; x < ent.Comp.Size.X; x++)
{
ent.Comp.BarBuf[y*ent.Comp.Size.X + x] = x < barLength ? color : transparent;
}
}
// Copy the buffer to the texture
try
{
ent.Comp.ScreenTexture.SetSubImage(
ent.Comp.Offset,
ent.Comp.Size,
new ReadOnlySpan<Rgba32>(ent.Comp.BarBuf)
);
}
catch (IndexOutOfRangeException)
{
Log.Warning($"Bar dimensions out of bounds with the texture on entity {ent.Owner}");
}
}
}

View File

@@ -7,7 +7,7 @@ using Robust.Shared.Timing;
namespace Content.Client.Anomaly; namespace Content.Client.Anomaly;
public sealed class AnomalySystem : SharedAnomalySystem public sealed partial class AnomalySystem : SharedAnomalySystem
{ {
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly FloatingVisualizerSystem _floating = default!; [Dependency] private readonly FloatingVisualizerSystem _floating = default!;
@@ -24,6 +24,7 @@ public sealed class AnomalySystem : SharedAnomalySystem
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentShutdown>(OnShutdown); SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentShutdown>(OnShutdown);
} }
private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args) private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args)
{ {
_floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime); _floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime);

View File

@@ -134,7 +134,7 @@ public sealed class AlignAtmosPipeLayers : SnapgridCenter
var newProtoId = altPrototypes[(int)layer]; var newProtoId = altPrototypes[(int)layer];
if (!_protoManager.TryIndex(newProtoId, out var newProto)) if (!_protoManager.Resolve(newProtoId, out var newProto))
return; return;
if (newProto.Type != ConstructionType.Structure) if (newProto.Type != ConstructionType.Structure)

View File

@@ -19,6 +19,7 @@ namespace Content.Client.Atmos.EntitySystems
[Dependency] private readonly SharedTransformSystem _xformSys = default!; [Dependency] private readonly SharedTransformSystem _xformSys = default!;
private GasTileOverlay _overlay = default!; private GasTileOverlay _overlay = default!;
private GasTileHeatOverlay _heatOverlay = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -28,12 +29,16 @@ namespace Content.Client.Atmos.EntitySystems
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys); _overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys);
_overlayMan.AddOverlay(_overlay); _overlayMan.AddOverlay(_overlay);
_heatOverlay = new GasTileHeatOverlay();
_overlayMan.AddOverlay(_heatOverlay);
} }
public override void Shutdown() public override void Shutdown()
{ {
base.Shutdown(); base.Shutdown();
_overlayMan.RemoveOverlay<GasTileOverlay>(); _overlayMan.RemoveOverlay<GasTileOverlay>();
_overlayMan.RemoveOverlay<GasTileHeatOverlay>();
} }
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args) private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)

View File

@@ -0,0 +1,210 @@
using System.Numerics;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Client.Atmos.EntitySystems;
using Content.Shared.CCVar;
using Robust.Client.Graphics;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
namespace Content.Client.Atmos.Overlays;
public sealed class GasTileHeatOverlay : Overlay
{
public override bool RequestScreenTexture { get; set; } = true;
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
private static readonly ProtoId<ShaderPrototype> HeatOverlayShader = "Heat";
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
// We can't resolve this immediately, because it's an entitysystem, but we will attempt to resolve and cache this
// once we begin to draw.
private GasTileOverlaySystem? _gasTileOverlay;
private readonly SharedTransformSystem _xformSys;
private IRenderTexture? _heatTarget;
private IRenderTexture? _heatBlurTarget;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
public GasTileHeatOverlay()
{
IoCManager.InjectDependencies(this);
_xformSys = _entManager.System<SharedTransformSystem>();
_shader = _proto.Index(HeatOverlayShader).InstanceUnique();
_configManager.OnValueChanged(CCVars.ReducedMotion, SetReducedMotion, invokeImmediately: true);
}
private void SetReducedMotion(bool reducedMotion)
{
_shader.SetParameter("strength_scale", reducedMotion ? 0.5f : 1f);
_shader.SetParameter("speed_scale", reducedMotion ? 0.25f : 1f);
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (args.MapId == MapId.Nullspace)
return false;
// If we haven't resolved this yet, give it a try or bail
_gasTileOverlay ??= _entManager.System<GasTileOverlaySystem>();
if (_gasTileOverlay == null)
return false;
var target = args.Viewport.RenderTarget;
// Probably the resolution of the game window changed, remake the textures.
if (_heatTarget?.Texture.Size != target.Size)
{
_heatTarget?.Dispose();
_heatTarget = _clyde.CreateRenderTarget(
target.Size,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
name: nameof(GasTileHeatOverlay));
}
if (_heatBlurTarget?.Texture.Size != target.Size)
{
_heatBlurTarget?.Dispose();
_heatBlurTarget = _clyde.CreateRenderTarget(
target.Size,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
name: $"{nameof(GasTileHeatOverlay)}-blur");
}
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
args.WorldHandle.UseShader(_proto.Index(UnshadedShader).Instance());
var mapId = args.MapId;
var worldAABB = args.WorldAABB;
var worldBounds = args.WorldBounds;
var worldHandle = args.WorldHandle;
var worldToViewportLocal = args.Viewport.GetWorldToLocalMatrix();
// If there is no distortion after checking all visible tiles, we can bail early
var anyDistortion = false;
// We're rendering in the context of the heat target texture, which will encode data as to where and how strong
// the heat distortion will be
args.WorldHandle.RenderInRenderTarget(_heatTarget,
() =>
{
List<Entity<MapGridComponent>> grids = new();
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref grids);
foreach (var grid in grids)
{
if (!overlayQuery.TryGetComponent(grid.Owner, out var comp))
continue;
var gridEntToWorld = _xformSys.GetWorldMatrix(grid.Owner);
var gridEntToViewportLocal = gridEntToWorld * worldToViewportLocal;
if (!Matrix3x2.Invert(gridEntToViewportLocal, out var viewportLocalToGridEnt))
continue;
var uvToUi = Matrix3Helpers.CreateScale(_heatTarget.Size.X, -_heatTarget.Size.Y);
var uvToGridEnt = uvToUi * viewportLocalToGridEnt;
// Because we want the actual distortion to be calculated based on the grid coordinates*, we need
// to pass a matrix transformation to go from the viewport coordinates to grid coordinates.
// * (why? because otherwise the effect would shimmer like crazy as you moved around, think
// moving a piece of warped glass above a picture instead of placing the warped glass on the
// paper and moving them together)
_shader.SetParameter("grid_ent_from_viewport_local", uvToGridEnt);
// Draw commands (like DrawRect) will be using grid coordinates from here
worldHandle.SetTransform(gridEntToViewportLocal);
// We only care about tiles that fit in these bounds
var floatBounds = worldToViewportLocal.TransformBox(worldBounds).Enlarged(grid.Comp.TileSize);
var localBounds = new Box2i(
(int)MathF.Floor(floatBounds.Left),
(int)MathF.Floor(floatBounds.Bottom),
(int)MathF.Ceiling(floatBounds.Right),
(int)MathF.Ceiling(floatBounds.Top));
// for each tile and its gas --->
foreach (var chunk in comp.Chunks.Values)
{
var enumerator = new GasChunkEnumerator(chunk);
while (enumerator.MoveNext(out var tileGas))
{
// --->
// Check and make sure the tile is within the viewport/screen
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
if (!localBounds.Contains(tilePosition))
continue;
// Get the distortion strength from the temperature and bail if it's not hot enough
var strength = _gasTileOverlay.GetHeatDistortionStrength(tileGas.Temperature);
if (strength <= 0f)
continue;
anyDistortion = true;
// Encode the strength in the red channel, then 1.0 alpha if it's an active tile.
// BlurRenderTarget will then apply a blur around the edge, but we don't want it to bleed
// past the tile.
// So we use this alpha channel to chop the lower alpha values off in the shader to fit a
// fit mask back into the tile.
worldHandle.DrawRect(
Box2.CenteredAround(tilePosition + new Vector2(0.5f, 0.5f), grid.Comp.TileSizeVector),
new Color(strength,0f, 0f, strength > 0f ? 1.0f : 0f));
}
}
}
},
// This clears the buffer to all zero first...
new Color(0, 0, 0, 0));
// no distortion, no need to render
if (!anyDistortion)
{
// Return the draw handle to normal settings
args.WorldHandle.UseShader(null);
args.WorldHandle.SetTransform(Matrix3x2.Identity);
return false;
}
// Clear to draw
return true;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture is null || _heatTarget is null || _heatBlurTarget is null)
return;
// Blur to soften the edges of the distortion. the lower parts of the alpha channel need to get cut off in the
// distortion shader to keep them in tile bounds.
_clyde.BlurRenderTarget(args.Viewport, _heatTarget, _heatBlurTarget, args.Viewport.Eye!, 14f);
// Set up and render the distortion
_shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
args.WorldHandle.UseShader(_shader);
args.WorldHandle.DrawTextureRect(_heatTarget.Texture, args.WorldBounds);
// Return the draw handle to normal settings
args.WorldHandle.UseShader(null);
args.WorldHandle.SetTransform(Matrix3x2.Identity);
}
protected override void DisposeBehavior()
{
_heatTarget = null;
_heatBlurTarget = null;
_configManager.UnsubValueChanged(CCVars.ReducedMotion, SetReducedMotion);
base.DisposeBehavior();
}
}

View File

@@ -208,7 +208,7 @@ namespace Content.Client.Atmos.UI
}); });
presBox.AddChild(new Label presBox.AddChild(new Label
{ {
Text = Loc.GetString("gas-analyzer-window-pressure-val-text", ("pressure", $"{gasMix.Pressure:0.##}")), Text = Loc.GetString("gas-analyzer-window-pressure-val-text", ("pressure", $"{gasMix.Pressure:0.00}")),
Align = Label.AlignMode.Right, Align = Label.AlignMode.Right,
HorizontalExpand = true HorizontalExpand = true
}); });
@@ -232,8 +232,8 @@ namespace Content.Client.Atmos.UI
tempBox.AddChild(new Label tempBox.AddChild(new Label
{ {
Text = Loc.GetString("gas-analyzer-window-temperature-val-text", Text = Loc.GetString("gas-analyzer-window-temperature-val-text",
("tempK", $"{gasMix.Temperature:0.#}"), ("tempK", $"{gasMix.Temperature:0.0}"),
("tempC", $"{TemperatureHelpers.KelvinToCelsius(gasMix.Temperature):0.#}")), ("tempC", $"{TemperatureHelpers.KelvinToCelsius(gasMix.Temperature):0.0}")),
Align = Label.AlignMode.Right, Align = Label.AlignMode.Right,
HorizontalExpand = true HorizontalExpand = true
}); });

View File

@@ -58,7 +58,7 @@ public sealed class JukeboxBoundUserInterface : BoundUserInterface
_menu.SetAudioStream(jukebox.AudioStream); _menu.SetAudioStream(jukebox.AudioStream);
if (_protoManager.TryIndex(jukebox.SelectedSongId, out var songProto)) if (_protoManager.Resolve(jukebox.SelectedSongId, out var songProto))
{ {
var length = EntMan.System<AudioSystem>().GetAudioLength(songProto.Path.Path.ToString()); var length = EntMan.System<AudioSystem>().GetAudioLength(songProto.Path.Path.ToString());
_menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds); _menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds);

View File

@@ -39,7 +39,7 @@ public sealed class BarSignSystem : VisualizerSystem<BarSignComponent>
if (powered if (powered
&& sign.Current != null && sign.Current != null
&& _prototypeManager.TryIndex(sign.Current, out var proto)) && _prototypeManager.Resolve(sign.Current, out var proto))
{ {
SpriteSystem.LayerSetSprite((id, sprite), 0, proto.Icon); SpriteSystem.LayerSetSprite((id, sprite), 0, proto.Icon);
sprite.LayerSetShader(0, "unshaded"); sprite.LayerSetShader(0, "unshaded");

View File

@@ -35,7 +35,7 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
public void Update(ProtoId<BarSignPrototype>? sign) public void Update(ProtoId<BarSignPrototype>? sign)
{ {
if (_prototype.TryIndex(sign, out var signPrototype)) if (_prototype.Resolve(sign, out var signPrototype))
_menu?.UpdateState(signPrototype); _menu?.UpdateState(signPrototype);
} }

View File

@@ -29,7 +29,7 @@ public sealed partial class BountyEntry : BoxContainer
UntilNextSkip = untilNextSkip; UntilNextSkip = untilNextSkip;
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype)) if (!_prototype.Resolve<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
return; return;
var items = new List<string>(); var items = new List<string>();

View File

@@ -19,7 +19,7 @@ public sealed partial class BountyHistoryEntry : BoxContainer
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype)) if (!_prototype.Resolve(bounty.Bounty, out var bountyPrototype))
return; return;
var items = new List<string>(); var items = new List<string>();

View File

@@ -1,4 +1,7 @@
using Content.Shared.Changeling.Systems; using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Changeling.Components;
using Content.Shared.Changeling.Systems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
@@ -7,28 +10,58 @@ namespace Content.Client.Changeling.UI;
[UsedImplicitly] [UsedImplicitly]
public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{ {
private ChangelingTransformMenu? _window; private SimpleRadialMenu? _menu;
private static readonly Color SelectedOptionBackground = StyleNano.ButtonColorGoodDefault.WithAlpha(128);
private static readonly Color SelectedOptionHoverBackground = StyleNano.ButtonColorGoodHovered.WithAlpha(128);
protected override void Open() protected override void Open()
{ {
base.Open(); base.Open();
_window = this.CreateWindow<ChangelingTransformMenu>(); _menu = this.CreateWindow<SimpleRadialMenu>();
Update();
_window.OnIdentitySelect += SendIdentitySelect; _menu.OpenOverMouseScreenPosition();
_window.Update(Owner);
} }
public override void Update() public override void Update()
{ {
if (_window == null) if (_menu == null)
return; return;
_window.Update(Owner); if (!EntMan.TryGetComponent<ChangelingIdentityComponent>(Owner, out var lingIdentity))
return;
var models = ConvertToButtons(lingIdentity.ConsumedIdentities, lingIdentity?.CurrentIdentity);
_menu.SetButtons(models);
} }
public void SendIdentitySelect(NetEntity identityId) private IEnumerable<RadialMenuOptionBase> ConvertToButtons(
IEnumerable<EntityUid> identities,
EntityUid? currentIdentity
)
{
var buttons = new List<RadialMenuOptionBase>();
foreach (var identity in identities)
{
if (!EntMan.TryGetComponent<MetaDataComponent>(identity, out var metadata))
continue;
var option = new RadialMenuActionOption<NetEntity>(SendIdentitySelect, EntMan.GetNetEntity(identity))
{
IconSpecifier = RadialMenuIconSpecifier.With(identity),
ToolTip = metadata.EntityName,
BackgroundColor = (currentIdentity == identity) ? SelectedOptionBackground : null,
HoverBackgroundColor = (currentIdentity == identity) ? SelectedOptionHoverBackground : null
};
buttons.Add(option);
}
return buttons;
}
private void SendIdentitySelect(NetEntity identityId)
{ {
SendPredictedMessage(new ChangelingTransformIdentitySelectMessage(identityId)); SendPredictedMessage(new ChangelingTransformIdentitySelectMessage(identityId));
} }

View File

@@ -1,8 +0,0 @@
<ui:RadialMenu
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True">
<ui:RadialContainer Name="Main">
</ui:RadialContainer>
</ui:RadialMenu>

View File

@@ -1,62 +0,0 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Changeling.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Changeling.UI;
[GenerateTypedNameReferences]
public sealed partial class ChangelingTransformMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entity = default!;
public event Action<NetEntity>? OnIdentitySelect;
public ChangelingTransformMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void Update(EntityUid uid)
{
Main.DisposeAllChildren();
if (!_entity.TryGetComponent<ChangelingIdentityComponent>(uid, out var identityComp))
return;
foreach (var identityUid in identityComp.ConsumedIdentities)
{
if (!_entity.TryGetComponent<MetaDataComponent>(identityUid, out var metadata))
continue;
var identityName = metadata.EntityName;
var button = new ChangelingTransformMenuButton()
{
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64, 64),
ToolTip = identityName,
};
var entView = new SpriteView()
{
SetSize = new Vector2(48, 48),
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Stretch = SpriteView.StretchMode.Fill,
};
entView.SetEntity(identityUid);
button.OnButtonUp += _ =>
{
OnIdentitySelect?.Invoke(_entity.GetNetEntity(identityUid));
Close();
};
button.AddChild(entView);
Main.AddChild(button);
}
}
}
public sealed class ChangelingTransformMenuButton : RadialMenuTextureButtonWithSector;

View File

@@ -27,7 +27,7 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
if (overrideIndicator != null) if (overrideIndicator != null)
currentTypingIndicator = overrideIndicator.Value; currentTypingIndicator = overrideIndicator.Value;
if (!_prototypeManager.TryIndex(currentTypingIndicator, out var proto)) if (!_prototypeManager.Resolve(currentTypingIndicator, out var proto))
{ {
Log.Error($"Unknown typing indicator id: {component.TypingIndicatorPrototype}"); Log.Error($"Unknown typing indicator id: {component.TypingIndicatorPrototype}");
return; return;

View File

@@ -2,7 +2,6 @@ using Content.Client.Chemistry.UI;
using Content.Client.Items; using Content.Client.Items;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.GameStates;
namespace Content.Client.Chemistry.EntitySystems; namespace Content.Client.Chemistry.EntitySystems;
@@ -11,6 +10,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
Subs.ItemStatus<InjectorComponent>(ent => new InjectorStatusControl(ent, SolutionContainers));
Subs.ItemStatus<InjectorComponent>(ent => new InjectorStatusControl(ent, SolutionContainer));
} }
} }

View File

@@ -38,13 +38,13 @@ public sealed class InjectorStatusControl : Control
// only updates the UI if any of the details are different than they previously were // only updates the UI if any of the details are different than they previously were
if (PrevVolume == solution.Volume if (PrevVolume == solution.Volume
&& PrevMaxVolume == solution.MaxVolume && PrevMaxVolume == solution.MaxVolume
&& PrevTransferAmount == _parent.Comp.TransferAmount && PrevTransferAmount == _parent.Comp.CurrentTransferAmount
&& PrevToggleState == _parent.Comp.ToggleState) && PrevToggleState == _parent.Comp.ToggleState)
return; return;
PrevVolume = solution.Volume; PrevVolume = solution.Volume;
PrevMaxVolume = solution.MaxVolume; PrevMaxVolume = solution.MaxVolume;
PrevTransferAmount = _parent.Comp.TransferAmount; PrevTransferAmount = _parent.Comp.CurrentTransferAmount;
PrevToggleState = _parent.Comp.ToggleState; PrevToggleState = _parent.Comp.ToggleState;
// Update current volume and injector state // Update current volume and injector state
@@ -59,6 +59,6 @@ public sealed class InjectorStatusControl : Control
("currentVolume", solution.Volume), ("currentVolume", solution.Volume),
("totalVolume", solution.MaxVolume), ("totalVolume", solution.MaxVolume),
("modeString", modeStringLocalized), ("modeString", modeStringLocalized),
("transferVolume", _parent.Comp.TransferAmount))); ("transferVolume", _parent.Comp.CurrentTransferAmount)));
} }
} }

View File

@@ -1,12 +1,10 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Numerics;
using Content.Client.DisplacementMap; using Content.Client.DisplacementMap;
using Content.Client.Inventory; using Content.Client.Inventory;
using Content.Shared.Clothing; using Content.Shared.Clothing;
using Content.Shared.Clothing.Components; using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems; using Content.Shared.Clothing.EntitySystems;
using Content.Shared.DisplacementMap;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
@@ -14,7 +12,6 @@ using Content.Shared.Item;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using static Robust.Client.GameObjects.SpriteComponent; using static Robust.Client.GameObjects.SpriteComponent;
@@ -177,6 +174,7 @@ public sealed class ClientClothingSystem : ClothingSystem
var layer = new PrototypeLayerData(); var layer = new PrototypeLayerData();
layer.RsiPath = rsi.Path.ToString(); layer.RsiPath = rsi.Path.ToString();
layer.State = state; layer.State = state;
layer.Scale = clothing.Scale;
layers = new() { layer }; layers = new() { layer };
return true; return true;

View File

@@ -45,7 +45,7 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
var newTargets = new List<EntProtoId>(); var newTargets = new List<EntProtoId>();
foreach (var target in targets) foreach (var target in targets)
{ {
if (string.IsNullOrEmpty(target) || !_proto.TryIndex(target, out EntityPrototype? proto)) if (string.IsNullOrEmpty(target) || !_proto.Resolve(target, out EntityPrototype? proto))
continue; continue;
if (!proto.TryGetComponent(out TagComponent? tag, EntMan.ComponentFactory) || !_tag.HasTag(tag, st.RequiredTag)) if (!proto.TryGetComponent(out TagComponent? tag, EntMan.ComponentFactory) || !_tag.HasTag(tag, st.RequiredTag))

View File

@@ -54,7 +54,7 @@ public sealed partial class ChameleonMenu : DefaultWindow
foreach (var id in _possibleIds) foreach (var id in _possibleIds)
{ {
if (!_prototypeManager.TryIndex(id, out EntityPrototype? proto)) if (!_prototypeManager.Resolve(id, out EntityPrototype? proto))
continue; continue;
var lowId = id.Id.ToLowerInvariant(); var lowId = id.Id.ToLowerInvariant();

View File

@@ -80,7 +80,7 @@ namespace Content.Client.Construction
{ {
foreach (var constructionProto in PrototypeManager.EnumeratePrototypes<ConstructionPrototype>()) foreach (var constructionProto in PrototypeManager.EnumeratePrototypes<ConstructionPrototype>())
{ {
if (!PrototypeManager.TryIndex(constructionProto.Graph, out var graphProto)) if (!PrototypeManager.Resolve(constructionProto.Graph, out var graphProto))
continue; continue;
if (constructionProto.TargetNode is not { } targetNodeId) if (constructionProto.TargetNode is not { } targetNodeId)
@@ -121,17 +121,14 @@ namespace Content.Client.Construction
// If we got the id of the prototype, we exit the “recursion” by clearing the stack. // If we got the id of the prototype, we exit the “recursion” by clearing the stack.
stack.Clear(); stack.Clear();
if (!PrototypeManager.TryIndex(constructionProto.ID, out ConstructionPrototype? recipe)) if (!PrototypeManager.Resolve(entityId, out var proto))
continue; continue;
if (!PrototypeManager.TryIndex(entityId, out var proto)) var name = constructionProto.SetName.HasValue ? Loc.GetString(constructionProto.SetName) : proto.Name;
continue; var desc = constructionProto.SetDescription.HasValue ? Loc.GetString(constructionProto.SetDescription) : proto.Description;
var name = recipe.SetName.HasValue ? Loc.GetString(recipe.SetName) : proto.Name; constructionProto.Name = name;
var desc = recipe.SetDescription.HasValue ? Loc.GetString(recipe.SetDescription) : proto.Description; constructionProto.Description = desc;
recipe.Name = name;
recipe.Description = desc;
_recipesMetadataCache.Add(constructionProto.ID, entityId); _recipesMetadataCache.Add(constructionProto.ID, entityId);
} while (stack.Count > 0); } while (stack.Count > 0);
@@ -172,7 +169,7 @@ namespace Content.Client.Construction
"construction-ghost-examine-message", "construction-ghost-examine-message",
("name", component.Prototype.Name))); ("name", component.Prototype.Name)));
if (!PrototypeManager.TryIndex(component.Prototype.Graph, out var graph)) if (!PrototypeManager.Resolve(component.Prototype.Graph, out var graph))
return; return;
var startNode = graph.Nodes[component.Prototype.StartNode]; var startNode = graph.Nodes[component.Prototype.StartNode];

View File

@@ -510,7 +510,7 @@ namespace Content.Client.Construction.UI
foreach (var id in favorites) foreach (var id in favorites)
{ {
if (_prototypeManager.TryIndex(id, out ConstructionPrototype? recipe, logError: false)) if (_prototypeManager.TryIndex(id, out ConstructionPrototype? recipe))
_favoritedRecipes.Add(recipe); _favoritedRecipes.Add(recipe);
} }

View File

@@ -150,7 +150,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
// If the damage container on our entity's DamageableComponent // If the damage container on our entity's DamageableComponent
// is not null, we can try to check through its groups. // is not null, we can try to check through its groups.
if (damageComponent.DamageContainerID != null if (damageComponent.DamageContainerID != null
&& _prototypeManager.TryIndex<DamageContainerPrototype>(damageComponent.DamageContainerID, out var damageContainer)) && _prototypeManager.Resolve<DamageContainerPrototype>(damageComponent.DamageContainerID, out var damageContainer))
{ {
// Are we using damage overlay sprites by group? // Are we using damage overlay sprites by group?
// Check if the container matches the supported groups, // Check if the container matches the supported groups,

View File

@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.DisplacementMap; using Content.Shared.DisplacementMap;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -10,6 +11,11 @@ public sealed class DisplacementMapSystem : EntitySystem
[Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly SpriteSystem _sprite = default!; [Dependency] private readonly SpriteSystem _sprite = default!;
private static string? BuildDisplacementLayerKey(object key)
{
return key.ToString() is null ? null : $"{key}-displacement";
}
/// <summary> /// <summary>
/// Attempting to apply a displacement map to a specific layer of SpriteComponent /// Attempting to apply a displacement map to a specific layer of SpriteComponent
/// </summary> /// </summary>
@@ -19,21 +25,22 @@ public sealed class DisplacementMapSystem : EntitySystem
/// <param name="key">Unique layer key, which will determine which layer to apply displacement map to</param> /// <param name="key">Unique layer key, which will determine which layer to apply displacement map to</param>
/// <param name="displacementKey">The key of the new displacement map layer added by this function.</param> /// <param name="displacementKey">The key of the new displacement map layer added by this function.</param>
/// <returns></returns> /// <returns></returns>
public bool TryAddDisplacement(DisplacementData data, public bool TryAddDisplacement(
DisplacementData data,
Entity<SpriteComponent> sprite, Entity<SpriteComponent> sprite,
int index, int index,
object key, object key,
out string displacementKey) [NotNullWhen(true)] out string? displacementKey
)
{ {
displacementKey = $"{key}-displacement"; displacementKey = BuildDisplacementLayerKey(key);
if (displacementKey is null)
if (key.ToString() is null)
return false; return false;
if (data.ShaderOverride != null) EnsureDisplacementIsNotOnSprite(sprite, key);
sprite.Comp.LayerSetShader(index, data.ShaderOverride);
_sprite.RemoveLayer(sprite.AsNullable(), displacementKey, false); if (data.ShaderOverride is not null)
sprite.Comp.LayerSetShader(index, data.ShaderOverride);
//allows you not to write it every time in the YML //allows you not to write it every time in the YML
foreach (var pair in data.SizeMaps) foreach (var pair in data.SizeMaps)
@@ -70,7 +77,11 @@ public sealed class DisplacementMapSystem : EntitySystem
} }
var displacementLayer = _serialization.CreateCopy(displacementDataLayer, notNullableOverride: true); var displacementLayer = _serialization.CreateCopy(displacementDataLayer, notNullableOverride: true);
displacementLayer.CopyToShaderParameters!.LayerKey = key.ToString() ?? "this is impossible";
// This previously assigned a string reading "this is impossible" if key.ToString eval'd to false.
// However, for the sake of sanity, we've changed this to assert non-null - !.
// If this throws an error, we're not sorry. Nanotrasen thanks you for your service fixing this bug.
displacementLayer.CopyToShaderParameters!.LayerKey = key.ToString()!;
_sprite.AddLayer(sprite.AsNullable(), displacementLayer, index); _sprite.AddLayer(sprite.AsNullable(), displacementLayer, index);
_sprite.LayerMapSet(sprite.AsNullable(), displacementKey, index); _sprite.LayerMapSet(sprite.AsNullable(), displacementKey, index);
@@ -78,14 +89,18 @@ public sealed class DisplacementMapSystem : EntitySystem
return true; return true;
} }
/// <inheritdoc cref="TryAddDisplacement"/> /// <summary>
[Obsolete("Use the Entity<SpriteComponent> overload")] /// Ensures that the displacement map associated with the given layer key is not in the Sprite's LayerMap.
public bool TryAddDisplacement(DisplacementData data, /// </summary>
SpriteComponent sprite, /// <param name="sprite">The sprite to remove the displacement layer from.</param>
int index, /// <param name="key">The key of the layer that is referenced by the displacement layer we want to remove.</param>
object key, /// <param name="logMissing">Whether to report an error if the displacement map isn't on the sprite.</param>
out string displacementKey) public void EnsureDisplacementIsNotOnSprite(Entity<SpriteComponent> sprite, object key)
{ {
return TryAddDisplacement(data, (sprite.Owner, sprite), index, key, out displacementKey); var displacementLayerKey = BuildDisplacementLayerKey(key);
if (displacementLayerKey is null)
return;
_sprite.RemoveLayer(sprite.AsNullable(), displacementLayerKey, false);
} }
} }

View File

@@ -142,7 +142,7 @@ public sealed class DoorSystem : SharedDoorSystem
private void UpdateSpriteLayers(Entity<SpriteComponent> sprite, string targetProto) private void UpdateSpriteLayers(Entity<SpriteComponent> sprite, string targetProto)
{ {
if (!_prototypeManager.TryIndex(targetProto, out var target)) if (!_prototypeManager.Resolve(targetProto, out var target))
return; return;
if (!target.TryGetComponent(out SpriteComponent? targetSprite, _componentFactory)) if (!target.TryGetComponent(out SpriteComponent? targetSprite, _componentFactory))

View File

@@ -1,25 +1,58 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Ghost.Roles; using Content.Shared.Ghost.Roles;
using Content.Shared.Ghost.Roles.Components;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Client.Ghost; namespace Content.Client.Ghost;
public sealed class GhostRoleRadioBoundUserInterface : BoundUserInterface public sealed class GhostRoleRadioBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{ {
private GhostRoleRadioMenu? _ghostRoleRadioMenu; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public GhostRoleRadioBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) private SimpleRadialMenu? _ghostRoleRadioMenu;
{
IoCManager.InjectDependencies(this);
}
protected override void Open() protected override void Open()
{ {
base.Open(); base.Open();
_ghostRoleRadioMenu = this.CreateWindow<GhostRoleRadioMenu>(); _ghostRoleRadioMenu = this.CreateWindow<SimpleRadialMenu>();
_ghostRoleRadioMenu.SetEntity(Owner);
_ghostRoleRadioMenu.SendGhostRoleRadioMessageAction += SendGhostRoleRadioMessage; // The purpose of this radial UI is for ghost role radios that allow you to select
// more than one potential option, such as with kobolds/lizards.
// This means that it won't show anything if SelectablePrototypes is empty.
if (!EntMan.TryGetComponent<GhostRoleMobSpawnerComponent>(Owner, out var comp))
return;
var list = ConvertToButtons(comp.SelectablePrototypes);
_ghostRoleRadioMenu.SetButtons(list);
}
private IEnumerable<RadialMenuOptionBase> ConvertToButtons(List<ProtoId<GhostRolePrototype>> protoIds)
{
var list = new List<RadialMenuOptionBase>();
foreach (var ghostRoleProtoId in protoIds)
{
// For each prototype we find we want to create a button that uses the name of the ghost role
// as the hover tooltip, and the icon is taken from either the ghost role entityprototype
// or the indicated icon entityprototype.
if (!_prototypeManager.Resolve(ghostRoleProtoId, out var ghostRoleProto))
continue;
var option = new RadialMenuActionOption<ProtoId<GhostRolePrototype>>(SendGhostRoleRadioMessage, ghostRoleProtoId)
{
ToolTip = Loc.GetString(ghostRoleProto.Name),
// pick the icon if it exists, otherwise fallback to the ghost role's entity
IconSpecifier = ghostRoleProto.IconPrototype != null
&& _prototypeManager.Resolve(ghostRoleProto.IconPrototype, out var iconProto)
? RadialMenuIconSpecifier.With(iconProto)
: RadialMenuIconSpecifier.With(ghostRoleProto.EntityPrototype)
};
list.Add(option);
}
return list;
} }
private void SendGhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId) private void SendGhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)

View File

@@ -1,8 +0,0 @@
<ui:RadialMenu
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True">
<ui:RadialContainer Name="Main">
</ui:RadialContainer>
</ui:RadialMenu>

View File

@@ -1,105 +0,0 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Ghost.Roles;
using Content.Shared.Ghost.Roles.Components;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using System.Numerics;
namespace Content.Client.Ghost;
public sealed partial class GhostRoleRadioMenu : RadialMenu
{
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public event Action<ProtoId<GhostRolePrototype>>? SendGhostRoleRadioMessageAction;
public EntityUid Entity { get; set; }
public GhostRoleRadioMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
}
public void SetEntity(EntityUid uid)
{
Entity = uid;
RefreshUI();
}
private void RefreshUI()
{
// The main control that will contain all the clickable options
var main = FindControl<RadialContainer>("Main");
// The purpose of this radial UI is for ghost role radios that allow you to select
// more than one potential option, such as with kobolds/lizards.
// This means that it won't show anything if SelectablePrototypes is empty.
if (!_entityManager.TryGetComponent<GhostRoleMobSpawnerComponent>(Entity, out var comp))
return;
foreach (var ghostRoleProtoString in comp.SelectablePrototypes)
{
// For each prototype we find we want to create a button that uses the name of the ghost role
// as the hover tooltip, and the icon is taken from either the ghost role entityprototype
// or the indicated icon entityprototype.
if (!_prototypeManager.TryIndex<GhostRolePrototype>(ghostRoleProtoString, out var ghostRoleProto))
continue;
var button = new GhostRoleRadioMenuButton()
{
SetSize = new Vector2(64, 64),
ToolTip = Loc.GetString(ghostRoleProto.Name),
ProtoId = ghostRoleProto.ID,
};
var entProtoView = new EntityPrototypeView()
{
SetSize = new Vector2(48, 48),
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Stretch = SpriteView.StretchMode.Fill
};
// pick the icon if it exists, otherwise fallback to the ghost role's entity
if (_prototypeManager.TryIndex(ghostRoleProto.IconPrototype, out var iconProto))
entProtoView.SetPrototype(iconProto);
else
entProtoView.SetPrototype(ghostRoleProto.EntityPrototype);
button.AddChild(entProtoView);
main.AddChild(button);
AddGhostRoleRadioMenuButtonOnClickActions(main);
}
}
private void AddGhostRoleRadioMenuButtonOnClickActions(Control control)
{
var mainControl = control as RadialContainer;
if (mainControl == null)
return;
foreach (var child in mainControl.Children)
{
var castChild = child as GhostRoleRadioMenuButton;
if (castChild == null)
continue;
castChild.OnButtonUp += _ =>
{
SendGhostRoleRadioMessageAction?.Invoke(castChild.ProtoId);
Close();
};
}
}
}
public sealed class GhostRoleRadioMenuButton : RadialMenuTextureButtonWithSector
{
public ProtoId<GhostRolePrototype> ProtoId { get; set; }
}

View File

@@ -5,14 +5,17 @@ using Content.Client.Guidebook.Richtext;
using Content.Client.Message; using Content.Client.Message;
using Content.Client.UserInterface.ControlExtensions; using Content.Client.UserInterface.ControlExtensions;
using Content.Shared.Body.Prototypes; using Content.Shared.Body.Prototypes;
using Content.Shared.CCVar;
using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Contraband;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -27,8 +30,10 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
[Dependency] private readonly IEntitySystemManager _systemManager = default!; [Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
private readonly ChemistryGuideDataSystem _chemistryGuideData; private readonly ChemistryGuideDataSystem _chemistryGuideData;
private readonly ContrabandSystem _contraband;
private readonly ISawmill _sawmill; private readonly ISawmill _sawmill;
public IPrototype? RepresentedPrototype { get; private set; } public IPrototype? RepresentedPrototype { get; private set; }
@@ -39,6 +44,7 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("guidebook.reagent"); _sawmill = _logManager.GetSawmill("guidebook.reagent");
_chemistryGuideData = _systemManager.GetEntitySystem<ChemistryGuideDataSystem>(); _chemistryGuideData = _systemManager.GetEntitySystem<ChemistryGuideDataSystem>();
_contraband = _systemManager.GetEntitySystem<ContrabandSystem>();
MouseFilter = MouseFilterMode.Stop; MouseFilter = MouseFilterMode.Stop;
} }
@@ -204,6 +210,25 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
description.PushNewline(); description.PushNewline();
description.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-physical-description", description.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-physical-description",
("description", reagent.LocalizedPhysicalDescription))); ("description", reagent.LocalizedPhysicalDescription)));
if (_config.GetCVar(CCVars.ContrabandExamine))
{
// Department-restricted text
if (reagent.AllowedJobs.Count > 0 || reagent.AllowedDepartments.Count > 0)
{
description.PushNewline();
description.AddMarkupPermissive(
_contraband.GenerateDepartmentExamineMessage(reagent.AllowedDepartments, reagent.AllowedJobs, ContrabandItemType.Reagent));
}
// Other contraband text
else if (reagent.ContrabandSeverity != null &&
_prototype.Resolve(reagent.ContrabandSeverity.Value, out var severity))
{
description.PushNewline();
description.AddMarkupPermissive(Loc.GetString(severity.ExamineText, ("type", ContrabandItemType.Reagent)));
}
}
ReagentDescription.SetMessage(description); ReagentDescription.SetMessage(description);
} }

View File

@@ -53,7 +53,7 @@ public sealed partial class DocumentParsingManager
public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId, bool log = true) public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId, bool log = true)
{ {
if (!_prototype.TryIndex(entryId, out var entry)) if (!_prototype.Resolve(entryId, out var entry))
return false; return false;
using var file = _resourceManager.ContentFileReadText(entry.Text); using var file = _resourceManager.ContentFileReadText(entry.Text);

View File

@@ -289,25 +289,26 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
private void RemoveMarking(Marking marking, Entity<SpriteComponent> spriteEnt) private void RemoveMarking(Marking marking, Entity<SpriteComponent> spriteEnt)
{ {
if (!_markingManager.TryGetMarking(marking, out var prototype)) if (!_markingManager.TryGetMarking(marking, out var prototype))
{
return; return;
}
foreach (var sprite in prototype.Sprites) foreach (var sprite in prototype.Sprites)
{ {
if (sprite is not SpriteSpecifier.Rsi rsi) if (sprite is not SpriteSpecifier.Rsi rsi)
{
continue; continue;
}
var layerId = $"{marking.MarkingId}-{rsi.RsiState}"; var layerId = $"{marking.MarkingId}-{rsi.RsiState}";
if (!_sprite.LayerMapTryGet(spriteEnt.AsNullable(), layerId, out var index, false)) if (!_sprite.LayerMapTryGet(spriteEnt.AsNullable(), layerId, out var index, false))
{
continue; continue;
}
_sprite.LayerMapRemove(spriteEnt.AsNullable(), layerId); _sprite.LayerMapRemove(spriteEnt.AsNullable(), layerId);
_sprite.RemoveLayer(spriteEnt.AsNullable(), index); _sprite.RemoveLayer(spriteEnt.AsNullable(), index);
// If this marking is one that can be displaced, we need to remove the displacement as well; otherwise
// altering a marking at runtime can lead to the renderer falling over.
// The Vulps must be shaved.
// (https://github.com/space-wizards/space-station-14/issues/40135).
if (prototype.CanBeDisplaced)
_displacement.EnsureDisplacementIsNotOnSprite(spriteEnt, layerId);
} }
} }
@@ -346,9 +347,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
var sprite = entity.Comp2; var sprite = entity.Comp2;
if (!_sprite.LayerMapTryGet((entity.Owner, sprite), markingPrototype.BodyPart, out var targetLayer, false)) if (!_sprite.LayerMapTryGet((entity.Owner, sprite), markingPrototype.BodyPart, out var targetLayer, false))
{
return; return;
}
visible &= !IsHidden(humanoid, markingPrototype.BodyPart); visible &= !IsHidden(humanoid, markingPrototype.BodyPart);
visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting) visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting)
@@ -359,9 +358,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
var markingSprite = markingPrototype.Sprites[j]; var markingSprite = markingPrototype.Sprites[j];
if (markingSprite is not SpriteSpecifier.Rsi rsi) if (markingSprite is not SpriteSpecifier.Rsi rsi)
{ return;
continue;
}
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}"; var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
@@ -375,26 +372,18 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
_sprite.LayerSetVisible((entity.Owner, sprite), layerId, visible); _sprite.LayerSetVisible((entity.Owner, sprite), layerId, visible);
if (!visible || setting == null) // this is kinda implied if (!visible || setting == null) // this is kinda implied
{
continue; continue;
}
// Okay so if the marking prototype is modified but we load old marking data this may no longer be valid // Okay so if the marking prototype is modified but we load old marking data this may no longer be valid
// and we need to check the index is correct. // and we need to check the index is correct.
// So if that happens just default to white? // So if that happens just default to white?
if (colors != null && j < colors.Count) if (colors != null && j < colors.Count)
{
_sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]); _sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]);
}
else else
{
_sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White); _sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
}
if (humanoid.MarkingsDisplacement.TryGetValue(markingPrototype.BodyPart, out var displacementData) && markingPrototype.CanBeDisplaced) if (humanoid.MarkingsDisplacement.TryGetValue(markingPrototype.BodyPart, out var displacementData) && markingPrototype.CanBeDisplaced)
{
_displacement.TryAddDisplacement(displacementData, (entity.Owner, sprite), targetLayer + j + 1, layerId, out _); _displacement.TryAddDisplacement(displacementData, (entity.Owner, sprite), targetLayer + j + 1, layerId, out _);
}
} }
} }

View File

@@ -28,7 +28,7 @@ public sealed class ImplanterSystem : SharedImplanterSystem
Dictionary<string, string> implants = new(); Dictionary<string, string> implants = new();
foreach (var implant in component.DeimplantWhitelist) foreach (var implant in component.DeimplantWhitelist)
{ {
if (_proto.TryIndex(implant, out var proto)) if (_proto.Resolve(implant, out var proto))
implants.Add(proto.ID, proto.Name); implants.Add(proto.ID, proto.Name);
} }

View File

@@ -62,7 +62,7 @@ public sealed partial class ChameleonControllerMenu : FancyWindow
// Go through every outfit and add them to the correct department. // Go through every outfit and add them to the correct department.
foreach (var outfit in _outfits) foreach (var outfit in _outfits)
{ {
_prototypeManager.TryIndex(outfit.Job, out var jobProto); _prototypeManager.Resolve(outfit.Job, out var jobProto);
var name = outfit.LoadoutName ?? outfit.Name ?? jobProto?.Name ?? "Prototype has no name or job."; var name = outfit.LoadoutName ?? outfit.Name ?? jobProto?.Name ?? "Prototype has no name or job.";

View File

@@ -49,7 +49,7 @@ public sealed class ImplanterStatusControl : Control
if (_parent.CurrentMode == ImplanterToggleMode.Draw) if (_parent.CurrentMode == ImplanterToggleMode.Draw)
{ {
string implantName = _parent.DeimplantChosen != null string implantName = _parent.DeimplantChosen != null
? (_prototype.TryIndex(_parent.DeimplantChosen.Value, out EntityPrototype? implantProto) ? implantProto.Name : Loc.GetString("implanter-empty-text")) ? (_prototype.Resolve(_parent.DeimplantChosen.Value, out EntityPrototype? implantProto) ? implantProto.Name : Loc.GetString("implanter-empty-text"))
: Loc.GetString("implanter-empty-text"); : Loc.GetString("implanter-empty-text");
_label.SetMarkup(Loc.GetString("implanter-label-draw", _label.SetMarkup(Loc.GetString("implanter-label-draw",

View File

@@ -11,6 +11,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Lathe.UI; namespace Content.Client.Lathe.UI;
@@ -96,7 +97,7 @@ public sealed partial class LatheMenu : DefaultWindow
var recipesToShow = new List<LatheRecipePrototype>(); var recipesToShow = new List<LatheRecipePrototype>();
foreach (var recipe in Recipes) foreach (var recipe in Recipes)
{ {
if (!_prototypeManager.TryIndex(recipe, out var proto)) if (!_prototypeManager.Resolve(recipe, out var proto))
continue; continue;
// Category filtering // Category filtering
@@ -128,21 +129,50 @@ public sealed partial class LatheMenu : DefaultWindow
RecipeCount.Text = Loc.GetString("lathe-menu-recipe-count", ("count", recipesToShow.Count)); RecipeCount.Text = Loc.GetString("lathe-menu-recipe-count", ("count", recipesToShow.Count));
var sortedRecipesToShow = recipesToShow.OrderBy(_lathe.GetRecipeName); var sortedRecipesToShow = recipesToShow.OrderBy(_lathe.GetRecipeName);
RecipeList.Children.Clear();
// Get the existing list of queue controls
var oldChildCount = RecipeList.ChildCount;
_entityManager.TryGetComponent(Entity, out LatheComponent? lathe); _entityManager.TryGetComponent(Entity, out LatheComponent? lathe);
int idx = 0;
foreach (var prototype in sortedRecipesToShow) foreach (var prototype in sortedRecipesToShow)
{ {
var canProduce = _lathe.CanProduce(Entity, prototype, quantity, component: lathe); var canProduce = _lathe.CanProduce(Entity, prototype, quantity, component: lathe);
var tooltipFunction = () => GenerateTooltipText(prototype);
var control = new RecipeControl(_lathe, prototype, () => GenerateTooltipText(prototype), canProduce, GetRecipeDisplayControl(prototype)); if (idx >= oldChildCount)
control.OnButtonPressed += s =>
{ {
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0) var control = new RecipeControl(_lathe, prototype, tooltipFunction, canProduce, GetRecipeDisplayControl(prototype));
amount = 1; control.OnButtonPressed += s =>
RecipeQueueAction?.Invoke(s, amount); {
}; if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
RecipeList.AddChild(control); amount = 1;
RecipeQueueAction?.Invoke(s, amount);
};
RecipeList.AddChild(control);
}
else
{
var child = RecipeList.GetChild(idx) as RecipeControl;
if (child == null)
{
DebugTools.Assert($"Lathe menu recipe control at {idx} is not of type RecipeControl"); // Something's gone terribly wrong.
continue;
}
child.SetRecipe(prototype);
child.SetTooltipSupplier(tooltipFunction);
child.SetCanProduce(canProduce);
child.SetDisplayControl(GetRecipeDisplayControl(prototype));
}
idx++;
}
// Shrink list if new list is shorter than old list.
for (var childIdx = oldChildCount - 1; idx <= childIdx; childIdx--)
{
RecipeList.RemoveChild(childIdx);
} }
} }
@@ -153,7 +183,7 @@ public sealed partial class LatheMenu : DefaultWindow
foreach (var (id, amount) in prototype.Materials) foreach (var (id, amount) in prototype.Materials)
{ {
if (!_prototypeManager.TryIndex(id, out var proto)) if (!_prototypeManager.Resolve(id, out var proto))
continue; continue;
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, multiplier); var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, multiplier);
@@ -238,9 +268,10 @@ public sealed partial class LatheMenu : DefaultWindow
/// <param name="queue"></param> /// <param name="queue"></param>
public void PopulateQueueList(IReadOnlyCollection<LatheRecipeBatch> queue) public void PopulateQueueList(IReadOnlyCollection<LatheRecipeBatch> queue)
{ {
QueueList.DisposeAllChildren(); // Get the existing list of queue controls
var oldChildCount = QueueList.ChildCount;
var idx = 1; var idx = 0;
foreach (var batch in queue) foreach (var batch in queue)
{ {
var recipe = _prototypeManager.Index(batch.Recipe); var recipe = _prototypeManager.Index(batch.Recipe);
@@ -248,18 +279,40 @@ public sealed partial class LatheMenu : DefaultWindow
var itemName = _lathe.GetRecipeName(batch.Recipe); var itemName = _lathe.GetRecipeName(batch.Recipe);
string displayText; string displayText;
if (batch.ItemsRequested > 1) if (batch.ItemsRequested > 1)
displayText = Loc.GetString("lathe-menu-item-batch", ("index", idx), ("name", itemName), ("printed", batch.ItemsPrinted), ("total", batch.ItemsRequested)); displayText = Loc.GetString("lathe-menu-item-batch", ("index", idx + 1), ("name", itemName), ("printed", batch.ItemsPrinted), ("total", batch.ItemsRequested));
else else
displayText = Loc.GetString("lathe-menu-item-single", ("index", idx), ("name", itemName)); displayText = Loc.GetString("lathe-menu-item-single", ("index", idx + 1), ("name", itemName));
var queuedRecipeBox = new QueuedRecipeControl(displayText, idx - 1, GetRecipeDisplayControl(recipe)); if (idx >= oldChildCount)
queuedRecipeBox.OnDeletePressed += s => QueueDeleteAction?.Invoke(s); {
queuedRecipeBox.OnMoveUpPressed += s => QueueMoveUpAction?.Invoke(s); var queuedRecipeBox = new QueuedRecipeControl(displayText, idx, GetRecipeDisplayControl(recipe));
queuedRecipeBox.OnMoveDownPressed += s => QueueMoveDownAction?.Invoke(s); queuedRecipeBox.OnDeletePressed += s => QueueDeleteAction?.Invoke(s);
queuedRecipeBox.OnMoveUpPressed += s => QueueMoveUpAction?.Invoke(s);
queuedRecipeBox.OnMoveDownPressed += s => QueueMoveDownAction?.Invoke(s);
QueueList.AddChild(queuedRecipeBox);
}
else
{
var child = QueueList.GetChild(idx) as QueuedRecipeControl;
QueueList.AddChild(queuedRecipeBox); if (child == null)
{
DebugTools.Assert($"Lathe menu queued recipe control at {idx} is not of type QueuedRecipeControl"); // Something's gone terribly wrong.
continue;
}
child.SetDisplayText(displayText);
child.SetIndex(idx);
child.SetDisplayControl(GetRecipeDisplayControl(recipe));
}
idx++; idx++;
} }
// Shrink list if new list is shorter than old list.
for (var childIdx = oldChildCount - 1; idx <= childIdx; childIdx--)
{
QueueList.RemoveChild(childIdx);
}
} }
public void SetQueueInfo(ProtoId<LatheRecipePrototype>? recipeProto) public void SetQueueInfo(ProtoId<LatheRecipePrototype>? recipeProto)

View File

@@ -11,26 +11,46 @@ public sealed partial class QueuedRecipeControl : Control
public Action<int>? OnMoveUpPressed; public Action<int>? OnMoveUpPressed;
public Action<int>? OnMoveDownPressed; public Action<int>? OnMoveDownPressed;
private int _index;
public QueuedRecipeControl(string displayText, int index, Control displayControl) public QueuedRecipeControl(string displayText, int index, Control displayControl)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
RecipeName.Text = displayText; SetDisplayText(displayText);
RecipeDisplayContainer.AddChild(displayControl); SetDisplayControl(displayControl);
SetIndex(index);
_index = index;
MoveUp.OnPressed += (_) => MoveUp.OnPressed += (_) =>
{ {
OnMoveUpPressed?.Invoke(index); OnMoveUpPressed?.Invoke(_index);
}; };
MoveDown.OnPressed += (_) => MoveDown.OnPressed += (_) =>
{ {
OnMoveDownPressed?.Invoke(index); OnMoveDownPressed?.Invoke(_index);
}; };
Delete.OnPressed += (_) => Delete.OnPressed += (_) =>
{ {
OnDeletePressed?.Invoke(index); OnDeletePressed?.Invoke(_index);
}; };
} }
public void SetDisplayText(string displayText)
{
RecipeName.Text = displayText;
}
public void SetDisplayControl(Control displayControl)
{
RecipeDisplayContainer.Children.Clear();
RecipeDisplayContainer.AddChild(displayControl);
}
public void SetIndex(int index)
{
_index = index;
}
} }

View File

@@ -2,6 +2,7 @@ using Content.Shared.Research.Prototypes;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Lathe.UI; namespace Content.Client.Lathe.UI;
@@ -11,20 +12,47 @@ public sealed partial class RecipeControl : Control
public Action<string>? OnButtonPressed; public Action<string>? OnButtonPressed;
public Func<string> TooltipTextSupplier; public Func<string> TooltipTextSupplier;
private ProtoId<LatheRecipePrototype> _recipeId;
private LatheSystem _latheSystem;
public RecipeControl(LatheSystem latheSystem, LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Control displayControl) public RecipeControl(LatheSystem latheSystem, LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Control displayControl)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
RecipeName.Text = latheSystem.GetRecipeName(recipe); _latheSystem = latheSystem;
RecipeDisplayContainer.AddChild(displayControl); _recipeId = recipe.ID;
Button.Disabled = !canProduce;
TooltipTextSupplier = tooltipTextSupplier; TooltipTextSupplier = tooltipTextSupplier;
Button.TooltipSupplier = SupplyTooltip; SetRecipe(recipe);
SetCanProduce(canProduce);
SetDisplayControl(displayControl);
Button.OnPressed += (_) => Button.OnPressed += (_) =>
{ {
OnButtonPressed?.Invoke(recipe.ID); OnButtonPressed?.Invoke(_recipeId);
}; };
Button.TooltipSupplier = SupplyTooltip;
}
public void SetRecipe(LatheRecipePrototype recipe)
{
RecipeName.Text = _latheSystem.GetRecipeName(recipe);
_recipeId = recipe.ID;
}
public void SetTooltipSupplier(Func<string> tooltipTextSupplier)
{
TooltipTextSupplier = tooltipTextSupplier;
}
public void SetCanProduce(bool canProduce)
{
Button.Disabled = !canProduce;
}
public void SetDisplayControl(Control displayControl)
{
RecipeDisplayContainer.Children.Clear();
RecipeDisplayContainer.AddChild(displayControl);
} }
private Control? SupplyTooltip(Control sender) private Control? SupplyTooltip(Control sender)

View File

@@ -72,6 +72,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
}); });
_configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor()); _configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor());
_configurationManager.OnValueChanged(CCVars.GameRoleLoadoutTimers, _ => RefreshProfileEditor());
_configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor()); _configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor());
} }
@@ -361,7 +362,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
{ {
foreach (var loadout in group) foreach (var loadout in group)
{ {
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto)) if (!_prototypeManager.Resolve(loadout.Prototype, out var loadoutProto))
continue; continue;
_spawn.EquipStartingGear(uid, loadoutProto); _spawn.EquipStartingGear(uid, loadoutProto);
@@ -384,14 +385,14 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
{ {
foreach (var loadout in loadouts) foreach (var loadout in loadouts)
{ {
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto)) if (!_prototypeManager.Resolve(loadout.Prototype, out var loadoutProto))
continue; continue;
// TODO: Need some way to apply starting gear to an entity and replace existing stuff coz holy fucking shit dude. // TODO: Need some way to apply starting gear to an entity and replace existing stuff coz holy fucking shit dude.
foreach (var slot in slots) foreach (var slot in slots)
{ {
// Try startinggear first // Try startinggear first
if (_prototypeManager.TryIndex(loadoutProto.StartingGear, out var loadoutGear)) if (_prototypeManager.Resolve(loadoutProto.StartingGear, out var loadoutGear))
{ {
var itemType = ((IEquipmentLoadout) loadoutGear).GetGear(slot.Name); var itemType = ((IEquipmentLoadout) loadoutGear).GetGear(slot.Name);
@@ -426,7 +427,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
} }
} }
if (!_prototypeManager.TryIndex(job.StartingGear, out var gear)) if (!_prototypeManager.Resolve(job.StartingGear, out var gear))
return; return;
foreach (var slot in slots) foreach (var slot in slots)

View File

@@ -810,7 +810,7 @@ namespace Content.Client.Lobby.UI
if (_prototypeManager.HasIndex<GuideEntryPrototype>(species)) if (_prototypeManager.HasIndex<GuideEntryPrototype>(species))
page = new ProtoId<GuideEntryPrototype>(species.Id); // Gross. See above todo comment. page = new ProtoId<GuideEntryPrototype>(species.Id); // Gross. See above todo comment.
if (_prototypeManager.TryIndex(DefaultSpeciesGuidebook, out var guideRoot)) if (_prototypeManager.Resolve(DefaultSpeciesGuidebook, out var guideRoot))
{ {
var dict = new Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>(); var dict = new Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>();
dict.Add(DefaultSpeciesGuidebook, guideRoot); dict.Add(DefaultSpeciesGuidebook, guideRoot);
@@ -1291,7 +1291,7 @@ namespace Content.Client.Lobby.UI
var sexes = new List<Sex>(); var sexes = new List<Sex>();
// add species sex options, default to just none if we are in bizzaro world and have no species // add species sex options, default to just none if we are in bizzaro world and have no species
if (_prototypeManager.TryIndex<SpeciesPrototype>(Profile.Species, out var speciesProto)) if (_prototypeManager.Resolve<SpeciesPrototype>(Profile.Species, out var speciesProto))
{ {
foreach (var sex in speciesProto.Sexes) foreach (var sex in speciesProto.Sexes)
{ {
@@ -1384,7 +1384,7 @@ namespace Content.Client.Lobby.UI
if (species is null) if (species is null)
return; return;
if (!_prototypeManager.TryIndex<SpeciesPrototype>(species, out var speciesProto)) if (!_prototypeManager.Resolve<SpeciesPrototype>(species, out var speciesProto))
return; return;
// Don't display the info button if no guide entry is found // Don't display the info button if no guide entry is found

View File

@@ -40,7 +40,7 @@ public sealed partial class LoadoutContainer : BoxContainer
SelectButton.TooltipSupplier = _ => tooltip; SelectButton.TooltipSupplier = _ => tooltip;
} }
if (_protoManager.TryIndex(proto, out var loadProto)) if (_protoManager.Resolve(proto, out var loadProto))
{ {
var ent = loadProto.DummyEntity ?? _entManager.System<LoadoutSystem>().GetFirstOrNull(loadProto); var ent = loadProto.DummyEntity ?? _entManager.System<LoadoutSystem>().GetFirstOrNull(loadProto);

View File

@@ -62,7 +62,7 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
}); });
} }
if (protoMan.TryIndex(loadout.Role, out var roleProto) && roleProto.Points != null && loadout.Points != null) if (protoMan.Resolve(loadout.Role, out var roleProto) && roleProto.Points != null && loadout.Points != null)
{ {
RestrictionsContainer.AddChild(new Label() RestrictionsContainer.AddChild(new Label()
{ {
@@ -112,14 +112,14 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
}) })
.ToList(); .ToList();
/* /*
* Determine which element should be displayed first: * Determine which element should be displayed first:
* - If any element is currently selected (its button is pressed), use it. * - If any element is currently selected (its button is pressed), use it.
* - Otherwise, fallback to the first element in the list. * - Otherwise, fallback to the first element in the list.
* *
* This moves the selected item outside of the sublist for better usability, * This moves the selected item outside of the sublist for better usability,
* making it easier for players to quickly toggle loadout options (e.g. clothing, accessories) * making it easier for players to quickly toggle loadout options (e.g. clothing, accessories)
* without having to search inside expanded subgroups. * without having to search inside expanded subgroups.
*/ */
var firstElement = uiElements.FirstOrDefault(e => e.Select.Pressed) ?? uiElements[0]; var firstElement = uiElements.FirstOrDefault(e => e.Select.Pressed) ?? uiElements[0];
@@ -195,8 +195,8 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
/// <summary> /// <summary>
/// Creates a UI container for a single Loadout item. /// Creates a UI container for a single Loadout item.
/// ///
/// This method was extracted from RefreshLoadouts because the logic for creating /// This method was extracted from RefreshLoadouts because the logic for creating
/// individual loadout items is used multiple times inside that method, and duplicating /// individual loadout items is used multiple times inside that method, and duplicating
/// the code made it harder to maintain. /// the code made it harder to maintain.
/// ///
/// Logic: /// Logic:

View File

@@ -68,7 +68,7 @@ public sealed partial class LoadoutWindow : FancyWindow
{ {
foreach (var group in proto.Groups) foreach (var group in proto.Groups)
{ {
if (!protoManager.TryIndex(group, out var groupProto)) if (!protoManager.Resolve(group, out var groupProto))
continue; continue;
if (groupProto.Hidden) if (groupProto.Hidden)

View File

@@ -64,7 +64,9 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl
if (!LocalizedNames.TryGetValue(netEntity, out var name)) if (!LocalizedNames.TryGetValue(netEntity, out var name))
name = "Unknown"; name = "Unknown";
var message = name + "\nLocation: [x = " + MathF.Round(blip.Coordinates.X) + ", y = " + MathF.Round(blip.Coordinates.Y) + "]"; var message = name + "\n" + Loc.GetString("navmap-location",
("x", MathF.Round(blip.Coordinates.X)),
("y", MathF.Round(blip.Coordinates.Y)));
_trackedEntityLabel.Text = message; _trackedEntityLabel.Text = message;
_trackedEntityPanel.Visible = true; _trackedEntityPanel.Visible = true;

View File

@@ -57,7 +57,7 @@ public sealed class EntityHealthBarOverlay : Overlay
const float scale = 1f; const float scale = 1f;
var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale)); var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation); var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation);
_prototype.TryIndex(StatusIcon, out var statusIcon); _prototype.Resolve(StatusIcon, out var statusIcon);
var query = _entManager.AllEntityQueryEnumerator<MobThresholdsComponent, MobStateComponent, DamageableComponent, SpriteComponent>(); var query = _entManager.AllEntityQueryEnumerator<MobThresholdsComponent, MobStateComponent, DamageableComponent, SpriteComponent>();
while (query.MoveNext(out var uid, while (query.MoveNext(out var uid,

View File

@@ -22,7 +22,7 @@ public sealed class ShowCriminalRecordIconsSystem : EquipmentHudSystem<ShowCrimi
if (!IsActive) if (!IsActive)
return; return;
if (_prototype.TryIndex(component.StatusIcon, out var iconPrototype)) if (_prototype.Resolve(component.StatusIcon, out var iconPrototype))
ev.StatusIcons.Add(iconPrototype); ev.StatusIcons.Add(iconPrototype);
} }
} }

View File

@@ -78,9 +78,9 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
if (TryComp<MobStateComponent>(entity, out var state)) if (TryComp<MobStateComponent>(entity, out var state))
{ {
// Since there is no MobState for a rotting mob, we have to deal with this case first. // Since there is no MobState for a rotting mob, we have to deal with this case first.
if (HasComp<RottingComponent>(entity) && _prototypeMan.TryIndex(damageableComponent.RottingIcon, out var rottingIcon)) if (HasComp<RottingComponent>(entity) && _prototypeMan.Resolve(damageableComponent.RottingIcon, out var rottingIcon))
result.Add(rottingIcon); result.Add(rottingIcon);
else if (damageableComponent.HealthIcons.TryGetValue(state.CurrentState, out var value) && _prototypeMan.TryIndex(value, out var icon)) else if (damageableComponent.HealthIcons.TryGetValue(state.CurrentState, out var value) && _prototypeMan.Resolve(value, out var icon))
result.Add(icon); result.Add(icon);
} }
} }

View File

@@ -51,7 +51,7 @@ public sealed class ShowJobIconsSystem : EquipmentHudSystem<ShowJobIconsComponen
} }
} }
if (_prototype.TryIndex(iconId, out var iconPrototype)) if (_prototype.Resolve(iconId, out var iconPrototype))
ev.StatusIcons.Add(iconPrototype); ev.StatusIcons.Add(iconPrototype);
else else
Log.Error($"Invalid job icon prototype: {iconPrototype}"); Log.Error($"Invalid job icon prototype: {iconPrototype}");

View File

@@ -23,7 +23,7 @@ public sealed class ShowMindShieldIconsSystem : EquipmentHudSystem<ShowMindShiel
{ {
if(!IsActive) if(!IsActive)
return; return;
if (component.IsEnabled && _prototype.TryIndex(component.MindShieldStatusIcon, out var fakeStatusIconPrototype)) if (component.IsEnabled && _prototype.Resolve(component.MindShieldStatusIcon, out var fakeStatusIconPrototype))
ev.StatusIcons.Add(fakeStatusIconPrototype); ev.StatusIcons.Add(fakeStatusIconPrototype);
} }
@@ -32,7 +32,7 @@ public sealed class ShowMindShieldIconsSystem : EquipmentHudSystem<ShowMindShiel
if (!IsActive) if (!IsActive)
return; return;
if (_prototype.TryIndex(component.MindShieldStatusIcon, out var iconPrototype)) if (_prototype.Resolve(component.MindShieldStatusIcon, out var iconPrototype))
ev.StatusIcons.Add(iconPrototype); ev.StatusIcons.Add(iconPrototype);
} }
} }

View File

@@ -65,7 +65,7 @@ public sealed partial class StencilOverlay : Overlay
{ {
foreach (var (proto, weather) in comp.Weather) foreach (var (proto, weather) in comp.Weather)
{ {
if (!_protoManager.TryIndex<WeatherPrototype>(proto, out var weatherProto)) if (!_protoManager.Resolve<WeatherPrototype>(proto, out var weatherProto))
continue; continue;
var alpha = _weather.GetPercent(weather, mapUid); var alpha = _weather.GetPercent(weather, mapUid);

View File

@@ -120,8 +120,8 @@ public sealed class MoverController : SharedMoverController
base.SetSprinting(entity, subTick, walking); base.SetSprinting(entity, subTick, walking);
if (walking && _cfg.GetCVar(CCVars.ToggleWalk)) if (walking && _cfg.GetCVar(CCVars.ToggleWalk))
_alerts.ShowAlert(entity, WalkingAlert, showCooldown: false, autoRemove: false); _alerts.ShowAlert(entity.Owner, WalkingAlert, showCooldown: false, autoRemove: false);
else else
_alerts.ClearAlert(entity, WalkingAlert); _alerts.ClearAlert(entity.Owner, WalkingAlert);
} }
} }

View File

@@ -51,10 +51,10 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface
_menu.OpenOverMouseScreenPosition(); _menu.OpenOverMouseScreenPosition();
} }
private IEnumerable<RadialMenuOption> ConvertToButtons(HashSet<ProtoId<RCDPrototype>> prototypes) private IEnumerable<RadialMenuOptionBase> ConvertToButtons(HashSet<ProtoId<RCDPrototype>> prototypes)
{ {
Dictionary<string, List<RadialMenuActionOption>> buttonsByCategory = new(); Dictionary<string, List<RadialMenuActionOptionBase>> buttonsByCategory = new();
ValueList<RadialMenuActionOption> topLevelActions = new(); ValueList<RadialMenuActionOptionBase> topLevelActions = new();
foreach (var protoId in prototypes) foreach (var protoId in prototypes)
{ {
var prototype = _prototypeManager.Index(protoId); var prototype = _prototypeManager.Index(protoId);
@@ -62,7 +62,7 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface
{ {
var topLevelActionOption = new RadialMenuActionOption<RCDPrototype>(HandleMenuOptionClick, prototype) var topLevelActionOption = new RadialMenuActionOption<RCDPrototype>(HandleMenuOptionClick, prototype)
{ {
Sprite = prototype.Sprite, IconSpecifier = RadialMenuIconSpecifier.With(prototype.Sprite),
ToolTip = GetTooltip(prototype) ToolTip = GetTooltip(prototype)
}; };
topLevelActions.Add(topLevelActionOption); topLevelActions.Add(topLevelActionOption);
@@ -74,26 +74,26 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface
if (!buttonsByCategory.TryGetValue(prototype.Category, out var list)) if (!buttonsByCategory.TryGetValue(prototype.Category, out var list))
{ {
list = new List<RadialMenuActionOption>(); list = new List<RadialMenuActionOptionBase>();
buttonsByCategory.Add(prototype.Category, list); buttonsByCategory.Add(prototype.Category, list);
} }
var actionOption = new RadialMenuActionOption<RCDPrototype>(HandleMenuOptionClick, prototype) var actionOption = new RadialMenuActionOption<RCDPrototype>(HandleMenuOptionClick, prototype)
{ {
Sprite = prototype.Sprite, IconSpecifier = RadialMenuIconSpecifier.With(prototype.Sprite),
ToolTip = GetTooltip(prototype) ToolTip = GetTooltip(prototype)
}; };
list.Add(actionOption); list.Add(actionOption);
} }
var models = new RadialMenuOption[buttonsByCategory.Count + topLevelActions.Count]; var models = new RadialMenuOptionBase[buttonsByCategory.Count + topLevelActions.Count];
var i = 0; var i = 0;
foreach (var (key, list) in buttonsByCategory) foreach (var (key, list) in buttonsByCategory)
{ {
var groupInfo = PrototypesGroupingInfo[key]; var groupInfo = PrototypesGroupingInfo[key];
models[i] = new RadialMenuNestedLayerOption(list) models[i] = new RadialMenuNestedLayerOption(list)
{ {
Sprite = groupInfo.Sprite, IconSpecifier = RadialMenuIconSpecifier.With(groupInfo.Sprite),
ToolTip = Loc.GetString(groupInfo.Tooltip) ToolTip = Loc.GetString(groupInfo.Tooltip)
}; };
i++; i++;
@@ -125,8 +125,10 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface
var name = Loc.GetString(proto.SetName); var name = Loc.GetString(proto.SetName);
if (proto.Prototype != null && if (proto.Prototype != null &&
_prototypeManager.TryIndex(proto.Prototype, out var entProto, logError: false)) _prototypeManager.Resolve(proto.Prototype, out var entProto))
{
name = entProto.Name; name = entProto.Name;
}
msg = Loc.GetString("rcd-component-change-build-mode", ("name", name)); msg = Loc.GetString("rcd-component-change-build-mode", ("name", name));
} }
@@ -142,7 +144,7 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface
if (proto.Mode is RcdMode.ConstructTile or RcdMode.ConstructObject if (proto.Mode is RcdMode.ConstructTile or RcdMode.ConstructObject
&& proto.Prototype != null && proto.Prototype != null
&& _prototypeManager.TryIndex(proto.Prototype, out var entProto, logError: false)) && _prototypeManager.Resolve(proto.Prototype, out var entProto))
{ {
tooltip = Loc.GetString(entProto.Name); tooltip = Loc.GetString(entProto.Name);
} }

View File

@@ -42,7 +42,7 @@ public sealed partial class IntercomMenu : FancyWindow
for (var i = 0; i < entity.Comp.SupportedChannels.Count; i++) for (var i = 0; i < entity.Comp.SupportedChannels.Count; i++)
{ {
var channel = entity.Comp.SupportedChannels[i]; var channel = entity.Comp.SupportedChannels[i];
if (!_prototype.TryIndex(channel, out var prototype)) if (!_prototype.Resolve(channel, out var prototype))
continue; continue;
_channels.Add(channel); _channels.Add(channel);

View File

@@ -25,13 +25,13 @@ public sealed class RevolutionarySystem : SharedRevolutionarySystem
if (HasComp<HeadRevolutionaryComponent>(ent)) if (HasComp<HeadRevolutionaryComponent>(ent))
return; return;
if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype)) if (_prototype.Resolve(ent.Comp.StatusIcon, out var iconPrototype))
args.StatusIcons.Add(iconPrototype); args.StatusIcons.Add(iconPrototype);
} }
private void GetHeadRevIcon(Entity<HeadRevolutionaryComponent> ent, ref GetStatusIconsEvent args) private void GetHeadRevIcon(Entity<HeadRevolutionaryComponent> ent, ref GetStatusIconsEvent args)
{ {
if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype)) if (_prototype.Resolve(ent.Comp.StatusIcon, out var iconPrototype))
args.StatusIcons.Add(iconPrototype); args.StatusIcons.Add(iconPrototype);
} }
} }

View File

@@ -0,0 +1,5 @@
using Content.Shared.Shuttles.Systems;
namespace Content.Client.Shuttles.Systems;
public sealed partial class EmergencyShuttleSystem : SharedEmergencyShuttleSystem;

View File

@@ -23,15 +23,15 @@ public sealed class StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : B
_menu.Open(); _menu.Open();
} }
private IEnumerable<RadialMenuActionOption> ConvertToButtons(IReadOnlyList<StationAiRadial> actions) private IEnumerable<RadialMenuActionOptionBase> ConvertToButtons(IReadOnlyList<StationAiRadial> actions)
{ {
var models = new RadialMenuActionOption[actions.Count]; var models = new RadialMenuActionOptionBase[actions.Count];
for (int i = 0; i < actions.Count; i++) for (int i = 0; i < actions.Count; i++)
{ {
var action = actions[i]; var action = actions[i];
models[i] = new RadialMenuActionOption<BaseStationAiAction>(HandleRadialMenuClick, action.Event) models[i] = new RadialMenuActionOption<BaseStationAiAction>(HandleRadialMenuClick, action.Event)
{ {
Sprite = action.Sprite, IconSpecifier = RadialMenuIconSpecifier.With(action.Sprite),
ToolTip = action.Tooltip ToolTip = action.Tooltip
}; };
} }

View File

@@ -44,7 +44,7 @@ public sealed partial class StationAiCustomizationMenu : FancyWindow
StationAiCustomizationPrototype? selectedPrototype = null; StationAiCustomizationPrototype? selectedPrototype = null;
if (stationAiCustomization?.ProtoIds.TryGetValue(groupPrototype, out var selectedProtoId) == true) if (stationAiCustomization?.ProtoIds.TryGetValue(groupPrototype, out var selectedProtoId) == true)
_protoManager.TryIndex(selectedProtoId, out selectedPrototype); _protoManager.Resolve(selectedProtoId, out selectedPrototype);
_buttonGroups[groupPrototype] = new ButtonGroup(); _buttonGroups[groupPrototype] = new ButtonGroup();
_groupContainers[groupPrototype] = new StationAiCustomizationGroupContainer(groupPrototype, selectedPrototype, _buttonGroups[groupPrototype], this, _protoManager); _groupContainers[groupPrototype] = new StationAiCustomizationGroupContainer(groupPrototype, selectedPrototype, _buttonGroups[groupPrototype], this, _protoManager);
@@ -76,7 +76,7 @@ public sealed partial class StationAiCustomizationMenu : FancyWindow
// Create UI entries for all customization in the group // Create UI entries for all customization in the group
foreach (var protoId in groupPrototype.ProtoIds) foreach (var protoId in groupPrototype.ProtoIds)
{ {
if (!protoManager.TryIndex(protoId, out var prototype)) if (!protoManager.Resolve(protoId, out var prototype))
continue; continue;
var entry = new StationAiCustomizationEntryContainer(groupPrototype, prototype, buttonGroup, menu); var entry = new StationAiCustomizationEntryContainer(groupPrototype, prototype, buttonGroup, menu);

View File

@@ -65,7 +65,7 @@ public sealed class SprayPainterSystem : SharedSprayPainterSystem
var groupList = new List<string>(); var groupList = new List<string>();
foreach (var groupId in category.Groups) foreach (var groupId in category.Groups)
{ {
if (!Proto.TryIndex(groupId, out var group)) if (!Proto.Resolve(groupId, out var group))
continue; continue;
groupList.Add(groupId); groupList.Add(groupId);

View File

@@ -42,7 +42,7 @@ public sealed class EntityStorageVisualizerSystem : VisualizerSystem<EntityStora
var forceRedrawBase = false; var forceRedrawBase = false;
if (AppearanceSystem.TryGetData<string>(uid, PaintableVisuals.Prototype, out var prototype, args.Component)) if (AppearanceSystem.TryGetData<string>(uid, PaintableVisuals.Prototype, out var prototype, args.Component))
{ {
if (_prototypeManager.TryIndex(prototype, out var proto)) if (_prototypeManager.Resolve(prototype, out var proto))
{ {
if (proto.TryGetComponent(out SpriteComponent? sprite, _componentFactory)) if (proto.TryGetComponent(out SpriteComponent? sprite, _componentFactory))
{ {

View File

@@ -33,7 +33,7 @@ public sealed partial class StoreWithdrawWindow : DefaultWindow
_validCurrencies.Clear(); _validCurrencies.Clear();
foreach (var currency in balance) foreach (var currency in balance)
{ {
if (!_prototypeManager.TryIndex(currency.Key, out var proto)) if (!_prototypeManager.Resolve(currency.Key, out var proto))
continue; continue;
_validCurrencies.Add(proto, currency.Value); _validCurrencies.Add(proto, currency.Value);

View File

@@ -229,10 +229,10 @@ public class RadialMenu : BaseWindow
/// from interactions. /// from interactions.
/// </summary> /// </summary>
[Virtual] [Virtual]
public class RadialMenuTextureButtonBase : TextureButton public abstract class RadialMenuButtonBase : BaseButton
{ {
/// <inheritdoc /> /// <inheritdoc />
protected RadialMenuTextureButtonBase() protected RadialMenuButtonBase()
{ {
EnableAllKeybinds = true; EnableAllKeybinds = true;
} }
@@ -242,7 +242,9 @@ public class RadialMenuTextureButtonBase : TextureButton
{ {
if (args.Function == EngineKeyFunctions.UIClick if (args.Function == EngineKeyFunctions.UIClick
|| args.Function == ContentKeyFunctions.AltActivateItemInWorld) || args.Function == ContentKeyFunctions.AltActivateItemInWorld)
{
base.KeyBindUp(args); base.KeyBindUp(args);
}
} }
} }
@@ -253,8 +255,14 @@ public class RadialMenuTextureButtonBase : TextureButton
/// works only if control have parent, and ActiveContainer property is set. /// works only if control have parent, and ActiveContainer property is set.
/// Also considers all space outside of radial menu buttons as itself for clicking. /// Also considers all space outside of radial menu buttons as itself for clicking.
/// </summary> /// </summary>
public sealed class RadialMenuContextualCentralTextureButton : RadialMenuTextureButtonBase public sealed class RadialMenuContextualCentralTextureButton : TextureButton
{ {
/// <inheritdoc />
public RadialMenuContextualCentralTextureButton()
{
EnableAllKeybinds = true;
}
public float InnerRadius { get; set; } public float InnerRadius { get; set; }
public Vector2? ParentCenter { get; set; } public Vector2? ParentCenter { get; set; }
@@ -271,15 +279,25 @@ public sealed class RadialMenuContextualCentralTextureButton : RadialMenuTexture
var innerRadiusSquared = InnerRadius * InnerRadius; var innerRadiusSquared = InnerRadius * InnerRadius;
// comparing to squared values is faster then making sqrt // comparing to squared values is faster, then making sqrt
return distSquared < innerRadiusSquared; return distSquared < innerRadiusSquared;
} }
/// <inheritdoc />
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
if (args.Function == EngineKeyFunctions.UIClick
|| args.Function == ContentKeyFunctions.AltActivateItemInWorld)
{
base.KeyBindUp(args);
}
}
} }
/// <summary> /// <summary>
/// Menu button for outer area of radial menu (covers everything 'outside'). /// Menu button for outer area of radial menu (covers everything 'outside').
/// </summary> /// </summary>
public sealed class RadialMenuOuterAreaButton : RadialMenuTextureButtonBase public sealed class RadialMenuOuterAreaButton : RadialMenuButtonBase
{ {
public float OuterRadius { get; set; } public float OuterRadius { get; set; }
@@ -303,7 +321,7 @@ public sealed class RadialMenuOuterAreaButton : RadialMenuTextureButtonBase
} }
[Virtual] [Virtual]
public class RadialMenuTextureButton : RadialMenuTextureButtonBase public class RadialMenuButton : RadialMenuButtonBase
{ {
/// <summary> /// <summary>
/// Upon clicking this button the radial menu will be moved to the layer of this control. /// Upon clicking this button the radial menu will be moved to the layer of this control.
@@ -319,9 +337,8 @@ public class RadialMenuTextureButton : RadialMenuTextureButtonBase
/// <summary> /// <summary>
/// A simple texture button that can move the user to a different layer within a radial menu /// A simple texture button that can move the user to a different layer within a radial menu
/// </summary> /// </summary>
public RadialMenuTextureButton() public RadialMenuButton()
{ {
EnableAllKeybinds = true;
OnButtonUp += OnClicked; OnButtonUp += OnClicked;
} }
@@ -391,7 +408,7 @@ public interface IRadialMenuItemWithSector
} }
[Virtual] [Virtual]
public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadialMenuItemWithSector public class RadialMenuButtonWithSector : RadialMenuButton, IRadialMenuItemWithSector
{ {
private Vector2[]? _sectorPointsForDrawing; private Vector2[]? _sectorPointsForDrawing;
@@ -500,7 +517,7 @@ public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadia
/// <summary> /// <summary>
/// A simple texture button that can move the user to a different layer within a radial menu /// A simple texture button that can move the user to a different layer within a radial menu
/// </summary> /// </summary>
public RadialMenuTextureButtonWithSector() public RadialMenuButtonWithSector()
{ {
} }

View File

@@ -7,6 +7,8 @@ using Robust.Client.GameObjects;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Client.Input; using Robust.Client.Input;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
namespace Content.Client.UserInterface.Controls; namespace Content.Client.UserInterface.Controls;
@@ -30,7 +32,7 @@ public sealed partial class SimpleRadialMenu : RadialMenu
_attachMenuToEntity = owner; _attachMenuToEntity = owner;
} }
public void SetButtons(IEnumerable<RadialMenuOption> models, SimpleRadialMenuSettings? settings = null) public void SetButtons(IEnumerable<RadialMenuOptionBase> models, SimpleRadialMenuSettings? settings = null)
{ {
ClearExistingChildrenRadialButtons(); ClearExistingChildrenRadialButtons();
@@ -45,7 +47,7 @@ public sealed partial class SimpleRadialMenu : RadialMenu
} }
private void Fill( private void Fill(
IEnumerable<RadialMenuOption> models, IEnumerable<RadialMenuOptionBase> models,
SpriteSystem sprites, SpriteSystem sprites,
ICollection<Control> rootControlChildren, ICollection<Control> rootControlChildren,
SimpleRadialMenuSettings settings SimpleRadialMenuSettings settings
@@ -77,7 +79,7 @@ public sealed partial class SimpleRadialMenu : RadialMenu
} }
} }
private RadialMenuTextureButton RecursiveContainerExtraction( private RadialMenuButton RecursiveContainerExtraction(
SpriteSystem sprites, SpriteSystem sprites,
ICollection<Control> rootControlChildren, ICollection<Control> rootControlChildren,
RadialMenuNestedLayerOption model, RadialMenuNestedLayerOption model,
@@ -112,8 +114,8 @@ public sealed partial class SimpleRadialMenu : RadialMenu
return thisLayerLinkButton; return thisLayerLinkButton;
} }
private RadialMenuTextureButton ConvertToButton( private RadialMenuButton ConvertToButton(
RadialMenuOption model, RadialMenuOptionBase model,
SpriteSystem sprites, SpriteSystem sprites,
SimpleRadialMenuSettings settings, SimpleRadialMenuSettings settings,
bool haveNested bool haveNested
@@ -121,29 +123,26 @@ public sealed partial class SimpleRadialMenu : RadialMenu
{ {
var button = settings.UseSectors var button = settings.UseSectors
? ConvertToButtonWithSector(model, settings) ? ConvertToButtonWithSector(model, settings)
: new RadialMenuTextureButton(); : new RadialMenuButton();
button.SetSize = new Vector2(64f, 64f); button.SetSize = new Vector2(64f, 64f);
button.ToolTip = model.ToolTip; button.ToolTip = model.ToolTip;
if (model.Sprite != null) var imageControl = model.IconSpecifier switch
{ {
var scale = Vector2.One; RadialMenuTextureIconSpecifier textureSpecifier => CreateTexture(textureSpecifier.Sprite, sprites),
RadialMenuEntityIconSpecifier entitySpecifier => CreateSpriteView(entitySpecifier.Entity),
RadialMenuEntityPrototypeIconSpecifier entProtoSpecifier => CreateEntityPrototypeView(entProtoSpecifier.ProtoId),
_ => null
};
var texture = sprites.Frame0(model.Sprite); if(imageControl != null)
if (texture.Width <= 32) button.AddChild(imageControl);
{
scale *= 2;
}
button.TextureNormal = texture; if (model is RadialMenuActionOptionBase actionOption)
button.Scale = scale;
}
if (model is RadialMenuActionOption actionOption)
{ {
button.OnPressed += _ => button.OnPressed += _ =>
{ {
actionOption.OnPressed?.Invoke(); actionOption.OnPressed?.Invoke();
if(!haveNested) if (!haveNested)
Close(); Close();
}; };
} }
@@ -151,9 +150,53 @@ public sealed partial class SimpleRadialMenu : RadialMenu
return button; return button;
} }
private static RadialMenuTextureButtonWithSector ConvertToButtonWithSector(RadialMenuOption model, SimpleRadialMenuSettings settings) private Control CreateEntityPrototypeView(EntProtoId protoId)
{ {
var button = new RadialMenuTextureButtonWithSector var entProtoView = new EntityPrototypeView
{
SetSize = new Vector2(48, 48),
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Stretch = SpriteView.StretchMode.Fill,
};
entProtoView.SetPrototype(protoId);
return entProtoView;
}
private static Control CreateSpriteView(EntityUid entityForSpriteView)
{
var entView = new SpriteView
{
SetSize = new Vector2(48, 48),
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Stretch = SpriteView.StretchMode.Fill,
};
entView.SetEntity(entityForSpriteView);
return entView;
}
private static Control CreateTexture(SpriteSpecifier spriteSpecifier, SpriteSystem sprites)
{
var scale = Vector2.One;
var texture = sprites.Frame0(spriteSpecifier);
if (texture.Width <= 32)
{
scale *= 2;
}
var imageControl = new TextureRect()
{
Texture = texture,
TextureScale = scale
};
return imageControl;
}
private static RadialMenuButtonWithSector ConvertToButtonWithSector(RadialMenuOptionBase model, SimpleRadialMenuSettings settings)
{
var button = new RadialMenuButtonWithSector
{ {
DrawBorder = settings.DisplayBorders, DrawBorder = settings.DisplayBorders,
DrawBackground = !settings.NoBackground DrawBackground = !settings.NoBackground
@@ -228,32 +271,99 @@ public sealed partial class SimpleRadialMenu : RadialMenu
} }
/// <summary>
public abstract class RadialMenuOption /// Abstract representation of a way to specify icon in radial menu.
/// </summary>
public abstract record RadialMenuIconSpecifier
{ {
public string? ToolTip { get; init; } /// <summary> Use entity prototype viewer. </summary>
public static RadialMenuIconSpecifier? With(EntProtoId? protoId)
{
if (protoId is null)
return null;
public SpriteSpecifier? Sprite { get; init; } return new RadialMenuEntityPrototypeIconSpecifier(protoId.Value);
public Color? BackgroundColor { get; set; } }
public Color? HoverBackgroundColor { get; set; }
/// <summary> Use simple texture icon. </summary>
public static RadialMenuIconSpecifier? With(SpriteSpecifier? sprite)
{
if (sprite == null)
return null;
return new RadialMenuTextureIconSpecifier(sprite);
}
/// <summary> Use entity sprite viewer. </summary>
public static RadialMenuIconSpecifier? With(EntityUid? entity)
{
if (entity == null)
return null;
return new RadialMenuEntityIconSpecifier(entity.Value);
}
} }
public abstract class RadialMenuActionOption(Action onPressed) : RadialMenuOption /// <summary> Marker that <see cref="SpriteView"/> should be used to display radial menu icon. </summary>
public sealed record RadialMenuEntityIconSpecifier(EntityUid Entity) : RadialMenuIconSpecifier;
/// <summary> Marker that <see cref="TextureRect"/> should be used to display radial menu icon. </summary>
public sealed record RadialMenuTextureIconSpecifier(SpriteSpecifier Sprite) : RadialMenuIconSpecifier;
/// <summary> Marker that <see cref="EntityPrototypeView"/> should be used to display radial menu icon. </summary>
public sealed record RadialMenuEntityPrototypeIconSpecifier(EntProtoId ProtoId) : RadialMenuIconSpecifier;
/// <summary> Container for common options for radial menu button. </summary>
public abstract class RadialMenuOptionBase
{ {
/// <summary> Tooltip to be displayed when button is hovered. </summary>
public string? ToolTip { get; init; }
/// <summary>
/// Color for button background.
/// Is used only with sector radial (<see cref="SimpleRadialMenuSettings.UseSectors"/>).
/// </summary>
public Color? BackgroundColor { get; set; }
/// <summary>
/// Color for button background when it is hovered.
/// Is used only with sector radial (<see cref="SimpleRadialMenuSettings.UseSectors"/>).
/// </summary>
public Color? HoverBackgroundColor { get; set; }
/// <summary>
/// Specifier that describes icon to be used for radial menu button.
/// </summary>
public RadialMenuIconSpecifier? IconSpecifier { get; set; }
}
/// <summary> Base type for model of radial menu button with some action on button pressed. </summary>
/// <param name="onPressed"></param>
public abstract class RadialMenuActionOptionBase(Action onPressed) : RadialMenuOptionBase
{
/// <summary> Action to be executed on button press. </summary>
public Action OnPressed { get; } = onPressed; public Action OnPressed { get; } = onPressed;
} }
public sealed class RadialMenuActionOption<T>(Action<T> onPressed, T data) /// <summary> Strong-typed model for radial menu button with action, stores provided data to be used upon button press. </summary>
: RadialMenuActionOption(onPressed: () => onPressed(data)); public sealed class RadialMenuActionOption<T>(Action<T> onPressed, T data) : RadialMenuActionOptionBase(onPressed: () => onPressed(data));
public sealed class RadialMenuNestedLayerOption(IReadOnlyCollection<RadialMenuOption> nested, float containerRadius = 100) /// <summary>
: RadialMenuOption /// Model for radial menu button that represents reference for next layer of radial buttons.
/// </summary>
/// <param name="nested">List of button models for next layer of menu.</param>
/// <param name="containerRadius">Radius for radial menu buttons of next layer.</param>
public sealed class RadialMenuNestedLayerOption(IReadOnlyCollection<RadialMenuOptionBase> nested, float containerRadius = 100) : RadialMenuOptionBase
{ {
/// <summary> Radius for radial menu buttons of next layer. </summary>
public float? ContainerRadius { get; } = containerRadius; public float? ContainerRadius { get; } = containerRadius;
public IReadOnlyCollection<RadialMenuOption> Nested { get; } = nested; /// <summary> List of button models for next layer of menu. </summary>
public IReadOnlyCollection<RadialMenuOptionBase> Nested { get; } = nested;
} }
/// <summary>
/// Additional settings for radial menu render.
/// </summary>
public sealed class SimpleRadialMenuSettings public sealed class SimpleRadialMenuSettings
{ {
/// <summary> /// <summary>

View File

@@ -16,6 +16,7 @@ using Content.Shared.Input;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.Audio; using Robust.Client.Audio;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers; using Robust.Client.UserInterface.Controllers;
@@ -37,6 +38,7 @@ public sealed class AHelpUIController: UIController, IOnSystemChanged<BwoinkSyst
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!; [Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IInputManager _input = default!;
[UISystemDependency] private readonly AudioSystem _audio = default!; [UISystemDependency] private readonly AudioSystem _audio = default!;
private BwoinkSystem? _bwoinkSystem; private BwoinkSystem? _bwoinkSystem;
@@ -98,15 +100,13 @@ public sealed class AHelpUIController: UIController, IOnSystemChanged<BwoinkSyst
_bwoinkSystem = system; _bwoinkSystem = system;
_bwoinkSystem.OnBwoinkTextMessageRecieved += ReceivedBwoink; _bwoinkSystem.OnBwoinkTextMessageRecieved += ReceivedBwoink;
CommandBinds.Builder _input.SetInputCommand(ContentKeyFunctions.OpenAHelp,
.Bind(ContentKeyFunctions.OpenAHelp, InputCmdHandler.FromDelegate(_ => ToggleWindow()));
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
.Register<AHelpUIController>();
} }
public void OnSystemUnloaded(BwoinkSystem system) public void OnSystemUnloaded(BwoinkSystem system)
{ {
CommandBinds.Unregister<AHelpUIController>(); _input.SetInputCommand(ContentKeyFunctions.OpenAHelp, null);
DebugTools.Assert(_bwoinkSystem != null); DebugTools.Assert(_bwoinkSystem != null);
_bwoinkSystem!.OnBwoinkTextMessageRecieved -= ReceivedBwoink; _bwoinkSystem!.OnBwoinkTextMessageRecieved -= ReceivedBwoink;

View File

@@ -116,8 +116,9 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
keyword = EndDoubleQuote.Replace(keyword, "(?<!\\w)"); keyword = EndDoubleQuote.Replace(keyword, "(?<!\\w)");
} }
// Make sure any name tagged as ours gets highlighted only when others say it. // Make sure the character's name is highlighted only when mentioned directly (eg. it's said by someone),
keyword = StartAtSign.Replace(keyword, "(?<=(?<=/name.*)|(?<=,.*\"\".*))"); // for example in 'Name Surname says, "..."' 'Name Surname' won't be highlighted.
keyword = StartAtSign.Replace(keyword, @"(?<=(?<=^.?OOC:.*:.*)|(?<=,.*"".*)|(?<=\n.*))");
_highlights.Add(keyword); _highlights.Add(keyword);
} }

View File

@@ -18,7 +18,6 @@ namespace Content.Client.UserInterface.Systems.Emotes;
[UsedImplicitly] [UsedImplicitly]
public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayState> public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayState>
{ {
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -133,12 +132,12 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
_menu = null; _menu = null;
} }
private IEnumerable<RadialMenuOption> ConvertToButtons(IEnumerable<EmotePrototype> emotePrototypes) private IEnumerable<RadialMenuOptionBase> ConvertToButtons(IEnumerable<EmotePrototype> emotePrototypes)
{ {
var whitelistSystem = EntitySystemManager.GetEntitySystem<EntityWhitelistSystem>(); var whitelistSystem = EntitySystemManager.GetEntitySystem<EntityWhitelistSystem>();
var player = _playerManager.LocalSession?.AttachedEntity; var player = _playerManager.LocalSession?.AttachedEntity;
Dictionary<EmoteCategory, List<RadialMenuOption>> emotesByCategory = new(); Dictionary<EmoteCategory, List<RadialMenuOptionBase>> emotesByCategory = new();
foreach (var emote in emotePrototypes) foreach (var emote in emotePrototypes)
{ {
if(emote.Category == EmoteCategory.Invalid) if(emote.Category == EmoteCategory.Invalid)
@@ -158,19 +157,19 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
if (!emotesByCategory.TryGetValue(emote.Category, out var list)) if (!emotesByCategory.TryGetValue(emote.Category, out var list))
{ {
list = new List<RadialMenuOption>(); list = new List<RadialMenuOptionBase>();
emotesByCategory.Add(emote.Category, list); emotesByCategory.Add(emote.Category, list);
} }
var actionOption = new RadialMenuActionOption<EmotePrototype>(HandleRadialButtonClick, emote) var actionOption = new RadialMenuActionOption<EmotePrototype>(HandleRadialButtonClick, emote)
{ {
Sprite = emote.Icon, IconSpecifier = RadialMenuIconSpecifier.With(emote.Icon),
ToolTip = Loc.GetString(emote.Name) ToolTip = Loc.GetString(emote.Name)
}; };
list.Add(actionOption); list.Add(actionOption);
} }
var models = new RadialMenuOption[emotesByCategory.Count]; var models = new RadialMenuOptionBase[emotesByCategory.Count];
var i = 0; var i = 0;
foreach (var (key, list) in emotesByCategory) foreach (var (key, list) in emotesByCategory)
{ {
@@ -178,7 +177,7 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
models[i] = new RadialMenuNestedLayerOption(list) models[i] = new RadialMenuNestedLayerOption(list)
{ {
Sprite = tuple.Sprite, IconSpecifier = RadialMenuIconSpecifier.With(tuple.Sprite),
ToolTip = Loc.GetString(tuple.Tooltip) ToolTip = Loc.GetString(tuple.Tooltip)
}; };
i++; i++;
@@ -189,6 +188,6 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
private void HandleRadialButtonClick(EmotePrototype prototype) private void HandleRadialButtonClick(EmotePrototype prototype)
{ {
_entityManager.RaisePredictiveEvent(new PlayEmoteMessage(prototype.ID)); EntityManager.RaisePredictiveEvent(new PlayEmoteMessage(prototype.ID));
} }
} }

View File

@@ -120,7 +120,7 @@ namespace Content.Client.VendingMachines.UI
{ {
var entry = inventory[i]; var entry = inventory[i];
if (!_prototypeManager.TryIndex(entry.ID, out var prototype)) if (!_prototypeManager.Resolve(entry.ID, out var prototype))
{ {
_amounts[entry.ID] = 0; _amounts[entry.ID] = 0;
continue; continue;

View File

@@ -273,7 +273,7 @@ namespace Content.Client.Verbs.UI
if (verbElement.SubMenu == null) if (verbElement.SubMenu == null)
{ {
var popupElement = new ConfirmationMenuElement(verb, "Confirm"); var popupElement = new ConfirmationMenuElement(verb, Loc.GetString("generic-confirm"));
verbElement.SubMenu = new ContextMenuPopup(_context, verbElement); verbElement.SubMenu = new ContextMenuPopup(_context, verbElement);
_context.AddElement(verbElement.SubMenu, popupElement); _context.AddElement(verbElement.SubMenu, popupElement);
} }

View File

@@ -21,6 +21,7 @@ public static partial class PoolManager
(CCVars.NPCMaxUpdates.Name, "999999"), (CCVars.NPCMaxUpdates.Name, "999999"),
(CVars.ThreadParallelCount.Name, "1"), (CVars.ThreadParallelCount.Name, "1"),
(CCVars.GameRoleTimers.Name, "false"), (CCVars.GameRoleTimers.Name, "false"),
(CCVars.GameRoleLoadoutTimers.Name, "false"),
(CCVars.GameRoleWhitelist.Name, "false"), (CCVars.GameRoleWhitelist.Name, "false"),
(CCVars.GridFill.Name, "false"), (CCVars.GridFill.Name, "false"),
(CCVars.PreloadGrids.Name, "false"), (CCVars.PreloadGrids.Name, "false"),

View File

@@ -0,0 +1,417 @@
using System.Linq;
using System.Numerics;
using Content.Server.Atmos;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
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.Utility;
namespace Content.IntegrationTests.Tests.Atmos;
/// <summary>
/// Tests for AtmosphereSystem.DeltaPressure and surrounding systems
/// handling the DeltaPressureComponent.
/// </summary>
[TestFixture]
[TestOf(typeof(DeltaPressureSystem))]
public sealed class DeltaPressureTest
{
#region Prototypes
[TestPrototypes]
private const string Prototypes = @"
- type: entity
parent: BaseStructure
id: DeltaPressureSolidTest
placement:
mode: SnapgridCenter
snap:
- Wall
components:
- type: Physics
bodyType: Static
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: ""-0.5,-0.5,0.5,0.5""
mask:
- FullTileMask
layer:
- WallLayer
density: 1000
- type: Airtight
- type: DeltaPressure
minPressure: 15000
minPressureDelta: 10000
scalingType: Threshold
baseDamage:
types:
Structural: 1000
- type: Damageable
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 300
behaviors:
- !type:SpawnEntitiesBehavior
spawn:
Girder:
min: 1
max: 1
- !type:DoActsBehavior
acts: [ ""Destruction"" ]
- type: entity
parent: DeltaPressureSolidTest
id: DeltaPressureSolidTestNoAutoJoin
components:
- type: DeltaPressure
autoJoinProcessingList: false
- type: entity
parent: DeltaPressureSolidTest
id: DeltaPressureSolidTestAbsolute
components:
- type: DeltaPressure
minPressure: 10000
minPressureDelta: 15000
scalingType: Threshold
baseDamage:
types:
Structural: 1000
";
#endregion
private readonly ResPath _testMap = new("Maps/Test/Atmospherics/DeltaPressure/deltapressuretest.yml");
/// <summary>
/// Asserts that an entity with a DeltaPressureComponent with autoJoinProcessingList
/// set to true is automatically added to the DeltaPressure processing list
/// on the grid's GridAtmosphereComponent.
///
/// Also asserts that an entity with a DeltaPressureComponent with autoJoinProcessingList
/// set to false is not automatically added to the DeltaPressure processing list.
/// </summary>
[Test]
public async Task ProcessingListAutoJoinTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var entMan = server.EntMan;
var mapLoader = entMan.System<MapLoaderSystem>();
var atmosphereSystem = entMan.System<AtmosphereSystem>();
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
Entity<MapGridComponent> grid = default;
Entity<DeltaPressureComponent> dpEnt;
// Load our test map in and assert that it exists.
await server.WaitPost(() =>
{
#pragma warning disable NUnit2045
Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
$"Failed to load map {_testMap}.");
Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
#pragma warning restore NUnit2045
grid = gridSet.First();
});
await server.WaitAssertion(() =>
{
var uid = entMan.SpawnAtPosition("DeltaPressureSolidTest", new EntityCoordinates(grid.Owner, Vector2.Zero));
dpEnt = new Entity<DeltaPressureComponent>(uid, entMan.GetComponent<DeltaPressureComponent>(uid));
Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have automatically joined!");
entMan.DeleteEntity(uid);
Assert.That(!atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was still in processing list after deletion!");
});
await pair.CleanReturnAsync();
}
/// <summary>
/// Asserts that an entity that doesn't need to be damaged by DeltaPressure
/// is not damaged by DeltaPressure.
/// </summary>
[Test]
public async Task ProcessingDeltaStandbyTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var entMan = server.EntMan;
var mapLoader = entMan.System<MapLoaderSystem>();
var atmosphereSystem = entMan.System<AtmosphereSystem>();
var transformSystem = entMan.System<SharedTransformSystem>();
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
Entity<MapGridComponent> grid = default;
Entity<DeltaPressureComponent> dpEnt = default;
TileAtmosphere tile = null!;
AtmosDirection direction = default;
// Load our test map in and assert that it exists.
await server.WaitPost(() =>
{
#pragma warning disable NUnit2045
Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
$"Failed to load map {_testMap}.");
Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
#pragma warning restore NUnit2045
grid = gridSet.First();
var uid = entMan.SpawnAtPosition("DeltaPressureSolidTest", new EntityCoordinates(grid.Owner, Vector2.Zero));
dpEnt = new Entity<DeltaPressureComponent>(uid, entMan.GetComponent<DeltaPressureComponent>(uid));
Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
});
for (var i = 0; i < Atmospherics.Directions; i++)
{
await server.WaitPost(() =>
{
var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
var gridAtmosComp = entMan.GetComponent<GridAtmosphereComponent>(grid);
direction = (AtmosDirection)(1 << i);
var offsetIndices = indices.Offset(direction);
tile = gridAtmosComp.Tiles[offsetIndices];
Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
var toPressurize = dpEnt.Comp!.MinPressureDelta - 10;
var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
});
await server.WaitRunTicks(30);
// Entity should exist, if it took one tick of damage then it should be instantly destroyed.
await server.WaitAssertion(() =>
{
Assert.That(!entMan.Deleted(dpEnt), $"{dpEnt} should still exist after experiencing non-threshold pressure from {direction} side!");
tile.Air!.Clear();
});
await server.WaitRunTicks(30);
}
await pair.CleanReturnAsync();
}
/// <summary>
/// Asserts that an entity that needs to be damaged by DeltaPressure
/// is damaged by DeltaPressure when the pressure is above the threshold.
/// </summary>
[Test]
public async Task ProcessingDeltaDamageTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var entMan = server.EntMan;
var mapLoader = entMan.System<MapLoaderSystem>();
var atmosphereSystem = entMan.System<AtmosphereSystem>();
var transformSystem = entMan.System<SharedTransformSystem>();
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
Entity<MapGridComponent> grid = default;
Entity<DeltaPressureComponent> dpEnt = default;
TileAtmosphere tile = null!;
AtmosDirection direction = default;
// Load our test map in and assert that it exists.
await server.WaitPost(() =>
{
#pragma warning disable NUnit2045
Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
$"Failed to load map {_testMap}.");
Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
#pragma warning restore NUnit2045
grid = gridSet.First();
});
for (var i = 0; i < Atmospherics.Directions; i++)
{
await server.WaitPost(() =>
{
// Need to spawn an entity each run to ensure it works for all directions.
var uid = entMan.SpawnAtPosition("DeltaPressureSolidTest", new EntityCoordinates(grid.Owner, Vector2.Zero));
dpEnt = new Entity<DeltaPressureComponent>(uid, entMan.GetComponent<DeltaPressureComponent>(uid));
Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
var gridAtmosComp = entMan.GetComponent<GridAtmosphereComponent>(grid);
direction = (AtmosDirection)(1 << i);
var offsetIndices = indices.Offset(direction);
tile = gridAtmosComp.Tiles[offsetIndices];
Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
var toPressurize = dpEnt.Comp!.MinPressureDelta + 10;
var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
});
await server.WaitRunTicks(30);
// Entity should exist, if it took one tick of damage then it should be instantly destroyed.
await server.WaitAssertion(() =>
{
Assert.That(entMan.Deleted(dpEnt), $"{dpEnt} still exists after experiencing threshold pressure from {direction} side!");
tile.Air!.Clear();
});
await server.WaitRunTicks(30);
}
await pair.CleanReturnAsync();
}
/// <summary>
/// Asserts that an entity that doesn't need to be damaged by DeltaPressure
/// is not damaged by DeltaPressure when using absolute pressure thresholds.
/// </summary>
[Test]
public async Task ProcessingAbsoluteStandbyTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var entMan = server.EntMan;
var mapLoader = entMan.System<MapLoaderSystem>();
var atmosphereSystem = entMan.System<AtmosphereSystem>();
var transformSystem = entMan.System<SharedTransformSystem>();
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
Entity<MapGridComponent> grid = default;
Entity<DeltaPressureComponent> dpEnt = default;
TileAtmosphere tile = null!;
AtmosDirection direction = default;
await server.WaitPost(() =>
{
#pragma warning disable NUnit2045
Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
$"Failed to load map {_testMap}.");
Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
#pragma warning restore NUnit2045
grid = gridSet.First();
var uid = entMan.SpawnAtPosition("DeltaPressureSolidTestAbsolute", new EntityCoordinates(grid.Owner, Vector2.Zero));
dpEnt = new Entity<DeltaPressureComponent>(uid, entMan.GetComponent<DeltaPressureComponent>(uid));
Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
});
for (var i = 0; i < Atmospherics.Directions; i++)
{
await server.WaitPost(() =>
{
var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
var gridAtmosComp = entMan.GetComponent<GridAtmosphereComponent>(grid);
direction = (AtmosDirection)(1 << i);
var offsetIndices = indices.Offset(direction);
tile = gridAtmosComp.Tiles[offsetIndices];
Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
var toPressurize = dpEnt.Comp!.MinPressure - 10; // just below absolute threshold
var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
});
await server.WaitRunTicks(30);
await server.WaitAssertion(() =>
{
Assert.That(!entMan.Deleted(dpEnt), $"{dpEnt} should still exist after experiencing non-threshold absolute pressure from {direction} side!");
tile.Air!.Clear();
});
await server.WaitRunTicks(30);
}
await pair.CleanReturnAsync();
}
/// <summary>
/// Asserts that an entity that needs to be damaged by DeltaPressure
/// is damaged by DeltaPressure when the pressure is above the absolute threshold.
/// </summary>
[Test]
public async Task ProcessingAbsoluteDamageTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var entMan = server.EntMan;
var mapLoader = entMan.System<MapLoaderSystem>();
var atmosphereSystem = entMan.System<AtmosphereSystem>();
var transformSystem = entMan.System<SharedTransformSystem>();
var deserializationOptions = DeserializationOptions.Default with { InitializeMaps = true };
Entity<MapGridComponent> grid = default;
Entity<DeltaPressureComponent> dpEnt = default;
TileAtmosphere tile = null!;
AtmosDirection direction = default;
await server.WaitPost(() =>
{
#pragma warning disable NUnit2045
Assert.That(mapLoader.TryLoadMap(_testMap, out _, out var gridSet, deserializationOptions),
$"Failed to load map {_testMap}.");
Assert.That(gridSet, Is.Not.Null, "There were no grids loaded from the map!");
#pragma warning restore NUnit2045
grid = gridSet.First();
});
for (var i = 0; i < Atmospherics.Directions; i++)
{
await server.WaitPost(() =>
{
// Spawn fresh entity each iteration to verify all directions work
var uid = entMan.SpawnAtPosition("DeltaPressureSolidTestAbsolute", new EntityCoordinates(grid.Owner, Vector2.Zero));
dpEnt = new Entity<DeltaPressureComponent>(uid, entMan.GetComponent<DeltaPressureComponent>(uid));
Assert.That(atmosphereSystem.IsDeltaPressureEntityInList(grid.Owner, dpEnt), "Entity was not in processing list when it should have been added!");
var indices = transformSystem.GetGridOrMapTilePosition(dpEnt);
var gridAtmosComp = entMan.GetComponent<GridAtmosphereComponent>(grid);
direction = (AtmosDirection)(1 << i);
var offsetIndices = indices.Offset(direction);
tile = gridAtmosComp.Tiles[offsetIndices];
Assert.That(tile.Air, Is.Not.Null, $"Tile at {offsetIndices} should have air!");
// Above absolute threshold but below delta threshold to ensure absolute alone causes damage
var toPressurize = dpEnt.Comp!.MinPressure + 10;
var moles = (toPressurize * tile.Air.Volume) / (Atmospherics.R * Atmospherics.T20C);
tile.Air!.AdjustMoles(Gas.Nitrogen, moles);
});
await server.WaitRunTicks(30);
await server.WaitAssertion(() =>
{
Assert.That(entMan.Deleted(dpEnt), $"{dpEnt} still exists after experiencing threshold absolute pressure from {direction} side!");
tile.Air!.Clear();
});
await server.WaitRunTicks(30);
}
await pair.CleanReturnAsync();
}
}

View File

@@ -0,0 +1,85 @@
using System.Linq;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Atmos;
[TestFixture]
[TestOf(typeof(Atmospherics))]
public sealed class GasArrayTest
{
private const string GasTankTestDummyId = "GasTankTestDummy";
private const string GasTankLegacyTestDummyId = "GasTankLegacyTestDummy";
[TestPrototypes]
private const string Prototypes = $@"
- type: entity
id: {GasTankTestDummyId}
components:
- type: GasTank
air:
volume: 5
moles:
Frezon: 20
Oxygen: 10
- type: entity
id: {GasTankLegacyTestDummyId}
components:
- type: GasTank
air:
volume: 5
moles:
- 0
- 0
- 0
- 10
";
[Test]
public async Task TestGasArrayDeserialization()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var compFactory = server.ResolveDependency<IComponentFactory>();
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
await server.WaitAssertion(() =>
{
var gasTank = prototypeManager.Index(GasTankTestDummyId);
Assert.Multiple(() =>
{
Assert.That(gasTank.TryGetComponent<GasTankComponent>(out var gasTankComponent, compFactory));
Assert.That(gasTankComponent!.Air.GetMoles(Gas.Oxygen), Is.EqualTo(10));
Assert.That(gasTankComponent!.Air.GetMoles(Gas.Frezon), Is.EqualTo(20));
foreach (var gas in Enum.GetValues<Gas>().Where(p => p != Gas.Oxygen && p != Gas.Frezon))
{
Assert.That(gasTankComponent!.Air.GetMoles(gas), Is.EqualTo(0));
}
});
var legacyGasTank = prototypeManager.Index(GasTankLegacyTestDummyId);
Assert.Multiple(() =>
{
Assert.That(legacyGasTank.TryGetComponent<GasTankComponent>(out var gasTankComponent, compFactory));
Assert.That(gasTankComponent!.Air.GetMoles(3), Is.EqualTo(10));
// Iterate through all other gases: check for 0 values
for (var i = 0; i < Atmospherics.AdjustedNumberOfGases; i++)
{
if (i == 3) // our case with a value.
continue;
Assert.That(gasTankComponent!.Air.GetMoles(i), Is.EqualTo(0));
}
});
});
await pair.CleanReturnAsync();
}
}

View File

@@ -146,8 +146,8 @@ public sealed class SuicideCommandTests
mobThresholdsComp = entManager.GetComponent<MobThresholdsComponent>(player); mobThresholdsComp = entManager.GetComponent<MobThresholdsComponent>(player);
damageableComp = entManager.GetComponent<DamageableComponent>(player); damageableComp = entManager.GetComponent<DamageableComponent>(player);
if (protoMan.TryIndex(DamageType, out var slashProto)) var slashProto = protoMan.Index(DamageType);
damageableSystem.TryChangeDamage(player, new DamageSpecifier(slashProto, FixedPoint2.New(46.5))); damageableSystem.TryChangeDamage(player, new DamageSpecifier(slashProto, FixedPoint2.New(46.5)));
}); });
// Check that running the suicide command kills the player // Check that running the suicide command kills the player

View File

@@ -0,0 +1,49 @@
using Content.IntegrationTests.Tests.Interaction;
using Content.Server.Construction.Components;
using Content.Shared.Temperature;
namespace Content.IntegrationTests.Tests.Construction.Interaction;
public sealed class EdgeClobbering : InteractionTest
{
[TestPrototypes]
private const string Prototypes = @"
- type: constructionGraph
id: ExampleGraph
start: A
graph:
- node: A
edges:
- to: B
steps:
- tool: Anchoring
doAfter: 1
- to: C
steps:
- tool: Screwing
doAfter: 1
- node: B
- node: C
- type: entity
id: ExampleEntity
components:
- type: Construction
graph: ExampleGraph
node: A
";
[Test]
public async Task EnsureNoEdgeClobbering()
{
await SpawnTarget("ExampleEntity");
var sTarget = SEntMan.GetEntity(Target!.Value);
await InteractUsing(Screw, false);
SEntMan.EventBus.RaiseLocalEvent(sTarget, new OnTemperatureChangeEvent(0f, 0f, 0f));
await AwaitDoAfters();
Assert.That(SEntMan.GetComponent<ConstructionComponent>(sTarget).Node, Is.EqualTo("C"));
}
}

View File

@@ -27,8 +27,11 @@ public sealed class ContrabandTest
if (!proto.TryGetComponent<ContrabandComponent>(out var contraband, componentFactory)) if (!proto.TryGetComponent<ContrabandComponent>(out var contraband, componentFactory))
continue; continue;
Assert.That(protoMan.TryIndex(contraband.Severity, out var severity, false), if (!protoMan.TryIndex(contraband.Severity, out var severity))
@$"{proto.ID} has a ContrabandComponent with a unknown severity."); {
Assert.Fail($"{proto.ID} has a ContrabandComponent with a unknown severity.");
continue;
}
if (!severity.ShowDepartmentsAndJobs) if (!severity.ShowDepartmentsAndJobs)
continue; continue;

View File

@@ -264,9 +264,10 @@ public abstract partial class InteractionTest
/// <param name="id">The entity or stack prototype to spawn and place into the users hand</param> /// <param name="id">The entity or stack prototype to spawn and place into the users hand</param>
/// <param name="quantity">The number of entities to spawn. If the prototype is a stack, this sets the stack count.</param> /// <param name="quantity">The number of entities to spawn. If the prototype is a stack, this sets the stack count.</param>
/// <param name="awaitDoAfters">Whether or not to wait for any do-afters to complete</param> /// <param name="awaitDoAfters">Whether or not to wait for any do-afters to complete</param>
protected async Task InteractUsing(string id, int quantity = 1, bool awaitDoAfters = true) /// <param name="altInteract">If true, perform an alternate interaction instead of a standard one.
protected async Task InteractUsing(string id, int quantity = 1, bool awaitDoAfters = true, bool altInteract = false)
{ {
await InteractUsing((id, quantity), awaitDoAfters); await InteractUsing((id, quantity), awaitDoAfters, altInteract);
} }
/// <summary> /// <summary>
@@ -274,7 +275,8 @@ public abstract partial class InteractionTest
/// </summary> /// </summary>
/// <param name="entity">The entity type & quantity to spawn and place into the users hand</param> /// <param name="entity">The entity type & quantity to spawn and place into the users hand</param>
/// <param name="awaitDoAfters">Whether or not to wait for any do-afters to complete</param> /// <param name="awaitDoAfters">Whether or not to wait for any do-afters to complete</param>
protected async Task InteractUsing(EntitySpecifier entity, bool awaitDoAfters = true) /// <param name="altInteract">If true, perform an alternate interaction instead of a standard one.
protected async Task InteractUsing(EntitySpecifier entity, bool awaitDoAfters = true, bool altInteract = false)
{ {
// For every interaction, we will also examine the entity, just in case this breaks something, somehow. // For every interaction, we will also examine the entity, just in case this breaks something, somehow.
// (e.g., servers attempt to assemble construction examine hints). // (e.g., servers attempt to assemble construction examine hints).
@@ -284,18 +286,19 @@ public abstract partial class InteractionTest
} }
await PlaceInHands(entity); await PlaceInHands(entity);
await Interact(awaitDoAfters); await Interact(awaitDoAfters, altInteract);
} }
/// <summary> /// <summary>
/// Interact with an entity using the currently held entity. /// Interact with an entity using the currently held entity.
/// </summary> /// </summary>
/// <param name="awaitDoAfters">Whether or not to wait for any do-afters to complete</param> /// <param name="awaitDoAfters">Whether or not to wait for any do-afters to complete</param>
protected async Task Interact(bool awaitDoAfters = true) /// <param name="altInteract">If true, performs an alternate interaction instead of a standard one.
protected async Task Interact(bool awaitDoAfters = true, bool altInteract = false)
{ {
if (Target == null || !Target.Value.IsClientSide()) if (Target == null || !Target.Value.IsClientSide())
{ {
await Interact(Target, TargetCoords, awaitDoAfters); await Interact(Target, TargetCoords, awaitDoAfters, altInteract);
return; return;
} }
@@ -311,23 +314,23 @@ public abstract partial class InteractionTest
await CheckTargetChange(); await CheckTargetChange();
} }
/// <inheritdoc cref="Interact(EntityUid?,EntityCoordinates,bool)"/> /// <inheritdoc cref="Interact(EntityUid?,EntityCoordinates,bool,bool)"/>
protected async Task Interact(NetEntity? target, NetCoordinates coordinates, bool awaitDoAfters = true) protected async Task Interact(NetEntity? target, NetCoordinates coordinates, bool awaitDoAfters = true, bool altInteract = false)
{ {
Assert.That(SEntMan.TryGetEntity(target, out var sTarget) || target == null); Assert.That(SEntMan.TryGetEntity(target, out var sTarget) || target == null);
var coords = SEntMan.GetCoordinates(coordinates); var coords = SEntMan.GetCoordinates(coordinates);
Assert.That(coords.IsValid(SEntMan)); Assert.That(coords.IsValid(SEntMan));
await Interact(sTarget, coords, awaitDoAfters); await Interact(sTarget, coords, awaitDoAfters, altInteract);
} }
/// <summary> /// <summary>
/// Interact with an entity using the currently held entity. /// Interact with an entity using the currently held entity.
/// </summary> /// </summary>
protected async Task Interact(EntityUid? target, EntityCoordinates coordinates, bool awaitDoAfters = true) protected async Task Interact(EntityUid? target, EntityCoordinates coordinates, bool awaitDoAfters = true, bool altInteract = false)
{ {
Assert.That(SEntMan.TryGetEntity(Player, out var player)); Assert.That(SEntMan.TryGetEntity(Player, out var player));
await Server.WaitPost(() => InteractSys.UserInteraction(player!.Value, coordinates, target)); await Server.WaitPost(() => InteractSys.UserInteraction(player!.Value, coordinates, target, altInteract: altInteract));
await RunTicks(1); await RunTicks(1);
if (awaitDoAfters) if (awaitDoAfters)

View File

@@ -88,14 +88,18 @@ public sealed class LatheTest
// Check each recipe assigned to this lathe // Check each recipe assigned to this lathe
foreach (var recipeId in recipes) foreach (var recipeId in recipes)
{ {
Assert.That(protoMan.TryIndex(recipeId, out var recipeProto)); if (!protoMan.TryIndex(recipeId, out var recipeProto))
{
Assert.Fail($"Lathe recipe '{recipeId}' does not exist");
continue;
}
// Track the total material volume of the recipe // Track the total material volume of the recipe
var totalQuantity = 0; var totalQuantity = 0;
// Check each material called for by the recipe // Check each material called for by the recipe
foreach (var (materialId, quantity) in recipeProto.Materials) foreach (var (materialId, quantity) in recipeProto.Materials)
{ {
Assert.That(protoMan.TryIndex(materialId, out var materialProto)); Assert.That(protoMan.HasIndex(materialId), $"Material '{materialId}' does not exist");
// Make sure the material is accepted by the lathe // Make sure the material is accepted by the lathe
Assert.That(acceptedMaterials, Does.Contain(materialId), $"Lathe {latheProto.ID} has recipe {recipeId} but does not accept any materials containing {materialId}"); Assert.That(acceptedMaterials, Does.Contain(materialId), $"Lathe {latheProto.ID} has recipe {recipeId} but does not accept any materials containing {materialId}");
totalQuantity += quantity; totalQuantity += quantity;

View File

@@ -145,10 +145,7 @@ public sealed partial class MindTests
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
var damageable = entMan.GetComponent<DamageableComponent>(entity); var damageable = entMan.GetComponent<DamageableComponent>(entity);
if (!protoMan.TryIndex(BluntDamageType, out var prototype)) var prototype = protoMan.Index(BluntDamageType);
{
return;
}
damageableSystem.SetDamage(entity, damageable, new DamageSpecifier(prototype, FixedPoint2.New(401))); damageableSystem.SetDamage(entity, damageable, new DamageSpecifier(prototype, FixedPoint2.New(401)));
Assert.That(mindSystem.GetMind(entity, mindContainerComp), Is.EqualTo(mindId)); Assert.That(mindSystem.GetMind(entity, mindContainerComp), Is.EqualTo(mindId));

View File

@@ -0,0 +1,99 @@
using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Content.Shared.Storage.Components;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Nutrition;
public sealed class WaterCoolerInteractionTest : InteractionTest
{
/// <summary>
/// ProtoId of the water cooler entity.
/// </summary>
private static readonly EntProtoId WaterCooler = "WaterCooler";
/// <summary>
/// ProtoId of the paper cup entity dispensed by the water cooler.
/// </summary>
private static readonly EntProtoId PaperCup = "DrinkWaterCup";
/// <summary>
/// ProtoId of the water reagent that is stored in the water cooler.
/// </summary>
private static readonly ProtoId<ReagentPrototype> Water = "Water";
/// <summary>
/// Spawns a water cooler and tests that the player can retrieve a paper cup
/// by interacting with it, and can return the paper cup by alt-interacting with it.
/// </summary>
[Test]
public async Task GetAndReturnCup()
{
// Spawn the water cooler
var cooler = await SpawnTarget(WaterCooler);
// Record how many paper cups are in the cooler
var binComp = Comp<BinComponent>(cooler);
var initialCount = binComp.Items.Count;
Assert.That(binComp.Items, Is.Not.Empty, "Water cooler didn't start with any cups");
// Interact with the water cooler using an empty hand to grab a paper cup
await Interact();
var cup = HandSys.GetActiveItem((SPlayer, Hands));
Assert.Multiple(() =>
{
// Make sure the player is now holding a cup
Assert.That(cup, Is.Not.Null, "Player's hand is empty");
AssertPrototype(PaperCup, SEntMan.GetNetEntity(cup));
// Make sure the number of cups in the cooler has decreased by one
Assert.That(binComp.Items, Has.Count.EqualTo(initialCount - 1), "Number of cups in cooler bin did not decrease by one");
// Make sure the cup isn't somehow still in the cooler too
Assert.That(binComp.Items, Does.Not.Contain(cup));
});
// Alt-interact with the water cooler while holding the cup to put it back
await Interact(altInteract: true);
Assert.Multiple(() =>
{
// Make sure the player's hand is empty
Assert.That(HandSys.ActiveHandIsEmpty((SPlayer, Hands)), "Player's hand is not empty");
// Make sure the count has gone back up by one
Assert.That(binComp.Items, Has.Count.EqualTo(initialCount), "Number of cups in cooler bin did not return to initial count");
// Make sure the cup is in the cooler
Assert.That(binComp.Items, Contains.Item(cup), "Cup was not returned to cooler");
});
}
/// <summary>
/// Spawns a water cooler and gives the player an empty paper cup.
/// Tests that the player can put water into the cup by interacting
/// with the water cooler while holding the cup.
/// </summary>
[Test]
public async Task FillCup()
{
var solutionSys = Server.System<SharedSolutionContainerSystem>();
// Spawn the water cooler
await SpawnTarget(WaterCooler);
// Give the player a cup
var cup = await PlaceInHands(PaperCup);
// Make the player interact with the water cooler using the held cup
await Interact();
// Make sure the cup now contains water
Assert.That(solutionSys.GetTotalPrototypeQuantity(ToServer(cup), Water), Is.GreaterThan(FixedPoint2.Zero),
"Cup does not contain any water");
}
}

View File

@@ -256,8 +256,7 @@ namespace Content.IntegrationTests.Tests
return; return;
var yamlEntities = node["entities"]; var yamlEntities = node["entities"];
if (!protoManager.TryIndex(DoNotMapCategory, out var dnmCategory)) var dnmCategory = protoManager.Index(DoNotMapCategory);
return;
Assert.Multiple(() => Assert.Multiple(() =>
{ {
@@ -266,7 +265,7 @@ namespace Content.IntegrationTests.Tests
var protoId = yamlEntity["proto"].AsString(); var protoId = yamlEntity["proto"].AsString();
// This doesn't properly handle prototype migrations, but thats not a significant issue. // This doesn't properly handle prototype migrations, but thats not a significant issue.
if (!protoManager.TryIndex(protoId, out var proto, false)) if (!protoManager.TryIndex(protoId, out var proto))
continue; continue;
Assert.That(!proto.Categories.Contains(dnmCategory), Assert.That(!proto.Categories.Contains(dnmCategory),

View File

@@ -78,7 +78,11 @@ public static class ClientPackaging
new[] { "Content.Client", "Content.Shared", "Content.Shared.Database" }, new[] { "Content.Client", "Content.Shared", "Content.Shared.Database" },
cancel: cancel); cancel: cancel);
await RobustClientPackaging.WriteClientResources(contentDir, inputPass, cancel); await RobustClientPackaging.WriteClientResources(
contentDir,
inputPass,
SharedPackaging.AdditionalIgnoredResources,
cancel);
inputPass.InjectFinished(); inputPass.InjectFinished();
} }

View File

@@ -25,6 +25,12 @@ public static class ServerPackaging
new PlatformReg("freebsd-x64", "FreeBSD", false), new PlatformReg("freebsd-x64", "FreeBSD", false),
}; };
private static IReadOnlySet<string> ServerContentIgnoresResources { get; } = new HashSet<string>
{
"ServerInfo",
"Changelog",
};
private static List<string> PlatformRids => Platforms private static List<string> PlatformRids => Platforms
.Select(o => o.Rid) .Select(o => o.Rid)
.ToList(); .ToList();
@@ -211,7 +217,11 @@ public static class ServerPackaging
contentAssemblies, contentAssemblies,
cancel: cancel); cancel: cancel);
await RobustServerPackaging.WriteServerResources(contentDir, inputPassResources, cancel); await RobustServerPackaging.WriteServerResources(
contentDir,
inputPassResources,
ServerContentIgnoresResources.Concat(SharedPackaging.AdditionalIgnoredResources).ToHashSet(),
cancel);
if (hybridAcz) if (hybridAcz)
{ {

View File

@@ -0,0 +1,10 @@
namespace Content.Packaging;
public sealed class SharedPackaging
{
public static readonly IReadOnlySet<string> AdditionalIgnoredResources = new HashSet<string>
{
// MapRenderer outputs into Resources. Avoid these getting included in packaging.
"MapImages",
};
}

View File

@@ -45,7 +45,7 @@ namespace Content.Server.Access.Systems
if (!TryComp<IdCardComponent>(ent, out var idCardComp)) if (!TryComp<IdCardComponent>(ent, out var idCardComp))
return; return;
_prototypeManager.TryIndex(args.Args.ChameleonOutfit.Job, out var jobProto); _prototypeManager.Resolve(args.Args.ChameleonOutfit.Job, out var jobProto);
var jobIcon = args.Args.ChameleonOutfit.Icon ?? jobProto?.Icon; var jobIcon = args.Args.ChameleonOutfit.Icon ?? jobProto?.Icon;
var jobName = args.Args.ChameleonOutfit.Name ?? jobProto?.Name ?? ""; var jobName = args.Args.ChameleonOutfit.Name ?? jobProto?.Name ?? "";
@@ -130,7 +130,7 @@ namespace Content.Server.Access.Systems
if (!TryComp<IdCardComponent>(uid, out var idCard)) if (!TryComp<IdCardComponent>(uid, out var idCard))
return; return;
if (!_prototypeManager.TryIndex(args.JobIconId, out var jobIcon)) if (!_prototypeManager.Resolve(args.JobIconId, out var jobIcon))
return; return;
_cardSystem.TryChangeJobIcon(uid, jobIcon, idCard); _cardSystem.TryChangeJobIcon(uid, jobIcon, idCard);

View File

@@ -98,7 +98,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
var targetIdComponent = Comp<IdCardComponent>(targetId); var targetIdComponent = Comp<IdCardComponent>(targetId);
var targetAccessComponent = Comp<AccessComponent>(targetId); var targetAccessComponent = Comp<AccessComponent>(targetId);
var jobProto = targetIdComponent.JobPrototype ?? new ProtoId<AccessLevelPrototype>(string.Empty); var jobProto = targetIdComponent.JobPrototype ?? new ProtoId<JobPrototype>(string.Empty);
if (TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage) if (TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
&& keyStorage.Key is { } key && keyStorage.Key is { } key
&& _record.TryGetRecord<GeneralStationRecord>(key, out var record)) && _record.TryGetRecord<GeneralStationRecord>(key, out var record))
@@ -130,7 +130,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
string newFullName, string newFullName,
string newJobTitle, string newJobTitle,
List<ProtoId<AccessLevelPrototype>> newAccessList, List<ProtoId<AccessLevelPrototype>> newAccessList,
ProtoId<AccessLevelPrototype> newJobProto, ProtoId<JobPrototype> newJobProto,
EntityUid player, EntityUid player,
IdCardConsoleComponent? component = null) IdCardConsoleComponent? component = null)
{ {
@@ -144,7 +144,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
_idCard.TryChangeJobTitle(targetId, newJobTitle, player: player); _idCard.TryChangeJobTitle(targetId, newJobTitle, player: player);
if (_prototype.TryIndex<JobPrototype>(newJobProto, out var job) if (_prototype.TryIndex<JobPrototype>(newJobProto, out var job)
&& _prototype.TryIndex(job.Icon, out var jobIcon)) && _prototype.Resolve(job.Icon, out var jobIcon))
{ {
_idCard.TryChangeJobIcon(targetId, jobIcon, player: player); _idCard.TryChangeJobIcon(targetId, jobIcon, player: player);
_idCard.TryChangeJobDepartment(targetId, job); _idCard.TryChangeJobDepartment(targetId, job);

View File

@@ -82,7 +82,7 @@ public sealed class PresetIdCardSystem : EntitySystem
_cardSystem.TryChangeJobTitle(uid, job.LocalizedName); _cardSystem.TryChangeJobTitle(uid, job.LocalizedName);
_cardSystem.TryChangeJobDepartment(uid, job); _cardSystem.TryChangeJobDepartment(uid, job);
if (_prototypeManager.TryIndex(job.Icon, out var jobIcon)) if (_prototypeManager.Resolve(job.Icon, out var jobIcon))
_cardSystem.TryChangeJobIcon(uid, jobIcon); _cardSystem.TryChangeJobIcon(uid, jobIcon);
} }
} }

View File

@@ -6,17 +6,13 @@ using Robust.Shared.Console;
namespace Content.Server.Administration.Commands; namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Fun)] [AdminCommand(AdminFlags.Fun)]
public sealed class AddPolymorphActionCommand : IConsoleCommand public sealed class AddPolymorphActionCommand : LocalizedEntityCommands
{ {
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly PolymorphSystem _polySystem = default!;
public string Command => "addpolymorphaction"; public override string Command => "addpolymorphaction";
public string Description => Loc.GetString("add-polymorph-action-command-description"); public override void Execute(IConsoleShell shell, string argStr, string[] args)
public string Help => Loc.GetString("add-polymorph-action-command-help-text");
public void Execute(IConsoleShell shell, string argStr, string[] args)
{ {
if (args.Length != 2) if (args.Length != 2)
{ {
@@ -24,15 +20,13 @@ public sealed class AddPolymorphActionCommand : IConsoleCommand
return; return;
} }
if (!NetEntity.TryParse(args[0], out var entityUidNet) || !_entityManager.TryGetEntity(entityUidNet, out var entityUid)) if (!NetEntity.TryParse(args[0], out var entityUidNet) || !EntityManager.TryGetEntity(entityUidNet, out var entityUid))
{ {
shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); shell.WriteError(Loc.GetString("shell-could-not-find-entity-with-uid", ("uid", args[0])));
return; return;
} }
var polySystem = _entityManager.EntitySysManager.GetEntitySystem<PolymorphSystem>(); var polymorphable = EntityManager.EnsureComponent<PolymorphableComponent>(entityUid.Value);
_polySystem.CreatePolymorphAction(args[1], (entityUid.Value, polymorphable));
var polymorphable = _entityManager.EnsureComponent<PolymorphableComponent>(entityUid.Value);
polySystem.CreatePolymorphAction(args[1], (entityUid.Value, polymorphable));
} }
} }

View File

@@ -118,7 +118,7 @@ public sealed class ExplosionCommand : LocalizedEntityCommands
return; return;
} }
} }
else if (!_prototypeManager.TryIndex(ExplosionSystem.DefaultExplosionPrototypeId, out type)) else if (!_prototypeManager.Resolve(ExplosionSystem.DefaultExplosionPrototypeId, out type))
{ {
// no prototype was specified, so lets default to whichever one was defined first // no prototype was specified, so lets default to whichever one was defined first
type = _prototypeManager.EnumeratePrototypes<ExplosionPrototype>().FirstOrDefault(); type = _prototypeManager.EnumeratePrototypes<ExplosionPrototype>().FirstOrDefault();

Some files were not shown because too many files have changed in this diff Show More