Compare commits

..

18 Commits

Author SHA1 Message Date
Deserty0
aa621ece26 Merge remote-tracking branch 'upstream/master' into funnyfunnystuff 2025-08-23 19:44:16 +10:00
Deserty0
52880de47e должно теперь работать 2025-08-13 21:07:21 +10:00
Deserty0
eb289104ab Update DiscordAuthManager.cs 2025-08-13 05:40:08 +10:00
Deserty0
db49a55481 Update CCvars.CP14DiscordAuth.cs 2025-08-13 05:40:08 +10:00
Deserty0
f01725fdc7 Update CCvars.CP14DiscordAuth.cs 2025-08-13 05:15:57 +10:00
Deserty0
0a6a560066 Update DiscordAuthManager.cs 2025-08-13 05:04:38 +10:00
Deserty0
6f170bb45f lol 2025-08-13 04:55:57 +10:00
Deserty0
cec07307bf 123 2025-08-13 04:49:12 +10:00
Deserty0
bd80a1eb60 Update PanicBunkerTab.xaml 2025-08-13 04:49:12 +10:00
Deserty0
b0c2b92464 Update DiscordAuthManager.cs 2025-08-13 04:15:38 +10:00
Deserty0
35e5025d3e Update CCvars.CP14DiscordAuth.cs 2025-08-13 04:15:37 +10:00
Deserty0
146967b786 looool 2025-08-13 04:15:37 +10:00
Deserty0
4b68d79729 uaua 2025-08-13 04:15:37 +10:00
Deserty0
8899695ed5 Update DiscordAuthManager.cs 2025-08-13 04:15:37 +10:00
Deserty0
1d410590b6 Update DiscordAuthManager.cs 2025-08-13 04:15:37 +10:00
Deserty0
24eb9d7493 Update DiscordAuthManager.cs 2025-08-13 04:15:37 +10:00
Deserty0
565283ae26 Update DiscordAuthManager.cs 2025-08-13 04:15:36 +10:00
Deserty0
d55800c575 funny 2025-08-13 04:15:36 +10:00
4418 changed files with 275887 additions and 455985 deletions

4
.github/CODEOWNERS vendored
View File

@@ -1,2 +1,6 @@
# TheShuEd
* @TheShuEd
# TornadoTechnology
*.cs @Tornado-Technology
*.xaml @Tornado-Technology

View File

@@ -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 win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release

View File

@@ -41,7 +41,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 win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release

View File

@@ -60,7 +60,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 win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release

View File

