diff --git a/.editorconfig b/.editorconfig
index a87e5e0651..fa740a7511 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -351,7 +351,7 @@ resharper_csharp_qualified_using_at_nested_scope = false
resharper_csharp_prefer_qualified_reference = false
resharper_csharp_allow_alias = false
-[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
+[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props,slnx}]
indent_size = 2
[nuget.config]
diff --git a/.github/workflows/build-docfx.yml b/.github/workflows/build-docfx.yml
index 1f010b7291..dee31e9b31 100644
--- a/.github/workflows/build-docfx.yml
+++ b/.github/workflows/build-docfx.yml
@@ -21,7 +21,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
- dotnet-version: 9.0.x
+ dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
diff --git a/.github/workflows/build-map-renderer.yml b/.github/workflows/build-map-renderer.yml
index f93f4b25ae..fb6be1603c 100644
--- a/.github/workflows/build-map-renderer.yml
+++ b/.github/workflows/build-map-renderer.yml
@@ -36,7 +36,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
- dotnet-version: 9.0.x
+ dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
diff --git a/.github/workflows/build-test-debug.yml b/.github/workflows/build-test-debug.yml
index b9f2796113..e850606703 100644
--- a/.github/workflows/build-test-debug.yml
+++ b/.github/workflows/build-test-debug.yml
@@ -36,7 +36,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
- dotnet-version: 9.0.x
+ dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
diff --git a/.github/workflows/publish-testing.yml b/.github/workflows/publish-testing.yml
index 7a792ed2df..1b1344315a 100644
--- a/.github/workflows/publish-testing.yml
+++ b/.github/workflows/publish-testing.yml
@@ -20,7 +20,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
- dotnet-version: 9.0.x
+ dotnet-version: 10.0.x
- name: Get Engine Tag
run: |
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 968782fd96..aaadeefd85 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -47,7 +47,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
- dotnet-version: 9.0.x
+ dotnet-version: 10.0.x
- name: Get Engine Tag
run: |
diff --git a/.github/workflows/test-packaging.yml b/.github/workflows/test-packaging.yml
index f8ae6092e6..30735fc828 100644
--- a/.github/workflows/test-packaging.yml
+++ b/.github/workflows/test-packaging.yml
@@ -65,7 +65,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
- dotnet-version: 9.0.x
+ dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml
index 898e694934..cdad918b5b 100644
--- a/.github/workflows/update-wiki.yml
+++ b/.github/workflows/update-wiki.yml
@@ -6,13 +6,10 @@ on:
branches: [ master, jsondump ]
paths:
- '.github/workflows/update-wiki.yml'
- - 'Content.Shared/Chemistry/**.cs'
- - 'Content.Server/Chemistry/**.cs'
- - 'Content.Server/GuideGenerator/**.cs'
- - 'Content.Server/Corvax/GuideGenerator/**.cs'
- - 'Resources/Prototypes/Reagents/**.yml'
- - 'Resources/Prototypes/Chemistry/**.yml'
- - 'Resources/Prototypes/Recipes/Reactions/**.yml'
+ - 'Content.Shared/**'
+ - 'Content.Server/**'
+ - 'Content.Client/**'
+ - 'Resources/**'
- 'RobustToolbox/'
jobs:
@@ -51,42 +48,50 @@ jobs:
run: dotnet ./bin/Content.Server/Content.Server.dll --cvar autogen.destination_file=prototypes.json
continue-on-error: true
- - name: Upload chem_prototypes.json to wiki
- uses: jtmullen/mediawiki-edit-action@v0.1.1
- with:
- wiki_text_file: ./bin/Content.Server/data/chem_prototypes.json
- edit_summary: Update chem_prototypes.json via GitHub Actions
- page_name: "${{ secrets.WIKI_PAGE_ROOT }}/chem_prototypes.json"
- api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
- username: ${{ secrets.WIKI_BOT_USER }}
- password: ${{ secrets.WIKI_BOT_PASS }}
+ # Проходит по всем JSON-файлам в директории BASE и загружает каждый файл как страницу в MediaWiki.
+ # Имя страницы формируется из относительного пути к файлу.
+ - name: Upload JSON files to wiki
+ shell: bash
+ run: |
+ set -euo pipefail
- - name: Upload react_prototypes.json to wiki
- uses: jtmullen/mediawiki-edit-action@v0.1.1
- with:
- wiki_text_file: ./bin/Content.Server/data/react_prototypes.json
- edit_summary: Update react_prototypes.json via GitHub Actions
- page_name: "${{ secrets.WIKI_PAGE_ROOT }}/react_prototypes.json"
- api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
- username: ${{ secrets.WIKI_BOT_USER }}
- password: ${{ secrets.WIKI_BOT_PASS }}
+ BASE="./bin/Content.Server/data"
+ ROOT="${{ secrets.WIKI_PAGE_ROOT }}"
+ API="${{ secrets.WIKI_ROOT_URL }}/api.php"
+ USER="${{ secrets.WIKI_BOT_USER }}"
+ PASS="${{ secrets.WIKI_BOT_PASS }}"
- - name: Upload entity_prototypes.json to wiki
- uses: jtmullen/mediawiki-edit-action@v0.1.1
- with:
- wiki_text_file: ./bin/Content.Server/data/entity_prototypes.json
- edit_summary: Update entity_prototypes.json via GitHub Actions
- page_name: "${{ secrets.WIKI_PAGE_ROOT }}/entity_prototypes.json"
- api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
- username: ${{ secrets.WIKI_BOT_USER }}
- password: ${{ secrets.WIKI_BOT_PASS }}
+ API="$(printf "%s" "$API" | tr -d '\r\n' | sed 's/[[:space:]]*$//')"
+ USER="$(printf "%s" "$USER" | tr -d '\r\n')"
+ PASS="$(printf "%s" "$PASS" | tr -d '\r\n')"
+ ROOT="$(printf "%s" "$ROOT" | tr -d '\r\n' | sed 's/[[:space:]]*$//')"
- - name: Upload mealrecipes_prototypes.json to wiki
- uses: jtmullen/mediawiki-edit-action@v0.1.1
- with:
- wiki_text_file: ./bin/Content.Server/data/mealrecipes_prototypes.json
- edit_summary: Update mealrecipes_prototypes.json via GitHub Actions
- page_name: "${{ secrets.WIKI_PAGE_ROOT }}/mealrecipes_prototypes.json"
- api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php
- username: ${{ secrets.WIKI_BOT_USER }}
- password: ${{ secrets.WIKI_BOT_PASS }}
+ cookiejar="$(mktemp)"
+ trap 'rm -f "$cookiejar"' EXIT
+
+ login_token=$(curl -sS -c "$cookiejar" --data "action=query&meta=tokens&type=login&format=json" "$API" | jq -r '.query.tokens.logintoken')
+ curl -sS -c "$cookiejar" -b "$cookiejar" \
+ --data-urlencode "action=login" \
+ --data-urlencode "lgname=$USER" \
+ --data-urlencode "lgpassword=$PASS" \
+ --data-urlencode "lgtoken=$login_token" \
+ --data-urlencode "format=json" \
+ "$API" > /dev/null
+
+ find "$BASE" -type f -name '*.json' | while IFS= read -r file; do
+ rel="${file#$BASE/}"
+ rel="$(printf "%s" "$rel" | tr -d '\r\n' | sed 's/:/_/g')"
+ page="$ROOT/$rel"
+ echo "Uploading $rel → $page"
+
+ token=$(curl -sS -b "$cookiejar" --data "action=query&meta=tokens&format=json" "$API" | jq -r '.query.tokens.csrftoken')
+
+ curl -sS -b "$cookiejar" \
+ --data-urlencode "action=edit" \
+ --data-urlencode "title=$page" \
+ --data-urlencode "summary=Update $rel via GitHub Actions" \
+ --data-urlencode "text@${file}" \
+ --data-urlencode "token=$token" \
+ --data-urlencode "format=json" \
+ "$API" | jq -r '.'
+ done
diff --git a/.github/workflows/yaml-linter.yml b/.github/workflows/yaml-linter.yml
index edc00b2b3b..50057ccfe6 100644
--- a/.github/workflows/yaml-linter.yml
+++ b/.github/workflows/yaml-linter.yml
@@ -29,7 +29,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
- dotnet-version: 9.0.x
+ dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
diff --git a/.gitignore b/.gitignore
index 5c7244e5e1..ce2b66c868 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+# MSbuild binlog files
+*.binlog
+
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
diff --git a/BuildChecker/git_helper.py b/BuildChecker/git_helper.py
index bd6603bd34..4e42b1e70e 100644
--- a/BuildChecker/git_helper.py
+++ b/BuildChecker/git_helper.py
@@ -11,7 +11,7 @@ import time
from pathlib import Path
from typing import List
-SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
+SOLUTION_PATH = Path("..") / "SpaceStation14.slnx"
# If this doesn't match the saved version we overwrite them all.
CURRENT_HOOKS_VERSION = "4"
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
diff --git a/Content.Benchmarks/Content.Benchmarks.csproj b/Content.Benchmarks/Content.Benchmarks.csproj
index c3b60a1c69..8d4dfa31bd 100644
--- a/Content.Benchmarks/Content.Benchmarks.csproj
+++ b/Content.Benchmarks/Content.Benchmarks.csproj
@@ -1,17 +1,20 @@
-
- $(TargetFramework)
..\bin\Content.Benchmarks\
- false
false
Exe
true
- 12
+ false
+ disable
+
+
+
+
+
@@ -19,10 +22,12 @@
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/Content.Benchmarks/HeatCapacityBenchmark.cs b/Content.Benchmarks/HeatCapacityBenchmark.cs
new file mode 100644
index 0000000000..cef5bc10c7
--- /dev/null
+++ b/Content.Benchmarks/HeatCapacityBenchmark.cs
@@ -0,0 +1,83 @@
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using Content.IntegrationTests;
+using Content.IntegrationTests.Pair;
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Atmos;
+using Robust.Shared;
+using Robust.Shared.Analyzers;
+using Robust.Shared.GameObjects;
+
+namespace Content.Benchmarks;
+
+[Virtual]
+[GcServer(true)]
+[MemoryDiagnoser]
+public class HeatCapacityBenchmark
+{
+ private TestPair _pair = default!;
+ private IEntityManager _sEntMan = default!;
+ private IEntityManager _cEntMan = default!;
+ private Client.Atmos.EntitySystems.AtmosphereSystem _cAtmos = default!;
+ private AtmosphereSystem _sAtmos = default!;
+ private GasMixture _mix;
+
+ [GlobalSetup]
+ public async Task SetupAsync()
+ {
+ ProgramShared.PathOffset = "../../../../";
+ PoolManager.Startup();
+ _pair = await PoolManager.GetServerClient();
+ await _pair.Connect();
+ _cEntMan = _pair.Client.ResolveDependency();
+ _sEntMan = _pair.Server.ResolveDependency();
+ _cAtmos = _cEntMan.System();
+ _sAtmos = _sEntMan.System();
+
+ const float volume = 2500f;
+ const float temperature = 293.15f;
+
+ const float o2 = 12.3f;
+ const float n2 = 45.6f;
+ const float co2 = 0.42f;
+ const float plasma = 0.05f;
+
+ _mix = new GasMixture(volume) { Temperature = temperature };
+
+ _mix.AdjustMoles(Gas.Oxygen, o2);
+ _mix.AdjustMoles(Gas.Nitrogen, n2);
+ _mix.AdjustMoles(Gas.CarbonDioxide, co2);
+ _mix.AdjustMoles(Gas.Plasma, plasma);
+ }
+
+ [Benchmark]
+ public async Task ClientHeatCapacityBenchmark()
+ {
+ await _pair.Client.WaitPost(delegate
+ {
+ for (var i = 0; i < 10000; i++)
+ {
+ _cAtmos.GetHeatCapacity(_mix, applyScaling: true);
+ }
+ });
+ }
+
+ [Benchmark]
+ public async Task ServerHeatCapacityBenchmark()
+ {
+ await _pair.Server.WaitPost(delegate
+ {
+ for (var i = 0; i < 10000; i++)
+ {
+ _sAtmos.GetHeatCapacity(_mix, applyScaling: true);
+ }
+ });
+ }
+
+ [GlobalCleanup]
+ public async Task CleanupAsync()
+ {
+ await _pair.DisposeAsync();
+ PoolManager.Shutdown();
+ }
+}
diff --git a/Content.Client/Administration/AdminNameOverlay.cs b/Content.Client/Administration/AdminNameOverlay.cs
index abeed65732..d1a9f6f7a7 100644
--- a/Content.Client/Administration/AdminNameOverlay.cs
+++ b/Content.Client/Administration/AdminNameOverlay.cs
@@ -134,7 +134,7 @@ internal sealed class AdminNameOverlay : Overlay
? null
: _prototypeManager.Index(playerInfo.RoleProto.Value);
- var roleName = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
+ var roleName = rolePrototype?.Name ?? RoleTypePrototype.FallbackName;
var roleColor = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
var roleSymbol = rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol;
@@ -213,7 +213,7 @@ internal sealed class AdminNameOverlay : Overlay
{
color = Color.GreenYellow;
color.A = alpha;
- args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? color : colorDisconnected);
+ args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.StartingJob, uiScale, playerInfo.Connected ? color : colorDisconnected);
currentOffset += lineoffset;
}
@@ -241,7 +241,7 @@ internal sealed class AdminNameOverlay : Overlay
color = roleColor;
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
text = IsFiltered(playerInfo.RoleProto)
- ? roleName.ToUpper()
+ ? Loc.GetString(roleName).ToUpper()
: string.Empty;
break;
case AdminOverlayAntagFormat.Subtype:
diff --git a/Content.Client/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.Gases.cs
new file mode 100644
index 0000000000..17b994e64f
--- /dev/null
+++ b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.Gases.cs
@@ -0,0 +1,35 @@
+using System.Runtime.CompilerServices;
+using Content.Shared.Atmos;
+
+namespace Content.Client.Atmos.EntitySystems;
+
+public sealed partial class AtmosphereSystem
+{
+ /*
+ Partial class for operations involving GasMixtures.
+
+ Any method that is overridden here is usually because the server-sided implementation contains
+ code that would escape sandbox. As such these methods are overridden here with a safe
+ implementation.
+ */
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override float GetHeatCapacityCalculation(float[] moles, bool space)
+ {
+ // Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms.
+ if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f))
+ {
+ return Atmospherics.SpaceHeatCapacity;
+ }
+
+ // explicit stackalloc call is banned on client tragically.
+ // the JIT does not stackalloc this during runtime,
+ // though this isnt the hottest code path so it should be fine
+ // the gc can eat a little as a treat
+ var tmp = new float[moles.Length];
+ NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp);
+ // Adjust heat capacity by speedup, because this is primarily what
+ // determines how quickly gases heat up/cool.
+ return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
+ }
+}
diff --git a/Content.Client/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.cs
index 44759372f4..30567abbf7 100644
--- a/Content.Client/Atmos/EntitySystems/AtmosphereSystem.cs
+++ b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.cs
@@ -5,7 +5,7 @@ using Robust.Shared.GameStates;
namespace Content.Client.Atmos.EntitySystems;
-public sealed class AtmosphereSystem : SharedAtmosphereSystem
+public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
{
public override void Initialize()
{
diff --git a/Content.Client/BarSign/BarSignSystem.cs b/Content.Client/BarSign/BarSignSystem.cs
deleted file mode 100644
index 1ea99864a1..0000000000
--- a/Content.Client/BarSign/BarSignSystem.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using Content.Client.BarSign.Ui;
-using Content.Shared.BarSign;
-using Content.Shared.Power;
-using Robust.Client.GameObjects;
-using Robust.Shared.Prototypes;
-
-namespace Content.Client.BarSign;
-
-public sealed class BarSignSystem : VisualizerSystem
-{
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly UserInterfaceSystem _ui = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnAfterAutoHandleState);
- }
-
- private void OnAfterAutoHandleState(EntityUid uid, BarSignComponent component, ref AfterAutoHandleStateEvent args)
- {
- if (_ui.TryGetOpenUi(uid, BarSignUiKey.Key, out var bui))
- bui.Update(component.Current);
-
- UpdateAppearance(uid, component);
- }
-
- protected override void OnAppearanceChange(EntityUid uid, BarSignComponent component, ref AppearanceChangeEvent args)
- {
- UpdateAppearance(uid, component, args.Component, args.Sprite);
- }
-
- private void UpdateAppearance(EntityUid id, BarSignComponent sign, AppearanceComponent? appearance = null, SpriteComponent? sprite = null)
- {
- if (!Resolve(id, ref appearance, ref sprite))
- return;
-
- AppearanceSystem.TryGetData(id, PowerDeviceVisuals.Powered, out var powered, appearance);
-
- if (powered
- && sign.Current != null
- && _prototypeManager.Resolve(sign.Current, out var proto))
- {
- SpriteSystem.LayerSetSprite((id, sprite), 0, proto.Icon);
- sprite.LayerSetShader(0, "unshaded");
- }
- else
- {
- SpriteSystem.LayerSetRsiState((id, sprite), 0, "empty");
- sprite.LayerSetShader(0, null, null);
- }
- }
-}
diff --git a/Content.Client/BarSign/BarSignVisualizerSystem.cs b/Content.Client/BarSign/BarSignVisualizerSystem.cs
new file mode 100644
index 0000000000..3e641fed70
--- /dev/null
+++ b/Content.Client/BarSign/BarSignVisualizerSystem.cs
@@ -0,0 +1,30 @@
+using Content.Shared.BarSign;
+using Content.Shared.Power;
+using Robust.Client.GameObjects;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.BarSign;
+
+public sealed class BarSignVisualizerSystem : VisualizerSystem
+{
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ protected override void OnAppearanceChange(EntityUid uid, BarSignComponent component, ref AppearanceChangeEvent args)
+ {
+ AppearanceSystem.TryGetData(uid, PowerDeviceVisuals.Powered, out var powered, args.Component);
+ AppearanceSystem.TryGetData(uid, BarSignVisuals.BarSignPrototype, out var currentSign, args.Component);
+
+ if (powered
+ && currentSign != null
+ && _prototypeManager.Resolve(currentSign, out var proto))
+ {
+ SpriteSystem.LayerSetSprite((uid, args.Sprite), 0, proto.Icon);
+ args.Sprite?.LayerSetShader(0, "unshaded");
+ }
+ else
+ {
+ SpriteSystem.LayerSetRsiState((uid, args.Sprite), 0, "empty");
+ args.Sprite?.LayerSetShader(0, null, null);
+ }
+ }
+}
diff --git a/Content.Client/BarSign/Ui/BarSignBoundUserInterface.cs b/Content.Client/BarSign/Ui/BarSignBoundUserInterface.cs
index fe07f0f1d1..8265877edf 100644
--- a/Content.Client/BarSign/Ui/BarSignBoundUserInterface.cs
+++ b/Content.Client/BarSign/Ui/BarSignBoundUserInterface.cs
@@ -19,32 +19,27 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
var sign = EntMan.GetComponentOrNull(Owner)?.Current is { } current
? _prototype.Index(current)
: null;
- var allSigns = Shared.BarSign.BarSignSystem.GetAllBarSigns(_prototype)
+ var allSigns = BarSignSystem.GetAllBarSigns(_prototype)
.OrderBy(p => Loc.GetString(p.Name))
.ToList();
_menu = new(sign, allSigns);
_menu.OnSignSelected += id =>
{
- SendMessage(new SetBarSignMessage(id));
+ SendPredictedMessage(new SetBarSignMessage(id));
};
_menu.OnClose += Close;
_menu.OpenCentered();
}
- public void Update(ProtoId? sign)
+ public override void Update()
{
- if (_prototype.Resolve(sign, out var signPrototype))
- _menu?.UpdateState(signPrototype);
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
+ if (!EntMan.TryGetComponent(Owner, out var signComp))
return;
- _menu?.Dispose();
+
+ if (_prototype.Resolve(signComp.Current, out var signPrototype))
+ _menu?.UpdateState(signPrototype);
}
}
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml.cs b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
index d813f70ff4..bac7d84bf7 100644
--- a/Content.Client/Cargo/UI/BountyEntry.xaml.cs
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
@@ -7,7 +7,6 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
-using Serilog;
namespace Content.Client.Cargo.UI;
diff --git a/Content.Client/Chemistry/EntitySystems/HyposprayStatusControlSystem.cs b/Content.Client/Chemistry/EntitySystems/HyposprayStatusControlSystem.cs
deleted file mode 100644
index 4dfc8506d2..0000000000
--- a/Content.Client/Chemistry/EntitySystems/HyposprayStatusControlSystem.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Client.Chemistry.UI;
-using Content.Client.Items;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Chemistry.EntitySystems;
-
-namespace Content.Client.Chemistry.EntitySystems;
-
-public sealed class HyposprayStatusControlSystem : EntitySystem
-{
- [Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!;
- public override void Initialize()
- {
- base.Initialize();
- Subs.ItemStatus(ent => new HyposprayStatusControl(ent, _solutionContainers));
- }
-}
diff --git a/Content.Client/Chemistry/EntitySystems/InjectorStatusControlSystem.cs b/Content.Client/Chemistry/EntitySystems/InjectorStatusControlSystem.cs
new file mode 100644
index 0000000000..ca8685eb3e
--- /dev/null
+++ b/Content.Client/Chemistry/EntitySystems/InjectorStatusControlSystem.cs
@@ -0,0 +1,20 @@
+using Content.Client.Chemistry.UI;
+using Content.Client.Items;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Chemistry.EntitySystems;
+
+public sealed class InjectorStatusControlSystem : EntitySystem
+{
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ Subs.ItemStatus(injector => new InjectorStatusControl(injector, _solutionContainers, _prototypeManager));
+ }
+}
diff --git a/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
deleted file mode 100644
index 58cb5330a2..0000000000
--- a/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Client.Chemistry.UI;
-using Content.Client.Items;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Chemistry.EntitySystems;
-
-namespace Content.Client.Chemistry.EntitySystems;
-
-public sealed class InjectorSystem : SharedInjectorSystem
-{
- public override void Initialize()
- {
- base.Initialize();
-
- Subs.ItemStatus(ent => new InjectorStatusControl(ent, SolutionContainer));
- }
-}
diff --git a/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs b/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs
index a669a8da4c..d0a9f5644c 100644
--- a/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs
+++ b/Content.Client/Chemistry/UI/ChemMasterBoundUserInterface.cs
@@ -48,6 +48,10 @@ namespace Content.Client.Chemistry.UI
(uint) _window.BottleDosage.Value, _window.LabelLine));
_window.BufferSortButton.OnPressed += _ => SendMessage(
new ChemMasterSortingTypeCycleMessage());
+ _window.OutputBufferDraw.OnPressed += _ => SendMessage(
+ new ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource.Internal));
+ _window.OutputBeakerDraw.OnPressed += _ => SendMessage(
+ new ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource.External));
for (uint i = 0; i < _window.PillTypeButtons.Length; i++)
{
diff --git a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml
index 5bc640ab44..cc2a199172 100644
--- a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml
+++ b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml
@@ -79,10 +79,13 @@
-
+
+
+
-
-
+
+
+
diff --git a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs
index d610f34b29..a8aab1d35f 100644
--- a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs
+++ b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs
@@ -150,7 +150,17 @@ namespace Content.Client.Chemistry.UI
// Ensure the Panel Info is updated, including UI elements for Buffer Volume, Output Container and so on
UpdatePanelInfo(castState);
- BufferCurrentVolume.Text = $" {castState.BufferCurrentVolume?.Int() ?? 0}u";
+ switch (castState.DrawSource)
+ {
+ case ChemMasterDrawSource.Internal:
+ SetBufferText(castState.BufferCurrentVolume, "chem-master-output-buffer-draw");
+ break;
+ case ChemMasterDrawSource.External:
+ SetBufferText(castState.InputContainerInfo?.CurrentVolume, "chem-master-output-beaker-draw");
+ break;
+ default:
+ throw new($"Chemmaster {castState.OutputContainerInfo} draw source is not set");
+ }
InputEjectButton.Disabled = castState.InputContainerInfo is null;
OutputEjectButton.Disabled = castState.OutputContainerInfo is null;
@@ -168,9 +178,14 @@ namespace Content.Client.Chemistry.UI
var holdsReagents = output?.Reagents != null;
var pillNumberMax = holdsReagents ? 0 : remainingCapacity;
var bottleAmountMax = holdsReagents ? remainingCapacity : 0;
- var bufferVolume = castState.BufferCurrentVolume?.Int() ?? 0;
+ var outputVolume = castState.DrawSource switch
+ {
+ ChemMasterDrawSource.Internal => castState.BufferCurrentVolume?.Int() ?? 0,
+ ChemMasterDrawSource.External => castState.InputContainerInfo?.CurrentVolume.Int() ?? 0,
+ _ => 0,
+ };
- PillDosage.Value = (int)Math.Min(bufferVolume, castState.PillDosageLimit);
+ PillDosage.Value = (int)Math.Min(outputVolume, castState.PillDosageLimit);
PillTypeButtons[castState.SelectedPillType].Pressed = true;
@@ -186,25 +201,35 @@ namespace Content.Client.Chemistry.UI
// Avoid division by zero
if (PillDosage.Value > 0)
{
- PillNumber.Value = Math.Min(bufferVolume / PillDosage.Value, pillNumberMax);
+ PillNumber.Value = Math.Min(outputVolume / PillDosage.Value, pillNumberMax);
}
else
{
PillNumber.Value = 0;
}
- BottleDosage.Value = Math.Min(bottleAmountMax, bufferVolume);
+ BottleDosage.Value = Math.Min(bottleAmountMax, outputVolume);
}
///
- /// Generate a product label based on reagents in the buffer.
+ /// Generate a product label based on reagents in the buffer or beaker.
///
/// State data sent by the server.
private string GenerateLabel(ChemMasterBoundUserInterfaceState state)
{
- if (state.BufferCurrentVolume == 0)
+ if (
+ state.BufferCurrentVolume == 0 && state.DrawSource == ChemMasterDrawSource.Internal ||
+ state.InputContainerInfo?.CurrentVolume == 0 && state.DrawSource == ChemMasterDrawSource.External ||
+ state.InputContainerInfo?.Reagents == null
+ )
return "";
- var reagent = state.BufferReagents.OrderBy(r => r.Quantity).First().Reagent;
+ var reagent = (state.DrawSource switch
+ {
+ ChemMasterDrawSource.Internal => state.BufferReagents,
+ ChemMasterDrawSource.External => state.InputContainerInfo.Reagents ?? [],
+ _ => throw new($"Chemmaster {state.OutputContainerInfo} draw source is not set"),
+ }).MinBy(r => r.Quantity)
+ .Reagent;
_prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto);
return proto?.LocalizedName ?? "";
}
@@ -233,6 +258,8 @@ namespace Content.Client.Chemistry.UI
_ => Loc.GetString("chem-master-window-sort-type-none")
};
+ OutputBufferDraw.Pressed = state.DrawSource == ChemMasterDrawSource.Internal;
+ OutputBeakerDraw.Pressed = state.DrawSource == ChemMasterDrawSource.External;
if (!state.BufferReagents.Any())
{
@@ -414,6 +441,12 @@ namespace Content.Client.Chemistry.UI
get => LabelLineEdit.Text;
set => LabelLineEdit.Text = value;
}
+
+ private void SetBufferText(FixedPoint2? volume, string text)
+ {
+ BufferCurrentVolume.Text = $" {volume ?? FixedPoint2.Zero}u";
+ DrawSource.Text = Loc.GetString(text);
+ }
}
public sealed class ReagentButton : Button
diff --git a/Content.Client/Chemistry/UI/HyposprayStatusControl.cs b/Content.Client/Chemistry/UI/HyposprayStatusControl.cs
deleted file mode 100644
index a564bcefc6..0000000000
--- a/Content.Client/Chemistry/UI/HyposprayStatusControl.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using Content.Client.Message;
-using Content.Client.Stylesheets;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Chemistry.EntitySystems;
-using Content.Shared.FixedPoint;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Timing;
-
-namespace Content.Client.Chemistry.UI;
-
-public sealed class HyposprayStatusControl : Control
-{
- private readonly Entity _parent;
- private readonly RichTextLabel _label;
- private readonly SharedSolutionContainerSystem _solutionContainers;
-
- private FixedPoint2 PrevVolume;
- private FixedPoint2 PrevMaxVolume;
- private bool PrevOnlyAffectsMobs;
-
- public HyposprayStatusControl(Entity parent, SharedSolutionContainerSystem solutionContainers)
- {
- _parent = parent;
- _solutionContainers = solutionContainers;
- _label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } };
- AddChild(_label);
- }
-
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
-
- if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution))
- return;
-
- // only updates the UI if any of the details are different than they previously were
- if (PrevVolume == solution.Volume
- && PrevMaxVolume == solution.MaxVolume
- && PrevOnlyAffectsMobs == _parent.Comp.OnlyAffectsMobs)
- return;
-
- PrevVolume = solution.Volume;
- PrevMaxVolume = solution.MaxVolume;
- PrevOnlyAffectsMobs = _parent.Comp.OnlyAffectsMobs;
-
- var modeStringLocalized = Loc.GetString((_parent.Comp.OnlyAffectsMobs && _parent.Comp.CanContainerDraw) switch
- {
- false => "hypospray-all-mode-text",
- true => "hypospray-mobs-only-mode-text",
- });
-
- _label.SetMarkup(Loc.GetString("hypospray-volume-label",
- ("currentVolume", solution.Volume),
- ("totalVolume", solution.MaxVolume),
- ("modeString", modeStringLocalized)));
- }
-}
diff --git a/Content.Client/Chemistry/UI/InjectorStatusControl.cs b/Content.Client/Chemistry/UI/InjectorStatusControl.cs
index 24f988bd35..0c57da7813 100644
--- a/Content.Client/Chemistry/UI/InjectorStatusControl.cs
+++ b/Content.Client/Chemistry/UI/InjectorStatusControl.cs
@@ -2,26 +2,32 @@ using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Chemistry.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Chemistry.UI;
public sealed class InjectorStatusControl : Control
{
+ private readonly IPrototypeManager _prototypeManager;
+
private readonly Entity _parent;
private readonly SharedSolutionContainerSystem _solutionContainers;
private readonly RichTextLabel _label;
- private FixedPoint2 PrevVolume;
- private FixedPoint2 PrevMaxVolume;
- private FixedPoint2 PrevTransferAmount;
- private InjectorToggleMode PrevToggleState;
+ private FixedPoint2 _prevVolume;
+ private FixedPoint2 _prevMaxVolume;
+ private FixedPoint2? _prevTransferAmount;
+ private InjectorBehavior _prevBehavior;
- public InjectorStatusControl(Entity parent, SharedSolutionContainerSystem solutionContainers)
+ public InjectorStatusControl(Entity parent, SharedSolutionContainerSystem solutionContainers, IPrototypeManager prototypeManager)
{
+ _prototypeManager = prototypeManager;
+
_parent = parent;
_solutionContainers = solutionContainers;
_label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } };
@@ -32,33 +38,38 @@ public sealed class InjectorStatusControl : Control
{
base.FrameUpdate(args);
- if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution))
+ if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution)
+ || !_prototypeManager.Resolve(_parent.Comp.ActiveModeProtoId, out var activeMode))
return;
// 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
- && PrevToggleState == _parent.Comp.ToggleState)
+ if (_prevVolume == solution.Volume
+ && _prevMaxVolume == solution.MaxVolume
+ && _prevTransferAmount == _parent.Comp.CurrentTransferAmount
+ && _prevBehavior == activeMode.Behavior)
return;
- PrevVolume = solution.Volume;
- PrevMaxVolume = solution.MaxVolume;
- PrevTransferAmount = _parent.Comp.CurrentTransferAmount;
- PrevToggleState = _parent.Comp.ToggleState;
+ _prevVolume = solution.Volume;
+ _prevMaxVolume = solution.MaxVolume;
+ _prevTransferAmount = _parent.Comp.CurrentTransferAmount;
+ _prevBehavior = activeMode.Behavior;
// Update current volume and injector state
- var modeStringLocalized = Loc.GetString(_parent.Comp.ToggleState switch
+ // Seeing transfer volume is only important for injectors that can change it.
+ if (activeMode.TransferAmounts.Count > 1 && _parent.Comp.CurrentTransferAmount.HasValue)
{
- InjectorToggleMode.Draw => "injector-draw-text",
- InjectorToggleMode.Inject => "injector-inject-text",
- _ => "injector-invalid-injector-toggle-mode"
- });
-
- _label.SetMarkup(Loc.GetString("injector-volume-label",
- ("currentVolume", solution.Volume),
- ("totalVolume", solution.MaxVolume),
- ("modeString", modeStringLocalized),
- ("transferVolume", _parent.Comp.CurrentTransferAmount)));
+ _label.SetMarkup(Loc.GetString("injector-volume-transfer-label",
+ ("currentVolume", solution.Volume),
+ ("totalVolume", solution.MaxVolume),
+ ("modeString", Loc.GetString(activeMode.Name)),
+ ("transferVolume", _parent.Comp.CurrentTransferAmount.Value)));
+ }
+ else
+ {
+ _label.SetMarkup(Loc.GetString("injector-volume-label",
+ ("currentVolume", solution.Volume),
+ ("totalVolume", solution.MaxVolume),
+ ("modeString", Loc.GetString(activeMode.Name))));
+ }
}
}
diff --git a/Content.Client/Chemistry/UI/TransferAmountBoundUserInterface.cs b/Content.Client/Chemistry/UI/TransferAmountBoundUserInterface.cs
index 1bc1c0dba9..884a5db9da 100644
--- a/Content.Client/Chemistry/UI/TransferAmountBoundUserInterface.cs
+++ b/Content.Client/Chemistry/UI/TransferAmountBoundUserInterface.cs
@@ -2,41 +2,31 @@ using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
-namespace Content.Client.Chemistry.UI
+namespace Content.Client.Chemistry.UI;
+
+[UsedImplicitly]
+public sealed class TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
- [UsedImplicitly]
- public sealed class TransferAmountBoundUserInterface : BoundUserInterface
+ [ViewVariables]
+ private TransferAmountWindow? _window;
+
+ protected override void Open()
{
- private IEntityManager _entManager;
- private EntityUid _owner;
- [ViewVariables]
- private TransferAmountWindow? _window;
+ base.Open();
+ _window = this.CreateWindow();
- public TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ if (EntMan.TryGetComponent(Owner, out var comp))
+ _window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
+
+ _window.ApplyButton.OnPressed += _ =>
{
- _owner = owner;
- _entManager = IoCManager.Resolve();
- }
-
- protected override void Open()
- {
- base.Open();
- _window = this.CreateWindow();
-
- if (_entManager.TryGetComponent(_owner, out var comp))
- _window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
-
- _window.ApplyButton.OnPressed += _ =>
+ if (int.TryParse(_window.AmountLineEdit.Text, out var i))
{
- if (int.TryParse(_window.AmountLineEdit.Text, out var i))
- {
- SendMessage(new TransferAmountSetValueMessage(FixedPoint2.New(i)));
- _window.Close();
- }
- };
- }
+ SendPredictedMessage(new TransferAmountSetValueMessage(FixedPoint2.New(i)));
+ _window.Close();
+ }
+ };
}
}
diff --git a/Content.Client/Chemistry/UI/TransferAmountWindow.xaml.cs b/Content.Client/Chemistry/UI/TransferAmountWindow.xaml.cs
index 6bae044441..2d01098213 100644
--- a/Content.Client/Chemistry/UI/TransferAmountWindow.xaml.cs
+++ b/Content.Client/Chemistry/UI/TransferAmountWindow.xaml.cs
@@ -3,34 +3,33 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-namespace Content.Client.Chemistry.UI
+namespace Content.Client.Chemistry.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class TransferAmountWindow : DefaultWindow
{
- [GenerateTypedNameReferences]
- public sealed partial class TransferAmountWindow : DefaultWindow
+ private int _max = Int32.MaxValue;
+ private int _min = 1;
+
+ public TransferAmountWindow()
{
- private int _max = Int32.MaxValue;
- private int _min = 1;
+ RobustXamlLoader.Load(this);
+ AmountLineEdit.OnTextChanged += OnValueChanged;
+ }
- public TransferAmountWindow()
- {
- RobustXamlLoader.Load(this);
- AmountLineEdit.OnTextChanged += OnValueChanged;
- }
+ public void SetBounds(int min, int max)
+ {
+ _min = min;
+ _max = max;
+ MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
+ MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
+ }
- public void SetBounds(int min, int max)
- {
- _min = min;
- _max = max;
- MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
- MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
- }
-
- private void OnValueChanged(LineEdit.LineEditEventArgs args)
- {
- if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
- ApplyButton.Disabled = true;
- else
- ApplyButton.Disabled = false;
- }
+ private void OnValueChanged(LineEdit.LineEditEventArgs args)
+ {
+ if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
+ ApplyButton.Disabled = true;
+ else
+ ApplyButton.Disabled = false;
}
}
diff --git a/Content.Client/CombatMode/CombatModeIndicatorsOverlay.cs b/Content.Client/CombatMode/CombatModeIndicatorsOverlay.cs
index b2bdf2893d..d852e20c45 100644
--- a/Content.Client/CombatMode/CombatModeIndicatorsOverlay.cs
+++ b/Content.Client/CombatMode/CombatModeIndicatorsOverlay.cs
@@ -4,10 +4,8 @@ using Content.Shared.Weapons.Ranged.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
-using Robust.Client.Serialization;
using Robust.Client.UserInterface;
using Robust.Shared.Enums;
-using Robust.Shared.Graphics;
using Robust.Shared.Utility;
namespace Content.Client.CombatMode;
diff --git a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
index d5fee2bdda..6041b405c9 100644
--- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
+++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
@@ -30,7 +30,10 @@ namespace Content.Client.Construction.UI
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
+ [Dependency] private readonly ILogManager _logManager = default!;
+
private readonly SpriteSystem _spriteSystem;
+ private readonly ISawmill _sawmill;
private readonly IConstructionMenuView _constructionView;
private readonly EntityWhitelistSystem _whitelistSystem;
@@ -90,6 +93,7 @@ namespace Content.Client.Construction.UI
_constructionView = new ConstructionMenu();
_whitelistSystem = _entManager.System();
_spriteSystem = _entManager.System();
+ _sawmill = _logManager.GetSawmill("construction.ui");
// This is required so that if we load after the system is initialized, we can bind to it immediately
if (_systemManager.TryGetEntitySystem(out var constructionSystem))
@@ -284,7 +288,7 @@ namespace Content.Client.Construction.UI
if (!_constructionSystem!.TryGetRecipePrototype(recipe.ID, out var targetProtoId))
{
- Logger.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
+ _sawmill.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
recipe.ID,
nameof(ConstructionPrototype));
continue;
diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
index 8ee8df48fd..7db15596bd 100644
--- a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
+++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
@@ -1,13 +1,11 @@
using System.Linq;
using Content.Client.Materials;
-using Content.Client.Materials.UI;
using Content.Client.Message;
using Content.Client.UserInterface.Controls;
using Content.Shared.Construction.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Materials;
using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -61,57 +59,48 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
return;
+ var flatpackerEnt = (_owner, flatpacker);
+
if (flatpacker.Packing)
{
PackButton.Disabled = true;
}
else if (_currentBoard != null)
{
- Dictionary cost;
- if (_entityManager.TryGetComponent(_currentBoard, out var machineBoardComp))
- cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
- else
- cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
-
- PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
+ PackButton.Disabled = !_flatpack.TryGetFlatpackCreationCost(flatpackerEnt, _currentBoard.Value, out var curCost)
+ || !_materialStorage.CanChangeMaterialAmount(_owner, curCost);
}
if (_currentBoard == itemSlot.Item)
return;
_currentBoard = itemSlot.Item;
- CostHeaderLabel.Visible = _currentBoard != null;
+ CostHeaderLabel.Visible = false;
InsertLabel.Visible = _currentBoard == null;
- if (_currentBoard is not null)
+ if (_currentBoard is null)
{
- string? prototype = null;
- Dictionary? cost = null;
+ MachineSprite.SetPrototype(NoBoardEffectId);
+ CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
+ MachineNameLabel.SetMessage(string.Empty);
+ PackButton.Disabled = true;
+ return;
+ }
- if (_entityManager.TryGetComponent(_currentBoard, out var newMachineBoardComp))
- {
- prototype = newMachineBoardComp.Prototype;
- cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, newMachineBoardComp));
- }
- else if (_entityManager.TryGetComponent(_currentBoard, out var computerBoard))
- {
- prototype = computerBoard.Prototype;
- cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
- }
-
- if (prototype is not null && cost is not null)
- {
- var proto = _prototypeManager.Index(prototype);
- MachineSprite.SetPrototype(prototype);
- MachineNameLabel.SetMessage(proto.Name);
- CostLabel.SetMarkup(GetCostString(cost));
- }
+ if (_flatpack.TryGetFlatpackResultPrototype(_currentBoard.Value, out var prototype) &&
+ _flatpack.TryGetFlatpackCreationCost(flatpackerEnt, _currentBoard.Value, out var cost))
+ {
+ var proto = _prototypeManager.Index(prototype);
+ MachineSprite.SetPrototype(prototype);
+ MachineNameLabel.SetMessage(proto.Name);
+ CostLabel.SetMarkup(GetCostString(cost));
+ CostHeaderLabel.Visible = true;
}
else
{
MachineSprite.SetPrototype(NoBoardEffectId);
- CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
- MachineNameLabel.SetMessage(" ");
+ CostLabel.SetMarkup(Loc.GetString("flatpacker-ui-board-invalid-label"));
+ MachineNameLabel.SetMessage(string.Empty);
PackButton.Disabled = true;
}
}
diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index 62c3abf68d..b93b5f525a 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -1,26 +1,24 @@
-
- $(TargetFramework)
- 12
- false
false
..\bin\Content.Client\
Exe
RA0032;nullable
- enable
- Debug;Release;Tools;DebugOpt
- AnyCPU
+
+
+
+
+
+
+
+
+
-
-
-
-
diff --git a/Content.Client/Corvax/TTS/TTSSystem.cs b/Content.Client/Corvax/TTS/TTSSystem.cs
index 54ad5f60f2..11cc525343 100644
--- a/Content.Client/Corvax/TTS/TTSSystem.cs
+++ b/Content.Client/Corvax/TTS/TTSSystem.cs
@@ -89,16 +89,28 @@ public sealed class TTSSystem : EntitySystem
{
// Corvax-Wega-SoundInsolation-Start
if (!TryGetEntity(ev.SourceUid.Value, out var sourceEntityOpt) || !sourceEntityOpt.HasValue)
+ {
+ _contentRoot.RemoveFile(filePath);
return;
+ }
var sourceEntity = sourceEntityOpt.Value;
+ if (!Exists(sourceEntity) || Deleted(sourceEntity))
+ {
+ _contentRoot.RemoveFile(filePath);
+ return;
+ }
+
float volumeMultiplier = 1f;
if (_player.LocalEntity != null && Exists(_player.LocalEntity.Value))
{
var insulation = _soundInsulation.GetSoundInsulation(sourceEntity, _player.LocalEntity.Value);
if (insulation >= 0.95f)
+ {
+ _contentRoot.RemoveFile(filePath);
return;
+ }
if (insulation > 0.1f && insulation < 0.95f)
{
diff --git a/Content.Client/Fluids/PuddleSystem.cs b/Content.Client/Fluids/PuddleSystem.cs
index 1ab1ded3e8..ab96d24d85 100644
--- a/Content.Client/Fluids/PuddleSystem.cs
+++ b/Content.Client/Fluids/PuddleSystem.cs
@@ -73,7 +73,19 @@ public sealed class PuddleSystem : SharedPuddleSystem
// Maybe someday we'll have clientside prediction for entity spawning, but not today.
// Until then, these methods do nothing on the client.
///
- public override bool TrySplashSpillAt(EntityUid uid, EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true, EntityUid? user = null)
+ public override bool TrySplashSpillAt(Entity entity, EntityCoordinates coordinates, out EntityUid puddleUid, out Solution solution, bool sound = true, EntityUid? user = null)
+ {
+ puddleUid = EntityUid.Invalid;
+ solution = new Solution();
+ return false;
+ }
+
+ public override bool TrySplashSpillAt(EntityUid entity,
+ EntityCoordinates coordinates,
+ Solution spilled,
+ out EntityUid puddleUid,
+ bool sound = true,
+ EntityUid? user = null)
{
puddleUid = EntityUid.Invalid;
return false;
diff --git a/Content.Client/Guidebook/Controls/GuidebookRichPrototypeLink.cs b/Content.Client/Guidebook/Controls/GuidebookRichPrototypeLink.cs
index b54dd8b701..1e8b524997 100644
--- a/Content.Client/Guidebook/Controls/GuidebookRichPrototypeLink.cs
+++ b/Content.Client/Guidebook/Controls/GuidebookRichPrototypeLink.cs
@@ -42,7 +42,7 @@ public sealed class GuidebookRichPrototypeLink : Control, IPrototypeLinkControl
public void SetMessage(FormattedMessage message)
{
_message = message;
- _richTextLabel.SetMessage(_message);
+ _richTextLabel.SetMessage(_message, tagsAllowed: null);
}
public IPrototype? LinkedPrototype { get; set; }
diff --git a/Content.Client/Guidebook/DocumentParsingManager.static.cs b/Content.Client/Guidebook/DocumentParsingManager.static.cs
index 3e37942381..c702ac97ad 100644
--- a/Content.Client/Guidebook/DocumentParsingManager.static.cs
+++ b/Content.Client/Guidebook/DocumentParsingManager.static.cs
@@ -82,7 +82,7 @@ public sealed partial class DocumentParsingManager
}
msg.Pop();
- rt.SetMessage(msg);
+ rt.SetMessage(msg, tagsAllowed: null);
return rt;
},
TextParser)
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerControl.xaml b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerControl.xaml
new file mode 100644
index 0000000000..06c6528f59
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerControl.xaml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerControl.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerControl.xaml.cs
new file mode 100644
index 0000000000..949b4770c4
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerControl.xaml.cs
@@ -0,0 +1,241 @@
+using System.Linq;
+using System.Numerics;
+using Content.Shared.Atmos;
+using Content.Shared.Damage.Components;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.IdentityManagement;
+using Content.Shared.MedicalScanner;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+namespace Content.Client.HealthAnalyzer.UI;
+
+// Health analyzer UI is split from its window because it's used by both the
+// health analyzer item and the cryo pod UI.
+
+[GenerateTypedNameReferences]
+public sealed partial class HealthAnalyzerControl : BoxContainer
+{
+ private readonly IEntityManager _entityManager;
+ private readonly SpriteSystem _spriteSystem;
+ private readonly IPrototypeManager _prototypes;
+ private readonly IResourceCache _cache;
+
+ public HealthAnalyzerControl()
+ {
+ RobustXamlLoader.Load(this);
+
+ var dependencies = IoCManager.Instance!;
+ _entityManager = dependencies.Resolve();
+ _spriteSystem = _entityManager.System();
+ _prototypes = dependencies.Resolve();
+ _cache = dependencies.Resolve();
+ }
+
+ public void Populate(HealthAnalyzerUiState state)
+ {
+ var target = _entityManager.GetEntity(state.TargetEntity);
+
+ if (target == null
+ || !_entityManager.TryGetComponent(target, out var damageable))
+ {
+ NoPatientDataText.Visible = true;
+ return;
+ }
+
+ NoPatientDataText.Visible = false;
+
+ // Scan Mode
+
+ ScanModeLabel.Text = state.ScanMode.HasValue
+ ? state.ScanMode.Value
+ ? Loc.GetString("health-analyzer-window-scan-mode-active")
+ : Loc.GetString("health-analyzer-window-scan-mode-inactive")
+ : Loc.GetString("health-analyzer-window-entity-unknown-text");
+
+ ScanModeLabel.FontColorOverride = state.ScanMode.HasValue && state.ScanMode.Value ? Color.Green : Color.Red;
+
+ // Patient Information
+
+ SpriteView.SetEntity(target.Value);
+ SpriteView.Visible = state.ScanMode.HasValue && state.ScanMode.Value;
+ NoDataTex.Visible = !SpriteView.Visible;
+
+ var name = new FormattedMessage();
+ name.PushColor(Color.White);
+ name.AddText(_entityManager.HasComponent(target.Value)
+ ? Identity.Name(target.Value, _entityManager)
+ : Loc.GetString("health-analyzer-window-entity-unknown-text"));
+ NameLabel.SetMessage(name);
+
+ SpeciesLabel.Text =
+ _entityManager.TryGetComponent(target.Value,
+ out var humanoidAppearanceComponent)
+ ? Loc.GetString(_prototypes.Index(humanoidAppearanceComponent.Species).Name)
+ : Loc.GetString("health-analyzer-window-entity-unknown-species-text");
+
+ // Basic Diagnostic
+
+ TemperatureLabel.Text = !float.IsNaN(state.Temperature)
+ ? $"{state.Temperature - Atmospherics.T0C:F1} °C ({state.Temperature:F1} K)"
+ : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
+
+ BloodLabel.Text = !float.IsNaN(state.BloodLevel)
+ ? $"{state.BloodLevel * 100:F1} %"
+ : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
+
+ StatusLabel.Text =
+ _entityManager.TryGetComponent(target.Value, out var mobStateComponent)
+ ? GetStatus(mobStateComponent.CurrentState)
+ : Loc.GetString("health-analyzer-window-entity-unknown-text");
+
+ // Total Damage
+
+ DamageLabel.Text = damageable.TotalDamage.ToString();
+
+ // Alerts
+
+ var showAlerts = state.Unrevivable == true || state.Bleeding == true;
+
+ AlertsDivider.Visible = showAlerts;
+ AlertsContainer.Visible = showAlerts;
+
+ if (showAlerts)
+ AlertsContainer.RemoveAllChildren();
+
+ if (state.Unrevivable == true)
+ AlertsContainer.AddChild(new RichTextLabel
+ {
+ Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
+ Margin = new Thickness(0, 4),
+ MaxWidth = 300
+ });
+
+ if (state.Bleeding == true)
+ AlertsContainer.AddChild(new RichTextLabel
+ {
+ Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
+ Margin = new Thickness(0, 4),
+ MaxWidth = 300
+ });
+
+ // Damage Groups
+
+ var damageSortedGroups =
+ damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
+ .ToDictionary(x => x.Key, x => x.Value);
+
+ IReadOnlyDictionary damagePerType = damageable.Damage.DamageDict;
+
+ DrawDiagnosticGroups(damageSortedGroups, damagePerType);
+ }
+
+ private static string GetStatus(MobState mobState)
+ {
+ return mobState switch
+ {
+ MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
+ MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
+ MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
+ _ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
+ };
+ }
+
+ private void DrawDiagnosticGroups(
+ Dictionary groups,
+ IReadOnlyDictionary damageDict)
+ {
+ GroupsContainer.RemoveAllChildren();
+
+ foreach (var (damageGroupId, damageAmount) in groups)
+ {
+ if (damageAmount == 0)
+ continue;
+
+ var groupTitleText = $"{Loc.GetString(
+ "health-analyzer-window-damage-group-text",
+ ("damageGroup", _prototypes.Index(damageGroupId).LocalizedName),
+ ("amount", damageAmount)
+ )}";
+
+ var groupContainer = new BoxContainer
+ {
+ Align = AlignMode.Begin,
+ Orientation = LayoutOrientation.Vertical,
+ };
+
+ groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
+
+ GroupsContainer.AddChild(groupContainer);
+
+ // Show the damage for each type in that group.
+ var group = _prototypes.Index(damageGroupId);
+
+ foreach (var type in group.DamageTypes)
+ {
+ if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
+ continue;
+
+ var damageString = Loc.GetString(
+ "health-analyzer-window-damage-type-text",
+ ("damageType", _prototypes.Index(type).LocalizedName),
+ ("amount", typeAmount)
+ );
+
+ groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
+ }
+ }
+ }
+
+ private Texture GetTexture(string texture)
+ {
+ var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");
+ var rsiSprite = new SpriteSpecifier.Rsi(rsiPath, texture);
+
+ var rsi = _cache.GetResource(rsiSprite.RsiPath).RSI;
+ if (!rsi.TryGetState(rsiSprite.RsiState, out var state))
+ {
+ rsiSprite = new SpriteSpecifier.Rsi(rsiPath, "unknown");
+ }
+
+ return _spriteSystem.Frame0(rsiSprite);
+ }
+
+ private static Label CreateDiagnosticItemLabel(string text)
+ {
+ return new Label
+ {
+ Text = text,
+ };
+ }
+
+ private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
+ {
+ var rootContainer = new BoxContainer
+ {
+ Margin = new Thickness(0, 6, 0, 0),
+ VerticalAlignment = VAlignment.Bottom,
+ Orientation = LayoutOrientation.Horizontal,
+ };
+
+ rootContainer.AddChild(new TextureRect
+ {
+ SetSize = new Vector2(30, 30),
+ Texture = GetTexture(id.ToLower())
+ });
+
+ rootContainer.AddChild(CreateDiagnosticItemLabel(text));
+
+ return rootContainer;
+ }
+}
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
index 51e9badb8c..932592ed37 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
@@ -1,68 +1,15 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
index 56d2ced75c..6c0ed360b0 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
@@ -1,250 +1,20 @@
-using System.Linq;
-using System.Numerics;
-using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls;
-using Content.Shared.Damage.Components;
-using Content.Shared.Damage.Prototypes;
-using Content.Shared.Disease.Components; // Corvax-Wega-Disease
-using Content.Shared.FixedPoint;
-using Content.Shared.Humanoid;
-using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.IdentityManagement;
using Content.Shared.MedicalScanner;
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
-using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.ResourceManagement;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Utility;
-namespace Content.Client.HealthAnalyzer.UI
+namespace Content.Client.HealthAnalyzer.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class HealthAnalyzerWindow : FancyWindow
{
- [GenerateTypedNameReferences]
- public sealed partial class HealthAnalyzerWindow : FancyWindow
+ public HealthAnalyzerWindow()
{
- private readonly IEntityManager _entityManager;
- private readonly SpriteSystem _spriteSystem;
- private readonly IPrototypeManager _prototypes;
- private readonly IResourceCache _cache;
+ RobustXamlLoader.Load(this);
+ }
- public HealthAnalyzerWindow()
- {
- RobustXamlLoader.Load(this);
-
- var dependencies = IoCManager.Instance!;
- _entityManager = dependencies.Resolve();
- _spriteSystem = _entityManager.System();
- _prototypes = dependencies.Resolve();
- _cache = dependencies.Resolve();
- }
-
- public void Populate(HealthAnalyzerScannedUserMessage msg)
- {
- var target = _entityManager.GetEntity(msg.TargetEntity);
-
- if (target == null
- || !_entityManager.TryGetComponent(target, out var damageable))
- {
- NoPatientDataText.Visible = true;
- return;
- }
-
- NoPatientDataText.Visible = false;
-
- // Scan Mode
-
- ScanModeLabel.Text = msg.ScanMode.HasValue
- ? msg.ScanMode.Value
- ? Loc.GetString("health-analyzer-window-scan-mode-active")
- : Loc.GetString("health-analyzer-window-scan-mode-inactive")
- : Loc.GetString("health-analyzer-window-entity-unknown-text");
-
- ScanModeLabel.FontColorOverride = msg.ScanMode.HasValue && msg.ScanMode.Value ? Color.Green : Color.Red;
-
- // Patient Information
-
- SpriteView.SetEntity(target.Value);
- SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
- NoDataTex.Visible = !SpriteView.Visible;
-
- var name = new FormattedMessage();
- name.PushColor(Color.White);
- name.AddText(_entityManager.HasComponent(target.Value)
- ? Identity.Name(target.Value, _entityManager)
- : Loc.GetString("health-analyzer-window-entity-unknown-text"));
- NameLabel.SetMessage(name);
-
- SpeciesLabel.Text =
- _entityManager.TryGetComponent(target.Value,
- out var humanoidAppearanceComponent)
- ? Loc.GetString(_prototypes.Index(humanoidAppearanceComponent.Species).Name)
- : Loc.GetString("health-analyzer-window-entity-unknown-species-text");
-
- // Basic Diagnostic
-
- TemperatureLabel.Text = !float.IsNaN(msg.Temperature)
- ? $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)"
- : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
-
- BloodLabel.Text = !float.IsNaN(msg.BloodLevel)
- ? $"{msg.BloodLevel * 100:F1} %"
- : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
-
- StatusLabel.Text =
- _entityManager.TryGetComponent(target.Value, out var mobStateComponent)
- ? GetStatus(mobStateComponent.CurrentState)
- : Loc.GetString("health-analyzer-window-entity-unknown-text");
-
- // Total Damage
-
- DamageLabel.Text = damageable.TotalDamage.ToString();
-
- // Corvax-Wega-Disease-start
- // Status Effects / Components
- StatusEffectsLabel.Text =
- _entityManager.HasComponent(target)
- ? Loc.GetString("disease-scanner-diseased")
- : Loc.GetString("disease-scanner-not-diseased");
- // Corvax-Wega-Disease-end
-
- // Alerts
-
- var showAlerts = msg.Unrevivable == true || msg.Bleeding == true;
-
- AlertsDivider.Visible = showAlerts;
- AlertsContainer.Visible = showAlerts;
-
- if (showAlerts)
- AlertsContainer.RemoveAllChildren();
-
- if (msg.Unrevivable == true)
- AlertsContainer.AddChild(new RichTextLabel
- {
- Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
- Margin = new Thickness(0, 4),
- MaxWidth = 300
- });
-
- if (msg.Bleeding == true)
- AlertsContainer.AddChild(new RichTextLabel
- {
- Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
- Margin = new Thickness(0, 4),
- MaxWidth = 300
- });
-
- // Damage Groups
-
- var damageSortedGroups =
- damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
- .ToDictionary(x => x.Key, x => x.Value);
-
- IReadOnlyDictionary damagePerType = damageable.Damage.DamageDict;
-
- DrawDiagnosticGroups(damageSortedGroups, damagePerType);
- }
-
- private static string GetStatus(MobState mobState)
- {
- return mobState switch
- {
- MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
- MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
- MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
- _ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
- };
- }
-
- private void DrawDiagnosticGroups(
- Dictionary groups,
- IReadOnlyDictionary damageDict)
- {
- GroupsContainer.RemoveAllChildren();
-
- foreach (var (damageGroupId, damageAmount) in groups)
- {
- if (damageAmount == 0)
- continue;
-
- var groupTitleText = $"{Loc.GetString(
- "health-analyzer-window-damage-group-text",
- ("damageGroup", _prototypes.Index(damageGroupId).LocalizedName),
- ("amount", damageAmount)
- )}";
-
- var groupContainer = new BoxContainer
- {
- Align = BoxContainer.AlignMode.Begin,
- Orientation = BoxContainer.LayoutOrientation.Vertical,
- };
-
- groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
-
- GroupsContainer.AddChild(groupContainer);
-
- // Show the damage for each type in that group.
- var group = _prototypes.Index(damageGroupId);
-
- foreach (var type in group.DamageTypes)
- {
- if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
- continue;
-
- var damageString = Loc.GetString(
- "health-analyzer-window-damage-type-text",
- ("damageType", _prototypes.Index(type).LocalizedName),
- ("amount", typeAmount)
- );
-
- groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
- }
- }
- }
-
- private Texture GetTexture(string texture)
- {
- var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");
- var rsiSprite = new SpriteSpecifier.Rsi(rsiPath, texture);
-
- var rsi = _cache.GetResource(rsiSprite.RsiPath).RSI;
- if (!rsi.TryGetState(rsiSprite.RsiState, out var state))
- {
- rsiSprite = new SpriteSpecifier.Rsi(rsiPath, "unknown");
- }
-
- return _spriteSystem.Frame0(rsiSprite);
- }
-
- private static Label CreateDiagnosticItemLabel(string text)
- {
- return new Label
- {
- Text = text,
- };
- }
-
- private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
- {
- var rootContainer = new BoxContainer
- {
- Margin = new Thickness(0, 6, 0, 0),
- VerticalAlignment = VAlignment.Bottom,
- Orientation = BoxContainer.LayoutOrientation.Horizontal,
- };
-
- rootContainer.AddChild(new TextureRect
- {
- SetSize = new Vector2(30, 30),
- Texture = GetTexture(id.ToLower())
- });
-
- rootContainer.AddChild(CreateDiagnosticItemLabel(text));
-
- return rootContainer;
- }
+ public void Populate(HealthAnalyzerScannedUserMessage msg)
+ {
+ HealthAnalyzer.Populate(msg.State);
}
}
diff --git a/Content.Client/Info/InfoSection.xaml.cs b/Content.Client/Info/InfoSection.xaml.cs
index 9e10a4d7b4..95a74c72c7 100644
--- a/Content.Client/Info/InfoSection.xaml.cs
+++ b/Content.Client/Info/InfoSection.xaml.cs
@@ -18,7 +18,7 @@ public sealed partial class InfoSection : BoxContainer
{
TitleLabel.Text = title;
if (markup)
- Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
+ Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()), tagsAllowed: null);
else
Content.SetMessage(text);
}
diff --git a/Content.Client/Info/ServerInfo.cs b/Content.Client/Info/ServerInfo.cs
index 901fc91337..a28a3d4a6e 100644
--- a/Content.Client/Info/ServerInfo.cs
+++ b/Content.Client/Info/ServerInfo.cs
@@ -24,7 +24,7 @@ namespace Content.Client.Info
}
public void SetInfoBlob(string markup)
{
- _richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup));
+ _richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup), tagsAllowed: null);
}
}
}
diff --git a/Content.Client/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Client/Kitchen/EntitySystems/ReagentGrinderSystem.cs
new file mode 100644
index 0000000000..0aaa8ba8d8
--- /dev/null
+++ b/Content.Client/Kitchen/EntitySystems/ReagentGrinderSystem.cs
@@ -0,0 +1,7 @@
+using Content.Shared.Kitchen.EntitySystems;
+using JetBrains.Annotations;
+
+namespace Content.Client.Kitchen.EntitySystems;
+
+[UsedImplicitly]
+public sealed class ReagentGrinderSystem : SharedReagentGrinderSystem;
diff --git a/Content.Client/Launcher/LauncherConnecting.cs b/Content.Client/Launcher/LauncherConnecting.cs
index 33d31cc52d..9b9c472781 100644
--- a/Content.Client/Launcher/LauncherConnecting.cs
+++ b/Content.Client/Launcher/LauncherConnecting.cs
@@ -20,8 +20,10 @@ namespace Content.Client.Launcher
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IClipboardManager _clipboard = default!;
+ [Dependency] private readonly ILogManager _logManager = default!;
private LauncherConnectingGui? _control;
+ private ISawmill _sawmill = default!;
private Page _currentPage;
private string? _connectFailReason;
@@ -61,6 +63,8 @@ namespace Content.Client.Launcher
{
_control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg, _clipboard);
+ _sawmill = _logManager.GetSawmill("launcher-ui");
+
_userInterfaceManager.StateRoot.AddChild(_control);
_clientNetManager.ConnectFailed += OnConnectFailed;
@@ -115,12 +119,12 @@ namespace Content.Client.Launcher
}
else
{
- Logger.InfoS("launcher-ui", $"Redial not possible, no Ss14Address");
+ _sawmill.Info($"Redial not possible, no Ss14Address");
}
}
catch (Exception ex)
{
- Logger.ErrorS("launcher-ui", $"Redial exception: {ex}");
+ _sawmill.Error($"Redial exception: {ex}");
}
return false;
}
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
index 9b1e7d50f8..988ece124f 100644
--- a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
+++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
@@ -1,6 +1,5 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
-using Prometheus;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
diff --git a/Content.Client/Medical/Cryogenics/BeakerBarChart.cs b/Content.Client/Medical/Cryogenics/BeakerBarChart.cs
new file mode 100644
index 0000000000..25301b5268
--- /dev/null
+++ b/Content.Client/Medical/Cryogenics/BeakerBarChart.cs
@@ -0,0 +1,285 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+// ReSharper disable CompareOfFloatsByEqualityOperator
+
+namespace Content.Client.Medical.Cryogenics;
+
+
+public sealed class BeakerBarChart : Control
+{
+ private sealed class Entry
+ {
+ public float WidthFraction; // This entry's width as a fraction of the chart's total width (between 0 and 1)
+ public float TargetAmount;
+ public string Uid; // This UID is used to track entries between frames, for animation.
+ public string? Tooltip;
+ public Color Color;
+ public Label Label;
+
+ public Entry(string uid, Label label)
+ {
+ Uid = uid;
+ Label = label;
+ }
+ }
+
+ public float Capacity = 50;
+
+ public Color NotchColor = new(1, 1, 1, 0.25f);
+ public Color BackgroundColor = new(0.1f, 0.1f, 0.1f);
+
+ public int MediumNotchInterval = 5;
+ public int BigNotchInterval = 10;
+
+ // When we have a very large beaker (i.e. bluespace beaker) we might need to increase the distance between notches.
+ // The distance between notches is increased by ScaleMultiplier when the distance between notches is less than
+ // MinSmallNotchScreenDistance in UI units.
+ public int MinSmallNotchScreenDistance = 2;
+ public int ScaleMultiplier = 10;
+
+ public float SmallNotchHeight = 0.1f;
+ public float MediumNotchHeight = 0.25f;
+ public float BigNotchHeight = 1f;
+
+ // We don't animate new entries until this control has been drawn at least once.
+ private bool _hasBeenDrawn = false;
+
+ // This is used to keep the segments of the chart in the same order as the SetEntry calls.
+ // For example: In update 1 we might get cryox, alox, bic (in that order), and in update 2 we get alox, cryox, bic.
+ // To keep the order of the entries the same as the order of the SetEntry calls, we let the old cryox entry
+ // disappear and create a new cryox entry behind the alox entry.
+ private int _nextUpdateableEntry = 0;
+
+ private readonly List _entries = new();
+
+
+ public BeakerBarChart()
+ {
+ MouseFilter = MouseFilterMode.Pass;
+ TooltipSupplier = SupplyTooltip;
+ }
+
+ public void Clear()
+ {
+ foreach (var entry in _entries)
+ {
+ entry.TargetAmount = 0;
+ }
+
+ _nextUpdateableEntry = 0;
+ }
+
+ ///
+ /// Either adds a new entry to the chart if the UID doesn't appear yet, or updates the amount of an existing entry.
+ ///
+ public void SetEntry(
+ string uid,
+ string label,
+ float amount,
+ Color color,
+ Color? textColor = null,
+ string? tooltip = null)
+ {
+ // If we can find an old entry we're allowed to update, update that one.
+ if (TryFindUpdateableEntry(uid, out var index))
+ {
+ _entries[index].TargetAmount = amount;
+ _entries[index].Tooltip = tooltip;
+ _entries[index].Label.Text = label;
+ _nextUpdateableEntry = index + 1;
+ return;
+ }
+
+ // Otherwise create a new entry.
+ if (amount <= 0)
+ return;
+
+ // If no text color is provided, use either white or black depending on how dark the background is.
+ textColor ??= (color.R + color.G + color.B < 1.5f ? Color.White : Color.Black);
+
+ var childLabel = new Label
+ {
+ Text = label,
+ ClipText = true,
+ FontColorOverride = textColor,
+ Margin = new Thickness(4, 0, 0, 0)
+ };
+ AddChild(childLabel);
+
+ _entries.Insert(
+ _nextUpdateableEntry,
+ new Entry(uid, childLabel)
+ {
+ WidthFraction = (_hasBeenDrawn ? 0 : amount / Capacity),
+ TargetAmount = amount,
+ Tooltip = tooltip,
+ Color = color
+ }
+ );
+
+ _nextUpdateableEntry += 1;
+ }
+
+ private bool TryFindUpdateableEntry(string uid, out int index)
+ {
+ for (int i = _nextUpdateableEntry; i < _entries.Count; i++)
+ {
+ if (_entries[i].Uid == uid)
+ {
+ index = i;
+ return true;
+ }
+ }
+
+ index = -1;
+ return false;
+ }
+
+ private IEnumerable<(Entry, float xMin, float xMax)> EntryRanges(float? pixelWidth = null)
+ {
+ float chartWidth = pixelWidth ?? PixelWidth;
+ var xStart = 0f;
+
+ foreach (var entry in _entries)
+ {
+ var entryWidth = entry.WidthFraction * chartWidth;
+ var xEnd = MathF.Min(xStart + entryWidth, chartWidth);
+
+ yield return (entry, xStart, xEnd);
+
+ xStart = xEnd;
+ }
+ }
+
+ private bool TryFindEntry(float x, [NotNullWhen(true)] out Entry? entry)
+ {
+ foreach (var (currentEntry, xMin, xMax) in EntryRanges())
+ {
+ if (xMin <= x && x < xMax)
+ {
+ entry = currentEntry;
+ return true;
+ }
+ }
+
+ entry = null;
+ return false;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ // Tween the amounts to their target amounts.
+ const float tweenInverseHalfLife = 8; // Half life of tween is 1/n
+ var hasChanged = false;
+
+ foreach (var entry in _entries)
+ {
+ var targetWidthFraction = entry.TargetAmount / Capacity;
+
+ if (entry.WidthFraction == targetWidthFraction)
+ continue;
+
+ // Tween with lerp abuse interpolation
+ entry.WidthFraction = MathHelper.Lerp(
+ entry.WidthFraction,
+ targetWidthFraction,
+ MathHelper.Clamp01(tweenInverseHalfLife * args.DeltaSeconds)
+ );
+ hasChanged = true;
+
+ if (MathF.Abs(entry.WidthFraction - targetWidthFraction) < 0.0001f)
+ entry.WidthFraction = targetWidthFraction;
+ }
+
+ if (!hasChanged)
+ return;
+
+ InvalidateArrange();
+
+ // Remove old entries whose animations have finished.
+ foreach (var entry in _entries)
+ {
+ if (entry.WidthFraction == 0 && entry.TargetAmount == 0)
+ RemoveChild(entry.Label);
+ }
+
+ _entries.RemoveAll(entry => entry.WidthFraction == 0 && entry.TargetAmount == 0);
+ }
+
+ protected override void MouseMove(GUIMouseMoveEventArgs args)
+ {
+ HideTooltip();
+ }
+
+ protected override void Draw(DrawingHandleScreen handle)
+ {
+ handle.DrawRect(PixelSizeBox, BackgroundColor);
+
+ // Draw the entry backgrounds
+ foreach (var (entry, xMin, xMax) in EntryRanges())
+ {
+ if (xMin != xMax)
+ handle.DrawRect(new(xMin, 0, xMax, PixelHeight), entry.Color);
+ }
+
+ // Draw notches
+ var unitWidth = PixelWidth / Capacity;
+ var unitsPerNotch = 1;
+
+ while (unitWidth < MinSmallNotchScreenDistance)
+ {
+ // This is here for 1000u bluespace beakers. If the distance between small notches is so small that it would
+ // be very ugly, we reduce the amount of notches by ScaleMultiplier (currently a factor of 10).
+ // (I could use an analytical algorithm here, but it would be more difficult to read with pretty much no
+ // performance benefit, since it loops zero times normally and one time for the bluespace beaker)
+ unitWidth *= ScaleMultiplier;
+ unitsPerNotch *= ScaleMultiplier;
+ }
+
+ for (int i = 0; i <= Capacity / unitsPerNotch; i++)
+ {
+ var x = i * unitWidth;
+ var height = (i % BigNotchInterval == 0 ? BigNotchHeight :
+ i % MediumNotchInterval == 0 ? MediumNotchHeight :
+ SmallNotchHeight) * PixelHeight;
+ var start = new Vector2(x, PixelHeight);
+ var end = new Vector2(x, PixelHeight - height);
+ handle.DrawLine(start, end, NotchColor);
+ }
+
+ _hasBeenDrawn = true;
+ }
+
+ protected override Vector2 ArrangeOverride(Vector2 finalSize)
+ {
+ foreach (var (entry, xMin, xMax) in EntryRanges(finalSize.X))
+ {
+ entry.Label.Arrange(new((int)xMin, 0, (int)xMax, (int)finalSize.Y));
+ }
+
+ return finalSize;
+ }
+
+ private Control? SupplyTooltip(Control sender)
+ {
+ var globalMousePos = UserInterfaceManager.MousePositionScaled.Position;
+ var mousePos = globalMousePos - GlobalPosition;
+
+ if (!TryFindEntry(mousePos.X, out var entry) || entry.Tooltip == null)
+ return null;
+
+ var msg = new FormattedMessage();
+ msg.AddText(entry.Tooltip);
+
+ var tooltip = new Tooltip();
+ tooltip.SetMessage(msg);
+ return tooltip;
+ }
+}
diff --git a/Content.Client/Medical/Cryogenics/CryoPodBoundUserInterface.cs b/Content.Client/Medical/Cryogenics/CryoPodBoundUserInterface.cs
new file mode 100644
index 0000000000..5e64cea720
--- /dev/null
+++ b/Content.Client/Medical/Cryogenics/CryoPodBoundUserInterface.cs
@@ -0,0 +1,53 @@
+using Content.Shared.FixedPoint;
+using Content.Shared.Medical.Cryogenics;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+namespace Content.Client.Medical.Cryogenics;
+
+[UsedImplicitly]
+public sealed class CryoPodBoundUserInterface : BoundUserInterface
+{
+ private CryoPodWindow? _window;
+
+ public CryoPodBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ _window = this.CreateWindowCenteredLeft();
+ _window.Title = EntMan.GetComponent(Owner).EntityName;
+ _window.OnEjectPatientPressed += EjectPatientPressed;
+ _window.OnEjectBeakerPressed += EjectBeakerPressed;
+ _window.OnInjectPressed += InjectPressed;
+ }
+
+ private void EjectPatientPressed()
+ {
+ var isLocked =
+ EntMan.TryGetComponent(Owner, out var cryoComp)
+ && cryoComp.Locked;
+
+ _window?.SetEjectErrorVisible(isLocked);
+ SendMessage(new CryoPodSimpleUiMessage(CryoPodSimpleUiMessage.MessageType.EjectPatient));
+ }
+
+ private void EjectBeakerPressed()
+ {
+ SendMessage(new CryoPodSimpleUiMessage(CryoPodSimpleUiMessage.MessageType.EjectBeaker));
+ }
+
+ private void InjectPressed(FixedPoint2 transferAmount)
+ {
+ SendMessage(new CryoPodInjectUiMessage(transferAmount));
+ }
+
+ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+ {
+ if (_window != null && message is CryoPodUserMessage cryoMsg)
+ {
+ _window.Populate(cryoMsg);
+ }
+ }
+}
diff --git a/Content.Client/Medical/Cryogenics/CryoPodSystem.cs b/Content.Client/Medical/Cryogenics/CryoPodSystem.cs
index c1cbfc573e..63c95a63d8 100644
--- a/Content.Client/Medical/Cryogenics/CryoPodSystem.cs
+++ b/Content.Client/Medical/Cryogenics/CryoPodSystem.cs
@@ -6,7 +6,6 @@ namespace Content.Client.Medical.Cryogenics;
public sealed class CryoPodSystem : SharedCryoPodSystem
{
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
@@ -46,8 +45,8 @@ public sealed class CryoPodSystem : SharedCryoPodSystem
return;
}
- if (!_appearance.TryGetData(uid, CryoPodVisuals.ContainsEntity, out var isOpen, args.Component)
- || !_appearance.TryGetData(uid, CryoPodVisuals.IsOn, out var isOn, args.Component))
+ if (!Appearance.TryGetData(uid, CryoPodVisuals.ContainsEntity, out var isOpen, args.Component)
+ || !Appearance.TryGetData(uid, CryoPodVisuals.IsOn, out var isOn, args.Component))
{
return;
}
@@ -64,6 +63,11 @@ public sealed class CryoPodSystem : SharedCryoPodSystem
_sprite.LayerSetVisible((uid, args.Sprite), CryoPodVisualLayers.Cover, true);
}
}
+
+ protected override void UpdateUi(Entity cryoPod)
+ {
+ // Atmos and health scanner aren't predicted currently...
+ }
}
public enum CryoPodVisualLayers : byte
diff --git a/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml b/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml
new file mode 100644
index 0000000000..9bea37d582
--- /dev/null
+++ b/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml
@@ -0,0 +1,232 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs b/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs
new file mode 100644
index 0000000000..ad5ab9d9ea
--- /dev/null
+++ b/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs
@@ -0,0 +1,260 @@
+using System.Linq;
+using System.Numerics;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Atmos;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Damage.Components;
+using Content.Shared.EntityConditions.Conditions;
+using Content.Shared.FixedPoint;
+using Content.Shared.Medical.Cryogenics;
+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.Medical.Cryogenics;
+
+[GenerateTypedNameReferences]
+public sealed partial class CryoPodWindow : FancyWindow
+{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ public event Action? OnEjectPatientPressed;
+ public event Action? OnEjectBeakerPressed;
+ public event Action? OnInjectPressed;
+
+ public CryoPodWindow()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+ EjectPatientButton.OnPressed += _ => OnEjectPatientPressed?.Invoke();
+ EjectBeakerButton.OnPressed += _ => OnEjectBeakerPressed?.Invoke();
+ Inject1.OnPressed += _ => OnInjectPressed?.Invoke(1);
+ Inject5.OnPressed += _ => OnInjectPressed?.Invoke(5);
+ Inject10.OnPressed += _ => OnInjectPressed?.Invoke(10);
+ Inject20.OnPressed += _ => OnInjectPressed?.Invoke(20);
+ }
+
+ public void Populate(CryoPodUserMessage msg)
+ {
+ // Loading screen
+ if (LoadingPlaceHolder.Visible)
+ {
+ LoadingPlaceHolder.Visible = false;
+ Sections.Visible = true;
+ }
+
+ // Atmosphere
+ var hasCorrectPressure = (msg.GasMix.Pressure > Atmospherics.WarningLowPressure);
+ var hasGas = (msg.GasMix.Pressure > Atmospherics.GasMinMoles);
+ var showsPressureWarning = !hasCorrectPressure;
+ LowPressureWarning.Visible = showsPressureWarning;
+ Pressure.Text = Loc.GetString("gas-analyzer-window-pressure-val-text",
+ ("pressure", $"{msg.GasMix.Pressure:0.00}"));
+ Temperature.Text = Loc.GetString("generic-not-available-shorthand");
+
+ if (hasGas)
+ {
+ var celsius = TemperatureHelpers.KelvinToCelsius(msg.GasMix.Temperature);
+ Temperature.Text = Loc.GetString("gas-analyzer-window-temperature-val-text",
+ ("tempK", $"{msg.GasMix.Temperature:0.0}"),
+ ("tempC", $"{celsius:0.0}"));
+ }
+
+ // Gas mix segmented bar chart
+ GasMixChart.Clear();
+ GasMixChart.Visible = hasGas;
+
+ if (msg.GasMix.Gases != null)
+ {
+ var totalGasAmount = msg.GasMix.Gases.Sum(gas => gas.Amount);
+
+ foreach (var gas in msg.GasMix.Gases)
+ {
+ var color = Color.FromHex($"#{gas.Color}", Color.White);
+ var percent = gas.Amount / totalGasAmount * 100;
+ var localizedName = Loc.GetString(gas.Name);
+ var tooltip = Loc.GetString("gas-analyzer-window-molarity-percentage-text",
+ ("gasName", localizedName),
+ ("amount", $"{gas.Amount:0.##}"),
+ ("percentage", $"{percent:0.#}"));
+ GasMixChart.AddEntry(gas.Amount, color, tooltip: tooltip);
+ }
+ }
+
+ // Health analyzer
+ var maybePatient = _entityManager.GetEntity(msg.Health.TargetEntity);
+ var hasPatient = msg.Health.TargetEntity.HasValue;
+ var hasDamage = (hasPatient
+ && _entityManager.TryGetComponent(maybePatient, out DamageableComponent? damageable)
+ && damageable.TotalDamage > 0);
+
+ NoDamageText.Visible = (hasPatient && !hasDamage);
+ HealthSection.Visible = hasPatient;
+ EjectPatientButton.Disabled = !hasPatient;
+
+ if (hasPatient)
+ HealthAnalyzer.Populate(msg.Health);
+
+ // Reagents
+ float? lowestTempRequirement = null;
+ ReagentId? lowestTempReagent = null;
+ var totalBeakerCapacity = msg.BeakerCapacity ?? 0;
+ var availableQuantity = new FixedPoint2();
+ var injectingQuantity =
+ msg.Injecting?.Aggregate(new FixedPoint2(), (sum, r) => sum + r.Quantity)
+ ?? new FixedPoint2(); // Either the sum of the reagent quantities in `msg.Injecting` or zero.
+ var hasBeaker = (msg.Beaker != null);
+
+ ChemicalsChart.Clear();
+ ChemicalsChart.Capacity = (totalBeakerCapacity < 1 ? 50 : (int)totalBeakerCapacity);
+
+ var chartMaxChemsQuantity = ChemicalsChart.Capacity - injectingQuantity; // Ensure space for injection buffer
+
+ if (hasBeaker)
+ {
+ foreach (var (reagent, quantity) in msg.Beaker!)
+ {
+ availableQuantity += quantity;
+
+ // Make sure we don't add too many chemicals to the chart, so that there's still enough space to
+ // visualize the injection buffer.
+ var chemsQuantityOvershoot = FixedPoint2.Max(0, availableQuantity - chartMaxChemsQuantity);
+ var chartQuantity = FixedPoint2.Max(0, quantity - chemsQuantityOvershoot);
+
+ var reagentProto = _prototypeManager.Index(reagent.Prototype);
+ ChemicalsChart.SetEntry(
+ reagent.Prototype,
+ reagentProto.LocalizedName,
+ (float)chartQuantity,
+ reagentProto.SubstanceColor,
+ tooltip: $"{quantity}u {reagentProto.LocalizedName}"
+ );
+
+ var temp = TryFindMaxTemperatureRequirement(reagent);
+ if (lowestTempRequirement == null
+ || temp < lowestTempRequirement)
+ {
+ lowestTempRequirement = temp;
+ lowestTempReagent = reagent;
+ }
+ }
+ }
+
+ if (injectingQuantity != 0)
+ {
+ var injectingText = (injectingQuantity > 1 ? $"{injectingQuantity}u" : "");
+ ChemicalsChart.SetEntry(
+ "injecting",
+ injectingText,
+ (float)injectingQuantity,
+ Color.MediumSpringGreen,
+ tooltip: Loc.GetString("cryo-pod-window-chems-injecting-tooltip",
+ ("quantity", injectingQuantity))
+ );
+ }
+
+ var isBeakerEmpty = (injectingQuantity + availableQuantity == 0);
+ var isChemicalsChartVisible = (hasBeaker || injectingQuantity != 0);
+ NoBeakerText.Visible = !isChemicalsChartVisible;
+ ChemicalsChart.Visible = isChemicalsChartVisible;
+ Inject1.Disabled = (!hasPatient || availableQuantity < 0.1f);
+ Inject5.Disabled = (!hasPatient || availableQuantity <= 1);
+ Inject10.Disabled = (!hasPatient || availableQuantity <= 5);
+ Inject20.Disabled = (!hasPatient || availableQuantity <= 10);
+ EjectBeakerButton.Disabled = !hasBeaker;
+
+ // Temperature warning
+ var hasCorrectTemperature = (lowestTempRequirement == null || lowestTempRequirement > msg.GasMix.Temperature);
+ var showsTemperatureWarning = (!showsPressureWarning && !hasCorrectTemperature);
+
+ HighTemperatureWarning.Visible = showsTemperatureWarning;
+
+ if (showsTemperatureWarning)
+ {
+ var reagentName = _prototypeManager.Index(lowestTempReagent!.Value.Prototype)
+ .LocalizedName;
+ HighTemperatureWarningText.Text = Loc.GetString("cryo-pod-window-high-temperature-warning",
+ ("reagent", reagentName),
+ ("temperature", lowestTempRequirement!));
+ }
+
+ // Status checklist
+ const float fallbackTemperatureRequirement = 213;
+ var hasTemperatureCheck = (hasGas && hasCorrectTemperature
+ && (lowestTempRequirement != null || msg.GasMix.Temperature < fallbackTemperatureRequirement));
+ var hasChemicals = (hasBeaker && !isBeakerEmpty);
+
+ UpdateChecklistItem(PressureCheck, Loc.GetString("cryo-pod-window-checklist-pressure"), hasCorrectPressure);
+ UpdateChecklistItem(ChemicalsCheck, Loc.GetString("cryo-pod-window-checklist-chemicals"), hasChemicals);
+ UpdateChecklistItem(TemperatureCheck, Loc.GetString("cryo-pod-window-checklist-temperature"), hasTemperatureCheck);
+
+ var isReady = (hasCorrectPressure && hasChemicals && hasTemperatureCheck);
+ var isCooling = (lowestTempRequirement != null && hasPatient
+ && msg.Health.Temperature > lowestTempRequirement);
+ var isInjecting = (injectingQuantity > 0);
+ StatusLabel.Text = (!isReady ? Loc.GetString("cryo-pod-window-status-not-ready") :
+ isCooling ? Loc.GetString("cryo-pod-window-status-cooling") :
+ isInjecting ? Loc.GetString("cryo-pod-window-status-injecting") :
+ hasPatient ? Loc.GetString("cryo-pod-window-status-ready-to-inject") :
+ Loc.GetString("cryo-pod-window-status-ready-for-patient"));
+ StatusLabel.FontColorOverride = (isReady ? Color.DeepSkyBlue : Color.Orange);
+ }
+
+ private void UpdateChecklistItem(Label label, string text, bool isOkay)
+ {
+ label.Text = (isOkay ? text : Loc.GetString("cryo-pod-window-checklist-fail", ("item", text)));
+ label.FontColorOverride = (isOkay ? null : Color.Orange);
+ }
+
+ private float? TryFindMaxTemperatureRequirement(ReagentId reagent)
+ {
+ var reagentProto = _prototypeManager.Index(reagent.Prototype);
+ if (reagentProto.Metabolisms == null)
+ return null;
+
+ float? result = null;
+
+ foreach (var (_, metabolism) in reagentProto.Metabolisms)
+ {
+ foreach (var effect in metabolism.Effects)
+ {
+ if (effect.Conditions == null)
+ continue;
+
+ foreach (var condition in effect.Conditions)
+ {
+ // If there are multiple temperature conditions in the same reagent (which could hypothetically
+ // happen, although it currently doesn't), we return the lowest max temperature.
+ if (condition is TemperatureCondition tempCondition
+ && float.IsFinite(tempCondition.Max)
+ && (result == null || tempCondition.Max < result))
+ {
+ result = tempCondition.Max;
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public void SetEjectErrorVisible(bool isVisible)
+ {
+ EjectError.Visible = isVisible;
+ }
+
+ protected override Vector2 MeasureOverride(Vector2 availableSize)
+ {
+ const float antiJiggleSlackSpace = 80;
+ var oldSize = DesiredSize;
+ var newSize = base.MeasureOverride(availableSize);
+
+ // Reduce how often the height of the window jiggles
+ if (newSize.Y < oldSize.Y && newSize.Y + antiJiggleSlackSpace > oldSize.Y)
+ newSize.Y = oldSize.Y;
+
+ return newSize;
+ }
+}
diff --git a/Content.Client/Medical/DefibrillatorSystem.cs b/Content.Client/Medical/DefibrillatorSystem.cs
new file mode 100644
index 0000000000..834bcbcc5b
--- /dev/null
+++ b/Content.Client/Medical/DefibrillatorSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Medical;
+
+namespace Content.Client.Medical;
+
+public sealed class DefibrillatorSystem : SharedDefibrillatorSystem;
diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs
index 19da5aa959..cd57c3b5fb 100644
--- a/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs
+++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs
@@ -41,9 +41,9 @@ public sealed class NetworkConfiguratorLinkOverlay : Overlay
if (!Colors.TryGetValue(uid, out var color))
{
color = new Color(
- _random.Next(0, 255),
- _random.Next(0, 255),
- _random.Next(0, 255));
+ _random.NextByte(0, 255),
+ _random.NextByte(0, 255),
+ _random.NextByte(0, 255));
Colors.Add(uid, color);
}
diff --git a/Content.Client/Overlays/ShowHealthBarsSystem.cs b/Content.Client/Overlays/ShowHealthBarsSystem.cs
index 9fefe93094..a25e08d1fe 100644
--- a/Content.Client/Overlays/ShowHealthBarsSystem.cs
+++ b/Content.Client/Overlays/ShowHealthBarsSystem.cs
@@ -1,8 +1,6 @@
using Content.Shared.Inventory.Events;
using Content.Shared.Overlays;
using Robust.Client.Graphics;
-using System.Linq;
-using Robust.Client.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Overlays;
@@ -35,6 +33,9 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem x.DamageContainers))
+ DamageContainers.Clear();
+ foreach (var comp in component.Components)
{
- DamageContainers.Add(damageContainerId);
+ foreach (var damageContainerId in comp.DamageContainers)
+ {
+ DamageContainers.Add(damageContainerId);
+ }
}
}
diff --git a/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs b/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs
index 3d1eb1723c..49b383a7d7 100644
--- a/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs
+++ b/Content.Client/Pinpointer/UI/StationMapBoundUserInterface.cs
@@ -17,7 +17,11 @@ public sealed class StationMapBoundUserInterface : BoundUserInterface
base.Open();
EntityUid? gridUid = null;
- if (EntMan.TryGetComponent(Owner, out var xform))
+ if (EntMan.TryGetComponent(Owner, out var comp) && comp.TargetGrid != null)
+ {
+ gridUid = comp.TargetGrid;
+ }
+ else if (EntMan.TryGetComponent(Owner, out var xform))
{
gridUid = xform.GridUid;
}
@@ -30,8 +34,8 @@ public sealed class StationMapBoundUserInterface : BoundUserInterface
{
stationName = gridMetaData.EntityName;
}
-
- if (EntMan.TryGetComponent(Owner, out var comp) && comp.ShowLocation)
+
+ if (comp != null && comp.ShowLocation)
_window.Set(stationName, gridUid, Owner);
else
_window.Set(stationName, gridUid, null);
diff --git a/Content.Client/Power/Visualizers/PowerDeviceVisuals.cs b/Content.Client/Power/Visualizers/PowerDeviceVisuals.cs
index 5cc86d203d..057dabae5d 100644
--- a/Content.Client/Power/Visualizers/PowerDeviceVisuals.cs
+++ b/Content.Client/Power/Visualizers/PowerDeviceVisuals.cs
@@ -3,5 +3,6 @@ namespace Content.Client.Power;
/// Remains in use by portable scrubbers and lathes.
public enum PowerDeviceVisualLayers : byte
{
- Powered
+ Powered,
+ Charging
}
diff --git a/Content.Client/SSDIndicator/SSDIndicatorSystem.cs b/Content.Client/SSDIndicator/SSDIndicatorSystem.cs
index e731195317..370bc902c2 100644
--- a/Content.Client/SSDIndicator/SSDIndicatorSystem.cs
+++ b/Content.Client/SSDIndicator/SSDIndicatorSystem.cs
@@ -32,8 +32,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
_cfg.GetCVar(CCVars.ICShowSSDIndicator) &&
!_mobState.IsDead(uid) &&
!HasComp(uid) &&
- TryComp(uid, out var mindContainer) &&
- mindContainer.ShowExamineInfo)
+ HasComp(uid))
{
args.StatusIcons.Add(_prototype.Index(component.Icon));
}
diff --git a/Content.Client/Security/Ui/GenpopLockerBoundUserInterface.cs b/Content.Client/Security/Ui/GenpopLockerBoundUserInterface.cs
index a546fa6fc6..63276e1a90 100644
--- a/Content.Client/Security/Ui/GenpopLockerBoundUserInterface.cs
+++ b/Content.Client/Security/Ui/GenpopLockerBoundUserInterface.cs
@@ -16,7 +16,7 @@ public sealed class GenpopLockerBoundUserInterface(EntityUid owner, Enum uiKey)
_menu.OnConfigurationComplete += (name, time, crime) =>
{
- SendMessage(new GenpopLockerIdConfiguredMessage(name, time, crime));
+ SendPredictedMessage(new GenpopLockerIdConfiguredMessage(name, time, crime));
Close();
};
diff --git a/Content.Client/Shuttles/BUI/IFFConsoleBoundUserInterface.cs b/Content.Client/Shuttles/BUI/IFFConsoleBoundUserInterface.cs
index 8d84abed8a..704307f06b 100644
--- a/Content.Client/Shuttles/BUI/IFFConsoleBoundUserInterface.cs
+++ b/Content.Client/Shuttles/BUI/IFFConsoleBoundUserInterface.cs
@@ -23,7 +23,6 @@ public sealed class IFFConsoleBoundUserInterface : BoundUserInterface
_window = this.CreateWindowCenteredLeft();
_window.ShowIFF += SendIFFMessage;
- _window.ShowVessel += SendVesselMessage;
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -44,14 +43,6 @@ public sealed class IFFConsoleBoundUserInterface : BoundUserInterface
});
}
- private void SendVesselMessage(bool obj)
- {
- SendMessage(new IFFShowVesselMessage()
- {
- Show = obj,
- });
- }
-
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
diff --git a/Content.Client/Shuttles/UI/IFFConsoleWindow.xaml b/Content.Client/Shuttles/UI/IFFConsoleWindow.xaml
index dab11a7b62..90684889a6 100644
--- a/Content.Client/Shuttles/UI/IFFConsoleWindow.xaml
+++ b/Content.Client/Shuttles/UI/IFFConsoleWindow.xaml
@@ -9,12 +9,6 @@
-
-
-
-
-
-
diff --git a/Content.Client/Shuttles/UI/IFFConsoleWindow.xaml.cs b/Content.Client/Shuttles/UI/IFFConsoleWindow.xaml.cs
index f14ef22c3c..d61486d2d8 100644
--- a/Content.Client/Shuttles/UI/IFFConsoleWindow.xaml.cs
+++ b/Content.Client/Shuttles/UI/IFFConsoleWindow.xaml.cs
@@ -13,9 +13,7 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
IComputerWindow
{
private readonly ButtonGroup _showIFFButtonGroup = new();
- private readonly ButtonGroup _showVesselButtonGroup = new();
public event Action? ShowIFF;
- public event Action? ShowVessel;
public IFFConsoleWindow()
{
@@ -24,11 +22,6 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
ShowIFFOnButton.Group = _showIFFButtonGroup;
ShowIFFOnButton.OnPressed += args => ShowIFFPressed(true);
ShowIFFOffButton.OnPressed += args => ShowIFFPressed(false);
-
- ShowVesselOffButton.Group = _showVesselButtonGroup;
- ShowVesselOnButton.Group = _showVesselButtonGroup;
- ShowVesselOnButton.OnPressed += args => ShowVesselPressed(true);
- ShowVesselOffButton.OnPressed += args => ShowVesselPressed(false);
}
private void ShowIFFPressed(bool pressed)
@@ -36,19 +29,14 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
ShowIFF?.Invoke(pressed);
}
- private void ShowVesselPressed(bool pressed)
- {
- ShowVessel?.Invoke(pressed);
- }
-
public void UpdateState(IFFConsoleBoundUserInterfaceState state)
{
- if ((state.AllowedFlags & IFFFlags.HideLabel) != 0x0)
+ if ((state.AllowedFlags & IFFFlags.HideLabel) != 0x0 || (state.AllowedFlags & IFFFlags.Hide) != 0x0)
{
ShowIFFOffButton.Disabled = false;
ShowIFFOnButton.Disabled = false;
- if ((state.Flags & IFFFlags.HideLabel) != 0x0)
+ if ((state.Flags & IFFFlags.HideLabel) != 0x0 || (state.Flags & IFFFlags.Hide) != 0x0)
{
ShowIFFOffButton.Pressed = true;
}
@@ -62,25 +50,5 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
ShowIFFOffButton.Disabled = true;
ShowIFFOnButton.Disabled = true;
}
-
- if ((state.AllowedFlags & IFFFlags.Hide) != 0x0)
- {
- ShowVesselOffButton.Disabled = false;
- ShowVesselOnButton.Disabled = false;
-
- if ((state.Flags & IFFFlags.Hide) != 0x0)
- {
- ShowVesselOffButton.Pressed = true;
- }
- else
- {
- ShowVesselOnButton.Pressed = true;
- }
- }
- else
- {
- ShowVesselOffButton.Disabled = true;
- ShowVesselOnButton.Disabled = true;
- }
}
}
diff --git a/Content.Client/Shuttles/UI/MapScreen.xaml.cs b/Content.Client/Shuttles/UI/MapScreen.xaml.cs
index 72ad3c28b1..ff55c0b462 100644
--- a/Content.Client/Shuttles/UI/MapScreen.xaml.cs
+++ b/Content.Client/Shuttles/UI/MapScreen.xaml.cs
@@ -153,7 +153,7 @@ public sealed partial class MapScreen : BoxContainer
break;
}
- if (IsFTLBlocked())
+ if (IsPingBlocked())
{
MapRebuildButton.Disabled = true;
ClearMapObjects();
@@ -408,9 +408,21 @@ public sealed partial class MapScreen : BoxContainer
}
}
+ ///
+ /// Returns true if we shouldn't be able to select the Scan for Objects button.
+ ///
+ private bool IsPingBlocked()
+ {
+ return _state switch
+ {
+ FTLState.Available or FTLState.Cooldown => false,
+ _ => true,
+ };
+ }
+
private void OnMapObjectPress(IMapObject mapObject)
{
- if (IsFTLBlocked())
+ if (IsPingBlocked())
return;
var coordinates = _shuttles.GetMapCoordinates(mapObject);
@@ -506,7 +518,7 @@ public sealed partial class MapScreen : BoxContainer
BumpMapDequeue();
}
- if (!IsFTLBlocked() && _nextPing < curTime)
+ if (!IsPingBlocked() && _nextPing < curTime)
{
MapRebuildButton.Disabled = false;
}
diff --git a/Content.Client/Silicons/Borgs/BorgMenu.xaml.cs b/Content.Client/Silicons/Borgs/BorgMenu.xaml.cs
index 0acc3a2646..eee45bacc8 100644
--- a/Content.Client/Silicons/Borgs/BorgMenu.xaml.cs
+++ b/Content.Client/Silicons/Borgs/BorgMenu.xaml.cs
@@ -21,7 +21,7 @@ public sealed partial class BorgMenu : FancyWindow
[Dependency] private readonly IEntityManager _entity = default!;
private readonly NameModifierSystem _nameModifier;
private readonly PowerCellSystem _powerCell;
- private readonly PredictedBatterySystem _battery;
+ private readonly SharedBatterySystem _battery;
public Action? BrainButtonPressed;
public Action? EjectBatteryButtonPressed;
@@ -44,7 +44,7 @@ public sealed partial class BorgMenu : FancyWindow
_nameModifier = _entity.System();
_powerCell = _entity.System();
- _battery = _entity.System();
+ _battery = _entity.System();
_maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength);
diff --git a/Content.Client/Silicons/Borgs/BorgModuleControl.xaml b/Content.Client/Silicons/Borgs/BorgModuleControl.xaml
index 2f6e25f983..1be7cea8d9 100644
--- a/Content.Client/Silicons/Borgs/BorgModuleControl.xaml
+++ b/Content.Client/Silicons/Borgs/BorgModuleControl.xaml
@@ -3,10 +3,10 @@
StyleClasses="PanelLight"
Margin="5 5 5 0">
+
-
diff --git a/Content.Client/Silicons/Borgs/BorgSystem.cs b/Content.Client/Silicons/Borgs/BorgSystem.cs
index a4ca3725dc..517027d082 100644
--- a/Content.Client/Silicons/Borgs/BorgSystem.cs
+++ b/Content.Client/Silicons/Borgs/BorgSystem.cs
@@ -18,7 +18,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
- [Dependency] private readonly PredictedBatterySystem _battery = default!;
+ [Dependency] private readonly SharedBatterySystem _battery = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;
diff --git a/Content.Client/SmartFridge/SmartFridgeBoundUserInterface.cs b/Content.Client/SmartFridge/SmartFridgeBoundUserInterface.cs
index 60d8ea413a..a364c8e6b9 100644
--- a/Content.Client/SmartFridge/SmartFridgeBoundUserInterface.cs
+++ b/Content.Client/SmartFridge/SmartFridgeBoundUserInterface.cs
@@ -19,12 +19,13 @@ public sealed class SmartFridgeBoundUserInterface : BoundUserInterface
_menu = this.CreateWindow();
_menu.OnItemSelected += OnItemSelected;
+ _menu.OnRemoveButtonPressed += data => SendPredictedMessage(new SmartFridgeRemoveEntryMessage(data.Entry));
Refresh();
}
public void Refresh()
{
- if (_menu is not {} menu || !EntMan.TryGetComponent(Owner, out SmartFridgeComponent? fridge))
+ if (_menu is not { } menu || !EntMan.TryGetComponent(Owner, out SmartFridgeComponent? fridge))
return;
menu.SetFlavorText(Loc.GetString(fridge.FlavorText));
diff --git a/Content.Client/SmartFridge/SmartFridgeItem.xaml b/Content.Client/SmartFridge/SmartFridgeItem.xaml
index 3960d7ce42..17eb89801e 100644
--- a/Content.Client/SmartFridge/SmartFridgeItem.xaml
+++ b/Content.Client/SmartFridge/SmartFridgeItem.xaml
@@ -13,4 +13,10 @@
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"/>
+
diff --git a/Content.Client/SmartFridge/SmartFridgeItem.xaml.cs b/Content.Client/SmartFridge/SmartFridgeItem.xaml.cs
index c69d2e7de2..1d1403878d 100644
--- a/Content.Client/SmartFridge/SmartFridgeItem.xaml.cs
+++ b/Content.Client/SmartFridge/SmartFridgeItem.xaml.cs
@@ -8,11 +8,18 @@ namespace Content.Client.SmartFridge;
[GenerateTypedNameReferences]
public sealed partial class SmartFridgeItem : BoxContainer
{
+ public Action? RemoveButtonPressed;
+
public SmartFridgeItem(EntityUid uid, string text)
{
RobustXamlLoader.Load(this);
EntityView.SetEntity(uid);
NameLabel.Text = text;
+
+ RemoveButton.OnPressed += _ => RemoveButtonPressed?.Invoke();
+
+ if (uid.IsValid())
+ RemoveButton.Visible = false;
}
}
diff --git a/Content.Client/SmartFridge/SmartFridgeMenu.xaml.cs b/Content.Client/SmartFridge/SmartFridgeMenu.xaml.cs
index c896e7fada..ee3b3a8e7a 100644
--- a/Content.Client/SmartFridge/SmartFridgeMenu.xaml.cs
+++ b/Content.Client/SmartFridge/SmartFridgeMenu.xaml.cs
@@ -17,6 +17,7 @@ public sealed partial class SmartFridgeMenu : FancyWindow
[Dependency] private readonly IEntityManager _entityManager = default!;
public event Action? OnItemSelected;
+ public event Action? OnRemoveButtonPressed;
private readonly StyleBoxFlat _styleBox = new() { BackgroundColor = new Color(70, 73, 102) };
@@ -48,8 +49,10 @@ public sealed partial class SmartFridgeMenu : FancyWindow
return;
var label = Loc.GetString("smart-fridge-list-item", ("item", entry.Entry.Name), ("amount", entry.Amount));
- button.AddChild(new SmartFridgeItem(entry.Representative, label));
+ var item = new SmartFridgeItem(entry.Representative, label);
+ item.RemoveButtonPressed += () => OnRemoveButtonPressed?.Invoke(entry);
+ button.AddChild(item);
button.ToolTip = label;
button.StyleBoxOverride = _styleBox;
}
diff --git a/Content.Client/SmartFridge/SmartFridgeSystem.cs b/Content.Client/SmartFridge/SmartFridgeSystem.cs
new file mode 100644
index 0000000000..cd3aeb6958
--- /dev/null
+++ b/Content.Client/SmartFridge/SmartFridgeSystem.cs
@@ -0,0 +1,18 @@
+using Content.Shared.SmartFridge;
+
+namespace Content.Client.SmartFridge;
+
+public sealed class SmartFridgeSystem : SharedSmartFridgeSystem
+{
+ [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
+
+ protected override void UpdateUI(Entity ent)
+ {
+ base.UpdateUI(ent);
+
+ if (!_uiSystem.TryGetOpenUi(ent.Owner, SmartFridgeUiKey.Key, out var bui))
+ return;
+
+ bui.Refresh();
+ }
+}
diff --git a/Content.Client/SmartFridge/SmartFridgeUISystem.cs b/Content.Client/SmartFridge/SmartFridgeUISystem.cs
deleted file mode 100644
index 4068c27e05..0000000000
--- a/Content.Client/SmartFridge/SmartFridgeUISystem.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Shared.SmartFridge;
-using Robust.Shared.Analyzers;
-
-namespace Content.Client.SmartFridge;
-
-public sealed class SmartFridgeUISystem : EntitySystem
-{
- [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnSmartFridgeAfterState);
- }
-
- private void OnSmartFridgeAfterState(Entity ent, ref AfterAutoHandleStateEvent args)
- {
- if (!_uiSystem.TryGetOpenUi(ent.Owner, SmartFridgeUiKey.Key, out var bui))
- return;
-
- bui.Refresh();
- }
-}
diff --git a/Content.Client/Stylesheets/Sheetlets/LabelSheetlet.cs b/Content.Client/Stylesheets/Sheetlets/LabelSheetlet.cs
index 77c554a9f3..fc23f9c7fb 100644
--- a/Content.Client/Stylesheets/Sheetlets/LabelSheetlet.cs
+++ b/Content.Client/Stylesheets/Sheetlets/LabelSheetlet.cs
@@ -69,10 +69,10 @@ public sealed class LabelSheetlet : Sheetlet
.Class(StyleClass.LabelMonospaceText)
.Prop(Label.StylePropertyFont, robotoMonoBold11),
E