Merge branch 'master' into fix-conflict-40263
This commit is contained in:
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
@@ -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
|
||||||
|
|||||||
2
.github/workflows/publish-testing.yml
vendored
2
.github/workflows/publish-testing.yml
vendored
@@ -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
|
||||||
|
|||||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -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
|
||||||
|
|||||||
2
.github/workflows/test-packaging.yml
vendored
2
.github/workflows/test-packaging.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
174
Content.Benchmarks/DeltaPressureBenchmark.cs
Normal file
174
Content.Benchmarks/DeltaPressureBenchmark.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
40
Content.Client/Anomaly/AnomalyScannerScreenComponent.cs
Normal file
40
Content.Client/Anomaly/AnomalyScannerScreenComponent.cs
Normal 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);
|
||||||
|
}
|
||||||
110
Content.Client/Anomaly/AnomalyScannerSystem.cs
Normal file
110
Content.Client/Anomaly/AnomalyScannerSystem.cs
Normal 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
210
Content.Client/Atmos/Overlays/GasTileHeatOverlay.cs
Normal file
210
Content.Client/Atmos/Overlays/GasTileHeatOverlay.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 _);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.";
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}");
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using Content.Shared.Shuttles.Systems;
|
||||||
|
|
||||||
|
namespace Content.Client.Shuttles.Systems;
|
||||||
|
|
||||||
|
public sealed partial class EmergencyShuttleSystem : SharedEmergencyShuttleSystem;
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
417
Content.IntegrationTests/Tests/Atmos/DeltaPressureTest.cs
Normal file
417
Content.IntegrationTests/Tests/Atmos/DeltaPressureTest.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Content.IntegrationTests/Tests/Atmos/GasArrayTest.cs
Normal file
85
Content.IntegrationTests/Tests/Atmos/GasArrayTest.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
10
Content.Packaging/SharedPackaging.cs
Normal file
10
Content.Packaging/SharedPackaging.cs
Normal 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",
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user