@@ -1,19 +1,17 @@
#!/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 os
import shutil
import subprocess
import sys
import os
import shutil
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 = "3"
CURRENT_HOOKS_VERSION = "2"
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
@@ -27,10 +25,12 @@ def run_command(command: List[str], capture: bool = False) -> subprocess.Complet
sys.stdout.flush()
completed = None
if capture:
completed = subprocess.run(command, stdout=subprocess.PIPE, text=True)
completed = subprocess.run(command, cwd="..", stdout=subprocess.PIPE)
else:
completed = subprocess.run(command)
completed = subprocess.run(command, cwd="..")
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,21 +76,22 @@ 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(run_command(["git", "rev-parse", "--git-path", "hooks"], True).stdout.strip())
hooks_target_dir = Path("..")/".git"/"hooks"
hooks_source_dir = Path("hooks")
# Clear entire tree since we need to kill deleted files too.
for filename in os.listdir(hooks_target_dir):
os.remove(hooks_target_dir / filename)
for filename in os.listdir(str(hooks_target_dir)):
os.remove(str(hooks_target_dir/filename))
for filename in os.listdir(hooks_source_dir):
for filename in os.listdir(str(hooks_source_dir)):
print("Copying hook {}".format(filename))
shutil.copy2(hooks_source_dir / filename, hooks_target_dir / filename)
with open("INSTALLED_HOOKS_VERSION", "w") as f:
f.write(CURRENT_HOOKS_VERSION)
shutil.copy2(str(hooks_source_dir/filename),
str(hooks_target_dir/filename))
def reset_solution():
@@ -106,7 +107,8 @@ def reset_solution():
def check_for_zip_download():
# Check if .git exists,
if run_command(["git", "rev-parse"]).returncode != 0:
cur_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if not os.path.isdir(os.path.join(cur_dir, ".git")):
print("It appears that you downloaded this repository directly from GitHub. (Using the .zip download option) \n"
"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"

View File

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

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
// Global usings for Content.Benchmarks
global using Robust.UnitTesting.Pool;

View File

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

View File

@@ -6,6 +6,7 @@ using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Mind;
using Content.Server.Warps;
using Content.Shared.Warps;
using Robust.Shared;
using Robust.Shared.Analyzers;

View File

@@ -25,11 +25,11 @@ namespace Content.Client.Access.UI
public void SetAccessLevels(IPrototypeManager protoManager, List<ProtoId<AccessLevelPrototype>> accessLevels)
{
_accessButtons.Clear();
AccessLevelGrid.RemoveAllChildren();
AccessLevelGrid.DisposeAllChildren();
foreach (var access in accessLevels)
{
if (!protoManager.Resolve(access, out var accessLevel))
if (!protoManager.TryIndex(access, out var accessLevel))
{
continue;
}

View File

@@ -41,7 +41,7 @@ namespace Content.Client.Access.UI
public void SetAllowedIcons(string currentJobIconId)
{
IconGrid.RemoveAllChildren();
IconGrid.DisposeAllChildren();
var jobIconButtonGroup = new ButtonGroup();
var i = 0;

View File

@@ -57,7 +57,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
foreach (var accessGroup in _accessGroups)
{
if (!_protoManager.Resolve(accessGroup, out var accessGroupProto))
if (!_protoManager.TryIndex(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.Resolve(GeneralAccessGroup, out var generalAccessProto))
if (_protoManager.TryIndex(GeneralAccessGroup, out var generalAccessProto))
_groupedAccessLevels.TryAdd(generalAccessProto, new());
// Assign known access levels with their associated groups
foreach (var accessLevel in _accessLevels)
{
if (!_protoManager.Resolve(accessLevel, out var accessLevelProto))
if (!_protoManager.TryIndex(accessLevel, out var accessLevelProto))
continue;
var assigned = false;
@@ -99,8 +99,8 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
private bool TryRebuildAccessGroupControls()
{
AccessGroupList.RemoveAllChildren();
AccessLevelChecklist.RemoveAllChildren();
AccessGroupList.DisposeAllChildren();
AccessLevelChecklist.DisposeAllChildren();
// 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.RemoveAllChildren();
AccessLevelChecklist.DisposeAllChildren();
_accessLevelEntries.Clear();
// No access level prototypes were assigned to any of the access level groups

View File

@@ -4,7 +4,6 @@ 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;
@@ -75,7 +74,7 @@ namespace Content.Client.Access.UI
_window?.UpdateState(castState);
}
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, ProtoId<JobPrototype> newJobPrototype)
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, string newJobPrototype)
{
if (newFullName.Length > _maxNameLength)
newFullName = newFullName[.._maxNameLength];

View File

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

View File

@@ -33,7 +33,6 @@ 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;
@@ -287,27 +286,8 @@ 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)
@@ -329,10 +309,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 is { Valid: true } entityUid &&
ValidateEntityTarget(user, entityUid, (uid, entity)))
args.Input.EntityUid != null &&
ValidateEntityTarget(user, args.Input.EntityUid, (uid, entity)))
{
targetEnt = entityUid;
targetEnt = args.Input.EntityUid;
}
if (action.ClientExclusive)

View File

@@ -0,0 +1,10 @@
using Content.Shared.Administration.Components;
using Robust.Shared.GameStates;
namespace Content.Client.Administration.Components;
[RegisterComponent]
public sealed partial class HeadstandComponent : SharedHeadstandComponent
{
}

View File

@@ -15,7 +15,6 @@ namespace Content.Client.Administration.Managers
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IClientNetManager _netMgr = default!;
[Dependency] private readonly IClientConGroupController _conGroup = default!;
[Dependency] private readonly IClientConsoleHost _host = default!;
[Dependency] private readonly IResourceManager _res = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterface = default!;
@@ -87,12 +86,12 @@ namespace Content.Client.Administration.Managers
private void UpdateMessageRx(MsgUpdateAdminStatus message)
{
_availableCommands.Clear();
var host = IoCManager.Resolve<IClientConsoleHost>();
// Anything marked as Any we'll just add even if the server doesn't know about it.
foreach (var (command, instance) in _host.AvailableCommands)
foreach (var (command, instance) in host.AvailableCommands)
{
if (Attribute.GetCustomAttribute(instance.GetType(), typeof(AnyCommandAttribute)) == null)
continue;
if (Attribute.GetCustomAttribute(instance.GetType(), typeof(AnyCommandAttribute)) == null) continue;
_availableCommands.Add(command);
}

View File

@@ -0,0 +1,7 @@
using Content.Shared.Administration;
namespace Content.Client.Administration.Systems;
public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
{
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.Administration.Components;
using Content.Client.Administration.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Administration.Systems;

View File

@@ -1,5 +1,6 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc admin-camera-window-title-placeholder}"
SetSize="425 550"
MinSize="200 225">
MinSize="200 225"
Name="Window">
</DefaultWindow>

View File

@@ -12,7 +12,7 @@ public sealed partial class AdminMenuWindow : DefaultWindow
public AdminMenuWindow()
{
MinSize = new Vector2(650, 250);
MinSize = new Vector2(650, 280);
Title = Loc.GetString("admin-menu-title");
RobustXamlLoader.Load(this);
MasterTabContainer.SetTabTitle((int) TabIndex.Admin, Loc.GetString("admin-menu-admin-tab"));

View File

@@ -24,7 +24,7 @@ namespace Content.Client.Administration.UI.BanPanel;
[GenerateTypedNameReferences]
public sealed partial class BanPanel : DefaultWindow
{
public event Action<Ban>? BanSubmitted;
public event Action<string?, (IPAddress, int)?, bool, ImmutableTypedHwid?, bool, uint, string, NoteSeverity, string[]?, bool>? 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, IPrototype)>> _roleCheckboxes = new();
private readonly ISawmill _banPanelSawmill;
private readonly Dictionary<string, List<Button>> _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 += _ =>
LastConnCheckbox.OnPressed += args =>
{
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(AntagPrototype.GroupName, AntagPrototype.GroupColor, antagRoles);
CreateRoleGroup("Antagonist", Color.Red, antagRoles);
}
/// <summary>
@@ -236,14 +236,14 @@ public sealed partial class BanPanel : DefaultWindow
{
foreach (var role in _roleCheckboxes[groupName])
{
role.Item1.Pressed = args.Pressed;
role.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.Item1.Pressed)
if (button.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 check button specifically for one "role" in a "group"
/// Adds a checkbutton 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,36 +302,22 @@ public sealed partial class BanPanel : DefaultWindow
var roleCheckboxContainer = new BoxContainer();
var roleCheckButton = new Button
{
Name = role,
Name = $"{role}RoleCheckbox",
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.Item1.Pressed))
if (args.Pressed && _roleCheckboxes[group].All(e => e.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
// 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))
// 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))
{
var jobIconTexture = new TextureRect
{
@@ -348,7 +334,7 @@ public sealed partial class BanPanel : DefaultWindow
roleGroupInnerContainer.AddChild(roleCheckboxContainer);
_roleCheckboxes.TryAdd(group, []);
_roleCheckboxes[group].Add((roleCheckButton, rolePrototype));
_roleCheckboxes[group].Add(roleCheckButton);
}
public void UpdateBanFlag(bool newFlag)
@@ -501,7 +487,7 @@ public sealed partial class BanPanel : DefaultWindow
newSeverity = serverSeverity;
else
{
_banPanelSawmill
_banpanelSawmill
.Warning("Server ban severity could not be parsed from config!");
}
@@ -514,7 +500,7 @@ public sealed partial class BanPanel : DefaultWindow
}
else
{
_banPanelSawmill
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
}
break;
@@ -559,51 +545,34 @@ public sealed partial class BanPanel : DefaultWindow
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
ProtoId<JobPrototype>[]? jobs = null;
ProtoId<AntagPrototype>[]? antags = null;
string[]? roles = null;
if (TypeOption.SelectedId == (int) Types.Role)
{
var jobList = new List<ProtoId<JobPrototype>>();
var antagList = new List<ProtoId<AntagPrototype>>();
var rolesList = new List<string>();
if (_roleCheckboxes.Count == 0)
throw new DebugAssertException("RoleCheckboxes was empty");
foreach (var button in _roleCheckboxes.Values.SelectMany(departmentButtons => departmentButtons))
{
if (button.Item1 is { Pressed: true, Name: not null })
if (button is { Pressed: true, Text: not null })
{
switch (button.Item2)
{
case JobPrototype:
jobList.Add(button.Item2.ID);
break;
case AntagPrototype:
antagList.Add(button.Item2.ID);
break;
}
rolesList.Add(button.Text);
}
}
if (jobList.Count + antagList.Count == 0)
if (rolesList.Count == 0)
{
Tabs.CurrentTab = (int) TabNumbers.Roles;
return;
}
jobs = jobList.ToArray();
antags = antagList.ToArray();
roles = rolesList.ToArray();
}
if (TypeOption.SelectedId == (int) Types.None)
{
TypeOption.ModulateSelfOverride = Color.Red;
Tabs.CurrentTab = (int) TabNumbers.BasicInfo;
return;
}
@@ -615,7 +584,6 @@ public sealed partial class BanPanel : DefaultWindow
ReasonTextEdit.GrabKeyboardFocus();
ReasonTextEdit.ModulateSelfOverride = Color.Red;
ReasonTextEdit.OnKeyBindDown += ResetTextEditor;
return;
}
@@ -624,7 +592,6 @@ 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;
}
@@ -633,22 +600,7 @@ public sealed partial class BanPanel : DefaultWindow
var useLastHwid = HwidCheckbox.Pressed && LastConnCheckbox.Pressed && Hwid is null;
var severity = (NoteSeverity) SeverityOption.SelectedId;
var erase = EraseCheckbox.Pressed;
var ban = new Ban(
player,
IpAddress,
useLastIp,
Hwid,
useLastHwid,
(uint)(TimeEntered * Multiplier),
reason,
severity,
jobs,
antags,
erase
);
BanSubmitted?.Invoke(ban);
BanSubmitted?.Invoke(player, IpAddress, useLastIp, Hwid, useLastHwid, (uint) (TimeEntered * Multiplier), reason, severity, roles, erase);
}
protected override void FrameUpdate(FrameEventArgs args)

View File

@@ -14,7 +14,8 @@ public sealed class BanPanelEui : BaseEui
{
BanPanel = new BanPanel();
BanPanel.OnClose += () => SendMessage(new CloseEuiMessage());
BanPanel.BanSubmitted += ban => SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(ban));
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.PlayerChanged += player => SendMessage(new BanPanelEuiStateMsg.GetPlayerInfoRequest(player));
}

View File

@@ -67,7 +67,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
/// </summary>
public void UpdateReagents()
{
ReagentList.RemoveAllChildren();
ReagentList.DisposeAllChildren();
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.RemoveAllChildren();
VolumeBox.DisposeAllChildren();
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.RemoveAllChildren();
ThermalBox.DisposeAllChildren();
var heatCap = solution.GetHeatCapacity(null);
var specificHeatLabel = new Label();
specificHeatLabel.HorizontalExpand = true;

View File

@@ -82,11 +82,7 @@ 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.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"))
);
ExtraLabel.Text = Loc.GetString("admin-notes-unbanned", ("admin", Note.UnbannedByName ?? "[error]"), ("date", Note.UnbannedTime));
ExtraLabel.Visible = true;
}
else if (Note.ExpiryTime is not null)
@@ -143,7 +139,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);
}

View File

@@ -57,43 +57,12 @@ public sealed partial class ObjectsTab : Control
private void TeleportTo(NetEntity nent)
{
var selection = _selections[ObjectTypeOptions.SelectedId];
switch (selection)
{
case ObjectsTabSelection.Grids:
{
// directly teleport to the entity
_console.ExecuteCommand($"tpto {nent}");
}
break;
case ObjectsTabSelection.Maps:
{
// teleport to the map, not to the map entity (which is in nullspace)
if (!_entityManager.TryGetEntity(nent, out var map) || !_entityManager.TryGetComponent<MapComponent>(map, out var mapComp))
break;
_console.ExecuteCommand($"tp 0 0 {mapComp.MapId}");
break;
}
case ObjectsTabSelection.Stations:
{
// teleport to the station's largest grid, not to the station entity (which is in nullspace)
if (!_entityManager.TryGetEntity(nent, out var station))
break;
var largestGrid = _entityManager.EntitySysManager.GetEntitySystem<StationSystem>().GetLargestGrid(station.Value);
if (largestGrid == null)
break;
_console.ExecuteCommand($"tpto {largestGrid.Value}");
break;
}
default:
throw new NotImplementedException();
}
_console.ExecuteCommand($"tpto {nent}");
}
private void Delete(NetEntity nent)
{
_console.ExecuteCommand($"delete {nent}");
RefreshObjectList();
}
public void RefreshObjectList()
@@ -107,24 +76,28 @@ public sealed partial class ObjectsTab : Control
switch (selection)
{
case ObjectsTabSelection.Stations:
entities.AddRange(_entityManager.EntitySysManager.GetEntitySystem<StationSystem>().GetStationNames());
entities.AddRange(_entityManager.EntitySysManager.GetEntitySystem<StationSystem>().Stations);
break;
case ObjectsTabSelection.Grids:
{
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
{
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
break;
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
break;
}
case ObjectsTabSelection.Maps:
{
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
{
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
break;
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
}

View File

@@ -1,6 +1,5 @@
<PanelContainer xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Name="BackgroundColorPanel">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
@@ -21,7 +20,7 @@
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<controls:ConfirmButton Name="DeleteButton"
<Button Name="DeleteButton"
Text="{Loc object-tab-entity-delete}"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"

View File

@@ -27,7 +27,12 @@
<cc:CommandButton Name="ShowReasonButton" Command="panicbunker_show_reason"
ToggleMode="True" Text="{Loc admin-ui-panic-bunker-show-reason}"
ToolTip="{Loc admin-ui-panic-bunker-show-reason-tooltip}" />
<BoxContainer Orientation="Vertical" Margin="0 10 0 0">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc cp14-admin-ui-suspicious-warning-level-setting}" MinWidth="175" />
<OptionButton Name="SuspiciousWarningLevel" HorizontalAlignment="Left" Margin="4 0"
ToolTip="{Loc cp14-admin-ui-suspicious-warning-level-setting-desc}" />
</BoxContainer>
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-panic-bunker-min-account-age}" MinWidth="175" />
<LineEdit Name="MinAccountAge" MinWidth="50" Margin="0 0 5 0" />

View File

@@ -1,7 +1,9 @@
using Content.Shared.Administration.Events;
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
namespace Content.Client.Administration.UI.Tabs.PanicBunkerTab;
@@ -10,9 +12,13 @@ namespace Content.Client.Administration.UI.Tabs.PanicBunkerTab;
public sealed partial class PanicBunkerTab : Control
{
[Dependency] private readonly IConsoleHost _console = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private string _minAccountAge;
private string _minOverallMinutes;
// CrystallEdge suspicious activity warning system
private List<SuspiciousWarningLevelSelection> _suspiciousWarningLevelSelections = [];
private string _currentSuspiciousWarningLevel = string.Empty;
public PanicBunkerTab()
{
@@ -28,6 +34,21 @@ public sealed partial class PanicBunkerTab : Control
MinOverallMinutes.OnTextEntered += args => SendMinOverallMinutes(args.Text);
MinOverallMinutes.OnFocusExit += args => SendMinOverallMinutes(args.Text);
_minOverallMinutes = MinOverallMinutes.Text;
// CrystallEdge suspicious activity warning system
foreach (var type in Enum.GetValues<SuspiciousWarningLevelSelection>())
{
_suspiciousWarningLevelSelections.Add(type);
SuspiciousWarningLevel.AddItem(GetLocalizedEnumValue(type));
}
UpdateSuspiciousWarningLevel();
SuspiciousWarningLevel.OnItemSelected += ev =>
{
SuspiciousWarningLevel.SelectId(ev.Id);
SendSuspiciousWarningLevel(_suspiciousWarningLevelSelections[ev.Id]);
};
}
private void SendMinAccountAge(string text)
@@ -74,4 +95,43 @@ public sealed partial class PanicBunkerTab : Control
MinOverallMinutes.Text = status.MinOverallMinutes.ToString();
_minOverallMinutes = MinOverallMinutes.Text;
}
// CrystallEdge suspicious activity warning system
private enum SuspiciousWarningLevelSelection
{
Disabled,
Low,
Medium,
High,
}
private string GetLocalizedEnumValue(SuspiciousWarningLevelSelection selection)
{
return selection switch
{
SuspiciousWarningLevelSelection.Disabled => Loc.GetString("cp14-admin-ui-suspicious-warning-level-disabled"),
SuspiciousWarningLevelSelection.Low => Loc.GetString("cp14-admin-ui-suspicious-warning-level-low"),
SuspiciousWarningLevelSelection.Medium => Loc.GetString("cp14-admin-ui-suspicious-warning-level-medium"),
SuspiciousWarningLevelSelection.High => Loc.GetString("cp14-admin-ui-suspicious-warning-level-high"),
_ => throw new ArgumentOutOfRangeException(nameof(selection), selection, null),
};
}
private void UpdateSuspiciousWarningLevel()
{
_currentSuspiciousWarningLevel = _cfg.GetCVar(CCVars.SuspiciousAccountsWarningLevel);
if (!Enum.TryParse(_currentSuspiciousWarningLevel, true, out SuspiciousWarningLevelSelection currentSuspiciousWarningSelection))
{
throw new ArgumentOutOfRangeException(nameof(_currentSuspiciousWarningLevel),
_currentSuspiciousWarningLevel,
null);
}
SuspiciousWarningLevel.SelectId((int)currentSuspiciousWarningSelection);
}
private void SendSuspiciousWarningLevel(SuspiciousWarningLevelSelection selection)
{
_console.ExecuteCommand($"cp14.suspicious-warning-level {Enum.GetName(selection)}");
}
}

View File

@@ -1,5 +0,0 @@
using Content.Shared.Animals.Systems;
namespace Content.Client.Animals.Systems;
public sealed class ParrotMemorySystem : SharedParrotMemorySystem;

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ using Robust.Shared.Timing;
namespace Content.Client.Anomaly;
public sealed partial class AnomalySystem : SharedAnomalySystem
public sealed class AnomalySystem : SharedAnomalySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly FloatingVisualizerSystem _floating = default!;
@@ -24,7 +24,6 @@ public sealed partial 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);

