mirror of
https://github.com/corvax-team/ss14-wl.git
synced 2026-02-14 19:29:57 +01:00
Upstream 09.10
This commit is contained in:
12
.github/CODEOWNERS
vendored
12
.github/CODEOWNERS
vendored
@@ -5,14 +5,20 @@
|
||||
* @JerryImMouse
|
||||
* @tau27
|
||||
|
||||
# Ping for all PRs that include translations/editing fluent strings
|
||||
*.ftl @ficcialfaint
|
||||
# Translations
|
||||
*.ftl @Morb0 @DIMMoon1 @ficcialfaint
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Map files
|
||||
/Resources/Prototypes/Maps/** @Ko4ergaPunk
|
||||
/Resources/Maps/** @Ko4ergaPunk
|
||||
/Resources/Prototypes/_WL/Maps/** @0leshe
|
||||
/Resources/Maps/_WL/** @0leshe
|
||||
=======
|
||||
# Maps
|
||||
/Resources/Prototypes/Maps/** @Morb0 @DIMMoon1 @Ko4ergaPunk
|
||||
/Resources/Maps/** @Morb0 @DIMMoon1 @Ko4ergaPunk
|
||||
>>>>>>> corvax/master
|
||||
|
||||
# Sprites
|
||||
/Resources/Textures/** @SonicHDC
|
||||
/Resources/Textures/** @Morb0 @DIMMoon1 @SonicHDC
|
||||
|
||||
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
|
||||
|
||||
- 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
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
run: dotnet build Content.Packaging --configuration Release --no-restore /m
|
||||
|
||||
- name: Package server
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
|
||||
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
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
2
.github/workflows/test-packaging.yml
vendored
2
.github/workflows/test-packaging.yml
vendored
@@ -74,7 +74,7 @@ jobs:
|
||||
run: dotnet build Content.Packaging --configuration Release --no-restore /m
|
||||
|
||||
- name: Package server
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
|
||||
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
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
#!/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 shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
|
||||
# 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"
|
||||
|
||||
|
||||
@@ -25,12 +27,10 @@ def run_command(command: List[str], capture: bool = False) -> subprocess.Complet
|
||||
|
||||
sys.stdout.flush()
|
||||
|
||||
completed = None
|
||||
|
||||
if capture:
|
||||
completed = subprocess.run(command, cwd="..", stdout=subprocess.PIPE)
|
||||
completed = subprocess.run(command, stdout=subprocess.PIPE, text=True)
|
||||
else:
|
||||
completed = subprocess.run(command, cwd="..")
|
||||
completed = subprocess.run(command)
|
||||
|
||||
if completed.returncode != 0:
|
||||
print("Error: command exited with code {}!".format(completed.returncode))
|
||||
@@ -43,7 +43,7 @@ def update_submodules():
|
||||
Updates all submodules.
|
||||
"""
|
||||
|
||||
if ('GITHUB_ACTIONS' in os.environ):
|
||||
if 'GITHUB_ACTIONS' in os.environ:
|
||||
return
|
||||
|
||||
if os.path.isfile("DISABLE_SUBMODULE_AUTOUPDATE"):
|
||||
@@ -76,22 +76,21 @@ def install_hooks():
|
||||
print("No hooks change detected.")
|
||||
return
|
||||
|
||||
with open("INSTALLED_HOOKS_VERSION", "w") as f:
|
||||
f.write(CURRENT_HOOKS_VERSION)
|
||||
|
||||
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")
|
||||
|
||||
# Clear entire tree since we need to kill deleted files too.
|
||||
for filename in os.listdir(str(hooks_target_dir)):
|
||||
os.remove(str(hooks_target_dir/filename))
|
||||
for filename in os.listdir(hooks_target_dir):
|
||||
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))
|
||||
shutil.copy2(str(hooks_source_dir/filename),
|
||||
str(hooks_target_dir/filename))
|
||||
shutil.copy2(hooks_source_dir / filename, hooks_target_dir / filename)
|
||||
|
||||
with open("INSTALLED_HOOKS_VERSION", "w") as f:
|
||||
f.write(CURRENT_HOOKS_VERSION)
|
||||
|
||||
|
||||
def reset_solution():
|
||||
@@ -107,8 +106,7 @@ def reset_solution():
|
||||
|
||||
def check_for_zip_download():
|
||||
# Check if .git exists,
|
||||
cur_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
if not os.path.isdir(os.path.join(cur_dir, ".git")):
|
||||
if run_command(["git", "rev-parse"]).returncode != 0:
|
||||
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. "
|
||||
"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
|
||||
|
||||
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
|
||||
py -3 git_helper.py --quiet
|
||||
else
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Just call post-checkout since it does the same thing.
|
||||
gitroot=`git rev-parse --show-toplevel`
|
||||
bash "$gitroot/.git/hooks/post-checkout"
|
||||
gitroot=$(git rev-parse --git-path hooks)
|
||||
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();
|
||||
}
|
||||
}
|
||||
3
Content.Benchmarks/GlobalUsings.cs
Normal file
3
Content.Benchmarks/GlobalUsings.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
// Global usings for Content.Benchmarks
|
||||
|
||||
global using Robust.UnitTesting.Pool;
|
||||
@@ -47,7 +47,7 @@ public class MapLoadBenchmark
|
||||
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))]
|
||||
public string Map;
|
||||
|
||||
@@ -25,11 +25,11 @@ namespace Content.Client.Access.UI
|
||||
public void SetAccessLevels(IPrototypeManager protoManager, List<ProtoId<AccessLevelPrototype>> accessLevels)
|
||||
{
|
||||
_accessButtons.Clear();
|
||||
AccessLevelGrid.DisposeAllChildren();
|
||||
AccessLevelGrid.RemoveAllChildren();
|
||||
|
||||
foreach (var access in accessLevels)
|
||||
{
|
||||
if (!protoManager.TryIndex(access, out var accessLevel))
|
||||
if (!protoManager.Resolve(access, out var accessLevel))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
public void SetAllowedIcons(string currentJobIconId)
|
||||
{
|
||||
IconGrid.DisposeAllChildren();
|
||||
IconGrid.RemoveAllChildren();
|
||||
|
||||
var jobIconButtonGroup = new ButtonGroup();
|
||||
var i = 0;
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
|
||||
|
||||
foreach (var accessGroup in _accessGroups)
|
||||
{
|
||||
if (!_protoManager.TryIndex(accessGroup, out var accessGroupProto))
|
||||
if (!_protoManager.Resolve(accessGroup, out var accessGroupProto))
|
||||
continue;
|
||||
|
||||
_groupedAccessLevels.Add(accessGroupProto, new());
|
||||
@@ -65,13 +65,13 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
|
||||
|
||||
// Ensure that the 'general' access group is added to handle
|
||||
// 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());
|
||||
|
||||
// Assign known access levels with their associated groups
|
||||
foreach (var accessLevel in _accessLevels)
|
||||
{
|
||||
if (!_protoManager.TryIndex(accessLevel, out var accessLevelProto))
|
||||
if (!_protoManager.Resolve(accessLevel, out var accessLevelProto))
|
||||
continue;
|
||||
|
||||
var assigned = false;
|
||||
@@ -99,8 +99,8 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
|
||||
|
||||
private bool TryRebuildAccessGroupControls()
|
||||
{
|
||||
AccessGroupList.DisposeAllChildren();
|
||||
AccessLevelChecklist.DisposeAllChildren();
|
||||
AccessGroupList.RemoveAllChildren();
|
||||
AccessLevelChecklist.RemoveAllChildren();
|
||||
|
||||
// No access level prototypes were assigned to any of the access level groups.
|
||||
// Either the turret controller has no assigned access levels or their names were invalid.
|
||||
@@ -165,7 +165,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
|
||||
/// </summary>
|
||||
public void RebuildAccessLevelsControls()
|
||||
{
|
||||
AccessLevelChecklist.DisposeAllChildren();
|
||||
AccessLevelChecklist.RemoveAllChildren();
|
||||
_accessLevelEntries.Clear();
|
||||
|
||||
// No access level prototypes were assigned to any of the access level groups
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.Access.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.Access.Components.IdCardConsoleComponent;
|
||||
@@ -74,7 +75,7 @@ namespace Content.Client.Access.UI
|
||||
_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)
|
||||
newFullName = newFullName[.._maxNameLength];
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
foreach (var group in job.AccessGroups)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(group, out AccessGroupPrototype? groupPrototype))
|
||||
if (!_prototypeManager.Resolve(group, out AccessGroupPrototype? groupPrototype))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Content.Client.Actions
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceManager _resources = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
|
||||
public event Action<EntityUid>? OnActionAdded;
|
||||
public event Action<EntityUid>? OnActionRemoved;
|
||||
@@ -286,8 +287,27 @@ namespace Content.Client.Actions
|
||||
continue;
|
||||
}
|
||||
|
||||
if (assignmentNode is SequenceDataNode sequenceAssignments)
|
||||
{
|
||||
try
|
||||
{
|
||||
var nodeAssignments = _serialization.Read<List<(byte Hotbar, byte Slot)>>(sequenceAssignments, notNullableOverride: true);
|
||||
|
||||
foreach (var index in nodeAssignments)
|
||||
{
|
||||
assignments.Add(new SlotAssignment(index.Hotbar, index.Slot, actionId));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Failed to parse action assignments: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
AddActionDirect((user, actions), actionId);
|
||||
}
|
||||
|
||||
AssignSlot?.Invoke(assignments);
|
||||
}
|
||||
|
||||
private void OnWorldTargetAttempt(Entity<WorldTargetActionComponent> ent, ref ActionTargetAttemptEvent args)
|
||||
@@ -309,10 +329,10 @@ namespace Content.Client.Actions
|
||||
// this is the actual entity-world targeting magic
|
||||
EntityUid? targetEnt = null;
|
||||
if (TryComp<EntityTargetActionComponent>(ent, out var entity) &&
|
||||
args.Input.EntityUid != null &&
|
||||
ValidateEntityTarget(user, args.Input.EntityUid, (uid, entity)))
|
||||
args.Input.EntityUid is { Valid: true } entityUid &&
|
||||
ValidateEntityTarget(user, entityUid, (uid, entity)))
|
||||
{
|
||||
targetEnt = args.Input.EntityUid;
|
||||
targetEnt = entityUid;
|
||||
}
|
||||
|
||||
if (action.ClientExclusive)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc admin-camera-window-title-placeholder}"
|
||||
SetSize="425 550"
|
||||
MinSize="200 225"
|
||||
Name="Window">
|
||||
MinSize="200 225">
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Content.Client.Administration.UI.BanPanel;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BanPanel : DefaultWindow
|
||||
{
|
||||
public event Action<string?, (IPAddress, int)?, bool, ImmutableTypedHwid?, bool, uint, string, NoteSeverity, string[]?, bool>? BanSubmitted;
|
||||
public event Action<Ban>? BanSubmitted;
|
||||
public event Action<string>? PlayerChanged;
|
||||
private string? PlayerUsername { get; set; }
|
||||
private (IPAddress, int)? IpAddress { get; set; }
|
||||
@@ -37,8 +37,8 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
// This is less efficient than just holding a reference to the root control and enumerating children, but you
|
||||
// have to know how the controls are nested, which makes the code more complicated.
|
||||
// Role group name -> the role buttons themselves.
|
||||
private readonly Dictionary<string, List<Button>> _roleCheckboxes = new();
|
||||
private readonly ISawmill _banpanelSawmill;
|
||||
private readonly Dictionary<string, List<(Button, IPrototype)>> _roleCheckboxes = new();
|
||||
private readonly ISawmill _banPanelSawmill;
|
||||
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -79,7 +79,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_banpanelSawmill = _logManager.GetSawmill("admin.banpanel");
|
||||
_banPanelSawmill = _logManager.GetSawmill("admin.banpanel");
|
||||
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
|
||||
PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged();
|
||||
PlayerCheckbox.OnPressed += _ =>
|
||||
@@ -110,7 +110,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
TypeOption.SelectId(args.Id);
|
||||
OnTypeChanged();
|
||||
};
|
||||
LastConnCheckbox.OnPressed += args =>
|
||||
LastConnCheckbox.OnPressed += _ =>
|
||||
{
|
||||
IpLine.ModulateSelfOverride = null;
|
||||
HwidLine.ModulateSelfOverride = null;
|
||||
@@ -164,7 +164,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
|
||||
var antagRoles = _protoMan.EnumeratePrototypes<AntagPrototype>()
|
||||
.OrderBy(x => x.ID);
|
||||
CreateRoleGroup("Antagonist", Color.Red, antagRoles);
|
||||
CreateRoleGroup(AntagPrototype.GroupName, AntagPrototype.GroupColor, antagRoles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -236,14 +236,14 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
{
|
||||
foreach (var role in _roleCheckboxes[groupName])
|
||||
{
|
||||
role.Pressed = args.Pressed;
|
||||
role.Item1.Pressed = args.Pressed;
|
||||
}
|
||||
|
||||
if (args.Pressed)
|
||||
{
|
||||
if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), true, out NoteSeverity newSeverity))
|
||||
{
|
||||
_banpanelSawmill
|
||||
_banPanelSawmill
|
||||
.Warning("Departmental role ban severity could not be parsed from config!");
|
||||
return;
|
||||
}
|
||||
@@ -255,14 +255,14 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
{
|
||||
foreach (var button in roleButtons)
|
||||
{
|
||||
if (button.Pressed)
|
||||
if (button.Item1.Pressed)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity newSeverity))
|
||||
{
|
||||
_banpanelSawmill
|
||||
_banPanelSawmill
|
||||
.Warning("Role ban severity could not be parsed from config!");
|
||||
return;
|
||||
}
|
||||
@@ -294,7 +294,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a checkbutton specifically for one "role" in a "group"
|
||||
/// Adds a check button specifically for one "role" in a "group"
|
||||
/// E.g. it would add the Chief Medical Officer "role" into the "Medical" group.
|
||||
/// </summary>
|
||||
private void AddRoleCheckbox(string group, string role, GridContainer roleGroupInnerContainer, Button roleGroupCheckbox)
|
||||
@@ -302,22 +302,36 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
var roleCheckboxContainer = new BoxContainer();
|
||||
var roleCheckButton = new Button
|
||||
{
|
||||
Name = $"{role}RoleCheckbox",
|
||||
Name = role,
|
||||
Text = role,
|
||||
ToggleMode = true,
|
||||
};
|
||||
roleCheckButton.OnToggled += args =>
|
||||
{
|
||||
// Checks the role group checkbox if all the children are pressed
|
||||
if (args.Pressed && _roleCheckboxes[group].All(e => e.Pressed))
|
||||
if (args.Pressed && _roleCheckboxes[group].All(e => e.Item1.Pressed))
|
||||
roleGroupCheckbox.Pressed = args.Pressed;
|
||||
else
|
||||
roleGroupCheckbox.Pressed = false;
|
||||
};
|
||||
|
||||
IPrototype rolePrototype;
|
||||
|
||||
if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype))
|
||||
rolePrototype = jobPrototype;
|
||||
else if (_protoMan.TryIndex<AntagPrototype>(role, out var antagPrototype))
|
||||
rolePrototype = antagPrototype;
|
||||
else
|
||||
{
|
||||
_banPanelSawmill.Error($"Adding a role checkbox for role {role}: role is not a JobPrototype or AntagPrototype.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype) && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto))
|
||||
// TODO: This should not be using raw strings for prototypes as it means it won't be validated at all.
|
||||
// // I know the ban manager is doing the same thing, but that should not leak into UI code.
|
||||
if (jobPrototype is not null && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto))
|
||||
{
|
||||
var jobIconTexture = new TextureRect
|
||||
{
|
||||
@@ -334,7 +348,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
roleGroupInnerContainer.AddChild(roleCheckboxContainer);
|
||||
|
||||
_roleCheckboxes.TryAdd(group, []);
|
||||
_roleCheckboxes[group].Add(roleCheckButton);
|
||||
_roleCheckboxes[group].Add((roleCheckButton, rolePrototype));
|
||||
}
|
||||
|
||||
public void UpdateBanFlag(bool newFlag)
|
||||
@@ -487,7 +501,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
newSeverity = serverSeverity;
|
||||
else
|
||||
{
|
||||
_banpanelSawmill
|
||||
_banPanelSawmill
|
||||
.Warning("Server ban severity could not be parsed from config!");
|
||||
}
|
||||
|
||||
@@ -500,7 +514,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
}
|
||||
else
|
||||
{
|
||||
_banpanelSawmill
|
||||
_banPanelSawmill
|
||||
.Warning("Role ban severity could not be parsed from config!");
|
||||
}
|
||||
break;
|
||||
@@ -545,34 +559,51 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
|
||||
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
string[]? roles = null;
|
||||
ProtoId<JobPrototype>[]? jobs = null;
|
||||
ProtoId<AntagPrototype>[]? antags = null;
|
||||
|
||||
if (TypeOption.SelectedId == (int) Types.Role)
|
||||
{
|
||||
var rolesList = new List<string>();
|
||||
var jobList = new List<ProtoId<JobPrototype>>();
|
||||
var antagList = new List<ProtoId<AntagPrototype>>();
|
||||
|
||||
if (_roleCheckboxes.Count == 0)
|
||||
throw new DebugAssertException("RoleCheckboxes was empty");
|
||||
|
||||
foreach (var button in _roleCheckboxes.Values.SelectMany(departmentButtons => departmentButtons))
|
||||
{
|
||||
if (button is { Pressed: true, Text: not null })
|
||||
if (button.Item1 is { Pressed: true, Name: not null })
|
||||
{
|
||||
rolesList.Add(button.Text);
|
||||
switch (button.Item2)
|
||||
{
|
||||
case JobPrototype:
|
||||
jobList.Add(button.Item2.ID);
|
||||
|
||||
break;
|
||||
case AntagPrototype:
|
||||
antagList.Add(button.Item2.ID);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rolesList.Count == 0)
|
||||
if (jobList.Count + antagList.Count == 0)
|
||||
{
|
||||
Tabs.CurrentTab = (int) TabNumbers.Roles;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
roles = rolesList.ToArray();
|
||||
jobs = jobList.ToArray();
|
||||
antags = antagList.ToArray();
|
||||
}
|
||||
|
||||
if (TypeOption.SelectedId == (int) Types.None)
|
||||
{
|
||||
TypeOption.ModulateSelfOverride = Color.Red;
|
||||
Tabs.CurrentTab = (int) TabNumbers.BasicInfo;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -584,6 +615,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
ReasonTextEdit.GrabKeyboardFocus();
|
||||
ReasonTextEdit.ModulateSelfOverride = Color.Red;
|
||||
ReasonTextEdit.OnKeyBindDown += ResetTextEditor;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -592,6 +624,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
ButtonResetOn = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(3));
|
||||
SubmitButton.ModulateSelfOverride = Color.Red;
|
||||
SubmitButton.Text = Loc.GetString("ban-panel-confirm");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -600,7 +633,22 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
var useLastHwid = HwidCheckbox.Pressed && LastConnCheckbox.Pressed && Hwid is null;
|
||||
var severity = (NoteSeverity) SeverityOption.SelectedId;
|
||||
var erase = EraseCheckbox.Pressed;
|
||||
BanSubmitted?.Invoke(player, IpAddress, useLastIp, Hwid, useLastHwid, (uint) (TimeEntered * Multiplier), reason, severity, roles, erase);
|
||||
|
||||
var ban = new Ban(
|
||||
player,
|
||||
IpAddress,
|
||||
useLastIp,
|
||||
Hwid,
|
||||
useLastHwid,
|
||||
(uint)(TimeEntered * Multiplier),
|
||||
reason,
|
||||
severity,
|
||||
jobs,
|
||||
antags,
|
||||
erase
|
||||
);
|
||||
|
||||
BanSubmitted?.Invoke(ban);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
|
||||
@@ -14,8 +14,7 @@ public sealed class BanPanelEui : BaseEui
|
||||
{
|
||||
BanPanel = new BanPanel();
|
||||
BanPanel.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
BanPanel.BanSubmitted += (player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles, erase)
|
||||
=> SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles, erase));
|
||||
BanPanel.BanSubmitted += ban => SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(ban));
|
||||
BanPanel.PlayerChanged += player => SendMessage(new BanPanelEuiStateMsg.GetPlayerInfoRequest(player));
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
/// </summary>
|
||||
public void UpdateReagents()
|
||||
{
|
||||
ReagentList.DisposeAllChildren();
|
||||
ReagentList.RemoveAllChildren();
|
||||
|
||||
if (_selectedSolution == null || _solutions == null)
|
||||
return;
|
||||
@@ -92,7 +92,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
/// <param name="solution">The selected solution.</param>
|
||||
private void UpdateVolumeBox(Solution solution)
|
||||
{
|
||||
VolumeBox.DisposeAllChildren();
|
||||
VolumeBox.RemoveAllChildren();
|
||||
|
||||
var volumeLabel = new Label();
|
||||
volumeLabel.HorizontalExpand = true;
|
||||
@@ -131,7 +131,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
/// <param name="solution">The selected solution.</param>
|
||||
private void UpdateThermalBox(Solution solution)
|
||||
{
|
||||
ThermalBox.DisposeAllChildren();
|
||||
ThermalBox.RemoveAllChildren();
|
||||
var heatCap = solution.GetHeatCapacity(null);
|
||||
var specificHeatLabel = new Label();
|
||||
specificHeatLabel.HorizontalExpand = true;
|
||||
|
||||
@@ -82,7 +82,11 @@ public sealed partial class AdminNotesLine : BoxContainer
|
||||
|
||||
if (Note.UnbannedTime is not null)
|
||||
{
|
||||
ExtraLabel.Text = Loc.GetString("admin-notes-unbanned", ("admin", Note.UnbannedByName ?? "[error]"), ("date", Note.UnbannedTime));
|
||||
ExtraLabel.Text = Loc.GetString(
|
||||
"admin-notes-unbanned",
|
||||
("admin", Note.UnbannedByName ?? "[error]"),
|
||||
("date", Note.UnbannedTime.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"))
|
||||
);
|
||||
ExtraLabel.Visible = true;
|
||||
}
|
||||
else if (Note.ExpiryTime is not null)
|
||||
@@ -139,7 +143,7 @@ public sealed partial class AdminNotesLine : BoxContainer
|
||||
|
||||
private string FormatRoleBanMessage()
|
||||
{
|
||||
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new []{"unknown"})} ");
|
||||
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new[] { "unknown" })} ");
|
||||
return FormatBanMessageCommon(banMessage);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
public sealed class AnomalySystem : SharedAnomalySystem
|
||||
public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly FloatingVisualizerSystem _floating = default!;
|
||||
@@ -24,6 +24,7 @@ public sealed class AnomalySystem : SharedAnomalySystem
|
||||
|
||||
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args)
|
||||
{
|
||||
_floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime);
|
||||
|
||||
@@ -134,7 +134,7 @@ public sealed class AlignAtmosPipeLayers : SnapgridCenter
|
||||
|
||||
var newProtoId = altPrototypes[(int)layer];
|
||||
|
||||
if (!_protoManager.TryIndex(newProtoId, out var newProto))
|
||||
if (!_protoManager.Resolve(newProtoId, out var newProto))
|
||||
return;
|
||||
|
||||
if (newProto.Type != ConstructionType.Structure)
|
||||
|
||||
@@ -30,7 +30,10 @@ public sealed partial class ThresholdBoundControl : BoxContainer
|
||||
public void SetValue(float value)
|
||||
{
|
||||
_value = value;
|
||||
CSpinner.Value = ScaledValue;
|
||||
if (!CSpinner.HasKeyboardFocus())
|
||||
{
|
||||
CSpinner.Value = ScaledValue;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEnabled(bool enabled)
|
||||
|
||||
@@ -208,7 +208,7 @@ namespace Content.Client.Atmos.UI
|
||||
});
|
||||
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,
|
||||
HorizontalExpand = true
|
||||
});
|
||||
@@ -232,8 +232,8 @@ namespace Content.Client.Atmos.UI
|
||||
tempBox.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("gas-analyzer-window-temperature-val-text",
|
||||
("tempK", $"{gasMix.Temperature:0.#}"),
|
||||
("tempC", $"{TemperatureHelpers.KelvinToCelsius(gasMix.Temperature):0.#}")),
|
||||
("tempK", $"{gasMix.Temperature:0.0}"),
|
||||
("tempC", $"{TemperatureHelpers.KelvinToCelsius(gasMix.Temperature):0.0}")),
|
||||
Align = Label.AlignMode.Right,
|
||||
HorizontalExpand = true
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed class JukeboxBoundUserInterface : BoundUserInterface
|
||||
|
||||
_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());
|
||||
_menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds);
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class BarSignSystem : VisualizerSystem<BarSignComponent>
|
||||
|
||||
if (powered
|
||||
&& sign.Current != null
|
||||
&& _prototypeManager.TryIndex(sign.Current, out var proto))
|
||||
&& _prototypeManager.Resolve(sign.Current, out var proto))
|
||||
{
|
||||
SpriteSystem.LayerSetSprite((id, sprite), 0, proto.Icon);
|
||||
sprite.LayerSetShader(0, "unshaded");
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
|
||||
|
||||
public void Update(ProtoId<BarSignPrototype>? sign)
|
||||
{
|
||||
if (_prototype.TryIndex(sign, out var signPrototype))
|
||||
if (_prototype.Resolve(sign, out var signPrototype))
|
||||
_menu?.UpdateState(signPrototype);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public sealed partial class BountyEntry : BoxContainer
|
||||
|
||||
UntilNextSkip = untilNextSkip;
|
||||
|
||||
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
|
||||
if (!_prototype.Resolve<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
|
||||
return;
|
||||
|
||||
var items = new List<string>();
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed partial class BountyHistoryEntry : BoxContainer
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
|
||||
if (!_prototype.Resolve(bounty.Bounty, out var bountyPrototype))
|
||||
return;
|
||||
|
||||
var items = new List<string>();
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace Content.Client.Cargo.UI
|
||||
if (!_orderConsoleQuery.TryComp(_owner, out var orderConsole))
|
||||
return;
|
||||
|
||||
Requests.DisposeAllChildren();
|
||||
Requests.RemoveAllChildren();
|
||||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Content.Client.Cargo.UI
|
||||
|
||||
public void SetOrders(SpriteSystem sprites, IPrototypeManager protoManager, List<CargoOrderData> orders)
|
||||
{
|
||||
Orders.DisposeAllChildren();
|
||||
Orders.RemoveAllChildren();
|
||||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Client.CrewManifest.UI;
|
||||
using Content.Client.CrewManifest.UI;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -21,7 +21,6 @@ public sealed partial class CrewManifestUiFragment : BoxContainer
|
||||
|
||||
public void UpdateState(string stationName, CrewManifestEntries? entries)
|
||||
{
|
||||
CrewManifestListing.DisposeAllChildren();
|
||||
CrewManifestListing.RemoveAllChildren();
|
||||
|
||||
StationNameContainer.Visible = entries != null;
|
||||
|
||||
@@ -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 Robust.Client.UserInterface;
|
||||
|
||||
@@ -7,28 +10,58 @@ namespace Content.Client.Changeling.UI;
|
||||
[UsedImplicitly]
|
||||
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()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<ChangelingTransformMenu>();
|
||||
|
||||
_window.OnIdentitySelect += SendIdentitySelect;
|
||||
|
||||
_window.Update(Owner);
|
||||
_menu = this.CreateWindow<SimpleRadialMenu>();
|
||||
Update();
|
||||
_menu.OpenOverMouseScreenPosition();
|
||||
}
|
||||
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (_window == null)
|
||||
if (_menu == null)
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -55,7 +55,7 @@ namespace Content.Client.Changelog
|
||||
// Changelog is not kept in memory so load it again.
|
||||
var changelogs = await _changelog.LoadChangelog();
|
||||
|
||||
Tabs.DisposeAllChildren();
|
||||
Tabs.RemoveAllChildren();
|
||||
|
||||
var i = 0;
|
||||
foreach (var changelog in changelogs)
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
|
||||
if (overrideIndicator != null)
|
||||
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}");
|
||||
return;
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Client.Chemistry.UI;
|
||||
using Content.Client.Items;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Chemistry.EntitySystems;
|
||||
|
||||
@@ -11,6 +10,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
public override void 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
|
||||
if (PrevVolume == solution.Volume
|
||||
&& PrevMaxVolume == solution.MaxVolume
|
||||
&& PrevTransferAmount == _parent.Comp.TransferAmount
|
||||
&& PrevTransferAmount == _parent.Comp.CurrentTransferAmount
|
||||
&& PrevToggleState == _parent.Comp.ToggleState)
|
||||
return;
|
||||
|
||||
PrevVolume = solution.Volume;
|
||||
PrevMaxVolume = solution.MaxVolume;
|
||||
PrevTransferAmount = _parent.Comp.TransferAmount;
|
||||
PrevTransferAmount = _parent.Comp.CurrentTransferAmount;
|
||||
PrevToggleState = _parent.Comp.ToggleState;
|
||||
|
||||
// Update current volume and injector state
|
||||
@@ -59,6 +59,6 @@ public sealed class InjectorStatusControl : Control
|
||||
("currentVolume", solution.Volume),
|
||||
("totalVolume", solution.MaxVolume),
|
||||
("modeString", modeStringLocalized),
|
||||
("transferVolume", _parent.Comp.TransferAmount)));
|
||||
("transferVolume", _parent.Comp.CurrentTransferAmount)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.DisplacementMap;
|
||||
using Content.Client.Inventory;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.DisplacementMap;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
@@ -14,7 +12,6 @@ using Content.Shared.Item;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.GameObjects.SpriteComponent;
|
||||
@@ -177,6 +174,7 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
var layer = new PrototypeLayerData();
|
||||
layer.RsiPath = rsi.Path.ToString();
|
||||
layer.State = state;
|
||||
layer.Scale = clothing.Scale;
|
||||
layers = new() { layer };
|
||||
|
||||
return true;
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
|
||||
var newTargets = new List<EntProtoId>();
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(id, out EntityPrototype? proto))
|
||||
if (!_prototypeManager.Resolve(id, out EntityPrototype? proto))
|
||||
continue;
|
||||
|
||||
var lowId = id.Id.ToLowerInvariant();
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Content.Client.Construction
|
||||
{
|
||||
foreach (var constructionProto in PrototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||
{
|
||||
if (!PrototypeManager.TryIndex(constructionProto.Graph, out var graphProto))
|
||||
if (!PrototypeManager.Resolve(constructionProto.Graph, out var graphProto))
|
||||
continue;
|
||||
|
||||
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.
|
||||
stack.Clear();
|
||||
|
||||
if (!PrototypeManager.TryIndex(constructionProto.ID, out ConstructionPrototype? recipe))
|
||||
if (!PrototypeManager.Resolve(entityId, out var proto))
|
||||
continue;
|
||||
|
||||
if (!PrototypeManager.TryIndex(entityId, out var proto))
|
||||
continue;
|
||||
var name = constructionProto.SetName.HasValue ? Loc.GetString(constructionProto.SetName) : proto.Name;
|
||||
var desc = constructionProto.SetDescription.HasValue ? Loc.GetString(constructionProto.SetDescription) : proto.Description;
|
||||
|
||||
var name = recipe.SetName.HasValue ? Loc.GetString(recipe.SetName) : proto.Name;
|
||||
var desc = recipe.SetDescription.HasValue ? Loc.GetString(recipe.SetDescription) : proto.Description;
|
||||
|
||||
recipe.Name = name;
|
||||
recipe.Description = desc;
|
||||
constructionProto.Name = name;
|
||||
constructionProto.Description = desc;
|
||||
|
||||
_recipesMetadataCache.Add(constructionProto.ID, entityId);
|
||||
} while (stack.Count > 0);
|
||||
@@ -172,7 +169,7 @@ namespace Content.Client.Construction
|
||||
"construction-ghost-examine-message",
|
||||
("name", component.Prototype.Name)));
|
||||
|
||||
if (!PrototypeManager.TryIndex(component.Prototype.Graph, out var graph))
|
||||
if (!PrototypeManager.Resolve(component.Prototype.Graph, out var graph))
|
||||
return;
|
||||
|
||||
var startNode = graph.Nodes[component.Prototype.StartNode];
|
||||
|
||||
@@ -510,7 +510,7 @@ namespace Content.Client.Construction.UI
|
||||
|
||||
foreach (var id in favorites)
|
||||
{
|
||||
if (_prototypeManager.TryIndex(id, out ConstructionPrototype? recipe, logError: false))
|
||||
if (_prototypeManager.TryIndex(id, out ConstructionPrototype? recipe))
|
||||
_favoritedRecipes.Add(recipe);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Content.Client.ContextMenu.UI
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
RootMenu.MenuBody.DisposeAllChildren();
|
||||
RootMenu.MenuBody.RemoveAllChildren();
|
||||
CancelOpen?.Cancel();
|
||||
CancelClose?.Cancel();
|
||||
OnContextClosed?.Invoke();
|
||||
|
||||
@@ -293,7 +293,7 @@ namespace Content.Client.ContextMenu.UI
|
||||
var element = new EntityMenuElement(entity);
|
||||
element.SubMenu = new ContextMenuPopup(_context, element);
|
||||
element.SubMenu.OnPopupOpen += () => _verb.OpenVerbMenu(entity, popup: element.SubMenu);
|
||||
element.SubMenu.OnPopupHide += element.SubMenu.MenuBody.DisposeAllChildren;
|
||||
element.SubMenu.OnPopupHide += element.SubMenu.MenuBody.RemoveAllChildren;
|
||||
_context.AddElement(menu, element);
|
||||
Elements.TryAdd(entity, element);
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Corvax.TTS;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Corvax.Interfaces.Shared;
|
||||
using Content.Shared.Corvax.TTS;
|
||||
using Content.Shared.Preferences;
|
||||
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
private ISharedSponsorsManager? _sponsorsMgr;
|
||||
private List<TTSVoicePrototype> _voiceList = new();
|
||||
|
||||
private void InitializeVoice()
|
||||
{
|
||||
_voiceList = _prototypeManager
|
||||
.EnumeratePrototypes<TTSVoicePrototype>()
|
||||
.Where(o => o.RoundStart)
|
||||
.OrderBy(o => Loc.GetString(o.Name))
|
||||
.ToList();
|
||||
|
||||
VoiceButton.OnItemSelected += args =>
|
||||
{
|
||||
VoiceButton.SelectId(args.Id);
|
||||
SetVoice(_voiceList[args.Id].ID);
|
||||
};
|
||||
|
||||
VoicePlayButton.OnPressed += _ => PlayPreviewTTS();
|
||||
|
||||
IoCManager.Instance!.TryResolveType(out _sponsorsMgr);
|
||||
}
|
||||
|
||||
private void UpdateTTSVoicesControls()
|
||||
{
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
VoiceButton.Clear();
|
||||
|
||||
var firstVoiceChoiceId = 1;
|
||||
for (var i = 0; i < _voiceList.Count; i++)
|
||||
{
|
||||
var voice = _voiceList[i];
|
||||
if (!HumanoidCharacterProfile.CanHaveVoice(voice, Profile.Sex))
|
||||
continue;
|
||||
|
||||
var name = Loc.GetString(voice.Name);
|
||||
VoiceButton.AddItem(name, i);
|
||||
|
||||
if (firstVoiceChoiceId == 1)
|
||||
firstVoiceChoiceId = i;
|
||||
|
||||
if (_sponsorsMgr is null)
|
||||
continue;
|
||||
if (voice.SponsorOnly && _sponsorsMgr != null &&
|
||||
!_sponsorsMgr.GetClientPrototypes().Contains(voice.ID))
|
||||
{
|
||||
VoiceButton.SetItemDisabled(VoiceButton.GetIdx(i), true);
|
||||
}
|
||||
}
|
||||
|
||||
var voiceChoiceId = _voiceList.FindIndex(x => x.ID == Profile.Voice);
|
||||
if (!VoiceButton.TrySelectId(voiceChoiceId) &&
|
||||
VoiceButton.TrySelectId(firstVoiceChoiceId))
|
||||
{
|
||||
SetVoice(_voiceList[firstVoiceChoiceId].ID);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayPreviewTTS()
|
||||
{
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
_entManager.System<TTSSystem>().RequestPreviewTTS(Profile.Voice, CTTSPreview?.Text ?? String.Empty); //WL-PreviewTTSEdit
|
||||
}
|
||||
}
|
||||
24
Content.Client/Corvax/TTS/TTSTab.xaml
Normal file
24
Content.Client/Corvax/TTS/TTSTab.xaml
Normal file
@@ -0,0 +1,24 @@
|
||||
<Control xmlns="https://spacestation14.io" xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5">
|
||||
<LineEdit Name="SearchEdit" MinWidth="600" PlaceHolder="{Loc 'humanoid-profile-editor-voice-placeholder'}"/>
|
||||
<Label Name="ResultsLabel" MinWidth="100" HorizontalExpand="True" HorizontalAlignment="Right"/>
|
||||
</BoxContainer>
|
||||
|
||||
<PanelContainer HorizontalExpand="True" VerticalExpand="False" MinSize="0 200" Margin="0 5 0 20">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1A1A1A" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="5">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-voice-categories'}" StyleClasses="LabelHeading" HorizontalAlignment="Center" Margin="0 0 0 5"/>
|
||||
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
|
||||
<GridContainer Name="CategoriesContainer" Columns="4" HorizontalExpand="True"/>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
<ScrollContainer VerticalExpand="True" Margin="5">
|
||||
<GridContainer Name="VoicesGrid" Columns="3" HorizontalExpand="True"/>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
194
Content.Client/Corvax/TTS/TTSTab.xaml.cs
Normal file
194
Content.Client/Corvax/TTS/TTSTab.xaml.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System.Linq;
|
||||
using Content.Corvax.Interfaces.Shared;
|
||||
using Content.Shared.Corvax.TTS;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Humanoid;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Content.Client.Corvax.TTS;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class TTSTab : Control
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public event Action<string>? OnVoiceSelected;
|
||||
public event Action<string>? OnPreviewRequested;
|
||||
|
||||
private List<TTSVoicePrototype> _allVoices = new();
|
||||
private List<TTSVoicePrototype> _filteredVoices = new();
|
||||
private Dictionary<string, List<TTSVoicePrototype>> _categorizedVoices = new();
|
||||
private string? _selectedVoiceId;
|
||||
|
||||
private static readonly Regex CategoryRegex = new Regex(@"^(.*?)\s*\(([^)]+)\)\s*$", RegexOptions.Compiled);
|
||||
|
||||
public TTSTab()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
LoadVoices();
|
||||
SearchEdit.OnTextChanged += OnSearchChanged;
|
||||
}
|
||||
|
||||
private void LoadVoices()
|
||||
{
|
||||
foreach (var voice in _allVoices)
|
||||
{
|
||||
var name = Loc.GetString(voice.Name);
|
||||
var category = Loc.GetString("humanoid-profile-editor-voice-other");
|
||||
|
||||
var match = CategoryRegex.Match(name);
|
||||
if (match.Success)
|
||||
{
|
||||
category = match.Groups[2].Value.Trim();
|
||||
}
|
||||
|
||||
if (!_categorizedVoices.ContainsKey(category))
|
||||
_categorizedVoices[category] = new List<TTSVoicePrototype>();
|
||||
|
||||
_categorizedVoices[category].Add(voice);
|
||||
}
|
||||
|
||||
CategoriesContainer.RemoveAllChildren();
|
||||
|
||||
foreach (var category in _categorizedVoices.Keys.OrderBy(k => k))
|
||||
{
|
||||
var button = new Button
|
||||
{
|
||||
Text = category,
|
||||
ToolTip = Loc.GetString("humanoid-profile-editor-voice-category-tooltip", ("category", category)),
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
button.OnPressed += _ =>
|
||||
{
|
||||
SearchEdit.Text = category;
|
||||
UpdateResults();
|
||||
};
|
||||
|
||||
CategoriesContainer.AddChild(button);
|
||||
}
|
||||
|
||||
UpdateResults();
|
||||
}
|
||||
|
||||
private void OnSearchChanged(LineEdit.LineEditEventArgs args)
|
||||
{
|
||||
UpdateResults();
|
||||
}
|
||||
|
||||
private void UpdateResults()
|
||||
{
|
||||
VoicesGrid.RemoveAllChildren();
|
||||
_filteredVoices.Clear();
|
||||
|
||||
var searchText = SearchEdit.Text.ToLowerInvariant();
|
||||
|
||||
foreach (var voice in _allVoices)
|
||||
{
|
||||
var name = Loc.GetString(voice.Name).ToLowerInvariant();
|
||||
|
||||
if (string.IsNullOrEmpty(searchText) ||
|
||||
name.Contains(searchText) ||
|
||||
voice.ID.ToLowerInvariant().Contains(searchText))
|
||||
{
|
||||
_filteredVoices.Add(voice);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var voice in _filteredVoices)
|
||||
{
|
||||
var displayName = Loc.GetString(voice.Name);
|
||||
var canSelectVoice = CanUseVoice(voice);
|
||||
|
||||
var voiceContainer = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center
|
||||
};
|
||||
|
||||
var selectButton = new Button
|
||||
{
|
||||
Text = displayName,
|
||||
ToolTip = canSelectVoice ? voice.ID : Loc.GetString("humanoid-profile-editor-voice-tooltip-sponsoronly"),
|
||||
HorizontalExpand = true,
|
||||
Disabled = !canSelectVoice,
|
||||
StyleClasses = { StyleNano.ButtonOpenRight }
|
||||
};
|
||||
|
||||
if (voice.ID == _selectedVoiceId)
|
||||
{
|
||||
selectButton.AddStyleClass(StyleBase.ButtonCaution);
|
||||
}
|
||||
|
||||
selectButton.OnPressed += _ =>
|
||||
{
|
||||
if (canSelectVoice)
|
||||
{
|
||||
OnVoiceSelected?.Invoke(voice.ID);
|
||||
}
|
||||
};
|
||||
|
||||
var previewButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("humanoid-profile-editor-voice-play"),
|
||||
MinWidth = 30,
|
||||
ToolTip = Loc.GetString("humanoid-profile-editor-voice-tooltip-play"),
|
||||
StyleClasses = { StyleNano.ButtonOpenLeft }
|
||||
};
|
||||
|
||||
previewButton.OnPressed += _ =>
|
||||
{
|
||||
OnPreviewRequested?.Invoke(voice.ID);
|
||||
};
|
||||
|
||||
voiceContainer.AddChild(selectButton);
|
||||
voiceContainer.AddChild(previewButton);
|
||||
|
||||
VoicesGrid.AddChild(voiceContainer);
|
||||
}
|
||||
|
||||
ResultsLabel.Text = Loc.GetString("humanoid-profile-editor-voice-match",
|
||||
("filtered", _filteredVoices.Count), ("all", _allVoices.Count));
|
||||
}
|
||||
|
||||
private bool CanUseVoice(TTSVoicePrototype voice)
|
||||
{
|
||||
if (!voice.SponsorOnly)
|
||||
return true;
|
||||
|
||||
var sponsorsManager = IoCManager.Resolve<ISharedSponsorsManager>();
|
||||
return sponsorsManager?.GetClientPrototypes().Contains(voice.ID) == true;
|
||||
}
|
||||
|
||||
public void UpdateControls(HumanoidCharacterProfile? profile, Sex sex)
|
||||
{
|
||||
if (profile == null)
|
||||
return;
|
||||
|
||||
_selectedVoiceId = profile.Voice;
|
||||
|
||||
_allVoices = _prototypeManager
|
||||
.EnumeratePrototypes<TTSVoicePrototype>()
|
||||
.Where(o => o.RoundStart && HumanoidCharacterProfile.CanHaveVoice(o, sex))
|
||||
.OrderBy(o => Loc.GetString(o.Name))
|
||||
.ToList();
|
||||
|
||||
_categorizedVoices.Clear();
|
||||
LoadVoices();
|
||||
}
|
||||
|
||||
public void SetSelectedVoice(string voiceId)
|
||||
{
|
||||
_selectedVoiceId = voiceId;
|
||||
UpdateResults();
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ namespace Content.Client.Crayon.UI
|
||||
private void RefreshList()
|
||||
{
|
||||
// Clear
|
||||
Grids.DisposeAllChildren();
|
||||
Grids.RemoveAllChildren();
|
||||
|
||||
if (_decals == null || _allDecals == null)
|
||||
return;
|
||||
|
||||
@@ -18,7 +18,6 @@ public sealed partial class CrewManifestUi : DefaultWindow
|
||||
|
||||
public void Populate(string name, CrewManifestEntries? entries)
|
||||
{
|
||||
CrewManifestListing.DisposeAllChildren();
|
||||
CrewManifestListing.RemoveAllChildren();
|
||||
|
||||
StationNameContainer.Visible = entries != null;
|
||||
|
||||
@@ -150,7 +150,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
|
||||
// If the damage container on our entity's DamageableComponent
|
||||
// is not null, we can try to check through its groups.
|
||||
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?
|
||||
// Check if the container matches the supported groups,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.DisplacementMap;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -10,6 +11,11 @@ public sealed class DisplacementMapSystem : EntitySystem
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
private static string? BuildDisplacementLayerKey(object key)
|
||||
{
|
||||
return key.ToString() is null ? null : $"{key}-displacement";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempting to apply a displacement map to a specific layer of SpriteComponent
|
||||
/// </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="displacementKey">The key of the new displacement map layer added by this function.</param>
|
||||
/// <returns></returns>
|
||||
public bool TryAddDisplacement(DisplacementData data,
|
||||
public bool TryAddDisplacement(
|
||||
DisplacementData data,
|
||||
Entity<SpriteComponent> sprite,
|
||||
int index,
|
||||
object key,
|
||||
out string displacementKey)
|
||||
[NotNullWhen(true)] out string? displacementKey
|
||||
)
|
||||
{
|
||||
displacementKey = $"{key}-displacement";
|
||||
|
||||
if (key.ToString() is null)
|
||||
displacementKey = BuildDisplacementLayerKey(key);
|
||||
if (displacementKey is null)
|
||||
return false;
|
||||
|
||||
if (data.ShaderOverride != null)
|
||||
sprite.Comp.LayerSetShader(index, data.ShaderOverride);
|
||||
EnsureDisplacementIsNotOnSprite(sprite, key);
|
||||
|
||||
_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
|
||||
foreach (var pair in data.SizeMaps)
|
||||
@@ -70,7 +77,11 @@ public sealed class DisplacementMapSystem : EntitySystem
|
||||
}
|
||||
|
||||
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.LayerMapSet(sprite.AsNullable(), displacementKey, index);
|
||||
@@ -78,14 +89,18 @@ public sealed class DisplacementMapSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryAddDisplacement"/>
|
||||
[Obsolete("Use the Entity<SpriteComponent> overload")]
|
||||
public bool TryAddDisplacement(DisplacementData data,
|
||||
SpriteComponent sprite,
|
||||
int index,
|
||||
object key,
|
||||
out string displacementKey)
|
||||
/// <summary>
|
||||
/// Ensures that the displacement map associated with the given layer key is not in the Sprite's LayerMap.
|
||||
/// </summary>
|
||||
/// <param name="sprite">The sprite to remove the displacement layer from.</param>
|
||||
/// <param name="key">The key of the layer that is referenced by the displacement layer we want to remove.</param>
|
||||
/// <param name="logMissing">Whether to report an error if the displacement map isn't on the sprite.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
|
||||
comp.OpeningAnimation = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(comp.OpeningAnimationTime),
|
||||
Length = comp.OpeningAnimationTime,
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
@@ -47,7 +47,7 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
|
||||
comp.ClosingAnimation = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(comp.ClosingAnimationTime),
|
||||
Length = comp.ClosingAnimationTime,
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
@@ -63,7 +63,7 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
|
||||
comp.EmaggingAnimation = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(comp.EmaggingAnimationTime),
|
||||
Length = comp.EmaggingAnimationTime,
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
@@ -116,14 +116,14 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
|
||||
return;
|
||||
case DoorState.Opening:
|
||||
if (entity.Comp.OpeningAnimationTime == 0.0)
|
||||
if (entity.Comp.OpeningAnimationTime == TimeSpan.Zero)
|
||||
return;
|
||||
|
||||
_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.AnimationKey);
|
||||
|
||||
return;
|
||||
case DoorState.Closing:
|
||||
if (entity.Comp.ClosingAnimationTime == 0.0 || entity.Comp.CurrentlyCrushing.Count != 0)
|
||||
if (entity.Comp.ClosingAnimationTime == TimeSpan.Zero || entity.Comp.CurrentlyCrushing.Count != 0)
|
||||
return;
|
||||
|
||||
_animationSystem.Play(entity, (Animation)entity.Comp.ClosingAnimation, DoorComponent.AnimationKey);
|
||||
@@ -142,7 +142,7 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
|
||||
private void UpdateSpriteLayers(Entity<SpriteComponent> sprite, string targetProto)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(targetProto, out var target))
|
||||
if (!_prototypeManager.Resolve(targetProto, out var target))
|
||||
return;
|
||||
|
||||
if (!target.TryGetComponent(out SpriteComponent? targetSprite, _componentFactory))
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed partial class GatewayWindow : FancyWindow,
|
||||
_isUnlockPending = _nextUnlock >= _timing.CurTime;
|
||||
_isCooldownPending = _nextReady >= _timing.CurTime;
|
||||
|
||||
Container.DisposeAllChildren();
|
||||
Container.RemoveAllChildren();
|
||||
|
||||
if (_destinations.Count == 0)
|
||||
{
|
||||
|
||||
@@ -1,25 +1,58 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Ghost.Roles;
|
||||
using Content.Shared.Ghost.Roles.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
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)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
private SimpleRadialMenu? _ghostRoleRadioMenu;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_ghostRoleRadioMenu = this.CreateWindow<GhostRoleRadioMenu>();
|
||||
_ghostRoleRadioMenu.SetEntity(Owner);
|
||||
_ghostRoleRadioMenu.SendGhostRoleRadioMessageAction += SendGhostRoleRadioMessage;
|
||||
_ghostRoleRadioMenu = this.CreateWindow<SimpleRadialMenu>();
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
90
Content.Client/Graphics/OverlayResourceCache.cs
Normal file
90
Content.Client/Graphics/OverlayResourceCache.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// A cache for <see cref="Overlay"/>s to store per-viewport render resources, such as render targets.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data stored in the cache.</typeparam>
|
||||
public sealed class OverlayResourceCache<T> : IDisposable where T : class, IDisposable
|
||||
{
|
||||
private readonly Dictionary<long, CacheEntry> _cache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Get the data for a specific viewport, creating a new entry if necessary.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The cached data may be cleared at any time if <see cref="IClydeViewport.ClearCachedResources"/> gets invoked.
|
||||
/// </remarks>
|
||||
/// <param name="viewport">The viewport for which to retrieve cached data.</param>
|
||||
/// <param name="factory">A delegate used to create the cached data, if necessary.</param>
|
||||
public T GetForViewport(IClydeViewport viewport, Func<IClydeViewport, T> factory)
|
||||
{
|
||||
return GetForViewport(viewport, out _, factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the data for a specific viewport, creating a new entry if necessary.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The cached data may be cleared at any time if <see cref="IClydeViewport.ClearCachedResources"/> gets invoked.
|
||||
/// </remarks>
|
||||
/// <param name="viewport">The viewport for which to retrieve cached data.</param>
|
||||
/// <param name="wasCached">True if the data was pulled from cache, false if it was created anew.</param>
|
||||
/// <param name="factory">A delegate used to create the cached data, if necessary.</param>
|
||||
public T GetForViewport(IClydeViewport viewport, out bool wasCached, Func<IClydeViewport, T> factory)
|
||||
{
|
||||
if (_cache.TryGetValue(viewport.Id, out var entry))
|
||||
{
|
||||
wasCached = true;
|
||||
return entry.Data;
|
||||
}
|
||||
|
||||
wasCached = false;
|
||||
|
||||
entry = new CacheEntry
|
||||
{
|
||||
Data = factory(viewport),
|
||||
Viewport = new WeakReference<IClydeViewport>(viewport),
|
||||
};
|
||||
_cache.Add(viewport.Id, entry);
|
||||
|
||||
viewport.ClearCachedResources += ViewportOnClearCachedResources;
|
||||
|
||||
return entry.Data;
|
||||
}
|
||||
|
||||
private void ViewportOnClearCachedResources(ClearCachedViewportResourcesEvent ev)
|
||||
{
|
||||
if (!_cache.Remove(ev.ViewportId, out var entry))
|
||||
{
|
||||
// I think this could theoretically happen if you manually dispose the cache *after* a leaked viewport got
|
||||
// GC'd, but before its ClearCachedResources got invoked.
|
||||
return;
|
||||
}
|
||||
|
||||
entry.Data.Dispose();
|
||||
|
||||
if (ev.Viewport != null)
|
||||
ev.Viewport.ClearCachedResources -= ViewportOnClearCachedResources;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var entry in _cache)
|
||||
{
|
||||
if (entry.Value.Viewport.TryGetTarget(out var viewport))
|
||||
viewport.ClearCachedResources -= ViewportOnClearCachedResources;
|
||||
|
||||
entry.Value.Data.Dispose();
|
||||
}
|
||||
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
private struct CacheEntry
|
||||
{
|
||||
public T Data;
|
||||
public WeakReference<IClydeViewport> Viewport;
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,17 @@ using Content.Client.Guidebook.Richtext;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.UserInterface.ControlExtensions;
|
||||
using Content.Shared.Body.Prototypes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Contraband;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
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 ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
private readonly ChemistryGuideDataSystem _chemistryGuideData;
|
||||
private readonly ContrabandSystem _contraband;
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
public IPrototype? RepresentedPrototype { get; private set; }
|
||||
@@ -39,6 +44,7 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
|
||||
IoCManager.InjectDependencies(this);
|
||||
_sawmill = _logManager.GetSawmill("guidebook.reagent");
|
||||
_chemistryGuideData = _systemManager.GetEntitySystem<ChemistryGuideDataSystem>();
|
||||
_contraband = _systemManager.GetEntitySystem<ContrabandSystem>();
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
}
|
||||
|
||||
@@ -204,6 +210,25 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
|
||||
description.PushNewline();
|
||||
description.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-physical-description",
|
||||
("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);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public sealed partial class DocumentParsingManager
|
||||
|
||||
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;
|
||||
|
||||
using var file = _resourceManager.ContentFileReadText(entry.Text);
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
AlertsContainer.Visible = showAlerts;
|
||||
|
||||
if (showAlerts)
|
||||
AlertsContainer.DisposeAllChildren();
|
||||
AlertsContainer.RemoveAllChildren();
|
||||
|
||||
if (msg.Unrevivable == true)
|
||||
AlertsContainer.AddChild(new RichTextLabel
|
||||
|
||||
@@ -291,25 +291,26 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
private void RemoveMarking(Marking marking, Entity<SpriteComponent> spriteEnt)
|
||||
{
|
||||
if (!_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var sprite in prototype.Sprites)
|
||||
{
|
||||
if (sprite is not SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var layerId = $"{marking.MarkingId}-{rsi.RsiState}";
|
||||
if (!_sprite.LayerMapTryGet(spriteEnt.AsNullable(), layerId, out var index, false))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_sprite.LayerMapRemove(spriteEnt.AsNullable(), layerId);
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,9 +349,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
var sprite = entity.Comp2;
|
||||
|
||||
if (!_sprite.LayerMapTryGet((entity.Owner, sprite), markingPrototype.BodyPart, out var targetLayer, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
visible &= !IsHidden(humanoid, markingPrototype.BodyPart);
|
||||
visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting)
|
||||
@@ -361,9 +360,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
var markingSprite = markingPrototype.Sprites[j];
|
||||
|
||||
if (markingSprite is not SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
|
||||
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
|
||||
|
||||
@@ -377,26 +374,18 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
_sprite.LayerSetVisible((entity.Owner, sprite), layerId, visible);
|
||||
|
||||
if (!visible || setting == null) // this is kinda implied
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// So if that happens just default to white?
|
||||
if (colors != null && j < colors.Count)
|
||||
{
|
||||
_sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
|
||||
}
|
||||
|
||||
if (humanoid.MarkingsDisplacement.TryGetValue(markingPrototype.BodyPart, out var displacementData) && markingPrototype.CanBeDisplaced)
|
||||
{
|
||||
_displacement.TryAddDisplacement(displacementData, (entity.Owner, sprite), targetLayer + j + 1, layerId, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -416,7 +416,7 @@ public sealed partial class MarkingPicker : Control
|
||||
|
||||
var stateNames = GetMarkingStateNames(prototype);
|
||||
_currentMarkingColors.Clear();
|
||||
CMarkingColors.DisposeAllChildren();
|
||||
CMarkingColors.RemoveAllChildren();
|
||||
List<ColorSelectorSliders> colorSliders = new();
|
||||
for (int i = 0; i < prototype.Sprites.Count; i++)
|
||||
{
|
||||
|
||||
@@ -228,7 +228,6 @@ public sealed partial class SingleMarkingPicker : BoxContainer
|
||||
|
||||
var marking = _markings[Slot];
|
||||
|
||||
ColorSelectorContainer.DisposeAllChildren();
|
||||
ColorSelectorContainer.RemoveAllChildren();
|
||||
|
||||
if (marking.MarkingColors.Count != proto.Sprites.Count)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
using Content.Shared.IdentityManagement;
|
||||
|
||||
namespace Content.Client.IdentityManagement;
|
||||
|
||||
public sealed class IdentitySystem : SharedIdentitySystem
|
||||
{
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public sealed class ImplanterSystem : SharedImplanterSystem
|
||||
Dictionary<string, string> implants = new();
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ public sealed partial class ChameleonControllerMenu : FancyWindow
|
||||
// Go through every outfit and add them to the correct department.
|
||||
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.";
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed class ImplanterStatusControl : Control
|
||||
if (_parent.CurrentMode == ImplanterToggleMode.Draw)
|
||||
{
|
||||
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");
|
||||
|
||||
_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.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Lathe.UI;
|
||||
|
||||
@@ -96,7 +97,7 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
var recipesToShow = new List<LatheRecipePrototype>();
|
||||
foreach (var recipe in Recipes)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(recipe, out var proto))
|
||||
if (!_prototypeManager.Resolve(recipe, out var proto))
|
||||
continue;
|
||||
|
||||
// Category filtering
|
||||
@@ -128,21 +129,50 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
RecipeCount.Text = Loc.GetString("lathe-menu-recipe-count", ("count", recipesToShow.Count));
|
||||
|
||||
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);
|
||||
|
||||
int idx = 0;
|
||||
foreach (var prototype in sortedRecipesToShow)
|
||||
{
|
||||
var canProduce = _lathe.CanProduce(Entity, prototype, quantity, component: lathe);
|
||||
var tooltipFunction = () => GenerateTooltipText(prototype);
|
||||
|
||||
var control = new RecipeControl(_lathe, prototype, () => GenerateTooltipText(prototype), canProduce, GetRecipeDisplayControl(prototype));
|
||||
control.OnButtonPressed += s =>
|
||||
if (idx >= oldChildCount)
|
||||
{
|
||||
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
|
||||
amount = 1;
|
||||
RecipeQueueAction?.Invoke(s, amount);
|
||||
};
|
||||
RecipeList.AddChild(control);
|
||||
var control = new RecipeControl(_lathe, prototype, tooltipFunction, canProduce, GetRecipeDisplayControl(prototype));
|
||||
control.OnButtonPressed += s =>
|
||||
{
|
||||
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
|
||||
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)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(id, out var proto))
|
||||
if (!_prototypeManager.Resolve(id, out var proto))
|
||||
continue;
|
||||
|
||||
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, multiplier);
|
||||
@@ -238,9 +268,10 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
/// <param name="queue"></param>
|
||||
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)
|
||||
{
|
||||
var recipe = _prototypeManager.Index(batch.Recipe);
|
||||
@@ -248,18 +279,40 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
var itemName = _lathe.GetRecipeName(batch.Recipe);
|
||||
string displayText;
|
||||
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
|
||||
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));
|
||||
queuedRecipeBox.OnDeletePressed += s => QueueDeleteAction?.Invoke(s);
|
||||
queuedRecipeBox.OnMoveUpPressed += s => QueueMoveUpAction?.Invoke(s);
|
||||
queuedRecipeBox.OnMoveDownPressed += s => QueueMoveDownAction?.Invoke(s);
|
||||
if (idx >= oldChildCount)
|
||||
{
|
||||
var queuedRecipeBox = new QueuedRecipeControl(displayText, idx, GetRecipeDisplayControl(recipe));
|
||||
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++;
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -11,26 +11,46 @@ public sealed partial class QueuedRecipeControl : Control
|
||||
public Action<int>? OnMoveUpPressed;
|
||||
public Action<int>? OnMoveDownPressed;
|
||||
|
||||
private int _index;
|
||||
|
||||
public QueuedRecipeControl(string displayText, int index, Control displayControl)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
RecipeName.Text = displayText;
|
||||
RecipeDisplayContainer.AddChild(displayControl);
|
||||
SetDisplayText(displayText);
|
||||
SetDisplayControl(displayControl);
|
||||
SetIndex(index);
|
||||
_index = index;
|
||||
|
||||
MoveUp.OnPressed += (_) =>
|
||||
{
|
||||
OnMoveUpPressed?.Invoke(index);
|
||||
OnMoveUpPressed?.Invoke(_index);
|
||||
};
|
||||
|
||||
MoveDown.OnPressed += (_) =>
|
||||
{
|
||||
OnMoveDownPressed?.Invoke(index);
|
||||
OnMoveDownPressed?.Invoke(_index);
|
||||
};
|
||||
|
||||
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.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Lathe.UI;
|
||||
|
||||
@@ -11,20 +12,47 @@ public sealed partial class RecipeControl : Control
|
||||
public Action<string>? OnButtonPressed;
|
||||
public Func<string> TooltipTextSupplier;
|
||||
|
||||
private ProtoId<LatheRecipePrototype> _recipeId;
|
||||
private LatheSystem _latheSystem;
|
||||
|
||||
public RecipeControl(LatheSystem latheSystem, LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Control displayControl)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
RecipeName.Text = latheSystem.GetRecipeName(recipe);
|
||||
RecipeDisplayContainer.AddChild(displayControl);
|
||||
Button.Disabled = !canProduce;
|
||||
_latheSystem = latheSystem;
|
||||
_recipeId = recipe.ID;
|
||||
TooltipTextSupplier = tooltipTextSupplier;
|
||||
Button.TooltipSupplier = SupplyTooltip;
|
||||
SetRecipe(recipe);
|
||||
SetCanProduce(canProduce);
|
||||
SetDisplayControl(displayControl);
|
||||
|
||||
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)
|
||||
|
||||
@@ -30,6 +30,7 @@ public sealed class AfterLightTargetOverlay : Overlay
|
||||
return;
|
||||
|
||||
var lightOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var lightRes = lightOverlay.GetCachedForViewport(args.Viewport);
|
||||
var bounds = args.WorldBounds;
|
||||
|
||||
// at 1-1 render scale it's mostly fine but at 4x4 it's way too fkn big
|
||||
@@ -38,7 +39,7 @@ public sealed class AfterLightTargetOverlay : Overlay
|
||||
|
||||
var localMatrix =
|
||||
viewport.LightRenderTarget.GetWorldToLocalMatrix(viewport.Eye, newScale);
|
||||
var diff = (lightOverlay.EnlargedLightTarget.Size - viewport.LightRenderTarget.Size);
|
||||
var diff = (lightRes.EnlargedLightTarget.Size - viewport.LightRenderTarget.Size);
|
||||
var halfDiff = diff / 2;
|
||||
|
||||
// Pixels -> Metres -> Half distance.
|
||||
@@ -53,7 +54,7 @@ public sealed class AfterLightTargetOverlay : Overlay
|
||||
viewport.LightRenderTarget.Size.Y + halfDiff.Y);
|
||||
|
||||
worldHandle.SetTransform(localMatrix);
|
||||
worldHandle.DrawTextureRectRegion(lightOverlay.EnlargedLightTarget.Texture, bounds, subRegion: subRegion);
|
||||
worldHandle.DrawTextureRectRegion(lightRes.EnlargedLightTarget.Texture, bounds, subRegion: subRegion);
|
||||
}, Color.Transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Graphics;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -27,11 +28,7 @@ public sealed class AmbientOcclusionOverlay : Overlay
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
|
||||
|
||||
private IRenderTexture? _aoTarget;
|
||||
private IRenderTexture? _aoBlurBuffer;
|
||||
|
||||
// Couldn't figure out a way to avoid this so if you can then please do.
|
||||
private IRenderTexture? _aoStencilTarget;
|
||||
private readonly OverlayResourceCache<CachedResources> _resources = new ();
|
||||
|
||||
public AmbientOcclusionOverlay()
|
||||
{
|
||||
@@ -69,30 +66,32 @@ public sealed class AmbientOcclusionOverlay : Overlay
|
||||
var turfSystem = _entManager.System<TurfSystem>();
|
||||
var invMatrix = args.Viewport.GetWorldToLocalMatrix();
|
||||
|
||||
if (_aoTarget?.Texture.Size != target.Size)
|
||||
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
|
||||
|
||||
if (res.AOTarget?.Texture.Size != target.Size)
|
||||
{
|
||||
_aoTarget?.Dispose();
|
||||
_aoTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-target");
|
||||
res.AOTarget?.Dispose();
|
||||
res.AOTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-target");
|
||||
}
|
||||
|
||||
if (_aoBlurBuffer?.Texture.Size != target.Size)
|
||||
if (res.AOBlurBuffer?.Texture.Size != target.Size)
|
||||
{
|
||||
_aoBlurBuffer?.Dispose();
|
||||
_aoBlurBuffer = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-blur-target");
|
||||
res.AOBlurBuffer?.Dispose();
|
||||
res.AOBlurBuffer = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-blur-target");
|
||||
}
|
||||
|
||||
if (_aoStencilTarget?.Texture.Size != target.Size)
|
||||
if (res.AOStencilTarget?.Texture.Size != target.Size)
|
||||
{
|
||||
_aoStencilTarget?.Dispose();
|
||||
_aoStencilTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-stencil-target");
|
||||
res.AOStencilTarget?.Dispose();
|
||||
res.AOStencilTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-stencil-target");
|
||||
}
|
||||
|
||||
// Draw the texture data to the texture.
|
||||
args.WorldHandle.RenderInRenderTarget(_aoTarget,
|
||||
args.WorldHandle.RenderInRenderTarget(res.AOTarget,
|
||||
() =>
|
||||
{
|
||||
worldHandle.UseShader(_proto.Index(UnshadedShader).Instance());
|
||||
var invMatrix = _aoTarget.GetWorldToLocalMatrix(viewport.Eye!, scale);
|
||||
var invMatrix = res.AOTarget.GetWorldToLocalMatrix(viewport.Eye!, scale);
|
||||
|
||||
foreach (var entry in query.QueryAabb(mapId, worldBounds))
|
||||
{
|
||||
@@ -106,11 +105,11 @@ public sealed class AmbientOcclusionOverlay : Overlay
|
||||
}
|
||||
}, Color.Transparent);
|
||||
|
||||
_clyde.BlurRenderTarget(viewport, _aoTarget, _aoBlurBuffer, viewport.Eye!, 14f);
|
||||
_clyde.BlurRenderTarget(viewport, res.AOTarget, res.AOBlurBuffer, viewport.Eye!, 14f);
|
||||
|
||||
// Need to do stencilling after blur as it will nuke it.
|
||||
// Draw stencil for the grid so we don't draw in space.
|
||||
args.WorldHandle.RenderInRenderTarget(_aoStencilTarget,
|
||||
args.WorldHandle.RenderInRenderTarget(res.AOStencilTarget,
|
||||
() =>
|
||||
{
|
||||
// Don't want lighting affecting it.
|
||||
@@ -136,13 +135,36 @@ public sealed class AmbientOcclusionOverlay : Overlay
|
||||
|
||||
// Draw the stencil texture to depth buffer.
|
||||
worldHandle.UseShader(_proto.Index(StencilMaskShader).Instance());
|
||||
worldHandle.DrawTextureRect(_aoStencilTarget!.Texture, worldBounds);
|
||||
worldHandle.DrawTextureRect(res.AOStencilTarget!.Texture, worldBounds);
|
||||
|
||||
// Draw the Blurred AO texture finally.
|
||||
worldHandle.UseShader(_proto.Index(StencilEqualDrawShader).Instance());
|
||||
worldHandle.DrawTextureRect(_aoTarget!.Texture, worldBounds, color);
|
||||
worldHandle.DrawTextureRect(res.AOTarget!.Texture, worldBounds, color);
|
||||
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
args.WorldHandle.UseShader(null);
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
{
|
||||
_resources.Dispose();
|
||||
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
private sealed class CachedResources : IDisposable
|
||||
{
|
||||
public IRenderTexture? AOTarget;
|
||||
public IRenderTexture? AOBlurBuffer;
|
||||
|
||||
// Couldn't figure out a way to avoid this so if you can then please do.
|
||||
public IRenderTexture? AOStencilTarget;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AOTarget?.Dispose();
|
||||
AOBlurBuffer?.Dispose();
|
||||
AOStencilTarget?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Graphics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
@@ -13,7 +13,8 @@ public sealed class BeforeLightTargetOverlay : Overlay
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
|
||||
public IRenderTexture EnlargedLightTarget = default!;
|
||||
private readonly OverlayResourceCache<CachedResources> _resources = new();
|
||||
|
||||
public Box2Rotated EnlargedBounds;
|
||||
|
||||
/// <summary>
|
||||
@@ -36,16 +37,42 @@ public sealed class BeforeLightTargetOverlay : Overlay
|
||||
var size = args.Viewport.LightRenderTarget.Size + (int) (_skirting * EyeManager.PixelsPerMeter);
|
||||
EnlargedBounds = args.WorldBounds.Enlarged(_skirting / 2f);
|
||||
|
||||
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
|
||||
|
||||
// This just exists to copy the lightrendertarget and write back to it.
|
||||
if (EnlargedLightTarget?.Size != size)
|
||||
if (res.EnlargedLightTarget?.Size != size)
|
||||
{
|
||||
EnlargedLightTarget = _clyde
|
||||
res.EnlargedLightTarget = _clyde
|
||||
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-copy");
|
||||
}
|
||||
|
||||
args.WorldHandle.RenderInRenderTarget(EnlargedLightTarget,
|
||||
args.WorldHandle.RenderInRenderTarget(res.EnlargedLightTarget,
|
||||
() =>
|
||||
{
|
||||
}, _clyde.GetClearColor(args.MapUid));
|
||||
}
|
||||
|
||||
internal CachedResources GetCachedForViewport(IClydeViewport viewport)
|
||||
{
|
||||
return _resources.GetForViewport(viewport,
|
||||
static _ => throw new InvalidOperationException(
|
||||
"Expected BeforeLightTargetOverlay to have created its resources"));
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
{
|
||||
_resources.Dispose();
|
||||
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
internal sealed class CachedResources : IDisposable
|
||||
{
|
||||
public IRenderTexture EnlargedLightTarget = default!;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EnlargedLightTarget?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.Graphics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
@@ -15,7 +16,7 @@ public sealed class LightBlurOverlay : Overlay
|
||||
|
||||
public const int ContentZIndex = TileEmissionOverlay.ContentZIndex + 1;
|
||||
|
||||
private IRenderTarget? _blurTarget;
|
||||
private readonly OverlayResourceCache<CachedResources> _resources = new();
|
||||
|
||||
public LightBlurOverlay()
|
||||
{
|
||||
@@ -29,16 +30,36 @@ public sealed class LightBlurOverlay : Overlay
|
||||
return;
|
||||
|
||||
var beforeOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var size = beforeOverlay.EnlargedLightTarget.Size;
|
||||
var beforeLightRes = beforeOverlay.GetCachedForViewport(args.Viewport);
|
||||
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
|
||||
|
||||
if (_blurTarget?.Size != size)
|
||||
var size = beforeLightRes.EnlargedLightTarget.Size;
|
||||
|
||||
if (res.BlurTarget?.Size != size)
|
||||
{
|
||||
_blurTarget = _clyde
|
||||
res.BlurTarget = _clyde
|
||||
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-blur");
|
||||
}
|
||||
|
||||
var target = beforeOverlay.EnlargedLightTarget;
|
||||
var target = beforeLightRes.EnlargedLightTarget;
|
||||
// Yeah that's all this does keep walkin.
|
||||
_clyde.BlurRenderTarget(args.Viewport, target, _blurTarget, args.Viewport.Eye, 14f * 5f);
|
||||
_clyde.BlurRenderTarget(args.Viewport, target, res.BlurTarget, args.Viewport.Eye, 14f * 5f);
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
{
|
||||
_resources.Dispose();
|
||||
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
private sealed class CachedResources : IDisposable
|
||||
{
|
||||
public IRenderTarget? BlurTarget;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BlurTarget?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,9 @@ public sealed class RoofOverlay : Overlay
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var lightRes = lightoverlay.GetCachedForViewport(args.Viewport);
|
||||
var bounds = lightoverlay.EnlargedBounds;
|
||||
var target = lightoverlay.EnlargedLightTarget;
|
||||
var target = lightRes.EnlargedLightTarget;
|
||||
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(args.MapId, bounds, ref _grids, approx: true, includeMap: true);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Graphics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -24,8 +25,7 @@ public sealed class SunShadowOverlay : Overlay
|
||||
|
||||
private readonly HashSet<Entity<SunShadowCastComponent>> _shadows = new();
|
||||
|
||||
private IRenderTexture? _blurTarget;
|
||||
private IRenderTexture? _target;
|
||||
private readonly OverlayResourceCache<CachedResources> _resources = new();
|
||||
|
||||
public SunShadowOverlay()
|
||||
{
|
||||
@@ -55,16 +55,18 @@ public sealed class SunShadowOverlay : Overlay
|
||||
var worldBounds = args.WorldBounds;
|
||||
var targetSize = viewport.LightRenderTarget.Size;
|
||||
|
||||
if (_target?.Size != targetSize)
|
||||
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
|
||||
|
||||
if (res.Target?.Size != targetSize)
|
||||
{
|
||||
_target = _clyde
|
||||
res.Target = _clyde
|
||||
.CreateRenderTarget(targetSize,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
|
||||
name: "sun-shadow-target");
|
||||
|
||||
if (_blurTarget?.Size != targetSize)
|
||||
if (res.BlurTarget?.Size != targetSize)
|
||||
{
|
||||
_blurTarget = _clyde
|
||||
res.BlurTarget = _clyde
|
||||
.CreateRenderTarget(targetSize, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "sun-shadow-blur");
|
||||
}
|
||||
}
|
||||
@@ -93,11 +95,11 @@ public sealed class SunShadowOverlay : Overlay
|
||||
_shadows.Clear();
|
||||
|
||||
// Draw shadow polys to stencil
|
||||
args.WorldHandle.RenderInRenderTarget(_target,
|
||||
args.WorldHandle.RenderInRenderTarget(res.Target,
|
||||
() =>
|
||||
{
|
||||
var invMatrix =
|
||||
_target.GetWorldToLocalMatrix(eye, scale);
|
||||
res.Target.GetWorldToLocalMatrix(eye, scale);
|
||||
var indices = new Vector2[PhysicsConstants.MaxPolygonVertices * 2];
|
||||
|
||||
// Go through shadows in range.
|
||||
@@ -142,7 +144,7 @@ public sealed class SunShadowOverlay : Overlay
|
||||
Color.Transparent);
|
||||
|
||||
// Slightly blur it just to avoid aliasing issues on the later viewport-wide blur.
|
||||
_clyde.BlurRenderTarget(viewport, _target, _blurTarget!, eye, 1f);
|
||||
_clyde.BlurRenderTarget(viewport, res.Target, res.BlurTarget!, eye, 1f);
|
||||
|
||||
// Draw stencil (see roofoverlay).
|
||||
args.WorldHandle.RenderInRenderTarget(viewport.LightRenderTarget,
|
||||
@@ -155,8 +157,27 @@ public sealed class SunShadowOverlay : Overlay
|
||||
var maskShader = _protoManager.Index(MixShader).Instance();
|
||||
worldHandle.UseShader(maskShader);
|
||||
|
||||
worldHandle.DrawTextureRect(_target.Texture, worldBounds, Color.Black.WithAlpha(alpha));
|
||||
worldHandle.DrawTextureRect(res.Target.Texture, worldBounds, Color.Black.WithAlpha(alpha));
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
{
|
||||
_resources.Dispose();
|
||||
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
private sealed class CachedResources : IDisposable
|
||||
{
|
||||
public IRenderTexture? BlurTarget;
|
||||
public IRenderTexture? Target;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BlurTarget?.Dispose();
|
||||
Target?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public sealed class TileEmissionOverlay : Overlay
|
||||
var worldHandle = args.WorldHandle;
|
||||
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var bounds = lightoverlay.EnlargedBounds;
|
||||
var target = lightoverlay.EnlargedLightTarget;
|
||||
var target = lightoverlay.GetCachedForViewport(args.Viewport).EnlargedLightTarget;
|
||||
var viewport = args.Viewport;
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(mapId, bounds, ref _grids, approx: true);
|
||||
|
||||
@@ -207,10 +207,10 @@ namespace Content.Client.Lobby
|
||||
else
|
||||
{
|
||||
Lobby!.StartTime.Text = string.Empty;
|
||||
Lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady;
|
||||
Lobby!.ReadyButton.Text = Loc.GetString(Lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready");
|
||||
Lobby!.ReadyButton.ToggleMode = true;
|
||||
Lobby!.ReadyButton.Disabled = false;
|
||||
Lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady;
|
||||
Lobby!.ObserveButton.Disabled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ public sealed partial class LobbyUIController : UIController, IOnStateEntered<Lo
|
||||
});
|
||||
|
||||
_configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor());
|
||||
_configurationManager.OnValueChanged(CCVars.GameRoleLoadoutTimers, _ => RefreshProfileEditor());
|
||||
|
||||
_configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor());
|
||||
}
|
||||
@@ -362,7 +363,7 @@ public sealed partial class LobbyUIController : UIController, IOnStateEntered<Lo
|
||||
{
|
||||
foreach (var loadout in group)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
|
||||
if (!_prototypeManager.Resolve(loadout.Prototype, out var loadoutProto))
|
||||
continue;
|
||||
|
||||
_spawn.EquipStartingGear(uid, loadoutProto);
|
||||
@@ -385,14 +386,14 @@ public sealed partial class LobbyUIController : UIController, IOnStateEntered<Lo
|
||||
{
|
||||
foreach (var loadout in loadouts)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
|
||||
if (!_prototypeManager.Resolve(loadout.Prototype, out var loadoutProto))
|
||||
continue;
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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);
|
||||
|
||||
@@ -427,7 +428,7 @@ public sealed partial class LobbyUIController : UIController, IOnStateEntered<Lo
|
||||
}
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(job.StartingGear, out var gear))
|
||||
if (!_prototypeManager.Resolve(job.StartingGear, out var gear))
|
||||
return;
|
||||
|
||||
foreach (var slot in slots)
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Content.Client.Lobby.UI
|
||||
public void ReloadCharacterPickers()
|
||||
{
|
||||
_createNewCharacterButton.Orphan();
|
||||
Characters.DisposeAllChildren();
|
||||
Characters.RemoveAllChildren();
|
||||
|
||||
var numberOfFullSlots = 0;
|
||||
var characterButtonsGroup = new ButtonGroup();
|
||||
|
||||
@@ -39,6 +39,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using static Content.Client.Corvax.SponsorOnlyHelpers; // Corvax-Sponsors
|
||||
using Content.Client.Corvax.TTS; // Corvax-TTS
|
||||
|
||||
namespace Content.Client.Lobby.UI
|
||||
{
|
||||
@@ -132,6 +133,8 @@ namespace Content.Client.Lobby.UI
|
||||
// One at a time.
|
||||
private LoadoutWindow? _loadoutWindow;
|
||||
|
||||
private TTSTab? _ttsTab; // Corvax-TTS
|
||||
|
||||
private bool _exporting;
|
||||
private bool _imaging;
|
||||
|
||||
@@ -333,18 +336,6 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
#endregion Gender
|
||||
|
||||
// Corvax-TTS-Start
|
||||
#region Voice
|
||||
|
||||
if (configurationManager.GetCVar(CCCVars.TTSEnabled))
|
||||
{
|
||||
TTSContainer.Visible = true;
|
||||
InitializeVoice();
|
||||
}
|
||||
|
||||
#endregion
|
||||
// Corvax-TTS-End
|
||||
|
||||
RefreshSpecies();
|
||||
|
||||
SpeciesButton.OnItemSelected += args =>
|
||||
@@ -732,6 +723,8 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
RefreshTraits();
|
||||
|
||||
TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab")); // Corvax-TTS-Edit
|
||||
|
||||
#region Markings
|
||||
|
||||
TabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab"));
|
||||
@@ -746,6 +739,7 @@ namespace Content.Client.Lobby.UI
|
||||
RefreshFlavorText();
|
||||
|
||||
RefreshRecords(); // WL-Records
|
||||
RefreshVoiceTab(); // Corvax-TTS
|
||||
|
||||
#region Dummy
|
||||
|
||||
@@ -856,16 +850,65 @@ namespace Content.Client.Lobby.UI
|
||||
SetDirty();
|
||||
}
|
||||
// WL-Records-End
|
||||
// Corvax-TTS-Start
|
||||
#region Voice
|
||||
|
||||
private void RefreshVoiceTab()
|
||||
{
|
||||
if (!_cfgManager.GetCVar(CCCVars.TTSEnabled))
|
||||
return;
|
||||
|
||||
_ttsTab = new TTSTab();
|
||||
var children = new List<Control>();
|
||||
foreach (var child in TabContainer.Children)
|
||||
children.Add(child);
|
||||
|
||||
TabContainer.RemoveAllChildren();
|
||||
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
if (i == 1) // Set the tab to the 2nd place.
|
||||
{
|
||||
TabContainer.AddChild(_ttsTab);
|
||||
}
|
||||
TabContainer.AddChild(children[i]);
|
||||
}
|
||||
|
||||
TabContainer.SetTabTitle(1, Loc.GetString("humanoid-profile-editor-voice-tab"));
|
||||
|
||||
_ttsTab.OnVoiceSelected += voiceId =>
|
||||
{
|
||||
SetVoice(voiceId);
|
||||
_ttsTab.SetSelectedVoice(voiceId);
|
||||
};
|
||||
|
||||
/*_ttsTab.OnPreviewRequested += voiceId =>
|
||||
{
|
||||
_entManager.System<TTSSystem>().RequestPreviewTTS(voiceId);
|
||||
};*/
|
||||
}
|
||||
|
||||
private void UpdateTTSVoicesControls()
|
||||
{
|
||||
if (Profile is null || _ttsTab is null)
|
||||
return;
|
||||
|
||||
_ttsTab.UpdateControls(Profile, Profile.Sex);
|
||||
_ttsTab.SetSelectedVoice(Profile.Voice);
|
||||
}
|
||||
|
||||
#endregion
|
||||
// Corvax-TTS-End
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes traits selector
|
||||
/// </summary>
|
||||
public void RefreshTraits()
|
||||
{
|
||||
TraitsList.DisposeAllChildren();
|
||||
TraitsList.RemoveAllChildren();
|
||||
|
||||
var traits = _prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
|
||||
TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
|
||||
// TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab")); // Corvax-TTS-Edit
|
||||
|
||||
if (traits.Count < 1)
|
||||
{
|
||||
@@ -1007,7 +1050,7 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
public void RefreshAntags()
|
||||
{
|
||||
AntagList.DisposeAllChildren();
|
||||
AntagList.RemoveAllChildren();
|
||||
var items = new[]
|
||||
{
|
||||
("humanoid-profile-editor-antag-preference-yes-button", 0),
|
||||
@@ -1037,8 +1080,10 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
|
||||
|
||||
var requirements = _entManager.System<SharedRoleSystem>().GetAntagRequirement(antag);
|
||||
if (!_requirements.CheckRoleRequirements(requirements, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
|
||||
if (!_requirements.IsAllowed(
|
||||
antag,
|
||||
(HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter,
|
||||
out var reason))
|
||||
{
|
||||
selector.LockRequirements(reason);
|
||||
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
||||
@@ -1192,7 +1237,7 @@ namespace Content.Client.Lobby.UI
|
||||
if (_prototypeManager.HasIndex<GuideEntryPrototype>(species))
|
||||
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>();
|
||||
dict.Add(DefaultSpeciesGuidebook, guideRoot);
|
||||
@@ -1208,7 +1253,7 @@ namespace Content.Client.Lobby.UI
|
||||
public void RefreshJobs()
|
||||
{
|
||||
JobList.DisposeAllChildren();
|
||||
|
||||
JobList.RemoveAllChildren();
|
||||
_jobCategories.Clear();
|
||||
_jobPriorities.Clear();
|
||||
var firstCategory = true;
|
||||
@@ -1512,10 +1557,11 @@ namespace Content.Client.Lobby.UI
|
||||
if (Profile is null) return;
|
||||
|
||||
var skin = _prototypeManager.Index<SpeciesPrototype>(Profile.Species).SkinColoration;
|
||||
var strategy = _prototypeManager.Index(skin).Strategy;
|
||||
|
||||
switch (skin)
|
||||
switch (strategy.InputType)
|
||||
{
|
||||
case HumanoidSkinColor.HumanToned:
|
||||
case SkinColorationStrategyInput.Unary:
|
||||
{
|
||||
if (!Skin.Visible)
|
||||
{
|
||||
@@ -1523,39 +1569,14 @@ namespace Content.Client.Lobby.UI
|
||||
RgbSkinColorContainer.Visible = false;
|
||||
}
|
||||
|
||||
var color = SkinColor.HumanSkinTone((int) Skin.Value);
|
||||
|
||||
Markings.CurrentSkinColor = color;
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));//
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.Hues:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
Skin.Visible = false;
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
Markings.CurrentSkinColor = _rgbSkinColorSelector.Color;
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(_rgbSkinColorSelector.Color));
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.TintedHues:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
Skin.Visible = false;
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
var color = SkinColor.TintedHues(_rgbSkinColorSelector.Color);
|
||||
var color = strategy.FromUnary(Skin.Value);
|
||||
|
||||
Markings.CurrentSkinColor = color;
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
|
||||
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.VoxFeathers:
|
||||
case SkinColorationStrategyInput.Color:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
@@ -1563,10 +1584,11 @@ namespace Content.Client.Lobby.UI
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
var color = SkinColor.ClosestVoxColor(_rgbSkinColorSelector.Color);
|
||||
var color = strategy.ClosestSkinColor(_rgbSkinColorSelector.Color);
|
||||
|
||||
Markings.CurrentSkinColor = color;
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1763,7 +1785,7 @@ namespace Content.Client.Lobby.UI
|
||||
var sexes = new List<Sex>();
|
||||
|
||||
// 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)
|
||||
{
|
||||
@@ -1793,10 +1815,11 @@ namespace Content.Client.Lobby.UI
|
||||
return;
|
||||
|
||||
var skin = _prototypeManager.Index<SpeciesPrototype>(Profile.Species).SkinColoration;
|
||||
var strategy = _prototypeManager.Index(skin).Strategy;
|
||||
|
||||
switch (skin)
|
||||
switch (strategy.InputType)
|
||||
{
|
||||
case HumanoidSkinColor.HumanToned:
|
||||
case SkinColorationStrategyInput.Unary:
|
||||
{
|
||||
if (!Skin.Visible)
|
||||
{
|
||||
@@ -1804,11 +1827,11 @@ namespace Content.Client.Lobby.UI
|
||||
RgbSkinColorContainer.Visible = false;
|
||||
}
|
||||
|
||||
Skin.Value = SkinColor.HumanSkinToneFromColor(Profile.Appearance.SkinColor);
|
||||
Skin.Value = strategy.ToUnary(Profile.Appearance.SkinColor);
|
||||
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.Hues:
|
||||
case SkinColorationStrategyInput.Color:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
@@ -1816,36 +1839,11 @@ namespace Content.Client.Lobby.UI
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
// set the RGB values to the direct values otherwise
|
||||
_rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.TintedHues:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
Skin.Visible = false;
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
// set the RGB values to the direct values otherwise
|
||||
_rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.VoxFeathers:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
Skin.Visible = false;
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
_rgbSkinColorSelector.Color = SkinColor.ClosestVoxColor(Profile.Appearance.SkinColor);
|
||||
_rgbSkinColorSelector.Color = strategy.ClosestSkinColor(Profile.Appearance.SkinColor);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void UpdateSpeciesGuidebookIcon()
|
||||
@@ -1856,7 +1854,7 @@ namespace Content.Client.Lobby.UI
|
||||
if (species is null)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex<SpeciesPrototype>(species, out var speciesProto))
|
||||
if (!_prototypeManager.Resolve<SpeciesPrototype>(species, out var speciesProto))
|
||||
return;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (_protoManager.TryIndex(proto, out var loadProto))
|
||||
if (_protoManager.Resolve(proto, out var loadProto))
|
||||
{
|
||||
var ent = loadProto.DummyEntity ?? _entManager.System<LoadoutSystem>().GetFirstOrNull(loadProto);
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
{
|
||||
var protoMan = collection.Resolve<IPrototypeManager>();
|
||||
var loadoutSystem = collection.Resolve<IEntityManager>().System<LoadoutSystem>();
|
||||
RestrictionsContainer.DisposeAllChildren();
|
||||
RestrictionsContainer.RemoveAllChildren();
|
||||
|
||||
if (_groupProto.MinLimit > 0)
|
||||
{
|
||||
@@ -63,7 +63,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()
|
||||
{
|
||||
@@ -72,7 +72,7 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
});
|
||||
}
|
||||
|
||||
LoadoutsContainer.DisposeAllChildren();
|
||||
LoadoutsContainer.RemoveAllChildren();
|
||||
|
||||
// Corvax-Loadouts-Start
|
||||
var groupLoadouts = _groupProto.Loadouts;
|
||||
@@ -124,14 +124,14 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
})
|
||||
.ToList();
|
||||
|
||||
/*
|
||||
* Determine which element should be displayed first:
|
||||
* - If any element is currently selected (its button is pressed), use it.
|
||||
* - Otherwise, fallback to the first element in the list.
|
||||
*
|
||||
* 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)
|
||||
* without having to search inside expanded subgroups.
|
||||
/*
|
||||
* Determine which element should be displayed first:
|
||||
* - If any element is currently selected (its button is pressed), use it.
|
||||
* - Otherwise, fallback to the first element in the list.
|
||||
*
|
||||
* 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)
|
||||
* without having to search inside expanded subgroups.
|
||||
*/
|
||||
var firstElement = uiElements.FirstOrDefault(e => e.Select.Pressed) ?? uiElements[0];
|
||||
|
||||
@@ -207,8 +207,8 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
/// <summary>
|
||||
/// Creates a UI container for a single Loadout item.
|
||||
///
|
||||
/// This method was extracted from RefreshLoadouts because the logic for creating
|
||||
/// individual loadout items is used multiple times inside that method, and duplicating
|
||||
/// This method was extracted from RefreshLoadouts because the logic for creating
|
||||
/// individual loadout items is used multiple times inside that method, and duplicating
|
||||
/// the code made it harder to maintain.
|
||||
///
|
||||
/// Logic:
|
||||
|
||||
@@ -68,7 +68,7 @@ public sealed partial class LoadoutWindow : FancyWindow
|
||||
{
|
||||
foreach (var group in proto.Groups)
|
||||
{
|
||||
if (!protoManager.TryIndex(group, out var groupProto))
|
||||
if (!protoManager.Resolve(group, out var groupProto))
|
||||
continue;
|
||||
|
||||
if (groupProto.Hidden)
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed partial class LobbyCharacterPreviewPanel : Control
|
||||
|
||||
_previewDummy = uid;
|
||||
|
||||
ViewBox.DisposeAllChildren();
|
||||
ViewBox.RemoveAllChildren();
|
||||
var spriteView = new SpriteView
|
||||
{
|
||||
OverrideDirection = Direction.South,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -37,7 +37,7 @@ public sealed partial class MappingPrototypeList : Control
|
||||
{
|
||||
_prototypes.Clear();
|
||||
|
||||
PrototypeList.DisposeAllChildren();
|
||||
PrototypeList.RemoveAllChildren();
|
||||
|
||||
_prototypes.AddRange(prototypes);
|
||||
|
||||
@@ -99,7 +99,7 @@ public sealed partial class MappingPrototypeList : Control
|
||||
public void Search(List<MappingPrototype> prototypes)
|
||||
{
|
||||
_search.Clear();
|
||||
SearchList.DisposeAllChildren();
|
||||
SearchList.RemoveAllChildren();
|
||||
_lastIndices = (0, -1);
|
||||
|
||||
_search.AddRange(prototypes);
|
||||
|
||||
@@ -861,7 +861,7 @@ public sealed class MappingState : GameplayStateBase
|
||||
}
|
||||
else
|
||||
{
|
||||
button.ChildrenPrototypes.DisposeAllChildren();
|
||||
button.ChildrenPrototypes.RemoveAllChildren();
|
||||
button.CollapseButton.Label.Text = "▶";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,9 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl
|
||||
if (!LocalizedNames.TryGetValue(netEntity, out var name))
|
||||
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;
|
||||
_trackedEntityPanel.Visible = true;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
namespace Content.Client.Nutrition.EntitySystems;
|
||||
|
||||
public sealed class DrinkSystem : SharedDrinkSystem
|
||||
{
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public sealed class EntityHealthBarOverlay : Overlay
|
||||
const float scale = 1f;
|
||||
var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale));
|
||||
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>();
|
||||
while (query.MoveNext(out var uid,
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class ShowCriminalRecordIconsSystem : EquipmentHudSystem<ShowCrimi
|
||||
if (!IsActive)
|
||||
return;
|
||||
|
||||
if (_prototype.TryIndex(component.StatusIcon, out var iconPrototype))
|
||||
if (_prototype.Resolve(component.StatusIcon, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,9 +78,9 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
|
||||
if (TryComp<MobStateComponent>(entity, out var state))
|
||||
{
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
else
|
||||
Log.Error($"Invalid job icon prototype: {iconPrototype}");
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed class ShowMindShieldIconsSystem : EquipmentHudSystem<ShowMindShiel
|
||||
{
|
||||
if(!IsActive)
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed class ShowMindShieldIconsSystem : EquipmentHudSystem<ShowMindShiel
|
||||
if (!IsActive)
|
||||
return;
|
||||
|
||||
if (_prototype.TryIndex(component.MindShieldStatusIcon, out var iconPrototype))
|
||||
if (_prototype.Resolve(component.MindShieldStatusIcon, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ namespace Content.Client.Overlays;
|
||||
|
||||
public sealed partial class StencilOverlay
|
||||
{
|
||||
private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3x2 invMatrix)
|
||||
private void DrawRestrictedRange(
|
||||
in OverlayDrawArgs args,
|
||||
CachedResources res,
|
||||
RestrictedRangeComponent rangeComp,
|
||||
Matrix3x2 invMatrix)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
var renderScale = args.Viewport.RenderScale.X;
|
||||
@@ -38,7 +42,7 @@ public sealed partial class StencilOverlay
|
||||
// Cut out the irrelevant bits via stencil
|
||||
// This is why we don't just use parallax; we might want specific tiles to get drawn over
|
||||
// particularly for planet maps or stations.
|
||||
worldHandle.RenderInRenderTarget(_blep!, () =>
|
||||
worldHandle.RenderInRenderTarget(res.Blep!, () =>
|
||||
{
|
||||
worldHandle.UseShader(_shader);
|
||||
worldHandle.DrawRect(localAABB, Color.White);
|
||||
@@ -46,7 +50,7 @@ public sealed partial class StencilOverlay
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
worldHandle.UseShader(_protoManager.Index(StencilMask).Instance());
|
||||
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
|
||||
worldHandle.DrawTextureRect(res.Blep!.Texture, worldBounds);
|
||||
var curTime = _timing.RealTime;
|
||||
var sprite = _sprite.GetFrame(new SpriteSpecifier.Texture(new ResPath("/Textures/Parallaxes/noise.png")), curTime);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user