View File

@@ -1,6 +1,6 @@
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects;
using Content.Shared.Humanoid;
using Content.Shared.Body.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Anomaly.Effects;
@@ -25,8 +25,9 @@ public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
var index = _sprite.LayerMapReserve((ent.Owner, sprite), ent.Comp.LayerMap);
if (TryComp<HumanoidAppearanceComponent>(ent, out var humanoidAppearance) &&
ent.Comp.SpeciesSprites.TryGetValue(humanoidAppearance.Species, out var speciesSprite))
if (TryComp<BodyComponent>(ent, out var body) &&
body.Prototype is not null &&
ent.Comp.SpeciesSprites.TryGetValue(body.Prototype.Value, out var speciesSprite))
{
_sprite.LayerSetSprite((ent.Owner, sprite), index, speciesSprite);
}

View File

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

View File

@@ -1,11 +1,46 @@
using Content.Client.Atmos.Components;
using Robust.Client.GameObjects;
using Content.Client.UserInterface.Systems.Storage.Controls;
using Content.Shared.Atmos.Piping;
using Content.Shared.Hands;
using Content.Shared.Atmos.Components;
using Content.Shared.Item;
namespace Content.Client.Atmos.EntitySystems;
public sealed class PipeColorVisualizerSystem : VisualizerSystem<PipeColorVisualsComponent>
{
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PipeColorVisualsComponent, GetInhandVisualsEvent>(OnGetVisuals);
SubscribeLocalEvent<PipeColorVisualsComponent, BeforeRenderInGridEvent>(OnDrawInGrid);
}
/// <summary>
/// This method is used to display the color changes of the pipe on the screen..
/// </summary>
private void OnGetVisuals(Entity<PipeColorVisualsComponent> item, ref GetInhandVisualsEvent args)
{
foreach (var (_, layerData) in args.Layers)
{
if (TryComp(item.Owner, out AtmosPipeColorComponent? pipeColor))
layerData.Color = pipeColor.Color;
}
}
/// <summary>
/// This method is used to change the pipe's color in a container grid.
/// </summary>
private void OnDrawInGrid(Entity<PipeColorVisualsComponent> item, ref BeforeRenderInGridEvent args)
{
if (TryComp(item.Owner, out AtmosPipeColorComponent? pipeColor))
args.Color = pipeColor.Color;
}
protected override void OnAppearanceChange(EntityUid uid, PipeColorVisualsComponent component, ref AppearanceChangeEvent args)
{
if (TryComp<SpriteComponent>(uid, out var sprite)
@@ -15,6 +50,8 @@ public sealed class PipeColorVisualizerSystem : VisualizerSystem<PipeColorVisual
var layer = sprite[PipeVisualLayers.Pipe];
layer.Color = color.WithAlpha(layer.Color.A);
}
_itemSystem.VisualsChanged(uid);
}
}

View File

@@ -59,7 +59,7 @@ public sealed partial class PumpControl : BoxContainer
foreach (var value in Enum.GetValues<VentPumpDirection>())
{
_pumpDirection.AddItem(Loc.GetString($"air-alarm-ui-pump-direction-{value.ToString().ToLower()}"), (int) value);
_pumpDirection.AddItem(Loc.GetString($"{value}"), (int) value);
}
_pumpDirection.SelectId((int) _data.PumpDirection);
@@ -72,7 +72,7 @@ public sealed partial class PumpControl : BoxContainer
foreach (var value in Enum.GetValues<VentPressureBound>())
{
_pressureCheck.AddItem(Loc.GetString($"air-alarm-ui-pressure-bound-{value.ToString().ToLower()}"), (int) value);
_pressureCheck.AddItem(Loc.GetString($"{value}"), (int) value);
}
_pressureCheck.SelectId((int) _data.PressureChecks);

View File

@@ -27,15 +27,9 @@
</BoxContainer>
<!-- Lower row: every single gas -->
<Collapsible Margin="2 2 2 2">
<CollapsibleHeading Title="{Loc 'air-alarm-ui-widget-gas-filters'}" />
<CollapsibleHeading Title="Gas filters" />
<CollapsibleBody Margin="20 0 0 0">
<BoxContainer Orientation="Vertical">
<BoxContainer Margin="2">
<Button Name="CSelectAll" Text="{Loc 'air-alarm-ui-scrubber-select-all-gases-label'}" />
<Button Name="CDeselectAll" Text="{Loc 'air-alarm-ui-scrubber-deselect-all-gases-label'}" />
</BoxContainer>
<GridContainer Name="CGasContainer" Columns="3" />
</BoxContainer>
<GridContainer HorizontalExpand="True" Name="CGasContainer" Columns="3" />
</CollapsibleBody>
</Collapsible>
</BoxContainer>

View File

@@ -1,21 +1,15 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Atmos.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Atmos.Monitor.UI.Widgets;
[GenerateTypedNameReferences]
public sealed partial class ScrubberControl : BoxContainer
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
private GasVentScrubberData _data;
private string _address;
@@ -28,18 +22,12 @@ public sealed partial class ScrubberControl : BoxContainer
private FloatSpinBox _volumeRate => CVolumeRate;
private CheckBox _wideNet => CWideNet;
private Button _copySettings => CCopySettings;
private Button _selectAll => CSelectAll;
private Button _deselectAll => CDeselectAll;
private GridContainer _gases => CGasContainer;
private Dictionary<Gas, Button> _gasControls = new();
public ScrubberControl(GasVentScrubberData data, string address)
{
IoCManager.InjectDependencies(this);
var atmosphereSystem = _entMan.System<SharedAtmosphereSystem>();
RobustXamlLoader.Load(this);
Name = address;
@@ -73,7 +61,7 @@ public sealed partial class ScrubberControl : BoxContainer
foreach (var value in Enum.GetValues<ScrubberPumpDirection>())
{
_pumpDirection.AddItem(Loc.GetString($"air-alarm-ui-pump-direction-{value.ToString().ToLower()}"), (int) value);
_pumpDirection.AddItem(Loc.GetString($"{value}"), (int) value);
}
_pumpDirection.SelectId((int) _data.PumpDirection);
@@ -90,28 +78,12 @@ public sealed partial class ScrubberControl : BoxContainer
ScrubberDataCopied?.Invoke(_data);
};
var allGases = Enum.GetValues<Gas>();
_selectAll.OnPressed += _ =>
foreach (var value in Enum.GetValues<Gas>())
{
_data.FilterGases = new HashSet<Gas>(allGases);
ScrubberDataChanged?.Invoke(_address, _data);
};
_deselectAll.OnPressed += _ =>
{
_data.FilterGases = [];
ScrubberDataChanged?.Invoke(_address, _data);
};
foreach (var value in allGases)
{
ProtoId<GasPrototype> gasProtoId = atmosphereSystem.GetGas(value);
var gasName = _prototypeManager.Index(gasProtoId).Name;
var gasButton = new Button
{
Name = value.ToString(),
Text = Loc.GetString(gasName),
Text = Loc.GetString($"{value}"),
ToggleMode = true,
HorizontalExpand = true,
Pressed = _data.FilterGases.Contains(value)

View File

@@ -1,22 +1,16 @@
using Content.Client.Message;
using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Prototypes;
using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Atmos.Monitor.UI.Widgets;
[GenerateTypedNameReferences]
public sealed partial class SensorInfo : BoxContainer
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
public Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? OnThresholdUpdate;
public event Action<AtmosSensorData>? SensorDataCopied;
private string _address;
@@ -29,9 +23,6 @@ public sealed partial class SensorInfo : BoxContainer
public SensorInfo(AtmosSensorData data, string address)
{
IoCManager.InjectDependencies(this);
var atmosphereSystem = _entMan.System<SharedAtmosphereSystem>();
RobustXamlLoader.Load(this);
_address = address;
@@ -54,12 +45,8 @@ public sealed partial class SensorInfo : BoxContainer
var label = new RichTextLabel();
var fractionGas = amount / data.TotalMoles;
ProtoId<GasPrototype> gasProtoId = atmosphereSystem.GetGas(gas);
var gasName = _prototypeManager.Index(gasProtoId).Name;
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
("gas", Loc.GetString(gasName)),
("gas", $"{gas}"),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * fractionGas):0.##}")));
@@ -67,7 +54,7 @@ public sealed partial class SensorInfo : BoxContainer
_gasLabels.Add(gas, label);
var threshold = data.GasThresholds[gas];
var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title"), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title", ("gas", $"{gas}")), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
gasThresholdControl.ThresholdDataChanged += (type, alarmThreshold, arg3) =>
{
@@ -103,9 +90,6 @@ public sealed partial class SensorInfo : BoxContainer
public void ChangeData(AtmosSensorData data)
{
IoCManager.InjectDependencies(this);
var atmosphereSystem = _entMan.System<SharedAtmosphereSystem>();
SensorAddress.Title = Loc.GetString("air-alarm-ui-window-listing-title", ("address", _address), ("state", data.AlarmState));
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state-indicator",
@@ -128,12 +112,8 @@ public sealed partial class SensorInfo : BoxContainer
}
var fractionGas = amount / data.TotalMoles;
ProtoId<GasPrototype> gasProtoId = atmosphereSystem.GetGas(gas);
var gasName = _prototypeManager.Index(gasProtoId).Name;
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
("gas", Loc.GetString(gasName)),
("gas", $"{gas}"),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * fractionGas):0.##}")));

View File

@@ -2,6 +2,6 @@
HorizontalExpand="True" Orientation="Vertical"
Margin = "20 0 0 0" MinSize="160 0" >
<Label Name="CBoundLabel" HorizontalAlignment="Center" />
<CheckBox Name="CBoundEnabled" HorizontalAlignment="Center" Text="{Loc 'air-alarm-ui-widget-enable'}" Pressed="True" />
<CheckBox Name="CBoundEnabled" HorizontalAlignment="Center" Text="{Loc 'Enable'}" Pressed="True" />
<FloatSpinBox Name="CSpinner" />
</BoxContainer>

View File

@@ -30,10 +30,7 @@ public sealed partial class ThresholdBoundControl : BoxContainer
public void SetValue(float value)
{
_value = value;
if (!CSpinner.HasKeyboardFocus())
{
CSpinner.Value = ScaledValue;
}
CSpinner.Value = ScaledValue;
}
public void SetEnabled(bool enabled)

View File

@@ -6,7 +6,7 @@
<CollapsibleBody Margin="20 0 0 0">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<CheckBox Name="CEnabled" Text="{Loc 'air-alarm-ui-widget-enable'}" />
<CheckBox Name="CEnabled" Text="{Loc 'Enabled'}" />
</BoxContainer>
<!-- Upper row: Danger bounds -->
<BoxContainer Name="CDangerBounds" Orientation="Horizontal" Margin="0 0 0 2"/>

View File

@@ -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.00}")),
Text = Loc.GetString("gas-analyzer-window-pressure-val-text", ("pressure", $"{gasMix.Pressure:0.##}")),
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.0}"),
("tempC", $"{TemperatureHelpers.KelvinToCelsius(gasMix.Temperature):0.0}")),
("tempK", $"{gasMix.Temperature:0.#}"),
("tempC", $"{TemperatureHelpers.KelvinToCelsius(gasMix.Temperature):0.#}")),
Align = Label.AlignMode.Right,
HorizontalExpand = true
});

View File

@@ -3,13 +3,17 @@ using Content.Shared.CCVar;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Audio;
using Robust.Shared.Log;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Linq;
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Shared.Audio.Effects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
@@ -27,7 +31,6 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
@@ -62,19 +65,18 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
get => _overlayEnabled;
set
{
if (_overlayEnabled == value)
return;
if (_overlayEnabled == value) return;
_overlayEnabled = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_overlayEnabled)
{
_overlay = new AmbientSoundOverlay(EntityManager, this, EntityManager.System<EntityLookupSystem>());
_overlayManager.AddOverlay(_overlay);
overlayManager.AddOverlay(_overlay);
}
else
{
_overlayManager.RemoveOverlay(_overlay!);
overlayManager.RemoveOverlay(_overlay!);
_overlay = null;
}
}

View File

@@ -3,8 +3,11 @@ using Content.Client.Gameplay;
using Content.Shared.Audio;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Random;
using Content.Shared.Random.Rules;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Components;
@@ -22,7 +25,6 @@ public sealed partial class ContentAudioSystem
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
@@ -59,7 +61,7 @@ public sealed partial class ContentAudioSystem
private void InitializeAmbientMusic()
{
Subs.CVar(_configManager, CCVars.AmbientMusicVolume, AmbienceCVarChanged, true);
_sawmill = _logManager.GetSawmill("audio.ambience");
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("audio.ambience");
// Reset audio
_nextAudio = TimeSpan.MaxValue;

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
using System.Numerics;
using Content.Shared.Body.Components;
using Content.Shared.CardboardBox;
using Content.Shared.CardboardBox.Components;
using Content.Shared.Examine;
using Content.Shared.Mobs.Components;
using Content.Shared.Movement.Components;
using Robust.Client.GameObjects;
@@ -15,13 +15,13 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
private EntityQuery<MobStateComponent> _mobStateQuery;
private EntityQuery<BodyComponent> _bodyQuery;
public override void Initialize()
{
base.Initialize();
_mobStateQuery = GetEntityQuery<MobStateComponent>();
_bodyQuery = GetEntityQuery<BodyComponent>();
SubscribeNetworkEvent<PlayBoxEffectMessage>(OnBoxEffect);
}
@@ -66,8 +66,8 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
if (!_examine.InRangeUnOccluded(sourcePos, mapPos, box.Distance, null))
continue;
// no effect for non-mobs that have MobMover, such as mechs and vehicles.
if (!_mobStateQuery.HasComp(mob))
// no effect for anything too exotic
if (!_bodyQuery.HasComp(mob))
continue;
var ent = Spawn(box.Effect, mapPos);

View File

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

View File

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

View File

@@ -206,7 +206,7 @@ namespace Content.Client.Cargo.UI
if (!_orderConsoleQuery.TryComp(_owner, out var orderConsole))
return;
Requests.RemoveAllChildren();
Requests.DisposeAllChildren();
foreach (var order in orders)
{

View File

@@ -30,7 +30,7 @@ namespace Content.Client.Cargo.UI
public void SetOrders(SpriteSystem sprites, IPrototypeManager protoManager, List<CargoOrderData> orders)
{
Orders.RemoveAllChildren();
Orders.DisposeAllChildren();
foreach (var order in orders)
{

View File

@@ -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,6 +21,7 @@ public sealed partial class CrewManifestUiFragment : BoxContainer
public void UpdateState(string stationName, CrewManifestEntries? entries)
{
CrewManifestListing.DisposeAllChildren();
CrewManifestListing.RemoveAllChildren();
StationNameContainer.Visible = entries != null;

View File

@@ -1,30 +0,0 @@
using Content.Shared.Changeling.Components;
using Content.Shared.Changeling.Systems;
using Robust.Client.GameObjects;
namespace Content.Client.Changeling.Systems;
public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem
{
[Dependency] private readonly UserInterfaceSystem _ui = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChangelingIdentityComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAfterAutoHandleState(Entity<ChangelingIdentityComponent> ent, ref AfterAutoHandleStateEvent args)
{
UpdateUi(ent);
}
public void UpdateUi(EntityUid uid)
{
if (_ui.TryGetOpenUi(uid, ChangelingTransformUiKey.Key, out var bui))
{
bui.Update();
}
}
}

View File

@@ -1,68 +0,0 @@
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;
namespace Content.Client.Changeling.UI;
[UsedImplicitly]
public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
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();
_menu = this.CreateWindow<SimpleRadialMenu>();
Update();
_menu.OpenOverMouseScreenPosition();
}
public override void Update()
{
if (_menu == null)
return;
if (!EntMan.TryGetComponent<ChangelingIdentityComponent>(Owner, out var lingIdentity))
return;
var models = ConvertToButtons(lingIdentity.ConsumedIdentities, lingIdentity?.CurrentIdentity);
_menu.SetButtons(models);
}
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));
}
}

View File

@@ -55,7 +55,7 @@ namespace Content.Client.Changelog
// Changelog is not kept in memory so load it again.
var changelogs = await _changelog.LoadChangelog();
Tabs.RemoveAllChildren();
Tabs.DisposeAllChildren();
var i = 0;
foreach (var changelog in changelogs)

View File

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

View File

@@ -2,6 +2,7 @@ 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;
@@ -10,7 +11,6 @@ public sealed class InjectorSystem : SharedInjectorSystem
public override void Initialize()
{
base.Initialize();
Subs.ItemStatus<InjectorComponent>(ent => new InjectorStatusControl(ent, SolutionContainer));
Subs.ItemStatus<InjectorComponent>(ent => new InjectorStatusControl(ent, SolutionContainers));
}
}

View File

@@ -38,13 +38,13 @@ public sealed class InjectorStatusControl : Control
// only updates the UI if any of the details are different than they previously were
if (PrevVolume == solution.Volume
&& PrevMaxVolume == solution.MaxVolume
&& PrevTransferAmount == _parent.Comp.CurrentTransferAmount
&& PrevTransferAmount == _parent.Comp.TransferAmount
&& PrevToggleState == _parent.Comp.ToggleState)
return;
PrevVolume = solution.Volume;
PrevMaxVolume = solution.MaxVolume;
PrevTransferAmount = _parent.Comp.CurrentTransferAmount;
PrevTransferAmount = _parent.Comp.TransferAmount;
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.CurrentTransferAmount)));
("transferVolume", _parent.Comp.TransferAmount)));
}
}

View File

@@ -1,5 +0,0 @@
using Content.Shared.Cloning;
namespace Content.Client.Cloning;
public sealed partial class CloningSystem : SharedCloningSystem;

View File

@@ -1,10 +1,12 @@
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;
@@ -12,6 +14,7 @@ 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;
@@ -179,7 +182,6 @@ 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;

View File

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

View File

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

View File

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

View File

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

View File

@@ -95,7 +95,7 @@ namespace Content.Client.ContextMenu.UI
/// </summary>
public void Close()
{
RootMenu.MenuBody.RemoveAllChildren();
RootMenu.MenuBody.DisposeAllChildren();
CancelOpen?.Cancel();
CancelClose?.Cancel();
OnContextClosed?.Invoke();

View File

@@ -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.RemoveAllChildren;
element.SubMenu.OnPopupHide += element.SubMenu.MenuBody.DisposeAllChildren;
_context.AddElement(menu, element);
Elements.TryAdd(entity, element);
}

View File

@@ -53,7 +53,7 @@ namespace Content.Client.Crayon.UI
private void RefreshList()
{
// Clear
Grids.RemoveAllChildren();
Grids.DisposeAllChildren();
if (_decals == null || _allDecals == null)
return;

View File

@@ -80,7 +80,7 @@ public sealed partial class CreditsWindow : DefaultWindow
private async void PopulateAttributions(BoxContainer attributionsContainer, int count)
{
attributionsContainer.RemoveAllChildren();
attributionsContainer.DisposeAllChildren();
if (_attributions.Count == 0)
{
@@ -253,8 +253,6 @@ public sealed partial class CreditsWindow : DefaultWindow
private void PopulateLicenses(BoxContainer licensesContainer)
{
licensesContainer.RemoveAllChildren();
foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
{
licensesContainer.AddChild(new Label
@@ -271,8 +269,6 @@ public sealed partial class CreditsWindow : DefaultWindow
private void PopulatePatrons(BoxContainer patronsContainer)
{
patronsContainer.RemoveAllChildren();
var patrons = LoadPatrons();
// Do not show "become a patron" button on Steam builds
@@ -322,8 +318,6 @@ public sealed partial class CreditsWindow : DefaultWindow
private void PopulateContributors(BoxContainer ss14ContributorsContainer)
{
ss14ContributorsContainer.RemoveAllChildren();
Button contributeButton;
ss14ContributorsContainer.AddChild(new BoxContainer
@@ -362,7 +356,6 @@ public sealed partial class CreditsWindow : DefaultWindow
AddSection(Loc.GetString("credits-window-contributors-section-title"), "GitHub.txt");
AddSection(Loc.GetString("credits-window-codebases-section-title"), "SpaceStation13.txt");
AddSection(Loc.GetString("credits-window-original-remake-team-section-title"), "OriginalRemake.txt");
AddSection(Loc.GetString("credits-window-immortals-title"), "Immortals.txt", true);
AddSection(Loc.GetString("credits-window-special-thanks-section-title"), "SpecialThanks.txt", true);
var linkGithub = _cfg.GetCVar(CCVars.InfoLinksGithub);

View File

@@ -18,6 +18,7 @@ public sealed partial class CrewManifestUi : DefaultWindow
public void Populate(string name, CrewManifestEntries? entries)
{
CrewManifestListing.DisposeAllChildren();
CrewManifestListing.RemoveAllChildren();
StationNameContainer.Visible = entries != null;

View File

@@ -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.Resolve<DamageContainerPrototype>(damageComponent.DamageContainerID, out var damageContainer))
&& _prototypeManager.TryIndex<DamageContainerPrototype>(damageComponent.DamageContainerID, out var damageContainer))
{
// Are we using damage overlay sprites by group?
// Check if the container matches the supported groups,

View File

@@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.DisplacementMap;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -11,11 +10,6 @@ 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>
@@ -25,23 +19,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,
[NotNullWhen(true)] out string? displacementKey
)
out string displacementKey)
{
displacementKey = BuildDisplacementLayerKey(key);
if (displacementKey is null)
displacementKey = $"{key}-displacement";
if (key.ToString() is null)
return false;
EnsureDisplacementIsNotOnSprite(sprite, key);
if (data.ShaderOverride is not null)
if (data.ShaderOverride != null)
sprite.Comp.LayerSetShader(index, data.ShaderOverride);
_sprite.RemoveLayer(sprite.AsNullable(), displacementKey, false);
//allows you not to write it every time in the YML
foreach (var pair in data.SizeMaps)
{
@@ -77,11 +70,7 @@ public sealed class DisplacementMapSystem : EntitySystem
}
var displacementLayer = _serialization.CreateCopy(displacementDataLayer, notNullableOverride: true);
// 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()!;
displacementLayer.CopyToShaderParameters!.LayerKey = key.ToString() ?? "this is impossible";
_sprite.AddLayer(sprite.AsNullable(), displacementLayer, index);
_sprite.LayerMapSet(sprite.AsNullable(), displacementKey, index);
@@ -89,18 +78,14 @@ public sealed class DisplacementMapSystem : EntitySystem
return true;
}
/// <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)
/// <inheritdoc cref="TryAddDisplacement"/>
[Obsolete("Use the Entity<SpriteComponent> overload")]
public bool TryAddDisplacement(DisplacementData data,
SpriteComponent sprite,
int index,
object key,
out string displacementKey)
{
var displacementLayerKey = BuildDisplacementLayerKey(key);
if (displacementLayerKey is null)
return;
_sprite.RemoveLayer(sprite.AsNullable(), displacementLayerKey, false);
return TryAddDisplacement(data, (sprite.Owner, sprite), index, key, out displacementKey);
}
}

View File

@@ -31,7 +31,7 @@ public sealed class DoorSystem : SharedDoorSystem
comp.OpeningAnimation = new Animation
{
Length = comp.OpeningAnimationTime,
Length = TimeSpan.FromSeconds(comp.OpeningAnimationTime),
AnimationTracks =
{
new AnimationTrackSpriteFlick
@@ -47,7 +47,7 @@ public sealed class DoorSystem : SharedDoorSystem
comp.ClosingAnimation = new Animation
{
Length = comp.ClosingAnimationTime,
Length = TimeSpan.FromSeconds(comp.ClosingAnimationTime),
AnimationTracks =
{
new AnimationTrackSpriteFlick
@@ -63,7 +63,7 @@ public sealed class DoorSystem : SharedDoorSystem
comp.EmaggingAnimation = new Animation
{
Length = comp.EmaggingAnimationTime,
Length = TimeSpan.FromSeconds(comp.EmaggingAnimationTime),
AnimationTracks =
{
new AnimationTrackSpriteFlick
@@ -116,14 +116,14 @@ public sealed class DoorSystem : SharedDoorSystem
return;
case DoorState.Opening:
if (entity.Comp.OpeningAnimationTime == TimeSpan.Zero)
if (entity.Comp.OpeningAnimationTime == 0.0)
return;
_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.AnimationKey);
return;
case DoorState.Closing:
if (entity.Comp.ClosingAnimationTime == TimeSpan.Zero || entity.Comp.CurrentlyCrushing.Count != 0)
if (entity.Comp.ClosingAnimationTime == 0.0 || 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.Resolve(targetProto, out var target))
if (!_prototypeManager.TryIndex(targetProto, out var target))
return;
if (!target.TryGetComponent(out SpriteComponent? targetSprite, _componentFactory))

View File

@@ -1,6 +1,5 @@
using Content.Shared.Drunk;
using Content.Shared.StatusEffect;
using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
@@ -44,21 +43,19 @@ public sealed class DrunkOverlay : Overlay
if (playerEntity == null)
return;
var statusSys = _sysMan.GetEntitySystem<Shared.StatusEffectNew.StatusEffectsSystem>();
if (!statusSys.TryGetMaxTime<DrunkStatusEffectComponent>(playerEntity.Value, out var status))
if (!_entityManager.HasComponent<DrunkComponent>(playerEntity)
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
return;
var time = status.Item2;
var statusSys = _sysMan.GetEntitySystem<StatusEffectsSystem>();
if (!statusSys.TryGetTime(playerEntity.Value, SharedDrunkSystem.DrunkKey, out var time, status))
return;
var power = SharedDrunkSystem.MagicNumber;
var curTime = _timing.CurTime;
var timeLeft = (float) (time.Value.Item2 - curTime).TotalSeconds;
if (time != null)
{
var curTime = _timing.CurTime;
power = (float) (time - curTime).Value.TotalSeconds;
}
CurrentBoozePower += 8f * (power * 0.5f - CurrentBoozePower) * args.DeltaSeconds / (power+1);
CurrentBoozePower += 8f * (0.5f*timeLeft - CurrentBoozePower) * args.DeltaSeconds / (timeLeft+1);
}
protected override bool BeforeDraw(in OverlayDrawArgs args)

View File

@@ -1,5 +1,4 @@
using Content.Shared.Drunk;
using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
@@ -17,41 +16,38 @@ public sealed class DrunkSystem : SharedDrunkSystem
{
base.Initialize();
SubscribeLocalEvent<DrunkStatusEffectComponent, StatusEffectAppliedEvent>(OnStatusApplied);
SubscribeLocalEvent<DrunkStatusEffectComponent, StatusEffectRemovedEvent>(OnStatusRemoved);
SubscribeLocalEvent<DrunkComponent, ComponentInit>(OnDrunkInit);
SubscribeLocalEvent<DrunkComponent, ComponentShutdown>(OnDrunkShutdown);
SubscribeLocalEvent<DrunkStatusEffectComponent, StatusEffectRelayedEvent<LocalPlayerAttachedEvent>>(OnPlayerAttached);
SubscribeLocalEvent<DrunkStatusEffectComponent, StatusEffectRelayedEvent<LocalPlayerDetachedEvent>>(OnPlayerDetached);
SubscribeLocalEvent<DrunkComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<DrunkComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
_overlay = new();
}
private void OnStatusApplied(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectAppliedEvent args)
{
if (!_overlayMan.HasOverlay<DrunkOverlay>())
_overlayMan.AddOverlay(_overlay);
}
private void OnStatusRemoved(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
{
if (Status.HasEffectComp<DrunkStatusEffectComponent>(args.Target))
return;
if (_player.LocalEntity != args.Target)
return;
_overlay.CurrentBoozePower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
private void OnPlayerAttached(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectRelayedEvent<LocalPlayerAttachedEvent> args)
private void OnPlayerAttached(EntityUid uid, DrunkComponent component, LocalPlayerAttachedEvent args)
{
_overlayMan.AddOverlay(_overlay);
}
private void OnPlayerDetached(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectRelayedEvent<LocalPlayerDetachedEvent> args)
private void OnPlayerDetached(EntityUid uid, DrunkComponent component, LocalPlayerDetachedEvent args)
{
_overlay.CurrentBoozePower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
private void OnDrunkInit(EntityUid uid, DrunkComponent component, ComponentInit args)
{
if (_player.LocalEntity == uid)
_overlayMan.AddOverlay(_overlay);
}
private void OnDrunkShutdown(EntityUid uid, DrunkComponent component, ComponentShutdown args)
{
if (_player.LocalEntity == uid)
{
_overlay.CurrentBoozePower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
}
}

View File

@@ -0,0 +1,7 @@
using Content.Shared.Explosion.EntitySystems;
namespace Content.Client.Explosion;
public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem
{
}

View File

@@ -0,0 +1,7 @@
using Content.Shared.Explosion;
using Content.Shared.Explosion.Components;
namespace Content.Client.Explosion;
[RegisterComponent, Access(typeof(TriggerSystem))]
public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent {}

View File

@@ -1,12 +1,11 @@
using Content.Shared.Trigger;
using Content.Shared.Trigger.Components.Triggers;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
namespace Content.Client.Trigger.Systems;
namespace Content.Client.Explosion;
public sealed class ProximityTriggerAnimationSystem : EntitySystem
public sealed partial class TriggerSystem
{
[Dependency] private readonly AnimationPlayerSystem _player = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
@@ -19,7 +18,7 @@ public sealed class ProximityTriggerAnimationSystem : EntitySystem
private const string AnimKey = "proximity";
private static readonly Animation FlasherAnimation = new Animation
private static readonly Animation _flasherAnimation = new Animation
{
Length = TimeSpan.FromSeconds(0.6f),
AnimationTracks = {
@@ -43,10 +42,8 @@ public sealed class ProximityTriggerAnimationSystem : EntitySystem
}
};
public override void Initialize()
private void InitializeProximity()
{
base.Initialize();
SubscribeLocalEvent<TriggerOnProximityComponent, ComponentInit>(OnProximityInit);
SubscribeLocalEvent<TriggerOnProximityComponent, AppearanceChangeEvent>(OnProxAppChange);
SubscribeLocalEvent<TriggerOnProximityComponent, AnimationCompletedEvent>(OnProxAnimation);
@@ -97,7 +94,7 @@ public sealed class ProximityTriggerAnimationSystem : EntitySystem
break;
case ProximityTriggerVisuals.Active:
if (_player.HasRunningAnimation(uid, player, AnimKey)) return;
_player.Play((uid, player), FlasherAnimation, AnimKey);
_player.Play((uid, player), _flasherAnimation, AnimKey);
break;
case ProximityTriggerVisuals.Off:
default:

View File

@@ -0,0 +1,10 @@
namespace Content.Client.Explosion;
public sealed partial class TriggerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
InitializeProximity();
}
}

View File

@@ -1,5 +0,0 @@
using Content.Shared.Forensics.Systems;
namespace Content.Client.Forensics.Systems;
public sealed class ForensicsSystem : SharedForensicsSystem;

View File

@@ -34,8 +34,6 @@ namespace Content.Client.GameTicking.Managers
[ViewVariables] public TimeSpan StartTime { get; private set; }
[ViewVariables] public new bool Paused { get; private set; }
public override IReadOnlyList<(TimeSpan, string)> AllPreviousGameRules => new List<(TimeSpan, string)>();
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> JobsAvailable => _jobsAvailable;
[ViewVariables] public IReadOnlyDictionary<NetEntity, string> StationNames => _stationNames;

View File

@@ -72,7 +72,7 @@ public sealed partial class GatewayWindow : FancyWindow,
_isUnlockPending = _nextUnlock >= _timing.CurTime;
_isCooldownPending = _nextReady >= _timing.CurTime;
Container.RemoveAllChildren();
Container.DisposeAllChildren();
if (_destinations.Count == 0)
{

View File

@@ -1,58 +1,25 @@
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(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
public sealed class GhostRoleRadioBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private GhostRoleRadioMenu? _ghostRoleRadioMenu;
private SimpleRadialMenu? _ghostRoleRadioMenu;
public GhostRoleRadioBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_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;
_ghostRoleRadioMenu = this.CreateWindow<GhostRoleRadioMenu>();
_ghostRoleRadioMenu.SetEntity(Owner);
_ghostRoleRadioMenu.SendGhostRoleRadioMessageAction += SendGhostRoleRadioMessage;
}
private void SendGhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)

View File

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

View File

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

View File

@@ -136,7 +136,6 @@ namespace Content.Client.Ghost
//_actions.RemoveAction(uid, component.ToggleGhostHearingActionEntity); //Dont need in CP14
//CP14
_actions.RemoveAction(uid, component.CP14ToggleRoofActionEntity);
_actions.RemoveAction(uid, component.CP14RespawnActionEntity);
//CP14 end
if (uid != _playerManager.LocalEntity)

View File

@@ -1,90 +0,0 @@
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;
}
}

View File

@@ -5,17 +5,14 @@ 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;
@@ -30,10 +27,8 @@ 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; }
@@ -44,7 +39,6 @@ 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;
}
@@ -210,25 +204,6 @@ 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);
}

View File

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

View File

@@ -116,7 +116,7 @@ namespace Content.Client.HealthAnalyzer.UI
AlertsContainer.Visible = showAlerts;
if (showAlerts)
AlertsContainer.RemoveAllChildren();
AlertsContainer.DisposeAllChildren();
if (msg.Unrevivable == true)
AlertsContainer.AddChild(new RichTextLabel

View File

@@ -1,6 +1,5 @@
using Content.Shared.HotPotato;
using Robust.Shared.Random;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.HotPotato;
@@ -11,9 +10,6 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private readonly EntProtoId _hotPotatoEffectId = "HotPotatoEffect";
// TODO: particle system
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -27,7 +23,7 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem
if (_timing.CurTime < comp.TargetTime)
continue;
comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(comp.EffectCooldown);
Spawn(_hotPotatoEffectId, _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
Spawn("HotPotatoEffect", _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
}
}
}

View File

@@ -300,26 +300,25 @@ 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);
}
}
@@ -358,7 +357,9 @@ 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)
@@ -369,7 +370,9 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
var markingSprite = markingPrototype.Sprites[j];
if (markingSprite is not SpriteSpecifier.Rsi rsi)
return;
{
continue;
}
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
@@ -383,18 +386,26 @@ 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 _);
}
}
}

View File

@@ -404,7 +404,7 @@ public sealed partial class MarkingPicker : Control
var stateNames = GetMarkingStateNames(prototype);
_currentMarkingColors.Clear();
CMarkingColors.RemoveAllChildren();
CMarkingColors.DisposeAllChildren();
List<ColorSelectorSliders> colorSliders = new();
for (int i = 0; i < prototype.Sprites.Count; i++)
{

View File

@@ -216,6 +216,7 @@ public sealed partial class SingleMarkingPicker : BoxContainer
var marking = _markings[Slot];
ColorSelectorContainer.DisposeAllChildren();
ColorSelectorContainer.RemoveAllChildren();
if (marking.MarkingColors.Count != proto.Sprites.Count)

View File

@@ -0,0 +1,7 @@
using Content.Shared.IdentityManagement;
namespace Content.Client.IdentityManagement;
public sealed class IdentitySystem : SharedIdentitySystem
{
}

View File

@@ -23,12 +23,10 @@ public sealed class ImplanterSystem : SharedImplanterSystem
{
if (_uiSystem.TryGetOpenUi<DeimplantBoundUserInterface>(uid, DeimplantUiKey.Key, out var bui))
{
// TODO: Don't use protoId for deimplanting
// and especially not raw strings!
Dictionary<string, string> implants = new();
foreach (var implant in component.DeimplantWhitelist)
{
if (_proto.Resolve(implant, out var proto))
if (_proto.TryIndex(implant, out var proto))
implants.Add(proto.ID, proto.Name);
}

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