diff --git a/.editorconfig b/.editorconfig
index 872a068c7c..a5dfab07a5 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,4 +1,5 @@
root = true
+
[*]
charset = utf-8
@@ -12,6 +13,7 @@ tab_width = 4
#end_of_line = crlf
insert_final_newline = true
trim_trailing_whitespace = true
+max_line_length = 120
#### .NET Coding Conventions ####
@@ -277,7 +279,7 @@ dotnet_naming_style.t_upper_camel_case_style.capitalization = pascal_case
dotnet_naming_style.t_upper_camel_case_style.required_prefix = T
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
-dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.constants_symbols.applicable_kinds = field
dotnet_naming_symbols.constants_symbols.required_modifiers = const
@@ -316,27 +318,32 @@ dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
-dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
+dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly
dotnet_naming_symbols.property_symbols.applicable_accessibilities = *
dotnet_naming_symbols.property_symbols.applicable_kinds = property
-dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field
-dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field
-dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly
+dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = *
-dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace,class,struct,enum,delegate
+dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace, class, struct, enum, delegate
dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = *
dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter
# ReSharper properties
resharper_braces_for_ifelse = required_for_multiline
+resharper_csharp_wrap_arguments_style = chop_if_long
+resharper_csharp_wrap_parameters_style = chop_if_long
resharper_keep_existing_attribute_arrangement = true
+resharper_wrap_chained_binary_patterns = chop_if_long
+resharper_wrap_chained_method_calls = chop_if_long
+resharper_csharp_trailing_comma_in_multiline_lists = true
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
indent_size = 2
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 69c5f0138f..da9d4d693a 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -15,6 +15,7 @@
/Content.*/GameTicking/ @moonheart08 @EmoGarbage404
/Resources/ServerInfo/ @moonheart08 @Chief-Engineer
/Resources/ServerInfo/Guidebook/ @moonheart08 @EmoGarbage404
+/Resources/ServerInfo/Guidebook/ServerRules/ @Chief-Engineer
/Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer
@@ -23,11 +24,12 @@
/Resources/Prototypes/Body/ @DrSmugleaf # suffering
/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf
/Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf
+/Resources/Prototypes/Guidebook/rules.yml @Chief-Engineer
/Content.*/Body/ @DrSmugleaf
/Content.YAMLLinter @DrSmugleaf
/Content.Shared/Damage/ @DrSmugleaf
-/Content.*/Anomaly/ @EmoGarbage404
+/Content.*/Anomaly/ @EmoGarbage404 @TheShuEd
/Content.*/Lathe/ @EmoGarbage404
/Content.*/Materials/ @EmoGarbage404
/Content.*/Mech/ @EmoGarbage404
@@ -35,7 +37,7 @@
/Content.*/Stack/ @EmoGarbage404
/Content.*/Xenoarchaeology/ @EmoGarbage404
/Content.*/Zombies/ @EmoGarbage404
-/Resources/Prototypes/Entities/Structures/Specific/anomalies.yml @EmoGarbage404
+/Resources/Prototypes/Entities/Structures/Specific/anomalies.yml @EmoGarbage404 @TheShuEd
/Resources/Prototypes/Research/ @EmoGarbage404
/Content.*/Forensics/ @ficcialfaint
diff --git a/.github/labeler.yml b/.github/labeler.yml
index dce5445bc2..6cbf8a4f73 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,15 +1,18 @@
-"sprites":
-- '**/*.rsi/*.png'
+"sprites":
+- changed-files:
+ - any-glob-to-any-file: '**/*.rsi/*.png'
"map":
-- 'Resources/Maps/*.yml'
-- 'Resources/Prototypes/Maps/*.yml'
+- changed-files:
+ - any-glob-to-any-file:
+ - 'Resources/Maps/*.yml'
+ - 'Resources/Prototypes/Maps/*.yml'
"ui":
-- '**/*.xaml*'
+- changed-files:
+ - any-glob-to-any-file: '**/*.xaml*'
"no C#":
-- all: ["!**/*.cs"]
-
-"ctv pr":
-- base-branch: ["cross-the-veil"]
+- changed-files:
+ # Equiv to any-glob-to-all as long as this has one matcher. If ALL changed files are not C# files, then apply label.
+ - all-globs-to-all-files: "!**/*.cs"
diff --git a/.github/workflows/check-crlf.yml b/.github/workflows/check-crlf.yml
new file mode 100644
index 0000000000..0afcab734f
--- /dev/null
+++ b/.github/workflows/check-crlf.yml
@@ -0,0 +1,15 @@
+name: CRLF Check
+
+on:
+ pull_request:
+ types: [ opened, reopened, synchronize, ready_for_review ]
+
+jobs:
+ build:
+ name: CRLF Check
+ if: github.event.pull_request.draft == false
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3.6.0
+ - name: Check for CRLF
+ run: Tools/check_crlf.py
diff --git a/.github/workflows/close-master-pr.yml b/.github/workflows/close-master-pr.yml
index 66843d35dd..51ce874dd0 100644
--- a/.github/workflows/close-master-pr.yml
+++ b/.github/workflows/close-master-pr.yml
@@ -1,4 +1,4 @@
-name: Close PR's on master
+name: Close PRs on master
on:
pull_request_target:
diff --git a/.github/workflows/conflict-labeler.yml b/.github/workflows/conflict-labeler.yml
index a78716bde6..1e2125c30a 100644
--- a/.github/workflows/conflict-labeler.yml
+++ b/.github/workflows/conflict-labeler.yml
@@ -1,18 +1,20 @@
name: Check Merge Conflicts
on:
- push:
- branches:
- - master
pull_request_target:
+ types:
+ - opened
+ - synchronize
+ - reopened
+ - ready_for_review
jobs:
Label:
- if: github.actor != 'PJBot'
+ if: ( github.event.pull_request.draft == false ) && ( github.actor != 'PJBot' )
runs-on: ubuntu-latest
steps:
- name: Check for Merge Conflicts
- uses: ike709/actions-label-merge-conflict@9eefdd17e10566023c46d2dc6dc04fcb8ec76142
+ uses: eps1lon/actions-label-merge-conflict@v3.0.0
with:
dirtyLabel: "Merge Conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/labeler-pr.yml b/.github/workflows/labeler-pr.yml
index cdc532d8ee..3130994cdd 100644
--- a/.github/workflows/labeler-pr.yml
+++ b/.github/workflows/labeler-pr.yml
@@ -6,11 +6,12 @@ on:
jobs:
labeler:
if: github.actor != 'PJBot'
+ permissions:
+ contents: read
+ pull-requests: write
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- - uses: actions/labeler@v3
- with:
- repo-token: "${{ secrets.GITHUB_TOKEN }}"
+ - uses: actions/labeler@v5
diff --git a/.github/workflows/labeler-untriaged.yml b/.github/workflows/labeler-untriaged.yml
new file mode 100644
index 0000000000..630122aa08
--- /dev/null
+++ b/.github/workflows/labeler-untriaged.yml
@@ -0,0 +1,13 @@
+name: "Labels: Untriaged"
+
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ add_label:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions-ecosystem/action-add-labels@v1
+ with:
+ labels: "Status: Untriaged"
diff --git a/Content.Benchmarks/ComponentQueryBenchmark.cs b/Content.Benchmarks/ComponentQueryBenchmark.cs
new file mode 100644
index 0000000000..11c7ab9d5f
--- /dev/null
+++ b/Content.Benchmarks/ComponentQueryBenchmark.cs
@@ -0,0 +1,273 @@
+#nullable enable
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using Content.IntegrationTests;
+using Content.IntegrationTests.Pair;
+using Content.Shared.Clothing.Components;
+using Content.Shared.Doors.Components;
+using Content.Shared.Item;
+using Robust.Server.GameObjects;
+using Robust.Shared;
+using Robust.Shared.Analyzers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Random;
+
+namespace Content.Benchmarks;
+
+///
+/// Benchmarks for comparing the speed of various component fetching/lookup related methods, including directed event
+/// subscriptions
+///
+[Virtual]
+[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
+[CategoriesColumn]
+public class ComponentQueryBenchmark
+{
+ public const string Map = "Maps/atlas.yml";
+
+ private TestPair _pair = default!;
+ private IEntityManager _entMan = default!;
+ private MapId _mapId = new(10);
+ private EntityQuery _itemQuery;
+ private EntityQuery _clothingQuery;
+ private EntityQuery _mapQuery;
+ private EntityUid[] _items = default!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ ProgramShared.PathOffset = "../../../../";
+ PoolManager.Startup(typeof(QueryBenchSystem).Assembly);
+
+ _pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
+ _entMan = _pair.Server.ResolveDependency();
+
+ _itemQuery = _entMan.GetEntityQuery();
+ _clothingQuery = _entMan.GetEntityQuery();
+ _mapQuery = _entMan.GetEntityQuery();
+
+ _pair.Server.ResolveDependency().SetSeed(42);
+ _pair.Server.WaitPost(() =>
+ {
+ var success = _entMan.System().TryLoad(_mapId, Map, out _);
+ if (!success)
+ throw new Exception("Map load failed");
+ _pair.Server.MapMan.DoMapInitialize(_mapId);
+ }).GetAwaiter().GetResult();
+
+ _items = new EntityUid[_entMan.Count()];
+ var i = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out var uid, out _))
+ {
+ _items[i++] = uid;
+ }
+ }
+
+ [GlobalCleanup]
+ public async Task Cleanup()
+ {
+ await _pair.DisposeAsync();
+ PoolManager.Shutdown();
+ }
+
+ #region TryComp
+
+ ///
+ /// Baseline TryComp benchmark. When the benchmark was created, around 40% of the items were clothing.
+ ///
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("TryComp")]
+ public int TryComp()
+ {
+ var hashCode = 0;
+ foreach (var uid in _items)
+ {
+ if (_clothingQuery.TryGetComponent(uid, out var clothing))
+ hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
+ }
+ return hashCode;
+ }
+
+ ///
+ /// Variant of that is meant to always fail to get a component.
+ ///
+ [Benchmark]
+ [BenchmarkCategory("TryComp")]
+ public int TryCompFail()
+ {
+ var hashCode = 0;
+ foreach (var uid in _items)
+ {
+ if (_mapQuery.TryGetComponent(uid, out var map))
+ hashCode = HashCode.Combine(hashCode, map.GetHashCode());
+ }
+ return hashCode;
+ }
+
+ ///
+ /// Variant of that is meant to always succeed getting a component.
+ ///
+ [Benchmark]
+ [BenchmarkCategory("TryComp")]
+ public int TryCompSucceed()
+ {
+ var hashCode = 0;
+ foreach (var uid in _items)
+ {
+ if (_itemQuery.TryGetComponent(uid, out var item))
+ hashCode = HashCode.Combine(hashCode, item.GetHashCode());
+ }
+ return hashCode;
+ }
+
+ ///
+ /// Variant of that uses `Resolve()` to try get the component.
+ ///
+ [Benchmark]
+ [BenchmarkCategory("TryComp")]
+ public int Resolve()
+ {
+ var hashCode = 0;
+ foreach (var uid in _items)
+ {
+ DoResolve(uid, ref hashCode);
+ }
+ return hashCode;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void DoResolve(EntityUid uid, ref int hash, ClothingComponent? clothing = null)
+ {
+ if (_clothingQuery.Resolve(uid, ref clothing, false))
+ hash = HashCode.Combine(hash, clothing.GetHashCode());
+ }
+
+ #endregion
+
+ #region Enumeration
+
+ [Benchmark]
+ [BenchmarkCategory("Item Enumerator")]
+ public int SingleItemEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out var item))
+ {
+ hashCode = HashCode.Combine(hashCode, item.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ [BenchmarkCategory("Item Enumerator")]
+ public int DoubleItemEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out _, out var item))
+ {
+ hashCode = HashCode.Combine(hashCode, item.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ [BenchmarkCategory("Item Enumerator")]
+ public int TripleItemEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out _, out _, out var xform))
+ {
+ hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ [BenchmarkCategory("Airlock Enumerator")]
+ public int SingleAirlockEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out var airlock))
+ {
+ hashCode = HashCode.Combine(hashCode, airlock.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ [BenchmarkCategory("Airlock Enumerator")]
+ public int DoubleAirlockEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out _, out var door))
+ {
+ hashCode = HashCode.Combine(hashCode, door.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ [Benchmark]
+ [BenchmarkCategory("Airlock Enumerator")]
+ public int TripleAirlockEnumerator()
+ {
+ var hashCode = 0;
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ while (enumerator.MoveNext(out _, out _, out var xform))
+ {
+ hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
+ }
+
+ return hashCode;
+ }
+
+ #endregion
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("Events")]
+ public int StructEvents()
+ {
+ var ev = new QueryBenchEvent();
+ foreach (var uid in _items)
+ {
+ _entMan.EventBus.RaiseLocalEvent(uid, ref ev);
+ }
+
+ return ev.HashCode;
+ }
+}
+
+[ByRefEvent]
+public struct QueryBenchEvent
+{
+ public int HashCode;
+}
+
+public sealed class QueryBenchSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnEvent);
+ }
+
+ private void OnEvent(EntityUid uid, ClothingComponent component, ref QueryBenchEvent args)
+ {
+ args.HashCode = HashCode.Combine(args.HashCode, component.GetHashCode());
+ }
+}
diff --git a/Content.Benchmarks/EntityManagerGetAllComponents.cs b/Content.Benchmarks/EntityManagerGetAllComponents.cs
index 0b9683a4ab..8e02b8d71d 100644
--- a/Content.Benchmarks/EntityManagerGetAllComponents.cs
+++ b/Content.Benchmarks/EntityManagerGetAllComponents.cs
@@ -47,6 +47,7 @@ public void Setup()
var componentFactory = new Mock();
componentFactory.Setup(p => p.GetComponent()).Returns(new DummyComponent());
+ componentFactory.Setup(m => m.GetIndex(typeof(DummyComponent))).Returns(CompIdx.Index());
componentFactory.Setup(p => p.GetRegistration(It.IsAny())).Returns(dummyReg);
componentFactory.Setup(p => p.GetAllRegistrations()).Returns(new[] { dummyReg });
componentFactory.Setup(p => p.GetAllRefTypes()).Returns(new[] { CompIdx.Index() });
diff --git a/Content.Benchmarks/EntityQueryBenchmark.cs b/Content.Benchmarks/EntityQueryBenchmark.cs
deleted file mode 100644
index cef6a5e35c..0000000000
--- a/Content.Benchmarks/EntityQueryBenchmark.cs
+++ /dev/null
@@ -1,137 +0,0 @@
-#nullable enable
-using System;
-using System.Threading.Tasks;
-using BenchmarkDotNet.Attributes;
-using Content.IntegrationTests;
-using Content.IntegrationTests.Pair;
-using Content.Shared.Clothing.Components;
-using Content.Shared.Item;
-using Robust.Server.GameObjects;
-using Robust.Shared;
-using Robust.Shared.Analyzers;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Map;
-using Robust.Shared.Random;
-
-namespace Content.Benchmarks;
-
-[Virtual]
-public class EntityQueryBenchmark
-{
- public const string Map = "Maps/atlas.yml";
-
- private TestPair _pair = default!;
- private IEntityManager _entMan = default!;
- private MapId _mapId = new MapId(10);
- private EntityQuery _clothingQuery;
-
- [GlobalSetup]
- public void Setup()
- {
- ProgramShared.PathOffset = "../../../../";
- PoolManager.Startup(null);
-
- _pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
- _entMan = _pair.Server.ResolveDependency();
-
- _pair.Server.ResolveDependency().SetSeed(42);
- _pair.Server.WaitPost(() =>
- {
- var success = _entMan.System().TryLoad(_mapId, Map, out _);
- if (!success)
- throw new Exception("Map load failed");
- _pair.Server.MapMan.DoMapInitialize(_mapId);
- }).GetAwaiter().GetResult();
-
- _clothingQuery = _entMan.GetEntityQuery();
-
- // Apparently ~40% of entities are items, and 1 in 6 of those are clothing.
- /*
- var entCount = _entMan.EntityCount;
- var itemCount = _entMan.Count();
- var clothingCount = _entMan.Count();
- var itemRatio = (float) itemCount / entCount;
- var clothingRatio = (float) clothingCount / entCount;
- Console.WriteLine($"Entities: {entCount}. Items: {itemRatio:P2}. Clothing: {clothingRatio:P2}.");
- */
- }
-
- [GlobalCleanup]
- public async Task Cleanup()
- {
- await _pair.DisposeAsync();
- PoolManager.Shutdown();
- }
-
- [Benchmark]
- public int HasComponent()
- {
- var hashCode = 0;
- var enumerator = _entMan.AllEntityQueryEnumerator();
- while (enumerator.MoveNext(out var uid, out var _))
- {
- if (_entMan.HasComponent(uid))
- hashCode = HashCode.Combine(hashCode, uid.Id);
- }
-
- return hashCode;
- }
-
- [Benchmark]
- public int HasComponentQuery()
- {
- var hashCode = 0;
- var enumerator = _entMan.AllEntityQueryEnumerator();
- while (enumerator.MoveNext(out var uid, out var _))
- {
- if (_clothingQuery.HasComponent(uid))
- hashCode = HashCode.Combine(hashCode, uid.Id);
- }
-
- return hashCode;
- }
-
- [Benchmark]
- public int TryGetComponent()
- {
- var hashCode = 0;
- var enumerator = _entMan.AllEntityQueryEnumerator();
- while (enumerator.MoveNext(out var uid, out var _))
- {
- if (_entMan.TryGetComponent(uid, out ClothingComponent? clothing))
- hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
- }
-
- return hashCode;
- }
-
- [Benchmark]
- public int TryGetComponentQuery()
- {
- var hashCode = 0;
- var enumerator = _entMan.AllEntityQueryEnumerator();
- while (enumerator.MoveNext(out var uid, out var _))
- {
- if (_clothingQuery.TryGetComponent(uid, out var clothing))
- hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
- }
-
- return hashCode;
- }
-
- ///
- /// Enumerate all entities with both an item and clothing component.
- ///
- [Benchmark]
- public int Enumerator()
- {
- var hashCode = 0;
- var enumerator = _entMan.AllEntityQueryEnumerator();
- while (enumerator.MoveNext(out var _, out var clothing))
- {
- hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
- }
-
- return hashCode;
- }
-}
diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs
index 243edca08c..4410dd72a2 100644
--- a/Content.Benchmarks/MapLoadBenchmark.cs
+++ b/Content.Benchmarks/MapLoadBenchmark.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -26,7 +26,7 @@ public class MapLoadBenchmark
public void Setup()
{
ProgramShared.PathOffset = "../../../../";
- PoolManager.Startup(null);
+ PoolManager.Startup();
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
var server = _pair.Server;
diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs
index c7f22bdb0c..fa7f9d4542 100644
--- a/Content.Benchmarks/PvsBenchmark.cs
+++ b/Content.Benchmarks/PvsBenchmark.cs
@@ -1,19 +1,17 @@
#nullable enable
using System;
-using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
+using Content.Server.Mind;
using Content.Server.Warps;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
-using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
-using Robust.Shared.GameStates;
using Robust.Shared.Map;
-using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Random;
@@ -49,7 +47,7 @@ public void Setup()
#if !DEBUG
ProgramShared.PathOffset = "../../../../";
#endif
- PoolManager.Startup(null);
+ PoolManager.Startup();
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency();
@@ -58,15 +56,20 @@ public void Setup()
_pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false);
_sys = _entMan.System();
+ SetupAsync().Wait();
+ }
+
+ private async Task SetupAsync()
+ {
// Spawn the map
_pair.Server.ResolveDependency().SetSeed(42);
- _pair.Server.WaitPost(() =>
+ await _pair.Server.WaitPost(() =>
{
var success = _entMan.System().TryLoad(_mapId, Map, out _);
if (!success)
throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId);
- }).Wait();
+ });
// Get list of ghost warp positions
_spawns = _entMan.AllComponentsList()
@@ -76,17 +79,19 @@ public void Setup()
Array.Resize(ref _players, PlayerCount);
- // Spawn "Players".
- _pair.Server.WaitPost(() =>
+ // Spawn "Players"
+ _players = await _pair.Server.AddDummySessions(PlayerCount);
+ await _pair.Server.WaitPost(() =>
{
+ var mind = _pair.Server.System();
for (var i = 0; i < PlayerCount; i++)
{
var pos = _spawns[i % _spawns.Length];
var uid =_entMan.SpawnEntity("MobHuman", pos);
_pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear");
- _players[i] = new DummySession{AttachedEntity = uid};
+ mind.ControlMob(_players[i].UserId, uid);
}
- }).Wait();
+ });
// Repeatedly move players around so that they "explore" the map and see lots of entities.
// This will populate their PVS data with out-of-view entities.
@@ -168,20 +173,4 @@ public void CycleTick()
}).Wait();
_pair.Server.PvsTick(_players);
}
-
- private sealed class DummySession : ICommonSession
- {
- public SessionStatus Status => SessionStatus.InGame;
- public EntityUid? AttachedEntity {get; set; }
- public NetUserId UserId => default;
- public string Name => string.Empty;
- public short Ping => default;
- public INetChannel Channel { get; set; } = default!;
- public LoginType AuthType => default;
- public HashSet ViewSubscriptions { get; } = new();
- public DateTime ConnectedTime { get; set; }
- public SessionState State => default!;
- public SessionData Data => default!;
- public bool ClientSide { get; set; }
- }
}
diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs
index de51b2fb19..0638d945aa 100644
--- a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs
+++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs
@@ -32,7 +32,7 @@ public class SpawnEquipDeleteBenchmark
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
- PoolManager.Startup(null);
+ PoolManager.Startup();
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
@@ -58,7 +58,7 @@ await _pair.Server.WaitPost(() =>
for (var i = 0; i < N; i++)
{
_entity = server.EntMan.SpawnAttachedTo(Mob, _coords);
- _spawnSys.EquipStartingGear(_entity, _gear, null);
+ _spawnSys.EquipStartingGear(_entity, _gear);
server.EntMan.DeleteEntity(_entity);
}
});
diff --git a/Content.Client/Access/AccessOverlay.cs b/Content.Client/Access/AccessOverlay.cs
index 10025705a2..e6c22112b3 100644
--- a/Content.Client/Access/AccessOverlay.cs
+++ b/Content.Client/Access/AccessOverlay.cs
@@ -9,20 +9,20 @@ namespace Content.Client.Access;
public sealed class AccessOverlay : Overlay
{
+ private const string TextFontPath = "/Fonts/B612_Mono/B612_Mono-Regular.ttf";
+ private const int TextFontSize = 12;
+
private readonly IEntityManager _entityManager;
- private readonly EntityLookupSystem _lookup;
- private readonly SharedTransformSystem _xform;
+ private readonly SharedTransformSystem _transformSystem;
private readonly Font _font;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
- public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup, SharedTransformSystem xform)
+ public AccessOverlay(IEntityManager entityManager, IResourceCache resourceCache, SharedTransformSystem transformSystem)
{
- _entityManager = entManager;
- _lookup = lookup;
- _xform = xform;
-
- _font = cache.GetFont("/Fonts/B612_Mono/B612_Mono-Regular.ttf", 12);
+ _entityManager = entityManager;
+ _transformSystem = transformSystem;
+ _font = resourceCache.GetFont(TextFontPath, TextFontSize);
}
protected override void Draw(in OverlayDrawArgs args)
@@ -30,52 +30,65 @@ protected override void Draw(in OverlayDrawArgs args)
if (args.ViewportControl == null)
return;
- var readerQuery = _entityManager.GetEntityQuery();
- var xformQuery = _entityManager.GetEntityQuery();
-
- foreach (var ent in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldAABB,
- LookupFlags.Static | LookupFlags.Approximate))
+ var textBuffer = new StringBuilder();
+ var query = _entityManager.EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var accessReader, out var transform))
{
- if (!readerQuery.TryGetComponent(ent, out var reader) ||
- !xformQuery.TryGetComponent(ent, out var xform))
+ textBuffer.Clear();
+
+ var entityName = _entityManager.ToPrettyString(uid);
+ textBuffer.AppendLine(entityName.Prototype);
+ textBuffer.Append("UID: ");
+ textBuffer.Append(entityName.Uid.Id);
+ textBuffer.Append(", NUID: ");
+ textBuffer.Append(entityName.Nuid.Id);
+ textBuffer.AppendLine();
+
+ if (!accessReader.Enabled)
{
+ textBuffer.AppendLine("-Disabled");
continue;
}
- var text = new StringBuilder();
- var index = 0;
- var a = $"{_entityManager.ToPrettyString(ent)}";
- text.Append(a);
-
- foreach (var list in reader.AccessLists)
+ if (accessReader.AccessLists.Count > 0)
{
- a = $"Tag {index}";
- text.AppendLine(a);
-
- foreach (var entry in list)
+ var groupNumber = 0;
+ foreach (var accessList in accessReader.AccessLists)
{
- a = $"- {entry}";
- text.AppendLine(a);
+ groupNumber++;
+ foreach (var entry in accessList)
+ {
+ textBuffer.Append("+Set ");
+ textBuffer.Append(groupNumber);
+ textBuffer.Append(": ");
+ textBuffer.Append(entry.Id);
+ textBuffer.AppendLine();
+ }
}
-
- index++;
}
-
- string textStr;
-
- if (text.Length >= 2)
+ else
{
- textStr = text.ToString();
- textStr = textStr[..^2];
+ textBuffer.AppendLine("+Unrestricted");
}
- else
+
+ foreach (var key in accessReader.AccessKeys)
{
- textStr = "";
+ textBuffer.Append("+Key ");
+ textBuffer.Append(key.OriginStation);
+ textBuffer.Append(": ");
+ textBuffer.Append(key.Id);
+ textBuffer.AppendLine();
}
- var screenPos = args.ViewportControl.WorldToScreen(_xform.GetWorldPosition(xform));
+ foreach (var tag in accessReader.DenyTags)
+ {
+ textBuffer.Append("-Tag ");
+ textBuffer.AppendLine(tag.Id);
+ }
- args.ScreenHandle.DrawString(_font, screenPos, textStr, Color.Gold);
+ var accessInfoText = textBuffer.ToString();
+ var screenPos = args.ViewportControl.WorldToScreen(_transformSystem.GetWorldPosition(transform));
+ args.ScreenHandle.DrawString(_font, screenPos, accessInfoText, Color.Gold);
}
}
}
diff --git a/Content.Client/Access/Commands/ShowAccessReadersCommand.cs b/Content.Client/Access/Commands/ShowAccessReadersCommand.cs
index 7c804dd969..cb6cb6cf6b 100644
--- a/Content.Client/Access/Commands/ShowAccessReadersCommand.cs
+++ b/Content.Client/Access/Commands/ShowAccessReadersCommand.cs
@@ -7,8 +7,16 @@ namespace Content.Client.Access.Commands;
public sealed class ShowAccessReadersCommand : IConsoleCommand
{
public string Command => "showaccessreaders";
- public string Description => "Shows all access readers in the viewport";
- public string Help => $"{Command}";
+
+ public string Description => "Toggles showing access reader permissions on the map";
+ public string Help => """
+ Overlay Info:
+ -Disabled | The access reader is disabled
+ +Unrestricted | The access reader has no restrictions
+ +Set [Index]: [Tag Name]| A tag in an access set (accessor needs all tags in the set to be allowed by the set)
+ +Key [StationUid]: [StationRecordKeyId] | A StationRecordKey that is allowed
+ -Tag [Tag Name] | A tag that is not allowed (takes priority over other allows)
+ """;
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var collection = IoCManager.Instance;
@@ -26,10 +34,9 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
var entManager = collection.Resolve();
var cache = collection.Resolve();
- var lookup = entManager.System();
var xform = entManager.System();
- overlay.AddOverlay(new AccessOverlay(entManager, cache, lookup, xform));
+ overlay.AddOverlay(new AccessOverlay(entManager, cache, xform));
shell.WriteLine($"Set access reader debug overlay to true");
}
}
diff --git a/Content.Client/Access/IdCardSystem.cs b/Content.Client/Access/IdCardSystem.cs
index fcf2bf57de..e0c02976f7 100644
--- a/Content.Client/Access/IdCardSystem.cs
+++ b/Content.Client/Access/IdCardSystem.cs
@@ -2,6 +2,4 @@
namespace Content.Client.Access;
-public sealed class IdCardSystem : SharedIdCardSystem
-{
-}
+public sealed class IdCardSystem : SharedIdCardSystem;
diff --git a/Content.Client/Access/UI/AccessLevelControl.xaml b/Content.Client/Access/UI/AccessLevelControl.xaml
new file mode 100644
index 0000000000..56968d8983
--- /dev/null
+++ b/Content.Client/Access/UI/AccessLevelControl.xaml
@@ -0,0 +1,4 @@
+
+
diff --git a/Content.Client/Access/UI/AccessLevelControl.xaml.cs b/Content.Client/Access/UI/AccessLevelControl.xaml.cs
new file mode 100644
index 0000000000..34db80b7af
--- /dev/null
+++ b/Content.Client/Access/UI/AccessLevelControl.xaml.cs
@@ -0,0 +1,52 @@
+using System.Linq;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Content.Shared.Access;
+using Content.Shared.Access.Systems;
+
+namespace Content.Client.Access.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class AccessLevelControl : GridContainer
+{
+ public readonly Dictionary, Button> ButtonsList = new();
+
+ public AccessLevelControl()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void Populate(List> accessLevels, IPrototypeManager prototypeManager)
+ {
+ foreach (var access in accessLevels)
+ {
+ if (!prototypeManager.TryIndex(access, out var accessLevel))
+ {
+ Logger.Error($"Unable to find accesslevel for {access}");
+ continue;
+ }
+
+ var newButton = new Button
+ {
+ Text = accessLevel.GetAccessLevelName(),
+ ToggleMode = true,
+ };
+ AddChild(newButton);
+ ButtonsList.Add(accessLevel.ID, newButton);
+ }
+ }
+
+ public void UpdateState(
+ List> pressedList,
+ List>? enabledList = null)
+ {
+ foreach (var (accessName, button) in ButtonsList)
+ {
+ button.Pressed = pressedList.Contains(accessName);
+ button.Disabled = !(enabledList?.Contains(accessName) ?? true);
+ }
+ }
+}
diff --git a/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs b/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs
index 0c23542f79..c1b63dc4d0 100644
--- a/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs
+++ b/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs
@@ -64,7 +64,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window?.UpdateState(castState);
}
- public void SubmitData(List newAccessList)
+ public void SubmitData(List> newAccessList)
{
SendMessage(new WriteToTargetAccessReaderIdMessage(newAccessList));
}
diff --git a/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs b/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs
index 2fd0057121..6025c3b551 100644
--- a/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs
+++ b/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs
@@ -16,7 +16,6 @@ public sealed partial class AccessOverriderWindow : DefaultWindow
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- private readonly ISawmill _logMill = default!;
private readonly AccessOverriderBoundUserInterface _owner;
private readonly Dictionary _accessButtons = new();
@@ -25,7 +24,7 @@ public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototype
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- _logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
+ var logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
_owner = owner;
@@ -33,13 +32,13 @@ public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototype
{
if (!prototypeManager.TryIndex(access, out var accessLevel))
{
- _logMill.Error($"Unable to find accesslevel for {access}");
+ logMill.Error($"Unable to find accesslevel for {access}");
continue;
}
var newButton = new Button
{
- Text = GetAccessLevelName(accessLevel),
+ Text = accessLevel.GetAccessLevelName(),
ToggleMode = true,
};
@@ -49,14 +48,6 @@ public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototype
}
}
- private static string GetAccessLevelName(AccessLevelPrototype prototype)
- {
- if (prototype.Name is { } name)
- return Loc.GetString(name);
-
- return prototype.ID;
- }
-
public void UpdateState(AccessOverriderBoundUserInterfaceState state)
{
PrivilegedIdLabel.Text = state.PrivilegedIdName;
@@ -105,7 +96,7 @@ private void SubmitData()
_owner.SubmitData(
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
- _accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
+ _accessButtons.Where(x => x.Value.Pressed).Select(x => new ProtoId(x.Key)).ToList());
}
}
}
diff --git a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs
index 73f18aec8d..761f52988a 100644
--- a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs
+++ b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs
@@ -1,5 +1,7 @@
using Content.Shared.Access.Systems;
+using Content.Shared.StatusIcon;
using Robust.Client.GameObjects;
+using Robust.Shared.Prototypes;
namespace Content.Client.Access.UI
{
@@ -40,9 +42,9 @@ private void OnJobChanged(string newJob)
SendMessage(new AgentIDCardJobChangedMessage(newJob));
}
- public void OnJobIconChanged(string newJobIcon)
+ public void OnJobIconChanged(ProtoId newJobIconId)
{
- SendMessage(new AgentIDCardJobIconChangedMessage(newJobIcon));
+ SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId));
}
///
@@ -57,7 +59,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window.SetCurrentName(cast.CurrentName);
_window.SetCurrentJob(cast.CurrentJob);
- _window.SetAllowedIcons(cast.Icons);
+ _window.SetAllowedIcons(cast.Icons, cast.CurrentJobIconId);
}
protected override void Dispose(bool disposing)
diff --git a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
index beca0c41ba..6d0b2a184f 100644
--- a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
+++ b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
@@ -38,7 +38,7 @@ public AgentIDCardWindow(AgentIDCardBoundUserInterface bui)
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
}
- public void SetAllowedIcons(HashSet icons)
+ public void SetAllowedIcons(HashSet> icons, string currentJobIconId)
{
IconGrid.DisposeAllChildren();
@@ -46,10 +46,8 @@ public void SetAllowedIcons(HashSet icons)
var i = 0;
foreach (var jobIconId in icons)
{
- if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon))
- {
+ if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon))
continue;
- }
String styleBase = StyleBase.ButtonOpenBoth;
var modulo = i % JobIconColumnCount;
@@ -77,8 +75,12 @@ public void SetAllowedIcons(HashSet icons)
};
jobIconButton.AddChild(jobIconTexture);
- jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIcon.ID);
+ jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIconId);
IconGrid.AddChild(jobIconButton);
+
+ if (jobIconId.Equals(currentJobIconId))
+ jobIconButton.Pressed = true;
+
i++;
}
}
diff --git a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs
index 898792aa03..a321b4121e 100644
--- a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs
+++ b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs
@@ -28,7 +28,6 @@ protected override void Open()
if (EntMan.TryGetComponent(Owner, out var idCard))
{
accessLevels = idCard.AccessLevels;
- accessLevels.Sort();
}
else
{
@@ -65,7 +64,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window?.UpdateState(castState);
}
- public void SubmitData(string newFullName, string newJobTitle, List newAccessList, string newJobPrototype)
+ public void SubmitData(string newFullName, string newJobTitle, List> newAccessList, string newJobPrototype)
{
if (newFullName.Length > MaxFullNameLength)
newFullName = newFullName[..MaxFullNameLength];
diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml b/Content.Client/Access/UI/IdCardConsoleWindow.xaml
index c29adc8ebd..a2f5f3382b 100644
--- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml
+++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml
@@ -30,10 +30,6 @@
-
-
-
-
-
+
diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
index bf5984e809..82f6ebd8b5 100644
--- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
+++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
@@ -20,13 +20,16 @@ public sealed partial class IdCardConsoleWindow : DefaultWindow
private readonly IdCardConsoleBoundUserInterface _owner;
- private readonly Dictionary _accessButtons = new();
+ private AccessLevelControl _accessButtons = new();
private readonly List _jobPrototypeIds = new();
private string? _lastFullName;
private string? _lastJobTitle;
private string? _lastJobProto;
+ // The job that will be picked if the ID doesn't have a job on the station.
+ private static ProtoId _defaultJob = "Passenger";
+
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager,
List> accessLevels)
{
@@ -65,37 +68,18 @@ public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeMana
}
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
+ _accessButtons.Populate(accessLevels, prototypeManager);
+ AccessLevelControlContainer.AddChild(_accessButtons);
- foreach (var access in accessLevels)
+ foreach (var (id, button) in _accessButtons.ButtonsList)
{
- if (!prototypeManager.TryIndex(access, out var accessLevel))
- {
- _logMill.Error($"Unable to find accesslevel for {access}");
- continue;
- }
-
- var newButton = new Button
- {
- Text = GetAccessLevelName(accessLevel),
- ToggleMode = true,
- };
- AccessLevelGrid.AddChild(newButton);
- _accessButtons.Add(accessLevel.ID, newButton);
- newButton.OnPressed += _ => SubmitData();
+ button.OnPressed += _ => SubmitData();
}
}
- private static string GetAccessLevelName(AccessLevelPrototype prototype)
- {
- if (prototype.Name is { } name)
- return Loc.GetString(name);
-
- return prototype.ID;
- }
-
private void ClearAllAccess()
{
- foreach (var button in _accessButtons.Values)
+ foreach (var button in _accessButtons.ButtonsList.Values)
{
if (button.Pressed)
{
@@ -119,7 +103,7 @@ private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args)
// this is a sussy way to do this
foreach (var access in job.Access)
{
- if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled)
+ if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
@@ -134,7 +118,7 @@ private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args)
foreach (var access in groupPrototype.Tags)
{
- if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled)
+ if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
@@ -184,22 +168,21 @@ public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
JobPresetOptionButton.Disabled = !interfaceEnabled;
- foreach (var (accessName, button) in _accessButtons)
- {
- button.Disabled = !interfaceEnabled;
- if (interfaceEnabled)
- {
- button.Pressed = state.TargetIdAccessList?.Contains(accessName) ?? false;
- button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true;
- }
- }
+ _accessButtons.UpdateState(state.TargetIdAccessList?.ToList() ??
+ new List>(),
+ state.AllowedModifyAccessList?.ToList() ??
+ new List>());
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
- if (jobIndex >= 0)
+ // If the job index is < 0 that means they don't have a job registered in the station records.
+ // For example, a new ID from a box would have no job index.
+ if (jobIndex < 0)
{
- JobPresetOptionButton.SelectId(jobIndex);
+ jobIndex = _jobPrototypeIds.IndexOf(_defaultJob);
}
+ JobPresetOptionButton.SelectId(jobIndex);
+
_lastFullName = state.TargetIdFullName;
_lastJobTitle = state.TargetIdJobTitle;
_lastJobProto = state.TargetIdJobPrototype;
@@ -215,7 +198,7 @@ private void SubmitData()
FullNameLineEdit.Text,
JobTitleLineEdit.Text,
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
- _accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
+ _accessButtons.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty);
}
}
diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs
index 4eaf06b691..aff6c1ff7b 100644
--- a/Content.Client/Actions/ActionsSystem.cs
+++ b/Content.Client/Actions/ActionsSystem.cs
@@ -94,6 +94,7 @@ private void BaseHandleState(EntityUid uid, BaseActionComponent component, Ba
component.Container = EnsureEntity(state.Container, uid);
component.EntityIcon = EnsureEntity(state.EntityIcon, uid);
component.CheckCanInteract = state.CheckCanInteract;
+ component.CheckConsciousness = state.CheckConsciousness;
component.ClientExclusive = state.ClientExclusive;
component.Priority = state.Priority;
component.AttachedEntity = EnsureEntity(state.AttachedEntity, uid);
@@ -246,7 +247,10 @@ public void TriggerAction(EntityUid actionId, BaseActionComponent action)
if (action.ClientExclusive)
{
if (instantAction.Event != null)
+ {
instantAction.Event.Performer = user;
+ instantAction.Event.Action = actionId;
+ }
PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
}
diff --git a/Content.Client/Administration/Components/HeadstandComponent.cs b/Content.Client/Administration/Components/HeadstandComponent.cs
index d95e74576b..a4e3bfc5aa 100644
--- a/Content.Client/Administration/Components/HeadstandComponent.cs
+++ b/Content.Client/Administration/Components/HeadstandComponent.cs
@@ -3,7 +3,7 @@
namespace Content.Client.Administration.Components;
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent]
public sealed partial class HeadstandComponent : SharedHeadstandComponent
{
diff --git a/Content.Client/Administration/Components/KillSignComponent.cs b/Content.Client/Administration/Components/KillSignComponent.cs
index 1cf47b93ff..91c44ef3f2 100644
--- a/Content.Client/Administration/Components/KillSignComponent.cs
+++ b/Content.Client/Administration/Components/KillSignComponent.cs
@@ -3,6 +3,5 @@
namespace Content.Client.Administration.Components;
-[NetworkedComponent, RegisterComponent]
-public sealed partial class KillSignComponent : SharedKillSignComponent
-{ }
+[RegisterComponent]
+public sealed partial class KillSignComponent : SharedKillSignComponent;
diff --git a/Content.Client/Administration/Managers/ClientAdminManager.cs b/Content.Client/Administration/Managers/ClientAdminManager.cs
index d33761be8f..0f740c8104 100644
--- a/Content.Client/Administration/Managers/ClientAdminManager.cs
+++ b/Content.Client/Administration/Managers/ClientAdminManager.cs
@@ -2,6 +2,7 @@
using Content.Shared.Administration.Managers;
using Robust.Client.Console;
using Robust.Client.Player;
+using Robust.Client.UserInterface;
using Robust.Shared.ContentPack;
using Robust.Shared.Network;
using Robust.Shared.Player;
@@ -16,6 +17,7 @@ public sealed class ClientAdminManager : IClientAdminManager, IClientConGroupImp
[Dependency] private readonly IClientConGroupController _conGroup = default!;
[Dependency] private readonly IResourceManager _res = default!;
[Dependency] private readonly ILogManager _logManager = default!;
+ [Dependency] private readonly IUserInterfaceManager _userInterface = default!;
private AdminData? _adminData;
private readonly HashSet _availableCommands = new();
@@ -101,6 +103,9 @@ private void UpdateMessageRx(MsgUpdateAdminStatus message)
{
var flagsText = string.Join("|", AdminFlagsHelper.FlagsToNames(_adminData.Flags));
_sawmill.Info($"Updated admin status: {_adminData.Active}/{_adminData.Title}/{flagsText}");
+
+ if (_adminData.Active)
+ _userInterface.DebugMonitors.SetMonitor(DebugMonitor.Coords, true);
}
else
{
@@ -121,12 +126,15 @@ void IPostInjectInit.PostInject()
public AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false)
{
- return uid == _player.LocalEntity ? _adminData : null;
+ if (uid == _player.LocalEntity && (_adminData?.Active ?? includeDeAdmin))
+ return _adminData;
+
+ return null;
}
public AdminData? GetAdminData(ICommonSession session, bool includeDeAdmin = false)
{
- if (_player.LocalUser == session.UserId)
+ if (_player.LocalUser == session.UserId && (_adminData?.Active ?? includeDeAdmin))
return _adminData;
return null;
diff --git a/Content.Client/Administration/Systems/AdminFrozenSystem.cs b/Content.Client/Administration/Systems/AdminFrozenSystem.cs
new file mode 100644
index 0000000000..885585f985
--- /dev/null
+++ b/Content.Client/Administration/Systems/AdminFrozenSystem.cs
@@ -0,0 +1,7 @@
+using Content.Shared.Administration;
+
+namespace Content.Client.Administration.Systems;
+
+public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
+{
+}
diff --git a/Content.Client/Administration/Systems/AdminVerbSystem.cs b/Content.Client/Administration/Systems/AdminVerbSystem.cs
index e0f84bc4f0..dced59bbf2 100644
--- a/Content.Client/Administration/Systems/AdminVerbSystem.cs
+++ b/Content.Client/Administration/Systems/AdminVerbSystem.cs
@@ -1,3 +1,6 @@
+using Content.Shared.Administration;
+using Content.Shared.Administration.Managers;
+using Content.Shared.Mind.Components;
using Content.Shared.Verbs;
using Robust.Client.Console;
using Robust.Shared.Utility;
@@ -11,10 +14,12 @@ sealed class AdminVerbSystem : EntitySystem
{
[Dependency] private readonly IClientConGroupController _clientConGroupController = default!;
[Dependency] private readonly IClientConsoleHost _clientConsoleHost = default!;
+ [Dependency] private readonly ISharedAdminManager _admin = default!;
public override void Initialize()
{
SubscribeLocalEvent>(AddAdminVerbs);
+
}
private void AddAdminVerbs(GetVerbsEvent args)
@@ -33,6 +38,24 @@ private void AddAdminVerbs(GetVerbsEvent args)
};
args.Verbs.Add(verb);
}
+
+ if (!_admin.IsAdmin(args.User))
+ return;
+
+ if (_admin.HasAdminFlag(args.User, AdminFlags.Admin))
+ args.ExtraCategories.Add(VerbCategory.Admin);
+
+ if (_admin.HasAdminFlag(args.User, AdminFlags.Fun) && HasComp(args.Target))
+ args.ExtraCategories.Add(VerbCategory.Antag);
+
+ if (_admin.HasAdminFlag(args.User, AdminFlags.Debug))
+ args.ExtraCategories.Add(VerbCategory.Debug);
+
+ if (_admin.HasAdminFlag(args.User, AdminFlags.Fun))
+ args.ExtraCategories.Add(VerbCategory.Smite);
+
+ if (_admin.HasAdminFlag(args.User, AdminFlags.Admin))
+ args.ExtraCategories.Add(VerbCategory.Tricks);
}
}
}
diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml b/Content.Client/Administration/UI/AdminMenuWindow.xaml
index 311d67b826..d3d3df02d9 100644
--- a/Content.Client/Administration/UI/AdminMenuWindow.xaml
+++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml
@@ -6,7 +6,8 @@
xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs"
xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
- xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab">
+ xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab"
+ xmlns:baby="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab">
@@ -14,6 +15,7 @@
+
diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
index c3ea67a3ed..f3aa2572f2 100644
--- a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
+++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
@@ -21,8 +21,12 @@ public AdminMenuWindow()
MasterTabContainer.SetTabTitle(3, Loc.GetString("admin-menu-round-tab"));
MasterTabContainer.SetTabTitle(4, Loc.GetString("admin-menu-server-tab"));
MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-panic-bunker-tab"));
- MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-players-tab"));
- MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-objects-tab"));
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+ MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-baby-jail-tab"));
+ MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-players-tab"));
+ MasterTabContainer.SetTabTitle(8, Loc.GetString("admin-menu-objects-tab"));
}
protected override void Dispose(bool disposing)
diff --git a/Content.Client/Administration/UI/AdminRemarks/AdminMessageEui.cs b/Content.Client/Administration/UI/AdminRemarks/AdminMessageEui.cs
index 06eace118d..502c56a5a6 100644
--- a/Content.Client/Administration/UI/AdminRemarks/AdminMessageEui.cs
+++ b/Content.Client/Administration/UI/AdminRemarks/AdminMessageEui.cs
@@ -2,6 +2,7 @@
using Content.Shared.Administration.Notes;
using Content.Shared.Eui;
using JetBrains.Annotations;
+using Robust.Client.UserInterface.Controls;
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
namespace Content.Client.Administration.UI.AdminRemarks;
@@ -14,9 +15,8 @@ public sealed class AdminMessageEui : BaseEui
public AdminMessageEui()
{
_popup = new AdminMessagePopupWindow();
- _popup.OnAcceptPressed += () => SendMessage(new Accept());
- _popup.OnDismissPressed += () => SendMessage(new Dismiss());
- _popup.OnClose += () => SendMessage(new CloseEuiMessage());
+ _popup.OnAcceptPressed += () => SendMessage(new Dismiss(true));
+ _popup.OnDismissPressed += () => SendMessage(new Dismiss(false));
}
public override void HandleState(EuiStateBase state)
@@ -26,13 +26,17 @@ public override void HandleState(EuiStateBase state)
return;
}
- _popup.SetMessage(s.Message);
- _popup.SetDetails(s.AdminName, s.AddedOn);
- _popup.Timer = s.Time;
+ _popup.SetState(s);
}
public override void Opened()
{
- _popup.OpenCentered();
+ _popup.UserInterfaceManager.WindowRoot.AddChild(_popup);
+ LayoutContainer.SetAnchorPreset(_popup, LayoutContainer.LayoutPreset.Wide);
+ }
+
+ public override void Closed()
+ {
+ _popup.Orphan();
}
}
diff --git a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupMessage.xaml b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupMessage.xaml
new file mode 100644
index 0000000000..9a60a6f72a
--- /dev/null
+++ b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupMessage.xaml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupMessage.xaml.cs b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupMessage.xaml.cs
new file mode 100644
index 0000000000..7bb425f618
--- /dev/null
+++ b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupMessage.xaml.cs
@@ -0,0 +1,23 @@
+using Content.Shared.Administration.Notes;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Administration.UI.AdminRemarks;
+
+[GenerateTypedNameReferences]
+public sealed partial class AdminMessagePopupMessage : Control
+{
+ public AdminMessagePopupMessage(AdminMessageEuiState.Message message)
+ {
+ RobustXamlLoader.Load(this);
+
+ Admin.SetMessage(FormattedMessage.FromMarkup(Loc.GetString(
+ "admin-notes-message-admin",
+ ("admin", message.AdminName),
+ ("date", message.AddedOn.ToLocalTime()))));
+
+ Message.SetMessage(message.Text);
+ }
+}
diff --git a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml
index 311829e8b2..cc5207bb3a 100644
--- a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml
+++ b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml
@@ -1,22 +1,36 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Text="{Loc 'admin-notes-message-dismiss'}"
+ Disabled="True"
+ HorizontalExpand="True"
+ StyleClasses="OpenRight" />
+ Disabled="True"
+ HorizontalExpand="True"
+ StyleClasses="OpenLeft" />
-
+
-
+
diff --git a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs
index f28fd1b60b..bf2ca9bec4 100644
--- a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs
+++ b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs
@@ -1,56 +1,65 @@
-using Content.Client.UserInterface.Controls;
+using Content.Client.Stylesheets;
+using Content.Shared.Administration.Notes;
using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
+using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.AdminRemarks;
[GenerateTypedNameReferences]
-public sealed partial class AdminMessagePopupWindow : FancyWindow
+public sealed partial class AdminMessagePopupWindow : Control
{
private float _timer = float.MaxValue;
- public float Timer
- {
- get => _timer;
- set
- {
- WaitLabel.Text = Loc.GetString("admin-notes-message-wait", ("time", MathF.Floor(value)));
- _timer = value;
- }
- }
public event Action? OnDismissPressed;
+
public event Action? OnAcceptPressed;
public AdminMessagePopupWindow()
{
RobustXamlLoader.Load(this);
+ Stylesheet = IoCManager.Resolve().SheetSpace;
+
AcceptButton.OnPressed += OnAcceptButtonPressed;
DismissButton.OnPressed += OnDismissButtonPressed;
}
- public void SetMessage(string message)
+ public float Timer
{
- MessageLabel.SetMessage(message);
+ get => _timer;
+ private set
+ {
+ WaitLabel.Text = Loc.GetString("admin-notes-message-wait", ("time", MathF.Floor(value)));
+ _timer = value;
+ }
}
- public void SetDetails(string adminName, DateTime addedOn)
+ public void SetState(AdminMessageEuiState state)
{
- AdminLabel.Text = Loc.GetString("admin-notes-message-admin", ("admin", adminName), ("date", addedOn));
+ Timer = (float) state.Time.TotalSeconds;
+
+ MessageContainer.RemoveAllChildren();
+
+ foreach (var message in state.Messages)
+ {
+ MessageContainer.AddChild(new AdminMessagePopupMessage(message));
+ }
+
+ Description.SetMessage(FormattedMessage.FromMarkup(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
}
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)
{
OnDismissPressed?.Invoke();
- Close();
}
private void OnAcceptButtonPressed(BaseButton.ButtonEventArgs obj)
{
OnAcceptPressed?.Invoke();
- Close();
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -70,6 +79,7 @@ protected override void FrameUpdate(FrameEventArgs args)
else
{
AcceptButton.Disabled = false;
+ DismissButton.Disabled = false;
}
}
}
diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs
index 1f32640f7d..588d62e560 100644
--- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs
+++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs
@@ -3,6 +3,7 @@
using System.Net.Sockets;
using Content.Client.Administration.UI.CustomControls;
using Content.Shared.Administration;
+using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
@@ -11,6 +12,7 @@
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -32,8 +34,11 @@ public sealed partial class BanPanel : DefaultWindow
// This is less efficient than just holding a reference to the root control and enumerating children, but you
// have to know how the controls are nested, which makes the code more complicated.
private readonly List _roleCheckboxes = new();
+ private readonly ISawmill _banpanelSawmill;
[Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly ILogManager _logManager = default!;
private enum TabNumbers
{
@@ -65,6 +70,7 @@ public BanPanel()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
+ _banpanelSawmill = _logManager.GetSawmill("admin.banpanel");
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged();
PlayerCheckbox.OnPressed += _ =>
@@ -104,6 +110,11 @@ public BanPanel()
};
SubmitButton.OnPressed += SubmitButtonOnOnPressed;
+ IpCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanIpBanDefault);
+ HwidCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanHwidBanDefault);
+ LastConnCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanUseLastDetails);
+ EraseCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanErasePlayer);
+
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-none"), (int) NoteSeverity.None);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-low"), (int) NoteSeverity.Minor);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-medium"), (int) NoteSeverity.Medium);
@@ -136,7 +147,7 @@ public BanPanel()
var prototypeManager = IoCManager.Resolve();
foreach (var proto in prototypeManager.EnumeratePrototypes())
{
- CreateRoleGroup(proto.ID, proto.Roles, proto.Color);
+ CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color);
}
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes().Select(p => p.ID), Color.Red);
@@ -175,6 +186,39 @@ private void CreateRoleGroup(string roleName, IEnumerable roleList, Colo
c.Pressed = args.Pressed;
}
}
+
+ if (args.Pressed)
+ {
+ if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), true, out NoteSeverity newSeverity))
+ {
+ _banpanelSawmill
+ .Warning("Departmental role ban severity could not be parsed from config!");
+ return;
+ }
+ SeverityOption.SelectId((int) newSeverity);
+ }
+ else
+ {
+ foreach (var childContainer in RolesContainer.Children)
+ {
+ if (childContainer is Container)
+ {
+ foreach (var child in childContainer.Children)
+ {
+ if (child is CheckBox { Pressed: true })
+ return;
+ }
+ }
+ }
+
+ if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity newSeverity))
+ {
+ _banpanelSawmill
+ .Warning("Role ban severity could not be parsed from config!");
+ return;
+ }
+ SeverityOption.SelectId((int) newSeverity);
+ }
};
outerContainer.AddChild(innerContainer);
foreach (var role in roleList)
@@ -353,6 +397,35 @@ private void OnTypeChanged()
{
TypeOption.ModulateSelfOverride = null;
Tabs.SetTabVisible((int) TabNumbers.Roles, TypeOption.SelectedId == (int) Types.Role);
+ NoteSeverity? newSeverity = null;
+ switch (TypeOption.SelectedId)
+ {
+ case (int)Types.Server:
+ if (Enum.TryParse(_cfg.GetCVar(CCVars.ServerBanDefaultSeverity), true, out NoteSeverity serverSeverity))
+ newSeverity = serverSeverity;
+ else
+ {
+ _banpanelSawmill
+ .Warning("Server ban severity could not be parsed from config!");
+ }
+
+ break;
+ case (int) Types.Role:
+
+ if (Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity roleSeverity))
+ {
+ newSeverity = roleSeverity;
+ }
+ else
+ {
+ _banpanelSawmill
+ .Warning("Role ban severity could not be parsed from config!");
+ }
+ break;
+ }
+
+ if (newSeverity != null)
+ SeverityOption.SelectId((int) newSeverity.Value);
}
private void UpdateSubmitEnabled()
diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
index af977f763c..ddd66623bd 100644
--- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
+++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
@@ -75,7 +75,7 @@ public BwoinkControl()
if (info.Antag && info.ActiveThisRound)
sb.Append(new Rune(0x1F5E1)); // 🗡
- if (info.OverallPlaytime <= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
+ if (info.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
sb.Append(new Rune(0x23F2)); // ⏲
sb.AppendFormat("\"{0}\"", text);
@@ -226,7 +226,7 @@ private string FormatTabTitle(ItemList.Item li, PlayerInfo? pl = default)
if (pl.Antag)
sb.Append(new Rune(0x1F5E1)); // 🗡
- if (pl.OverallPlaytime <= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
+ if (pl.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
sb.Append(new Rune(0x23F2)); // ⏲
sb.AppendFormat("\"{0}\"", pl.CharacterName);
@@ -243,9 +243,9 @@ private void SwitchToChannel(NetUserId? ch)
{
UpdateButtons();
+ AHelpHelper.HideAllPanels();
if (ch != null)
{
- AHelpHelper.HideAllPanels();
var panel = AHelpHelper.EnsurePanel(ch.Value);
panel.Visible = true;
}
diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs
index f8d06f758f..30f9d24df1 100644
--- a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs
+++ b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs
@@ -16,14 +16,17 @@ public BwoinkWindow()
Bwoink.ChannelSelector.OnSelectionChanged += sel =>
{
- if (sel is not null)
+ if (sel is null)
{
- Title = $"{sel.CharacterName} / {sel.Username}";
+ Title = Loc.GetString("bwoink-title-none-selected");
+ return;
+ }
+
+ Title = $"{sel.CharacterName} / {sel.Username}";
- if (sel.OverallPlaytime != null)
- {
- Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
- }
+ if (sel.OverallPlaytime != null)
+ {
+ Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
}
};
diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
index e02b22f4a2..1cdbedbb46 100644
--- a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
+++ b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
@@ -20,7 +20,7 @@ public sealed partial class PlayerListControl : BoxContainer
private List _playerList = new();
private readonly List _sortedPlayerList = new();
- public event Action? OnSelectionChanged;
+ public event Action? OnSelectionChanged;
public IReadOnlyList PlayerInfo => _playerList;
public Func? OverrideText;
@@ -41,12 +41,19 @@ public PlayerListControl()
PlayerListContainer.ItemPressed += PlayerListItemPressed;
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
PlayerListContainer.GenerateItem += GenerateButton;
+ PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
PopulateList(_adminSystem.PlayerList);
FilterLineEdit.OnTextChanged += _ => FilterList();
_adminSystem.PlayerListChanged += PopulateList;
BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(20, 20, 20)};
}
+ private void PlayerListNoItemSelected()
+ {
+ _selectedPlayer = null;
+ OnSelectionChanged?.Invoke(null);
+ }
+
private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
{
if (args == null || data is not PlayerListData {Info: var selectedPlayer})
diff --git a/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml b/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml
index 9e0f9d182e..01259b60f7 100644
--- a/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml
+++ b/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml
@@ -12,7 +12,7 @@
-
+
@@ -23,7 +23,7 @@
-
+
diff --git a/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs
index 1e09253eff..0ba5553e21 100644
--- a/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs
+++ b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs
@@ -25,7 +25,7 @@ public sealed class ExplosionDebugOverlay : Overlay
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
- public Matrix3 SpaceMatrix;
+ public Matrix3x2 SpaceMatrix;
public MapId Map;
private readonly Font _font;
@@ -78,7 +78,8 @@ private void DrawScreen(OverlayDrawArgs args)
if (SpaceTiles == null)
return;
- gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds);
+ Matrix3x2.Invert(SpaceMatrix, out var invSpace);
+ gridBounds = invSpace.TransformBox(args.WorldBounds);
DrawText(handle, gridBounds, SpaceMatrix, SpaceTiles, SpaceTileSize);
}
@@ -86,7 +87,7 @@ private void DrawScreen(OverlayDrawArgs args)
private void DrawText(
DrawingHandleScreen handle,
Box2 gridBounds,
- Matrix3 transform,
+ Matrix3x2 transform,
Dictionary> tileSets,
ushort tileSize)
{
@@ -103,7 +104,7 @@ private void DrawText(
if (!gridBounds.Contains(centre))
continue;
- var worldCenter = transform.Transform(centre);
+ var worldCenter = Vector2.Transform(centre, transform);
var screenCenter = _eyeManager.WorldToScreen(worldCenter);
@@ -119,7 +120,7 @@ private void DrawText(
if (tileSets.TryGetValue(0, out var set))
{
var epicenter = set.First();
- var worldCenter = transform.Transform((epicenter + Vector2Helpers.Half) * tileSize);
+ var worldCenter = Vector2.Transform((epicenter + Vector2Helpers.Half) * tileSize, transform);
var screenCenter = _eyeManager.WorldToScreen(worldCenter) + new Vector2(-24, -24);
var text = $"{Intensity[0]:F2}\nΣ={TotalIntensity:F1}\nΔ={Slope:F1}";
handle.DrawString(_font, screenCenter, text);
@@ -148,11 +149,12 @@ private void DrawWorld(in OverlayDrawArgs args)
if (SpaceTiles == null)
return;
- gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds).Enlarged(2);
+ Matrix3x2.Invert(SpaceMatrix, out var invSpace);
+ gridBounds = invSpace.TransformBox(args.WorldBounds).Enlarged(2);
handle.SetTransform(SpaceMatrix);
DrawTiles(handle, gridBounds, SpaceTiles, SpaceTileSize);
- handle.SetTransform(Matrix3.Identity);
+ handle.SetTransform(Matrix3x2.Identity);
}
private void DrawTiles(
diff --git a/Content.Client/Administration/UI/SpawnExplosion/SpawnExplosionWindow.xaml.cs b/Content.Client/Administration/UI/SpawnExplosion/SpawnExplosionWindow.xaml.cs
index 5f187cad79..b0d8a946ec 100644
--- a/Content.Client/Administration/UI/SpawnExplosion/SpawnExplosionWindow.xaml.cs
+++ b/Content.Client/Administration/UI/SpawnExplosion/SpawnExplosionWindow.xaml.cs
@@ -3,6 +3,7 @@
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
+using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
@@ -22,7 +23,7 @@ public sealed partial class SpawnExplosionWindow : DefaultWindow
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
-
+ private readonly SharedTransformSystem _transform = default!;
private readonly SpawnExplosionEui _eui;
private List _mapData = new();
@@ -37,6 +38,7 @@ public SpawnExplosionWindow(SpawnExplosionEui eui)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
+ _transform = _entMan.System();
_eui = eui;
ExplosionOption.OnItemSelected += ExplosionSelected;
@@ -104,7 +106,7 @@ private void SetLocation()
_pausePreview = true;
MapOptions.Select(_mapData.IndexOf(transform.MapID));
- (MapX.Value, MapY.Value) = transform.MapPosition.Position;
+ (MapX.Value, MapY.Value) = _transform.GetMapCoordinates(_playerManager.LocalEntity!.Value, xform: transform).Position;
_pausePreview = false;
UpdatePreview();
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml
new file mode 100644
index 0000000000..b8034faf52
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml.cs b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml.cs
new file mode 100644
index 0000000000..9e1d53818f
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml.cs
@@ -0,0 +1,21 @@
+using Content.Client.Message;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
+
+/*
+ * TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+[GenerateTypedNameReferences]
+public sealed partial class BabyJailStatusWindow : FancyWindow
+{
+ public BabyJailStatusWindow()
+ {
+ RobustXamlLoader.Load(this);
+ MessageLabel.SetMarkup(Loc.GetString("admin-ui-baby-jail-is-enabled"));
+ }
+}
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml
new file mode 100644
index 0000000000..dd770c2be5
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml.cs b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml.cs
new file mode 100644
index 0000000000..aa9d6ced95
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml.cs
@@ -0,0 +1,75 @@
+using Content.Shared.Administration.Events;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Console;
+
+/*
+ * TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
+
+[GenerateTypedNameReferences]
+public sealed partial class BabyJailTab : Control
+{
+ [Dependency] private readonly IConsoleHost _console = default!;
+
+ private string _maxAccountAge;
+ private string _maxOverallMinutes;
+
+ public BabyJailTab()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ MaxAccountAge.OnTextEntered += args => SendMaxAccountAge(args.Text);
+ MaxAccountAge.OnFocusExit += args => SendMaxAccountAge(args.Text);
+ _maxAccountAge = MaxAccountAge.Text;
+
+ MaxOverallMinutes.OnTextEntered += args => SendMaxOverallMinutes(args.Text);
+ MaxOverallMinutes.OnFocusExit += args => SendMaxOverallMinutes(args.Text);
+ _maxOverallMinutes = MaxOverallMinutes.Text;
+ }
+
+ private void SendMaxAccountAge(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text) ||
+ text == _maxAccountAge ||
+ !int.TryParse(text, out var minutes))
+ {
+ return;
+ }
+
+ _console.ExecuteCommand($"babyjail_max_account_age {minutes}");
+ }
+
+ private void SendMaxOverallMinutes(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text) ||
+ text == _maxOverallMinutes ||
+ !int.TryParse(text, out var minutes))
+ {
+ return;
+ }
+
+ _console.ExecuteCommand($"babyjail_max_overall_minutes {minutes}");
+ }
+
+ public void UpdateStatus(BabyJailStatus status)
+ {
+ EnabledButton.Pressed = status.Enabled;
+ EnabledButton.Text = Loc.GetString(status.Enabled
+ ? "admin-ui-baby-jail-enabled"
+ : "admin-ui-baby-jail-disabled"
+ );
+ EnabledButton.ModulateSelfOverride = status.Enabled ? Color.Red : null;
+ ShowReasonButton.Pressed = status.ShowReason;
+
+ MaxAccountAge.Text = status.MaxAccountAgeMinutes.ToString();
+ _maxAccountAge = MaxAccountAge.Text;
+
+ MaxOverallMinutes.Text = status.MaxOverallMinutes.ToString();
+ _maxOverallMinutes = MaxOverallMinutes.Text;
+ }
+}
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml
index fb68e6c790..ea89916ba8 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml
@@ -1,15 +1,21 @@
+ xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
+ xmlns:ot="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
+ xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
+
+
+
-
-
-
-
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
index a5c3008436..c8606ca80d 100644
--- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
@@ -1,5 +1,7 @@
using Content.Client.Station;
+using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components;
@@ -10,20 +12,20 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTab : Control
{
- [Dependency] private readonly EntityManager _entityManager = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly List _objects = new();
- private List _selections = new();
+ private readonly List _selections = new();
+ private bool _ascending = false; // Set to false for descending order by default
+ private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName;
+ private readonly Color _altColor = Color.FromHex("#292B38");
+ private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
- public event Action? OnEntryKeyBindDown;
+ public event Action? OnEntryKeyBindDown;
- // Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController
- // OR
- // I can do this.
- private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
-
- private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2);
+ private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
+ private TimeSpan _nextUpdate;
public ObjectsTab()
{
@@ -42,6 +44,30 @@ public ObjectsTab()
ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
}
+ ListHeader.OnHeaderClicked += HeaderClicked;
+ SearchList.SearchBar = SearchLineEdit;
+ SearchList.GenerateItem += GenerateButton;
+ SearchList.DataFilterCondition += DataFilterCondition;
+
+ RefreshObjectList();
+ // Set initial selection and refresh the list to apply the initial sort order
+ var defaultSelection = ObjectsTabSelection.Grids;
+ ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection
+ RefreshObjectList(defaultSelection); // Refresh the list with the default selection
+
+ // Initialize the next update time
+ _nextUpdate = TimeSpan.Zero;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if (_timing.CurTime < _nextUpdate)
+ return;
+
+ _nextUpdate = _timing.CurTime + _updateFrequency;
+
RefreshObjectList();
}
@@ -81,32 +107,70 @@ private void RefreshObjectList(ObjectsTabSelection selection)
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
}
- foreach (var control in _objects)
+ entities.Sort((a, b) =>
{
- ObjectList.RemoveChild(control);
- }
+ var valueA = GetComparableValue(a, _headerClicked);
+ var valueB = GetComparableValue(b, _headerClicked);
+ return _ascending ? Comparer
[GenerateTypedNameReferences]
- public sealed partial class ReagentDispenserWindow : DefaultWindow
+ public sealed partial class ReagentDispenserWindow : FancyWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- public event Action? OnDispenseReagentButtonPressed;
- public event Action? OnDispenseReagentButtonMouseEntered;
- public event Action? OnDispenseReagentButtonMouseExited;
-
- public event Action? OnEjectJugButtonPressed;
- public event Action? OnEjectJugButtonMouseEntered;
- public event Action? OnEjectJugButtonMouseExited;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ public event Action? OnDispenseReagentButtonPressed;
+ public event Action? OnEjectJugButtonPressed;
///
/// Create and initialize the dispenser UI client-side. Creates the basic layout,
@@ -35,44 +29,27 @@ public ReagentDispenserWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
-
- var dispenseAmountGroup = new ButtonGroup();
- DispenseButton1.Group = dispenseAmountGroup;
- DispenseButton5.Group = dispenseAmountGroup;
- DispenseButton10.Group = dispenseAmountGroup;
- DispenseButton15.Group = dispenseAmountGroup;
- DispenseButton20.Group = dispenseAmountGroup;
- DispenseButton25.Group = dispenseAmountGroup;
- DispenseButton30.Group = dispenseAmountGroup;
- DispenseButton50.Group = dispenseAmountGroup;
- DispenseButton100.Group = dispenseAmountGroup;
}
///
/// Update the button grid of reagents which can be dispensed.
///
/// Reagents which can be dispensed by this dispenser
- public void UpdateReagentsList(List>> inventory)
+ public void UpdateReagentsList(List inventory)
{
- if (ChemicalList == null)
+ if (ReagentList == null)
return;
- ChemicalList.Children.Clear();
+ ReagentList.Children.Clear();
//Sort inventory by reagentLabel
- inventory.Sort((x, y) => x.Value.Key.CompareTo(y.Value.Key));
+ inventory.Sort((x, y) => x.ReagentLabel.CompareTo(y.ReagentLabel));
- foreach (KeyValuePair> entry in inventory)
+ foreach (var item in inventory)
{
- var button = new DispenseReagentButton(entry.Key, entry.Value.Key, entry.Value.Value);
- button.OnPressed += args => OnDispenseReagentButtonPressed?.Invoke(args, button);
- button.OnMouseEntered += args => OnDispenseReagentButtonMouseEntered?.Invoke(args, button);
- button.OnMouseExited += args => OnDispenseReagentButtonMouseExited?.Invoke(args, button);
- ChemicalList.AddChild(button);
- var ejectButton = new EjectJugButton(entry.Key);
- ejectButton.OnPressed += args => OnEjectJugButtonPressed?.Invoke(args, ejectButton);
- ejectButton.OnMouseEntered += args => OnEjectJugButtonMouseEntered?.Invoke(args, ejectButton);
- ejectButton.OnMouseExited += args => OnEjectJugButtonMouseExited?.Invoke(args, ejectButton);
- ChemicalList.AddChild(ejectButton);
+ var card = new ReagentCardControl(item);
+ card.OnPressed += OnDispenseReagentButtonPressed;
+ card.OnEjectButtonPressed += OnEjectJugButtonPressed;
+ ReagentList.Children.Add(card);
}
}
@@ -86,40 +63,14 @@ public void UpdateState(BoundUserInterfaceState state)
UpdateContainerInfo(castState);
UpdateReagentsList(castState.Inventory);
+ _entityManager.TryGetEntity(castState.OutputContainerEntity, out var outputContainerEnt);
+ View.SetEntity(outputContainerEnt);
+
// Disable the Clear & Eject button if no beaker
ClearButton.Disabled = castState.OutputContainer is null;
EjectButton.Disabled = castState.OutputContainer is null;
- switch (castState.SelectedDispenseAmount)
- {
- case ReagentDispenserDispenseAmount.U1:
- DispenseButton1.Pressed = true;
- break;
- case ReagentDispenserDispenseAmount.U5:
- DispenseButton5.Pressed = true;
- break;
- case ReagentDispenserDispenseAmount.U10:
- DispenseButton10.Pressed = true;
- break;
- case ReagentDispenserDispenseAmount.U15:
- DispenseButton15.Pressed = true;
- break;
- case ReagentDispenserDispenseAmount.U20:
- DispenseButton20.Pressed = true;
- break;
- case ReagentDispenserDispenseAmount.U25:
- DispenseButton25.Pressed = true;
- break;
- case ReagentDispenserDispenseAmount.U30:
- DispenseButton30.Pressed = true;
- break;
- case ReagentDispenserDispenseAmount.U50:
- DispenseButton50.Pressed = true;
- break;
- case ReagentDispenserDispenseAmount.U100:
- DispenseButton100.Pressed = true;
- break;
- }
+ AmountGrid.Selected = ((int)castState.SelectedDispenseAmount).ToString();
}
///
@@ -134,23 +85,15 @@ public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state)
if (state.OutputContainer is null)
{
- ContainerInfo.Children.Add(new Label {Text = Loc.GetString("reagent-dispenser-window-no-container-loaded-text") });
+ ContainerInfoName.Text = "";
+ ContainerInfoFill.Text = "";
+ ContainerInfo.Children.Add(new Label { Text = Loc.GetString("reagent-dispenser-window-no-container-loaded-text") });
return;
}
- ContainerInfo.Children.Add(new BoxContainer // Name of the container and its fill status (Ex: 44/100u)
- {
- Orientation = LayoutOrientation.Horizontal,
- Children =
- {
- new Label {Text = $"{state.OutputContainer.DisplayName}: "},
- new Label
- {
- Text = $"{state.OutputContainer.CurrentVolume}/{state.OutputContainer.MaxVolume}",
- StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
- }
- }
- });
+ // Set Name of the container and its fill status (Ex: 44/100u)
+ ContainerInfoName.Text = state.OutputContainer.DisplayName;
+ ContainerInfoFill.Text = state.OutputContainer.CurrentVolume + "/" + state.OutputContainer.MaxVolume;
foreach (var (reagent, quantity) in state.OutputContainer.Reagents!)
{
@@ -159,11 +102,11 @@ public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state)
? p.LocalizedName
: Loc.GetString("reagent-dispenser-window-reagent-name-not-found-text");
- var nameLabel = new Label {Text = $"{localizedName}: "};
+ var nameLabel = new Label { Text = $"{localizedName}: " };
var quantityLabel = new Label
{
Text = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", quantity)),
- StyleClasses = {StyleNano.StyleClassLabelSecondaryColor},
+ StyleClasses = { StyleNano.StyleClassLabelSecondaryColor },
};
ContainerInfo.Children.Add(new BoxContainer
@@ -178,28 +121,4 @@ public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state)
}
}
}
-
- public sealed class DispenseReagentButton : Button
- {
- public string ReagentId { get; }
-
- public DispenseReagentButton(string reagentId, string text, string amount)
- {
- AddStyleClass("OpenRight");
- ReagentId = reagentId;
- Text = text + " " + amount;
- }
- }
-
- public sealed class EjectJugButton : Button
- {
- public string ReagentId { get; }
-
- public EjectJugButton(string reagentId)
- {
- AddStyleClass("OpenLeft");
- ReagentId = reagentId;
- Text = "⏏";
- }
- }
}
diff --git a/Content.Client/Chemistry/UI/SolutionStatusControl.cs b/Content.Client/Chemistry/UI/SolutionStatusControl.cs
new file mode 100644
index 0000000000..1a33ffb0e1
--- /dev/null
+++ b/Content.Client/Chemistry/UI/SolutionStatusControl.cs
@@ -0,0 +1,59 @@
+using Content.Client.Chemistry.Components;
+using Content.Client.Chemistry.EntitySystems;
+using Content.Client.Items.UI;
+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.Controls;
+
+namespace Content.Client.Chemistry.UI;
+
+///
+/// Displays basic solution information for .
+///
+///
+public sealed class SolutionStatusControl : PollingItemStatusControl
+{
+ private readonly Entity _parent;
+ private readonly IEntityManager _entityManager;
+ private readonly SharedSolutionContainerSystem _solutionContainers;
+ private readonly RichTextLabel _label;
+
+ public SolutionStatusControl(
+ Entity parent,
+ IEntityManager entityManager,
+ SharedSolutionContainerSystem solutionContainers)
+ {
+ _parent = parent;
+ _entityManager = entityManager;
+ _solutionContainers = solutionContainers;
+ _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
+ AddChild(_label);
+ }
+
+ protected override Data PollData()
+ {
+ if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.Solution, out _, out var solution))
+ return default;
+
+ FixedPoint2? transferAmount = null;
+ if (_entityManager.TryGetComponent(_parent.Owner, out SolutionTransferComponent? transfer))
+ transferAmount = transfer.TransferAmount;
+
+ return new Data(solution.Volume, solution.MaxVolume, transferAmount);
+ }
+
+ protected override void Update(in Data data)
+ {
+ var markup = Loc.GetString("solution-status-volume",
+ ("currentVolume", data.Volume),
+ ("maxVolume", data.MaxVolume));
+ if (data.TransferVolume is { } transferVolume)
+ markup += "\n" + Loc.GetString("solution-status-transfer", ("volume", transferVolume));
+ _label.SetMarkup(markup);
+ }
+
+ public readonly record struct Data(FixedPoint2 Volume, FixedPoint2 MaxVolume, FixedPoint2? TransferVolume);
+}
diff --git a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
index f1e8e8d7aa..17b88fb5a8 100644
--- a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
+++ b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
@@ -3,6 +3,7 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Hands;
+using Content.Shared.Item;
using Content.Shared.Rounding;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
@@ -150,6 +151,9 @@ private void OnGetHeldVisuals(EntityUid uid, SolutionContainerVisualsComponent c
if (!TryComp(uid, out AppearanceComponent? appearance))
return;
+ if (!TryComp(uid, out var item))
+ return;
+
if (!AppearanceSystem.TryGetData(uid, SolutionContainerVisuals.FillFraction, out var fraction, appearance))
return;
@@ -159,7 +163,8 @@ private void OnGetHeldVisuals(EntityUid uid, SolutionContainerVisualsComponent c
{
var layer = new PrototypeLayerData();
- var key = "inhand-" + args.Location.ToString().ToLowerInvariant() + component.InHandsFillBaseName + closestFillSprite;
+ var heldPrefix = item.HeldPrefix == null ? "inhand-" : $"{item.HeldPrefix}-inhand-";
+ var key = heldPrefix + args.Location.ToString().ToLowerInvariant() + component.InHandsFillBaseName + closestFillSprite;
layer.State = key;
diff --git a/Content.Client/Clickable/ClickableComponent.cs b/Content.Client/Clickable/ClickableComponent.cs
index cfbd1a99d6..6f75df4683 100644
--- a/Content.Client/Clickable/ClickableComponent.cs
+++ b/Content.Client/Clickable/ClickableComponent.cs
@@ -38,9 +38,9 @@ public bool CheckClick(SpriteComponent sprite, TransformComponent transform, Ent
renderOrder = sprite.RenderOrder;
var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery);
var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
- bottom = Matrix3.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
+ bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
- var invSpriteMatrix = Matrix3.Invert(sprite.GetLocalMatrix());
+ Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
// This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
@@ -48,8 +48,8 @@ public bool CheckClick(SpriteComponent sprite, TransformComponent transform, Ent
Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
// First we get `localPos`, the clicked location in the sprite-coordinate frame.
- var entityXform = Matrix3.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
- var localPos = invSpriteMatrix.Transform(entityXform.Transform(worldPos));
+ var entityXform = Matrix3Helpers.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
+ var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
// Check explicitly defined click-able bounds
if (CheckDirBound(sprite, relativeRotation, localPos))
@@ -79,8 +79,8 @@ public bool CheckClick(SpriteComponent sprite, TransformComponent transform, Ent
// convert to layer-local coordinates
layer.GetLayerDrawMatrix(dir, out var matrix);
- var inverseMatrix = Matrix3.Invert(matrix);
- var layerLocal = inverseMatrix.Transform(localPos);
+ Matrix3x2.Invert(matrix, out var inverseMatrix);
+ var layerLocal = Vector2.Transform(localPos, inverseMatrix);
// Convert to image coordinates
var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);
diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs
index eccc94661c..6c4adaafca 100644
--- a/Content.Client/Clothing/ClientClothingSystem.cs
+++ b/Content.Client/Clothing/ClientClothingSystem.cs
@@ -11,6 +11,7 @@
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
+using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Utility;
using static Robust.Client.GameObjects.SpriteComponent;
@@ -47,6 +48,7 @@ public sealed class ClientClothingSystem : ClothingSystem
};
[Dependency] private readonly IResourceCache _cache = default!;
+ [Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
public override void Initialize()
@@ -134,7 +136,7 @@ private bool TryGetDefaultVisuals(EntityUid uid, ClothingComponent clothing, str
else if (TryComp(uid, out SpriteComponent? sprite))
rsi = sprite.BaseRSI;
- if (rsi == null || rsi.Path == null)
+ if (rsi == null)
return false;
var correctedSlot = slot;
@@ -266,13 +268,14 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
// temporary, until layer draw depths get added. Basically: a layer with the key "slot" is being used as a
// bookmark to determine where in the list of layers we should insert the clothing layers.
bool slotLayerExists = sprite.LayerMapTryGet(slot, out var index);
+ var displacementData = inventory.Displacements.GetValueOrDefault(slot);
// add the new layers
foreach (var (key, layerData) in ev.Layers)
{
if (!revealedLayers.Add(key))
{
- Logger.Warning($"Duplicate key for clothing visuals: {key}. Are multiple components attempting to modify the same layer? Equipment: {ToPrettyString(equipment)}");
+ Log.Warning($"Duplicate key for clothing visuals: {key}. Are multiple components attempting to modify the same layer? Equipment: {ToPrettyString(equipment)}");
continue;
}
@@ -282,6 +285,9 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
// note that every insertion requires reshuffling & remapping all the existing layers.
sprite.AddBlankLayer(index);
sprite.LayerMapSet(key, index);
+
+ if (layerData.Color != null)
+ sprite.LayerSetColor(key, layerData.Color.Value);
}
else
index = sprite.LayerMapReserveBlank(key);
@@ -306,6 +312,28 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
sprite.LayerSetData(index, layerData);
layer.Offset += slotDef.Offset;
+
+ if (displacementData != null)
+ {
+ if (displacementData.ShaderOverride != null)
+ sprite.LayerSetShader(index, displacementData.ShaderOverride);
+
+ var displacementKey = $"{key}-displacement";
+ if (!revealedLayers.Add(displacementKey))
+ {
+ Log.Warning($"Duplicate key for clothing visuals DISPLACEMENT: {displacementKey}.");
+ continue;
+ }
+
+ var displacementLayer = _serialization.CreateCopy(displacementData.Layer, notNullableOverride: true);
+ displacementLayer.CopyToShaderParameters!.LayerKey = key;
+
+ // Add before main layer for this item.
+ sprite.AddLayer(displacementLayer, index);
+ sprite.LayerMapSet(displacementKey, index);
+
+ revealedLayers.Add(displacementKey);
+ }
}
RaiseLocalEvent(equipment, new EquipmentVisualsUpdatedEvent(equipee, slot, revealedLayers), true);
diff --git a/Content.Client/Clothing/MagbootsSystem.cs b/Content.Client/Clothing/MagbootsSystem.cs
deleted file mode 100644
index a3d39eafde..0000000000
--- a/Content.Client/Clothing/MagbootsSystem.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Content.Shared.Clothing;
-
-namespace Content.Client.Clothing;
-
-public sealed class MagbootsSystem : SharedMagbootsSystem
-{
-
-}
diff --git a/Content.Client/Commands/ShowHealthBarsCommand.cs b/Content.Client/Commands/ShowHealthBarsCommand.cs
index bd3e21718f..0811f96663 100644
--- a/Content.Client/Commands/ShowHealthBarsCommand.cs
+++ b/Content.Client/Commands/ShowHealthBarsCommand.cs
@@ -35,6 +35,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
var showHealthBarsComponent = new ShowHealthBarsComponent
{
DamageContainers = args.ToList(),
+ HealthStatusIcon = null,
NetSyncEnabled = false
};
diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs
index 90643e45cf..4d8dd86a4d 100644
--- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs
+++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs
@@ -1,7 +1,9 @@
using Content.Client.UserInterface.Controls;
using System.Threading;
+using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
@@ -13,6 +15,8 @@ public sealed partial class CommunicationsConsoleMenu : FancyWindow
private CommunicationsConsoleBoundUserInterface Owner { get; set; }
private readonly CancellationTokenSource _timerCancelTokenSource = new();
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
{
IoCManager.InjectDependencies(this);
@@ -23,6 +27,22 @@ public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
var loc = IoCManager.Resolve();
MessageInput.Placeholder = new Rope.Leaf(loc.GetString("comms-console-menu-announcement-placeholder"));
+ var maxAnnounceLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
+ MessageInput.OnTextChanged += (args) =>
+ {
+ if (args.Control.TextLength > maxAnnounceLength)
+ {
+ AnnounceButton.Disabled = true;
+ AnnounceButton.ToolTip = Loc.GetString("comms-console-message-too-long");
+ }
+ else
+ {
+ AnnounceButton.Disabled = !owner.CanAnnounce;
+ AnnounceButton.ToolTip = null;
+
+ }
+ };
+
AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(Rope.Collapse(MessageInput.TextRope));
AnnounceButton.Disabled = !owner.CanAnnounce;
diff --git a/Content.Client/Construction/ConstructionSystem.cs b/Content.Client/Construction/ConstructionSystem.cs
index ae1724c3bf..453658bebf 100644
--- a/Content.Client/Construction/ConstructionSystem.cs
+++ b/Content.Client/Construction/ConstructionSystem.cs
@@ -27,6 +27,8 @@ public sealed class ConstructionSystem : SharedConstructionSystem
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
+ [Dependency] private readonly ExamineSystemShared _examineSystem = default!;
+ [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
private readonly Dictionary _ghosts = new();
@@ -194,9 +196,8 @@ public bool TrySpawnGhost(
if (GhostPresent(loc))
return false;
- // This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?"
- var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager));
- if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate))
+ var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
+ if (!_examineSystem.InRangeUnOccluded(user, loc, 20f, predicate: predicate))
return false;
if (!CheckConstructionConditions(prototype, loc, dir, user, showPopup: true))
diff --git a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
index 9a09436176..0c7912e0bc 100644
--- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
+++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
@@ -2,6 +2,7 @@
using Content.Client.UserInterface.Systems.MenuBar.Widgets;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Tag;
+using Content.Shared.Whitelist;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Placement;
@@ -23,6 +24,7 @@ namespace Content.Client.Construction.UI
///
internal sealed class ConstructionMenuPresenter : IDisposable
{
+ [Dependency] private readonly EntityManager _entManager = default!;
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
@@ -30,6 +32,7 @@ internal sealed class ConstructionMenuPresenter : IDisposable
[Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly IConstructionMenuView _constructionView;
+ private readonly EntityWhitelistSystem _whitelistSystem;
private ConstructionSystem? _constructionSystem;
private ConstructionPrototype? _selected;
@@ -78,6 +81,7 @@ public ConstructionMenuPresenter()
// This is a lot easier than a factory
IoCManager.InjectDependencies(this);
_constructionView = new ConstructionMenu();
+ _whitelistSystem = _entManager.System();
// 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))
@@ -157,7 +161,7 @@ private void OnViewPopulateRecipes(object? sender, (string search, string catago
if (_playerManager.LocalSession == null
|| _playerManager.LocalEntity == null
- || (recipe.EntityWhitelist != null && !recipe.EntityWhitelist.IsValid(_playerManager.LocalEntity.Value)))
+ || _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value))
continue;
if (!string.IsNullOrEmpty(search))
diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml
index eec5c229cb..322e4e66a9 100644
--- a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml
+++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml
@@ -8,7 +8,7 @@
-
+
diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
index ad19bc30f4..9f3d5695bb 100644
--- a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
+++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
@@ -23,7 +23,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
private readonly ItemSlotsSystem _itemSlots;
private readonly FlatpackSystem _flatpack;
private readonly MaterialStorageSystem _materialStorage;
- private readonly SpriteSystem _spriteSystem;
private readonly EntityUid _owner;
@@ -31,7 +30,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
public const string NoBoardEffectId = "FlatpackerNoBoardEffect";
private EntityUid? _currentBoard = EntityUid.Invalid;
- private EntityUid? _machinePreview;
public event Action? PackButtonPressed;
@@ -43,7 +41,6 @@ public FlatpackCreatorMenu(EntityUid uid)
_itemSlots = _entityManager.System();
_flatpack = _entityManager.System();
_materialStorage = _entityManager.System();
- _spriteSystem = _entityManager.System();
_owner = uid;
@@ -57,17 +54,10 @@ protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
- if (_machinePreview is not { } && _entityManager.Deleted(_machinePreview))
- {
- _machinePreview = null;
- MachineSprite.SetEntity(_machinePreview);
- }
-
if (!_entityManager.TryGetComponent(_owner, out var flatpacker) ||
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
return;
- MachineBoardComponent? machineBoardComp = null;
if (flatpacker.Packing)
{
PackButton.Disabled = true;
@@ -75,11 +65,10 @@ protected override void FrameUpdate(FrameEventArgs args)
else if (_currentBoard != null)
{
Dictionary cost;
- if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp) &&
- machineBoardComp.Prototype is not null)
+ if (_entityManager.TryGetComponent(_currentBoard, out var machineBoardComp))
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
else
- cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker));
+ cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
}
@@ -87,9 +76,6 @@ protected override void FrameUpdate(FrameEventArgs args)
if (_currentBoard == itemSlot.Item)
return;
- if (_machinePreview != null)
- _entityManager.DeleteEntity(_machinePreview);
-
_currentBoard = itemSlot.Item;
CostHeaderLabel.Visible = _currentBoard != null;
InsertLabel.Visible = _currentBoard == null;
@@ -99,35 +85,32 @@ protected override void FrameUpdate(FrameEventArgs args)
string? prototype = null;
Dictionary? cost = null;
- if (machineBoardComp != null || _entityManager.TryGetComponent(_currentBoard, out machineBoardComp))
+ if (_entityManager.TryGetComponent(_currentBoard, out var newMachineBoardComp))
{
- prototype = machineBoardComp.Prototype;
- cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
+ 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));
+ cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
}
if (prototype is not null && cost is not null)
{
var proto = _prototypeManager.Index(prototype);
- _machinePreview = _entityManager.Spawn(proto.ID);
- _spriteSystem.ForceUpdate(_machinePreview.Value);
+ MachineSprite.SetPrototype(prototype);
MachineNameLabel.SetMessage(proto.Name);
CostLabel.SetMarkup(GetCostString(cost));
}
}
else
{
- _machinePreview = _entityManager.Spawn(NoBoardEffectId);
+ MachineSprite.SetPrototype(NoBoardEffectId);
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
MachineNameLabel.SetMessage(" ");
PackButton.Disabled = true;
}
-
- MachineSprite.SetEntity(_machinePreview);
}
private string GetCostString(Dictionary costs)
@@ -149,7 +132,7 @@ private string GetCostString(Dictionary costs)
("amount", amountText),
("material", Loc.GetString(matProto.Name)));
- msg.AddMarkup(text);
+ msg.TryAddMarkup(text, out _);
if (i != orderedCosts.Length - 1)
msg.PushNewline();
@@ -157,12 +140,4 @@ private string GetCostString(Dictionary costs)
return msg.ToMarkup();
}
-
- public override void Close()
- {
- base.Close();
-
- _entityManager.DeleteEntity(_machinePreview);
- _machinePreview = null;
- }
}
diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index 0e15fadfae..956f2fd035 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -2,7 +2,7 @@
$(TargetFramework)
- 11
+ 12
false
false
..\bin\Content.Client\
diff --git a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
index ae1b3ec3bf..a60619baa3 100644
--- a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
+++ b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs
@@ -170,7 +170,7 @@ private bool HandleOpenEntityMenu(in PointerInputCmdHandler.PointerInputCmdArgs
if (_combatMode.IsInCombatMode(args.Session?.AttachedEntity))
return false;
- var coords = args.Coordinates.ToMap(_entityManager);
+ var coords = args.Coordinates.ToMap(_entityManager, _xform);
if (_verbSystem.TryGetEntityMenuEntities(coords, out var entities))
OpenRootMenu(entities);
diff --git a/Content.Client/CrewManifest/UI/CrewManifestListing.cs b/Content.Client/CrewManifest/UI/CrewManifestListing.cs
index 614add536e..03d8b7168f 100644
--- a/Content.Client/CrewManifest/UI/CrewManifestListing.cs
+++ b/Content.Client/CrewManifest/UI/CrewManifestListing.cs
@@ -1,18 +1,14 @@
-using Content.Shared.CCVar;
-using Content.Shared.CrewManifest;
+using Content.Shared.CrewManifest;
using Content.Shared.Roles;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
-using System.Linq;
namespace Content.Client.CrewManifest.UI;
public sealed class CrewManifestListing : BoxContainer
{
- [Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly SpriteSystem _spriteSystem;
@@ -25,7 +21,7 @@ public CrewManifestListing()
public void AddCrewManifestEntries(CrewManifestEntries entries)
{
- var entryDict = new Dictionary>();
+ var entryDict = new Dictionary>();
foreach (var entry in entries.Entries)
{
@@ -34,37 +30,19 @@ public void AddCrewManifestEntries(CrewManifestEntries entries)
// this is a little expensive, and could be better
if (department.Roles.Contains(entry.JobPrototype))
{
- entryDict.GetOrNew(department.ID).Add(entry);
+ entryDict.GetOrNew(department).Add(entry);
}
}
}
- var entryList = new List<(string section, List entries)>();
+ var entryList = new List<(DepartmentPrototype section, List entries)>();
foreach (var (section, listing) in entryDict)
{
entryList.Add((section, listing));
}
- var sortOrder = _configManager.GetCVar(CCVars.CrewManifestOrdering).Split(",").ToList();
-
- entryList.Sort((a, b) =>
- {
- var ai = sortOrder.IndexOf(a.section);
- var bi = sortOrder.IndexOf(b.section);
-
- // this is up here so -1 == -1 occurs first
- if (ai == bi)
- return 0;
-
- if (ai == -1)
- return -1;
-
- if (bi == -1)
- return 1;
-
- return ai.CompareTo(bi);
- });
+ entryList.Sort((a, b) => DepartmentUIComparer.Instance.Compare(a.section, b.section));
foreach (var item in entryList)
{
diff --git a/Content.Client/CrewManifest/UI/CrewManifestSection.cs b/Content.Client/CrewManifest/UI/CrewManifestSection.cs
index 9b51b5d424..4b1b02cb75 100644
--- a/Content.Client/CrewManifest/UI/CrewManifestSection.cs
+++ b/Content.Client/CrewManifest/UI/CrewManifestSection.cs
@@ -4,24 +4,25 @@
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
using System.Numerics;
+using Content.Shared.Roles;
namespace Content.Client.CrewManifest.UI;
public sealed class CrewManifestSection : BoxContainer
{
- public CrewManifestSection(IPrototypeManager prototypeManager, SpriteSystem spriteSystem, string sectionTitle,
+ public CrewManifestSection(
+ IPrototypeManager prototypeManager,
+ SpriteSystem spriteSystem,
+ DepartmentPrototype section,
List entries)
{
Orientation = LayoutOrientation.Vertical;
HorizontalExpand = true;
- if (Loc.TryGetString($"department-{sectionTitle}", out var localizedDepart))
- sectionTitle = localizedDepart;
-
AddChild(new Label()
{
StyleClasses = { "LabelBig" },
- Text = Loc.GetString(sectionTitle)
+ Text = Loc.GetString($"department-{section.ID}")
});
var gridContainer = new GridContainer()
@@ -55,8 +56,9 @@ public CrewManifestSection(IPrototypeManager prototypeManager, SpriteSystem spri
var icon = new TextureRect()
{
TextureScale = new Vector2(2, 2),
- Stretch = TextureRect.StretchMode.KeepCentered,
+ VerticalAlignment = VAlignment.Center,
Texture = spriteSystem.Frame0(jobIcon.Icon),
+ Margin = new Thickness(0, 0, 4, 0)
};
titleContainer.AddChild(icon);
diff --git a/Content.Client/CriminalRecords/CriminalRecordsConsoleBoundUserInterface.cs b/Content.Client/CriminalRecords/CriminalRecordsConsoleBoundUserInterface.cs
index 88b9c90c2f..9047624f49 100644
--- a/Content.Client/CriminalRecords/CriminalRecordsConsoleBoundUserInterface.cs
+++ b/Content.Client/CriminalRecords/CriminalRecordsConsoleBoundUserInterface.cs
@@ -37,8 +37,8 @@ protected override void Open()
SendMessage(new SetStationRecordFilter(type, filterValue));
_window.OnStatusSelected += status =>
SendMessage(new CriminalRecordChangeStatus(status, null));
- _window.OnDialogConfirmed += (_, reason) =>
- SendMessage(new CriminalRecordChangeStatus(SecurityStatus.Wanted, reason));
+ _window.OnDialogConfirmed += (status, reason) =>
+ SendMessage(new CriminalRecordChangeStatus(status, reason));
_window.OnHistoryUpdated += UpdateHistory;
_window.OnHistoryClosed += () => _historyWindow?.Close();
_window.OnClose += Close;
diff --git a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs
index 61b5c38331..b259e08e72 100644
--- a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs
+++ b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs
@@ -219,16 +219,16 @@ private void FilterListingOfRecords(string text = "")
private void SetStatus(SecurityStatus status)
{
- if (status == SecurityStatus.Wanted)
+ if (status == SecurityStatus.Wanted || status == SecurityStatus.Suspected)
{
- GetWantedReason();
+ GetReason(status);
return;
}
OnStatusSelected?.Invoke(status);
}
- private void GetWantedReason()
+ private void GetReason(SecurityStatus status)
{
if (_reasonDialog != null)
{
@@ -237,7 +237,7 @@ private void GetWantedReason()
}
var field = "reason";
- var title = Loc.GetString("criminal-records-status-wanted");
+ var title = Loc.GetString("criminal-records-status-" + status.ToString().ToLower());
var placeholders = _proto.Index(ReasonPlaceholders);
var placeholder = Loc.GetString("criminal-records-console-reason-placeholder", ("placeholder", _random.Pick(placeholders.Values))); // just funny it doesn't actually get used
var prompt = Loc.GetString("criminal-records-console-reason");
@@ -251,7 +251,7 @@ private void GetWantedReason()
if (reason.Length < 1 || reason.Length > _maxLength)
return;
- OnDialogConfirmed?.Invoke(SecurityStatus.Wanted, reason);
+ OnDialogConfirmed?.Invoke(status, reason);
};
_reasonDialog.OnClose += () => { _reasonDialog = null; };
diff --git a/Content.Client/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs b/Content.Client/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs
index 2a34376f6a..4bf9d4c3e1 100644
--- a/Content.Client/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs
+++ b/Content.Client/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs
@@ -2,6 +2,4 @@
namespace Content.Client.CriminalRecords.Systems;
-public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleSystem
-{
-}
+public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleSystem;
diff --git a/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs b/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs
new file mode 100644
index 0000000000..c895a00c88
--- /dev/null
+++ b/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.CriminalRecords.Systems;
+
+namespace Content.Client.CriminalRecords.Systems;
+
+public sealed class CriminalRecordsHackerSystem : SharedCriminalRecordsHackerSystem;
diff --git a/Content.Client/CriminalRecords/Systems/CriminalRecordsSystem.cs b/Content.Client/CriminalRecords/Systems/CriminalRecordsSystem.cs
new file mode 100644
index 0000000000..c0b98d7ce3
--- /dev/null
+++ b/Content.Client/CriminalRecords/Systems/CriminalRecordsSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.CriminalRecords.Systems;
+
+namespace Content.Client.CriminalRecords.Systems;
+
+public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem;
diff --git a/Content.Client/Decals/DecalSystem.cs b/Content.Client/Decals/DecalSystem.cs
index be442ab8a0..41e5f39c28 100644
--- a/Content.Client/Decals/DecalSystem.cs
+++ b/Content.Client/Decals/DecalSystem.cs
@@ -50,48 +50,49 @@ public override void Shutdown()
protected override void OnDecalRemoved(EntityUid gridId, uint decalId, DecalGridComponent component, Vector2i indices, DecalChunk chunk)
{
base.OnDecalRemoved(gridId, decalId, component, indices, chunk);
-
- if (!component.DecalZIndexIndex.Remove(decalId, out var zIndex))
- return;
-
- if (!component.DecalRenderIndex.TryGetValue(zIndex, out var renderIndex))
- return;
-
- renderIndex.Remove(decalId);
- if (renderIndex.Count == 0)
- component.DecalRenderIndex.Remove(zIndex);
+ DebugTools.Assert(chunk.Decals.ContainsKey(decalId));
+ chunk.Decals.Remove(decalId);
}
private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args)
{
- if (args.Current is not DecalGridState state)
- return;
-
// is this a delta or full state?
_removedChunks.Clear();
+ Dictionary modifiedChunks;
- if (!state.FullState)
+ switch (args.Current)
{
- foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
+ case DecalGridDeltaState delta:
{
- if (!state.AllChunks!.Contains(key))
- _removedChunks.Add(key);
+ modifiedChunks = delta.ModifiedChunks;
+ foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
+ {
+ if (!delta.AllChunks.Contains(key))
+ _removedChunks.Add(key);
+ }
+
+ break;
}
- }
- else
- {
- foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
+ case DecalGridState state:
{
- if (!state.Chunks.ContainsKey(key))
- _removedChunks.Add(key);
+ modifiedChunks = state.Chunks;
+ foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
+ {
+ if (!state.Chunks.ContainsKey(key))
+ _removedChunks.Add(key);
+ }
+
+ break;
}
+ default:
+ return;
}
if (_removedChunks.Count > 0)
RemoveChunks(gridUid, gridComp, _removedChunks);
- if (state.Chunks.Count > 0)
- UpdateChunks(gridUid, gridComp, state.Chunks);
+ if (modifiedChunks.Count > 0)
+ UpdateChunks(gridUid, gridComp, modifiedChunks);
}
private void OnChunkUpdate(DecalChunkUpdateEvent ev)
@@ -133,8 +134,6 @@ private void OnChunkUpdate(DecalChunkUpdateEvent ev)
private void UpdateChunks(EntityUid gridId, DecalGridComponent gridComp, Dictionary updatedGridChunks)
{
var chunkCollection = gridComp.ChunkCollection.ChunkCollection;
- var renderIndex = gridComp.DecalRenderIndex;
- var zIndexIndex = gridComp.DecalZIndexIndex;
// Update any existing data / remove decals we didn't receive data for.
foreach (var (indices, newChunkData) in updatedGridChunks)
@@ -155,11 +154,6 @@ private void UpdateChunks(EntityUid gridId, DecalGridComponent gridComp, Diction
foreach (var (uid, decal) in newChunkData.Decals)
{
- if (zIndexIndex.TryGetValue(uid, out var zIndex))
- renderIndex[zIndex].Remove(uid);
-
- renderIndex.GetOrNew(decal.ZIndex)[uid] = decal;
- zIndexIndex[uid] = decal.ZIndex;
gridComp.DecalIndex[uid] = indices;
}
}
diff --git a/Content.Client/Decals/Overlays/DecalOverlay.cs b/Content.Client/Decals/Overlays/DecalOverlay.cs
index 8eb1a25664..0de3301e58 100644
--- a/Content.Client/Decals/Overlays/DecalOverlay.cs
+++ b/Content.Client/Decals/Overlays/DecalOverlay.cs
@@ -1,8 +1,10 @@
+using System.Numerics;
using Content.Shared.Decals;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Map;
+using Robust.Shared.Map.Enumerators;
using Robust.Shared.Prototypes;
namespace Content.Client.Decals.Overlays
@@ -15,6 +17,8 @@ public sealed class DecalOverlay : GridOverlay
private readonly Dictionary _cachedTextures = new(64);
+ private readonly List<(uint Id, Decal Decal)> _decals = new();
+
public DecalOverlay(
SpriteSystem sprites,
IEntityManager entManager,
@@ -30,10 +34,10 @@ protected override void Draw(in OverlayDrawArgs args)
if (args.MapId == MapId.Nullspace)
return;
- var grid = Grid;
+ var owner = Grid.Owner;
- if (!_entManager.TryGetComponent(grid, out DecalGridComponent? decalGrid) ||
- !_entManager.TryGetComponent(grid, out TransformComponent? xform))
+ if (!_entManager.TryGetComponent(owner, out DecalGridComponent? decalGrid) ||
+ !_entManager.TryGetComponent(owner, out TransformComponent? xform))
{
return;
}
@@ -46,49 +50,71 @@ protected override void Draw(in OverlayDrawArgs args)
var xformSystem = _entManager.System();
var eyeAngle = args.Viewport.Eye?.Rotation ?? Angle.Zero;
- var zIndexDictionary = decalGrid.DecalRenderIndex;
+ var gridAABB = xformSystem.GetInvWorldMatrix(xform).TransformBox(args.WorldBounds.Enlarged(1f));
+ var chunkEnumerator = new ChunkIndicesEnumerator(gridAABB, SharedDecalSystem.ChunkSize);
+ _decals.Clear();
+
+ while (chunkEnumerator.MoveNext(out var index))
+ {
+ if (!decalGrid.ChunkCollection.ChunkCollection.TryGetValue(index.Value, out var chunk))
+ continue;
+
+ foreach (var (id, decal) in chunk.Decals)
+ {
+ if (!gridAABB.Contains(decal.Coordinates))
+ continue;
+
+ _decals.Add((id, decal));
+ }
+ }
- if (zIndexDictionary.Count == 0)
+ if (_decals.Count == 0)
return;
- var (_, worldRot, worldMatrix) = xformSystem.GetWorldPositionRotationMatrix(xform);
+ _decals.Sort((x, y) =>
+ {
+ var zComp = x.Decal.ZIndex.CompareTo(y.Decal.ZIndex);
+ if (zComp != 0)
+ return zComp;
+
+ return x.Id.CompareTo(y.Id);
+ });
+
+ var (_, worldRot, worldMatrix) = xformSystem.GetWorldPositionRotationMatrix(xform);
handle.SetTransform(worldMatrix);
- foreach (var decals in zIndexDictionary.Values)
+ foreach (var (_, decal) in _decals)
{
- foreach (var decal in decals.Values)
+ if (!_cachedTextures.TryGetValue(decal.Id, out var cache))
{
- if (!_cachedTextures.TryGetValue(decal.Id, out var cache))
+ // Nothing to cache someone messed up
+ if (!_prototypeManager.TryIndex(decal.Id, out var decalProto))
{
- // Nothing to cache someone messed up
- if (!_prototypeManager.TryIndex(decal.Id, out var decalProto))
- {
- continue;
- }
-
- cache = (_sprites.Frame0(decalProto.Sprite), decalProto.SnapCardinals);
- _cachedTextures[decal.Id] = cache;
+ continue;
}
- var cardinal = Angle.Zero;
-
- if (cache.SnapCardinals)
- {
- var worldAngle = eyeAngle + worldRot;
- cardinal = worldAngle.GetCardinalDir().ToAngle();
- }
+ cache = (_sprites.Frame0(decalProto.Sprite), decalProto.SnapCardinals);
+ _cachedTextures[decal.Id] = cache;
+ }
- var angle = decal.Angle - cardinal;
+ var cardinal = Angle.Zero;
- if (angle.Equals(Angle.Zero))
- handle.DrawTexture(cache.Texture, decal.Coordinates, decal.Color);
- else
- handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color);
+ if (cache.SnapCardinals)
+ {
+ var worldAngle = eyeAngle + worldRot;
+ cardinal = worldAngle.GetCardinalDir().ToAngle();
}
+
+ var angle = decal.Angle - cardinal;
+
+ if (angle.Equals(Angle.Zero))
+ handle.DrawTexture(cache.Texture, decal.Coordinates, decal.Color);
+ else
+ handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color);
}
- handle.SetTransform(Matrix3.Identity);
+ handle.SetTransform(Matrix3x2.Identity);
}
}
}
diff --git a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs
index be277448ed..845bd7c03d 100644
--- a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs
+++ b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs
@@ -51,7 +51,7 @@ protected override void Draw(in OverlayDrawArgs args)
var handle = args.WorldHandle;
handle.SetTransform(worldMatrix);
- var localPos = invMatrix.Transform(mousePos.Position);
+ var localPos = Vector2.Transform(mousePos.Position, invMatrix);
if (snap)
{
@@ -63,6 +63,6 @@ protected override void Draw(in OverlayDrawArgs args)
var box = new Box2Rotated(aabb, rotation, localPos);
handle.DrawTextureRect(_sprite.Frame0(decal.Sprite), box, color);
- handle.SetTransform(Matrix3.Identity);
+ handle.SetTransform(Matrix3x2.Identity);
}
}
diff --git a/Content.Client/Decals/ToggleDecalCommand.cs b/Content.Client/Decals/ToggleDecalCommand.cs
index 9f0851f080..025ed1299d 100644
--- a/Content.Client/Decals/ToggleDecalCommand.cs
+++ b/Content.Client/Decals/ToggleDecalCommand.cs
@@ -5,11 +5,13 @@ namespace Content.Client.Decals;
public sealed class ToggleDecalCommand : IConsoleCommand
{
+ [Dependency] private readonly IEntityManager _e = default!;
+
public string Command => "toggledecals";
public string Description => "Toggles decaloverlay";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
- EntitySystem.Get().ToggleOverlay();
+ _e.System().ToggleOverlay();
}
}
diff --git a/Content.Client/Decals/UI/DecalPlacerWindow.xaml.cs b/Content.Client/Decals/UI/DecalPlacerWindow.xaml.cs
index 1be1751080..21b816515a 100644
--- a/Content.Client/Decals/UI/DecalPlacerWindow.xaml.cs
+++ b/Content.Client/Decals/UI/DecalPlacerWindow.xaml.cs
@@ -16,6 +16,7 @@ namespace Content.Client.Decals.UI;
public sealed partial class DecalPlacerWindow : DefaultWindow
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IEntityManager _e = default!;
private readonly DecalPlacementSystem _decalPlacementSystem;
@@ -39,7 +40,7 @@ public DecalPlacerWindow()
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- _decalPlacementSystem = EntitySystem.Get();
+ _decalPlacementSystem = _e.System();
// This needs to be done in C# so we can have custom stuff passed in the constructor
// and thus have a proper step size
diff --git a/Content.Client/DeviceNetwork/JammerSystem.cs b/Content.Client/DeviceNetwork/JammerSystem.cs
new file mode 100644
index 0000000000..c7dbf8c8fe
--- /dev/null
+++ b/Content.Client/DeviceNetwork/JammerSystem.cs
@@ -0,0 +1,8 @@
+using Content.Shared.Radio.EntitySystems;
+
+namespace Content.Client.DeviceNetwork;
+
+public sealed class JammerSystem : SharedJammerSystem
+{
+
+}
diff --git a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
index 344bd2ec97..2fe56fcce9 100644
--- a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
+++ b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
@@ -22,6 +22,9 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
private const string AnimationKey = "disposal_unit_animation";
+ private const string DefaultFlushState = "disposal-flush";
+ private const string DefaultChargeState = "disposal-charging";
+
public override void Initialize()
{
base.Initialize();
@@ -93,20 +96,24 @@ private void OnAppearanceChange(EntityUid uid, SharedDisposalUnitComponent unit,
private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, SpriteComponent sprite, AppearanceComponent appearance)
{
if (!_appearanceSystem.TryGetData(uid, Visuals.VisualState, out var state, appearance))
- {
return;
- }
sprite.LayerSetVisible(DisposalUnitVisualLayers.Unanchored, state == VisualState.UnAnchored);
sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == VisualState.Anchored);
- sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseFlush, state is VisualState.Flushing or VisualState.Charging);
+ sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFlush, state is VisualState.OverlayFlushing or VisualState.OverlayCharging);
+
+ var chargingState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseCharging, out var chargingLayer)
+ ? sprite.LayerGetState(chargingLayer)
+ : new RSI.StateId(DefaultChargeState);
// This is a transient state so not too worried about replaying in range.
- if (state == VisualState.Flushing)
+ if (state == VisualState.OverlayFlushing)
{
if (!_animationSystem.HasRunningAnimation(uid, AnimationKey))
{
- var flushState = new RSI.StateId("disposal-flush");
+ var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.OverlayFlush, out var flushLayer)
+ ? sprite.LayerGetState(flushLayer)
+ : new RSI.StateId(DefaultFlushState);
// Setup the flush animation to play
var anim = new Animation
@@ -116,7 +123,7 @@ private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, Sprite
{
new AnimationTrackSpriteFlick
{
- LayerKey = DisposalUnitVisualLayers.BaseFlush,
+ LayerKey = DisposalUnitVisualLayers.OverlayFlush,
KeyFrames =
{
// Play the flush animation
@@ -124,7 +131,7 @@ private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, Sprite
// Return to base state (though, depending on how the unit is
// configured we might get an appearance change event telling
// us to go to charging state)
- new AnimationTrackSpriteFlick.KeyFrame("disposal-charging", (float) unit.FlushDelay.TotalSeconds)
+ new AnimationTrackSpriteFlick.KeyFrame(chargingState, (float) unit.FlushDelay.TotalSeconds)
}
},
}
@@ -145,26 +152,18 @@ private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, Sprite
_animationSystem.Play(uid, anim, AnimationKey);
}
}
- else if (state == VisualState.Charging)
- {
- sprite.LayerSetState(DisposalUnitVisualLayers.BaseFlush, new RSI.StateId("disposal-charging"));
- }
+ else if (state == VisualState.OverlayCharging)
+ sprite.LayerSetState(DisposalUnitVisualLayers.OverlayFlush, chargingState);
else
- {
_animationSystem.Stop(uid, AnimationKey);
- }
if (!_appearanceSystem.TryGetData(uid, Visuals.Handle, out var handleState, appearance))
- {
handleState = HandleState.Normal;
- }
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != HandleState.Normal);
if (!_appearanceSystem.TryGetData(uid, Visuals.Light, out var lightState, appearance))
- {
lightState = LightStates.Off;
- }
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayCharging,
(lightState & LightStates.Charging) != 0);
@@ -180,7 +179,7 @@ public enum DisposalUnitVisualLayers : byte
Unanchored,
Base,
BaseCharging,
- BaseFlush,
+ OverlayFlush,
OverlayCharging,
OverlayReady,
OverlayFull,
diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs
index 2957dafdb7..dfbbf10891 100644
--- a/Content.Client/DoAfter/DoAfterOverlay.cs
+++ b/Content.Client/DoAfter/DoAfterOverlay.cs
@@ -1,9 +1,9 @@
using System.Numerics;
using Content.Shared.DoAfter;
+using Content.Client.UserInterface.Systems;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
-using Robust.Shared.Graphics;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -18,9 +18,10 @@ public sealed class DoAfterOverlay : Overlay
private readonly IPlayerManager _player;
private readonly SharedTransformSystem _transform;
private readonly MetaDataSystem _meta;
+ private readonly ProgressColorSystem _progressColor;
private readonly Texture _barTexture;
- private readonly ShaderInstance _shader;
+ private readonly ShaderInstance _unshadedShader;
///
/// Flash time for cancelled DoAfters
@@ -40,10 +41,11 @@ public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager,
_player = player;
_transform = _entManager.EntitySysManager.GetEntitySystem();
_meta = _entManager.EntitySysManager.GetEntitySystem();
+ _progressColor = _entManager.System();
var sprite = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/progress_bar.rsi"), "icon");
_barTexture = _entManager.EntitySysManager.GetEntitySystem().Frame0(sprite);
- _shader = protoManager.Index("unshaded").Instance();
+ _unshadedShader = protoManager.Index("unshaded").Instance();
}
protected override void Draw(in OverlayDrawArgs args)
@@ -54,9 +56,8 @@ protected override void Draw(in OverlayDrawArgs args)
// If you use the display UI scale then need to set max(1f, displayscale) because 0 is valid.
const float scale = 1f;
- var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
- var rotationMatrix = Matrix3.CreateRotation(-rotation);
- handle.UseShader(_shader);
+ var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale));
+ var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation);
var curTime = _timing.CurTime;
@@ -77,15 +78,22 @@ protected override void Draw(in OverlayDrawArgs args)
if (!bounds.Contains(worldPosition))
continue;
+ // shades the do-after bar if the do-after bar belongs to other players
+ // does not shade do-afters belonging to the local player
+ if (uid != localEnt)
+ handle.UseShader(null);
+ else
+ handle.UseShader(_unshadedShader);
+
// If the entity is paused, we will draw the do-after as it was when the entity got paused.
var meta = metaQuery.GetComponent(uid);
var time = meta.EntityPaused
? curTime - _meta.GetPauseTime(uid, meta)
: curTime;
- var worldMatrix = Matrix3.CreateTranslation(worldPosition);
- Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
- Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
+ var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition);
+ var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix);
+ var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld);
handle.SetTransform(matty);
var offset = 0f;
@@ -125,7 +133,7 @@ protected override void Draw(in OverlayDrawArgs args)
elapsedRatio = (float) Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds);
var cancelElapsed = (time - doAfter.CancelledTime.Value).TotalSeconds;
var flash = Math.Floor(cancelElapsed / FlashTime) % 2 == 0;
- color = new Color(1f, 0f, 0f, flash ? alpha : 0f);
+ color = GetProgressColor(0, flash ? alpha : 0);
}
else
{
@@ -143,17 +151,11 @@ protected override void Draw(in OverlayDrawArgs args)
}
handle.UseShader(null);
- handle.SetTransform(Matrix3.Identity);
+ handle.SetTransform(Matrix3x2.Identity);
}
- public static Color GetProgressColor(float progress, float alpha = 1f)
+ public Color GetProgressColor(float progress, float alpha = 1f)
{
- if (progress >= 1.0f)
- {
- return new Color(0f, 1f, 0f, alpha);
- }
- // lerp
- var hue = (5f / 18f) * progress;
- return Color.FromHsv((hue, 1f, 0.75f, alpha));
+ return _progressColor.GetProgressColor(progress).WithAlpha(alpha);
}
}
diff --git a/Content.Client/Doors/DoorSystem.cs b/Content.Client/Doors/DoorSystem.cs
index ff4e858b65..ccc33322c1 100644
--- a/Content.Client/Doors/DoorSystem.cs
+++ b/Content.Client/Doors/DoorSystem.cs
@@ -5,14 +5,12 @@
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
-using Robust.Shared.Timing;
namespace Content.Client.Doors;
public sealed class DoorSystem : SharedDoorSystem
{
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
public override void Initialize()
diff --git a/Content.Client/Doors/Electronics/DoorElectronicsBoundUserInterface.cs b/Content.Client/Doors/Electronics/DoorElectronicsBoundUserInterface.cs
new file mode 100644
index 0000000000..cd7ea717ce
--- /dev/null
+++ b/Content.Client/Doors/Electronics/DoorElectronicsBoundUserInterface.cs
@@ -0,0 +1,59 @@
+using Content.Shared.Access;
+using Content.Shared.Doors.Electronics;
+using Robust.Client.GameObjects;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Doors.Electronics;
+
+public sealed class DoorElectronicsBoundUserInterface : BoundUserInterface
+{
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ private DoorElectronicsConfigurationMenu? _window;
+
+ public DoorElectronicsBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ List> accessLevels = new();
+
+ foreach (var accessLevel in _prototypeManager.EnumeratePrototypes())
+ {
+ if (accessLevel.Name != null)
+ {
+ accessLevels.Add(accessLevel.ID);
+ }
+ }
+
+ accessLevels.Sort();
+
+ _window = new DoorElectronicsConfigurationMenu(this, accessLevels, _prototypeManager);
+ _window.OnClose += Close;
+ _window.OpenCentered();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ var castState = (DoorElectronicsConfigurationState) state;
+
+ _window?.UpdateState(castState);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing) return;
+
+ _window?.Dispose();
+ }
+
+ public void UpdateConfiguration(List> newAccessList)
+ {
+ SendMessage(new DoorElectronicsUpdateConfigurationMessage(newAccessList));
+ }
+}
diff --git a/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml
new file mode 100644
index 0000000000..4cd59f38b2
--- /dev/null
+++ b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml.cs b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml.cs
new file mode 100644
index 0000000000..c01f13a462
--- /dev/null
+++ b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml.cs
@@ -0,0 +1,41 @@
+using System.Linq;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Content.Client.Access.UI;
+using Content.Client.Doors.Electronics;
+using Content.Shared.Access;
+using Content.Shared.Doors.Electronics;
+using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
+
+namespace Content.Client.Doors.Electronics;
+
+[GenerateTypedNameReferences]
+public sealed partial class DoorElectronicsConfigurationMenu : FancyWindow
+{
+ private readonly DoorElectronicsBoundUserInterface _owner;
+ private AccessLevelControl _buttonsList = new();
+
+ public DoorElectronicsConfigurationMenu(DoorElectronicsBoundUserInterface ui, List> accessLevels, IPrototypeManager prototypeManager)
+ {
+ RobustXamlLoader.Load(this);
+
+ _owner = ui;
+
+ _buttonsList.Populate(accessLevels, prototypeManager);
+ AccessLevelControlContainer.AddChild(_buttonsList);
+
+ foreach (var (id, button) in _buttonsList.ButtonsList)
+ {
+ button.OnPressed += _ => _owner.UpdateConfiguration(
+ _buttonsList.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
+ }
+ }
+
+ public void UpdateState(DoorElectronicsConfigurationState state)
+ {
+ _buttonsList.UpdateState(state.AccessList);
+ }
+}
diff --git a/Content.Client/Doors/FirelockSystem.cs b/Content.Client/Doors/FirelockSystem.cs
index cfd84a4713..ad869391f4 100644
--- a/Content.Client/Doors/FirelockSystem.cs
+++ b/Content.Client/Doors/FirelockSystem.cs
@@ -1,9 +1,10 @@
using Content.Shared.Doors.Components;
+using Content.Shared.Doors.Systems;
using Robust.Client.GameObjects;
namespace Content.Client.Doors;
-public sealed class FirelockSystem : EntitySystem
+public sealed class FirelockSystem : SharedFirelockSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
@@ -24,15 +25,12 @@ private void OnAppearanceChange(EntityUid uid, FirelockComponent comp, ref Appea
if (!_appearanceSystem.TryGetData(uid, DoorVisuals.State, out var state, args.Component))
state = DoorState.Closed;
- if (_appearanceSystem.TryGetData(uid, DoorVisuals.Powered, out var powered, args.Component) && powered)
- {
- boltedVisible = _appearanceSystem.TryGetData(uid, DoorVisuals.BoltLights, out var lights, args.Component) && lights;
- unlitVisible =
- state == DoorState.Closing
- || state == DoorState.Opening
- || state == DoorState.Denying
- || (_appearanceSystem.TryGetData(uid, DoorVisuals.ClosedLights, out var closedLights, args.Component) && closedLights);
- }
+ boltedVisible = _appearanceSystem.TryGetData(uid, DoorVisuals.BoltLights, out var lights, args.Component) && lights;
+ unlitVisible =
+ state == DoorState.Closing
+ || state == DoorState.Opening
+ || state == DoorState.Denying
+ || (_appearanceSystem.TryGetData(uid, DoorVisuals.ClosedLights, out var closedLights, args.Component) && closedLights);
args.Sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible && !boltedVisible);
args.Sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, boltedVisible);
diff --git a/Content.Client/Drugs/DrugOverlaySystem.cs b/Content.Client/Drugs/DrugOverlaySystem.cs
index 9bfa4fdf82..60fdf12f5e 100644
--- a/Content.Client/Drugs/DrugOverlaySystem.cs
+++ b/Content.Client/Drugs/DrugOverlaySystem.cs
@@ -38,6 +38,7 @@ private void OnPlayerAttached(EntityUid uid, SeeingRainbowsComponent component,
private void OnPlayerDetached(EntityUid uid, SeeingRainbowsComponent component, LocalPlayerDetachedEvent args)
{
_overlay.Intoxication = 0;
+ _overlay.TimeTicker = 0;
_overlayMan.RemoveOverlay(_overlay);
}
@@ -52,6 +53,7 @@ private void OnShutdown(EntityUid uid, SeeingRainbowsComponent component, Compon
if (_player.LocalEntity == uid)
{
_overlay.Intoxication = 0;
+ _overlay.TimeTicker = 0;
_overlayMan.RemoveOverlay(_overlay);
}
}
diff --git a/Content.Client/Drugs/RainbowOverlay.cs b/Content.Client/Drugs/RainbowOverlay.cs
index 6ef5d0f65c..fb48c91010 100644
--- a/Content.Client/Drugs/RainbowOverlay.cs
+++ b/Content.Client/Drugs/RainbowOverlay.cs
@@ -1,7 +1,9 @@
+using Content.Shared.CCVar;
using Content.Shared.Drugs;
using Content.Shared.StatusEffect;
using Robust.Client.Graphics;
using Robust.Client.Player;
+using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -10,6 +12,7 @@ namespace Content.Client.Drugs;
public sealed class RainbowOverlay : Overlay
{
+ [Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -20,6 +23,7 @@ public sealed class RainbowOverlay : Overlay
private readonly ShaderInstance _rainbowShader;
public float Intoxication = 0.0f;
+ public float TimeTicker = 0.0f;
private const float VisualThreshold = 10.0f;
private const float PowerDivisor = 250.0f;
@@ -48,7 +52,17 @@ protected override void FrameUpdate(FrameEventArgs args)
return;
var timeLeft = (float) (time.Value.Item2 - time.Value.Item1).TotalSeconds;
- Intoxication += (timeLeft - Intoxication) * args.DeltaSeconds / 16f;
+
+ TimeTicker += args.DeltaSeconds;
+
+ if (timeLeft - TimeTicker > timeLeft / 16f)
+ {
+ Intoxication += (timeLeft - Intoxication) * args.DeltaSeconds / 16f;
+ }
+ else
+ {
+ Intoxication -= Intoxication/(timeLeft - TimeTicker) * args.DeltaSeconds;
+ }
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
@@ -64,6 +78,10 @@ protected override bool BeforeDraw(in OverlayDrawArgs args)
protected override void Draw(in OverlayDrawArgs args)
{
+ // TODO disable only the motion part or ike's idea (single static frame of the overlay)
+ if (_config.GetCVar(CCVars.ReducedMotion))
+ return;
+
if (ScreenTexture == null)
return;
diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs
index 54a0284355..1e49dba216 100644
--- a/Content.Client/Entry/EntryPoint.cs
+++ b/Content.Client/Entry/EntryPoint.cs
@@ -3,18 +3,16 @@
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
using Content.Client.Eui;
-using Content.Client.Flash;
using Content.Client.Fullscreen;
using Content.Client.GhostKick;
using Content.Client.Guidebook;
-using Content.Client.Info;
using Content.Client.Input;
using Content.Client.IoC;
using Content.Client.Launcher;
+using Content.Client.Lobby;
using Content.Client.MainMenu;
using Content.Client.Parallax.Managers;
using Content.Client.Players.PlayTimeTracking;
-using Content.Client.Preferences;
using Content.Client.Radiation.Overlays;
using Content.Client.Replay;
using Content.Client.Screenshot;
@@ -22,7 +20,7 @@
using Content.Client.Stylesheets;
using Content.Client.Viewport;
using Content.Client.Voting;
-using Content.Shared.Ame;
+using Content.Shared.Ame.Components;
using Content.Shared.Gravity;
using Content.Shared.Localizations;
using Robust.Client;
@@ -53,7 +51,6 @@ public sealed class EntryPoint : GameClient
[Dependency] private readonly IScreenshotHook _screenshotHook = default!;
[Dependency] private readonly FullscreenHook _fullscreenHook = default!;
[Dependency] private readonly ChangelogManager _changelogManager = default!;
- [Dependency] private readonly RulesManager _rulesManager = default!;
[Dependency] private readonly ViewportManager _viewportManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
@@ -71,6 +68,7 @@ public sealed class EntryPoint : GameClient
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IReplayLoadManager _replayLoad = default!;
[Dependency] private readonly ILogManager _logManager = default!;
+ [Dependency] private readonly ContentReplayPlaybackManager _replayMan = default!;
public override void Init()
{
@@ -101,14 +99,11 @@ public override void Init()
_prototypeManager.RegisterIgnore("seed"); // Seeds prototypes are server-only.
_prototypeManager.RegisterIgnore("objective");
_prototypeManager.RegisterIgnore("holiday");
- _prototypeManager.RegisterIgnore("aiFaction");
_prototypeManager.RegisterIgnore("htnCompound");
_prototypeManager.RegisterIgnore("htnPrimitive");
_prototypeManager.RegisterIgnore("gameMap");
_prototypeManager.RegisterIgnore("gameMapPool");
- _prototypeManager.RegisterIgnore("npcFaction");
_prototypeManager.RegisterIgnore("lobbyBackground");
- _prototypeManager.RegisterIgnore("advertisementsPack");
_prototypeManager.RegisterIgnore("gamePreset");
_prototypeManager.RegisterIgnore("noiseChannel");
_prototypeManager.RegisterIgnore("spaceBiome");
@@ -121,6 +116,7 @@ public override void Init()
_prototypeManager.RegisterIgnore("wireLayout");
_prototypeManager.RegisterIgnore("alertLevels");
_prototypeManager.RegisterIgnore("nukeopsRole");
+ _prototypeManager.RegisterIgnore("ghostRoleRaffleDecider");
_prototypeManager.RegisterIgnore("ftlAmmo");
_prototypeManager.RegisterIgnore("ftlPoint");
_prototypeManager.RegisterIgnore("printerPaper");
@@ -130,7 +126,6 @@ public override void Init()
_screenshotHook.Initialize();
_fullscreenHook.Initialize();
_changelogManager.Initialize();
- _rulesManager.Initialize();
_viewportManager.Initialize();
_ghostKick.Initialize();
_extendedDisconnectInformation.Initialize();
@@ -157,7 +152,6 @@ public override void PostInit()
_parallaxManager.LoadDefaultParallax();
_overlayManager.AddOverlay(new SingularityOverlay());
- _overlayManager.AddOverlay(new FlashOverlay());
_overlayManager.AddOverlay(new RadiationPulseOverlay());
_overlayManager.AddOverlay(new FilmGrainOverlay());
_chatManager.Initialize();
@@ -198,6 +192,7 @@ private void SwitchToDefaultState(bool disconnected = false)
_resourceManager,
ReplayConstants.ReplayZipFolder.ToRootedPath());
+ _replayMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath());
_replayLoad.LoadAndStartReplay(reader);
}
else if (_gameController.LaunchState.FromLauncher)
diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs
index 1be472b06d..b476971a13 100644
--- a/Content.Client/Examine/ExamineSystem.cs
+++ b/Content.Client/Examine/ExamineSystem.cs
@@ -212,14 +212,16 @@ public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCurso
var vBox = new BoxContainer
{
Name = "ExaminePopupVbox",
- Orientation = LayoutOrientation.Vertical
+ Orientation = LayoutOrientation.Vertical,
+ MaxWidth = _examineTooltipOpen.MaxWidth
};
panel.AddChild(vBox);
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
- SeparationOverride = 5
+ SeparationOverride = 5,
+ Margin = new Thickness(6, 0, 6, 0)
};
vBox.AddChild(hBox);
@@ -229,8 +231,7 @@ public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCurso
var spriteView = new SpriteView
{
OverrideDirection = Direction.South,
- SetSize = new Vector2(32, 32),
- Margin = new Thickness(2, 0, 2, 0),
+ SetSize = new Vector2(32, 32)
};
spriteView.SetEntity(target);
hBox.AddChild(spriteView);
@@ -238,19 +239,17 @@ public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCurso
if (knowTarget)
{
- hBox.AddChild(new Label
- {
- Text = Identity.Name(target, EntityManager, player),
- HorizontalExpand = true,
- });
+ var itemName = FormattedMessage.EscapeText(Identity.Name(target, EntityManager, player));
+ var labelMessage = FormattedMessage.FromMarkupPermissive($"[bold]{itemName}[/bold]");
+ var label = new RichTextLabel();
+ label.SetMessage(labelMessage);
+ hBox.AddChild(label);
}
else
{
- hBox.AddChild(new Label
- {
- Text = "???",
- HorizontalExpand = true,
- });
+ var label = new RichTextLabel();
+ label.SetMessage(FormattedMessage.FromMarkupOrThrow("[bold]???[/bold]"));
+ hBox.AddChild(label);
}
panel.Measure(Vector2Helpers.Infinity);
diff --git a/Content.Client/Explosion/ExplosionOverlay.cs b/Content.Client/Explosion/ExplosionOverlay.cs
index 2d8c15f1b9..8cf7447a5d 100644
--- a/Content.Client/Explosion/ExplosionOverlay.cs
+++ b/Content.Client/Explosion/ExplosionOverlay.cs
@@ -48,7 +48,7 @@ protected override void Draw(in OverlayDrawArgs args)
DrawExplosion(drawHandle, args.WorldBounds, visuals, index, xforms, textures);
}
- drawHandle.SetTransform(Matrix3.Identity);
+ drawHandle.SetTransform(Matrix3x2.Identity);
drawHandle.UseShader(null);
}
@@ -78,7 +78,8 @@ private void DrawExplosion(
if (visuals.SpaceTiles == null)
return;
- gridBounds = Matrix3.Invert(visuals.SpaceMatrix).TransformBox(worldBounds).Enlarged(2);
+ Matrix3x2.Invert(visuals.SpaceMatrix, out var invSpace);
+ gridBounds = invSpace.TransformBox(worldBounds).Enlarged(2);
drawHandle.SetTransform(visuals.SpaceMatrix);
DrawTiles(drawHandle, gridBounds, index, visuals.SpaceTiles, visuals, visuals.SpaceTileSize, textures);
diff --git a/Content.Client/Extinguisher/FireExtinguisherComponent.cs b/Content.Client/Extinguisher/FireExtinguisherComponent.cs
index 126c172924..324b05a93d 100644
--- a/Content.Client/Extinguisher/FireExtinguisherComponent.cs
+++ b/Content.Client/Extinguisher/FireExtinguisherComponent.cs
@@ -3,7 +3,5 @@
namespace Content.Client.Extinguisher;
-[NetworkedComponent, RegisterComponent]
-public sealed partial class FireExtinguisherComponent : SharedFireExtinguisherComponent
-{
-}
+[RegisterComponent]
+public sealed partial class FireExtinguisherComponent : SharedFireExtinguisherComponent;
diff --git a/Content.Client/Eye/EyeLerpingSystem.cs b/Content.Client/Eye/EyeLerpingSystem.cs
index 78e1b851fc..ac32299dca 100644
--- a/Content.Client/Eye/EyeLerpingSystem.cs
+++ b/Content.Client/Eye/EyeLerpingSystem.cs
@@ -86,7 +86,7 @@ public void RemoveEye(EntityUid uid)
private void HandleMapChange(EntityUid uid, LerpingEyeComponent component, ref EntParentChangedMessage args)
{
// Is this actually a map change? If yes, stop any lerps
- if (args.OldMapId != args.Transform.MapID)
+ if (args.OldMapId != args.Transform.MapUid)
component.LastRotation = GetRotation(uid, args.Transform);
}
diff --git a/Content.Client/Fax/System/FaxVisualsSystem.cs b/Content.Client/Fax/System/FaxVisualsSystem.cs
new file mode 100644
index 0000000000..892aec1d95
--- /dev/null
+++ b/Content.Client/Fax/System/FaxVisualsSystem.cs
@@ -0,0 +1,48 @@
+using Robust.Client.GameObjects;
+using Content.Shared.Fax.Components;
+using Content.Shared.Fax;
+using Robust.Client.Animations;
+
+namespace Content.Client.Fax.System;
+
+///
+/// Visualizer for the fax machine which displays the correct sprite based on the inserted entity.
+///
+public sealed class FaxVisualsSystem : EntitySystem
+{
+ [Dependency] private readonly AnimationPlayerSystem _player = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAppearanceChanged);
+ }
+
+ private void OnAppearanceChanged(EntityUid uid, FaxMachineComponent component, ref AppearanceChangeEvent args)
+ {
+ if (args.Sprite == null)
+ return;
+
+ if (_appearance.TryGetData(uid, FaxMachineVisuals.VisualState, out FaxMachineVisualState visuals) && visuals == FaxMachineVisualState.Inserting)
+ {
+ _player.Play(uid, new Animation()
+ {
+ Length = TimeSpan.FromSeconds(2.4),
+ AnimationTracks =
+ {
+ new AnimationTrackSpriteFlick()
+ {
+ LayerKey = FaxMachineVisuals.VisualState,
+ KeyFrames =
+ {
+ new AnimationTrackSpriteFlick.KeyFrame(component.InsertingState, 0f),
+ new AnimationTrackSpriteFlick.KeyFrame("icon", 2.4f),
+ }
+ }
+ }
+ }, "faxecute");
+ }
+ }
+}
diff --git a/Content.Client/Fax/UI/FaxBoundUi.cs b/Content.Client/Fax/UI/FaxBoundUi.cs
index 9b57595d7b..a95066a3b5 100644
--- a/Content.Client/Fax/UI/FaxBoundUi.cs
+++ b/Content.Client/Fax/UI/FaxBoundUi.cs
@@ -40,7 +40,7 @@ private async void OnFileButtonPressed()
{
if (_dialogIsOpen)
return;
-
+
_dialogIsOpen = true;
var filters = new FileDialogFilters(new FileDialogFilters.Group("txt"));
await using var file = await _fileDialogManager.OpenFile(filters);
@@ -52,8 +52,27 @@ private async void OnFileButtonPressed()
}
using var reader = new StreamReader(file);
+
+ var firstLine = await reader.ReadLineAsync();
+ string? label = null;
var content = await reader.ReadToEndAsync();
- SendMessage(new FaxFileMessage(content[..Math.Min(content.Length, FaxFileMessageValidation.MaxContentSize)], _window.OfficePaper));
+
+ if (firstLine is { })
+ {
+ if (firstLine.StartsWith('#'))
+ {
+ label = firstLine[1..].Trim();
+ }
+ else
+ {
+ content = firstLine + "\n" + content;
+ }
+ }
+
+ SendMessage(new FaxFileMessage(
+ label?[..Math.Min(label.Length, FaxFileMessageValidation.MaxLabelSize)],
+ content[..Math.Min(content.Length, FaxFileMessageValidation.MaxContentSize)],
+ _window.OfficePaper));
}
private void OnSendButtonPressed()
diff --git a/Content.Client/Flash/FlashOverlay.cs b/Content.Client/Flash/FlashOverlay.cs
index fe9c888227..9ea00275e8 100644
--- a/Content.Client/Flash/FlashOverlay.cs
+++ b/Content.Client/Flash/FlashOverlay.cs
@@ -1,12 +1,11 @@
-using System.Numerics;
+using Content.Shared.Flash;
+using Content.Shared.Flash.Components;
+using Content.Shared.StatusEffect;
using Content.Client.Viewport;
using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Client.Player;
using Robust.Shared.Enums;
-using Robust.Shared.Graphics;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using SixLabors.ImageSharp.PixelFormats;
@@ -17,66 +16,87 @@ public sealed class FlashOverlay : Overlay
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ private readonly StatusEffectsSystem _statusSys;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
- private double _startTime = -1;
- private double _lastsFor = 1;
- private Texture? _screenshotTexture;
+ public float PercentComplete = 0.0f;
+ public Texture? ScreenshotTexture;
public FlashOverlay()
{
IoCManager.InjectDependencies(this);
- _shader = _prototypeManager.Index("FlashedEffect").Instance().Duplicate();
+ _shader = _prototypeManager.Index("FlashedEffect").InstanceUnique();
+ _statusSys = _entityManager.System();
}
- public void ReceiveFlash(double duration)
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ var playerEntity = _playerManager.LocalEntity;
+
+ if (playerEntity == null)
+ return;
+
+ if (!_entityManager.HasComponent(playerEntity)
+ || !_entityManager.TryGetComponent(playerEntity, out var status))
+ return;
+
+ if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status))
+ return;
+
+ var curTime = _timing.CurTime;
+ var lastsFor = (float) (time.Value.Item2 - time.Value.Item1).TotalSeconds;
+ var timeDone = (float) (curTime - time.Value.Item1).TotalSeconds;
+
+ PercentComplete = timeDone / lastsFor;
+ }
+
+ public void ReceiveFlash()
{
if (_stateManager.CurrentState is IMainViewportState state)
{
+ // take a screenshot
+ // note that the callback takes a while and ScreenshotTexture will be null the first few Draws
state.Viewport.Viewport.Screenshot(image =>
{
var rgba32Image = image.CloneAs(SixLabors.ImageSharp.Configuration.Default);
- _screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image);
+ ScreenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image);
});
}
-
- _startTime = _gameTiming.CurTime.TotalSeconds;
- _lastsFor = duration;
}
- protected override void Draw(in OverlayDrawArgs args)
+ protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
- return;
-
+ return false;
if (args.Viewport.Eye != eyeComp.Eye)
- return;
+ return false;
+
+ return PercentComplete < 1.0f;
+ }
- var percentComplete = (float) ((_gameTiming.CurTime.TotalSeconds - _startTime) / _lastsFor);
- if (percentComplete >= 1.0f)
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (ScreenshotTexture == null)
return;
var worldHandle = args.WorldHandle;
+ _shader.SetParameter("percentComplete", PercentComplete);
worldHandle.UseShader(_shader);
- _shader.SetParameter("percentComplete", percentComplete);
-
- if (_screenshotTexture != null)
- {
- worldHandle.DrawTextureRectRegion(_screenshotTexture, args.WorldBounds);
- }
-
+ worldHandle.DrawTextureRectRegion(ScreenshotTexture, args.WorldBounds);
worldHandle.UseShader(null);
}
protected override void DisposeBehavior()
{
base.DisposeBehavior();
- _screenshotTexture = null;
+ ScreenshotTexture = null;
+ PercentComplete = 1.0f;
}
}
}
diff --git a/Content.Client/Flash/FlashSystem.cs b/Content.Client/Flash/FlashSystem.cs
index ad8f8b0b82..9a0579f6aa 100644
--- a/Content.Client/Flash/FlashSystem.cs
+++ b/Content.Client/Flash/FlashSystem.cs
@@ -1,62 +1,67 @@
using Content.Shared.Flash;
+using Content.Shared.Flash.Components;
+using Content.Shared.StatusEffect;
using Robust.Client.Graphics;
using Robust.Client.Player;
-using Robust.Shared.GameStates;
-using Robust.Shared.Timing;
+using Robust.Shared.Player;
-namespace Content.Client.Flash
+namespace Content.Client.Flash;
+
+public sealed class FlashSystem : SharedFlashSystem
{
- public sealed class FlashSystem : SharedFlashSystem
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IOverlayManager _overlayMan = default!;
+
+ private FlashOverlay _overlay = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+ SubscribeLocalEvent(OnStatusAdded);
+
+ _overlay = new();
+ }
+
+ private void OnPlayerAttached(EntityUid uid, FlashedComponent component, LocalPlayerAttachedEvent args)
{
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IOverlayManager _overlayManager = default!;
+ _overlayMan.AddOverlay(_overlay);
+ }
- public override void Initialize()
+ private void OnPlayerDetached(EntityUid uid, FlashedComponent component, LocalPlayerDetachedEvent args)
+ {
+ _overlay.PercentComplete = 1.0f;
+ _overlay.ScreenshotTexture = null;
+ _overlayMan.RemoveOverlay(_overlay);
+ }
+
+ private void OnInit(EntityUid uid, FlashedComponent component, ComponentInit args)
+ {
+ if (_player.LocalEntity == uid)
{
- base.Initialize();
+ _overlayMan.AddOverlay(_overlay);
+ }
+ }
- SubscribeLocalEvent(OnFlashableHandleState);
+ private void OnShutdown(EntityUid uid, FlashedComponent component, ComponentShutdown args)
+ {
+ if (_player.LocalEntity == uid)
+ {
+ _overlay.PercentComplete = 1.0f;
+ _overlay.ScreenshotTexture = null;
+ _overlayMan.RemoveOverlay(_overlay);
}
+ }
- private void OnFlashableHandleState(EntityUid uid, FlashableComponent component, ref ComponentHandleState args)
+ private void OnStatusAdded(EntityUid uid, FlashedComponent component, StatusEffectAddedEvent args)
+ {
+ if (_player.LocalEntity == uid && args.Key == FlashedKey)
{
- if (args.Current is not FlashableComponentState state)
- return;
-
- // Yes, this code is awful. I'm just porting it to an entity system so don't blame me.
- if (_playerManager.LocalEntity != uid)
- {
- return;
- }
-
- if (state.Time == default)
- {
- return;
- }
-
- // Few things here:
- // 1. If a shorter duration flash is applied then don't do anything
- // 2. If the client-side time is later than when the flash should've ended don't do anything
- var currentTime = _gameTiming.CurTime.TotalSeconds;
- var newEndTime = state.Time.TotalSeconds + state.Duration;
- var currentEndTime = component.LastFlash.TotalSeconds + component.Duration;
-
- if (currentEndTime > newEndTime)
- {
- return;
- }
-
- if (currentTime > newEndTime)
- {
- return;
- }
-
- component.LastFlash = state.Time;
- component.Duration = state.Duration;
-
- var overlay = _overlayManager.GetOverlay();
- overlay.ReceiveFlash(component.Duration);
+ _overlay.ReceiveFlash();
}
}
}
diff --git a/Content.Client/FlavorText/FlavorText.xaml.cs b/Content.Client/FlavorText/FlavorText.xaml.cs
index ffcf653f11..91b59046a4 100644
--- a/Content.Client/FlavorText/FlavorText.xaml.cs
+++ b/Content.Client/FlavorText/FlavorText.xaml.cs
@@ -17,7 +17,7 @@ public FlavorText()
var loc = IoCManager.Resolve();
CFlavorTextInput.Placeholder = new Rope.Leaf(loc.GetString("flavor-text-placeholder"));
- CFlavorTextInput.OnKeyBindDown += _ => FlavorTextChanged();
+ CFlavorTextInput.OnTextChanged += _ => FlavorTextChanged();
}
public void FlavorTextChanged()
diff --git a/Content.Client/Fluids/PuddleOverlay.cs b/Content.Client/Fluids/PuddleOverlay.cs
index def8582c1c..1a9a3d2688 100644
--- a/Content.Client/Fluids/PuddleOverlay.cs
+++ b/Content.Client/Fluids/PuddleOverlay.cs
@@ -1,4 +1,5 @@
-using Content.Shared.FixedPoint;
+using System.Numerics;
+using Content.Shared.FixedPoint;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
@@ -73,7 +74,7 @@ private void DrawWorld(in OverlayDrawArgs args)
}
}
- drawHandle.SetTransform(Matrix3.Identity);
+ drawHandle.SetTransform(Matrix3x2.Identity);
}
private void DrawScreen(in OverlayDrawArgs args)
@@ -99,7 +100,7 @@ private void DrawScreen(in OverlayDrawArgs args)
if (!gridBounds.Contains(centre))
continue;
- var screenCenter = _eyeManager.WorldToScreen(matrix.Transform(centre));
+ var screenCenter = _eyeManager.WorldToScreen(Vector2.Transform(centre, matrix));
drawHandle.DrawString(_font, screenCenter, debugOverlayData.CurrentVolume.ToString(), Color.White);
}
diff --git a/Content.Client/Fluids/PuddleSystem.cs b/Content.Client/Fluids/PuddleSystem.cs
index 54b1d5b86b..5dbffe0fd2 100644
--- a/Content.Client/Fluids/PuddleSystem.cs
+++ b/Content.Client/Fluids/PuddleSystem.cs
@@ -1,7 +1,9 @@
using Content.Client.IconSmoothing;
+using Content.Shared.Chemistry.Components;
using Content.Shared.Fluids;
using Content.Shared.Fluids.Components;
using Robust.Client.GameObjects;
+using Robust.Shared.Map;
namespace Content.Client.Fluids;
@@ -21,7 +23,7 @@ private void OnPuddleAppearance(EntityUid uid, PuddleComponent component, ref Ap
if (args.Sprite == null)
return;
- float volume = 1f;
+ var volume = 1f;
if (args.AppearanceData.TryGetValue(PuddleVisuals.CurrentVolume, out var volumeObj))
{
@@ -64,4 +66,38 @@ private void OnPuddleAppearance(EntityUid uid, PuddleComponent component, ref Ap
args.Sprite.Color *= baseColor;
}
}
+
+ #region Spill
+
+ // 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)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ ///
+ public override bool TrySpillAt(EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ ///
+ public override bool TrySpillAt(EntityUid uid, Solution solution, out EntityUid puddleUid, bool sound = true, TransformComponent? transformComponent = null)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ ///
+ public override bool TrySpillAt(TileRef tileRef, Solution solution, out EntityUid puddleUid, bool sound = true, bool tileReact = true)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ #endregion Spill
}
diff --git a/Content.Client/GPS/UI/HandheldGpsStatusControl.cs b/Content.Client/GPS/UI/HandheldGpsStatusControl.cs
index de6a1031ba..7dcf3f29c5 100644
--- a/Content.Client/GPS/UI/HandheldGpsStatusControl.cs
+++ b/Content.Client/GPS/UI/HandheldGpsStatusControl.cs
@@ -1,6 +1,7 @@
using Content.Client.GPS.Components;
using Content.Client.Message;
using Content.Client.Stylesheets;
+using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
@@ -13,11 +14,13 @@ public sealed class HandheldGpsStatusControl : Control
private readonly RichTextLabel _label;
private float _updateDif;
private readonly IEntityManager _entMan;
+ private readonly SharedTransformSystem _transform;
public HandheldGpsStatusControl(Entity parent)
{
_parent = parent;
_entMan = IoCManager.Resolve();
+ _transform = _entMan.System();
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
UpdateGpsDetails();
@@ -41,7 +44,7 @@ private void UpdateGpsDetails()
var posText = "Error";
if (_entMan.TryGetComponent(_parent, out TransformComponent? transComp))
{
- var pos = transComp.MapPosition;
+ var pos = _transform.GetMapCoordinates(_parent.Owner, xform: transComp);
var x = (int) pos.X;
var y = (int) pos.Y;
posText = $"({x}, {y})";
diff --git a/Content.Client/GameTicking/Managers/ClientGameTicker.cs b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
index 25b6802bd7..fcf5ae91a4 100644
--- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs
+++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
@@ -1,17 +1,15 @@
+using Content.Client.Administration.Managers;
using Content.Client.Gameplay;
using Content.Client.Lobby;
using Content.Client.RoundEnd;
-using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.GameWindow;
+using Content.Shared.Roles;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.State;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Configuration;
-using Robust.Shared.Player;
-using Robust.Shared.Utility;
+using Robust.Client.UserInterface;
+using Robust.Shared.Prototypes;
namespace Content.Client.GameTicking.Managers
{
@@ -19,20 +17,15 @@ namespace Content.Client.GameTicking.Managers
public sealed class ClientGameTicker : SharedGameTicker
{
[Dependency] private readonly IStateManager _stateManager = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IClientAdminManager _admin = default!;
+ [Dependency] private readonly IClyde _clyde = default!;
+ [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
- [ViewVariables] private bool _initialized;
- private Dictionary> _jobsAvailable = new();
+ private Dictionary, int?>> _jobsAvailable = new();
private Dictionary _stationNames = new();
- ///
- /// The current round-end window. Could be used to support re-opening the window after closing it.
- ///
- private RoundEndSummaryWindow? _window;
-
[ViewVariables] public bool AreWeReady { get; private set; }
[ViewVariables] public bool IsGameStarted { get; private set; }
- [ViewVariables] public string? LobbySong { get; private set; }
[ViewVariables] public string? RestartSound { get; private set; }
[ViewVariables] public string? LobbyBackground { get; private set; }
[ViewVariables] public bool DisallowedLateJoin { get; private set; }
@@ -40,19 +33,16 @@ public sealed class ClientGameTicker : SharedGameTicker
[ViewVariables] public TimeSpan StartTime { get; private set; }
[ViewVariables] public new bool Paused { get; private set; }
- [ViewVariables] public IReadOnlyDictionary> JobsAvailable => _jobsAvailable;
+ [ViewVariables] public IReadOnlyDictionary, int?>> JobsAvailable => _jobsAvailable;
[ViewVariables] public IReadOnlyDictionary StationNames => _stationNames;
public event Action? InfoBlobUpdated;
public event Action? LobbyStatusUpdated;
- public event Action? LobbySongUpdated;
public event Action? LobbyLateJoinStatusUpdated;
- public event Action>>? LobbyJobsAvailableUpdated;
+ public event Action, int?>>>? LobbyJobsAvailableUpdated;
public override void Initialize()
{
- DebugTools.Assert(!_initialized);
-
SubscribeNetworkEvent(JoinLobby);
SubscribeNetworkEvent(JoinGame);
SubscribeNetworkEvent(ConnectionStatus);
@@ -60,24 +50,33 @@ public override void Initialize()
SubscribeNetworkEvent(LobbyInfo);
SubscribeNetworkEvent(LobbyCountdown);
SubscribeNetworkEvent(RoundEnd);
- SubscribeNetworkEvent(msg =>
- {
- IoCManager.Resolve().RequestWindowAttention();
- });
+ SubscribeNetworkEvent(OnAttentionRequest);
SubscribeNetworkEvent(LateJoinStatus);
SubscribeNetworkEvent(UpdateJobsAvailable);
- _initialized = true;
+ _admin.AdminStatusUpdated += OnAdminUpdated;
+ OnAdminUpdated();
}
- public void SetLobbySong(string? song, bool forceUpdate = false)
+ public override void Shutdown()
{
- var updated = song != LobbySong;
+ _admin.AdminStatusUpdated -= OnAdminUpdated;
+ base.Shutdown();
+ }
- LobbySong = song;
+ private void OnAdminUpdated()
+ {
+ // Hide some map/grid related logs from clients. This is to try prevent some easy metagaming by just
+ // reading the console. E.g., logs like this one could leak the nuke station/grid:
+ // > Grid NT-Arrivals 1101 (122/n25896) changed parent. Old parent: map 10 (121/n25895). New parent: FTL (123/n26470)
+#if !DEBUG
+ EntityManager.System().Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
+#endif
+ }
- if (updated || forceUpdate)
- LobbySongUpdated?.Invoke();
+ private void OnAttentionRequest(RequestWindowAttentionEvent ev)
+ {
+ _clyde.RequestWindowAttention();
}
private void LateJoinStatus(TickerLateJoinStatusEvent message)
@@ -120,7 +119,6 @@ private void LobbyStatus(TickerLobbyStatusEvent message)
RoundStartTimeSpan = message.RoundStartTimeSpan;
IsGameStarted = message.IsRoundStarted;
AreWeReady = message.YouAreReady;
- SetLobbySong(message.LobbySong);
LobbyBackground = message.LobbyBackground;
Paused = message.Paused;
@@ -148,15 +146,9 @@ private void LobbyCountdown(TickerLobbyCountdownEvent message)
private void RoundEnd(RoundEndMessageEvent message)
{
// Force an update in the event of this song being the same as the last.
- SetLobbySong(message.LobbySong, true);
RestartSound = message.RestartSound;
- // Don't open duplicate windows (mainly for replays).
- if (_window?.RoundId == message.RoundId)
- return;
-
- //This is not ideal at all, but I don't see an immediately better fit anywhere else.
- _window = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, _entityManager);
+ _userInterfaceManager.GetUIController().OpenRoundEndSummaryWindow(message);
}
}
}
diff --git a/Content.Client/Gameplay/GameplayStateBase.cs b/Content.Client/Gameplay/GameplayStateBase.cs
index bdbd69d108..6236cd8e95 100644
--- a/Content.Client/Gameplay/GameplayStateBase.cs
+++ b/Content.Client/Gameplay/GameplayStateBase.cs
@@ -104,7 +104,7 @@ private bool HandleInspect(ICommonSession? session, EntityCoordinates coords, En
public IEnumerable GetClickableEntities(EntityCoordinates coordinates)
{
- return GetClickableEntities(coordinates.ToMap(_entityManager));
+ return GetClickableEntities(coordinates.ToMap(_entityManager, _entitySystemManager.GetEntitySystem()));
}
public IEnumerable GetClickableEntities(MapCoordinates coordinates)
diff --git a/Content.Client/Ghost/Commands/ToggleGhostVisibilityCommand.cs b/Content.Client/Ghost/Commands/ToggleGhostVisibilityCommand.cs
new file mode 100644
index 0000000000..480da6ad8d
--- /dev/null
+++ b/Content.Client/Ghost/Commands/ToggleGhostVisibilityCommand.cs
@@ -0,0 +1,26 @@
+using Robust.Shared.Console;
+
+namespace Content.Client.Ghost.Commands;
+
+public sealed class ToggleGhostVisibilityCommand : IConsoleCommand
+{
+ [Dependency] private readonly IEntitySystemManager _entSysMan = default!;
+
+ public string Command => "toggleghostvisibility";
+ public string Description => "Toggles ghost visibility on the client.";
+ public string Help => "toggleghostvisibility [bool]";
+
+ public void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var ghostSystem = _entSysMan.GetEntitySystem();
+
+ if (args.Length != 0 && bool.TryParse(args[0], out var visibility))
+ {
+ ghostSystem.ToggleGhostVisibility(visibility);
+ }
+ else
+ {
+ ghostSystem.ToggleGhostVisibility();
+ }
+ }
+}
diff --git a/Content.Client/Ghost/GhostSystem.cs b/Content.Client/Ghost/GhostSystem.cs
index c42e7cd0e0..94872a58ef 100644
--- a/Content.Client/Ghost/GhostSystem.cs
+++ b/Content.Client/Ghost/GhostSystem.cs
@@ -3,7 +3,6 @@
using Content.Shared.Ghost;
using Robust.Client.Console;
using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
@@ -177,9 +176,9 @@ public void OpenGhostRoles()
_console.RemoteExecuteCommand(null, "ghostroles");
}
- public void ToggleGhostVisibility()
+ public void ToggleGhostVisibility(bool? visibility = null)
{
- GhostVisibility = !GhostVisibility;
+ GhostVisibility = visibility ?? !GhostVisibility;
}
}
}
diff --git a/Content.Client/Gravity/GravitySystem.Shake.cs b/Content.Client/Gravity/GravitySystem.Shake.cs
index c4356588d3..9b9918ca3e 100644
--- a/Content.Client/Gravity/GravitySystem.Shake.cs
+++ b/Content.Client/Gravity/GravitySystem.Shake.cs
@@ -25,7 +25,7 @@ private void OnShakeInit(EntityUid uid, GravityShakeComponent component, Compone
{
var localPlayer = _playerManager.LocalEntity;
- if (!TryComp(localPlayer, out var xform) ||
+ if (!TryComp(localPlayer, out TransformComponent? xform) ||
xform.GridUid != uid && xform.MapUid != uid)
{
return;
@@ -46,7 +46,7 @@ protected override void ShakeGrid(EntityUid uid, GravityComponent? gravity = nul
var localPlayer = _playerManager.LocalEntity;
- if (!TryComp(localPlayer, out var xform))
+ if (!TryComp(localPlayer, out TransformComponent? xform))
return;
if (xform.GridUid != uid ||
diff --git a/Content.Client/Guidebook/Components/GuideHelpComponent.cs b/Content.Client/Guidebook/Components/GuideHelpComponent.cs
index db19bb9dcc..bb1d30bbc1 100644
--- a/Content.Client/Guidebook/Components/GuideHelpComponent.cs
+++ b/Content.Client/Guidebook/Components/GuideHelpComponent.cs
@@ -1,4 +1,5 @@
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+using Content.Shared.Guidebook;
+using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Components;
@@ -13,9 +14,8 @@ public sealed partial class GuideHelpComponent : Component
/// What guides to include show when opening the guidebook. The first entry will be used to select the currently
/// selected guidebook.
///
- [DataField("guides", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)]
- [ViewVariables]
- public List Guides = new();
+ [DataField(required: true)]
+ public List> Guides = new();
///
/// Whether or not to automatically include the children of the given guides.
diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml
index 752c9cc6c2..73a17e9bcc 100644
--- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml
+++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml
@@ -11,7 +11,7 @@
-
+
-
+
-
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
index e2b09386df..87931bf845 100644
--- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
@@ -4,6 +4,7 @@
using Content.Client.Guidebook.Richtext;
using Content.Client.Message;
using Content.Client.UserInterface.ControlExtensions;
+using Content.Shared.Body.Prototypes;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
@@ -128,7 +129,7 @@ private void GenerateControl(ReagentPrototype reagent)
var groupLabel = new RichTextLabel();
groupLabel.SetMarkup(Loc.GetString("guidebook-reagent-effects-metabolism-group-rate",
- ("group", group), ("rate", effect.MetabolismRate)));
+ ("group", _prototype.Index(group).LocalizedName), ("rate", effect.MetabolismRate)));
var descriptionLabel = new RichTextLabel
{
Margin = new Thickness(25, 0, 10, 0)
@@ -156,6 +157,39 @@ private void GenerateControl(ReagentPrototype reagent)
}
#endregion
+ #region PlantMetabolisms
+ if (_chemistryGuideData.ReagentGuideRegistry.TryGetValue(reagent.ID, out var guideEntryRegistryPlant) &&
+ guideEntryRegistryPlant.PlantMetabolisms != null &&
+ guideEntryRegistryPlant.PlantMetabolisms.Count > 0)
+ {
+ PlantMetabolismsDescriptionContainer.Children.Clear();
+ var metabolismLabel = new RichTextLabel();
+ metabolismLabel.SetMarkup(Loc.GetString("guidebook-reagent-plant-metabolisms-rate"));
+ var descriptionLabel = new RichTextLabel
+ {
+ Margin = new Thickness(25, 0, 10, 0)
+ };
+ var descMsg = new FormattedMessage();
+ var descriptionsCount = guideEntryRegistryPlant.PlantMetabolisms.Count;
+ var i = 0;
+ foreach (var effectString in guideEntryRegistryPlant.PlantMetabolisms)
+ {
+ descMsg.AddMarkup(effectString);
+ i++;
+ if (i < descriptionsCount)
+ descMsg.PushNewline();
+ }
+ descriptionLabel.SetMessage(descMsg);
+
+ PlantMetabolismsDescriptionContainer.AddChild(metabolismLabel);
+ PlantMetabolismsDescriptionContainer.AddChild(descriptionLabel);
+ }
+ else
+ {
+ PlantMetabolismsContainer.Visible = false;
+ }
+ #endregion
+
GenerateSources(reagent);
FormattedMessage description = new();
diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml
index 8dbfde3c47..69534af7f6 100644
--- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml
+++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml
@@ -2,7 +2,7 @@
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:fancyTree="clr-namespace:Content.Client.UserInterface.Controls.FancyTree"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
- SetSize="750 700"
+ SetSize="900 700"
MinSize="100 200"
Resizable="True"
Title="{Loc 'guidebook-window-title'}">
@@ -18,12 +18,16 @@
Name="SearchBar"
PlaceHolder="{Loc 'guidebook-filter-placeholder-text'}"
HorizontalExpand="True"
- Margin="0 5 10 5">
+ Margin="0 5 10 5">
+
+
+
-
+
+
diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
index 4776386c1d..c904a9c789 100644
--- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
@@ -1,15 +1,17 @@
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Guidebook.RichText;
using Content.Client.UserInterface.ControlExtensions;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Controls.FancyTree;
-using JetBrains.Annotations;
+using Content.Client.UserInterface.Systems.Info;
+using Content.Shared.CCVar;
+using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
+using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Controls;
@@ -19,7 +21,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
- private Dictionary _entries = new();
+ private Dictionary, GuideEntry> _entries = new();
public GuidebookWindow()
{
@@ -37,7 +39,13 @@ public GuidebookWindow()
private void OnSelectionChanged(TreeItem? item)
{
if (item != null && item.Metadata is GuideEntry entry)
+ {
ShowGuide(entry);
+
+ var isRulesEntry = entry.RuleEntry;
+ ReturnContainer.Visible = isRulesEntry;
+ HomeButton.OnPressed += _ => ShowGuide(entry);
+ }
else
ClearSelectedGuide();
}
@@ -69,10 +77,10 @@ private void ShowGuide(GuideEntry entry)
}
public void UpdateGuides(
- Dictionary entries,
- List? rootEntries = null,
- string? forceRoot = null,
- string? selected = null)
+ Dictionary, GuideEntry> entries,
+ List>? rootEntries = null,
+ ProtoId? forceRoot = null,
+ ProtoId? selected = null)
{
_entries = entries;
RepopulateTree(rootEntries, forceRoot);
@@ -98,11 +106,11 @@ public void UpdateGuides(
}
}
- private IEnumerable GetSortedEntries(List? rootEntries)
+ private IEnumerable GetSortedEntries(List>? rootEntries)
{
if (rootEntries == null)
{
- HashSet entries = new(_entries.Keys);
+ HashSet> entries = new(_entries.Keys);
foreach (var entry in _entries.Values)
{
if (entry.Children.Count > 0)
@@ -111,7 +119,7 @@ private IEnumerable GetSortedEntries(List? rootEntries)
.Select(childId => _entries[childId])
.OrderBy(childEntry => childEntry.Priority)
.ThenBy(childEntry => Loc.GetString(childEntry.Name))
- .Select(childEntry => childEntry.Id)
+ .Select(childEntry => new ProtoId(childEntry.Id))
.ToList();
entry.Children = sortedChildren;
@@ -127,13 +135,13 @@ private IEnumerable GetSortedEntries(List? rootEntries)
.ThenBy(rootEntry => Loc.GetString(rootEntry.Name));
}
- private void RepopulateTree(List? roots = null, string? forcedRoot = null)
+ private void RepopulateTree(List>? roots = null, ProtoId? forcedRoot = null)
{
Tree.Clear();
- HashSet addedEntries = new();
+ HashSet> addedEntries = new();
- TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot, null, addedEntries);
+ TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
foreach (var entry in GetSortedEntries(roots))
{
AddEntry(entry.Id, parent, addedEntries);
@@ -141,17 +149,23 @@ private void RepopulateTree(List? roots = null, string? forcedRoot = nul
Tree.SetAllExpanded(true);
}
- private TreeItem? AddEntry(string id, TreeItem? parent, HashSet addedEntries)
+ private TreeItem? AddEntry(ProtoId id, TreeItem? parent, HashSet> addedEntries)
{
if (!_entries.TryGetValue(id, out var entry))
return null;
if (!addedEntries.Add(id))
{
+ // TODO GUIDEBOOK Maybe allow duplicate entries?
+ // E.g., for adding medicine under both chemicals & the chemist job
Logger.Error($"Adding duplicate guide entry: {id}");
return null;
}
+ var rulesProto = UserInterfaceManager.GetUIController().GetCoreRuleEntry();
+ if (entry.RuleEntry && entry.Id != rulesProto.Id)
+ return null;
+
var item = Tree.AddItem(parent);
item.Metadata = entry;
var name = Loc.GetString(entry.Name);
diff --git a/Content.Client/Guidebook/DocumentParsingManager.cs b/Content.Client/Guidebook/DocumentParsingManager.cs
index b81866a626..e8a0743b9e 100644
--- a/Content.Client/Guidebook/DocumentParsingManager.cs
+++ b/Content.Client/Guidebook/DocumentParsingManager.cs
@@ -1,7 +1,10 @@
using System.Linq;
using Content.Client.Guidebook.Richtext;
+using Content.Shared.Guidebook;
using Pidgin;
using Robust.Client.UserInterface;
+using Robust.Shared.ContentPack;
+using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Sandboxing;
using static Pidgin.Parser;
@@ -13,7 +16,9 @@ namespace Content.Client.Guidebook;
///
public sealed partial class DocumentParsingManager
{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
+ [Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly ISandboxHelper _sandboxHelper = default!;
private readonly Dictionary> _tagControlParsers = new();
@@ -37,6 +42,21 @@ public void Initialize()
ControlParser = SkipWhitespaces.Then(_controlParser.Many());
}
+ public bool TryAddMarkup(Control control, ProtoId entryId, bool log = true)
+ {
+ if (!_prototype.TryIndex(entryId, out var entry))
+ return false;
+
+ using var file = _resourceManager.ContentFileReadText(entry.Text);
+ return TryAddMarkup(control, file.ReadToEnd(), log);
+ }
+
+ public bool TryAddMarkup(Control control, GuideEntry entry, bool log = true)
+ {
+ using var file = _resourceManager.ContentFileReadText(entry.Text);
+ return TryAddMarkup(control, file.ReadToEnd(), log);
+ }
+
public bool TryAddMarkup(Control control, string text, bool log = true)
{
try
diff --git a/Content.Client/Guidebook/GuideEntry.cs b/Content.Client/Guidebook/GuideEntry.cs
deleted file mode 100644
index b3c004267d..0000000000
--- a/Content.Client/Guidebook/GuideEntry.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-using Robust.Shared.Utility;
-
-namespace Content.Client.Guidebook;
-
-[Virtual]
-public class GuideEntry
-{
- ///
- /// The file containing the contents of this guide.
- ///
- [DataField("text", required: true)] public ResPath Text = default!;
-
- ///
- /// The unique id for this guide.
- ///
- [IdDataField]
- public string Id = default!;
-
- ///
- /// The name of this guide. This gets localized.
- ///
- [DataField("name", required: true)] public string Name = default!;
-
- ///
- /// The "children" of this guide for when guides are shown in a tree / table of contents.
- ///
- [DataField("children", customTypeSerializer:typeof(PrototypeIdListSerializer))]
- public List Children = new();
-
- ///
- /// Enable filtering of items.
- ///
- [DataField("filterEnabled")] public bool FilterEnabled = default!;
-
- ///
- /// Priority for sorting top-level guides when shown in a tree / table of contents.
- /// If the guide is the child of some other guide, the order simply determined by the order of children in .
- ///
- [DataField("priority")] public int Priority = 0;
-}
-
-[Prototype("guideEntry")]
-public sealed class GuideEntryPrototype : GuideEntry, IPrototype
-{
- public string ID => Id;
-}
diff --git a/Content.Client/Guidebook/GuidebookSystem.cs b/Content.Client/Guidebook/GuidebookSystem.cs
index a09fa18655..675a025d7a 100644
--- a/Content.Client/Guidebook/GuidebookSystem.cs
+++ b/Content.Client/Guidebook/GuidebookSystem.cs
@@ -1,8 +1,8 @@
using System.Linq;
using Content.Client.Guidebook.Components;
using Content.Client.Light;
-using Content.Client.UserInterface.Systems.Guidebook;
using Content.Client.Verbs;
+using Content.Shared.Guidebook;
using Content.Shared.Interaction;
using Content.Shared.Light.Components;
using Content.Shared.Speech;
@@ -14,6 +14,7 @@
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -32,7 +33,12 @@ public sealed class GuidebookSystem : EntitySystem
[Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!;
[Dependency] private readonly TagSystem _tags = default!;
- public event Action, List?, string?, bool, string?, bool>? OnGuidebookOpen;
+ public event Action>,
+ List>?,
+ ProtoId?,
+ bool,
+ ProtoId?>? OnGuidebookOpen;
+
public const string GuideEmbedTag = "GuideEmbeded";
private EntityUid _defaultUser;
@@ -75,16 +81,17 @@ private void OnGetVerbs(EntityUid uid, GuideHelpComponent component, GetVerbsEve
{
Text = Loc.GetString("guide-help-verb"),
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
- Act = () =>
- {
- OnGuidebookOpen?.Invoke(component.Guides, null, null, component.IncludeChildren,
- component.Guides[0], true);
- },
+ Act = () => OnGuidebookOpen?.Invoke(component.Guides, null, null, component.IncludeChildren, component.Guides[0]),
ClientExclusive = true,
CloseMenu = true
});
}
+ public void OpenHelp(List> guides)
+ {
+ OnGuidebookOpen?.Invoke(guides, null, null, true, guides[0]);
+ }
+
private void OnInteract(EntityUid uid, GuideHelpComponent component, ActivateInWorldEvent args)
{
if (!_timing.IsFirstTimePredicted)
@@ -93,7 +100,7 @@ private void OnInteract(EntityUid uid, GuideHelpComponent component, ActivateInW
if (!component.OpenOnActivation || component.Guides.Count == 0 || _tags.HasTag(uid, GuideEmbedTag))
return;
- OnGuidebookOpen?.Invoke(component.Guides, null, null, component.IncludeChildren, component.Guides[0], true);
+ OnGuidebookOpen?.Invoke(component.Guides, null, null, component.IncludeChildren, component.Guides[0]);
args.Handled = true;
}
@@ -148,7 +155,7 @@ private void OnGuidebookControlsTestInteractHand(EntityUid uid, GuidebookControl
public void FakeClientActivateInWorld(EntityUid activated)
{
- var activateMsg = new ActivateInWorldEvent(GetGuidebookUser(), activated);
+ var activateMsg = new ActivateInWorldEvent(GetGuidebookUser(), activated, true);
RaiseLocalEvent(activated, activateMsg);
}
diff --git a/Content.Client/Guidebook/Richtext/Box.cs b/Content.Client/Guidebook/Richtext/Box.cs
index ecf6cb21f7..6e18ad9c57 100644
--- a/Content.Client/Guidebook/Richtext/Box.cs
+++ b/Content.Client/Guidebook/Richtext/Box.cs
@@ -11,6 +11,9 @@ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out
HorizontalExpand = true;
control = this;
+ if (args.TryGetValue("Margin", out var margin))
+ Margin = new Thickness(float.Parse(margin));
+
if (args.TryGetValue("Orientation", out var orientation))
Orientation = Enum.Parse(orientation);
else
diff --git a/Content.Client/Guidebook/Richtext/ColorBox.cs b/Content.Client/Guidebook/Richtext/ColorBox.cs
new file mode 100644
index 0000000000..84de300d6e
--- /dev/null
+++ b/Content.Client/Guidebook/Richtext/ColorBox.cs
@@ -0,0 +1,49 @@
+using System.Diagnostics.CodeAnalysis;
+using JetBrains.Annotations;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.Guidebook.Richtext;
+
+[UsedImplicitly]
+public sealed class ColorBox : PanelContainer, IDocumentTag
+{
+ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control)
+ {
+ HorizontalExpand = true;
+ VerticalExpand = true;
+ control = this;
+
+ if (args.TryGetValue("Margin", out var margin))
+ Margin = new Thickness(float.Parse(margin));
+
+ if (args.TryGetValue("HorizontalAlignment", out var halign))
+ HorizontalAlignment = Enum.Parse(halign);
+ else
+ HorizontalAlignment = HAlignment.Stretch;
+
+ if (args.TryGetValue("VerticalAlignment", out var valign))
+ VerticalAlignment = Enum.Parse(valign);
+ else
+ VerticalAlignment = VAlignment.Stretch;
+
+ var styleBox = new StyleBoxFlat();
+ if (args.TryGetValue("Color", out var color))
+ styleBox.BackgroundColor = Color.FromHex(color);
+
+ if (args.TryGetValue("OutlineThickness", out var outlineThickness))
+ styleBox.BorderThickness = new Thickness(float.Parse(outlineThickness));
+ else
+ styleBox.BorderThickness = new Thickness(1);
+
+ if (args.TryGetValue("OutlineColor", out var outlineColor))
+ styleBox.BorderColor = Color.FromHex(outlineColor);
+ else
+ styleBox.BorderColor = Color.White;
+
+ PanelOverride = styleBox;
+
+ return true;
+ }
+}
diff --git a/Content.Client/Guidebook/Richtext/Table.cs b/Content.Client/Guidebook/Richtext/Table.cs
new file mode 100644
index 0000000000..b6923c3698
--- /dev/null
+++ b/Content.Client/Guidebook/Richtext/Table.cs
@@ -0,0 +1,27 @@
+using System.Diagnostics.CodeAnalysis;
+using Content.Client.UserInterface.Controls;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Guidebook.Richtext;
+
+[UsedImplicitly]
+public sealed class Table : TableContainer, IDocumentTag
+{
+ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control)
+ {
+ HorizontalExpand = true;
+ control = this;
+
+ if (!args.TryGetValue("Columns", out var columns) || !int.TryParse(columns, out var columnsCount))
+ {
+ Logger.Error("Guidebook tag \"Table\" does not specify required property \"Columns.\"");
+ control = null;
+ return false;
+ }
+
+ Columns = columnsCount;
+
+ return true;
+ }
+}
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
index 28872ef1d5..401f976862 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
@@ -32,6 +32,9 @@
+
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
index 2f19cd0a05..fcf6d4551f 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
@@ -1,5 +1,6 @@
using System.Linq;
using System.Numerics;
+using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
@@ -79,13 +80,23 @@ public void Populate(HealthAnalyzerScannedUserMessage msg)
);
Temperature.Text = Loc.GetString("health-analyzer-window-entity-temperature-text",
- ("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - 273f:F1} °C ({msg.Temperature:F1} °K)")
+ ("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)")
);
BloodLevel.Text = Loc.GetString("health-analyzer-window-entity-blood-level-text",
("bloodLevel", float.IsNaN(msg.BloodLevel) ? "N/A" : $"{msg.BloodLevel * 100:F1} %")
);
+ if (msg.Bleeding == true)
+ {
+ Bleeding.Text = Loc.GetString("health-analyzer-window-entity-bleeding-text");
+ Bleeding.FontColorOverride = Color.Red;
+ }
+ else
+ {
+ Bleeding.Text = string.Empty; // Clear the text
+ }
+
patientDamageAmount.Text = Loc.GetString(
"health-analyzer-window-entity-damage-total-text",
("amount", damageable.TotalDamage)
@@ -128,7 +139,7 @@ private void DrawDiagnosticGroups(
var groupTitleText = $"{Loc.GetString(
"health-analyzer-window-damage-group-text",
- ("damageGroup", Loc.GetString("health-analyzer-window-damage-group-" + damageGroupId)),
+ ("damageGroup", _prototypes.Index(damageGroupId).LocalizedName),
("amount", damageAmount)
)}";
@@ -159,7 +170,7 @@ private void DrawDiagnosticGroups(
var damageString = Loc.GetString(
"health-analyzer-window-damage-type-text",
- ("damageType", Loc.GetString("health-analyzer-window-damage-type-" + type)),
+ ("damageType", _prototypes.Index(type).LocalizedName),
("amount", typeAmount)
);
diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
index e4d86ab4cc..6eb5dd9ec9 100644
--- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
+++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
@@ -108,8 +108,11 @@ private void SetLayerData(
/// This should not be used if the entity is owned by the server. The server will otherwise
/// override this with the appearance data it sends over.
///
- public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null)
+ public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile? profile, HumanoidAppearanceComponent? humanoid = null)
{
+ if (profile == null)
+ return;
+
if (!Resolve(uid, ref humanoid))
{
return;
@@ -191,7 +194,6 @@ public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile
humanoid.Sex = profile.Sex;
humanoid.Gender = profile.Gender;
humanoid.Age = profile.Age;
- humanoid.Height = profile.Height;
humanoid.Species = profile.Species;
humanoid.SkinColor = profile.Appearance.SkinColor;
humanoid.EyeColor = profile.Appearance.EyeColor;
diff --git a/Content.Client/Humanoid/HumanoidMarkingModifierWindow.xaml b/Content.Client/Humanoid/HumanoidMarkingModifierWindow.xaml
index d32d3ba2cf..78e65db2a3 100644
--- a/Content.Client/Humanoid/HumanoidMarkingModifierWindow.xaml
+++ b/Content.Client/Humanoid/HumanoidMarkingModifierWindow.xaml
@@ -7,7 +7,7 @@
-
+
diff --git a/Content.Client/Humanoid/MarkingPicker.xaml.cs b/Content.Client/Humanoid/MarkingPicker.xaml.cs
index 43333439f0..0e0b9dd384 100644
--- a/Content.Client/Humanoid/MarkingPicker.xaml.cs
+++ b/Content.Client/Humanoid/MarkingPicker.xaml.cs
@@ -124,17 +124,16 @@ public MarkingPicker()
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- SetupCategoryButtons();
CMarkingCategoryButton.OnItemSelected += OnCategoryChange;
CMarkingsUnused.OnItemSelected += item =>
_selectedUnusedMarking = CMarkingsUnused[item.ItemIndex];
- CMarkingAdd.OnPressed += args =>
+ CMarkingAdd.OnPressed += _ =>
MarkingAdd();
CMarkingsUsed.OnItemSelected += OnUsedMarkingSelected;
- CMarkingRemove.OnPressed += args =>
+ CMarkingRemove.OnPressed += _ =>
MarkingRemove();
CMarkingRankUp.OnPressed += _ => SwapMarkingUp();
@@ -146,16 +145,34 @@ public MarkingPicker()
private void SetupCategoryButtons()
{
CMarkingCategoryButton.Clear();
+
+ var validCategories = new List();
for (var i = 0; i < _markingCategories.Count; i++)
{
- if (_ignoreCategories.Contains(_markingCategories[i]))
+ var category = _markingCategories[i];
+ var markings = GetMarkings(category);
+ if (_ignoreCategories.Contains(category) ||
+ markings.Count == 0)
{
continue;
}
- CMarkingCategoryButton.AddItem(Loc.GetString($"markings-category-{_markingCategories[i].ToString()}"), i);
+ validCategories.Add(category);
+ CMarkingCategoryButton.AddItem(Loc.GetString($"markings-category-{category.ToString()}"), i);
+ }
+
+ if (validCategories.Contains(_selectedMarkingCategory))
+ {
+ CMarkingCategoryButton.SelectId(_markingCategories.IndexOf(_selectedMarkingCategory));
+ }
+ else if (validCategories.Count > 0)
+ {
+ _selectedMarkingCategory = validCategories[0];
+ }
+ else
+ {
+ _selectedMarkingCategory = MarkingCategories.Chest;
}
- CMarkingCategoryButton.SelectId(_markingCategories.IndexOf(_selectedMarkingCategory));
}
private string GetMarkingName(MarkingPrototype marking) => Loc.GetString($"marking-{marking.ID}");
@@ -179,16 +196,21 @@ private List GetMarkingStateNames(MarkingPrototype marking)
return result;
}
+ private IReadOnlyDictionary GetMarkings(MarkingCategories category)
+ {
+ return IgnoreSpecies
+ ? _markingManager.MarkingsByCategoryAndSex(category, _currentSex)
+ : _markingManager.MarkingsByCategoryAndSpeciesAndSex(category, _currentSpecies, _currentSex);
+ }
+
public void Populate(string filter)
{
+ SetupCategoryButtons();
+
CMarkingsUnused.Clear();
_selectedUnusedMarking = null;
- var markings = IgnoreSpecies
- ? _markingManager.MarkingsByCategoryAndSex(_selectedMarkingCategory, _currentSex)
- : _markingManager.MarkingsByCategoryAndSpeciesAndSex(_selectedMarkingCategory, _currentSpecies, _currentSex);
-
- var sortedMarkings = markings.Values.Where(m =>
+ var sortedMarkings = GetMarkings(_selectedMarkingCategory).Values.Where(m =>
m.ID.ToLower().Contains(filter.ToLower()) ||
GetMarkingName(m).ToLower().Contains(filter.ToLower())
).OrderBy(p => Loc.GetString(GetMarkingName(p)));
diff --git a/Content.Client/IconSmoothing/IconSmoothSystem.cs b/Content.Client/IconSmoothing/IconSmoothSystem.cs
index 7334c2fd43..4b02560846 100644
--- a/Content.Client/IconSmoothing/IconSmoothSystem.cs
+++ b/Content.Client/IconSmoothing/IconSmoothSystem.cs
@@ -16,8 +16,6 @@ namespace Content.Client.IconSmoothing
[UsedImplicitly]
public sealed partial class IconSmoothSystem : EntitySystem
{
- [Dependency] private readonly IMapManager _mapManager = default!;
-
private readonly Queue _dirtyEntities = new();
private readonly Queue _anchorChangedEntities = new();
@@ -47,7 +45,7 @@ private void OnStartup(EntityUid uid, IconSmoothComponent component, ComponentSt
var xform = Transform(uid);
if (xform.Anchored)
{
- component.LastPosition = _mapManager.TryGetGrid(xform.GridUid, out var grid)
+ component.LastPosition = TryComp(xform.GridUid, out var grid)
? (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates))
: (null, new Vector2i(0, 0));
@@ -134,7 +132,7 @@ public void DirtyNeighbours(EntityUid uid, IconSmoothComponent? comp = null, Tra
Vector2i pos;
- if (transform.Anchored && _mapManager.TryGetGrid(transform.GridUid, out var grid))
+ if (transform.Anchored && TryComp(transform.GridUid, out var grid))
{
pos = grid.CoordinatesToTile(transform.Coordinates);
}
@@ -144,7 +142,7 @@ public void DirtyNeighbours(EntityUid uid, IconSmoothComponent? comp = null, Tra
if (comp.LastPosition is not (EntityUid gridId, Vector2i oldPos))
return;
- if (!_mapManager.TryGetGrid(gridId, out grid))
+ if (!TryComp(gridId, out grid))
return;
pos = oldPos;
@@ -206,7 +204,7 @@ private void CalculateNewSprite(EntityUid uid,
{
var directions = DirectionFlag.None;
- if (_mapManager.TryGetGrid(xform.GridUid, out grid))
+ if (TryComp(xform.GridUid, out grid))
{
var pos = grid.TileIndicesFor(xform.Coordinates);
@@ -231,7 +229,7 @@ private void CalculateNewSprite(EntityUid uid,
if (!spriteQuery.TryGetComponent(uid, out var sprite))
{
- Logger.Error($"Encountered a icon-smoothing entity without a sprite: {ToPrettyString(uid)}");
+ Log.Error($"Encountered a icon-smoothing entity without a sprite: {ToPrettyString(uid)}");
RemCompDeferred(uid, smooth);
return;
}
@@ -240,9 +238,9 @@ private void CalculateNewSprite(EntityUid uid,
if (xform.Anchored)
{
- if (!_mapManager.TryGetGrid(xform.GridUid, out grid))
+ if (!TryComp(xform.GridUid, out grid))
{
- Logger.Error($"Failed to calculate IconSmoothComponent sprite in {uid} because grid {xform.GridUid} was missing.");
+ Log.Error($"Failed to calculate IconSmoothComponent sprite in {uid} because grid {xform.GridUid} was missing.");
return;
}
}
diff --git a/Content.Client/Implants/UI/ImplanterStatusControl.cs b/Content.Client/Implants/UI/ImplanterStatusControl.cs
index f3f0cdea7d..e2ffabd17d 100644
--- a/Content.Client/Implants/UI/ImplanterStatusControl.cs
+++ b/Content.Client/Implants/UI/ImplanterStatusControl.cs
@@ -1,5 +1,6 @@
using Content.Client.Message;
using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
using Content.Shared.Implants.Components;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -17,7 +18,7 @@ public ImplanterStatusControl(ImplanterComponent parent)
_parent = parent;
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
_label.MaxWidth = 350;
- AddChild(_label);
+ AddChild(new ClipControl { Children = { _label } });
Update();
}
@@ -42,17 +43,12 @@ private void Update()
_ => Loc.GetString("injector-invalid-injector-toggle-mode")
};
- var (implantName, implantDescription) = _parent.ImplanterSlot.HasItem switch
- {
- false => (Loc.GetString("implanter-empty-text"), ""),
- true => (_parent.ImplantData.Item1, _parent.ImplantData.Item2),
- };
-
+ var implantName = _parent.ImplanterSlot.HasItem
+ ? _parent.ImplantData.Item1
+ : Loc.GetString("implanter-empty-text");
_label.SetMarkup(Loc.GetString("implanter-label",
("implantName", implantName),
- ("implantDescription", implantDescription),
- ("modeString", modeStringLocalized),
- ("lineBreak", "\n")));
+ ("modeString", modeStringLocalized)));
}
}
diff --git a/Content.Client/Info/InfoSystem.cs b/Content.Client/Info/InfoSystem.cs
deleted file mode 100644
index 4572f3afdc..0000000000
--- a/Content.Client/Info/InfoSystem.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Content.Shared.Info;
-using Robust.Shared.Log;
-
-namespace Content.Client.Info;
-
-public sealed class InfoSystem : EntitySystem
-{
- public RulesMessage Rules = new RulesMessage("Server Rules", "The server did not send any rules.");
- [Dependency] private readonly RulesManager _rules = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeNetworkEvent(OnRulesReceived);
- Logger.DebugS("info", "Requested server info.");
- RaiseNetworkEvent(new RequestRulesMessage());
- }
-
- private void OnRulesReceived(RulesMessage message, EntitySessionEventArgs eventArgs)
- {
- Logger.DebugS("info", "Received server rules.");
- Rules = message;
- _rules.UpdateRules();
- }
-}
diff --git a/Content.Client/Info/RulesAndInfoWindow.cs b/Content.Client/Info/RulesAndInfoWindow.cs
index 7a763a1d6f..b9131dcb3c 100644
--- a/Content.Client/Info/RulesAndInfoWindow.cs
+++ b/Content.Client/Info/RulesAndInfoWindow.cs
@@ -1,10 +1,8 @@
using System.Numerics;
using Content.Client.UserInterface.Systems.EscapeMenu;
-using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
-using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
namespace Content.Client.Info
@@ -12,7 +10,6 @@ namespace Content.Client.Info
public sealed class RulesAndInfoWindow : DefaultWindow
{
[Dependency] private readonly IResourceManager _resourceManager = default!;
- [Dependency] private readonly RulesManager _rules = default!;
public RulesAndInfoWindow()
{
@@ -22,8 +19,14 @@ public RulesAndInfoWindow()
var rootContainer = new TabContainer();
- var rulesList = new Info();
- var tutorialList = new Info();
+ var rulesList = new RulesControl
+ {
+ Margin = new Thickness(10)
+ };
+ var tutorialList = new Info
+ {
+ Margin = new Thickness(10)
+ };
rootContainer.AddChild(rulesList);
rootContainer.AddChild(tutorialList);
@@ -31,7 +34,6 @@ public RulesAndInfoWindow()
TabContainer.SetTabTitle(rulesList, Loc.GetString("ui-info-tab-rules"));
TabContainer.SetTabTitle(tutorialList, Loc.GetString("ui-info-tab-tutorial"));
- AddSection(rulesList, _rules.RulesSection());
PopulateTutorial(tutorialList);
Contents.AddChild(rootContainer);
diff --git a/Content.Client/Info/RulesControl.xaml b/Content.Client/Info/RulesControl.xaml
index 3b24764688..04fa719123 100644
--- a/Content.Client/Info/RulesControl.xaml
+++ b/Content.Client/Info/RulesControl.xaml
@@ -1,6 +1,18 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Info/RulesControl.xaml.cs b/Content.Client/Info/RulesControl.xaml.cs
index 6086735e0e..22a520d539 100644
--- a/Content.Client/Info/RulesControl.xaml.cs
+++ b/Content.Client/Info/RulesControl.xaml.cs
@@ -1,22 +1,54 @@
-using System.IO;
-using Content.Shared.CCVar;
+using Content.Client.Guidebook;
+using Content.Client.Guidebook.RichText;
+using Content.Client.UserInterface.Systems.Info;
+using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated;
-using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Configuration;
+using Robust.Shared.Prototypes;
namespace Content.Client.Info;
[GenerateTypedNameReferences]
-public sealed partial class RulesControl : BoxContainer
+public sealed partial class RulesControl : BoxContainer, ILinkClickHandler
{
- [Dependency] private readonly RulesManager _rules = default!;
+ [Dependency] private readonly DocumentParsingManager _parsingMan = default!;
+
+ private string? _currentEntry;
+ private readonly Stack _priorEntries = new();
public RulesControl()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- AddChild(_rules.RulesSection());
+
+ SetGuide();
+
+ HomeButton.OnPressed += _ => SetGuide();
+
+ BackButton.OnPressed += _ => SetGuide(_priorEntries.Pop(), false);
+ }
+
+ public void HandleClick(string link)
+ {
+ SetGuide(link);
+ }
+
+ private void SetGuide(ProtoId? entry = null, bool addToPrior = true)
+ {
+ var coreEntry = UserInterfaceManager.GetUIController().GetCoreRuleEntry();
+ entry ??= coreEntry;
+
+ Scroll.SetScrollValue(default);
+ RulesContainer.Children.Clear();
+ if (!_parsingMan.TryAddMarkup(RulesContainer, entry.Value))
+ return;
+
+ if (addToPrior && _currentEntry != null)
+ _priorEntries.Push(_currentEntry);
+ _currentEntry = entry.Value;
+
+ HomeButton.Visible = entry.Value != coreEntry.Id;
+ BackButton.Visible = _priorEntries.Count != 0 && _priorEntries.Peek() != entry.Value;
}
}
diff --git a/Content.Client/Info/RulesManager.cs b/Content.Client/Info/RulesManager.cs
deleted file mode 100644
index 76e7c34e5f..0000000000
--- a/Content.Client/Info/RulesManager.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-using Content.Client.Lobby;
-using Content.Client.Gameplay;
-using Content.Shared.CCVar;
-using Content.Shared.Info;
-using Robust.Client.Console;
-using Robust.Client.State;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Configuration;
-using Robust.Shared.Network;
-
-namespace Content.Client.Info;
-
-public sealed class RulesManager : SharedRulesManager
-{
- [Dependency] private readonly IConfigurationManager _configManager = default!;
- [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
- [Dependency] private readonly IStateManager _stateManager = default!;
- [Dependency] private readonly IClientConsoleHost _consoleHost = default!;
- [Dependency] private readonly INetManager _netManager = default!;
- [Dependency] private readonly IEntitySystemManager _sysMan = default!;
-
- private InfoSection rulesSection = new InfoSection("", "", false);
- private bool _shouldShowRules = false;
-
- private RulesPopup? _activePopup;
-
- public void Initialize()
- {
- _netManager.RegisterNetMessage(OnShouldShowRules);
- _netManager.RegisterNetMessage(OnShowRulesPopupMessage);
- _netManager.RegisterNetMessage();
- _stateManager.OnStateChanged += OnStateChanged;
-
- _consoleHost.RegisterCommand("fuckrules", "", "", (_, _, _) =>
- {
- OnAcceptPressed();
- });
- }
-
- private void OnShouldShowRules(ShouldShowRulesPopupMessage message)
- {
- _shouldShowRules = true;
- }
-
- private void OnShowRulesPopupMessage(ShowRulesPopupMessage message)
- {
- ShowRules(message.PopupTime);
- }
-
- private void OnStateChanged(StateChangedEventArgs args)
- {
- if (args.NewState is not (GameplayState or LobbyState))
- return;
-
- if (!_shouldShowRules)
- return;
-
- _shouldShowRules = false;
-
- ShowRules(_configManager.GetCVar(CCVars.RulesWaitTime));
- }
-
- private void ShowRules(float time)
- {
- if (_activePopup != null)
- return;
-
- _activePopup = new RulesPopup
- {
- Timer = time
- };
-
- _activePopup.OnQuitPressed += OnQuitPressed;
- _activePopup.OnAcceptPressed += OnAcceptPressed;
- _userInterfaceManager.WindowRoot.AddChild(_activePopup);
- LayoutContainer.SetAnchorPreset(_activePopup, LayoutContainer.LayoutPreset.Wide);
- }
-
- private void OnQuitPressed()
- {
- _consoleHost.ExecuteCommand("quit");
- }
-
- private void OnAcceptPressed()
- {
- _netManager.ClientSendMessage(new RulesAcceptedMessage());
-
- _activePopup?.Orphan();
- _activePopup = null;
- }
-
- public void UpdateRules()
- {
- var rules = _sysMan.GetEntitySystem().Rules;
- rulesSection.SetText(rules.Title, rules.Text, true);
- }
-
- public Control RulesSection()
- {
- rulesSection = new InfoSection("", "", false);
- UpdateRules();
- return rulesSection;
- }
-}
diff --git a/Content.Client/Info/RulesPopup.xaml b/Content.Client/Info/RulesPopup.xaml
index dc004af5a2..ca1e35f08e 100644
--- a/Content.Client/Info/RulesPopup.xaml
+++ b/Content.Client/Info/RulesPopup.xaml
@@ -5,20 +5,20 @@
MouseFilter="Stop">
+ MaxWidth="800"
+ MaxHeight="900">
-
-
-
+
+
-
diff --git a/Content.Client/Info/RulesPopup.xaml.cs b/Content.Client/Info/RulesPopup.xaml.cs
index 1e09004936..d770b83dc2 100644
--- a/Content.Client/Info/RulesPopup.xaml.cs
+++ b/Content.Client/Info/RulesPopup.xaml.cs
@@ -1,9 +1,7 @@
-using System;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Localization;
using Robust.Shared.Timing;
namespace Content.Client.Info;
diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs
index 03f4f3f38b..7a8a993854 100644
--- a/Content.Client/Input/ContentContexts.cs
+++ b/Content.Client/Input/ContentContexts.cs
@@ -32,11 +32,13 @@ public static void SetupContexts(IInputContextContainer contexts)
common.AddFunction(ContentKeyFunctions.ToggleFullscreen);
common.AddFunction(ContentKeyFunctions.MoveStoredItem);
common.AddFunction(ContentKeyFunctions.RotateStoredItem);
+ common.AddFunction(ContentKeyFunctions.SaveItemLocation);
common.AddFunction(ContentKeyFunctions.Point);
common.AddFunction(ContentKeyFunctions.ZoomOut);
common.AddFunction(ContentKeyFunctions.ZoomIn);
common.AddFunction(ContentKeyFunctions.ResetZoom);
common.AddFunction(ContentKeyFunctions.InspectEntity);
+ common.AddFunction(ContentKeyFunctions.ToggleRoundEndSummaryWindow);
// Not in engine, because engine cannot check for sanbox/admin status before starting placement.
common.AddFunction(ContentKeyFunctions.EditorCopyObject);
@@ -44,6 +46,9 @@ public static void SetupContexts(IInputContextContainer contexts)
// Not in engine because the engine doesn't understand what a flipped object is
common.AddFunction(ContentKeyFunctions.EditorFlipObject);
+ // Not in engine so that the RCD can rotate objects
+ common.AddFunction(EngineKeyFunctions.EditorRotateObject);
+
var human = contexts.GetContext("human");
human.AddFunction(EngineKeyFunctions.MoveUp);
human.AddFunction(EngineKeyFunctions.MoveDown);
@@ -55,6 +60,7 @@ public static void SetupContexts(IInputContextContainer contexts)
human.AddFunction(ContentKeyFunctions.UseItemInHand);
human.AddFunction(ContentKeyFunctions.AltUseItemInHand);
human.AddFunction(ContentKeyFunctions.OpenCharacterMenu);
+ human.AddFunction(ContentKeyFunctions.OpenEmotesMenu);
human.AddFunction(ContentKeyFunctions.ActivateItemInWorld);
human.AddFunction(ContentKeyFunctions.ThrowItemInHand);
human.AddFunction(ContentKeyFunctions.AltActivateItemInWorld);
diff --git a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs
index 2a846ff708..0f5729f55b 100644
--- a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs
+++ b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs
@@ -37,14 +37,8 @@ public InstrumentBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, u
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
- switch (message)
- {
- case InstrumentBandResponseBuiMessage bandRx:
- _bandMenu?.Populate(bandRx.Nearby, EntMan);
- break;
- default:
- break;
- }
+ if (message is InstrumentBandResponseBuiMessage bandRx)
+ _bandMenu?.Populate(bandRx.Nearby, EntMan);
}
protected override void Open()
diff --git a/Content.Client/Interactable/InteractionSystem.cs b/Content.Client/Interactable/InteractionSystem.cs
index 0af8830e9a..ff0a607920 100644
--- a/Content.Client/Interactable/InteractionSystem.cs
+++ b/Content.Client/Interactable/InteractionSystem.cs
@@ -4,24 +4,6 @@
namespace Content.Client.Interactable
{
- public sealed class InteractionSystem : SharedInteractionSystem
- {
- [Dependency] private readonly SharedContainerSystem _container = default!;
-
- public override bool CanAccessViaStorage(EntityUid user, EntityUid target)
- {
- if (!EntityManager.EntityExists(target))
- return false;
-
- if (!_container.TryGetContainingContainer(target, out var container))
- return false;
-
- if (!HasComp(container.Owner))
- return false;
-
- // we don't check if the user can access the storage entity itself. This should be handed by the UI system.
- // Need to return if UI is open or not
- return true;
- }
- }
+ // TODO Remove Shared prefix
+ public sealed class InteractionSystem : SharedInteractionSystem;
}
diff --git a/Content.Client/Interaction/DragDropHelper.cs b/Content.Client/Interaction/DragDropHelper.cs
index abe35bf6d9..e453dfd74b 100644
--- a/Content.Client/Interaction/DragDropHelper.cs
+++ b/Content.Client/Interaction/DragDropHelper.cs
@@ -73,11 +73,6 @@ public DragDropHelper(OnBeginDrag onBeginDrag, OnContinueDrag onContinueDrag, On
_cfg.OnValueChanged(CCVars.DragDropDeadZone, SetDeadZone, true);
}
- ~DragDropHelper()
- {
- _cfg.UnsubValueChanged(CCVars.DragDropDeadZone, SetDeadZone);
- }
-
///
/// Tell the helper that the mouse button was pressed down on
/// a target, thus a drag has the possibility to begin for this target.
diff --git a/Content.Client/Inventory/ClientInventorySystem.cs b/Content.Client/Inventory/ClientInventorySystem.cs
index 7b98513a92..87cea4e3d2 100644
--- a/Content.Client/Inventory/ClientInventorySystem.cs
+++ b/Content.Client/Inventory/ClientInventorySystem.cs
@@ -199,7 +199,7 @@ public void UIInventoryActivate(string slot)
public void UIInventoryStorageActivate(string slot)
{
- EntityManager.EntityNetManager?.SendSystemNetworkMessage(new OpenSlotStorageNetworkMessage(slot));
+ EntityManager.RaisePredictiveEvent(new OpenSlotStorageNetworkMessage(slot));
}
public void UIInventoryExamine(string slot, EntityUid uid)
@@ -251,6 +251,7 @@ public sealed class SlotData
public string SlotGroup => SlotDef.SlotGroup;
public string SlotDisplayName => SlotDef.DisplayName;
public string TextureName => "Slots/" + SlotDef.TextureName;
+ public string FullTextureName => SlotDef.FullTextureName;
public SlotData(SlotDefinition slotDef, ContainerSlot? container = null, bool highlighted = false,
bool blocked = false)
diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs
index f8eb12df91..7e50eb1c68 100644
--- a/Content.Client/Inventory/StrippableBoundUserInterface.cs
+++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs
@@ -21,7 +21,6 @@
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
using static Content.Client.Inventory.ClientInventorySystem;
using static Robust.Client.UserInterface.Control;
@@ -53,9 +52,13 @@ public StrippableBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, u
_inv = EntMan.System();
_cuffable = EntMan.System();
+ // TODO update name when identity changes
var title = Loc.GetString("strippable-bound-user-interface-stripping-menu-title", ("ownerName", Identity.Name(Owner, EntMan)));
_strippingMenu = new StrippingMenu(title, this);
_strippingMenu.OnClose += Close;
+
+ // TODO use global entity
+ // BUIs are opened and closed while applying comp sates, so spawning entities here is probably not the best idea.
_virtualHiddenEntity = EntMan.SpawnEntity(HiddenPocketEntityId, MapCoordinates.Nullspace);
}
@@ -219,7 +222,7 @@ private void UpdateEntityIcon(SlotControl button, EntityUid? entity)
if (entity == null)
{
- button.SpriteView.SetEntity(null);
+ button.SetEntity(null);
return;
}
@@ -231,7 +234,7 @@ private void UpdateEntityIcon(SlotControl button, EntityUid? entity)
else
return;
- button.SpriteView.SetEntity(viewEnt);
+ button.SetEntity(viewEnt);
}
}
}
diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs
index 70fe191658..d1c595ae9b 100644
--- a/Content.Client/IoC/ClientContentIoC.cs
+++ b/Content.Client/IoC/ClientContentIoC.cs
@@ -2,25 +2,22 @@
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
using Content.Client.Clickable;
-using Content.Client.Options;
using Content.Client.Eui;
using Content.Client.GhostKick;
-using Content.Client.Info;
using Content.Client.Launcher;
using Content.Client.Parallax.Managers;
using Content.Client.Players.PlayTimeTracking;
-using Content.Client.Preferences;
using Content.Client.Screenshot;
using Content.Client.Fullscreen;
using Content.Client.Stylesheets;
using Content.Client.Viewport;
using Content.Client.Voting;
-using Content.Shared.Administration;
using Content.Shared.Administration.Logs;
-using Content.Shared.Module;
using Content.Client.Guidebook;
+using Content.Client.Lobby;
using Content.Client.Replay;
using Content.Shared.Administration.Managers;
+using Content.Shared.Players.PlayTimeTracking;
namespace Content.Client.IoC
@@ -29,26 +26,28 @@ internal static class ClientContentIoC
{
public static void Register()
{
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
- IoCManager.Register();
+ var collection = IoCManager.Instance!;
+
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
}
}
}
diff --git a/Content.Client/Items/Systems/ItemSystem.cs b/Content.Client/Items/Systems/ItemSystem.cs
index e406ba2b55..5e60d06d0c 100644
--- a/Content.Client/Items/Systems/ItemSystem.cs
+++ b/Content.Client/Items/Systems/ItemSystem.cs
@@ -93,7 +93,7 @@ private bool TryGetDefaultVisuals(EntityUid uid, ItemComponent item, string defa
else if (TryComp(uid, out SpriteComponent? sprite))
rsi = sprite.BaseRSI;
- if (rsi == null || rsi.Path == null)
+ if (rsi == null)
return false;
var state = (item.HeldPrefix == null)
diff --git a/Content.Client/Items/UI/PollingItemStatusControl.cs b/Content.Client/Items/UI/PollingItemStatusControl.cs
new file mode 100644
index 0000000000..39cffb06f6
--- /dev/null
+++ b/Content.Client/Items/UI/PollingItemStatusControl.cs
@@ -0,0 +1,28 @@
+using Robust.Client.UserInterface;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Items.UI;
+
+///
+/// A base for item status controls that poll data every frame. Avoids UI updates if data didn't change.
+///
+/// The full status control data that is polled every frame.
+public abstract class PollingItemStatusControl : Control where TData : struct, IEquatable
+{
+ private TData _lastData;
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ var newData = PollData();
+ if (newData.Equals(_lastData))
+ return;
+
+ _lastData = newData;
+ Update(newData);
+ }
+
+ protected abstract TData PollData();
+ protected abstract void Update(in TData data);
+}
diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml b/Content.Client/Kitchen/UI/GrinderMenu.xaml
index b83128d004..dacddd0df6 100644
--- a/Content.Client/Kitchen/UI/GrinderMenu.xaml
+++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml
@@ -3,10 +3,12 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc grinder-menu-title}" MinSize="768 256">
-
-
-
-
+
+
+
+
+
+
diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
index 6e4b7a7618..f97d8a7330 100644
--- a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
+++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
@@ -24,6 +24,7 @@ public GrinderMenu(ReagentGrinderBoundUserInterface owner, IEntityManager entity
_entityManager = entityManager;
_prototypeManager = prototypeManager;
_owner = owner;
+ AutoModeButton.OnPressed += owner.ToggleAutoMode;
GrindButton.OnPressed += owner.StartGrinding;
JuiceButton.OnPressed += owner.StartJuicing;
ChamberContentBox.EjectButton.OnPressed += owner.EjectAll;
@@ -56,6 +57,19 @@ public void UpdateState(ReagentGrinderInterfaceState state)
GrindButton.Disabled = !state.CanGrind || !state.Powered;
JuiceButton.Disabled = !state.CanJuice || !state.Powered;
+ switch (state.AutoMode)
+ {
+ case GrinderAutoMode.Grind:
+ AutoModeButton.Text = Loc.GetString("grinder-menu-grind-button");
+ break;
+ case GrinderAutoMode.Juice:
+ AutoModeButton.Text = Loc.GetString("grinder-menu-juice-button");
+ break;
+ default:
+ AutoModeButton.Text = Loc.GetString("grinder-menu-auto-button-off");
+ break;
+ }
+
// TODO move this to a component state and ensure the net ids.
RefreshContentsDisplay(state.ReagentQuantities, _entityManager.GetEntityArray(state.ChamberContents), state.HasBeakerIn);
}
diff --git a/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs b/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
index 39b85c261b..e6f108b305 100644
--- a/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
+++ b/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
@@ -52,6 +52,11 @@ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
_menu?.HandleMessage(message);
}
+ public void ToggleAutoMode(BaseButton.ButtonEventArgs args)
+ {
+ SendMessage(new ReagentGrinderToggleAutoModeMessage());
+ }
+
public void StartGrinding(BaseButton.ButtonEventArgs? _ = null)
{
SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Grind));
diff --git a/Content.Client/Labels/EntitySystems/HandLabelerSystem.cs b/Content.Client/Labels/EntitySystems/HandLabelerSystem.cs
new file mode 100644
index 0000000000..e956014b2e
--- /dev/null
+++ b/Content.Client/Labels/EntitySystems/HandLabelerSystem.cs
@@ -0,0 +1,18 @@
+using Content.Client.Labels.UI;
+using Content.Shared.Labels;
+using Content.Shared.Labels.Components;
+using Content.Shared.Labels.EntitySystems;
+
+namespace Content.Client.Labels.EntitySystems;
+
+public sealed class HandLabelerSystem : SharedHandLabelerSystem
+{
+ protected override void UpdateUI(Entity ent)
+ {
+ if (UserInterfaceSystem.TryGetOpenUi(ent.Owner, HandLabelerUiKey.Key, out var bui)
+ && bui is HandLabelerBoundUserInterface cBui)
+ {
+ cBui.Reload();
+ }
+ }
+}
diff --git a/Content.Client/Labels/EntitySystems/LabelSystem.cs b/Content.Client/Labels/EntitySystems/LabelSystem.cs
new file mode 100644
index 0000000000..baa9f7fee7
--- /dev/null
+++ b/Content.Client/Labels/EntitySystems/LabelSystem.cs
@@ -0,0 +1,7 @@
+using Content.Shared.Labels.EntitySystems;
+
+namespace Content.Client.Labels;
+
+public sealed partial class LabelSystem : SharedLabelSystem
+{
+}
diff --git a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
index d393c43f93..555f1ff09e 100644
--- a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
+++ b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
@@ -1,4 +1,5 @@
using Content.Shared.Labels;
+using Content.Shared.Labels.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Labels.UI
@@ -8,11 +9,14 @@ namespace Content.Client.Labels.UI
///
public sealed class HandLabelerBoundUserInterface : BoundUserInterface
{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+
[ViewVariables]
private HandLabelerWindow? _window;
public HandLabelerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
+ IoCManager.InjectDependencies(this);
}
protected override void Open()
@@ -27,24 +31,25 @@ protected override void Open()
_window.OnClose += Close;
_window.OnLabelChanged += OnLabelChanged;
+ Reload();
}
private void OnLabelChanged(string newLabel)
{
- SendMessage(new HandLabelerLabelChangedMessage(newLabel));
+ // Focus moment
+ if (_entManager.TryGetComponent(Owner, out HandLabelerComponent? labeler) &&
+ labeler.AssignedLabel.Equals(newLabel))
+ return;
+
+ SendPredictedMessage(new HandLabelerLabelChangedMessage(newLabel));
}
- ///
- /// Update the UI state based on server-sent info
- ///
- ///
- protected override void UpdateState(BoundUserInterfaceState state)
+ public void Reload()
{
- base.UpdateState(state);
- if (_window == null || state is not HandLabelerBoundUserInterfaceState cast)
+ if (_window == null || !_entManager.TryGetComponent(Owner, out HandLabelerComponent? component))
return;
- _window.SetCurrentLabel(cast.CurrentLabel);
+ _window.SetCurrentLabel(component.AssignedLabel);
}
protected override void Dispose(bool disposing)
diff --git a/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs b/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs
index 7706c31f85..6482cdc1cc 100644
--- a/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs
+++ b/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs
@@ -9,17 +9,40 @@ public sealed partial class HandLabelerWindow : DefaultWindow
{
public event Action? OnLabelChanged;
+ ///
+ /// Is the user currently entering text into the control?
+ ///
+ private bool _focused;
+ // TODO LineEdit Make this a bool on the LineEdit control
+
+ private string _label = string.Empty;
+
public HandLabelerWindow()
{
RobustXamlLoader.Load(this);
- LabelLineEdit.OnTextEntered += e => OnLabelChanged?.Invoke(e.Text);
- LabelLineEdit.OnFocusExit += e => OnLabelChanged?.Invoke(e.Text);
+ LabelLineEdit.OnTextEntered += e =>
+ {
+ _label = e.Text;
+ OnLabelChanged?.Invoke(_label);
+ };
+
+ LabelLineEdit.OnFocusEnter += _ => _focused = true;
+ LabelLineEdit.OnFocusExit += _ =>
+ {
+ _focused = false;
+ LabelLineEdit.Text = _label;
+ };
}
public void SetCurrentLabel(string label)
{
- LabelLineEdit.Text = label;
+ if (label == _label)
+ return;
+
+ _label = label;
+ if (!_focused)
+ LabelLineEdit.Text = label;
}
}
}
diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs
index 3e7ca57476..62a06629f2 100644
--- a/Content.Client/LateJoin/LateJoinGui.cs
+++ b/Content.Client/LateJoin/LateJoinGui.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using System.Numerics;
using Content.Client.CrewManifest;
using Content.Client.GameTicking.Managers;
@@ -32,7 +33,7 @@ public sealed class LateJoinGui : DefaultWindow
private readonly SpriteSystem _sprites;
private readonly CrewManifestSystem _crewManifest;
- private readonly Dictionary> _jobButtons = new();
+ private readonly Dictionary>> _jobButtons = new();
private readonly Dictionary> _jobCategories = new();
private readonly List _jobLists = new();
@@ -138,7 +139,7 @@ private void RebuildUI()
var jobListScroll = new ScrollContainer()
{
VerticalExpand = true,
- Children = {jobList},
+ Children = { jobList },
Visible = false,
};
@@ -159,12 +160,15 @@ private void RebuildUI()
};
var firstCategory = true;
+ var departments = _prototypeManager.EnumeratePrototypes().ToArray();
+ Array.Sort(departments, DepartmentUIComparer.Instance);
- foreach (var department in _prototypeManager.EnumeratePrototypes())
+ _jobButtons[id] = new Dictionary>();
+
+ foreach (var department in departments)
{
var departmentName = Loc.GetString($"department-{department.ID}");
_jobCategories[id] = new Dictionary();
- _jobButtons[id] = new Dictionary();
var stationAvailable = _gameTicker.JobsAvailable[id];
var jobsAvailable = new List();
@@ -176,7 +180,7 @@ private void RebuildUI()
jobsAvailable.Add(_prototypeManager.Index(jobId));
}
- jobsAvailable.Sort((x, y) => -string.Compare(x.LocalizedName, y.LocalizedName, StringComparison.CurrentCultureIgnoreCase));
+ jobsAvailable.Sort(JobUIComparer.Instance);
// Do not display departments with no jobs available.
if (jobsAvailable.Count == 0)
@@ -220,7 +224,13 @@ private void RebuildUI()
foreach (var prototype in jobsAvailable)
{
var value = stationAvailable[prototype.ID];
- var jobButton = new JobButton(prototype.ID, value);
+
+ var jobLabel = new Label
+ {
+ Margin = new Thickness(5f, 0, 0, 0)
+ };
+
+ var jobButton = new JobButton(jobLabel, prototype.ID, prototype.LocalizedName, value);
var jobSelector = new BoxContainer
{
@@ -231,21 +241,13 @@ private void RebuildUI()
var icon = new TextureRect
{
TextureScale = new Vector2(2, 2),
- Stretch = TextureRect.StretchMode.KeepCentered
+ VerticalAlignment = VAlignment.Center
};
- var jobIcon = _prototypeManager.Index(prototype.Icon);
+ var jobIcon = _prototypeManager.Index(prototype.Icon);
icon.Texture = _sprites.Frame0(jobIcon.Icon);
jobSelector.AddChild(icon);
- var jobLabel = new Label
- {
- Margin = new Thickness(5f, 0, 0, 0),
- Text = value != null ?
- Loc.GetString("late-join-gui-job-slot-capped", ("jobName", prototype.LocalizedName), ("amount", value)) :
- Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", prototype.LocalizedName)),
- };
-
jobSelector.AddChild(jobLabel);
jobButton.AddChild(jobSelector);
category.AddChild(jobButton);
@@ -277,15 +279,43 @@ private void RebuildUI()
jobButton.Disabled = true;
}
- _jobButtons[id][prototype.ID] = jobButton;
+ if (!_jobButtons[id].ContainsKey(prototype.ID))
+ {
+ _jobButtons[id][prototype.ID] = new List();
+ }
+
+ _jobButtons[id][prototype.ID].Add(jobButton);
}
}
}
}
- private void JobsAvailableUpdated(IReadOnlyDictionary> _)
+ private void JobsAvailableUpdated(IReadOnlyDictionary, int?>> updatedJobs)
{
- RebuildUI();
+ foreach (var stationEntries in updatedJobs)
+ {
+ if (_jobButtons.ContainsKey(stationEntries.Key))
+ {
+ var jobsAvailable = stationEntries.Value;
+
+ var existingJobEntries = _jobButtons[stationEntries.Key];
+ foreach (var existingJobEntry in existingJobEntries)
+ {
+ if (jobsAvailable.ContainsKey(existingJobEntry.Key))
+ {
+ var updatedJobValue = jobsAvailable[existingJobEntry.Key];
+ foreach (var matchingJobButton in existingJobEntry.Value)
+ {
+ if (matchingJobButton.Amount != updatedJobValue)
+ {
+ matchingJobButton.RefreshLabel(updatedJobValue);
+ matchingJobButton.Disabled |= matchingJobButton.Amount == 0;
+ }
+ }
+ }
+ }
+ }
+ }
}
protected override void Dispose(bool disposing)
@@ -304,14 +334,33 @@ protected override void Dispose(bool disposing)
sealed class JobButton : ContainerButton
{
+ public Label JobLabel { get; }
public string JobId { get; }
- public uint? Amount { get; }
+ public string JobLocalisedName { get; }
+ public int? Amount { get; private set; }
+ private bool _initialised = false;
- public JobButton(string jobId, uint? amount)
+ public JobButton(Label jobLabel, ProtoId jobId, string jobLocalisedName, int? amount)
{
+ JobLabel = jobLabel;
JobId = jobId;
- Amount = amount;
+ JobLocalisedName = jobLocalisedName;
+ RefreshLabel(amount);
AddStyleClass(StyleClassButton);
+ _initialised = true;
+ }
+
+ public void RefreshLabel(int? amount)
+ {
+ if (Amount == amount && _initialised)
+ {
+ return;
+ }
+ Amount = amount;
+
+ JobLabel.Text = Amount != null ?
+ Loc.GetString("late-join-gui-job-slot-capped", ("jobName", JobLocalisedName), ("amount", Amount)) :
+ Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", JobLocalisedName));
}
}
}
diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml
index 2b97166f05..6f484d8c7b 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml
@@ -124,9 +124,7 @@
+ Orientation="Vertical">
textures;
+ if (_prototypeManager.TryIndex(prototype.Result, out EntityPrototype? entityProto) && entityProto != null)
{
- if (!_prototypeManager.TryIndex(id, out var proto))
- continue;
-
- if (first)
- first = false;
- else
- sb.Append('\n');
-
- var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, component.MaterialUseMultiplier);
- var sheetVolume = _materialStorage.GetSheetVolume(proto);
-
- var unit = Loc.GetString(proto.Unit);
- // rounded in locale not here
- var sheets = adjustedAmount / (float) sheetVolume;
- var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit));
- var name = Loc.GetString(proto.Name);
- sb.Append(Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText)));
+ textures = SpriteComponent.GetPrototypeTextures(entityProto, _resources).Select(o => o.Default).ToList();
}
-
- if (!string.IsNullOrWhiteSpace(prototype.Description))
+ else
{
- sb.Append('\n');
- sb.Append(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
+ textures = prototype.Icon == null
+ ? new List { _spriteSystem.GetPrototypeIcon(prototype.Result).Default }
+ : new List { _spriteSystem.Frame0(prototype.Icon) };
}
- var icon = prototype.Icon == null
- ? _spriteSystem.GetPrototypeIcon(prototype.Result).Default
- : _spriteSystem.Frame0(prototype.Icon);
var canProduce = _lathe.CanProduce(_owner, prototype, quantity);
- var control = new RecipeControl(prototype, sb.ToString(), canProduce, icon);
+ var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, textures);
control.OnButtonPressed += s =>
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
@@ -149,6 +133,51 @@ public void PopulateRecipes()
}
}
+ private string GenerateTooltipText(LatheRecipePrototype prototype)
+ {
+ StringBuilder sb = new();
+
+ foreach (var (id, amount) in prototype.RequiredMaterials)
+ {
+ if (!_prototypeManager.TryIndex(id, out var proto))
+ continue;
+
+ var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, _entityManager.GetComponent(_owner).MaterialUseMultiplier);
+ var sheetVolume = _materialStorage.GetSheetVolume(proto);
+
+ var unit = Loc.GetString(proto.Unit);
+ var sheets = adjustedAmount / (float) sheetVolume;
+
+ var availableAmount = _materialStorage.GetMaterialAmount(_owner, id);
+ var missingAmount = Math.Max(0, adjustedAmount - availableAmount);
+ var missingSheets = missingAmount / (float) sheetVolume;
+
+ var name = Loc.GetString(proto.Name);
+
+ string tooltipText;
+ if (missingSheets > 0)
+ {
+ tooltipText = Loc.GetString("lathe-menu-material-amount-missing", ("amount", sheets), ("missingAmount", missingSheets), ("unit", unit), ("material", name));
+ }
+ else
+ {
+ var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit));
+ tooltipText = Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText));
+ }
+
+ sb.AppendLine(tooltipText);
+ }
+
+ if (!string.IsNullOrWhiteSpace(prototype.Description))
+ sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
+
+ // Remove last newline
+ if (sb.Length > 0)
+ sb.Remove(sb.Length - 1, 1);
+
+ return sb.ToString();
+ }
+
public void UpdateCategories()
{
var currentCategories = new List>();
diff --git a/Content.Client/Lathe/UI/RecipeControl.xaml b/Content.Client/Lathe/UI/RecipeControl.xaml
index 2e02c8a614..d1371a026a 100644
--- a/Content.Client/Lathe/UI/RecipeControl.xaml
+++ b/Content.Client/Lathe/UI/RecipeControl.xaml
@@ -5,11 +5,15 @@
Margin="0"
StyleClasses="ButtonSquare">
-
+ CanShrink="true"
+ />
diff --git a/Content.Client/Lathe/UI/RecipeControl.xaml.cs b/Content.Client/Lathe/UI/RecipeControl.xaml.cs
index 451a988765..47b6b5932c 100644
--- a/Content.Client/Lathe/UI/RecipeControl.xaml.cs
+++ b/Content.Client/Lathe/UI/RecipeControl.xaml.cs
@@ -2,8 +2,8 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Graphics;
namespace Content.Client.Lathe.UI;
@@ -11,17 +11,16 @@ namespace Content.Client.Lathe.UI;
public sealed partial class RecipeControl : Control
{
public Action? OnButtonPressed;
+ public Func TooltipTextSupplier;
- public string TooltipText;
-
- public RecipeControl(LatheRecipePrototype recipe, string tooltip, bool canProduce, Texture? texture = null)
+ public RecipeControl(LatheRecipePrototype recipe, Func tooltipTextSupplier, bool canProduce, List textures)
{
RobustXamlLoader.Load(this);
RecipeName.Text = recipe.Name;
- RecipeTexture.Texture = texture;
+ RecipeTextures.Textures = textures;
Button.Disabled = !canProduce;
- TooltipText = tooltip;
+ TooltipTextSupplier = tooltipTextSupplier;
Button.TooltipSupplier = SupplyTooltip;
Button.OnPressed += (_) =>
@@ -32,6 +31,6 @@ public RecipeControl(LatheRecipePrototype recipe, string tooltip, bool canProduc
private Control? SupplyTooltip(Control sender)
{
- return new RecipeTooltip(TooltipText);
+ return new RecipeTooltip(TooltipTextSupplier());
}
}
diff --git a/Content.Client/Lathe/UI/RecipeTooltip.xaml b/Content.Client/Lathe/UI/RecipeTooltip.xaml
index 8cb0ec507a..23f9e65c51 100644
--- a/Content.Client/Lathe/UI/RecipeTooltip.xaml
+++ b/Content.Client/Lathe/UI/RecipeTooltip.xaml
@@ -3,13 +3,13 @@
MaxWidth="400">
-
-
diff --git a/Content.Client/Launcher/LauncherConnecting.cs b/Content.Client/Launcher/LauncherConnecting.cs
index 9625f9e31c..29b241ae5d 100644
--- a/Content.Client/Launcher/LauncherConnecting.cs
+++ b/Content.Client/Launcher/LauncherConnecting.cs
@@ -54,6 +54,7 @@ private set
public event Action? PageChanged;
public event Action? ConnectFailReasonChanged;
public event Action? ConnectionStateChanged;
+ public event Action? ConnectFailed;
protected override void Startup()
{
@@ -85,6 +86,7 @@ private void OnConnectFailed(object? _, NetConnectFailArgs args)
}
ConnectFailReason = args.Reason;
CurrentPage = Page.ConnectFailed;
+ ConnectFailed?.Invoke(args);
}
private void OnConnectStateChanged(ClientConnectionState state)
diff --git a/Content.Client/Launcher/LauncherConnectingGui.xaml b/Content.Client/Launcher/LauncherConnectingGui.xaml
index 083e4ca871..3734fa5b3a 100644
--- a/Content.Client/Launcher/LauncherConnectingGui.xaml
+++ b/Content.Client/Launcher/LauncherConnectingGui.xaml
@@ -33,10 +33,6 @@
-
diff --git a/Content.Client/Launcher/LauncherConnectingGui.xaml.cs b/Content.Client/Launcher/LauncherConnectingGui.xaml.cs
index 79fba7c285..ac74ad7b60 100644
--- a/Content.Client/Launcher/LauncherConnectingGui.xaml.cs
+++ b/Content.Client/Launcher/LauncherConnectingGui.xaml.cs
@@ -9,7 +9,6 @@
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
-using Robust.Shared.Localization;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
@@ -21,12 +20,15 @@ public sealed partial class LauncherConnectingGui : Control
{
private const float RedialWaitTimeSeconds = 15f;
private readonly LauncherConnecting _state;
+ private float _waitTime;
+
+ // Pressing reconnect will redial instead of simply reconnecting.
+ private bool _redial;
+
private readonly IRobustRandom _random;
private readonly IPrototypeManager _prototype;
private readonly IConfigurationManager _cfg;
- private float _redialWaitTime = RedialWaitTimeSeconds;
-
public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
IPrototypeManager prototype, IConfigurationManager config)
{
@@ -42,14 +44,8 @@ public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
Stylesheet = IoCManager.Resolve().SheetSpace;
ChangeLoginTip();
- ReconnectButton.OnPressed += _ => _state.RetryConnect();
- // Redial shouldn't fail, but if it does, try a reconnect (maybe we're being run from debug)
- RedialButton.OnPressed += _ =>
- {
- if (!_state.Redial())
- _state.RetryConnect();
- };
- RetryButton.OnPressed += _ => _state.RetryConnect();
+ ReconnectButton.OnPressed += ReconnectButtonPressed;
+ RetryButton.OnPressed += ReconnectButtonPressed;
ExitButton.OnPressed += _ => _state.Exit();
var addr = state.Address;
@@ -59,6 +55,7 @@ public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
state.PageChanged += OnPageChanged;
state.ConnectFailReasonChanged += ConnectFailReasonChanged;
state.ConnectionStateChanged += ConnectionStateChanged;
+ state.ConnectFailed += HandleDisconnectReason;
ConnectionStateChanged(state.ConnectionState);
@@ -68,6 +65,19 @@ public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
LastNetDisconnectedArgsChanged(edim.LastNetDisconnectedArgs);
}
+ // Just button, there's only one at once anyways :)
+ private void ReconnectButtonPressed(BaseButton.ButtonEventArgs args)
+ {
+ if (_redial)
+ {
+ // Redial shouldn't fail, but if it does, try a reconnect (maybe we're being run from debug)
+ if (_state.Redial())
+ return;
+ }
+
+ _state.RetryConnect();
+ }
+
private void ConnectFailReasonChanged(string? reason)
{
ConnectFailReason.SetMessage(reason == null
@@ -77,9 +87,30 @@ private void ConnectFailReasonChanged(string? reason)
private void LastNetDisconnectedArgsChanged(NetDisconnectedArgs? args)
{
- var redialFlag = args?.RedialFlag ?? false;
- RedialButton.Visible = redialFlag;
- ReconnectButton.Visible = !redialFlag;
+ HandleDisconnectReason(args);
+ }
+
+ private void HandleDisconnectReason(INetStructuredReason? reason)
+ {
+ if (reason == null)
+ {
+ _waitTime = 0;
+ _redial = false;
+ }
+ else
+ {
+ _redial = reason.RedialFlag;
+
+ if (reason.Message.Int32Of("delay") is { } delay)
+ {
+ _waitTime = delay;
+ }
+ else if (_redial)
+ {
+ _waitTime = RedialWaitTimeSeconds;
+ }
+
+ }
}
private void ChangeLoginTip()
@@ -108,16 +139,27 @@ private void ChangeLoginTip()
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
- _redialWaitTime -= args.DeltaSeconds;
- if (_redialWaitTime <= 0)
+
+ var button = _state.CurrentPage == LauncherConnecting.Page.ConnectFailed
+ ? RetryButton
+ : ReconnectButton;
+
+ _waitTime -= args.DeltaSeconds;
+ if (_waitTime <= 0)
{
- RedialButton.Disabled = false;
- RedialButton.Text = Loc.GetString("connecting-redial");
+ button.Disabled = false;
+ var key = _redial
+ ? "connecting-redial"
+ : _state.CurrentPage == LauncherConnecting.Page.ConnectFailed
+ ? "connecting-reconnect"
+ : "connecting-retry";
+
+ button.Text = Loc.GetString(key);
}
else
{
- RedialButton.Disabled = true;
- RedialButton.Text = Loc.GetString("connecting-redial-wait", ("time", _redialWaitTime.ToString("00.000")));
+ button.Disabled = true;
+ button.Text = Loc.GetString("connecting-redial-wait", ("time", _waitTime.ToString("00.000")));
}
}
diff --git a/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs b/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs
index a2a7fb2531..8077406730 100644
--- a/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs
+++ b/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs
@@ -52,7 +52,7 @@ protected override void OnAppearanceChange(EntityUid uid, ExpendableLightCompone
case ExpendableLightState.Lit:
_audioSystem.Stop(comp.PlayingStream);
comp.PlayingStream = _audioSystem.PlayPvs(
- comp.LoopedSound, uid, SharedExpendableLightComponent.LoopedSoundParams)?.Entity;
+ comp.LoopedSound, uid)?.Entity;
if (args.Sprite.LayerMapTryGet(ExpendableLightVisualLayers.Overlay, out var layerIdx, true))
{
diff --git a/Content.Client/Light/RgbLightControllerSystem.cs b/Content.Client/Light/RgbLightControllerSystem.cs
index ad8ca47582..85b6114830 100644
--- a/Content.Client/Light/RgbLightControllerSystem.cs
+++ b/Content.Client/Light/RgbLightControllerSystem.cs
@@ -146,7 +146,7 @@ private void GetOriginalColors(EntityUid uid, RgbLightControllerComponent? rgb =
else
{
// admeme fuck-ups or bad yaml?
- Logger.Warning($"RGB light attempted to use invalid sprite index {index} on entity {ToPrettyString(uid)}");
+ Log.Warning($"RGB light attempted to use invalid sprite index {index} on entity {ToPrettyString(uid)}");
rgb.Layers.Remove(index);
}
}
@@ -207,8 +207,11 @@ public override void FrameUpdate(float frameTime)
public static Color GetCurrentRgbColor(TimeSpan curTime, TimeSpan offset, Entity rgb)
{
+ var delta = (float)(curTime - offset).TotalSeconds;
+ var entOffset = Math.Abs(rgb.Owner.Id * 0.09817f);
+ var hue = (delta * rgb.Comp.CycleRate + entOffset) % 1;
return Color.FromHsv(new Vector4(
- (float) (((curTime.TotalSeconds - offset.TotalSeconds) * rgb.Comp.CycleRate + Math.Abs(rgb.Owner.Id * 0.1)) % 1),
+ MathF.Abs(hue),
1.0f,
1.0f,
1.0f
diff --git a/Content.Client/Preferences/ClientPreferencesManager.cs b/Content.Client/Lobby/ClientPreferencesManager.cs
similarity index 93%
rename from Content.Client/Preferences/ClientPreferencesManager.cs
rename to Content.Client/Lobby/ClientPreferencesManager.cs
index 34b2c33140..3f01e1a8f6 100644
--- a/Content.Client/Preferences/ClientPreferencesManager.cs
+++ b/Content.Client/Lobby/ClientPreferencesManager.cs
@@ -1,13 +1,11 @@
-using System;
-using System.Collections.Generic;
using System.Linq;
using Content.Shared.Preferences;
using Robust.Client;
-using Robust.Shared.IoC;
+using Robust.Client.Player;
using Robust.Shared.Network;
using Robust.Shared.Utility;
-namespace Content.Client.Preferences
+namespace Content.Client.Lobby
{
///
/// Receives and from the server during the initial
@@ -18,6 +16,7 @@ public sealed class ClientPreferencesManager : IClientPreferencesManager
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
public event Action? OnServerDataLoaded;
@@ -60,7 +59,8 @@ public void SelectCharacter(int slot)
public void UpdateCharacter(ICharacterProfile profile, int slot)
{
- profile.EnsureValid();
+ var collection = IoCManager.Instance!;
+ profile.EnsureValid(_playerManager.LocalSession!, collection);
var characters = new Dictionary(Preferences.Characters) {[slot] = profile};
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
var msg = new MsgUpdateCharacter
diff --git a/Content.Client/Preferences/IClientPreferencesManager.cs b/Content.Client/Lobby/IClientPreferencesManager.cs
similarity index 92%
rename from Content.Client/Preferences/IClientPreferencesManager.cs
rename to Content.Client/Lobby/IClientPreferencesManager.cs
index e55d6b600c..45a770b162 100644
--- a/Content.Client/Preferences/IClientPreferencesManager.cs
+++ b/Content.Client/Lobby/IClientPreferencesManager.cs
@@ -1,7 +1,6 @@
-using System;
using Content.Shared.Preferences;
-namespace Content.Client.Preferences
+namespace Content.Client.Lobby
{
public interface IClientPreferencesManager
{
diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs
index e9975f6827..6085442d41 100644
--- a/Content.Client/Lobby/LobbyState.cs
+++ b/Content.Client/Lobby/LobbyState.cs
@@ -1,19 +1,15 @@
+using Content.Client.Audio;
using Content.Client.GameTicking.Managers;
using Content.Client.LateJoin;
using Content.Client.Lobby.UI;
using Content.Client.Message;
-using Content.Client.Preferences;
-using Content.Client.Preferences.UI;
using Content.Client.UserInterface.Systems.Chat;
using Content.Client.Voting;
using Robust.Client;
using Robust.Client.Console;
-using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Configuration;
-using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -25,20 +21,15 @@ public sealed class LobbyState : Robust.Client.State.State
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
- [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IVoteManager _voteManager = default!;
- [Dependency] private readonly IConfigurationManager _configurationManager = default!;
- [Dependency] private readonly IClyde _clyde = default!;
-
- [ViewVariables] private CharacterSetupGui? _characterSetup;
private ClientGameTicker _gameTicker = default!;
+ private ContentAudioSystem _contentAudioSystem = default!;
protected override Type? LinkedScreenType { get; } = typeof(LobbyGui);
- private LobbyGui? _lobby;
+ public LobbyGui? Lobby;
protected override void Startup()
{
@@ -47,47 +38,29 @@ protected override void Startup()
return;
}
- _lobby = (LobbyGui) _userInterfaceManager.ActiveScreen;
+ Lobby = (LobbyGui) _userInterfaceManager.ActiveScreen;
var chatController = _userInterfaceManager.GetUIController();
_gameTicker = _entityManager.System();
- _characterSetup = new CharacterSetupGui(_entityManager, _resourceCache, _preferencesManager,
- _prototypeManager, _configurationManager);
- LayoutContainer.SetAnchorPreset(_characterSetup, LayoutContainer.LayoutPreset.Wide);
+ _contentAudioSystem = _entityManager.System();
+ _contentAudioSystem.LobbySoundtrackChanged += UpdateLobbySoundtrackInfo;
- _lobby.CharacterSetupState.AddChild(_characterSetup);
chatController.SetMainChat(true);
- _voteManager.SetPopupContainer(_lobby.VoteContainer);
-
- _characterSetup.CloseButton.OnPressed += _ =>
- {
- _lobby.SwitchState(LobbyGui.LobbyGuiState.Default);
- };
-
- _characterSetup.SaveButton.OnPressed += _ =>
- {
- _characterSetup.Save();
- // _lobby.CharacterPreview.UpdateUI();
- };
-
- LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide);
- _lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you...
- _clyde.SetWindowTitle($"{_baseClient.GameInfo?.ServerName} (veilcode)");
+ _voteManager.SetPopupContainer(Lobby.VoteContainer);
+ LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide);
+ Lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you...
UpdateLobbyUi();
- _lobby!.SetupCharacterButton.OnPressed += OnSetupPressed;
- _lobby.ReadyButton.OnPressed += OnReadyPressed;
- _lobby.ReadyButton.OnToggled += OnReadyToggled;
- _lobby.ToggleMenu.OnPressed += OnMenuPressed;
+ Lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed;
+ Lobby.SetupCharacterButton.OnPressed += OnSetupPressed;
+ Lobby.ReadyButton.OnPressed += OnReadyPressed;
+ Lobby.ReadyButton.OnToggled += OnReadyToggled;
+ Lobby!.AHelpButton.OnPressed += OnAhelpPressed;
_gameTicker.InfoBlobUpdated += UpdateLobbyUi;
_gameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
_gameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated;
-
- _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
-
- // _lobby.CharacterPreview.UpdateUI();
}
protected override void Shutdown()
@@ -97,32 +70,34 @@ protected override void Shutdown()
_gameTicker.InfoBlobUpdated -= UpdateLobbyUi;
_gameTicker.LobbyStatusUpdated -= LobbyStatusUpdated;
_gameTicker.LobbyLateJoinStatusUpdated -= LobbyLateJoinStatusUpdated;
+ _contentAudioSystem.LobbySoundtrackChanged -= UpdateLobbySoundtrackInfo;
_voteManager.ClearPopupContainer();
- // _lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed;
- _lobby!.SetupCharacterButton.OnPressed -= OnSetupPressed;
- _lobby!.ReadyButton.OnPressed -= OnReadyPressed;
- _lobby!.ReadyButton.OnToggled -= OnReadyToggled;
- _lobby.ToggleMenu.OnPressed -= OnMenuPressed;
-
- _lobby = null;
+ Lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed;
+ Lobby!.ReadyButton.OnPressed -= OnReadyPressed;
+ Lobby!.ReadyButton.OnToggled -= OnReadyToggled;
+ Lobby!.AHelpButton.OnPressed -= OnAhelpPressed;
- _characterSetup?.Dispose();
- _characterSetup = null;
-
- _preferencesManager.OnServerDataLoaded -= PreferencesDataLoaded;
+ Lobby = null;
}
- private void PreferencesDataLoaded()
+ public void SwitchState(LobbyGui.LobbyGuiState state)
{
- // _lobby?.CharacterPreview.UpdateUI();
+ // Yeah I hate this but LobbyState contains all the badness for now.
+ Lobby?.SwitchState(state);
}
private void OnSetupPressed(BaseButton.ButtonEventArgs args)
{
SetReady(false);
- _lobby!.SwitchState(LobbyGui.LobbyGuiState.CharacterSetup);
+ Lobby?.SwitchState(LobbyGui.LobbyGuiState.CharacterSetup);
+ }
+
+ private void OnAhelpPressed(BaseButton.ButtonEventArgs obj)
+ {
+ IoCManager.Resolve().ExecuteCommand(
+ $"openahelp");
}
private void OnReadyPressed(BaseButton.ButtonEventArgs args)
@@ -135,11 +110,6 @@ private void OnReadyPressed(BaseButton.ButtonEventArgs args)
new LateJoinGui().OpenCentered();
}
- private void OnMenuPressed(BaseButton.ButtonEventArgs args)
- {
- _lobby!.CenterPanel.Visible = !_lobby.CenterPanel.Visible;
- }
-
private void OnReadyToggled(BaseButton.ButtonToggledEventArgs args)
{
SetReady(args.Pressed);
@@ -149,13 +119,13 @@ public override void FrameUpdate(FrameEventArgs e)
{
if (_gameTicker.IsGameStarted)
{
- _lobby!.StartTime.Text = string.Empty;
+ Lobby!.StartTime.Text = string.Empty;
var roundTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
- _lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-time", ("hours", roundTime.Hours), ("minutes", roundTime.Minutes));
+ Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-time", ("hours", roundTime.Hours), ("minutes", roundTime.Minutes));
return;
}
- _lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started");
+ Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started");
string text;
if (_gameTicker.Paused)
@@ -164,7 +134,7 @@ public override void FrameUpdate(FrameEventArgs e)
}
else if (_gameTicker.StartTime < _gameTiming.CurTime)
{
- _lobby!.StartTime.Text = Loc.GetString("lobby-state-soon");
+ Lobby!.StartTime.Text = Loc.GetString("lobby-state-soon");
return;
}
else
@@ -181,7 +151,7 @@ public override void FrameUpdate(FrameEventArgs e)
}
}
- _lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text));
+ Lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text));
}
private void LobbyStatusUpdated()
@@ -192,54 +162,60 @@ private void LobbyStatusUpdated()
private void LobbyLateJoinStatusUpdated()
{
- _lobby!.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin;
+ Lobby!.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin;
}
private void UpdateLobbyUi()
{
if (_gameTicker.IsGameStarted)
{
- _lobby!.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state");
- _lobby!.ReadyButton.ToggleMode = false;
- _lobby!.ReadyButton.Pressed = false;
- _lobby!.ObserveButton.Disabled = false;
+ Lobby!.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state");
+ Lobby!.ReadyButton.ToggleMode = false;
+ Lobby!.ReadyButton.Pressed = false;
+ Lobby!.ObserveButton.Disabled = false;
}
else
{
- _lobby!.StartTime.Text = string.Empty;
- _lobby!.ReadyButton.Text = Loc.GetString(_lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready");
- _lobby!.ReadyButton.ToggleMode = true;
- _lobby!.ReadyButton.Disabled = false;
- _lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady;
- _lobby!.ObserveButton.Disabled = true;
+ Lobby!.StartTime.Text = string.Empty;
+ Lobby!.ReadyButton.Text = Loc.GetString(Lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready");
+ Lobby!.ReadyButton.ToggleMode = true;
+ Lobby!.ReadyButton.Disabled = false;
+ Lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady;
+ Lobby!.ObserveButton.Disabled = true;
}
if (_gameTicker.ServerInfoBlob != null)
{
- _lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob);
+ Lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob);
}
+ }
- if (_gameTicker.LobbySong == null)
+ private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev)
+ {
+ if (ev.SoundtrackFilename == null)
{
- _lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text"));
+ Lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text"));
}
- else if (_resourceCache.TryGetResource(_gameTicker.LobbySong, out var lobbySongResource))
+ else if (
+ ev.SoundtrackFilename != null
+ && _resourceCache.TryGetResource(ev.SoundtrackFilename, out var lobbySongResource)
+ )
{
var lobbyStream = lobbySongResource.AudioStream;
- var title = string.IsNullOrEmpty(lobbyStream.Title) ?
- Loc.GetString("lobby-state-song-unknown-title") :
- lobbyStream.Title;
+ var title = string.IsNullOrEmpty(lobbyStream.Title)
+ ? Loc.GetString("lobby-state-song-unknown-title")
+ : lobbyStream.Title;
- var artist = string.IsNullOrEmpty(lobbyStream.Artist) ?
- Loc.GetString("lobby-state-song-unknown-artist") :
- lobbyStream.Artist;
+ var artist = string.IsNullOrEmpty(lobbyStream.Artist)
+ ? Loc.GetString("lobby-state-song-unknown-artist")
+ : lobbyStream.Artist;
var markup = Loc.GetString("lobby-state-song-text",
("songTitle", title),
("songArtist", artist));
- _lobby!.LobbySong.SetMarkup(markup);
+ Lobby!.LobbySong.SetMarkup(markup);
}
}
@@ -247,11 +223,11 @@ private void UpdateLobbyBackground()
{
if (_gameTicker.LobbyBackground != null)
{
- _lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground );
+ Lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground );
}
else
{
- _lobby!.Background.Texture = null;
+ Lobby!.Background.Texture = null;
}
}
diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs
new file mode 100644
index 0000000000..e4a13ed8c6
--- /dev/null
+++ b/Content.Client/Lobby/LobbyUIController.cs
@@ -0,0 +1,426 @@
+using System.Linq;
+using Content.Client.Guidebook;
+using Content.Client.Humanoid;
+using Content.Client.Inventory;
+using Content.Client.Lobby.UI;
+using Content.Client.Players.PlayTimeTracking;
+using Content.Client.Station;
+using Content.Shared.CCVar;
+using Content.Shared.Clothing;
+using Content.Shared.GameTicking;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Markings;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Roles;
+using Content.Shared.Traits;
+using Robust.Client.Player;
+using Robust.Client.ResourceManagement;
+using Robust.Client.State;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controllers;
+using Robust.Shared.Configuration;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Lobby;
+
+public sealed class LobbyUIController : UIController, IOnStateEntered, IOnStateExited
+{
+ [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
+ [Dependency] private readonly IConfigurationManager _configurationManager = default!;
+ [Dependency] private readonly IFileDialogManager _dialogManager = default!;
+ [Dependency] private readonly ILogManager _logManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IResourceCache _resourceCache = default!;
+ [Dependency] private readonly IStateManager _stateManager = default!;
+ [Dependency] private readonly JobRequirementsManager _requirements = default!;
+ [Dependency] private readonly MarkingManager _markings = default!;
+ [UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
+ [UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
+ [UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
+ [UISystemDependency] private readonly GuidebookSystem _guide = default!;
+
+ private CharacterSetupGui? _characterSetup;
+ private HumanoidProfileEditor? _profileEditor;
+
+ ///
+ /// This is the characher preview panel in the chat. This should only update if their character updates.
+ ///
+ private LobbyCharacterPreviewPanel? PreviewPanel => GetLobbyPreview();
+
+ ///
+ /// This is the modified profile currently being edited.
+ ///
+ private HumanoidCharacterProfile? EditedProfile => _profileEditor?.Profile;
+
+ private int? EditedSlot => _profileEditor?.CharacterSlot;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ _prototypeManager.PrototypesReloaded += OnProtoReload;
+ _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
+ _requirements.Updated += OnRequirementsUpdated;
+
+ _configurationManager.OnValueChanged(CCVars.FlavorText, args =>
+ {
+ _profileEditor?.RefreshFlavorText();
+ });
+
+ _configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor());
+
+ _configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor());
+ }
+
+ private LobbyCharacterPreviewPanel? GetLobbyPreview()
+ {
+ if (_stateManager.CurrentState is LobbyState lobby)
+ {
+ return lobby.Lobby?.CharacterPreview;
+ }
+
+ return null;
+ }
+
+ private void OnRequirementsUpdated()
+ {
+ if (_profileEditor != null)
+ {
+ _profileEditor.RefreshAntags();
+ _profileEditor.RefreshJobs();
+ }
+ }
+
+ private void OnProtoReload(PrototypesReloadedEventArgs obj)
+ {
+ if (_profileEditor != null)
+ {
+ if (obj.WasModified())
+ {
+ _profileEditor.RefreshAntags();
+ }
+
+ if (obj.WasModified() ||
+ obj.WasModified())
+ {
+ _profileEditor.RefreshJobs();
+ }
+
+ if (obj.WasModified() ||
+ obj.WasModified() ||
+ obj.WasModified())
+ {
+ _profileEditor.RefreshLoadouts();
+ }
+
+ if (obj.WasModified())
+ {
+ _profileEditor.RefreshSpecies();
+ }
+
+ if (obj.WasModified())
+ {
+ _profileEditor.RefreshTraits();
+ }
+ }
+ }
+
+ private void PreferencesDataLoaded()
+ {
+ PreviewPanel?.SetLoaded(true);
+
+ if (_stateManager.CurrentState is not LobbyState)
+ return;
+
+ ReloadCharacterSetup();
+ }
+
+ public void OnStateEntered(LobbyState state)
+ {
+ PreviewPanel?.SetLoaded(_preferencesManager.ServerDataLoaded);
+ ReloadCharacterSetup();
+ }
+
+ public void OnStateExited(LobbyState state)
+ {
+ PreviewPanel?.SetLoaded(false);
+ _profileEditor?.Dispose();
+ _characterSetup?.Dispose();
+
+ _characterSetup = null;
+ _profileEditor = null;
+ }
+
+ ///
+ /// Reloads every single character setup control.
+ ///
+ public void ReloadCharacterSetup()
+ {
+ RefreshLobbyPreview();
+ var (characterGui, profileEditor) = EnsureGui();
+ characterGui.ReloadCharacterPickers();
+ profileEditor.SetProfile(
+ (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter,
+ _preferencesManager.Preferences?.SelectedCharacterIndex);
+ }
+
+ ///
+ /// Refreshes the character preview in the lobby chat.
+ ///
+ private void RefreshLobbyPreview()
+ {
+ if (PreviewPanel == null)
+ return;
+
+ // Get selected character, load it, then set it
+ var character = _preferencesManager.Preferences?.SelectedCharacter;
+
+ if (character is not HumanoidCharacterProfile humanoid)
+ {
+ PreviewPanel.SetSprite(EntityUid.Invalid);
+ PreviewPanel.SetSummaryText(string.Empty);
+ return;
+ }
+
+ var dummy = LoadProfileEntity(humanoid, null, true);
+ PreviewPanel.SetSprite(dummy);
+ PreviewPanel.SetSummaryText(humanoid.Summary);
+ }
+
+ private void RefreshProfileEditor()
+ {
+ _profileEditor?.RefreshAntags();
+ _profileEditor?.RefreshJobs();
+ _profileEditor?.RefreshLoadouts();
+ }
+
+ private void SaveProfile()
+ {
+ DebugTools.Assert(EditedProfile != null);
+
+ if (EditedProfile == null || EditedSlot == null)
+ return;
+
+ var selected = _preferencesManager.Preferences?.SelectedCharacterIndex;
+
+ if (selected == null)
+ return;
+
+ _preferencesManager.UpdateCharacter(EditedProfile, EditedSlot.Value);
+ ReloadCharacterSetup();
+ }
+
+ private (CharacterSetupGui, HumanoidProfileEditor) EnsureGui()
+ {
+ if (_characterSetup != null && _profileEditor != null)
+ {
+ _characterSetup.Visible = true;
+ _profileEditor.Visible = true;
+ return (_characterSetup, _profileEditor);
+ }
+
+ _profileEditor = new HumanoidProfileEditor(
+ _preferencesManager,
+ _configurationManager,
+ EntityManager,
+ _dialogManager,
+ _logManager,
+ _playerManager,
+ _prototypeManager,
+ _requirements,
+ _markings);
+
+ _profileEditor.OnOpenGuidebook += _guide.OpenHelp;
+
+ _characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
+
+ _characterSetup.CloseButton.OnPressed += _ =>
+ {
+ // Reset sliders etc.
+ _profileEditor.SetProfile(null, null);
+ _profileEditor.Visible = false;
+
+ if (_stateManager.CurrentState is LobbyState lobbyGui)
+ {
+ lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default);
+ }
+ };
+
+ _profileEditor.Save += SaveProfile;
+
+ _characterSetup.SelectCharacter += args =>
+ {
+ _preferencesManager.SelectCharacter(args);
+ ReloadCharacterSetup();
+ };
+
+ _characterSetup.DeleteCharacter += args =>
+ {
+ _preferencesManager.DeleteCharacter(args);
+
+ // Reload everything
+ if (EditedSlot == args)
+ {
+ ReloadCharacterSetup();
+ }
+ else
+ {
+ // Only need to reload character pickers
+ _characterSetup?.ReloadCharacterPickers();
+ }
+ };
+
+ if (_stateManager.CurrentState is LobbyState lobby)
+ {
+ lobby.Lobby?.CharacterSetupState.AddChild(_characterSetup);
+ }
+
+ return (_characterSetup, _profileEditor);
+ }
+
+ #region Helpers
+
+ ///
+ /// Applies the highest priority job's clothes to the dummy.
+ ///
+ public void GiveDummyJobClothesLoadout(EntityUid dummy, JobPrototype? jobProto, HumanoidCharacterProfile profile)
+ {
+ var job = jobProto ?? GetPreferredJob(profile);
+ GiveDummyJobClothes(dummy, profile, job);
+
+ if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(job.ID)))
+ {
+ var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), _playerManager.LocalSession, profile.Species, EntityManager, _prototypeManager);
+ GiveDummyLoadout(dummy, loadout);
+ }
+ }
+
+ ///
+ /// Gets the highest priority job for the profile.
+ ///
+ public JobPrototype GetPreferredJob(HumanoidCharacterProfile profile)
+ {
+ var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
+ // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
+ return _prototypeManager.Index(highPriorityJob.Id ?? SharedGameTicker.FallbackOverflowJob);
+ }
+
+ public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)
+ {
+ if (roleLoadout == null)
+ return;
+
+ foreach (var group in roleLoadout.SelectedLoadouts.Values)
+ {
+ foreach (var loadout in group)
+ {
+ if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
+ continue;
+
+ _spawn.EquipStartingGear(uid, _prototypeManager.Index(loadoutProto.Equipment));
+ }
+ }
+ }
+
+ ///
+ /// Applies the specified job's clothes to the dummy.
+ ///
+ public void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile, JobPrototype job)
+ {
+ if (!_inventory.TryGetSlots(dummy, out var slots))
+ return;
+
+ // Apply loadout
+ if (profile.Loadouts.TryGetValue(job.ID, out var jobLoadout))
+ {
+ foreach (var loadouts in jobLoadout.SelectedLoadouts.Values)
+ {
+ foreach (var loadout in loadouts)
+ {
+ if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
+ continue;
+
+ // TODO: Need some way to apply starting gear to an entity coz holy fucking shit dude.
+ var loadoutGear = _prototypeManager.Index(loadoutProto.Equipment);
+
+ foreach (var slot in slots)
+ {
+ var itemType = loadoutGear.GetGear(slot.Name);
+
+ if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
+ {
+ EntityManager.DeleteEntity(unequippedItem.Value);
+ }
+
+ if (itemType != string.Empty)
+ {
+ var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
+ _inventory.TryEquip(dummy, item, slot.Name, true, true);
+ }
+ }
+ }
+ }
+ }
+
+ if (job.StartingGear == null)
+ return;
+
+ var gear = _prototypeManager.Index(job.StartingGear);
+
+ foreach (var slot in slots)
+ {
+ var itemType = gear.GetGear(slot.Name);
+
+ if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
+ {
+ EntityManager.DeleteEntity(unequippedItem.Value);
+ }
+
+ if (itemType != string.Empty)
+ {
+ var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
+ _inventory.TryEquip(dummy, item, slot.Name, true, true);
+ }
+ }
+ }
+
+ ///
+ /// Loads the profile onto a dummy entity.
+ ///
+ public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, JobPrototype? job, bool jobClothes)
+ {
+ EntityUid dummyEnt;
+
+ if (humanoid is not null)
+ {
+ var dummy = _prototypeManager.Index(humanoid.Species).DollPrototype;
+ dummyEnt = EntityManager.SpawnEntity(dummy, MapCoordinates.Nullspace);
+ }
+ else
+ {
+ dummyEnt = EntityManager.SpawnEntity(_prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace);
+ }
+
+ _humanoid.LoadProfile(dummyEnt, humanoid);
+
+ if (humanoid != null && jobClothes)
+ {
+ job ??= GetPreferredJob(humanoid);
+ GiveDummyJobClothes(dummyEnt, humanoid, job);
+
+ if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(job.ID)))
+ {
+ var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), _playerManager.LocalSession, humanoid.Species, EntityManager, _prototypeManager);
+ GiveDummyLoadout(dummyEnt, loadout);
+ }
+ }
+
+ return dummyEnt;
+ }
+
+ #endregion
+}
diff --git a/Content.Client/Lobby/UI/CharacterPickerButton.xaml b/Content.Client/Lobby/UI/CharacterPickerButton.xaml
new file mode 100644
index 0000000000..af1e640aad
--- /dev/null
+++ b/Content.Client/Lobby/UI/CharacterPickerButton.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs b/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs
new file mode 100644
index 0000000000..7efd1c594f
--- /dev/null
+++ b/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs
@@ -0,0 +1,92 @@
+using System.Linq;
+using Content.Client.Humanoid;
+using Content.Shared.Clothing;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Roles;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Lobby.UI;
+
+///
+/// Holds character data on the side of the setup GUI.
+///
+[GenerateTypedNameReferences]
+public sealed partial class CharacterPickerButton : ContainerButton
+{
+ private IEntityManager _entManager;
+
+ private EntityUid _previewDummy;
+
+ ///
+ /// Invoked if we should delete the attached character
+ ///
+ public event Action? OnDeletePressed;
+
+ public CharacterPickerButton(
+ IEntityManager entityManager,
+ IPrototypeManager prototypeManager,
+ ButtonGroup group,
+ ICharacterProfile profile,
+ bool isSelected)
+ {
+ RobustXamlLoader.Load(this);
+ _entManager = entityManager;
+ AddStyleClass(StyleClassButton);
+ ToggleMode = true;
+ Group = group;
+ var description = profile.Name;
+
+ if (profile is not HumanoidCharacterProfile humanoid)
+ {
+ _previewDummy = entityManager.SpawnEntity(prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace);
+ }
+ else
+ {
+ _previewDummy = UserInterfaceManager.GetUIController()
+ .LoadProfileEntity(humanoid, null, true);
+
+ var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
+ if (highPriorityJob != default)
+ {
+ var jobName = prototypeManager.Index(highPriorityJob).LocalizedName;
+ description = $"{description}\n{jobName}";
+ }
+ }
+
+ Pressed = isSelected;
+ DeleteButton.Visible = !isSelected;
+
+ View.SetEntity(_previewDummy);
+ DescriptionLabel.Text = description;
+
+ ConfirmDeleteButton.OnPressed += _ =>
+ {
+ Parent?.RemoveChild(this);
+ Parent?.RemoveChild(ConfirmDeleteButton);
+ OnDeletePressed?.Invoke();
+ };
+
+ DeleteButton.OnPressed += _ =>
+ {
+ DeleteButton.Visible = false;
+ ConfirmDeleteButton.Visible = true;
+ };
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ _entManager.DeleteEntity(_previewDummy);
+ _previewDummy = default;
+ }
+}
diff --git a/Content.Client/Preferences/UI/CharacterSetupGui.xaml b/Content.Client/Lobby/UI/CharacterSetupGui.xaml
similarity index 87%
rename from Content.Client/Preferences/UI/CharacterSetupGui.xaml
rename to Content.Client/Lobby/UI/CharacterSetupGui.xaml
index 9a76029ce0..bb0b33511a 100644
--- a/Content.Client/Preferences/UI/CharacterSetupGui.xaml
+++ b/Content.Client/Lobby/UI/CharacterSetupGui.xaml
@@ -4,7 +4,7 @@
xmlns:style="clr-namespace:Content.Client.Stylesheets"
VerticalExpand="True">
-
+
-
-
+
diff --git a/Content.Client/Lobby/UI/CharacterSetupGui.xaml.cs b/Content.Client/Lobby/UI/CharacterSetupGui.xaml.cs
new file mode 100644
index 0000000000..b336f65656
--- /dev/null
+++ b/Content.Client/Lobby/UI/CharacterSetupGui.xaml.cs
@@ -0,0 +1,118 @@
+using Content.Client.Info;
+using Content.Client.Info.PlaytimeStats;
+using Content.Client.Resources;
+using Content.Shared.Preferences;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Lobby.UI
+{
+ ///
+ /// Holds the entire character setup GUI, from character picks to individual character editing.
+ ///
+ [GenerateTypedNameReferences]
+ public sealed partial class CharacterSetupGui : Control
+ {
+ private readonly IClientPreferencesManager _preferencesManager;
+ private readonly IEntityManager _entManager;
+ private readonly IPrototypeManager _protomanager;
+
+ private readonly Button _createNewCharacterButton;
+
+ public event Action? SelectCharacter;
+ public event Action? DeleteCharacter;
+
+ public CharacterSetupGui(
+ IEntityManager entManager,
+ IPrototypeManager protoManager,
+ IResourceCache resourceCache,
+ IClientPreferencesManager preferencesManager,
+ HumanoidProfileEditor profileEditor)
+ {
+ RobustXamlLoader.Load(this);
+ _preferencesManager = preferencesManager;
+ _entManager = entManager;
+ _protomanager = protoManager;
+
+ var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
+ var back = new StyleBoxTexture
+ {
+ Texture = panelTex,
+ Modulate = new Color(37, 37, 42)
+ };
+ back.SetPatchMargin(StyleBox.Margin.All, 10);
+
+ // BackgroundPanel.PanelOverride = back;
+
+ _createNewCharacterButton = new Button
+ {
+ Text = Loc.GetString("character-setup-gui-create-new-character-button"),
+ };
+
+ _createNewCharacterButton.OnPressed += args =>
+ {
+ preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random());
+ ReloadCharacterPickers();
+ args.Event.Handle();
+ };
+
+ CharEditor.AddChild(profileEditor);
+ RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open();
+
+ StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered();
+ }
+
+ ///
+ /// Disposes and reloads all character picker buttons from the preferences data.
+ ///
+ public void ReloadCharacterPickers()
+ {
+ _createNewCharacterButton.Orphan();
+ Characters.DisposeAllChildren();
+
+ var numberOfFullSlots = 0;
+ var characterButtonsGroup = new ButtonGroup();
+
+ if (!_preferencesManager.ServerDataLoaded)
+ {
+ return;
+ }
+
+ _createNewCharacterButton.ToolTip =
+ Loc.GetString("character-setup-gui-create-new-character-button-tooltip",
+ ("maxCharacters", _preferencesManager.Settings!.MaxCharacterSlots));
+
+ var selectedSlot = _preferencesManager.Preferences?.SelectedCharacterIndex;
+
+ foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
+ {
+ numberOfFullSlots++;
+ var characterPickerButton = new CharacterPickerButton(_entManager,
+ _protomanager,
+ characterButtonsGroup,
+ character,
+ slot == selectedSlot);
+
+ Characters.AddChild(characterPickerButton);
+
+ characterPickerButton.OnPressed += args =>
+ {
+ SelectCharacter?.Invoke(slot);
+ };
+
+ characterPickerButton.OnDeletePressed += () =>
+ {
+ DeleteCharacter?.Invoke(slot);
+ };
+ }
+
+ _createNewCharacterButton.Disabled = numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots;
+ Characters.AddChild(_createNewCharacterButton);
+ }
+ }
+}
diff --git a/Content.Client/Lobby/UI/HighlightedContainer.xaml b/Content.Client/Lobby/UI/HighlightedContainer.xaml
new file mode 100644
index 0000000000..dc662665bc
--- /dev/null
+++ b/Content.Client/Lobby/UI/HighlightedContainer.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/HighlightedContainer.xaml.cs b/Content.Client/Lobby/UI/HighlightedContainer.xaml.cs
new file mode 100644
index 0000000000..084c1c3709
--- /dev/null
+++ b/Content.Client/Lobby/UI/HighlightedContainer.xaml.cs
@@ -0,0 +1,14 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Lobby.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class HighlightedContainer : PanelContainer
+{
+ public HighlightedContainer()
+ {
+ RobustXamlLoader.Load(this);
+ }
+}
diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml
new file mode 100644
index 0000000000..0f5aec4c4e
--- /dev/null
+++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
new file mode 100644
index 0000000000..504a9b5d4e
--- /dev/null
+++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
@@ -0,0 +1,1584 @@
+using System.IO;
+using System.Linq;
+using System.Numerics;
+using Content.Client.Humanoid;
+using Content.Client.Lobby.UI.Loadouts;
+using Content.Client.Lobby.UI.Roles;
+using Content.Client.Message;
+using Content.Client.Players.PlayTimeTracking;
+using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Systems.Guidebook;
+using Content.Shared.CCVar;
+using Content.Shared.Clothing;
+using Content.Shared.GameTicking;
+using Content.Shared.Guidebook;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Markings;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Roles;
+using Content.Shared.Traits;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.Utility;
+using Robust.Shared.Configuration;
+using Robust.Shared.Enums;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+using Direction = Robust.Shared.Maths.Direction;
+
+namespace Content.Client.Lobby.UI
+{
+ [GenerateTypedNameReferences]
+ public sealed partial class HumanoidProfileEditor : BoxContainer
+ {
+ private readonly IClientPreferencesManager _preferencesManager;
+ private readonly IConfigurationManager _cfgManager;
+ private readonly IEntityManager _entManager;
+ private readonly IFileDialogManager _dialogManager;
+ private readonly IPlayerManager _playerManager;
+ private readonly IPrototypeManager _prototypeManager;
+ private readonly MarkingManager _markingManager;
+ private readonly JobRequirementsManager _requirements;
+ private readonly LobbyUIController _controller;
+
+ private FlavorText.FlavorText? _flavorText;
+ private TextEdit? _flavorTextEdit;
+
+ // One at a time.
+ private LoadoutWindow? _loadoutWindow;
+
+ private bool _exporting;
+
+ ///
+ /// If we're attempting to save.
+ ///
+ public event Action? Save;
+
+ ///
+ /// Entity used for the profile editor preview
+ ///
+ public EntityUid PreviewDummy;
+
+ ///
+ /// Temporary override of their selected job, used to preview roles.
+ ///
+ public JobPrototype? JobOverride;
+
+ ///
+ /// The character slot for the current profile.
+ ///
+ public int? CharacterSlot;
+
+ ///
+ /// The work in progress profile being edited.
+ ///
+ public HumanoidCharacterProfile? Profile;
+
+ private List _species = new();
+
+ private List<(string, RequirementsSelector)> _jobPriorities = new();
+
+ private readonly Dictionary _jobCategories;
+
+ private Direction _previewRotation = Direction.North;
+
+ private ColorSelectorSliders _rgbSkinColorSelector;
+
+ private bool _isDirty;
+
+ [ValidatePrototypeId]
+ private const string DefaultSpeciesGuidebook = "Species";
+
+ public event Action>>? OnOpenGuidebook;
+
+ private ISawmill _sawmill;
+
+ public HumanoidProfileEditor(
+ IClientPreferencesManager preferencesManager,
+ IConfigurationManager configurationManager,
+ IEntityManager entManager,
+ IFileDialogManager dialogManager,
+ ILogManager logManager,
+ IPlayerManager playerManager,
+ IPrototypeManager prototypeManager,
+ JobRequirementsManager requirements,
+ MarkingManager markings)
+ {
+ RobustXamlLoader.Load(this);
+ _sawmill = logManager.GetSawmill("profile.editor");
+ _cfgManager = configurationManager;
+ _entManager = entManager;
+ _dialogManager = dialogManager;
+ _playerManager = playerManager;
+ _prototypeManager = prototypeManager;
+ _markingManager = markings;
+ _preferencesManager = preferencesManager;
+ _requirements = requirements;
+ _controller = UserInterfaceManager.GetUIController();
+
+ ImportButton.OnPressed += args =>
+ {
+ ImportProfile();
+ };
+
+ ExportButton.OnPressed += args =>
+ {
+ ExportProfile();
+ };
+
+ ResetButton.OnPressed += args =>
+ {
+ SetProfile((HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter, _preferencesManager.Preferences?.SelectedCharacterIndex);
+ };
+
+ SaveButton.OnPressed += args =>
+ {
+ Save?.Invoke();
+ };
+
+ #region Left
+
+ #region Name
+
+ NameEdit.OnTextChanged += args => { SetName(args.Text); };
+ NameRandomize.OnPressed += args => RandomizeName();
+ RandomizeEverythingButton.OnPressed += args => { RandomizeEverything(); };
+ WarningLabel.SetMarkup($"[color=red]{Loc.GetString("humanoid-profile-editor-naming-rules-warning")}[/color]");
+
+ #endregion Name
+
+ #region Appearance
+
+ TabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
+
+ #region Sex
+
+ SexButton.OnItemSelected += args =>
+ {
+ SexButton.SelectId(args.Id);
+ SetSex((Sex) args.Id);
+ };
+
+ #endregion Sex
+
+ #region Age
+
+ AgeEdit.OnTextChanged += args =>
+ {
+ if (!int.TryParse(args.Text, out var newAge))
+ return;
+
+ SetAge(newAge);
+ };
+
+ #endregion Age
+
+ #region Gender
+
+ PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-male-text"), (int) Gender.Male);
+ PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-female-text"), (int) Gender.Female);
+ PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-epicene-text"), (int) Gender.Epicene);
+ PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-neuter-text"), (int) Gender.Neuter);
+
+ PronounsButton.OnItemSelected += args =>
+ {
+ PronounsButton.SelectId(args.Id);
+ SetGender((Gender) args.Id);
+ };
+
+ #endregion Gender
+
+ RefreshSpecies();
+
+ SpeciesButton.OnItemSelected += args =>
+ {
+ SpeciesButton.SelectId(args.Id);
+ SetSpecies(_species[args.Id].ID);
+ UpdateHairPickers();
+ OnSkinColorOnValueChanged();
+ };
+
+ #region Skin
+
+ Skin.OnValueChanged += _ =>
+ {
+ OnSkinColorOnValueChanged();
+ };
+
+ RgbSkinColorContainer.AddChild(_rgbSkinColorSelector = new ColorSelectorSliders());
+ _rgbSkinColorSelector.OnColorChanged += _ =>
+ {
+ OnSkinColorOnValueChanged();
+ };
+
+ #endregion
+
+ #region Hair
+
+ HairStylePicker.OnMarkingSelect += newStyle =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithHairStyleName(newStyle.id));
+ ReloadPreview();
+ };
+
+ HairStylePicker.OnColorChanged += newColor =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
+ UpdateCMarkingsHair();
+ ReloadPreview();
+ };
+
+ FacialHairPicker.OnMarkingSelect += newStyle =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithFacialHairStyleName(newStyle.id));
+ ReloadPreview();
+ };
+
+ FacialHairPicker.OnColorChanged += newColor =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
+ UpdateCMarkingsFacialHair();
+ ReloadPreview();
+ };
+
+ HairStylePicker.OnSlotRemove += _ =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithHairStyleName(HairStyles.DefaultHairStyle)
+ );
+ UpdateHairPickers();
+ UpdateCMarkingsHair();
+ ReloadPreview();
+ };
+
+ FacialHairPicker.OnSlotRemove += _ =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithFacialHairStyleName(HairStyles.DefaultFacialHairStyle)
+ );
+ UpdateHairPickers();
+ UpdateCMarkingsFacialHair();
+ ReloadPreview();
+ };
+
+ HairStylePicker.OnSlotAdd += delegate()
+ {
+ if (Profile is null)
+ return;
+
+ var hair = _markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.Hair, Profile.Species).Keys
+ .FirstOrDefault();
+
+ if (string.IsNullOrEmpty(hair))
+ return;
+
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithHairStyleName(hair)
+ );
+
+ UpdateHairPickers();
+ UpdateCMarkingsHair();
+ ReloadPreview();
+ };
+
+ FacialHairPicker.OnSlotAdd += delegate()
+ {
+ if (Profile is null)
+ return;
+
+ var hair = _markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.FacialHair, Profile.Species).Keys
+ .FirstOrDefault();
+
+ if (string.IsNullOrEmpty(hair))
+ return;
+
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithFacialHairStyleName(hair)
+ );
+
+ UpdateHairPickers();
+ UpdateCMarkingsFacialHair();
+ ReloadPreview();
+ };
+
+ #endregion Hair
+
+ #region SpawnPriority
+
+ // foreach (var value in Enum.GetValues())
+ // {
+ // SpawnPriorityButton.AddItem(Loc.GetString($"humanoid-profile-editor-preference-spawn-priority-{value.ToString().ToLower()}"), (int) value);
+ // }
+ //
+ // SpawnPriorityButton.OnItemSelected += args =>
+ // {
+ // SpawnPriorityButton.SelectId(args.Id);
+ // SetSpawnPriority((SpawnPriorityPreference) args.Id);
+ // };
+
+ #endregion SpawnPriority
+
+ #region Eyes
+
+ EyeColorPicker.OnEyeColorPicked += newColor =>
+ {
+ if (Profile is null)
+ return;
+ Profile = Profile.WithCharacterAppearance(
+ Profile.Appearance.WithEyeColor(newColor));
+ Markings.CurrentEyeColor = Profile.Appearance.EyeColor;
+ ReloadProfilePreview();
+ };
+
+ #endregion Eyes
+
+ #endregion Appearance
+
+ #region Jobs
+
+ TabContainer.SetTabTitle(1, Loc.GetString("humanoid-profile-editor-jobs-tab"));
+
+ PreferenceUnavailableButton.AddItem(
+ Loc.GetString("humanoid-profile-editor-preference-unavailable-stay-in-lobby-button"),
+ (int) PreferenceUnavailableMode.StayInLobby);
+ PreferenceUnavailableButton.AddItem(
+ Loc.GetString("humanoid-profile-editor-preference-unavailable-spawn-as-overflow-button",
+ ("overflowJob", Loc.GetString(SharedGameTicker.FallbackOverflowJobName))),
+ (int) PreferenceUnavailableMode.SpawnAsOverflow);
+
+ PreferenceUnavailableButton.OnItemSelected += args =>
+ {
+ PreferenceUnavailableButton.SelectId(args.Id);
+ Profile = Profile?.WithPreferenceUnavailable((PreferenceUnavailableMode) args.Id);
+ SetDirty();
+ };
+
+ _jobCategories = new Dictionary();
+
+ RefreshAntags();
+ RefreshJobs();
+
+ #endregion Jobs
+
+ TabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
+
+ RefreshTraits();
+
+ #region Markings
+
+ TabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab"));
+
+ Markings.OnMarkingAdded += OnMarkingChange;
+ Markings.OnMarkingRemoved += OnMarkingChange;
+ Markings.OnMarkingColorChange += OnMarkingChange;
+ Markings.OnMarkingRankChange += OnMarkingChange;
+
+ #endregion Markings
+
+ RefreshFlavorText();
+
+ #region Dummy
+
+ SpriteRotateLeft.OnPressed += _ =>
+ {
+ _previewRotation = _previewRotation.TurnCw();
+ SetPreviewRotation(_previewRotation);
+ };
+ SpriteRotateRight.OnPressed += _ =>
+ {
+ _previewRotation = _previewRotation.TurnCcw();
+ SetPreviewRotation(_previewRotation);
+ };
+
+ #endregion Dummy
+
+ #endregion Left
+
+ ShowClothes.OnToggled += args =>
+ {
+ ReloadPreview();
+ };
+
+ SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
+
+ UpdateSpeciesGuidebookIcon();
+ ReloadPreview();
+ IsDirty = false;
+ }
+
+ ///
+ /// Refreshes the flavor text editor status.
+ ///
+ public void RefreshFlavorText()
+ {
+ if (_cfgManager.GetCVar(CCVars.FlavorText))
+ {
+ if (_flavorText != null)
+ return;
+
+ _flavorText = new FlavorText.FlavorText();
+ TabContainer.AddChild(_flavorText);
+ TabContainer.SetTabTitle(TabContainer.ChildCount - 1, Loc.GetString("humanoid-profile-editor-flavortext-tab"));
+ _flavorTextEdit = _flavorText.CFlavorTextInput;
+
+ _flavorText.OnFlavorTextChanged += OnFlavorTextChange;
+ }
+ else
+ {
+ if (_flavorText == null)
+ return;
+
+ TabContainer.RemoveChild(_flavorText);
+ _flavorText.OnFlavorTextChanged -= OnFlavorTextChange;
+ _flavorText.Dispose();
+ _flavorTextEdit?.Dispose();
+ _flavorTextEdit = null;
+ _flavorText = null;
+ }
+ }
+
+ ///
+ /// Refreshes traits selector
+ ///
+ public void RefreshTraits()
+ {
+ TraitsList.DisposeAllChildren();
+
+ var traits = _prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList();
+ TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
+
+ if (traits.Count < 1)
+ {
+ TraitsList.AddChild(new Label
+ {
+ Text = Loc.GetString("humanoid-profile-editor-no-traits"),
+ FontColorOverride = Color.Gray,
+ });
+ return;
+ }
+
+ //Setup model
+ Dictionary> model = new();
+ List defaultTraits = new();
+ model.Add("default", defaultTraits);
+
+ foreach (var trait in traits)
+ {
+ if (trait.Category == null)
+ {
+ defaultTraits.Add(trait.ID);
+ continue;
+ }
+
+ if (!model.ContainsKey(trait.Category))
+ {
+ model.Add(trait.Category, new());
+ }
+ model[trait.Category].Add(trait.ID);
+ }
+
+ //Create UI view from model
+ foreach (var (categoryId, traitId) in model)
+ {
+ TraitCategoryPrototype? category = null;
+ if (categoryId != "default")
+ {
+ category = _prototypeManager.Index(categoryId);
+ // Label
+ TraitsList.AddChild(new Label
+ {
+ Text = Loc.GetString(category.Name),
+ Margin = new Thickness(0, 10, 0, 0),
+ StyleClasses = { StyleBase.StyleClassLabelHeading },
+ });
+ }
+
+ List selectors = new();
+ var selectionCount = 0;
+
+ foreach (var traitProto in traitId)
+ {
+ var trait = _prototypeManager.Index(traitProto);
+ var selector = new TraitPreferenceSelector(trait);
+
+ selector.Preference = Profile?.TraitPreferences.Contains(trait.ID) == true;
+ if (selector.Preference)
+ selectionCount += trait.Cost;
+
+ selector.PreferenceChanged += preference =>
+ {
+ Profile = Profile?.WithTraitPreference(trait.ID, categoryId, preference);
+ SetDirty();
+ RefreshTraits(); // If too many traits are selected, they will be reset to the real value.
+ };
+ selectors.Add(selector);
+ }
+
+ // Selection counter
+ if (category is { MaxTraitPoints: >= 0 })
+ {
+ TraitsList.AddChild(new Label
+ {
+ Text = Loc.GetString("humanoid-profile-editor-trait-count-hint", ("current", selectionCount) ,("max", category.MaxTraitPoints)),
+ FontColorOverride = Color.Gray
+ });
+ }
+
+ foreach (var selector in selectors)
+ {
+ if (selector == null)
+ continue;
+
+ if (category is { MaxTraitPoints: >= 0 } &&
+ selector.Cost + selectionCount > category.MaxTraitPoints)
+ {
+ selector.Checkbox.Label.FontColorOverride = Color.Red;
+ }
+
+ TraitsList.AddChild(selector);
+ }
+ }
+ }
+
+ ///
+ /// Refreshes the species selector.
+ ///
+ public void RefreshSpecies()
+ {
+ SpeciesButton.Clear();
+ _species.Clear();
+
+ _species.AddRange(_prototypeManager.EnumeratePrototypes().Where(o => o.RoundStart));
+ var speciesIds = _species.Select(o => o.ID).ToList();
+
+ for (var i = 0; i < _species.Count; i++)
+ {
+ var name = Loc.GetString(_species[i].Name);
+ SpeciesButton.AddItem(name, i);
+
+ if (Profile?.Species.Equals(_species[i].ID) == true)
+ {
+ SpeciesButton.SelectId(i);
+ }
+ }
+
+ // If our species isn't available then reset it to default.
+ if (Profile != null)
+ {
+ if (!speciesIds.Contains(Profile.Species))
+ {
+ SetSpecies(SharedHumanoidAppearanceSystem.DefaultSpecies);
+ }
+ }
+ }
+
+ public void RefreshAntags()
+ {
+ AntagList.DisposeAllChildren();
+ var items = new[]
+ {
+ ("humanoid-profile-editor-antag-preference-yes-button", 0),
+ ("humanoid-profile-editor-antag-preference-no-button", 1)
+ };
+
+ foreach (var antag in _prototypeManager.EnumeratePrototypes().OrderBy(a => Loc.GetString(a.Name)))
+ {
+ if (!antag.SetPreference)
+ continue;
+
+ var antagContainer = new BoxContainer()
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ };
+
+ var selector = new RequirementsSelector()
+ {
+ Margin = new Thickness(3f, 3f, 3f, 0f),
+ };
+ selector.OnOpenGuidebook += OnOpenGuidebook;
+
+ var title = Loc.GetString(antag.Name);
+ var description = Loc.GetString(antag.Objective);
+ selector.Setup(items, title, 250, description, guides: antag.Guides);
+ selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
+
+ var requirements = _entManager.System().GetAntagRequirement(antag);
+ if (!_requirements.CheckRoleTime(requirements, out var reason))
+ {
+ selector.LockRequirements(reason);
+ Profile = Profile?.WithAntagPreference(antag.ID, false);
+ SetDirty();
+ }
+ else
+ {
+ selector.UnlockRequirements();
+ }
+
+ selector.OnSelected += preference =>
+ {
+ Profile = Profile?.WithAntagPreference(antag.ID, preference == 0);
+ SetDirty();
+ };
+
+ antagContainer.AddChild(selector);
+
+ antagContainer.AddChild(new Button()
+ {
+ Disabled = true,
+ Text = Loc.GetString("loadout-window"),
+ HorizontalAlignment = HAlignment.Right,
+ Margin = new Thickness(3f, 0f, 0f, 0f),
+ });
+
+ AntagList.AddChild(antagContainer);
+ }
+ }
+
+ private void SetDirty()
+ {
+ // If it equals default then reset the button.
+ if (Profile == null || _preferencesManager.Preferences?.SelectedCharacter.MemberwiseEquals(Profile) == true)
+ {
+ IsDirty = false;
+ return;
+ }
+
+ // TODO: Check if profile matches default.
+ IsDirty = true;
+ }
+
+ ///
+ /// Refresh all loadouts.
+ ///
+ public void RefreshLoadouts()
+ {
+ _loadoutWindow?.Dispose();
+ }
+
+ ///
+ /// Reloads the entire dummy entity for preview.
+ ///
+ ///
+ /// This is expensive so not recommended to run if you have a slider.
+ ///
+ private void ReloadPreview()
+ {
+ _entManager.DeleteEntity(PreviewDummy);
+ PreviewDummy = EntityUid.Invalid;
+
+ if (Profile == null || !_prototypeManager.HasIndex(Profile.Species))
+ return;
+
+ PreviewDummy = _controller.LoadProfileEntity(Profile, JobOverride, ShowClothes.Pressed);
+ SpriteView.SetEntity(PreviewDummy);
+ }
+
+ ///
+ /// Resets the profile to the defaults.
+ ///
+ public void ResetToDefault()
+ {
+ SetProfile(
+ (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter,
+ _preferencesManager.Preferences?.SelectedCharacterIndex);
+ }
+
+ ///
+ /// Sets the editor to the specified profile with the specified slot.
+ ///
+ public void SetProfile(HumanoidCharacterProfile? profile, int? slot)
+ {
+ Profile = profile?.Clone();
+ CharacterSlot = slot;
+ IsDirty = false;
+ JobOverride = null;
+
+ UpdateNameEdit();
+ UpdateFlavorTextEdit();
+ UpdateSexControls();
+ UpdateGenderControls();
+ UpdateSkinColor();
+ UpdateSpawnPriorityControls();
+ UpdateAgeEdit();
+ UpdateEyePickers();
+ UpdateSaveButton();
+ UpdateMarkings();
+ UpdateHairPickers();
+ UpdateCMarkingsHair();
+ UpdateCMarkingsFacialHair();
+
+ RefreshAntags();
+ RefreshJobs();
+ RefreshLoadouts();
+ RefreshSpecies();
+ RefreshTraits();
+ RefreshFlavorText();
+ ReloadPreview();
+
+ if (Profile != null)
+ {
+ PreferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
+ }
+ }
+
+
+ ///
+ /// A slim reload that only updates the entity itself and not any of the job entities, etc.
+ ///
+ private void ReloadProfilePreview()
+ {
+ if (Profile == null || !_entManager.EntityExists(PreviewDummy))
+ return;
+
+ _entManager.System().LoadProfile(PreviewDummy, Profile);
+ }
+
+ private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
+ {
+ // TODO GUIDEBOOK
+ // make the species guide book a field on the species prototype.
+ // I.e., do what jobs/antags do.
+
+ var guidebookController = UserInterfaceManager.GetUIController();
+ var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
+ var page = DefaultSpeciesGuidebook;
+ if (_prototypeManager.HasIndex(species))
+ page = species;
+
+ if (_prototypeManager.TryIndex(DefaultSpeciesGuidebook, out var guideRoot))
+ {
+ var dict = new Dictionary, GuideEntry>();
+ dict.Add(DefaultSpeciesGuidebook, guideRoot);
+ //TODO: Don't close the guidebook if its already open, just go to the correct page
+ guidebookController.OpenGuidebook(dict, includeChildren:true, selected: page);
+ }
+ }
+
+ ///
+ /// Refreshes all job selectors.
+ ///
+ public void RefreshJobs()
+ {
+ JobList.DisposeAllChildren();
+ _jobCategories.Clear();
+ _jobPriorities.Clear();
+ var firstCategory = true;
+
+ // Get all displayed departments
+ var departments = new List();
+ foreach (var department in _prototypeManager.EnumeratePrototypes())
+ {
+ if (department.EditorHidden)
+ continue;
+
+ departments.Add(department);
+ }
+
+ departments.Sort(DepartmentUIComparer.Instance);
+
+ var items = new[]
+ {
+ ("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
+ ("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
+ ("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
+ ("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
+ };
+
+ foreach (var department in departments)
+ {
+ var departmentName = Loc.GetString($"department-{department.ID}");
+
+ if (!_jobCategories.TryGetValue(department.ID, out var category))
+ {
+ category = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Name = department.ID,
+ ToolTip = Loc.GetString("humanoid-profile-editor-jobs-amount-in-department-tooltip",
+ ("departmentName", departmentName))
+ };
+
+ if (firstCategory)
+ {
+ firstCategory = false;
+ }
+ else
+ {
+ category.AddChild(new Control
+ {
+ MinSize = new Vector2(0, 23),
+ });
+ }
+
+ category.AddChild(new PanelContainer
+ {
+ PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#262626")},
+ Children =
+ {
+ new Label
+ {
+ Text = Loc.GetString("humanoid-profile-editor-department-jobs-label",
+ ("departmentName", departmentName)),
+ Margin = new Thickness(5f, 0, 0, 0)
+ }
+ }
+ });
+
+ _jobCategories[department.ID] = category;
+ JobList.AddChild(category);
+ }
+
+ var jobs = department.Roles.Select(jobId => _prototypeManager.Index(jobId))
+ .Where(job => job.SetPreference)
+ .ToArray();
+
+ Array.Sort(jobs, JobUIComparer.Instance);
+
+ foreach (var job in jobs)
+ {
+ var jobContainer = new BoxContainer()
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ };
+
+ var selector = new RequirementsSelector()
+ {
+ Margin = new Thickness(3f, 3f, 3f, 0f),
+ };
+ selector.OnOpenGuidebook += OnOpenGuidebook;
+
+ var icon = new TextureRect
+ {
+ TextureScale = new Vector2(2, 2),
+ VerticalAlignment = VAlignment.Center
+ };
+ var jobIcon = _prototypeManager.Index(job.Icon);
+ icon.Texture = jobIcon.Icon.Frame0();
+ selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
+
+ if (!_requirements.IsAllowed(job, out var reason))
+ {
+ selector.LockRequirements(reason);
+ }
+ else
+ {
+ selector.UnlockRequirements();
+ }
+
+ selector.OnSelected += selectedPrio =>
+ {
+ var selectedJobPrio = (JobPriority) selectedPrio;
+ Profile = Profile?.WithJobPriority(job.ID, selectedJobPrio);
+
+ foreach (var (jobId, other) in _jobPriorities)
+ {
+ // Sync other selectors with the same job in case of multiple department jobs
+ if (jobId == job.ID)
+ {
+ other.Select(selectedPrio);
+ continue;
+ }
+
+ if (selectedJobPrio != JobPriority.High || (JobPriority) other.Selected != JobPriority.High)
+ continue;
+
+ // Lower any other high priorities to medium.
+ other.Select((int)JobPriority.Medium);
+ Profile = Profile?.WithJobPriority(jobId, JobPriority.Medium);
+ }
+
+ // TODO: Only reload on high change (either to or from).
+ ReloadPreview();
+
+ UpdateJobPriorities();
+ SetDirty();
+ };
+
+ var loadoutWindowBtn = new Button()
+ {
+ Text = Loc.GetString("loadout-window"),
+ HorizontalAlignment = HAlignment.Right,
+ VerticalAlignment = VAlignment.Center,
+ Margin = new Thickness(3f, 3f, 0f, 0f),
+ };
+
+ var collection = IoCManager.Instance!;
+ var protoManager = collection.Resolve();
+
+ // If no loadout found then disabled button
+ if (!protoManager.TryIndex(LoadoutSystem.GetJobPrototype(job.ID), out var roleLoadoutProto))
+ {
+ loadoutWindowBtn.Disabled = true;
+ }
+ // else
+ else
+ {
+ loadoutWindowBtn.OnPressed += args =>
+ {
+ RoleLoadout? loadout = null;
+
+ // Clone so we don't modify the underlying loadout.
+ Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout);
+ loadout = loadout?.Clone();
+
+ if (loadout == null)
+ {
+ loadout = new RoleLoadout(roleLoadoutProto.ID);
+ loadout.SetDefault(Profile, _playerManager.LocalSession, _prototypeManager);
+ }
+
+ OpenLoadout(job, loadout, roleLoadoutProto);
+ };
+ }
+
+ _jobPriorities.Add((job.ID, selector));
+ jobContainer.AddChild(selector);
+ jobContainer.AddChild(loadoutWindowBtn);
+ category.AddChild(jobContainer);
+ }
+ }
+
+ UpdateJobPriorities();
+ }
+
+ private void OpenLoadout(JobPrototype? jobProto, RoleLoadout roleLoadout, RoleLoadoutPrototype roleLoadoutProto)
+ {
+ _loadoutWindow?.Dispose();
+ _loadoutWindow = null;
+ var collection = IoCManager.Instance;
+
+ if (collection == null || _playerManager.LocalSession == null || Profile == null)
+ return;
+
+ JobOverride = jobProto;
+ var session = _playerManager.LocalSession;
+
+ _loadoutWindow = new LoadoutWindow(Profile, roleLoadout, roleLoadoutProto, _playerManager.LocalSession, collection)
+ {
+ Title = jobProto?.ID + "-loadout",
+ };
+
+ // Refresh the buttons etc.
+ _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
+ _loadoutWindow.OpenCenteredLeft();
+
+ _loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
+ {
+ roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
+ _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
+ Profile = Profile?.WithLoadout(roleLoadout);
+ SetDirty();
+ ReloadPreview();
+ };
+
+ _loadoutWindow.OnLoadoutUnpressed += (loadoutGroup, loadoutProto) =>
+ {
+ roleLoadout.RemoveLoadout(loadoutGroup, loadoutProto, _prototypeManager);
+ _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
+ Profile = Profile?.WithLoadout(roleLoadout);
+ SetDirty();
+ ReloadPreview();
+ };
+
+ JobOverride = jobProto;
+ ReloadPreview();
+
+ _loadoutWindow.OnClose += () =>
+ {
+ JobOverride = null;
+ SetDirty();
+ ReloadPreview();
+ };
+
+ if (Profile is null)
+ return;
+
+ UpdateJobPriorities();
+ }
+
+ private void OnFlavorTextChange(string content)
+ {
+ if (Profile is null)
+ return;
+
+ Profile = Profile.WithFlavorText(content);
+ SetDirty();
+ }
+
+ private void OnMarkingChange(MarkingSet markings)
+ {
+ if (Profile is null)
+ return;
+
+ Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
+ SetDirty();
+ ReloadProfilePreview();
+ }
+
+ private void OnSkinColorOnValueChanged()
+ {
+ if (Profile is null) return;
+
+ var skin = _prototypeManager.Index(Profile.Species).SkinColoration;
+
+ switch (skin)
+ {
+ case HumanoidSkinColor.HumanToned:
+ {
+ if (!Skin.Visible)
+ {
+ Skin.Visible = true;
+ RgbSkinColorContainer.Visible = false;
+ }
+
+ var color = SkinColor.HumanSkinTone((int) Skin.Value);
+
+ Markings.CurrentSkinColor = color;
+ Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));//
+ break;
+ }
+ case HumanoidSkinColor.Hues:
+ {
+ if (!RgbSkinColorContainer.Visible)
+ {
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
+ }
+
+ Markings.CurrentSkinColor = _rgbSkinColorSelector.Color;
+ Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(_rgbSkinColorSelector.Color));
+ break;
+ }
+ case HumanoidSkinColor.TintedHues:
+ {
+ if (!RgbSkinColorContainer.Visible)
+ {
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
+ }
+
+ var color = SkinColor.TintedHues(_rgbSkinColorSelector.Color);
+
+ Markings.CurrentSkinColor = color;
+ Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
+ break;
+ }
+ case HumanoidSkinColor.VoxFeathers:
+ {
+ if (!RgbSkinColorContainer.Visible)
+ {
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
+ }
+
+ var color = SkinColor.ClosestVoxColor(_rgbSkinColorSelector.Color);
+
+ Markings.CurrentSkinColor = color;
+ Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
+ break;
+ }
+ }
+
+ SetDirty();
+ ReloadProfilePreview();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ _loadoutWindow?.Dispose();
+ _loadoutWindow = null;
+ _entManager.DeleteEntity(PreviewDummy);
+ PreviewDummy = EntityUid.Invalid;
+ }
+
+ private void SetAge(int newAge)
+ {
+ Profile = Profile?.WithAge(newAge);
+ ReloadPreview();
+ SetDirty();
+ }
+
+ private void SetSex(Sex newSex)
+ {
+ Profile = Profile?.WithSex(newSex);
+ // for convenience, default to most common gender when new sex is selected
+ switch (newSex)
+ {
+ case Sex.Male:
+ Profile = Profile?.WithGender(Gender.Male);
+ break;
+ case Sex.Female:
+ Profile = Profile?.WithGender(Gender.Female);
+ break;
+ default:
+ Profile = Profile?.WithGender(Gender.Epicene);
+ break;
+ }
+
+ UpdateGenderControls();
+ Markings.SetSex(newSex);
+ ReloadPreview();
+ SetDirty();
+ }
+
+ private void SetGender(Gender newGender)
+ {
+ Profile = Profile?.WithGender(newGender);
+ ReloadPreview();
+ SetDirty();
+ }
+
+ private void SetSpecies(string newSpecies)
+ {
+ Profile = Profile?.WithSpecies(newSpecies);
+ OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
+ Markings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
+ // In case there's job restrictions for the species
+ RefreshJobs();
+ // In case there's species restrictions for loadouts
+ RefreshLoadouts();
+ UpdateSexControls(); // update sex for new species
+ UpdateSpeciesGuidebookIcon();
+ SetDirty();
+ ReloadPreview();
+ }
+
+ private void SetName(string newName)
+ {
+ Profile = Profile?.WithName(newName);
+ SetDirty();
+ }
+
+ private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
+ {
+ Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
+ SetDirty();
+ }
+
+ private bool IsDirty
+ {
+ get => _isDirty;
+ set
+ {
+ if (_isDirty == value)
+ return;
+
+ _isDirty = value;
+ UpdateSaveButton();
+ }
+ }
+
+ private void UpdateNameEdit()
+ {
+ NameEdit.Text = Profile?.Name ?? "";
+ }
+
+ private void UpdateFlavorTextEdit()
+ {
+ if (_flavorTextEdit != null)
+ {
+ _flavorTextEdit.TextRope = new Rope.Leaf(Profile?.FlavorText ?? "");
+ }
+ }
+
+ private void UpdateAgeEdit()
+ {
+ AgeEdit.Text = Profile?.Age.ToString() ?? "";
+ }
+
+ ///
+ /// Updates selected job priorities to the profile's.
+ ///
+ private void UpdateJobPriorities()
+ {
+ foreach (var (jobId, prioritySelector) in _jobPriorities)
+ {
+ var priority = Profile?.JobPriorities.GetValueOrDefault(jobId, JobPriority.Never) ?? JobPriority.Never;
+ prioritySelector.Select((int) priority);
+ }
+ }
+
+ private void UpdateSexControls()
+ {
+ if (Profile == null)
+ return;
+
+ SexButton.Clear();
+
+ var sexes = new List();
+
+ // add species sex options, default to just none if we are in bizzaro world and have no species
+ if (_prototypeManager.TryIndex(Profile.Species, out var speciesProto))
+ {
+ foreach (var sex in speciesProto.Sexes)
+ {
+ sexes.Add(sex);
+ }
+ }
+ else
+ {
+ sexes.Add(Sex.Unsexed);
+ }
+
+ // add button for each sex
+ foreach (var sex in sexes)
+ {
+ SexButton.AddItem(Loc.GetString($"humanoid-profile-editor-sex-{sex.ToString().ToLower()}-text"), (int) sex);
+ }
+
+ if (sexes.Contains(Profile.Sex))
+ SexButton.SelectId((int) Profile.Sex);
+ else
+ SexButton.SelectId((int) sexes[0]);
+ }
+
+ private void UpdateSkinColor()
+ {
+ if (Profile == null)
+ return;
+
+ var skin = _prototypeManager.Index(Profile.Species).SkinColoration;
+
+ switch (skin)
+ {
+ case HumanoidSkinColor.HumanToned:
+ {
+ if (!Skin.Visible)
+ {
+ Skin.Visible = true;
+ RgbSkinColorContainer.Visible = false;
+ }
+
+ Skin.Value = SkinColor.HumanSkinToneFromColor(Profile.Appearance.SkinColor);
+
+ break;
+ }
+ case HumanoidSkinColor.Hues:
+ {
+ if (!RgbSkinColorContainer.Visible)
+ {
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
+ }
+
+ // set the RGB values to the direct values otherwise
+ _rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
+ break;
+ }
+ case HumanoidSkinColor.TintedHues:
+ {
+ if (!RgbSkinColorContainer.Visible)
+ {
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
+ }
+
+ // set the RGB values to the direct values otherwise
+ _rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
+ break;
+ }
+ case HumanoidSkinColor.VoxFeathers:
+ {
+ if (!RgbSkinColorContainer.Visible)
+ {
+ Skin.Visible = false;
+ RgbSkinColorContainer.Visible = true;
+ }
+
+ _rgbSkinColorSelector.Color = SkinColor.ClosestVoxColor(Profile.Appearance.SkinColor);
+
+ break;
+ }
+ }
+
+ }
+
+ public void UpdateSpeciesGuidebookIcon()
+ {
+ SpeciesInfoButton.StyleClasses.Clear();
+
+ var species = Profile?.Species;
+ if (species is null)
+ return;
+
+ if (!_prototypeManager.TryIndex(species, out var speciesProto))
+ return;
+
+ // Don't display the info button if no guide entry is found
+ if (!_prototypeManager.HasIndex(species))
+ return;
+
+ const string style = "SpeciesInfoDefault";
+ SpeciesInfoButton.StyleClasses.Add(style);
+ }
+
+ private void UpdateMarkings()
+ {
+ if (Profile == null)
+ {
+ return;
+ }
+
+ Markings.SetData(Profile.Appearance.Markings, Profile.Species,
+ Profile.Sex, Profile.Appearance.SkinColor, Profile.Appearance.EyeColor
+ );
+ }
+
+ private void UpdateGenderControls()
+ {
+ if (Profile == null)
+ {
+ return;
+ }
+
+ PronounsButton.SelectId((int) Profile.Gender);
+ }
+
+ private void UpdateSpawnPriorityControls()
+ {
+ if (Profile == null)
+ {
+ return;
+ }
+
+ // SpawnPriorityButton.SelectId((int) Profile.SpawnPriority);
+ }
+
+ private void UpdateHairPickers()
+ {
+ if (Profile == null)
+ {
+ return;
+ }
+ var hairMarking = Profile.Appearance.HairStyleId switch
+ {
+ HairStyles.DefaultHairStyle => new List(),
+ _ => new() { new(Profile.Appearance.HairStyleId, new List() { Profile.Appearance.HairColor }) },
+ };
+
+ var facialHairMarking = Profile.Appearance.FacialHairStyleId switch
+ {
+ HairStyles.DefaultFacialHairStyle => new List(),
+ _ => new() { new(Profile.Appearance.FacialHairStyleId, new List() { Profile.Appearance.FacialHairColor }) },
+ };
+
+ HairStylePicker.UpdateData(
+ hairMarking,
+ Profile.Species,
+ 1);
+ FacialHairPicker.UpdateData(
+ facialHairMarking,
+ Profile.Species,
+ 1);
+ }
+
+ private void UpdateCMarkingsHair()
+ {
+ if (Profile == null)
+ {
+ return;
+ }
+
+ // hair color
+ Color? hairColor = null;
+ if ( Profile.Appearance.HairStyleId != HairStyles.DefaultHairStyle &&
+ _markingManager.Markings.TryGetValue(Profile.Appearance.HairStyleId, out var hairProto)
+ )
+ {
+ if (_markingManager.CanBeApplied(Profile.Species, Profile.Sex, hairProto, _prototypeManager))
+ {
+ if (_markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, out var _, _prototypeManager))
+ {
+ hairColor = Profile.Appearance.SkinColor;
+ }
+ else
+ {
+ hairColor = Profile.Appearance.HairColor;
+ }
+ }
+ }
+ if (hairColor != null)
+ {
+ Markings.HairMarking = new (Profile.Appearance.HairStyleId, new List() { hairColor.Value });
+ }
+ else
+ {
+ Markings.HairMarking = null;
+ }
+ }
+
+ private void UpdateCMarkingsFacialHair()
+ {
+ if (Profile == null)
+ {
+ return;
+ }
+
+ // facial hair color
+ Color? facialHairColor = null;
+ if ( Profile.Appearance.FacialHairStyleId != HairStyles.DefaultFacialHairStyle &&
+ _markingManager.Markings.TryGetValue(Profile.Appearance.FacialHairStyleId, out var facialHairProto))
+ {
+ if (_markingManager.CanBeApplied(Profile.Species, Profile.Sex, facialHairProto, _prototypeManager))
+ {
+ if (_markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, out var _, _prototypeManager))
+ {
+ facialHairColor = Profile.Appearance.SkinColor;
+ }
+ else
+ {
+ facialHairColor = Profile.Appearance.FacialHairColor;
+ }
+ }
+ }
+ if (facialHairColor != null)
+ {
+ Markings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List() { facialHairColor.Value });
+ }
+ else
+ {
+ Markings.FacialHairMarking = null;
+ }
+ }
+
+ private void UpdateEyePickers()
+ {
+ if (Profile == null)
+ {
+ return;
+ }
+
+ Markings.CurrentEyeColor = Profile.Appearance.EyeColor;
+ EyeColorPicker.SetData(Profile.Appearance.EyeColor);
+ }
+
+ private void UpdateSaveButton()
+ {
+ SaveButton.Disabled = Profile is null || !IsDirty;
+ ResetButton.Disabled = Profile is null || !IsDirty;
+ }
+
+ private void SetPreviewRotation(Direction direction)
+ {
+ SpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2);
+ }
+
+ private void RandomizeEverything()
+ {
+ Profile = HumanoidCharacterProfile.Random();
+ SetProfile(Profile, CharacterSlot);
+ SetDirty();
+ }
+
+ private void RandomizeName()
+ {
+ if (Profile == null) return;
+ var name = HumanoidCharacterProfile.GetName(Profile.Species, Profile.Gender);
+ SetName(name);
+ UpdateNameEdit();
+ }
+
+ private async void ImportProfile()
+ {
+ if (_exporting || CharacterSlot == null || Profile == null)
+ return;
+
+ StartExport();
+ await using var file = await _dialogManager.OpenFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
+
+ if (file == null)
+ {
+ EndExport();
+ return;
+ }
+
+ try
+ {
+ var profile = _entManager.System().FromStream(file, _playerManager.LocalSession!);
+ var oldProfile = Profile;
+ SetProfile(profile, CharacterSlot);
+
+ IsDirty = !profile.MemberwiseEquals(oldProfile);
+ }
+ catch (Exception exc)
+ {
+ _sawmill.Error($"Error when importing profile\n{exc.StackTrace}");
+ }
+ finally
+ {
+ EndExport();
+ }
+ }
+
+ private async void ExportProfile()
+ {
+ if (Profile == null || _exporting)
+ return;
+
+ StartExport();
+ var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
+
+ if (file == null)
+ {
+ EndExport();
+ return;
+ }
+
+ try
+ {
+ var dataNode = _entManager.System().ToDataNode(Profile);
+ await using var writer = new StreamWriter(file.Value.fileStream);
+ dataNode.Write(writer);
+ }
+ catch (Exception exc)
+ {
+ _sawmill.Error($"Error when exporting profile\n{exc.StackTrace}");
+ }
+ finally
+ {
+ EndExport();
+ await file.Value.fileStream.DisposeAsync();
+ }
+ }
+
+ private void StartExport()
+ {
+ _exporting = true;
+ ImportButton.Disabled = true;
+ ExportButton.Disabled = true;
+ }
+
+ private void EndExport()
+ {
+ _exporting = false;
+ ImportButton.Disabled = false;
+ ExportButton.Disabled = false;
+ }
+ }
+}
diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml b/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml
new file mode 100644
index 0000000000..a84a4a9640
--- /dev/null
+++ b/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs
new file mode 100644
index 0000000000..36f0772d78
--- /dev/null
+++ b/Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs
@@ -0,0 +1,74 @@
+using Content.Shared.Clothing;
+using Content.Shared.Preferences.Loadouts;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Lobby.UI.Loadouts;
+
+[GenerateTypedNameReferences]
+public sealed partial class LoadoutContainer : BoxContainer
+{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
+
+ private readonly EntityUid? _entity;
+
+ public Button Select => SelectButton;
+
+ public LoadoutContainer(ProtoId proto, bool disabled, FormattedMessage? reason)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ SelectButton.Disabled = disabled;
+
+ if (disabled && reason != null)
+ {
+ var tooltip = new Tooltip();
+ tooltip.SetMessage(reason);
+ SelectButton.TooltipSupplier = _ => tooltip;
+ }
+
+ if (_protoManager.TryIndex(proto, out var loadProto))
+ {
+ var ent = _entManager.System().GetFirstOrNull(loadProto);
+
+ if (ent != null)
+ {
+ _entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace);
+ Sprite.SetEntity(_entity);
+
+ var spriteTooltip = new Tooltip();
+ spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent(_entity.Value).EntityDescription));
+ TooltipSupplier = _ => spriteTooltip;
+ }
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (!disposing)
+ return;
+
+ _entManager.DeleteEntity(_entity);
+ }
+
+ public bool Pressed
+ {
+ get => SelectButton.Pressed;
+ set => SelectButton.Pressed = value;
+ }
+
+ public string? Text
+ {
+ get => SelectButton.Text;
+ set => SelectButton.Text = value;
+ }
+}
diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml b/Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml
new file mode 100644
index 0000000000..1e3eb14d3f
--- /dev/null
+++ b/Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml.cs
new file mode 100644
index 0000000000..bc7cfc7f48
--- /dev/null
+++ b/Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml.cs
@@ -0,0 +1,94 @@
+using System.Linq;
+using Content.Shared.Clothing;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Lobby.UI.Loadouts;
+
+[GenerateTypedNameReferences]
+public sealed partial class LoadoutGroupContainer : BoxContainer
+{
+ private readonly LoadoutGroupPrototype _groupProto;
+
+ public event Action>? OnLoadoutPressed;
+ public event Action>? OnLoadoutUnpressed;
+
+ public LoadoutGroupContainer(HumanoidCharacterProfile profile, RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection)
+ {
+ RobustXamlLoader.Load(this);
+ _groupProto = groupProto;
+
+ RefreshLoadouts(profile, loadout, session, collection);
+ }
+
+ ///
+ /// Updates button availabilities and buttons.
+ ///
+ public void RefreshLoadouts(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
+ {
+ var protoMan = collection.Resolve();
+ var loadoutSystem = collection.Resolve().System();
+ RestrictionsContainer.DisposeAllChildren();
+
+ if (_groupProto.MinLimit > 0)
+ {
+ RestrictionsContainer.AddChild(new Label()
+ {
+ Text = Loc.GetString("loadouts-min-limit", ("count", _groupProto.MinLimit)),
+ Margin = new Thickness(5, 0, 5, 5),
+ });
+ }
+
+ if (_groupProto.MaxLimit > 0)
+ {
+ RestrictionsContainer.AddChild(new Label()
+ {
+ Text = Loc.GetString("loadouts-max-limit", ("count", _groupProto.MaxLimit)),
+ Margin = new Thickness(5, 0, 5, 5),
+ });
+ }
+
+ if (protoMan.TryIndex(loadout.Role, out var roleProto) && roleProto.Points != null && loadout.Points != null)
+ {
+ RestrictionsContainer.AddChild(new Label()
+ {
+ Text = Loc.GetString("loadouts-points-limit", ("count", loadout.Points.Value), ("max", roleProto.Points.Value)),
+ Margin = new Thickness(5, 0, 5, 5),
+ });
+ }
+
+ LoadoutsContainer.DisposeAllChildren();
+ // Didn't use options because this is more robust in future.
+
+ var selected = loadout.SelectedLoadouts[_groupProto.ID];
+
+ foreach (var loadoutProto in _groupProto.Loadouts)
+ {
+ if (!protoMan.TryIndex(loadoutProto, out var loadProto))
+ continue;
+
+ var matchingLoadout = selected.FirstOrDefault(e => e.Prototype == loadoutProto);
+ var pressed = matchingLoadout != null;
+
+ var enabled = loadout.IsValid(profile, session, loadoutProto, collection, out var reason);
+ var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason);
+ loadoutContainer.Select.Pressed = pressed;
+ loadoutContainer.Text = loadoutSystem.GetName(loadProto);
+
+ loadoutContainer.Select.OnPressed += args =>
+ {
+ if (args.Button.Pressed)
+ OnLoadoutPressed?.Invoke(loadoutProto);
+ else
+ OnLoadoutUnpressed?.Invoke(loadoutProto);
+ };
+
+ LoadoutsContainer.AddChild(loadoutContainer);
+ }
+ }
+}
diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml
new file mode 100644
index 0000000000..afa783c7aa
--- /dev/null
+++ b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
new file mode 100644
index 0000000000..2737eef1f1
--- /dev/null
+++ b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
@@ -0,0 +1,55 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Preferences;
+using Content.Shared.Preferences.Loadouts;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Lobby.UI.Loadouts;
+
+[GenerateTypedNameReferences]
+public sealed partial class LoadoutWindow : FancyWindow
+{
+ public event Action, ProtoId>? OnLoadoutPressed;
+ public event Action, ProtoId>? OnLoadoutUnpressed;
+
+ private List _groups = new();
+
+ public HumanoidCharacterProfile Profile;
+
+ public LoadoutWindow(HumanoidCharacterProfile profile, RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection)
+ {
+ RobustXamlLoader.Load(this);
+ Profile = profile;
+ var protoManager = collection.Resolve();
+
+ foreach (var group in proto.Groups)
+ {
+ if (!protoManager.TryIndex(group, out var groupProto))
+ continue;
+
+ var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
+ LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
+ _groups.Add(container);
+
+ container.OnLoadoutPressed += args =>
+ {
+ OnLoadoutPressed?.Invoke(group, args);
+ };
+
+ container.OnLoadoutUnpressed += args =>
+ {
+ OnLoadoutUnpressed?.Invoke(group, args);
+ };
+ }
+ }
+
+ public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
+ {
+ foreach (var group in _groups)
+ {
+ group.RefreshLoadouts(Profile, loadout, session, collection);
+ }
+ }
+}
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs
deleted file mode 100644
index f9481caa3b..0000000000
--- a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-using System.Linq;
-using System.Numerics;
-using Content.Client.Alerts;
-using Content.Client.Humanoid;
-using Content.Client.Inventory;
-using Content.Client.Preferences;
-using Content.Client.UserInterface.Controls;
-using Content.Shared.GameTicking;
-using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.Inventory;
-using Content.Shared.Preferences;
-using Content.Shared.Roles;
-using Robust.Client.GameObjects;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-
-namespace Content.Client.Lobby.UI
-{
- public sealed class LobbyCharacterPreviewPanel : Control
- {
- [Dependency] private readonly IEntityManager _entityManager = default!;
- [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-
-
- private EntityUid? _previewDummy;
- private readonly Label _summaryLabel;
- private readonly BoxContainer _loaded;
- private readonly BoxContainer _viewBox;
- private readonly Label _unloaded;
-
- public LobbyCharacterPreviewPanel()
- {
- IoCManager.InjectDependencies(this);
- var header = new NanoHeading
- {
- Text = Loc.GetString("lobby-character-preview-panel-header")
- };
-
- CharacterSetupButton = new Button
- {
- Text = Loc.GetString("lobby-character-preview-panel-character-setup-button"),
- HorizontalAlignment = HAlignment.Center,
- Margin = new Thickness(0, 5, 0, 0),
- };
-
- _summaryLabel = new Label
- {
- HorizontalAlignment = HAlignment.Center,
- Margin = new Thickness(3, 3),
- };
-
- var vBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
- _unloaded = new Label { Text = Loc.GetString("lobby-character-preview-panel-unloaded-preferences-label") };
-
- _loaded = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Visible = false
- };
- _viewBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalAlignment = HAlignment.Center,
- };
- var _vSpacer = new VSpacer();
-
- _loaded.AddChild(_summaryLabel);
- _loaded.AddChild(_viewBox);
- _loaded.AddChild(_vSpacer);
- _loaded.AddChild(CharacterSetupButton);
-
- vBox.AddChild(header);
- vBox.AddChild(_loaded);
- vBox.AddChild(_unloaded);
- AddChild(vBox);
-
- UpdateUI();
- }
-
- public Button CharacterSetupButton { get; }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (!disposing) return;
- if (_previewDummy != null) _entityManager.DeleteEntity(_previewDummy.Value);
- _previewDummy = default;
- }
-
- public void UpdateUI()
- {
- if (!_preferencesManager.ServerDataLoaded)
- {
- _loaded.Visible = false;
- _unloaded.Visible = true;
- }
- else
- {
- _loaded.Visible = true;
- _unloaded.Visible = false;
- if (_preferencesManager.Preferences?.SelectedCharacter is not HumanoidCharacterProfile selectedCharacter)
- {
- _summaryLabel.Text = string.Empty;
- }
- else
- {
- _previewDummy = _entityManager.SpawnEntity(_prototypeManager.Index(selectedCharacter.Species).DollPrototype, MapCoordinates.Nullspace);
- _viewBox.DisposeAllChildren();
- var spriteView = new SpriteView
- {
- OverrideDirection = Direction.South,
- Scale = new Vector2(4f, 4f),
- MaxSize = new Vector2(112, 112),
- Stretch = SpriteView.StretchMode.Fill,
- };
- spriteView.SetEntity(_previewDummy.Value);
- _viewBox.AddChild(spriteView);
- _summaryLabel.Text = selectedCharacter.Summary;
- _entityManager.System().LoadProfile(_previewDummy.Value, selectedCharacter);
- GiveDummyJobClothes(_previewDummy.Value, selectedCharacter);
- }
- }
- }
-
- public static void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile)
- {
- var protoMan = IoCManager.Resolve();
- var entMan = IoCManager.Resolve();
- var invSystem = EntitySystem.Get();
-
- var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
-
- // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
- var job = protoMan.Index(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
-
- if (job.StartingGear != null && invSystem.TryGetSlots(dummy, out var slots))
- {
- var gear = protoMan.Index(job.StartingGear);
-
- foreach (var slot in slots)
- {
- var itemType = gear.GetGear(slot.Name, profile);
-
- if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
- {
- entMan.DeleteEntity(unequippedItem.Value);
- }
-
- if (itemType != string.Empty)
- {
- var item = entMan.SpawnEntity(itemType, MapCoordinates.Nullspace);
- invSystem.TryEquip(dummy, item, slot.Name, true, true);
- }
- }
- }
- }
- }
-}
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml
new file mode 100644
index 0000000000..997507414c
--- /dev/null
+++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
new file mode 100644
index 0000000000..14709f8b1f
--- /dev/null
+++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs
@@ -0,0 +1,63 @@
+using System.Numerics;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Lobby.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class LobbyCharacterPreviewPanel : Control
+{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+
+ public Button CharacterSetupButton => CharacterSetup;
+
+ private EntityUid? _previewDummy;
+
+ public LobbyCharacterPreviewPanel()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ }
+
+ public void SetLoaded(bool value)
+ {
+ Loaded.Visible = value;
+ Unloaded.Visible = !value;
+ }
+
+ public void SetSummaryText(string value)
+ {
+ Summary.Text = value;
+ }
+
+ public void SetSprite(EntityUid uid)
+ {
+ if (_previewDummy != null)
+ {
+ _entManager.DeleteEntity(_previewDummy);
+ }
+
+ _previewDummy = uid;
+
+ ViewBox.DisposeAllChildren();
+ var spriteView = new SpriteView
+ {
+ OverrideDirection = Direction.South,
+ Scale = new Vector2(4f, 4f),
+ MaxSize = new Vector2(112, 112),
+ Stretch = SpriteView.StretchMode.Fill,
+ };
+ spriteView.SetEntity(uid);
+ ViewBox.AddChild(spriteView);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ _entManager.DeleteEntity(_previewDummy);
+ _previewDummy = null;
+ }
+}
diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml b/Content.Client/Lobby/UI/LobbyGui.xaml
index 5fe261a7c9..6d1662cf16 100644
--- a/Content.Client/Lobby/UI/LobbyGui.xaml
+++ b/Content.Client/Lobby/UI/LobbyGui.xaml
@@ -62,10 +62,11 @@
-
+
-
@@ -73,7 +74,7 @@
@@ -86,14 +87,15 @@
StyleClasses="ButtonBig"
WindowType="{x:Type lobbyUi:ObserveWarningWindow}"
Margin="8 0 8 0"/>
-
-
+
+
+
diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml.cs b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
index d45a99c16f..6471edb6f3 100644
--- a/Content.Client/Lobby/UI/LobbyGui.xaml.cs
+++ b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
@@ -1,30 +1,16 @@
-using Content.Client.Chat.UI;
-using Content.Client.Info;
-using Content.Client.Preferences;
-using Content.Client.Preferences.UI;
-using Content.Client.UserInterface.Screens;
-using Content.Client.UserInterface.Systems.Chat.Widgets;
+using Content.Client.Message;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
-using Robust.Client.Graphics;
-using Robust.Client.State;
using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Lobby.UI
{
[GenerateTypedNameReferences]
- internal sealed partial class LobbyGui : UIScreen
+ public sealed partial class LobbyGui : UIScreen
{
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
- [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
public LobbyGui()
{
@@ -33,8 +19,10 @@ public LobbyGui()
SetAnchorPreset(MainContainer, LayoutPreset.Wide);
SetAnchorPreset(Background, LayoutPreset.Wide);
+ LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text"));
+
LeaveButton.OnPressed += _ => _consoleHost.ExecuteCommand("disconnect");
- OptionsButton.OnPressed += _ => _userInterfaceManager.GetUIController().ToggleWindow();
+ OptionsButton.OnPressed += _ => UserInterfaceManager.GetUIController().ToggleWindow();
}
public void SwitchState(LobbyGuiState state)
@@ -51,7 +39,7 @@ public void SwitchState(LobbyGuiState state)
case LobbyGuiState.CharacterSetup:
CharacterSetupState.Visible = true;
- var actualWidth = (float) _userInterfaceManager.RootControl.PixelWidth;
+ var actualWidth = (float) UserInterfaceManager.RootControl.PixelWidth;
var setupWidth = (float) LeftSide.PixelWidth;
if (1 - (setupWidth / actualWidth) > 0.30)
@@ -59,6 +47,8 @@ public void SwitchState(LobbyGuiState state)
RightSide.Visible = false;
}
+ UserInterfaceManager.GetUIController().ReloadCharacterSetup();
+
break;
}
}
diff --git a/Content.Client/Lobby/UI/ObserveWarningWindow.xaml b/Content.Client/Lobby/UI/ObserveWarningWindow.xaml
index 3fe8e83f57..2feac5792a 100644
--- a/Content.Client/Lobby/UI/ObserveWarningWindow.xaml
+++ b/Content.Client/Lobby/UI/ObserveWarningWindow.xaml
@@ -4,10 +4,11 @@
-
+
-
+
+
diff --git a/Content.Client/Lobby/UI/ObserveWarningWindow.xaml.cs b/Content.Client/Lobby/UI/ObserveWarningWindow.xaml.cs
index 2d37cb97df..a002043ab1 100644
--- a/Content.Client/Lobby/UI/ObserveWarningWindow.xaml.cs
+++ b/Content.Client/Lobby/UI/ObserveWarningWindow.xaml.cs
@@ -1,24 +1,34 @@
+using Content.Shared.Administration.Managers;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
+using Robust.Client.Player;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-namespace Content.Client.Lobby.UI
+namespace Content.Client.Lobby.UI;
+
+[GenerateTypedNameReferences]
+[UsedImplicitly]
+public sealed partial class ObserveWarningWindow : DefaultWindow
{
- [GenerateTypedNameReferences]
- [UsedImplicitly]
- internal sealed partial class ObserveWarningWindow : DefaultWindow
+ [Dependency] private readonly ISharedAdminManager _adminManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+
+ public ObserveWarningWindow()
{
- public ObserveWarningWindow()
- {
- Title = Loc.GetString("observe-warning-window-title");
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
+ Title = Loc.GetString("observe-warning-window-title");
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ var player = _playerManager.LocalSession;
- ObserveButton.OnPressed += _ => { this.Close(); };
- NevermindButton.OnPressed += _ => { this.Close(); };
+ if (player != null && _adminManager.IsAdmin(player))
+ {
+ ObserveButton.Text = Loc.GetString("observe-as-player");
+ ObserveAsAdminButton.Visible = true;
+ ObserveAsAdminButton.OnPressed += _ => { this.Close(); };
}
+
+ ObserveButton.OnPressed += _ => { this.Close(); };
+ NevermindButton.OnPressed += _ => { this.Close(); };
}
}
diff --git a/Content.Client/Lobby/UI/Roles/RequirementsSelector.xaml b/Content.Client/Lobby/UI/Roles/RequirementsSelector.xaml
new file mode 100644
index 0000000000..4cab5286c2
--- /dev/null
+++ b/Content.Client/Lobby/UI/Roles/RequirementsSelector.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/Roles/RequirementsSelector.xaml.cs b/Content.Client/Lobby/UI/Roles/RequirementsSelector.xaml.cs
new file mode 100644
index 0000000000..ce75537355
--- /dev/null
+++ b/Content.Client/Lobby/UI/Roles/RequirementsSelector.xaml.cs
@@ -0,0 +1,137 @@
+using System.Numerics;
+using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Guidebook;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Lobby.UI.Roles;
+
+///
+/// A generic locking selector.
+///
+[GenerateTypedNameReferences]
+public sealed partial class RequirementsSelector : BoxContainer
+{
+ private readonly RadioOptions _options;
+ private readonly StripeBack _lockStripe;
+ private List>? _guides;
+
+ public event Action? OnSelected;
+ public event Action>>? OnOpenGuidebook;
+
+ public int Selected => _options.SelectedId;
+
+ public RequirementsSelector()
+ {
+ RobustXamlLoader.Load(this);
+ _options = new RadioOptions(RadioOptionsLayout.Horizontal)
+ {
+ FirstButtonStyle = StyleBase.ButtonOpenRight,
+ ButtonStyle = StyleBase.ButtonOpenBoth,
+ LastButtonStyle = StyleBase.ButtonOpenLeft,
+ HorizontalExpand = true,
+ };
+ //Override default radio option button width
+ _options.GenerateItem = GenerateButton;
+
+ _options.OnItemSelected += args =>
+ {
+ _options.Select(args.Id);
+ OnSelected?.Invoke(args.Id);
+ };
+
+ var requirementsLabel = new Label()
+ {
+ Text = Loc.GetString("role-timer-locked"),
+ Visible = true,
+ HorizontalAlignment = HAlignment.Center,
+ StyleClasses = {StyleBase.StyleClassLabelSubText},
+ };
+
+ _lockStripe = new StripeBack()
+ {
+ Visible = false,
+ HorizontalExpand = true,
+ HasMargins = false,
+ MouseFilter = MouseFilterMode.Stop,
+ Children =
+ {
+ requirementsLabel
+ }
+ };
+
+ Help.OnPressed += _ =>
+ {
+ if (_guides != null)
+ OnOpenGuidebook?.Invoke(_guides);
+ };
+ }
+
+ ///
+ /// Actually adds the controls.
+ ///
+ public void Setup(
+ (string, int)[] items,
+ string title,
+ int titleSize,
+ string? description,
+ TextureRect? icon = null,
+ List>? guides = null)
+ {
+ foreach (var (text, value) in items)
+ {
+ _options.AddItem(Loc.GetString(text), value);
+ }
+
+ Help.Visible = guides != null;
+ _guides = guides;
+
+ TitleLabel.Text = title;
+ TitleLabel.MinSize = new Vector2(titleSize, 0f);
+ TitleLabel.ToolTip = description;
+
+ if (icon != null)
+ {
+ AddChild(icon);
+ icon.SetPositionFirst();
+ }
+
+ OptionsContainer.AddChild(_options);
+ OptionsContainer.AddChild(_lockStripe);
+ }
+
+ public void LockRequirements(FormattedMessage requirements)
+ {
+ var tooltip = new Tooltip();
+ tooltip.SetMessage(requirements);
+ _lockStripe.TooltipSupplier = _ => tooltip;
+ _lockStripe.Visible = true;
+ _options.Visible = false;
+ }
+
+ public void UnlockRequirements()
+ {
+ _lockStripe.Visible = false;
+ _options.Visible = true;
+ }
+
+ private Button GenerateButton(string text, int value)
+ {
+ return new Button
+ {
+ Text = text,
+ MinWidth = 90,
+ HorizontalExpand = true,
+ };
+ }
+
+ public void Select(int id)
+ {
+ _options.Select(id);
+ }
+}
diff --git a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml
new file mode 100644
index 0000000000..266b4b8eee
--- /dev/null
+++ b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs
new file mode 100644
index 0000000000..a52a3fa2db
--- /dev/null
+++ b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs
@@ -0,0 +1,43 @@
+using Content.Shared.Traits;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Lobby.UI.Roles;
+
+[GenerateTypedNameReferences]
+public sealed partial class TraitPreferenceSelector : Control
+{
+ public int Cost;
+
+ public bool Preference
+ {
+ get => Checkbox.Pressed;
+ set => Checkbox.Pressed = value;
+ }
+
+ public event Action? PreferenceChanged;
+
+ public TraitPreferenceSelector(TraitPrototype trait)
+ {
+ RobustXamlLoader.Load(this);
+
+ var text = trait.Cost != 0 ? $"[{trait.Cost}] " : "";
+ text += Loc.GetString(trait.Name);
+
+ Cost = trait.Cost;
+ Checkbox.Text = text;
+ Checkbox.OnToggled += OnCheckBoxToggled;
+
+ if (trait.Description is { } desc)
+ {
+ Checkbox.ToolTip = Loc.GetString(desc);
+ }
+ }
+
+ private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
+ {
+ PreferenceChanged?.Invoke(Preference);
+ }
+}
diff --git a/Content.Client/Magic/MagicSystem.cs b/Content.Client/Magic/MagicSystem.cs
new file mode 100644
index 0000000000..03aa9eb56d
--- /dev/null
+++ b/Content.Client/Magic/MagicSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Magic;
+
+namespace Content.Client.Magic;
+
+public sealed class MagicSystem : SharedMagicSystem;
diff --git a/Content.Client/MagicMirror/MagicMirrorBoundUserInterface.cs b/Content.Client/MagicMirror/MagicMirrorBoundUserInterface.cs
index bfbf2efe4f..f6979bf8d7 100644
--- a/Content.Client/MagicMirror/MagicMirrorBoundUserInterface.cs
+++ b/Content.Client/MagicMirror/MagicMirrorBoundUserInterface.cs
@@ -72,9 +72,6 @@ protected override void Dispose(bool disposing)
if (!disposing)
return;
- if (_window != null)
- _window.OnClose -= Close;
-
_window?.Dispose();
}
}
diff --git a/Content.Client/MagicMirror/MagicMirrorSystem.cs b/Content.Client/MagicMirror/MagicMirrorSystem.cs
new file mode 100644
index 0000000000..9b0b1dea0b
--- /dev/null
+++ b/Content.Client/MagicMirror/MagicMirrorSystem.cs
@@ -0,0 +1,8 @@
+using Content.Shared.MagicMirror;
+
+namespace Content.Client.MagicMirror;
+
+public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
+{
+
+}
diff --git a/Content.Client/MapText/MapTextComponent.cs b/Content.Client/MapText/MapTextComponent.cs
new file mode 100644
index 0000000000..f70b3c8dd4
--- /dev/null
+++ b/Content.Client/MapText/MapTextComponent.cs
@@ -0,0 +1,20 @@
+using Content.Shared.MapText;
+using Robust.Client.Graphics;
+
+namespace Content.Client.MapText;
+
+[RegisterComponent]
+public sealed partial class MapTextComponent : SharedMapTextComponent
+{
+ ///
+ /// The font that gets cached on component init or state changes
+ ///
+ [ViewVariables]
+ public VectorFont? CachedFont;
+
+ ///
+ /// The text currently being displayed. This is either or the
+ /// localized text or
+ ///
+ public string CachedText = string.Empty;
+}
diff --git a/Content.Client/MapText/MapTextOverlay.cs b/Content.Client/MapText/MapTextOverlay.cs
new file mode 100644
index 0000000000..70e708bba8
--- /dev/null
+++ b/Content.Client/MapText/MapTextOverlay.cs
@@ -0,0 +1,85 @@
+using System.Numerics;
+using Content.Shared.MapText;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.RichText;
+using Robust.Shared;
+using Robust.Shared.Configuration;
+using Robust.Shared.Enums;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.MapText;
+
+///
+/// Draws map text as an overlay
+///
+public sealed class MapTextOverlay : Overlay
+{
+ private readonly IConfigurationManager _configManager;
+ private readonly IEntityManager _entManager;
+ private readonly IUserInterfaceManager _uiManager;
+ private readonly SharedTransformSystem _transform;
+ public override OverlaySpace Space => OverlaySpace.ScreenSpace;
+
+ public MapTextOverlay(
+ IConfigurationManager configManager,
+ IEntityManager entManager,
+ IUserInterfaceManager uiManager,
+ SharedTransformSystem transform,
+ IResourceCache resourceCache,
+ IPrototypeManager prototypeManager)
+ {
+ _configManager = configManager;
+ _entManager = entManager;
+ _uiManager = uiManager;
+ _transform = transform;
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (args.ViewportControl == null)
+ return;
+
+ args.DrawingHandle.SetTransform(Matrix3x2.Identity);
+
+ var scale = _configManager.GetCVar(CVars.DisplayUIScale);
+
+ if (scale == 0f)
+ scale = _uiManager.DefaultUIScale;
+
+ DrawWorld(args.ScreenHandle, args, scale);
+
+ args.DrawingHandle.UseShader(null);
+ }
+
+ private void DrawWorld(DrawingHandleScreen handle, OverlayDrawArgs args, float scale)
+ {
+ if ( args.ViewportControl == null)
+ return;
+
+ var matrix = args.ViewportControl.GetWorldToScreenMatrix();
+ var query = _entManager.AllEntityQueryEnumerator();
+
+ // Enlarge bounds to try prevent pop-in due to large text.
+ var bounds = args.WorldBounds.Enlarged(2);
+
+ while(query.MoveNext(out var uid, out var mapText))
+ {
+ var mapPos = _transform.GetMapCoordinates(uid);
+
+ if (mapPos.MapId != args.MapId)
+ continue;
+
+ if (!bounds.Contains(mapPos.Position))
+ continue;
+
+ if (mapText.CachedFont == null)
+ continue;
+
+ var pos = Vector2.Transform(mapPos.Position, matrix) + mapText.Offset;
+ var dimensions = handle.GetDimensions(mapText.CachedFont, mapText.CachedText, scale);
+ handle.DrawString(mapText.CachedFont, pos - dimensions / 2f, mapText.CachedText, scale, mapText.Color);
+ }
+ }
+}
diff --git a/Content.Client/MapText/MapTextSystem.cs b/Content.Client/MapText/MapTextSystem.cs
new file mode 100644
index 0000000000..96ce8f93c2
--- /dev/null
+++ b/Content.Client/MapText/MapTextSystem.cs
@@ -0,0 +1,81 @@
+using Content.Shared.MapText;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.RichText;
+using Robust.Shared.Configuration;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.MapText;
+
+///
+public sealed class MapTextSystem : SharedMapTextSystem
+{
+ [Dependency] private readonly IConfigurationManager _configManager = default!;
+ [Dependency] private readonly IUserInterfaceManager _uiManager = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly IResourceCache _resourceCache = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IOverlayManager _overlayManager = default!;
+
+ private MapTextOverlay _overlay = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnComponentStartup);
+ SubscribeLocalEvent(HandleCompState);
+
+ _overlay = new MapTextOverlay(_configManager, EntityManager, _uiManager, _transform, _resourceCache, _prototypeManager);
+ _overlayManager.AddOverlay(_overlay);
+
+ // TODO move font prototype to robust.shared, then use ProtoId
+ DebugTools.Assert(_prototypeManager.HasIndex(SharedMapTextComponent.DefaultFont));
+ }
+
+ private void OnComponentStartup(Entity ent, ref ComponentStartup args)
+ {
+ CacheText(ent.Comp);
+ // TODO move font prototype to robust.shared, then use ProtoId
+ DebugTools.Assert(_prototypeManager.HasIndex(ent.Comp.FontId));
+ }
+
+ private void HandleCompState(Entity ent, ref ComponentHandleState args)
+ {
+ if (args.Current is not MapTextComponentState state)
+ return;
+
+ ent.Comp.Text = state.Text;
+ ent.Comp.LocText = state.LocText;
+ ent.Comp.Color = state.Color;
+ ent.Comp.FontId = state.FontId;
+ ent.Comp.FontSize = state.FontSize;
+ ent.Comp.Offset = state.Offset;
+
+ CacheText(ent.Comp);
+ }
+
+ private void CacheText(MapTextComponent component)
+ {
+ component.CachedFont = null;
+
+ component.CachedText = string.IsNullOrWhiteSpace(component.Text)
+ ? Loc.GetString(component.LocText)
+ : component.Text;
+
+ if (!_prototypeManager.TryIndex(component.FontId, out var fontPrototype))
+ {
+ component.CachedText = Loc.GetString("map-text-font-error");
+ component.Color = Color.Red;
+
+ if(_prototypeManager.TryIndex(SharedMapTextComponent.DefaultFont, out var @default))
+ component.CachedFont = new VectorFont(_resourceCache.GetResource(@default.Path), 14);
+ return;
+ }
+
+ var fontResource = _resourceCache.GetResource(fontPrototype.Path);
+ component.CachedFont = new VectorFont(fontResource, component.FontSize);
+ }
+}
diff --git a/Content.Client/Mapping/MappingSystem.cs b/Content.Client/Mapping/MappingSystem.cs
index 4456be36a6..8daf193dfe 100644
--- a/Content.Client/Mapping/MappingSystem.cs
+++ b/Content.Client/Mapping/MappingSystem.cs
@@ -83,7 +83,7 @@ private void OnFillActionSlot(FillActionSlotEvent ev)
if (tileDef is not ContentTileDefinition contentTileDef)
return;
- var tileIcon = contentTileDef.IsSpace
+ var tileIcon = contentTileDef.MapAtmosphere
? _spaceIcon
: new Texture(contentTileDef.Sprite!.Value);
diff --git a/Content.Client/Maps/GridDraggingSystem.cs b/Content.Client/Maps/GridDraggingSystem.cs
index 16357c8983..5e9250f0ce 100644
--- a/Content.Client/Maps/GridDraggingSystem.cs
+++ b/Content.Client/Maps/GridDraggingSystem.cs
@@ -61,7 +61,7 @@ private void StopDragging()
{
if (_dragging == null) return;
- if (_lastMousePosition != null && TryComp(_dragging.Value, out var xform) &&
+ if (_lastMousePosition != null && TryComp(_dragging.Value, out TransformComponent? xform) &&
TryComp(_dragging.Value, out var body) &&
xform.MapID == _lastMousePosition.Value.MapId)
{
@@ -101,10 +101,10 @@ public override void Update(float frameTime)
if (!_mapManager.TryFindGridAt(mousePos, out var gridUid, out var grid))
return;
- StartDragging(gridUid, Transform(gridUid).InvWorldMatrix.Transform(mousePos.Position));
+ StartDragging(gridUid, Vector2.Transform(mousePos.Position, Transform(gridUid).InvWorldMatrix));
}
- if (!TryComp(_dragging, out var xform))
+ if (!TryComp(_dragging, out TransformComponent? xform))
{
StopDragging();
return;
@@ -116,7 +116,7 @@ public override void Update(float frameTime)
return;
}
- var localToWorld = xform.WorldMatrix.Transform(_localPosition);
+ var localToWorld = Vector2.Transform(_localPosition, xform.WorldMatrix);
if (localToWorld.EqualsApprox(mousePos.Position, 0.01f)) return;
diff --git a/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml
new file mode 100644
index 0000000000..a0f63dad6e
--- /dev/null
+++ b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs
new file mode 100644
index 0000000000..5e068f1e9c
--- /dev/null
+++ b/Content.Client/MassMedia/Ui/ArticleEditorPanel.xaml.cs
@@ -0,0 +1,115 @@
+using Content.Client.Message;
+using Content.Client.Stylesheets;
+using Content.Shared.MassMedia.Systems;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+
+namespace Content.Client.MassMedia.Ui;
+
+[GenerateTypedNameReferences]
+public sealed partial class ArticleEditorPanel : Control
+{
+ public event Action? PublishButtonPressed;
+
+ private bool _preview;
+
+ public ArticleEditorPanel()
+ {
+ RobustXamlLoader.Load(this);
+
+ ButtonPublish.StyleClasses.Add(StyleBase.ButtonOpenLeft);
+ ButtonPublish.StyleClasses.Add(StyleNano.StyleClassButtonColorGreen);
+
+ ContentField.GetChild(0).Margin = new Thickness(9, 3);
+ // Customize scrollbar width and margin. This is not possible in xaml
+ var scrollbar = ContentField.GetChild(1);
+ scrollbar.SetWidth = 6f;
+ scrollbar.Margin = new Thickness(9, 0, 2 , 0);
+
+ RichTextInfoLabel.TooltipSupplier = sender =>
+ {
+ var label = new RichTextLabel();
+ label.SetMarkup(Loc.GetString("news-write-ui-richtext-tooltip"));
+
+ var tooltip = new Tooltip();
+ tooltip.GetChild(0).Children.Clear();
+ tooltip.GetChild(0).Children.Add(label);
+
+ return tooltip;
+ };
+
+ ButtonPreview.OnPressed += OnPreview;
+ ButtonCancel.OnPressed += OnCancel;
+ ButtonPublish.OnPressed += OnPublish;
+
+ TitleField.OnTextChanged += args => OnTextChanged(args.Text.Length, args.Control, SharedNewsSystem.MaxTitleLength);
+ ContentField.OnTextChanged += args => OnTextChanged(Rope.CalcTotalLength(args.TextRope), args.Control, SharedNewsSystem.MaxContentLength);
+ }
+
+ private void OnTextChanged(long length, Control control, long maxLength)
+ {
+ if (length > maxLength)
+ {
+ control.ModulateSelfOverride = Color.Red;
+ control.ToolTip = Loc.GetString("news-writer-text-length-exceeded");
+
+ ButtonPublish.Disabled = true;
+ ButtonPreview.Disabled = true;
+ }
+ else
+ {
+ control.ModulateSelfOverride = null;
+ control.ToolTip = string.Empty;
+
+ ButtonPublish.Disabled = false;
+ ButtonPreview.Disabled = false;
+ }
+ }
+
+ private void OnPreview(BaseButton.ButtonEventArgs eventArgs)
+ {
+ _preview = !_preview;
+
+ TextEditPanel.Visible = !_preview;
+ PreviewPanel.Visible = _preview;
+ PreviewLabel.SetMarkupPermissive(Rope.Collapse(ContentField.TextRope));
+ }
+
+ private void OnCancel(BaseButton.ButtonEventArgs eventArgs)
+ {
+ Reset();
+ Visible = false;
+ }
+
+ private void OnPublish(BaseButton.ButtonEventArgs eventArgs)
+ {
+ PublishButtonPressed?.Invoke();
+ Reset();
+ Visible = false;
+ }
+
+ private void Reset()
+ {
+ _preview = false;
+ TextEditPanel.Visible = true;
+ PreviewPanel.Visible = false;
+ PreviewLabel.SetMarkup("");
+ TitleField.Text = "";
+ ContentField.TextRope = Rope.Leaf.Empty;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ ButtonPreview.OnPressed -= OnPreview;
+ ButtonCancel.OnPressed -= OnCancel;
+ ButtonPublish.OnPressed -= OnPublish;
+ }
+}
diff --git a/Content.Client/MassMedia/Ui/MiniArticleCardControl.xaml b/Content.Client/MassMedia/Ui/MiniArticleCardControl.xaml
deleted file mode 100644
index ede51fc23b..0000000000
--- a/Content.Client/MassMedia/Ui/MiniArticleCardControl.xaml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Content.Client/MassMedia/Ui/MiniArticleCardControl.xaml.cs b/Content.Client/MassMedia/Ui/MiniArticleCardControl.xaml.cs
deleted file mode 100644
index 3c5076e68f..0000000000
--- a/Content.Client/MassMedia/Ui/MiniArticleCardControl.xaml.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Content.Client.Message;
-using Content.Shared.Research.Prototypes;
-using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Utility;
-
-namespace Content.Client.MassMedia.Ui;
-
-[GenerateTypedNameReferences]
-public sealed partial class MiniArticleCardControl : Control
-{
- public Action? OnDeletePressed;
- public int ArticleNum;
-
- public MiniArticleCardControl(string name, string author)
- {
- RobustXamlLoader.Load(this);
-
- NameLabel.Text = name;
- Author.SetMarkup(author);
-
- Delete.OnPressed += _ => OnDeletePressed?.Invoke();
- }
-}
diff --git a/Content.Client/MassMedia/Ui/NewsArticleCard.xaml b/Content.Client/MassMedia/Ui/NewsArticleCard.xaml
new file mode 100644
index 0000000000..abfc1cbcf1
--- /dev/null
+++ b/Content.Client/MassMedia/Ui/NewsArticleCard.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ButtonSmall
+ ButtonColorRed
+
+
+
+
+
diff --git a/Content.Client/MassMedia/Ui/NewsArticleCard.xaml.cs b/Content.Client/MassMedia/Ui/NewsArticleCard.xaml.cs
new file mode 100644
index 0000000000..f6fa3e00be
--- /dev/null
+++ b/Content.Client/MassMedia/Ui/NewsArticleCard.xaml.cs
@@ -0,0 +1,47 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.MassMedia.Ui;
+
+[GenerateTypedNameReferences]
+public sealed partial class NewsArticleCard : Control
+{
+ private string? _authorMarkup;
+ private TimeSpan? _publicationTime;
+
+ public Action? OnDeletePressed;
+ public int ArtcileNumber;
+
+ public string? Title
+ {
+ get => TitleLabel.Text;
+ set => TitleLabel.Text = value?.Length <= 30 ? value : $"{value?[..30]}...";
+ }
+
+ public string? Author
+ {
+ get => _authorMarkup;
+ set
+ {
+ _authorMarkup = value;
+ AuthorLabel.Text = _authorMarkup ?? "";
+ }
+ }
+
+ public TimeSpan? PublicationTime
+ {
+ get => _publicationTime;
+ set
+ {
+ _publicationTime = value;
+ PublishTimeLabel.Text = value?.ToString(@"hh\:mm\:ss") ?? "";
+ }
+ }
+
+ public NewsArticleCard()
+ {
+ RobustXamlLoader.Load(this);
+ DeleteButton.OnPressed += _ => OnDeletePressed?.Invoke();
+ }
+}
diff --git a/Content.Client/MassMedia/Ui/NewsWriteBoundUserInterface.cs b/Content.Client/MassMedia/Ui/NewsWriteBoundUserInterface.cs
deleted file mode 100644
index d1d61d5adb..0000000000
--- a/Content.Client/MassMedia/Ui/NewsWriteBoundUserInterface.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using JetBrains.Annotations;
-using Content.Shared.MassMedia.Components;
-using Content.Shared.MassMedia.Systems;
-using Robust.Shared.Utility;
-
-namespace Content.Client.MassMedia.Ui
-{
- [UsedImplicitly]
- public sealed class NewsWriteBoundUserInterface : BoundUserInterface
- {
- [ViewVariables]
- private NewsWriteMenu? _menu;
-
- [ViewVariables]
- private string _windowName = Loc.GetString("news-read-ui-default-title");
-
- public NewsWriteBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
- {
- }
-
- protected override void Open()
- {
- _menu = new NewsWriteMenu(_windowName);
-
- _menu.OpenCentered();
- _menu.OnClose += Close;
-
- _menu.ShareButtonPressed += OnShareButtonPressed;
- _menu.DeleteButtonPressed += OnDeleteButtonPressed;
-
- SendMessage(new NewsWriteArticlesRequestMessage());
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- _menu?.Close();
- _menu?.Dispose();
- }
-
- protected override void UpdateState(BoundUserInterfaceState state)
- {
- base.UpdateState(state);
- if (_menu == null || state is not NewsWriteBoundUserInterfaceState cast)
- return;
-
- _menu.UpdateUI(cast.Articles, cast.ShareAvalible);
- }
-
- private void OnShareButtonPressed()
- {
- if (_menu == null || _menu.NameInput.Text.Length == 0)
- return;
-
- var stringContent = Rope.Collapse(_menu.ContentInput.TextRope);
-
- if (stringContent.Length == 0)
- return;
-
- var stringName = _menu.NameInput.Text.Trim();
- var name = stringName[..Math.Min(stringName.Length, (SharedNewsSystem.MaxNameLength))];
- var content = stringContent[..Math.Min(stringContent.Length, (SharedNewsSystem.MaxArticleLength))];
- _menu.ContentInput.TextRope = new Rope.Leaf(string.Empty);
- _menu.NameInput.Text = string.Empty;
- SendMessage(new NewsWriteShareMessage(name, content));
- }
-
- private void OnDeleteButtonPressed(int articleNum)
- {
- if (_menu == null)
- return;
-
- SendMessage(new NewsWriteDeleteMessage(articleNum));
- }
- }
-}
diff --git a/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml b/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml
deleted file mode 100644
index 08d113f8a9..0000000000
--- a/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml.cs b/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml.cs
deleted file mode 100644
index 89ab1490af..0000000000
--- a/Content.Client/MassMedia/Ui/NewsWriteMenu.xaml.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.CustomControls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Prototypes;
-using Content.Shared.MassMedia.Systems;
-
-namespace Content.Client.MassMedia.Ui;
-
-[GenerateTypedNameReferences]
-public sealed partial class NewsWriteMenu : DefaultWindow
-{
- public event Action? ShareButtonPressed;
- public event Action? DeleteButtonPressed;
-
- public NewsWriteMenu(string name)
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
-
- if (Window != null)
- Window.Title = name;
-
- Share.OnPressed += _ => ShareButtonPressed?.Invoke();
- }
-
- public void UpdateUI(NewsArticle[] articles, bool shareAvalible)
- {
- ArticleCardsContainer.Children.Clear();
-
- for (int i = 0; i < articles.Length; i++)
- {
- var article = articles[i];
- var mini = new MiniArticleCardControl(article.Name, (article.Author != null ? article.Author : Loc.GetString("news-read-ui-no-author")));
- mini.ArticleNum = i;
- mini.OnDeletePressed += () => DeleteButtonPressed?.Invoke(mini.ArticleNum);
-
- ArticleCardsContainer.AddChild(mini);
- }
-
- Share.Disabled = !shareAvalible;
- }
-}
diff --git a/Content.Client/MassMedia/Ui/NewsWriterBoundUserInterface.cs b/Content.Client/MassMedia/Ui/NewsWriterBoundUserInterface.cs
new file mode 100644
index 0000000000..80eca82e32
--- /dev/null
+++ b/Content.Client/MassMedia/Ui/NewsWriterBoundUserInterface.cs
@@ -0,0 +1,84 @@
+using JetBrains.Annotations;
+using Content.Shared.MassMedia.Systems;
+using Content.Shared.MassMedia.Components;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Client.MassMedia.Ui;
+
+[UsedImplicitly]
+public sealed class NewsWriterBoundUserInterface : BoundUserInterface
+{
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+
+ [ViewVariables]
+ private NewsWriterMenu? _menu;
+
+ public NewsWriterBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+
+ }
+
+ protected override void Open()
+ {
+ _menu = new NewsWriterMenu(_gameTiming);
+
+ _menu.OpenCentered();
+ _menu.OnClose += Close;
+
+ _menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed;
+ _menu.DeleteButtonPressed += OnDeleteButtonPressed;
+
+ SendMessage(new NewsWriterArticlesRequestMessage());
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ _menu?.Close();
+ _menu?.Dispose();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+ if (state is not NewsWriterBoundUserInterfaceState cast)
+ return;
+
+ _menu?.UpdateUI(cast.Articles, cast.PublishEnabled, cast.NextPublish);
+ }
+
+ private void OnPublishButtonPressed()
+ {
+ var title = _menu?.ArticleEditorPanel.TitleField.Text.Trim() ?? "";
+ if (_menu == null || title.Length == 0)
+ return;
+
+ var stringContent = Rope.Collapse(_menu.ArticleEditorPanel.ContentField.TextRope).Trim();
+
+ if (stringContent.Length == 0)
+ return;
+
+ var name = title.Length <= SharedNewsSystem.MaxTitleLength
+ ? title
+ : $"{title[..(SharedNewsSystem.MaxTitleLength - 3)]}...";
+
+ var content = stringContent.Length <= SharedNewsSystem.MaxContentLength
+ ? stringContent
+ : $"{stringContent[..(SharedNewsSystem.MaxContentLength - 3)]}...";
+
+
+ SendMessage(new NewsWriterPublishMessage(name, content));
+ }
+
+ private void OnDeleteButtonPressed(int articleNum)
+ {
+ if (_menu == null)
+ return;
+
+ SendMessage(new NewsWriterDeleteMessage(articleNum));
+ }
+}
diff --git a/Content.Client/MassMedia/Ui/NewsWriterMenu.xaml b/Content.Client/MassMedia/Ui/NewsWriterMenu.xaml
new file mode 100644
index 0000000000..64932bc6cf
--- /dev/null
+++ b/Content.Client/MassMedia/Ui/NewsWriterMenu.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/MassMedia/Ui/NewsWriterMenu.xaml.cs b/Content.Client/MassMedia/Ui/NewsWriterMenu.xaml.cs
new file mode 100644
index 0000000000..e2d57935e3
--- /dev/null
+++ b/Content.Client/MassMedia/Ui/NewsWriterMenu.xaml.cs
@@ -0,0 +1,97 @@
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Content.Shared.MassMedia.Systems;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Timing;
+
+namespace Content.Client.MassMedia.Ui;
+
+[GenerateTypedNameReferences]
+public sealed partial class NewsWriterMenu : FancyWindow
+{
+ private readonly IGameTiming _gameTiming;
+
+ private TimeSpan? _nextPublish;
+
+ public event Action? DeleteButtonPressed;
+
+ public NewsWriterMenu(IGameTiming gameTiming)
+ {
+ RobustXamlLoader.Load(this);
+
+ _gameTiming = gameTiming;
+ ContentsContainer.RectClipContent = false;
+
+ // Customize scrollbar width and margin. This is not possible in xaml
+ var scrollbar = ArticleListScrollbar.GetChild(1);
+ scrollbar.SetWidth = 6f;
+ scrollbar.Margin = new Thickness(0, 0, 2 , 0);
+
+ ButtonCreate.OnPressed += OnCreate;
+ }
+
+ public void UpdateUI(NewsArticle[] articles, bool publishEnabled, TimeSpan nextPublish)
+ {
+ ArticlesContainer.Children.Clear();
+ ArticleCount.Text = Loc.GetString("news-write-ui-article-count-text", ("count", articles.Length));
+
+ //Iterate backwards to have the newest article at the top
+ for (var i = articles.Length - 1; i >= 0 ; i--)
+ {
+ var article = articles[i];
+ var control = new NewsArticleCard
+ {
+ Title = article.Title,
+ Author = article.Author ?? Loc.GetString("news-read-ui-no-author"),
+ PublicationTime = article.ShareTime,
+ ArtcileNumber = i
+ };
+ control.OnDeletePressed += () => DeleteButtonPressed?.Invoke(control.ArtcileNumber);
+
+ ArticlesContainer.AddChild(control);
+ }
+
+ ButtonCreate.Disabled = !publishEnabled;
+ _nextPublish = nextPublish;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+ if (!_nextPublish.HasValue)
+ return;
+
+ var remainingTime = _nextPublish.Value.Subtract(_gameTiming.CurTime);
+ if (remainingTime.TotalSeconds <= 0)
+ {
+ _nextPublish = null;
+ ButtonCreate.Text = Loc.GetString("news-write-ui-create-text");
+ return;
+ }
+
+ ButtonCreate.Text = remainingTime.Seconds.ToString("D2");
+ }
+
+ protected override void Resized()
+ {
+ base.Resized();
+ var margin = ArticleEditorPanel.Margin;
+ // Bandaid for the funny 1 pixel margin differences
+ ArticleEditorPanel.Margin = new Thickness(Width - 1, margin.Top, margin.Right, margin.Bottom);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ ButtonCreate.OnPressed -= OnCreate;
+ }
+
+ private void OnCreate(BaseButton.ButtonEventArgs buttonEventArgs)
+ {
+ ArticleEditorPanel.Visible = true;
+ }
+}
diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml b/Content.Client/Materials/UI/MaterialStorageControl.xaml
index e504434649..2be0f40aa5 100644
--- a/Content.Client/Materials/UI/MaterialStorageControl.xaml
+++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml
@@ -1,7 +1,8 @@
-
-
-
+
+
+
+
diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
index 3ef247d529..0237b86db7 100644
--- a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
+++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
@@ -11,7 +11,7 @@ namespace Content.Client.Materials.UI;
/// This widget is one row in the lathe eject menu.
///
[GenerateTypedNameReferences]
-public sealed partial class MaterialStorageControl : BoxContainer
+public sealed partial class MaterialStorageControl : ScrollContainer
{
[Dependency] private readonly IEntityManager _entityManager = default!;
@@ -63,7 +63,7 @@ protected override void FrameUpdate(FrameEventArgs args)
}
var children = new List();
- children.AddRange(Children.OfType());
+ children.AddRange(MaterialList.Children.OfType());
foreach (var display in children)
{
@@ -71,7 +71,7 @@ protected override void FrameUpdate(FrameEventArgs args)
if (extra.Contains(mat))
{
- RemoveChild(display);
+ MaterialList.RemoveChild(display);
continue;
}
@@ -83,10 +83,10 @@ protected override void FrameUpdate(FrameEventArgs args)
foreach (var mat in missing)
{
var volume = mats[mat];
- AddChild(new MaterialDisplay(_owner.Value, mat, volume, canEject));
+ MaterialList.AddChild(new MaterialDisplay(_owner.Value, mat, volume, canEject));
}
_currentMaterials = mats;
- NoMatsLabel.Visible = ChildCount == 1;
+ NoMatsLabel.Visible = MaterialList.ChildCount == 1;
}
}
diff --git a/Content.Client/Mech/Ui/MechMenu.xaml.cs b/Content.Client/Mech/Ui/MechMenu.xaml.cs
index 8d1d936031..fad7648808 100644
--- a/Content.Client/Mech/Ui/MechMenu.xaml.cs
+++ b/Content.Client/Mech/Ui/MechMenu.xaml.cs
@@ -35,9 +35,17 @@ public void UpdateMechStats()
IntegrityDisplayBar.Value = integrityPercent.Float();
IntegrityDisplay.Text = Loc.GetString("mech-integrity-display", ("amount", (integrityPercent*100).Int()));
- var energyPercent = mechComp.Energy / mechComp.MaxEnergy;
- EnergyDisplayBar.Value = energyPercent.Float();
- EnergyDisplay.Text = Loc.GetString("mech-energy-display", ("amount", (energyPercent*100).Int()));
+ if (mechComp.MaxEnergy != 0f)
+ {
+ var energyPercent = mechComp.Energy / mechComp.MaxEnergy;
+ EnergyDisplayBar.Value = energyPercent.Float();
+ EnergyDisplay.Text = Loc.GetString("mech-energy-display", ("amount", (energyPercent*100).Int()));
+ }
+ else
+ {
+ EnergyDisplayBar.Value = 0f;
+ EnergyDisplay.Text = Loc.GetString("mech-energy-missing");
+ }
SlotDisplay.Text = Loc.GetString("mech-slot-display",
("amount", mechComp.MaxEquipmentAmount - mechComp.EquipmentContainer.ContainedEntities.Count));
diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs
index fcecbad465..340cc9af89 100644
--- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs
+++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs
@@ -1,6 +1,7 @@
using Content.Client.Pinpointer.UI;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Timing;
namespace Content.Client.Medical.CrewMonitoring;
@@ -16,7 +17,7 @@ public CrewMonitoringNavMapControl() : base()
{
WallColor = new Color(192, 122, 196);
TileColor = new(71, 42, 72);
- _backgroundColor = Color.FromSrgb(TileColor.WithAlpha(_backgroundOpacity));
+ BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity));
_trackedEntityLabel = new Label
{
@@ -30,7 +31,7 @@ public CrewMonitoringNavMapControl() : base()
{
PanelOverride = new StyleBoxFlat
{
- BackgroundColor = _backgroundColor,
+ BackgroundColor = BackgroundColor,
},
Margin = new Thickness(5f, 10f),
@@ -43,9 +44,9 @@ public CrewMonitoringNavMapControl() : base()
this.AddChild(_trackedEntityPanel);
}
- protected override void Draw(DrawingHandleScreen handle)
+ protected override void FrameUpdate(FrameEventArgs args)
{
- base.Draw(handle);
+ base.FrameUpdate(args);
if (Focus == null)
{
diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs
index d8c87899db..863412e553 100644
--- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs
+++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs
@@ -23,7 +23,6 @@ namespace Content.Client.Medical.CrewMonitoring;
[GenerateTypedNameReferences]
public sealed partial class CrewMonitoringWindow : FancyWindow
{
- private List _rowsContent = new();
private readonly IEntityManager _entManager;
private readonly IPrototypeManager _prototypeManager;
private readonly SpriteSystem _spriteSystem;
@@ -100,7 +99,6 @@ public void ShowSensors(List sensors, EntityUid monitor, Entit
};
SensorsTable.AddChild(spacer);
- _rowsContent.Add(spacer);
}
var deparmentLabel = new RichTextLabel()
@@ -113,7 +111,6 @@ public void ShowSensors(List sensors, EntityUid monitor, Entit
deparmentLabel.StyleClasses.Add(StyleNano.StyleClassTooltipActionDescription);
SensorsTable.AddChild(deparmentLabel);
- _rowsContent.Add(deparmentLabel);
PopulateDepartmentList(departmentSensors);
}
@@ -129,7 +126,6 @@ public void ShowSensors(List sensors, EntityUid monitor, Entit
};
SensorsTable.AddChild(spacer);
- _rowsContent.Add(spacer);
var deparmentLabel = new RichTextLabel()
{
@@ -141,7 +137,6 @@ public void ShowSensors(List sensors, EntityUid monitor, Entit
deparmentLabel.StyleClasses.Add(StyleNano.StyleClassTooltipActionDescription);
SensorsTable.AddChild(deparmentLabel);
- _rowsContent.Add(deparmentLabel);
PopulateDepartmentList(remainingSensors);
}
@@ -175,7 +170,6 @@ private void PopulateDepartmentList(IEnumerable departmentSens
sensorButton.AddStyleClass(StyleNano.StyleClassButtonColorGreen);
SensorsTable.AddChild(sensorButton);
- _rowsContent.Add(sensorButton);
// Primary container to hold the button UI elements
var mainContainer = new BoxContainer()
@@ -216,9 +210,9 @@ private void PopulateDepartmentList(IEnumerable departmentSens
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "dead");
}
- else if (sensor.TotalDamage != null)
+ else if (sensor.DamagePercentage != null)
{
- var index = MathF.Round(4f * (sensor.TotalDamage.Value / 100f));
+ var index = MathF.Round(4f * sensor.DamagePercentage.Value);
if (index >= 5)
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical");
@@ -265,7 +259,7 @@ private void PopulateDepartmentList(IEnumerable departmentSens
var jobIcon = new TextureRect()
{
TextureScale = new Vector2(2f, 2f),
- Stretch = TextureRect.StretchMode.KeepCentered,
+ VerticalAlignment = VAlignment.Center,
Texture = _spriteSystem.Frame0(proto.Icon),
Margin = new Thickness(5, 0, 5, 0),
};
@@ -422,7 +416,6 @@ private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollP
private void ClearOutDatedData()
{
SensorsTable.RemoveAllChildren();
- _rowsContent.Clear();
NavMap.TrackedCoordinates.Clear();
NavMap.TrackedEntities.Clear();
NavMap.LocalizedNames.Clear();
diff --git a/Content.Client/Message/RichTextLabelExt.cs b/Content.Client/Message/RichTextLabelExt.cs
index ab6d17bf44..7ff6390764 100644
--- a/Content.Client/Message/RichTextLabelExt.cs
+++ b/Content.Client/Message/RichTextLabelExt.cs
@@ -5,9 +5,27 @@ namespace Content.Client.Message;
public static class RichTextLabelExt
{
+
+
+ ///
+ /// Sets the labels markup.
+ ///
+ ///
+ /// Invalid markup will cause exceptions to be thrown. Don't use this for user input!
+ ///
public static RichTextLabel SetMarkup(this RichTextLabel label, string markup)
{
label.SetMessage(FormattedMessage.FromMarkup(markup));
return label;
}
+
+ ///
+ /// Sets the labels markup.
+ /// Uses FormatedMessage.FromMarkupPermissive which treats invalid markup as text.
+ ///
+ public static RichTextLabel SetMarkupPermissive(this RichTextLabel label, string markup)
+ {
+ label.SetMessage(FormattedMessage.FromMarkupPermissive(markup));
+ return label;
+ }
}
diff --git a/Content.Client/MouseRotator/MouseRotatorSystem.cs b/Content.Client/MouseRotator/MouseRotatorSystem.cs
index 44e8205355..ce174c6144 100644
--- a/Content.Client/MouseRotator/MouseRotatorSystem.cs
+++ b/Content.Client/MouseRotator/MouseRotatorSystem.cs
@@ -2,6 +2,7 @@
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
+using Robust.Client.Replays.Loading;
using Robust.Shared.Map;
using Robust.Shared.Timing;
@@ -37,7 +38,7 @@ public override void Update(float frameTime)
if (mapPos.MapId == MapId.Nullspace)
return;
- var angle = (mapPos.Position - xform.MapPosition.Position).ToWorldAngle();
+ var angle = (mapPos.Position - _transform.GetMapCoordinates(player.Value, xform: xform).Position).ToWorldAngle();
var curRot = _transform.GetWorldRotation(xform);
@@ -49,7 +50,7 @@ public override void Update(float frameTime)
if (angleDir == curRot.GetCardinalDir())
return;
- RaisePredictiveEvent(new RequestMouseRotatorRotationSimpleEvent()
+ RaisePredictiveEvent(new RequestMouseRotatorRotationSimpleEvent()
{
Direction = angleDir,
});
diff --git a/Content.Client/Movement/Systems/FloorOcclusionSystem.cs b/Content.Client/Movement/Systems/FloorOcclusionSystem.cs
index 27ba99b3aa..44573f8e08 100644
--- a/Content.Client/Movement/Systems/FloorOcclusionSystem.cs
+++ b/Content.Client/Movement/Systems/FloorOcclusionSystem.cs
@@ -10,46 +10,56 @@ public sealed class FloorOcclusionSystem : SharedFloorOcclusionSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
+ private EntityQuery _spriteQuery;
+
public override void Initialize()
{
base.Initialize();
+
+ _spriteQuery = GetEntityQuery();
+
SubscribeLocalEvent(OnOcclusionStartup);
+ SubscribeLocalEvent(OnOcclusionShutdown);
SubscribeLocalEvent(OnOcclusionAuto);
}
- private void OnOcclusionAuto(EntityUid uid, FloorOcclusionComponent component, ref AfterAutoHandleStateEvent args)
+ private void OnOcclusionAuto(Entity ent, ref AfterAutoHandleStateEvent args)
{
- SetEnabled(uid, component, component.Enabled);
+ SetShader(ent.Owner, ent.Comp.Enabled);
}
- private void OnOcclusionStartup(EntityUid uid, FloorOcclusionComponent component, ComponentStartup args)
+ private void OnOcclusionStartup(Entity ent, ref ComponentStartup args)
{
- if (component.Enabled && TryComp(uid, out var sprite))
- SetShader(sprite, true);
+ SetShader(ent.Owner, ent.Comp.Enabled);
}
- protected override void SetEnabled(EntityUid uid, FloorOcclusionComponent component, bool enabled)
+ private void OnOcclusionShutdown(Entity ent, ref ComponentShutdown args)
{
- if (component.Enabled == enabled)
- return;
+ SetShader(ent.Owner, false);
+ }
- base.SetEnabled(uid, component, enabled);
+ protected override void SetEnabled(Entity entity)
+ {
+ SetShader(entity.Owner, entity.Comp.Enabled);
+ }
- if (!TryComp(uid, out var sprite))
+ private void SetShader(Entity sprite, bool enabled)
+ {
+ if (!_spriteQuery.Resolve(sprite.Owner, ref sprite.Comp, false))
return;
- SetShader(sprite, enabled);
- }
+ var shader = _proto.Index("HorizontalCut").Instance();
+
+ if (sprite.Comp.PostShader is not null && sprite.Comp.PostShader != shader)
+ return;
- private void SetShader(SpriteComponent sprite, bool enabled)
- {
if (enabled)
{
- sprite.PostShader = _proto.Index("HorizontalCut").Instance();
+ sprite.Comp.PostShader = shader;
}
else
{
- sprite.PostShader = null;
+ sprite.Comp.PostShader = null;
}
}
}
diff --git a/Content.Client/Movement/Systems/JetpackSystem.cs b/Content.Client/Movement/Systems/JetpackSystem.cs
index f0836ee9b6..b7f5e48821 100644
--- a/Content.Client/Movement/Systems/JetpackSystem.cs
+++ b/Content.Client/Movement/Systems/JetpackSystem.cs
@@ -4,6 +4,7 @@
using Content.Shared.Movement.Systems;
using Robust.Client.GameObjects;
using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
@@ -12,7 +13,6 @@ namespace Content.Client.Movement.Systems;
public sealed class JetpackSystem : SharedJetpackSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ClothingSystem _clothing = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
@@ -75,7 +75,7 @@ private void CreateParticles(EntityUid uid)
var coordinates = uidXform.Coordinates;
var gridUid = coordinates.GetGridUid(EntityManager);
- if (_mapManager.TryGetGrid(gridUid, out var grid))
+ if (TryComp(gridUid, out var grid))
{
coordinates = new EntityCoordinates(gridUid.Value, grid.WorldToLocal(coordinates.ToMapPos(EntityManager, _transform)));
}
diff --git a/Content.Client/Movement/Systems/WaddleAnimationSystem.cs b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs
new file mode 100644
index 0000000000..0ed2d04f69
--- /dev/null
+++ b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs
@@ -0,0 +1,149 @@
+using System.Numerics;
+using Content.Client.Buckle;
+using Content.Client.Gravity;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Systems;
+using Robust.Client.Animations;
+using Robust.Client.GameObjects;
+using Robust.Shared.Animations;
+
+namespace Content.Client.Movement.Systems;
+
+public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem
+{
+ [Dependency] private readonly AnimationPlayerSystem _animation = default!;
+ [Dependency] private readonly GravitySystem _gravity = default!;
+ [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
+ [Dependency] private readonly BuckleSystem _buckle = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeAllEvent(OnStartWaddling);
+ SubscribeLocalEvent(OnAnimationCompleted);
+ SubscribeAllEvent(OnStopWaddling);
+ }
+
+ private void OnStartWaddling(StartedWaddlingEvent msg, EntitySessionEventArgs args)
+ {
+ if (TryComp(GetEntity(msg.Entity), out var comp))
+ StartWaddling((GetEntity(msg.Entity), comp));
+ }
+
+ private void OnStopWaddling(StoppedWaddlingEvent msg, EntitySessionEventArgs args)
+ {
+ if (TryComp(GetEntity(msg.Entity), out var comp))
+ StopWaddling((GetEntity(msg.Entity), comp));
+ }
+
+ private void StartWaddling(Entity entity)
+ {
+ if (_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
+ return;
+
+ if (!TryComp(entity.Owner, out var mover))
+ return;
+
+ if (_gravity.IsWeightless(entity.Owner))
+ return;
+
+ if (!_actionBlocker.CanMove(entity.Owner, mover))
+ return;
+
+ // Do nothing if buckled in
+ if (_buckle.IsBuckled(entity.Owner))
+ return;
+
+ // Do nothing if crit or dead (for obvious reasons)
+ if (_mobState.IsIncapacitated(entity.Owner))
+ return;
+
+ PlayWaddleAnimationUsing(
+ (entity.Owner, entity.Comp),
+ CalculateAnimationLength(entity.Comp, mover),
+ CalculateTumbleIntensity(entity.Comp)
+ );
+ }
+
+ private static float CalculateTumbleIntensity(WaddleAnimationComponent component)
+ {
+ return component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
+ }
+
+ private static float CalculateAnimationLength(WaddleAnimationComponent component, InputMoverComponent mover)
+ {
+ return mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
+ }
+
+ private void OnAnimationCompleted(Entity entity, ref AnimationCompletedEvent args)
+ {
+ if (args.Key != entity.Comp.KeyName)
+ return;
+
+ if (!TryComp(entity.Owner, out var mover))
+ return;
+
+ PlayWaddleAnimationUsing(
+ (entity.Owner, entity.Comp),
+ CalculateAnimationLength(entity.Comp, mover),
+ CalculateTumbleIntensity(entity.Comp)
+ );
+ }
+
+ private void StopWaddling(Entity entity)
+ {
+ if (!_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
+ return;
+
+ _animation.Stop(entity.Owner, entity.Comp.KeyName);
+
+ if (!TryComp(entity.Owner, out var sprite))
+ return;
+
+ sprite.Offset = new Vector2();
+ sprite.Rotation = Angle.FromDegrees(0);
+ }
+
+ private void PlayWaddleAnimationUsing(Entity entity, float len, float tumbleIntensity)
+ {
+ entity.Comp.LastStep = !entity.Comp.LastStep;
+
+ var anim = new Animation()
+ {
+ Length = TimeSpan.FromSeconds(len),
+ AnimationTracks =
+ {
+ new AnimationTrackComponentProperty()
+ {
+ ComponentType = typeof(SpriteComponent),
+ Property = nameof(SpriteComponent.Rotation),
+ InterpolationMode = AnimationInterpolationMode.Linear,
+ KeyFrames =
+ {
+ new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
+ new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
+ new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
+ }
+ },
+ new AnimationTrackComponentProperty()
+ {
+ ComponentType = typeof(SpriteComponent),
+ Property = nameof(SpriteComponent.Offset),
+ InterpolationMode = AnimationInterpolationMode.Linear,
+ KeyFrames =
+ {
+ new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
+ new AnimationTrackProperty.KeyFrame(entity.Comp.HopIntensity, len/2),
+ new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
+ }
+ }
+ }
+ };
+
+ _animation.Play(entity.Owner, anim, entity.Comp.KeyName);
+ }
+}
diff --git a/Content.Client/NPC/PathfindingSystem.cs b/Content.Client/NPC/PathfindingSystem.cs
index 7e332c5c6a..d8451a125a 100644
--- a/Content.Client/NPC/PathfindingSystem.cs
+++ b/Content.Client/NPC/PathfindingSystem.cs
@@ -23,6 +23,7 @@ public sealed class PathfindingSystem : SharedPathfindingSystem
[Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly NPCSteeringSystem _steering = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
+ [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
public PathfindingDebugMode Modes
{
@@ -39,7 +40,7 @@ public PathfindingDebugMode Modes
}
else if (!overlayManager.HasOverlay())
{
- overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this, _mapSystem));
+ overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this, _mapSystem, _transformSystem));
}
if ((value & PathfindingDebugMode.Steering) != 0x0)
@@ -140,6 +141,7 @@ public sealed class PathfindingOverlay : Overlay
private readonly IMapManager _mapManager;
private readonly PathfindingSystem _system;
private readonly MapSystem _mapSystem;
+ private readonly SharedTransformSystem _transformSystem;
public override OverlaySpace Space => OverlaySpace.ScreenSpace | OverlaySpace.WorldSpace;
@@ -153,7 +155,8 @@ public PathfindingOverlay(
IMapManager mapManager,
IResourceCache cache,
PathfindingSystem system,
- MapSystem mapSystem)
+ MapSystem mapSystem,
+ SharedTransformSystem transformSystem)
{
_entManager = entManager;
_eyeManager = eyeManager;
@@ -161,6 +164,7 @@ public PathfindingOverlay(
_mapManager = mapManager;
_system = system;
_mapSystem = mapSystem;
+ _transformSystem = transformSystem;
_font = new VectorFont(cache.GetResource("/Fonts/B612_Mono/B612_Mono-Regular.ttf"), 10);
}
@@ -219,7 +223,7 @@ private void DrawScreen(OverlayDrawArgs args, DrawingHandleScreen screenHandle)
foreach (var crumb in chunk.Value)
{
- var crumbMapPos = worldMatrix.Transform(_system.GetCoordinate(chunk.Key, crumb.Coordinates));
+ var crumbMapPos = Vector2.Transform(_system.GetCoordinate(chunk.Key, crumb.Coordinates), worldMatrix);
var distance = (crumbMapPos - mouseWorldPos.Position).Length();
if (distance < nearestDistance)
@@ -285,11 +289,10 @@ private void DrawScreen(OverlayDrawArgs args, DrawingHandleScreen screenHandle)
var invGridMatrix = gridXform.InvWorldMatrix;
DebugPathPoly? nearest = null;
- var nearestDistance = float.MaxValue;
foreach (var poly in tile)
{
- if (poly.Box.Contains(invGridMatrix.Transform(mouseWorldPos.Position)))
+ if (poly.Box.Contains(Vector2.Transform(mouseWorldPos.Position, invGridMatrix)))
{
nearest = poly;
break;
@@ -480,12 +483,12 @@ private void DrawWorld(OverlayDrawArgs args, DrawingHandleWorld worldHandle)
if (neighborPoly.NetEntity != poly.GraphUid)
{
color = Color.Green;
- var neighborMap = _entManager.GetCoordinates(neighborPoly).ToMap(_entManager);
+ var neighborMap = _entManager.GetCoordinates(neighborPoly).ToMap(_entManager, _transformSystem);
if (neighborMap.MapId != args.MapId)
continue;
- neighborPos = invMatrix.Transform(neighborMap.Position);
+ neighborPos = Vector2.Transform(neighborMap.Position, invMatrix);
}
else
{
@@ -573,7 +576,7 @@ private void DrawWorld(OverlayDrawArgs args, DrawingHandleWorld worldHandle)
}
}
- worldHandle.SetTransform(Matrix3.Identity);
+ worldHandle.SetTransform(Matrix3x2.Identity);
}
}
}
diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs b/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs
index 264c297b63..80c98f143b 100644
--- a/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs
+++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs
@@ -88,6 +88,7 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
if (!disposing) return;
+ _linkMenu?.Dispose();
_listMenu?.Dispose();
_configurationMenu?.Dispose();
}
diff --git a/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs b/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs
index 2fd4f29655..2d561ba5f2 100644
--- a/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs
+++ b/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs
@@ -139,11 +139,13 @@ protected override void FrameUpdate(FrameEventArgs args)
public sealed class ClearAllNetworkLinkOverlays : IConsoleCommand
{
+ [Dependency] private readonly IEntityManager _e = default!;
+
public string Command => "clearnetworklinkoverlays";
public string Description => "Clear all network link overlays.";
public string Help => Command;
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
- IoCManager.Resolve().System().ClearAllOverlays();
+ _e.System().ClearAllOverlays();
}
}
diff --git a/Content.Client/NodeContainer/NodeVisCommand.cs b/Content.Client/NodeContainer/NodeVisCommand.cs
index c6a95fce5b..249c0d9427 100644
--- a/Content.Client/NodeContainer/NodeVisCommand.cs
+++ b/Content.Client/NodeContainer/NodeVisCommand.cs
@@ -8,6 +8,8 @@ namespace Content.Client.NodeContainer
{
public sealed class NodeVisCommand : IConsoleCommand
{
+ [Dependency] private readonly IEntityManager _e = default!;
+
public string Command => "nodevis";
public string Description => "Toggles node group visualization";
public string Help => "";
@@ -21,20 +23,22 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
return;
}
- var sys = EntitySystem.Get();
+ var sys = _e.System();
sys.SetVisEnabled(!sys.VisEnabled);
}
}
public sealed class NodeVisFilterCommand : IConsoleCommand
{
+ [Dependency] private readonly IEntityManager _e = default!;
+
public string Command => "nodevisfilter";
public string Description => "Toggles showing a specific group on nodevis";
public string Help => "Usage: nodevis [filter]\nOmit filter to list currently masked-off";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
- var sys = EntitySystem.Get();
+ var sys = _e.System();
if (args.Length == 0)
{
diff --git a/Content.Client/NodeContainer/NodeVisualizationOverlay.cs b/Content.Client/NodeContainer/NodeVisualizationOverlay.cs
index 70d91ad2c1..b1fea5a90f 100644
--- a/Content.Client/NodeContainer/NodeVisualizationOverlay.cs
+++ b/Content.Client/NodeContainer/NodeVisualizationOverlay.cs
@@ -80,7 +80,7 @@ private void DrawScreen(in OverlayDrawArgs args)
var xform = _entityManager.GetComponent(_entityManager.GetEntity(node.Entity));
- if (!_mapManager.TryGetGrid(xform.GridUid, out var grid))
+ if (!_entityManager.TryGetComponent(xform.GridUid, out var grid))
return;
var gridTile = grid.TileIndicesFor(xform.Coordinates);
@@ -145,7 +145,7 @@ private void DrawWorld(in OverlayDrawArgs overlayDrawArgs)
foreach (var (gridId, gridDict) in _gridIndex)
{
- var grid = _mapManager.GetGrid(gridId);
+ var grid = _entityManager.GetComponent(gridId);
var (_, _, worldMatrix, invMatrix) = _entityManager.GetComponent(gridId).GetWorldPositionRotationMatrixWithInv();
var lCursorBox = invMatrix.TransformBox(cursorBox);
@@ -199,7 +199,7 @@ private void DrawWorld(in OverlayDrawArgs overlayDrawArgs)
}
- handle.SetTransform(Matrix3.Identity);
+ handle.SetTransform(Matrix3x2.Identity);
_gridIndex.Clear();
}
diff --git a/Content.Client/NukeOps/WarDeclaratorBoundUserInterface.cs b/Content.Client/NukeOps/WarDeclaratorBoundUserInterface.cs
index 20103a9743..ec055b3240 100644
--- a/Content.Client/NukeOps/WarDeclaratorBoundUserInterface.cs
+++ b/Content.Client/NukeOps/WarDeclaratorBoundUserInterface.cs
@@ -3,6 +3,7 @@
using Content.Shared.NukeOps;
using JetBrains.Annotations;
using Robust.Shared.Configuration;
+using Robust.Shared.Timing;
namespace Content.Client.NukeOps;
@@ -10,6 +11,8 @@ namespace Content.Client.NukeOps;
public sealed class WarDeclaratorBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly ILocalizationManager _localizationManager = default!;
[ViewVariables]
private WarDeclaratorWindow? _window;
@@ -20,7 +23,7 @@ protected override void Open()
{
base.Open();
- _window = new WarDeclaratorWindow();
+ _window = new WarDeclaratorWindow(_gameTiming, _localizationManager);
if (State != null)
UpdateState(State);
@@ -42,7 +45,8 @@ protected override void UpdateState(BoundUserInterfaceState state)
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
- if (disposing) _window?.Dispose();
+ if (disposing)
+ _window?.Dispose();
}
private void OnWarDeclaratorActivated(string message)
diff --git a/Content.Client/NukeOps/WarDeclaratorWindow.xaml b/Content.Client/NukeOps/WarDeclaratorWindow.xaml
index f90ed865a0..43381158a2 100644
--- a/Content.Client/NukeOps/WarDeclaratorWindow.xaml
+++ b/Content.Client/NukeOps/WarDeclaratorWindow.xaml
@@ -1,4 +1,5 @@
-
@@ -7,12 +8,16 @@
MinHeight="200"
Access="Public" />
-
-
+
+
+
+
-
+
diff --git a/Content.Client/NukeOps/WarDeclaratorWindow.xaml.cs b/Content.Client/NukeOps/WarDeclaratorWindow.xaml.cs
index 104e776daa..b4a3f1c7fa 100644
--- a/Content.Client/NukeOps/WarDeclaratorWindow.xaml.cs
+++ b/Content.Client/NukeOps/WarDeclaratorWindow.xaml.cs
@@ -1,8 +1,7 @@
using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
using Content.Shared.NukeOps;
using Robust.Client.AutoGenerated;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -10,74 +9,83 @@
namespace Content.Client.NukeOps;
[GenerateTypedNameReferences]
-public sealed partial class WarDeclaratorWindow : DefaultWindow
+public sealed partial class WarDeclaratorWindow : FancyWindow
{
private readonly IGameTiming _gameTiming;
public event Action? OnActivated;
private TimeSpan _endTime;
- private TimeSpan _timeStamp;
+ private TimeSpan _shuttleDisabledTime;
private WarConditionStatus _status;
- public WarDeclaratorWindow()
+ public WarDeclaratorWindow(IGameTiming gameTiming, ILocalizationManager localizationManager)
{
RobustXamlLoader.Load(this);
- _gameTiming = IoCManager.Resolve();
+ _gameTiming = gameTiming;
WarButton.OnPressed += (_) => OnActivated?.Invoke(Rope.Collapse(MessageEdit.TextRope));
- var loc = IoCManager.Resolve();
- MessageEdit.Placeholder = new Rope.Leaf(loc.GetString("war-declarator-message-placeholder"));
+ MessageEdit.Placeholder = new Rope.Leaf(localizationManager.GetString("war-declarator-message-placeholder"));
}
- protected override void Draw(DrawingHandleScreen handle)
+ protected override void FrameUpdate(FrameEventArgs args)
{
- base.Draw(handle);
UpdateTimer();
}
public void UpdateState(WarDeclaratorBoundUserInterfaceState state)
{
- WarButton.Disabled = state.Status != WarConditionStatus.YES_WAR;
+ if (state.Status == null)
+ return;
+
+ WarButton.Disabled = state.Status == WarConditionStatus.WarReady;
- _timeStamp = state.Delay;
_endTime = state.EndTime;
- _status = state.Status;
+ _shuttleDisabledTime = state.ShuttleDisabledTime;
+ _status = state.Status.Value;
+
+ UpdateStatus(state.Status.Value);
+
+ }
- switch(state.Status)
+ private void UpdateStatus(WarConditionStatus status)
+ {
+ switch (status)
{
- case WarConditionStatus.WAR_READY:
+ case WarConditionStatus.WarReady:
+ WarButton.Disabled = true;
StatusLabel.Text = Loc.GetString("war-declarator-boost-declared");
- InfoLabel.Text = Loc.GetString("war-declarator-conditions-ready");
- StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateLow);
- break;
- case WarConditionStatus.WAR_DELAY:
- StatusLabel.Text = Loc.GetString("war-declarator-boost-declared-delay");
UpdateTimer();
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateLow);
break;
- case WarConditionStatus.YES_WAR:
+ case WarConditionStatus.YesWar:
+ WarButton.Text = Loc.GetString("war-declarator-ui-war-button");
StatusLabel.Text = Loc.GetString("war-declarator-boost-possible");
UpdateTimer();
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
break;
- case WarConditionStatus.NO_WAR_SMALL_CREW:
+ case WarConditionStatus.NoWarSmallCrew:
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
- InfoLabel.Text = Loc.GetString("war-declarator-conditions-small-crew", ("min", state.MinCrew));
+ InfoLabel.Text = Loc.GetString("war-declarator-conditions-small-crew");
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
break;
- case WarConditionStatus.NO_WAR_SHUTTLE_DEPARTED:
+ case WarConditionStatus.NoWarShuttleDeparted:
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
InfoLabel.Text = Loc.GetString("war-declarator-conditions-left-outpost");
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
break;
- case WarConditionStatus.NO_WAR_TIMEOUT:
+ case WarConditionStatus.NoWarTimeout:
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
InfoLabel.Text = Loc.GetString("war-declarator-conditions-time-out");
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
break;
+ case WarConditionStatus.NoWarUnknown:
+ StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
+ InfoLabel.Text = Loc.GetString("war-declarator-conditions-unknown");
+ StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
+ break;
default:
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
InfoLabel.Text = Loc.GetString("war-declarator-conditions-unknown");
@@ -86,43 +94,24 @@ public void UpdateState(WarDeclaratorBoundUserInterfaceState state)
}
}
- public void UpdateTimer()
+ private void UpdateTimer()
{
switch(_status)
{
- case WarConditionStatus.YES_WAR:
- var gameruleTime = _gameTiming.CurTime.Subtract(_timeStamp);
- var timeLeft = _endTime.Subtract(gameruleTime);
-
+ case WarConditionStatus.YesWar:
+ var timeLeft = _endTime.Subtract(_gameTiming.CurTime);
if (timeLeft > TimeSpan.Zero)
- {
- InfoLabel.Text = Loc.GetString("war-declarator-boost-timer", ("minutes", timeLeft.Minutes), ("seconds", timeLeft.Seconds));
- }
+ InfoLabel.Text = Loc.GetString("war-declarator-boost-timer", ("time", timeLeft.ToString("mm\\:ss")));
else
- {
- _status = WarConditionStatus.NO_WAR_TIMEOUT;
- StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
- InfoLabel.Text = Loc.GetString("war-declarator-conditions-time-out");
- StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
- WarButton.Disabled = true;
- }
+ UpdateStatus(WarConditionStatus.NoWarTimeout);
break;
- case WarConditionStatus.WAR_DELAY:
- var timeAfterDeclaration = _gameTiming.CurTime.Subtract(_timeStamp);
- var timeRemain = _endTime.Subtract(timeAfterDeclaration);
-
- if (timeRemain > TimeSpan.Zero)
- {
- InfoLabel.Text = Loc.GetString("war-declarator-boost-timer", ("minutes", timeRemain.Minutes), ("seconds", timeRemain.Seconds));
- }
+
+ case WarConditionStatus.WarReady:
+ var time = _shuttleDisabledTime.Subtract(_gameTiming.CurTime);
+ if (time > TimeSpan.Zero)
+ InfoLabel.Text = Loc.GetString("war-declarator-boost-timer", ("time", time.ToString("mm\\:ss")));
else
- {
- _status = WarConditionStatus.WAR_READY;
- StatusLabel.Text = Loc.GetString("war-declarator-boost-declared");
InfoLabel.Text = Loc.GetString("war-declarator-conditions-ready");
- StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateLow);
- WarButton.Disabled = true;
- }
break;
default:
return;
diff --git a/Content.Client/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Client/Nutrition/EntitySystems/DrinkSystem.cs
new file mode 100644
index 0000000000..16dbecb793
--- /dev/null
+++ b/Content.Client/Nutrition/EntitySystems/DrinkSystem.cs
@@ -0,0 +1,7 @@
+using Content.Shared.Nutrition.EntitySystems;
+
+namespace Content.Client.Nutrition.EntitySystems;
+
+public sealed class DrinkSystem : SharedDrinkSystem
+{
+}
diff --git a/Content.Client/Options/UI/OptionsMenu.xaml b/Content.Client/Options/UI/OptionsMenu.xaml
index ab3b88ca4e..4f624c1bb6 100644
--- a/Content.Client/Options/UI/OptionsMenu.xaml
+++ b/Content.Client/Options/UI/OptionsMenu.xaml
@@ -7,6 +7,5 @@
-
diff --git a/Content.Client/Options/UI/OptionsMenu.xaml.cs b/Content.Client/Options/UI/OptionsMenu.xaml.cs
index c3a8e66470..35a3f751bb 100644
--- a/Content.Client/Options/UI/OptionsMenu.xaml.cs
+++ b/Content.Client/Options/UI/OptionsMenu.xaml.cs
@@ -19,7 +19,6 @@ public OptionsMenu()
Tabs.SetTabTitle(1, Loc.GetString("ui-options-tab-graphics"));
Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls"));
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
- Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-network"));
UpdateTabs();
}
diff --git a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml
index 118b85b87b..ec1b9aa002 100644
--- a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml
+++ b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml
@@ -36,6 +36,9 @@
+
diff --git a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs
index 3113e644ba..a22adf3e63 100644
--- a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs
@@ -67,6 +67,12 @@ public GraphicsTab()
UpdateApplyButton();
};
+ ViewportVerticalFitCheckBox.OnToggled += _ =>
+ {
+ UpdateViewportScale();
+ UpdateApplyButton();
+ };
+
IntegerScalingCheckBox.OnToggled += OnCheckBoxToggled;
ViewportLowResCheckBox.OnToggled += OnCheckBoxToggled;
ParallaxLowQualityCheckBox.OnToggled += OnCheckBoxToggled;
@@ -79,6 +85,7 @@ public GraphicsTab()
ViewportScaleSlider.Value = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
ViewportStretchCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportStretch);
IntegerScalingCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0;
+ ViewportVerticalFitCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportVerticalFit);
ViewportLowResCheckBox.Pressed = !_cfg.GetCVar(CCVars.ViewportScaleRender);
ParallaxLowQualityCheckBox.Pressed = _cfg.GetCVar(CCVars.ParallaxLowQuality);
FpsCounterCheckBox.Pressed = _cfg.GetCVar(CCVars.HudFpsCounterVisible);
@@ -111,6 +118,7 @@ private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
_cfg.SetCVar(CCVars.ViewportFixedScaleFactor, (int) ViewportScaleSlider.Value);
_cfg.SetCVar(CCVars.ViewportSnapToleranceMargin,
IntegerScalingCheckBox.Pressed ? CCVars.ViewportSnapToleranceMargin.DefaultValue : 0);
+ _cfg.SetCVar(CCVars.ViewportVerticalFit, ViewportVerticalFitCheckBox.Pressed);
_cfg.SetCVar(CCVars.ViewportScaleRender, !ViewportLowResCheckBox.Pressed);
_cfg.SetCVar(CCVars.ParallaxLowQuality, ParallaxLowQualityCheckBox.Pressed);
_cfg.SetCVar(CCVars.HudFpsCounterVisible, FpsCounterCheckBox.Pressed);
@@ -140,6 +148,7 @@ private void UpdateApplyButton()
var isVPStretchSame = ViewportStretchCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportStretch);
var isVPScaleSame = (int) ViewportScaleSlider.Value == _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
var isIntegerScalingSame = IntegerScalingCheckBox.Pressed == (_cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0);
+ var isVPVerticalFitSame = ViewportVerticalFitCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportVerticalFit);
var isVPResSame = ViewportLowResCheckBox.Pressed == !_cfg.GetCVar(CCVars.ViewportScaleRender);
var isPLQSame = ParallaxLowQualityCheckBox.Pressed == _cfg.GetCVar(CCVars.ParallaxLowQuality);
var isFpsCounterVisibleSame = FpsCounterCheckBox.Pressed == _cfg.GetCVar(CCVars.HudFpsCounterVisible);
@@ -152,6 +161,7 @@ private void UpdateApplyButton()
isVPStretchSame &&
isVPScaleSame &&
isIntegerScalingSame &&
+ isVPVerticalFitSame &&
isVPResSame &&
isPLQSame &&
isFpsCounterVisibleSame &&
@@ -235,6 +245,8 @@ private void UpdateViewportScale()
{
ViewportScaleBox.Visible = !ViewportStretchCheckBox.Pressed;
IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed;
+ ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed;
+ ViewportWidthSlider.Visible = ViewportWidthSliderDisplay.Visible = !ViewportStretchCheckBox.Pressed || ViewportStretchCheckBox.Pressed && !ViewportVerticalFitCheckBox.Pressed;
ViewportScaleText.Text = Loc.GetString("ui-options-vp-scale", ("scale", ViewportScaleSlider.Value));
}
diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
index ce5cf421ae..6fa416ed59 100644
--- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
@@ -183,6 +183,7 @@ void AddCheckBox(string checkBoxName, bool currentState, Action
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
diff --git a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
index d7a600dd79..13e3fd05f5 100644
--- a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
@@ -3,11 +3,14 @@
using Content.Shared.CCVar;
using Content.Shared.HUD;
using Robust.Client.AutoGenerated;
+using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared;
using Robust.Shared.Configuration;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Range = Robust.Client.UserInterface.Controls.Range;
@@ -16,6 +19,7 @@ namespace Content.Client.Options.UI.Tabs
[GenerateTypedNameReferences]
public sealed partial class MiscTab : Control
{
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -55,8 +59,13 @@ public MiscTab()
UpdateApplyButton();
};
+ // Channel can be null in replays so.
+ // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
+ ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
+
HudThemeOption.OnItemSelected += OnHudThemeChanged;
DiscordRich.OnToggled += OnCheckBoxToggled;
+ ShowOocPatronColor.OnToggled += OnCheckBoxToggled;
ShowLoocAboveHeadCheckBox.OnToggled += OnCheckBoxToggled;
ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled;
ShowCombatModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
@@ -64,13 +73,16 @@ public MiscTab()
FancySpeechBubblesCheckBox.OnToggled += OnCheckBoxToggled;
FancyNameBackgroundsCheckBox.OnToggled += OnCheckBoxToggled;
EnableColorNameCheckBox.OnToggled += OnCheckBoxToggled;
+ ColorblindFriendlyCheckBox.OnToggled += OnCheckBoxToggled;
ReducedMotionCheckBox.OnToggled += OnCheckBoxToggled;
+ ChatWindowOpacitySlider.OnValueChanged += OnChatWindowOpacitySliderChanged;
ScreenShakeIntensitySlider.OnValueChanged += OnScreenShakeIntensitySliderChanged;
// ToggleWalk.OnToggled += OnCheckBoxToggled;
StaticStorageUI.OnToggled += OnCheckBoxToggled;
HudThemeOption.SelectId(_hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0));
DiscordRich.Pressed = _cfg.GetCVar(CVars.DiscordEnabled);
+ ShowOocPatronColor.Pressed = _cfg.GetCVar(CCVars.ShowOocPatronColor);
ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow);
ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow);
ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
@@ -78,7 +90,9 @@ public MiscTab()
FancySpeechBubblesCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
FancyNameBackgroundsCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatFancyNameBackground);
EnableColorNameCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableColorName);
+ ColorblindFriendlyCheckBox.Pressed = _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
ReducedMotionCheckBox.Pressed = _cfg.GetCVar(CCVars.ReducedMotion);
+ ChatWindowOpacitySlider.Value = _cfg.GetCVar(CCVars.ChatWindowOpacity);
ScreenShakeIntensitySlider.Value = _cfg.GetCVar(CCVars.ScreenShakeIntensity) * 100f;
// ToggleWalk.Pressed = _cfg.GetCVar(CCVars.ToggleWalk);
StaticStorageUI.Pressed = _cfg.GetCVar(CCVars.StaticStorageUI);
@@ -99,6 +113,13 @@ private void OnHudThemeChanged(OptionButton.ItemSelectedEventArgs args)
UpdateApplyButton();
}
+ private void OnChatWindowOpacitySliderChanged(Range range)
+ {
+ ChatWindowOpacityLabel.Text = Loc.GetString("ui-options-chat-window-opacity-percent",
+ ("opacity", range.Value));
+ UpdateApplyButton();
+ }
+
private void OnScreenShakeIntensitySliderChanged(Range obj)
{
ScreenShakeIntensityLabel.Text = Loc.GetString("ui-options-screen-shake-percent", ("intensity", ScreenShakeIntensitySlider.Value / 100f));
@@ -119,11 +140,14 @@ private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
_cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed);
_cfg.SetCVar(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox.Pressed);
_cfg.SetCVar(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox.Pressed);
+ _cfg.SetCVar(CCVars.ShowOocPatronColor, ShowOocPatronColor.Pressed);
_cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
_cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
_cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
_cfg.SetCVar(CCVars.ChatEnableColorName, EnableColorNameCheckBox.Pressed);
+ _cfg.SetCVar(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox.Pressed);
_cfg.SetCVar(CCVars.ReducedMotion, ReducedMotionCheckBox.Pressed);
+ _cfg.SetCVar(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider.Value);
_cfg.SetCVar(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider.Value / 100f);
// _cfg.SetCVar(CCVars.ToggleWalk, ToggleWalk.Pressed);
_cfg.SetCVar(CCVars.StaticStorageUI, StaticStorageUI.Pressed);
@@ -145,11 +169,14 @@ private void UpdateApplyButton()
var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow);
var isCombatModeIndicatorsSame = ShowCombatModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
var isOpaqueStorageWindow = OpaqueStorageWindowCheckBox.Pressed == _cfg.GetCVar(CCVars.OpaqueStorageWindow);
+ var isOocPatronColorShowSame = ShowOocPatronColor.Pressed == _cfg.GetCVar(CCVars.ShowOocPatronColor);
var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
var isEnableColorNameSame = EnableColorNameCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableColorName);
+ var isColorblindFriendly = ColorblindFriendlyCheckBox.Pressed == _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
var isReducedMotionSame = ReducedMotionCheckBox.Pressed == _cfg.GetCVar(CCVars.ReducedMotion);
+ var isChatWindowOpacitySame = Math.Abs(ChatWindowOpacitySlider.Value - _cfg.GetCVar(CCVars.ChatWindowOpacity)) < 0.01f;
var isScreenShakeIntensitySame = Math.Abs(ScreenShakeIntensitySlider.Value / 100f - _cfg.GetCVar(CCVars.ScreenShakeIntensity)) < 0.01f;
// var isToggleWalkSame = ToggleWalk.Pressed == _cfg.GetCVar(CCVars.ToggleWalk);
var isStaticStorageUISame = StaticStorageUI.Pressed == _cfg.GetCVar(CCVars.StaticStorageUI);
@@ -160,11 +187,14 @@ private void UpdateApplyButton()
isShowHeldItemSame &&
isCombatModeIndicatorsSame &&
isOpaqueStorageWindow &&
+ isOocPatronColorShowSame &&
isLoocShowSame &&
isFancyChatSame &&
isFancyBackgroundSame &&
isEnableColorNameSame &&
+ isColorblindFriendly &&
isReducedMotionSame &&
+ isChatWindowOpacitySame &&
isScreenShakeIntensitySame &&
// isToggleWalkSame &&
isStaticStorageUISame;
diff --git a/Content.Client/Options/UI/Tabs/NetworkTab.xaml b/Content.Client/Options/UI/Tabs/NetworkTab.xaml
deleted file mode 100644
index d010f0bd31..0000000000
--- a/Content.Client/Options/UI/Tabs/NetworkTab.xaml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Content.Client/Options/UI/Tabs/NetworkTab.xaml.cs b/Content.Client/Options/UI/Tabs/NetworkTab.xaml.cs
deleted file mode 100644
index 0cdc3b53fb..0000000000
--- a/Content.Client/Options/UI/Tabs/NetworkTab.xaml.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using System.Globalization;
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Shared;
-using Robust.Shared.Configuration;
-using Robust.Client.GameStates;
-using Content.Client.Entry;
-
-namespace Content.Client.Options.UI.Tabs
-{
- [GenerateTypedNameReferences]
- public sealed partial class NetworkTab : Control
- {
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- [Dependency] private readonly IClientGameStateManager _stateMan = default!;
-
- public NetworkTab()
- {
-
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
-
- ApplyButton.OnPressed += OnApplyButtonPressed;
- ResetButton.OnPressed += OnResetButtonPressed;
- DefaultButton.OnPressed += OnDefaultButtonPressed;
- NetPredictCheckbox.OnToggled += OnPredictToggled;
- NetInterpRatioSlider.OnValueChanged += OnSliderChanged;
- NetInterpRatioSlider.MinValue = _stateMan.MinBufferSize;
- NetPredictTickBiasSlider.OnValueChanged += OnSliderChanged;
- NetPvsSpawnSlider.OnValueChanged += OnSliderChanged;
- NetPvsEntrySlider.OnValueChanged += OnSliderChanged;
- NetPvsLeaveSlider.OnValueChanged += OnSliderChanged;
-
- Reset();
- }
-
- protected override void Dispose(bool disposing)
- {
- ApplyButton.OnPressed -= OnApplyButtonPressed;
- ResetButton.OnPressed -= OnResetButtonPressed;
- DefaultButton.OnPressed -= OnDefaultButtonPressed;
- NetPredictCheckbox.OnToggled -= OnPredictToggled;
- NetInterpRatioSlider.OnValueChanged -= OnSliderChanged;
- NetPredictTickBiasSlider.OnValueChanged -= OnSliderChanged;
- NetPvsSpawnSlider.OnValueChanged -= OnSliderChanged;
- NetPvsEntrySlider.OnValueChanged -= OnSliderChanged;
- NetPvsLeaveSlider.OnValueChanged -= OnSliderChanged;
- base.Dispose(disposing);
- }
-
- private void OnPredictToggled(BaseButton.ButtonToggledEventArgs obj)
- {
- UpdateChanges();
- }
-
- private void OnSliderChanged(Robust.Client.UserInterface.Controls.Range range)
- {
- UpdateChanges();
- }
-
- private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
- {
- _cfg.SetCVar(CVars.NetBufferSize, (int) NetInterpRatioSlider.Value - _stateMan.MinBufferSize);
- _cfg.SetCVar(CVars.NetPredictTickBias, (int) NetPredictTickBiasSlider.Value);
- _cfg.SetCVar(CVars.NetPVSEntityBudget, (int) NetPvsSpawnSlider.Value);
- _cfg.SetCVar(CVars.NetPVSEntityEnterBudget, (int) NetPvsEntrySlider.Value);
- _cfg.SetCVar(CVars.NetPVSEntityExitBudget, (int) NetPvsLeaveSlider.Value);
- _cfg.SetCVar(CVars.NetPredict, NetPredictCheckbox.Pressed);
-
- _cfg.SaveToFile();
- UpdateChanges();
- }
-
- private void OnResetButtonPressed(BaseButton.ButtonEventArgs args)
- {
- Reset();
- }
-
- private void OnDefaultButtonPressed(BaseButton.ButtonEventArgs obj)
- {
- NetPredictTickBiasSlider.Value = CVars.NetPredictTickBias.DefaultValue;
- NetPvsSpawnSlider.Value = CVars.NetPVSEntityBudget.DefaultValue;
- NetPvsEntrySlider.Value = CVars.NetPVSEntityEnterBudget.DefaultValue;
- NetPvsLeaveSlider.Value = CVars.NetPVSEntityExitBudget.DefaultValue;
- NetInterpRatioSlider.Value = CVars.NetBufferSize.DefaultValue + _stateMan.MinBufferSize;
-
- UpdateChanges();
- }
-
- private void Reset()
- {
- NetInterpRatioSlider.Value = _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize;
- NetPredictTickBiasSlider.Value = _cfg.GetCVar(CVars.NetPredictTickBias);
- NetPvsSpawnSlider.Value = _cfg.GetCVar(CVars.NetPVSEntityBudget);
- NetPvsEntrySlider.Value = _cfg.GetCVar(CVars.NetPVSEntityEnterBudget);
- NetPvsLeaveSlider.Value = _cfg.GetCVar(CVars.NetPVSEntityExitBudget);
- NetPredictCheckbox.Pressed = _cfg.GetCVar(CVars.NetPredict);
- UpdateChanges();
- }
-
- private void UpdateChanges()
- {
- var isEverythingSame =
- NetInterpRatioSlider.Value == _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize &&
- NetPredictTickBiasSlider.Value == _cfg.GetCVar(CVars.NetPredictTickBias) &&
- NetPredictCheckbox.Pressed == _cfg.GetCVar(CVars.NetPredict) &&
- NetPvsSpawnSlider.Value == _cfg.GetCVar(CVars.NetPVSEntityBudget) &&
- NetPvsEntrySlider.Value == _cfg.GetCVar(CVars.NetPVSEntityEnterBudget) &&
- NetPvsLeaveSlider.Value == _cfg.GetCVar(CVars.NetPVSEntityExitBudget);
-
- ApplyButton.Disabled = isEverythingSame;
- ResetButton.Disabled = isEverythingSame;
- NetInterpRatioLabel.Text = NetInterpRatioSlider.Value.ToString(CultureInfo.InvariantCulture);
- NetPredictTickBiasLabel.Text = NetPredictTickBiasSlider.Value.ToString(CultureInfo.InvariantCulture);
- NetPvsSpawnLabel.Text = NetPvsSpawnSlider.Value.ToString(CultureInfo.InvariantCulture);
- NetPvsEntryLabel.Text = NetPvsEntrySlider.Value.ToString(CultureInfo.InvariantCulture);
- NetPvsLeaveLabel.Text = NetPvsLeaveSlider.Value.ToString(CultureInfo.InvariantCulture);
-
- // TODO disable / grey-out the predict and interp sliders if prediction is disabled.
- // Currently no option to do this, but should be added to the slider control in general
- }
- }
-}
diff --git a/Content.Client/Outline/TargetOutlineSystem.cs b/Content.Client/Outline/TargetOutlineSystem.cs
index 2a6867f51f..df57578b1f 100644
--- a/Content.Client/Outline/TargetOutlineSystem.cs
+++ b/Content.Client/Outline/TargetOutlineSystem.cs
@@ -22,6 +22,7 @@ public sealed class TargetOutlineSystem : EntitySystem
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
private bool _enabled = false;
@@ -137,7 +138,7 @@ private void HighlightTargets()
// check the entity whitelist
if (valid && Whitelist != null)
- valid = Whitelist.IsValid(entity);
+ valid = _whitelistSystem.IsWhitelistPass(Whitelist, entity);
// and check the cancellable event
if (valid && ValidationEvent != null)
diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs
index 11bec13539..4f92843739 100644
--- a/Content.Client/Overlays/EntityHealthBarOverlay.cs
+++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs
@@ -1,13 +1,17 @@
+using System.Numerics;
+using Content.Client.StatusIcon;
+using Content.Client.UserInterface.Systems;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
+using Content.Shared.StatusIcon;
+using Content.Shared.StatusIcon.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
-using System.Numerics;
-using Content.Shared.StatusIcon.Components;
+using Robust.Shared.Prototypes;
using static Robust.Shared.Maths.Color;
namespace Content.Client.Overlays;
@@ -18,18 +22,28 @@ namespace Content.Client.Overlays;
public sealed class EntityHealthBarOverlay : Overlay
{
private readonly IEntityManager _entManager;
+ private readonly IPrototypeManager _prototype;
+
private readonly SharedTransformSystem _transform;
private readonly MobStateSystem _mobStateSystem;
private readonly MobThresholdSystem _mobThresholdSystem;
+ private readonly StatusIconSystem _statusIconSystem;
+ private readonly ProgressColorSystem _progressColor;
+
+
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
public HashSet DamageContainers = new();
+ public ProtoId? StatusIcon;
- public EntityHealthBarOverlay(IEntityManager entManager)
+ public EntityHealthBarOverlay(IEntityManager entManager, IPrototypeManager prototype)
{
_entManager = entManager;
- _transform = _entManager.EntitySysManager.GetEntitySystem();
- _mobStateSystem = _entManager.EntitySysManager.GetEntitySystem();
- _mobThresholdSystem = _entManager.EntitySysManager.GetEntitySystem();
+ _prototype = prototype;
+ _transform = _entManager.System();
+ _mobStateSystem = _entManager.System();
+ _mobThresholdSystem = _entManager.System();
+ _statusIconSystem = _entManager.System();
+ _progressColor = _entManager.System();
}
protected override void Draw(in OverlayDrawArgs args)
@@ -39,8 +53,9 @@ protected override void Draw(in OverlayDrawArgs args)
var xformQuery = _entManager.GetEntityQuery();
const float scale = 1f;
- var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
- var rotationMatrix = Matrix3.CreateRotation(-rotation);
+ var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale));
+ var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation);
+ _prototype.TryIndex(StatusIcon, out var statusIcon);
var query = _entManager.AllEntityQueryEnumerator();
while (query.MoveNext(out var uid,
@@ -49,37 +64,33 @@ protected override void Draw(in OverlayDrawArgs args)
out var damageableComponent,
out var spriteComponent))
{
- if (_entManager.TryGetComponent(uid, out var metaDataComponent) &&
- metaDataComponent.Flags.HasFlag(MetaDataFlags.InContainer))
- {
+ if (statusIcon != null && !_statusIconSystem.IsVisible((uid, _entManager.GetComponent(uid)), statusIcon))
continue;
- }
+ // We want the stealth user to still be able to see his health bar himself
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapID != args.MapId)
- {
continue;
- }
if (damageableComponent.DamageContainerID == null || !DamageContainers.Contains(damageableComponent.DamageContainerID))
- {
continue;
- }
// we use the status icon component bounds if specified otherwise use sprite
var bounds = _entManager.GetComponentOrNull(uid)?.Bounds ?? spriteComponent.Bounds;
var worldPos = _transform.GetWorldPosition(xform, xformQuery);
if (!bounds.Translated(worldPos).Intersects(args.WorldAABB))
- {
continue;
- }
+
+ // we are all progressing towards death every day
+ if (CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent) is not { } deathProgress)
+ continue;
var worldPosition = _transform.GetWorldPosition(xform);
- var worldMatrix = Matrix3.CreateTranslation(worldPosition);
+ var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition);
- Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
- Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
+ var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix);
+ var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld);
handle.SetTransform(matty);
@@ -87,10 +98,6 @@ protected override void Draw(in OverlayDrawArgs args)
var widthOfMob = bounds.Width * EyeManager.PixelsPerMeter;
var position = new Vector2(-widthOfMob / EyeManager.PixelsPerMeter / 2, yOffset / EyeManager.PixelsPerMeter);
-
- // we are all progressing towards death every day
- (float ratio, bool inCrit) deathProgress = CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent);
-
var color = GetProgressColor(deathProgress.ratio, deathProgress.inCrit);
// Hardcoded width of the progress bar because it doesn't match the texture.
@@ -112,17 +119,19 @@ protected override void Draw(in OverlayDrawArgs args)
handle.DrawRect(pixelDarken, Black.WithAlpha(128));
}
- handle.UseShader(null);
- handle.SetTransform(Matrix3.Identity);
+ handle.SetTransform(Matrix3x2.Identity);
}
///
/// Returns a ratio between 0 and 1, and whether the entity is in crit.
///
- private (float, bool) CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds)
+ private (float ratio, bool inCrit)? CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds)
{
if (_mobStateSystem.IsAlive(uid, component))
{
+ if (dmg.HealthBarThreshold != null && dmg.TotalDamage < dmg.HealthBarThreshold)
+ return null;
+
if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholds) &&
!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out threshold, thresholds))
return (1, false);
@@ -147,26 +156,11 @@ protected override void Draw(in OverlayDrawArgs args)
return (0, true);
}
- public static Color GetProgressColor(float progress, bool crit)
+ public Color GetProgressColor(float progress, bool crit)
{
- if (progress >= 1.0f)
- {
- return SeaBlue;
- }
-
- if (!crit)
- {
- switch (progress)
- {
- case > 0.90F:
- return SeaBlue;
- case > 0.50F:
- return Violet;
- case > 0.15F:
- return Ruber;
- }
- }
+ if (crit)
+ progress = 0;
- return VividGamboge;
+ return _progressColor.GetProgressColor(progress);
}
}
diff --git a/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs b/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs
new file mode 100644
index 0000000000..c353b17272
--- /dev/null
+++ b/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs
@@ -0,0 +1,28 @@
+using Content.Shared.Overlays;
+using Content.Shared.Security.Components;
+using Content.Shared.StatusIcon;
+using Content.Shared.StatusIcon.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Overlays;
+
+public sealed class ShowCriminalRecordIconsSystem : EquipmentHudSystem
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnGetStatusIconsEvent);
+ }
+
+ private void OnGetStatusIconsEvent(EntityUid uid, CriminalRecordComponent component, ref GetStatusIconsEvent ev)
+ {
+ if (!IsActive)
+ return;
+
+ if (_prototype.TryIndex(component.StatusIcon, out var iconPrototype))
+ ev.StatusIcons.Add(iconPrototype);
+ }
+}
diff --git a/Content.Client/Overlays/ShowHealthBarsSystem.cs b/Content.Client/Overlays/ShowHealthBarsSystem.cs
index 170f552cf3..1eb712a898 100644
--- a/Content.Client/Overlays/ShowHealthBarsSystem.cs
+++ b/Content.Client/Overlays/ShowHealthBarsSystem.cs
@@ -2,6 +2,8 @@
using Content.Shared.Overlays;
using Robust.Client.Graphics;
using System.Linq;
+using Robust.Client.Player;
+using Robust.Shared.Prototypes;
namespace Content.Client.Overlays;
@@ -11,6 +13,7 @@ namespace Content.Client.Overlays;
public sealed class ShowHealthBarsSystem : EquipmentHudSystem
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
private EntityHealthBarOverlay _overlay = default!;
@@ -18,16 +21,21 @@ public override void Initialize()
{
base.Initialize();
- _overlay = new(EntityManager);
+ _overlay = new(EntityManager, _prototype);
}
protected override void UpdateInternal(RefreshEquipmentHudEvent component)
{
base.UpdateInternal(component);
- foreach (var damageContainerId in component.Components.SelectMany(x => x.DamageContainers))
+ foreach (var comp in component.Components)
{
- _overlay.DamageContainers.Add(damageContainerId);
+ foreach (var damageContainerId in comp.DamageContainers)
+ {
+ _overlay.DamageContainers.Add(damageContainerId);
+ }
+
+ _overlay.StatusIcon = comp.HealthStatusIcon;
}
if (!_overlayMan.HasOverlay())
diff --git a/Content.Client/Overlays/ShowHealthIconsSystem.cs b/Content.Client/Overlays/ShowHealthIconsSystem.cs
index 6ed9d6a41d..d8af91482b 100644
--- a/Content.Client/Overlays/ShowHealthIconsSystem.cs
+++ b/Content.Client/Overlays/ShowHealthIconsSystem.cs
@@ -1,5 +1,7 @@
+using Content.Shared.Atmos.Rotting;
using Content.Shared.Damage;
using Content.Shared.Inventory.Events;
+using Content.Shared.Mobs.Components;
using Content.Shared.Overlays;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
@@ -17,15 +19,11 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem DamageContainers = new();
- [ValidatePrototypeId]
- private const string HealthIconFine = "HealthIconFine";
-
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnGetStatusIconsEvent);
-
}
protected override void UpdateInternal(RefreshEquipmentHudEvent component)
@@ -45,18 +43,20 @@ protected override void DeactivateInternal()
DamageContainers.Clear();
}
- private void OnGetStatusIconsEvent(EntityUid uid, DamageableComponent damageableComponent, ref GetStatusIconsEvent args)
+ private void OnGetStatusIconsEvent(Entity entity, ref GetStatusIconsEvent args)
{
- if (!IsActive || args.InContainer)
+ if (!IsActive)
return;
- var healthIcons = DecideHealthIcons(damageableComponent);
+ var healthIcons = DecideHealthIcons(entity);
args.StatusIcons.AddRange(healthIcons);
}
- private IReadOnlyList DecideHealthIcons(DamageableComponent damageableComponent)
+ private IReadOnlyList DecideHealthIcons(Entity entity)
{
+ var damageableComponent = entity.Comp;
+
if (damageableComponent.DamageContainerID == null ||
!DamageContainers.Contains(damageableComponent.DamageContainerID))
{
@@ -66,10 +66,16 @@ private IReadOnlyList DecideHealthIcons(DamageableComponent
var result = new List();
// Here you could check health status, diseases, mind status, etc. and pick a good icon, or multiple depending on whatever.
- if (damageableComponent?.DamageContainerID == "Biological" &&
- _prototypeMan.TryIndex(HealthIconFine, out var healthyIcon))
+ if (damageableComponent?.DamageContainerID == "Biological")
{
- result.Add(healthyIcon);
+ if (TryComp(entity, out var state))
+ {
+ // Since there is no MobState for a rotting mob, we have to deal with this case first.
+ if (HasComp(entity) && _prototypeMan.TryIndex(damageableComponent.RottingIcon, out var rottingIcon))
+ result.Add(rottingIcon);
+ else if (damageableComponent.HealthIcons.TryGetValue(state.CurrentState, out var value) && _prototypeMan.TryIndex(value, out var icon))
+ result.Add(icon);
+ }
}
return result;
diff --git a/Content.Client/Overlays/ShowHungerIconsSystem.cs b/Content.Client/Overlays/ShowHungerIconsSystem.cs
index 58551b30c2..6b0d575a81 100644
--- a/Content.Client/Overlays/ShowHungerIconsSystem.cs
+++ b/Content.Client/Overlays/ShowHungerIconsSystem.cs
@@ -1,14 +1,13 @@
+using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Overlays;
-using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
-using Robust.Shared.Prototypes;
namespace Content.Client.Overlays;
public sealed class ShowHungerIconsSystem : EquipmentHudSystem
{
- [Dependency] private readonly IPrototypeManager _prototypeMan = default!;
+ [Dependency] private readonly HungerSystem _hunger = default!;
public override void Initialize()
{
@@ -17,42 +16,12 @@ public override void Initialize()
SubscribeLocalEvent(OnGetStatusIconsEvent);
}
- private void OnGetStatusIconsEvent(EntityUid uid, HungerComponent hungerComponent, ref GetStatusIconsEvent args)
+ private void OnGetStatusIconsEvent(EntityUid uid, HungerComponent component, ref GetStatusIconsEvent ev)
{
- if (!IsActive || args.InContainer)
+ if (!IsActive)
return;
- var hungerIcons = DecideHungerIcon(uid, hungerComponent);
-
- args.StatusIcons.AddRange(hungerIcons);
- }
-
- private IReadOnlyList DecideHungerIcon(EntityUid uid, HungerComponent hungerComponent)
- {
- var result = new List();
-
- switch (hungerComponent.CurrentThreshold)
- {
- case HungerThreshold.Overfed:
- if (_prototypeMan.TryIndex("HungerIconOverfed", out var overfed))
- {
- result.Add(overfed);
- }
- break;
- case HungerThreshold.Peckish:
- if (_prototypeMan.TryIndex("HungerIconPeckish", out var peckish))
- {
- result.Add(peckish);
- }
- break;
- case HungerThreshold.Starving:
- if (_prototypeMan.TryIndex("HungerIconStarving", out var starving))
- {
- result.Add(starving);
- }
- break;
- }
-
- return result;
+ if (_hunger.TryGetStatusIconPrototype(component, out var iconPrototype))
+ ev.StatusIcons.Add(iconPrototype);
}
}
diff --git a/Content.Client/Overlays/ShowJobIconsSystem.cs b/Content.Client/Overlays/ShowJobIconsSystem.cs
new file mode 100644
index 0000000000..a6d4f70af6
--- /dev/null
+++ b/Content.Client/Overlays/ShowJobIconsSystem.cs
@@ -0,0 +1,60 @@
+using Content.Shared.Access.Components;
+using Content.Shared.Access.Systems;
+using Content.Shared.Overlays;
+using Content.Shared.PDA;
+using Content.Shared.StatusIcon;
+using Content.Shared.StatusIcon.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Overlays;
+
+public sealed class ShowJobIconsSystem : EquipmentHudSystem
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly AccessReaderSystem _accessReader = default!;
+
+ [ValidatePrototypeId]
+ private const string JobIconForNoId = "JobIconNoId";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnGetStatusIconsEvent);
+ }
+
+ private void OnGetStatusIconsEvent(EntityUid uid, StatusIconComponent _, ref GetStatusIconsEvent ev)
+ {
+ if (!IsActive)
+ return;
+
+ var iconId = JobIconForNoId;
+
+ if (_accessReader.FindAccessItemsInventory(uid, out var items))
+ {
+ foreach (var item in items)
+ {
+ // ID Card
+ if (TryComp(item, out var id))
+ {
+ iconId = id.JobIcon;
+ break;
+ }
+
+ // PDA
+ if (TryComp(item, out var pda)
+ && pda.ContainedId != null
+ && TryComp(pda.ContainedId, out id))
+ {
+ iconId = id.JobIcon;
+ break;
+ }
+ }
+ }
+
+ if (_prototype.TryIndex(iconId, out var iconPrototype))
+ ev.StatusIcons.Add(iconPrototype);
+ else
+ Log.Error($"Invalid job icon prototype: {iconPrototype}");
+ }
+}
diff --git a/Content.Client/Overlays/ShowMindShieldIconsSystem.cs b/Content.Client/Overlays/ShowMindShieldIconsSystem.cs
new file mode 100644
index 0000000000..cdb9c54fdf
--- /dev/null
+++ b/Content.Client/Overlays/ShowMindShieldIconsSystem.cs
@@ -0,0 +1,28 @@
+using Content.Shared.Mindshield.Components;
+using Content.Shared.Overlays;
+using Content.Shared.StatusIcon;
+using Content.Shared.StatusIcon.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Overlays;
+
+public sealed class ShowMindShieldIconsSystem : EquipmentHudSystem
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnGetStatusIconsEvent);
+ }
+
+ private void OnGetStatusIconsEvent(EntityUid uid, MindShieldComponent component, ref GetStatusIconsEvent ev)
+ {
+ if (!IsActive)
+ return;
+
+ if (_prototype.TryIndex(component.MindShieldStatusIcon, out var iconPrototype))
+ ev.StatusIcons.Add(iconPrototype);
+ }
+}
diff --git a/Content.Client/Overlays/ShowSecurityIconsSystem.cs b/Content.Client/Overlays/ShowSecurityIconsSystem.cs
deleted file mode 100644
index 77c14c5ef0..0000000000
--- a/Content.Client/Overlays/ShowSecurityIconsSystem.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using Content.Shared.Access.Components;
-using Content.Shared.Access.Systems;
-using Content.Shared.Mindshield.Components;
-using Content.Shared.Overlays;
-using Content.Shared.PDA;
-using Content.Shared.StatusIcon;
-using Content.Shared.StatusIcon.Components;
-using Robust.Shared.Prototypes;
-
-namespace Content.Client.Overlays;
-
-public sealed class ShowSecurityIconsSystem : EquipmentHudSystem
-{
- [Dependency] private readonly IPrototypeManager _prototypeMan = default!;
- [Dependency] private readonly AccessReaderSystem _accessReader = default!;
-
- [ValidatePrototypeId]
- private const string JobIconForNoId = "JobIconNoId";
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnGetStatusIconsEvent);
- }
-
- private void OnGetStatusIconsEvent(EntityUid uid, StatusIconComponent _, ref GetStatusIconsEvent @event)
- {
- if (!IsActive || @event.InContainer)
- {
- return;
- }
-
- var securityIcons = DecideSecurityIcon(uid);
-
- @event.StatusIcons.AddRange(securityIcons);
- }
-
- private IReadOnlyList DecideSecurityIcon(EntityUid uid)
- {
- var result = new List();
-
- var jobIconToGet = JobIconForNoId;
- if (_accessReader.FindAccessItemsInventory(uid, out var items))
- {
- foreach (var item in items)
- {
- // ID Card
- if (TryComp(item, out IdCardComponent? id))
- {
- jobIconToGet = id.JobIcon;
- break;
- }
-
- // PDA
- if (TryComp(item, out PdaComponent? pda)
- && pda.ContainedId != null
- && TryComp(pda.ContainedId, out id))
- {
- jobIconToGet = id.JobIcon;
- break;
- }
- }
- }
-
- if (_prototypeMan.TryIndex(jobIconToGet, out var jobIcon))
- result.Add(jobIcon);
- else
- Log.Error($"Invalid job icon prototype: {jobIcon}");
-
- if (TryComp(uid, out var comp))
- {
- if (_prototypeMan.TryIndex(comp.MindShieldStatusIcon.Id, out var icon))
- result.Add(icon);
- }
-
- // Add arrest icons here, WYCI.
-
- return result;
- }
-}
diff --git a/Content.Client/Overlays/ShowSyndicateIconsSystem.cs b/Content.Client/Overlays/ShowSyndicateIconsSystem.cs
index a640726685..782178a29d 100644
--- a/Content.Client/Overlays/ShowSyndicateIconsSystem.cs
+++ b/Content.Client/Overlays/ShowSyndicateIconsSystem.cs
@@ -1,10 +1,11 @@
using Content.Shared.Overlays;
-using Content.Shared.StatusIcon.Components;
using Content.Shared.NukeOps;
using Content.Shared.StatusIcon;
+using Content.Shared.StatusIcon.Components;
using Robust.Shared.Prototypes;
namespace Content.Client.Overlays;
+
public sealed class ShowSyndicateIconsSystem : EquipmentHudSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
@@ -16,28 +17,12 @@ public override void Initialize()
SubscribeLocalEvent(OnGetStatusIconsEvent);
}
- private void OnGetStatusIconsEvent(EntityUid uid, NukeOperativeComponent nukeOperativeComponent, ref GetStatusIconsEvent args)
+ private void OnGetStatusIconsEvent(EntityUid uid, NukeOperativeComponent component, ref GetStatusIconsEvent ev)
{
- if (!IsActive || args.InContainer)
- {
+ if (!IsActive)
return;
- }
-
- var syndicateIcons = SyndicateIcon(uid, nukeOperativeComponent);
-
- args.StatusIcons.AddRange(syndicateIcons);
- }
-
- private IReadOnlyList SyndicateIcon(EntityUid uid, NukeOperativeComponent nukeOperativeComponent)
- {
- var result = new List();
- if (_prototype.TryIndex(nukeOperativeComponent.SyndStatusIcon, out var syndicateicon))
- {
- result.Add(syndicateicon);
- }
-
- return result;
+ if (_prototype.TryIndex(component.SyndStatusIcon, out var iconPrototype))
+ ev.StatusIcons.Add(iconPrototype);
}
}
-
diff --git a/Content.Client/Overlays/ShowThirstIconsSystem.cs b/Content.Client/Overlays/ShowThirstIconsSystem.cs
index f9d6d0ab25..44be1f7a67 100644
--- a/Content.Client/Overlays/ShowThirstIconsSystem.cs
+++ b/Content.Client/Overlays/ShowThirstIconsSystem.cs
@@ -1,14 +1,13 @@
+using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Overlays;
-using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
-using Robust.Shared.Prototypes;
namespace Content.Client.Overlays;
public sealed class ShowThirstIconsSystem : EquipmentHudSystem
{
- [Dependency] private readonly IPrototypeManager _prototypeMan = default!;
+ [Dependency] private readonly ThirstSystem _thirst = default!;
public override void Initialize()
{
@@ -17,42 +16,12 @@ public override void Initialize()
SubscribeLocalEvent(OnGetStatusIconsEvent);
}
- private void OnGetStatusIconsEvent(EntityUid uid, ThirstComponent thirstComponent, ref GetStatusIconsEvent args)
+ private void OnGetStatusIconsEvent(EntityUid uid, ThirstComponent component, ref GetStatusIconsEvent ev)
{
- if (!IsActive || args.InContainer)
+ if (!IsActive)
return;
- var thirstIcons = DecideThirstIcon(uid, thirstComponent);
-
- args.StatusIcons.AddRange(thirstIcons);
- }
-
- private IReadOnlyList DecideThirstIcon(EntityUid uid, ThirstComponent thirstComponent)
- {
- var result = new List();
-
- switch (thirstComponent.CurrentThirstThreshold)
- {
- case ThirstThreshold.OverHydrated:
- if (_prototypeMan.TryIndex("ThirstIconOverhydrated", out var overhydrated))
- {
- result.Add(overhydrated);
- }
- break;
- case ThirstThreshold.Thirsty:
- if (_prototypeMan.TryIndex("ThirstIconThirsty", out var thirsty))
- {
- result.Add(thirsty);
- }
- break;
- case ThirstThreshold.Parched:
- if (_prototypeMan.TryIndex("ThirstIconParched", out var parched))
- {
- result.Add(parched);
- }
- break;
- }
-
- return result;
+ if (_thirst.TryGetStatusIconPrototype(component, out var iconPrototype))
+ ev.StatusIcons.Add(iconPrototype!);
}
}
diff --git a/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs
index 9581fec37b..d29564caa9 100644
--- a/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs
+++ b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs
@@ -7,7 +7,7 @@ namespace Content.Client.Overlays;
public sealed partial class StencilOverlay
{
- private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3 invMatrix)
+ private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3x2 invMatrix)
{
var worldHandle = args.WorldHandle;
var renderScale = args.Viewport.RenderScale.X;
@@ -16,7 +16,7 @@ private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeCompone
var length = zoom.X;
var bufferRange = MathF.Min(10f, rangeComp.Range);
- var pixelCenter = invMatrix.Transform(rangeComp.Origin);
+ var pixelCenter = Vector2.Transform(rangeComp.Origin, invMatrix);
// Something something offset?
var vertical = args.Viewport.Size.Y;
@@ -44,7 +44,7 @@ private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeCompone
worldHandle.DrawRect(localAABB, Color.White);
}, Color.Transparent);
- worldHandle.SetTransform(Matrix3.Identity);
+ worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(_protoManager.Index("StencilMask").Instance());
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
var curTime = _timing.RealTime;
diff --git a/Content.Client/Overlays/StencilOverlay.Weather.cs b/Content.Client/Overlays/StencilOverlay.Weather.cs
index 31bc88af45..29ed157a79 100644
--- a/Content.Client/Overlays/StencilOverlay.Weather.cs
+++ b/Content.Client/Overlays/StencilOverlay.Weather.cs
@@ -10,7 +10,7 @@ public sealed partial class StencilOverlay
{
private List> _grids = new();
- private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3 invMatrix)
+ private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3x2 invMatrix)
{
var worldHandle = args.WorldHandle;
var mapId = args.MapId;
@@ -32,7 +32,7 @@ private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto,
foreach (var grid in _grids)
{
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
- Matrix3.Multiply(in matrix, in invMatrix, out var matty);
+ var matty = Matrix3x2.Multiply(matrix, invMatrix);
worldHandle.SetTransform(matty);
foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB))
@@ -52,7 +52,7 @@ private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto,
}, Color.Transparent);
- worldHandle.SetTransform(Matrix3.Identity);
+ worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(_protoManager.Index("StencilMask").Instance());
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
var curTime = _timing.RealTime;
@@ -62,7 +62,7 @@ private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto,
worldHandle.UseShader(_protoManager.Index("StencilDraw").Instance());
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, Vector2.Zero, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha));
- worldHandle.SetTransform(Matrix3.Identity);
+ worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(null);
}
}
diff --git a/Content.Client/Overlays/StencilOverlay.cs b/Content.Client/Overlays/StencilOverlay.cs
index e475dca759..78b1c4d2b1 100644
--- a/Content.Client/Overlays/StencilOverlay.cs
+++ b/Content.Client/Overlays/StencilOverlay.cs
@@ -1,3 +1,4 @@
+using System.Numerics;
using Content.Client.Parallax;
using Content.Client.Weather;
using Content.Shared.Salvage;
@@ -72,6 +73,6 @@ protected override void Draw(in OverlayDrawArgs args)
}
args.WorldHandle.UseShader(null);
- args.WorldHandle.SetTransform(Matrix3.Identity);
+ args.WorldHandle.SetTransform(Matrix3x2.Identity);
}
}
diff --git a/Content.Client/PDA/PdaBoundUserInterface.cs b/Content.Client/PDA/PdaBoundUserInterface.cs
index ef9d6e8b9b..f8f4c67076 100644
--- a/Content.Client/PDA/PdaBoundUserInterface.cs
+++ b/Content.Client/PDA/PdaBoundUserInterface.cs
@@ -21,9 +21,16 @@ public PdaBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
protected override void Open()
{
base.Open();
- SendMessage(new PdaRequestUpdateInterfaceMessage());
+
+ if (_menu == null)
+ CreateMenu();
+
+ _menu?.OpenCenteredLeft();
+ }
+
+ private void CreateMenu()
+ {
_menu = new PdaMenu();
- _menu.OpenCenteredLeft();
_menu.OnClose += Close;
_menu.FlashLightToggleButton.OnToggled += _ =>
{
@@ -32,17 +39,17 @@ protected override void Open()
_menu.EjectIdButton.OnPressed += _ =>
{
- SendMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaIdSlotId));
+ SendPredictedMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaIdSlotId));
};
_menu.EjectPenButton.OnPressed += _ =>
{
- SendMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaPenSlotId));
+ SendPredictedMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaPenSlotId));
};
_menu.EjectPaiButton.OnPressed += _ =>
{
- SendMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaPaiSlotId));
+ SendPredictedMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaPaiSlotId));
};
_menu.ActivateMusicButton.OnPressed += _ =>
diff --git a/Content.Client/Paper/PaperComponent.cs b/Content.Client/Paper/PaperComponent.cs
index d197cd3721..1dc827bf7e 100644
--- a/Content.Client/Paper/PaperComponent.cs
+++ b/Content.Client/Paper/PaperComponent.cs
@@ -1,9 +1,6 @@
using Content.Shared.Paper;
-using Robust.Shared.GameStates;
namespace Content.Client.Paper;
-[NetworkedComponent, RegisterComponent]
-public sealed partial class PaperComponent : SharedPaperComponent
-{
-}
+[RegisterComponent]
+public sealed partial class PaperComponent : SharedPaperComponent;
diff --git a/Content.Client/Paper/UI/PaperBoundUserInterface.cs b/Content.Client/Paper/UI/PaperBoundUserInterface.cs
index 30f1502779..4b0ac868f0 100644
--- a/Content.Client/Paper/UI/PaperBoundUserInterface.cs
+++ b/Content.Client/Paper/UI/PaperBoundUserInterface.cs
@@ -1,6 +1,5 @@
using JetBrains.Annotations;
using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Input;
using Robust.Shared.Utility;
using static Content.Shared.Paper.SharedPaperComponent;
@@ -22,15 +21,7 @@ protected override void Open()
_window = new PaperWindow();
_window.OnClose += Close;
- _window.Input.OnKeyBindDown += args => // Solution while TextEdit don't have events
- {
- if (args.Function == EngineKeyFunctions.TextSubmit)
- {
- var text = Rope.Collapse(_window.Input.TextRope);
- Input_OnTextEntered(text);
- args.Handle();
- }
- };
+ _window.OnSaved += Input_OnTextEntered;
if (EntMan.TryGetComponent(Owner, out var visuals))
{
diff --git a/Content.Client/Paper/UI/PaperWindow.xaml b/Content.Client/Paper/UI/PaperWindow.xaml
index 279dd72b27..2344afd5ef 100644
--- a/Content.Client/Paper/UI/PaperWindow.xaml
+++ b/Content.Client/Paper/UI/PaperWindow.xaml
@@ -25,5 +25,9 @@
+
+
+
+
diff --git a/Content.Client/Paper/UI/PaperWindow.xaml.cs b/Content.Client/Paper/UI/PaperWindow.xaml.cs
index c3192a495d..7a5fd65264 100644
--- a/Content.Client/Paper/UI/PaperWindow.xaml.cs
+++ b/Content.Client/Paper/UI/PaperWindow.xaml.cs
@@ -2,18 +2,22 @@
using Content.Shared.Paper;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
+using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
using Robust.Client.UserInterface.RichText;
+using Robust.Shared.Input;
namespace Content.Client.Paper.UI
{
[GenerateTypedNameReferences]
public sealed partial class PaperWindow : BaseWindow
{
+ [Dependency] private readonly IInputManager _inputManager = default!;
+
private static Color DefaultTextColor = new(25, 25, 25);
//
@@ -41,8 +45,11 @@ public sealed partial class PaperWindow : BaseWindow
typeof(ItalicTag)
};
+ public event Action? OnSaved;
+
public PaperWindow()
{
+ IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
// We can't configure the RichTextLabel contents from xaml, so do it here:
@@ -50,6 +57,23 @@ public PaperWindow()
// Hook up the close button:
CloseButton.OnPressed += _ => Close();
+
+ Input.OnKeyBindDown += args => // Solution while TextEdit don't have events
+ {
+ if (args.Function == EngineKeyFunctions.MultilineTextSubmit)
+ {
+ RunOnSaved();
+ args.Handle();
+ }
+ };
+
+ SaveButton.OnPressed += _ =>
+ {
+ RunOnSaved();
+ };
+
+ SaveButton.Text = Loc.GetString("paper-ui-save-button",
+ ("keybind", _inputManager.GetKeyFunctionButtonString(EngineKeyFunctions.MultilineTextSubmit)));
}
///
@@ -196,6 +220,7 @@ public void Populate(SharedPaperComponent.PaperBoundUserInterfaceState state)
bool isEditing = state.Mode == SharedPaperComponent.PaperAction.Write;
bool wasEditing = InputContainer.Visible;
InputContainer.Visible = isEditing;
+ EditButtons.Visible = isEditing;
var msg = new FormattedMessage();
msg.AddMarkupPermissive(state.Text);
@@ -266,5 +291,10 @@ protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
}
return mode & _allowedResizeModes;
}
+
+ private void RunOnSaved()
+ {
+ OnSaved?.Invoke(Rope.Collapse(Input.TextRope));
+ }
}
}
diff --git a/Content.Client/Paper/UI/StampLabel.xaml.cs b/Content.Client/Paper/UI/StampLabel.xaml.cs
index 6a8eb5f98f..be6d52baea 100644
--- a/Content.Client/Paper/UI/StampLabel.xaml.cs
+++ b/Content.Client/Paper/UI/StampLabel.xaml.cs
@@ -50,7 +50,7 @@ protected override void Draw(DrawingHandleScreen handle)
base.Draw(handle);
// Restore a sane transform+shader
- handle.SetTransform(Matrix3.Identity);
+ handle.SetTransform(Matrix3x2.Identity);
handle.UseShader(null);
}
}
diff --git a/Content.Client/Paper/UI/StampWidget.xaml.cs b/Content.Client/Paper/UI/StampWidget.xaml.cs
index a04508aeba..487e0732b4 100644
--- a/Content.Client/Paper/UI/StampWidget.xaml.cs
+++ b/Content.Client/Paper/UI/StampWidget.xaml.cs
@@ -53,7 +53,7 @@ protected override void Draw(DrawingHandleScreen handle)
base.Draw(handle);
// Restore a sane transform+shader
- handle.SetTransform(Matrix3.Identity);
+ handle.SetTransform(Matrix3x2.Identity);
handle.UseShader(null);
}
}
diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs
index 7a8a6e39cf..31042854d4 100644
--- a/Content.Client/Physics/Controllers/MoverController.cs
+++ b/Content.Client/Physics/Controllers/MoverController.cs
@@ -1,6 +1,7 @@
using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Systems;
-using Content.Shared.Pulling.Components;
+using Robust.Client.GameObjects;
using Robust.Client.Physics;
using Robust.Client.Player;
using Robust.Shared.Physics.Components;
@@ -24,7 +25,7 @@ public override void Initialize()
SubscribeLocalEvent(OnUpdatePredicted);
SubscribeLocalEvent(OnUpdateRelayTargetPredicted);
- SubscribeLocalEvent(OnUpdatePullablePredicted);
+ SubscribeLocalEvent(OnUpdatePullablePredicted);
}
private void OnUpdatePredicted(EntityUid uid, InputMoverComponent component, ref UpdateIsPredictedEvent args)
@@ -40,7 +41,7 @@ private void OnUpdateRelayTargetPredicted(EntityUid uid, MovementRelayTargetComp
args.IsPredicted = true;
}
- private void OnUpdatePullablePredicted(EntityUid uid, SharedPullableComponent component, ref UpdateIsPredictedEvent args)
+ private void OnUpdatePullablePredicted(EntityUid uid, PullableComponent component, ref UpdateIsPredictedEvent args)
{
// Enable prediction if an entity is being pulled by the player.
// Disable prediction if an entity is being pulled by some non-player entity.
diff --git a/Content.Client/Pinpointer/NavMapSystem.cs b/Content.Client/Pinpointer/NavMapSystem.cs
index bd7dfc1117..9aeb792a42 100644
--- a/Content.Client/Pinpointer/NavMapSystem.cs
+++ b/Content.Client/Pinpointer/NavMapSystem.cs
@@ -1,109 +1,63 @@
-using System.Numerics;
using Content.Shared.Pinpointer;
-using Robust.Client.Graphics;
-using Robust.Shared.Enums;
using Robust.Shared.GameStates;
-using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
namespace Content.Client.Pinpointer;
-public sealed class NavMapSystem : SharedNavMapSystem
+public sealed partial class NavMapSystem : SharedNavMapSystem
{
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent(OnHandleState);
}
private void OnHandleState(EntityUid uid, NavMapComponent component, ref ComponentHandleState args)
{
- if (args.Current is not NavMapComponentState state)
- return;
-
- component.Chunks.Clear();
+ Dictionary modifiedChunks;
+ Dictionary beacons;
- foreach (var (origin, data) in state.TileData)
+ switch (args.Current)
{
- component.Chunks.Add(origin, new NavMapChunk(origin)
+ case NavMapDeltaState delta:
{
- TileData = data,
- });
- }
-
- component.Beacons.Clear();
- component.Beacons.AddRange(state.Beacons);
-
- component.Airlocks.Clear();
- component.Airlocks.AddRange(state.Airlocks);
- }
-}
-
-public sealed class NavMapOverlay : Overlay
-{
- private readonly IEntityManager _entManager;
- private readonly IMapManager _mapManager;
-
- public override OverlaySpace Space => OverlaySpace.WorldSpace;
-
- private List> _grids = new();
-
- public NavMapOverlay(IEntityManager entManager, IMapManager mapManager)
- {
- _entManager = entManager;
- _mapManager = mapManager;
- }
-
- protected override void Draw(in OverlayDrawArgs args)
- {
- var query = _entManager.GetEntityQuery();
- var xformQuery = _entManager.GetEntityQuery();
- var scale = Matrix3.CreateScale(new Vector2(1f, 1f));
-
- _grids.Clear();
- _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref _grids);
-
- foreach (var grid in _grids)
- {
- if (!query.TryGetComponent(grid, out var navMap) || !xformQuery.TryGetComponent(grid.Owner, out var xform))
- continue;
-
- // TODO: Faster helper method
- var (_, _, matrix, invMatrix) = xform.GetWorldPositionRotationMatrixWithInv();
-
- var localAABB = invMatrix.TransformBox(args.WorldBounds);
- Matrix3.Multiply(in scale, in matrix, out var matty);
-
- args.WorldHandle.SetTransform(matty);
+ modifiedChunks = delta.ModifiedChunks;
+ beacons = delta.Beacons;
+ foreach (var index in component.Chunks.Keys)
+ {
+ if (!delta.AllChunks!.Contains(index))
+ component.Chunks.Remove(index);
+ }
- for (var x = Math.Floor(localAABB.Left); x <= Math.Ceiling(localAABB.Right); x += SharedNavMapSystem.ChunkSize * grid.Comp.TileSize)
+ break;
+ }
+ case NavMapState state:
{
- for (var y = Math.Floor(localAABB.Bottom); y <= Math.Ceiling(localAABB.Top); y += SharedNavMapSystem.ChunkSize * grid.Comp.TileSize)
+ modifiedChunks = state.Chunks;
+ beacons = state.Beacons;
+ foreach (var index in component.Chunks.Keys)
{
- var floored = new Vector2i((int) x, (int) y);
-
- var chunkOrigin = SharedMapSystem.GetChunkIndices(floored, SharedNavMapSystem.ChunkSize);
-
- if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
- continue;
-
- // TODO: Okay maybe I should just use ushorts lmao...
- for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
- {
- var value = (int) Math.Pow(2, i);
-
- var mask = chunk.TileData & value;
-
- if (mask == 0x0)
- continue;
-
- var tile = chunk.Origin * SharedNavMapSystem.ChunkSize + SharedNavMapSystem.GetTile(mask);
- args.WorldHandle.DrawRect(new Box2(tile * grid.Comp.TileSize, (tile + 1) * grid.Comp.TileSize), Color.Aqua, false);
- }
+ if (!state.Chunks.ContainsKey(index))
+ component.Chunks.Remove(index);
}
+
+ break;
}
+ default:
+ return;
}
- args.WorldHandle.SetTransform(Matrix3.Identity);
+ foreach (var (origin, chunk) in modifiedChunks)
+ {
+ var newChunk = new NavMapChunk(origin);
+ Array.Copy(chunk, newChunk.TileData, chunk.Length);
+ component.Chunks[origin] = newChunk;
+ }
+
+ component.Beacons.Clear();
+ foreach (var (nuid, beacon) in beacons)
+ {
+ component.Beacons[nuid] = beacon;
+ }
}
}
diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs
index 3b426e73d8..3c99a18818 100644
--- a/Content.Client/Pinpointer/UI/NavMapControl.cs
+++ b/Content.Client/Pinpointer/UI/NavMapControl.cs
@@ -16,6 +16,9 @@
using Robust.Shared.Timing;
using System.Numerics;
using JetBrains.Annotations;
+using Content.Shared.Atmos;
+using System.Linq;
+using Robust.Shared.Utility;
namespace Content.Client.Pinpointer.UI;
@@ -25,12 +28,15 @@ namespace Content.Client.Pinpointer.UI;
[UsedImplicitly, Virtual]
public partial class NavMapControl : MapGridControl
{
- [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private IResourceCache _cache = default!;
private readonly SharedTransformSystem _transformSystem;
+ private readonly SharedNavMapSystem _navMapSystem;
public EntityUid? Owner;
public EntityUid? MapUid;
+ protected override bool Draggable => true;
+
// Actions
public event Action? TrackedEntitySelectedAction;
public event Action? PostWallDrawingAction;
@@ -38,7 +44,10 @@ public partial class NavMapControl : MapGridControl
// Tracked data
public Dictionary TrackedCoordinates = new();
public Dictionary TrackedEntities = new();
- public Dictionary>? TileGrid = default!;
+
+ public List<(Vector2, Vector2)> TileLines = new();
+ public List<(Vector2, Vector2)> TileRects = new();
+ public List<(Vector2[], Color)> TilePolygons = new();
// Default colors
public Color WallColor = new(102, 217, 102);
@@ -47,23 +56,26 @@ public partial class NavMapControl : MapGridControl
// Constants
protected float UpdateTime = 1.0f;
protected float MaxSelectableDistance = 10f;
- protected float RecenterMinimum = 0.05f;
protected float MinDragDistance = 5f;
protected static float MinDisplayedRange = 8f;
protected static float MaxDisplayedRange = 128f;
protected static float DefaultDisplayedRange = 48f;
+ protected float MinmapScaleModifier = 0.075f;
+ protected float FullWallInstep = 0.165f;
+ protected float ThinWallThickness = 0.165f;
+ protected float ThinDoorThickness = 0.30f;
// Local variables
- private Vector2 _offset;
- private bool _draggin;
- private Vector2 _startDragPosition = default!;
- private bool _recentering = false;
- private float _updateTimer = 0.25f;
- private Dictionary _sRGBLookUp = new Dictionary();
- public Color _backgroundColor;
- public float _backgroundOpacity = 0.9f;
+ private float _updateTimer = 1.0f;
+ private Dictionary _sRGBLookUp = new();
+ protected Color BackgroundColor;
+ protected float BackgroundOpacity = 0.9f;
private int _targetFontsize = 8;
- private IResourceCache _cache;
+
+ private Dictionary _horizLines = new();
+ private Dictionary _horizLinesReversed = new();
+ private Dictionary _vertLines = new();
+ private Dictionary _vertLinesReversed = new();
// Components
private NavMapComponent? _navMap;
@@ -76,6 +88,7 @@ public partial class NavMapControl : MapGridControl
private readonly Label _zoom = new()
{
VerticalAlignment = VAlignment.Top,
+ HorizontalExpand = true,
Margin = new Thickness(8f, 8f),
};
@@ -84,6 +97,7 @@ public partial class NavMapControl : MapGridControl
Text = Loc.GetString("navmap-recenter"),
VerticalAlignment = VAlignment.Top,
HorizontalAlignment = HAlignment.Right,
+ HorizontalExpand = true,
Margin = new Thickness(8f, 4f),
Disabled = true,
};
@@ -91,19 +105,21 @@ public partial class NavMapControl : MapGridControl
private readonly CheckBox _beacons = new()
{
Text = Loc.GetString("navmap-toggle-beacons"),
- Margin = new Thickness(4f, 0f),
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
+ HorizontalExpand = true,
+ Margin = new Thickness(4f, 0f),
Pressed = true,
};
public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDisplayedRange)
{
IoCManager.InjectDependencies(this);
- _cache = IoCManager.Resolve();
- _transformSystem = _entManager.System();
- _backgroundColor = Color.FromSrgb(TileColor.WithAlpha(_backgroundOpacity));
+ _transformSystem = EntManager.System();
+ _navMapSystem = EntManager.System();
+
+ BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity));
RectClipContent = true;
HorizontalExpand = true;
@@ -117,17 +133,27 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl
BorderColor = StyleNano.PanelDark
},
VerticalExpand = false,
+ HorizontalExpand = true,
+ SetWidth = 650f,
Children =
{
- _zoom,
- _beacons,
- _recenter,
+ new BoxContainer()
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
+ Children =
+ {
+ _zoom,
+ _beacons,
+ _recenter
+ }
+ }
}
};
var topContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
+ HorizontalExpand = true,
Children =
{
topPanel,
@@ -145,21 +171,19 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl
_recenter.OnPressed += args =>
{
- _recentering = true;
+ Recentering = true;
};
ForceNavMapUpdate();
}
- public void ForceRecenter()
- {
- _recentering = true;
- }
-
public void ForceNavMapUpdate()
{
- _entManager.TryGetComponent(MapUid, out _navMap);
- _entManager.TryGetComponent(MapUid, out _grid);
+ EntManager.TryGetComponent(MapUid, out _navMap);
+ EntManager.TryGetComponent(MapUid, out _grid);
+ EntManager.TryGetComponent(MapUid, out _xform);
+ EntManager.TryGetComponent(MapUid, out _physics);
+ EntManager.TryGetComponent(MapUid, out _fixtures);
UpdateNavMap();
}
@@ -167,29 +191,15 @@ public void ForceNavMapUpdate()
public void CenterToCoordinates(EntityCoordinates coordinates)
{
if (_physics != null)
- _offset = new Vector2(coordinates.X, coordinates.Y) - _physics.LocalCenter;
+ Offset = new Vector2(coordinates.X, coordinates.Y) - _physics.LocalCenter;
_recenter.Disabled = false;
}
- protected override void KeyBindDown(GUIBoundKeyEventArgs args)
- {
- base.KeyBindDown(args);
-
- if (args.Function == EngineKeyFunctions.Use)
- {
- _startDragPosition = args.PointerLocation.Position;
- _draggin = true;
- }
- }
-
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
- if (args.Function == EngineKeyFunctions.Use)
- _draggin = false;
-
if (args.Function == EngineKeyFunctions.UIClick)
{
if (TrackedEntitySelectedAction == null)
@@ -199,16 +209,16 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args)
return;
// If the cursor has moved a significant distance, exit
- if ((_startDragPosition - args.PointerLocation.Position).Length() > MinDragDistance)
+ if ((StartDragPosition - args.PointerLocation.Position).Length() > MinDragDistance)
return;
// Get the clicked position
- var offset = _offset + _physics.LocalCenter;
+ var offset = Offset + _physics.LocalCenter;
var localPosition = args.PointerLocation.Position - GlobalPixelPosition;
// Convert to a world position
- var unscaledPosition = (localPosition - MidpointVector) / MinimapScale;
- var worldPosition = _transformSystem.GetWorldMatrix(_xform).Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset);
+ var unscaledPosition = (localPosition - MidPointVector) / MinimapScale;
+ var worldPosition = Vector2.Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset, _transformSystem.GetWorldMatrix(_xform));
// Find closest tracked entity in range
var closestEntity = NetEntity.Invalid;
@@ -219,7 +229,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args)
if (!blip.Selectable)
continue;
- var currentDistance = (blip.Coordinates.ToMapPos(_entManager, _transformSystem) - worldPosition).Length();
+ var currentDistance = (blip.Coordinates.ToMapPos(EntManager, _transformSystem) - worldPosition).Length();
if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance)
continue;
@@ -251,15 +261,8 @@ protected override void MouseMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
- if (!_draggin)
- return;
-
- _recentering = false;
- _offset -= new Vector2(args.Relative.X, -args.Relative.Y) / MidPoint * WorldRange;
-
- if (_offset != Vector2.Zero)
+ if (Offset != Vector2.Zero)
_recenter.Disabled = false;
-
else
_recenter.Disabled = true;
}
@@ -269,140 +272,99 @@ protected override void Draw(DrawingHandleScreen handle)
base.Draw(handle);
// Get the components necessary for drawing the navmap
- _entManager.TryGetComponent(MapUid, out _navMap);
- _entManager.TryGetComponent(MapUid, out _grid);
- _entManager.TryGetComponent(MapUid, out _xform);
- _entManager.TryGetComponent(MapUid, out _physics);
- _entManager.TryGetComponent(MapUid, out _fixtures);
+ EntManager.TryGetComponent(MapUid, out _navMap);
+ EntManager.TryGetComponent(MapUid, out _grid);
+ EntManager.TryGetComponent(MapUid, out _xform);
+ EntManager.TryGetComponent(MapUid, out _physics);
+ EntManager.TryGetComponent(MapUid, out _fixtures);
- // Map re-centering
- if (_recentering)
- {
- var frameTime = Timing.FrameTime;
- var diff = _offset * (float) frameTime.TotalSeconds;
-
- if (_offset.LengthSquared() < RecenterMinimum)
- {
- _offset = Vector2.Zero;
- _recentering = false;
- _recenter.Disabled = true;
- }
- else
- {
- _offset -= diff * 5f;
- }
- }
+ if (_navMap == null || _grid == null || _xform == null)
+ return;
- _zoom.Text = Loc.GetString("navmap-zoom", ("value", $"{(DefaultDisplayedRange / WorldRange ):0.0}"));
+ // Map re-centering
+ _recenter.Disabled = DrawRecenter();
- if (_navMap == null || _xform == null)
- return;
+ // Update zoom text
+ _zoom.Text = Loc.GetString("navmap-zoom", ("value", $"{(DefaultDisplayedRange / WorldRange):0.0}"));
- var offset = _offset;
+ // Update offset with physics local center
+ var offset = Offset;
if (_physics != null)
offset += _physics.LocalCenter;
- // Draw tiles
- if (_fixtures != null)
+ var offsetVec = new Vector2(offset.X, -offset.Y);
+
+ // Wall sRGB
+ if (!_sRGBLookUp.TryGetValue(WallColor, out var wallsRGB))
+ {
+ wallsRGB = Color.ToSrgb(WallColor);
+ _sRGBLookUp[WallColor] = wallsRGB;
+ }
+
+ // Draw floor tiles
+ if (TilePolygons.Any())
{
Span verts = new Vector2[8];
- foreach (var fixture in _fixtures.Fixtures.Values)
+ foreach (var (polygonVerts, polygonColor) in TilePolygons)
{
- if (fixture.Shape is not PolygonShape poly)
- continue;
-
- for (var i = 0; i < poly.VertexCount; i++)
+ for (var i = 0; i < polygonVerts.Length; i++)
{
- var vert = poly.Vertices[i] - offset;
-
- verts[i] = Scale(new Vector2(vert.X, -vert.Y));
+ var vert = polygonVerts[i] - offset;
+ verts[i] = ScalePosition(new Vector2(vert.X, -vert.Y));
}
- handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..poly.VertexCount], TileColor);
+ handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..polygonVerts.Length], polygonColor);
}
}
- var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset);
-
- // Drawing lines can be rather expensive due to the number of neighbors that need to be checked in order
- // to figure out where they should be drawn. However, we don't *need* to do check these every frame.
- // Instead, lets periodically update where to draw each line and then store these points in a list.
- // Then we can just run through the list each frame and draw the lines without any extra computation.
-
- // Draw walls
- if (TileGrid != null && TileGrid.Count > 0)
+ // Draw map lines
+ if (TileLines.Any())
{
- var walls = new ValueList();
+ var lines = new ValueList(TileLines.Count * 2);
- foreach ((var chunk, var chunkedLines) in TileGrid)
+ foreach (var (o, t) in TileLines)
{
- var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
-
- if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
- continue;
-
- if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
- continue;
+ var origin = ScalePosition(o - offsetVec);
+ var terminus = ScalePosition(t - offsetVec);
- foreach (var chunkedLine in chunkedLines)
- {
- var start = Scale(chunkedLine.Origin - new Vector2(offset.X, -offset.Y));
- var end = Scale(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y));
-
- walls.Add(start);
- walls.Add(end);
- }
+ lines.Add(origin);
+ lines.Add(terminus);
}
- if (walls.Count > 0)
- {
- if (!_sRGBLookUp.TryGetValue(WallColor, out var sRGB))
- {
- sRGB = Color.ToSrgb(WallColor);
- _sRGBLookUp[WallColor] = sRGB;
- }
-
- handle.DrawPrimitives(DrawPrimitiveTopology.LineList, walls.Span, sRGB);
- }
+ if (lines.Count > 0)
+ handle.DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, wallsRGB);
}
- var airlockBuffer = Vector2.One * (MinimapScale / 2.25f) * 0.75f;
- var airlockLines = new ValueList();
- var foobarVec = new Vector2(1, -1);
-
- foreach (var airlock in _navMap.Airlocks)
+ // Draw map rects
+ if (TileRects.Any())
{
- var position = airlock.Position - offset;
- position = Scale(position with { Y = -position.Y });
- airlockLines.Add(position + airlockBuffer);
- airlockLines.Add(position - airlockBuffer * foobarVec);
-
- airlockLines.Add(position + airlockBuffer);
- airlockLines.Add(position + airlockBuffer * foobarVec);
+ var rects = new ValueList(TileRects.Count * 8);
- airlockLines.Add(position - airlockBuffer);
- airlockLines.Add(position + airlockBuffer * foobarVec);
-
- airlockLines.Add(position - airlockBuffer);
- airlockLines.Add(position - airlockBuffer * foobarVec);
-
- airlockLines.Add(position + airlockBuffer * -Vector2.UnitY);
- airlockLines.Add(position - airlockBuffer * -Vector2.UnitY);
- }
-
- if (airlockLines.Count > 0)
- {
- if (!_sRGBLookUp.TryGetValue(WallColor, out var sRGB))
+ foreach (var (lt, rb) in TileRects)
{
- sRGB = Color.ToSrgb(WallColor);
- _sRGBLookUp[WallColor] = sRGB;
+ var leftTop = ScalePosition(lt - offsetVec);
+ var rightBottom = ScalePosition(rb - offsetVec);
+
+ var rightTop = new Vector2(rightBottom.X, leftTop.Y);
+ var leftBottom = new Vector2(leftTop.X, rightBottom.Y);
+
+ rects.Add(leftTop);
+ rects.Add(rightTop);
+ rects.Add(rightTop);
+ rects.Add(rightBottom);
+ rects.Add(rightBottom);
+ rects.Add(leftBottom);
+ rects.Add(leftBottom);
+ rects.Add(leftTop);
}
- handle.DrawPrimitives(DrawPrimitiveTopology.LineList, airlockLines.Span, sRGB);
+ if (rects.Count > 0)
+ handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, wallsRGB);
}
+ // Invoke post wall drawing action
if (PostWallDrawingAction != null)
PostWallDrawingAction.Invoke(handle);
@@ -412,16 +374,16 @@ protected override void Draw(DrawingHandleScreen handle)
var rectBuffer = new Vector2(5f, 3f);
// Calculate font size for current zoom level
- var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize , 0);
+ var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0);
var font = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize);
- foreach (var beacon in _navMap.Beacons)
+ foreach (var beacon in _navMap.Beacons.Values)
{
var position = beacon.Position - offset;
- position = Scale(position with { Y = -position.Y });
+ position = ScalePosition(position with { Y = -position.Y });
var textDimensions = handle.GetDimensions(font, beacon.Text, 1f);
- handle.DrawRect(new UIBox2(position - textDimensions / 2 - rectBuffer, position + textDimensions / 2 + rectBuffer), _backgroundColor);
+ handle.DrawRect(new UIBox2(position - textDimensions / 2 - rectBuffer, position + textDimensions / 2 + rectBuffer), BackgroundColor);
handle.DrawString(font, position - textDimensions / 2, beacon.Text, beacon.Color);
}
}
@@ -435,12 +397,12 @@ protected override void Draw(DrawingHandleScreen handle)
{
if (lit && value.Visible)
{
- var mapPos = coord.ToMap(_entManager, _transformSystem);
+ var mapPos = coord.ToMap(EntManager, _transformSystem);
if (mapPos.MapId != MapId.Nullspace)
{
- var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset;
- position = Scale(new Vector2(position.X, -position.Y));
+ var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset;
+ position = ScalePosition(new Vector2(position.X, -position.Y));
handle.DrawCircle(position, float.Sqrt(MinimapScale) * 2f, value.Color);
}
@@ -448,8 +410,6 @@ protected override void Draw(DrawingHandleScreen handle)
}
// Tracked entities (can use a supplied sprite as a marker instead; should probably just replace TrackedCoordinates with this eventually)
- var iconVertexUVs = new Dictionary<(Texture, Color), ValueList>();
-
foreach (var blip in TrackedEntities.Values)
{
if (blip.Blinks && !lit)
@@ -458,39 +418,18 @@ protected override void Draw(DrawingHandleScreen handle)
if (blip.Texture == null)
continue;
- if (!iconVertexUVs.TryGetValue((blip.Texture, blip.Color), out var vertexUVs))
- vertexUVs = new();
-
- var mapPos = blip.Coordinates.ToMap(_entManager, _transformSystem);
+ var mapPos = blip.Coordinates.ToMap(EntManager, _transformSystem);
if (mapPos.MapId != MapId.Nullspace)
{
- var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset;
- position = Scale(new Vector2(position.X, -position.Y));
-
- var scalingCoefficient = 2.5f;
- var positionOffset = scalingCoefficient * float.Sqrt(MinimapScale);
-
- vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y - positionOffset), new Vector2(1f, 1f)));
- vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y + positionOffset), new Vector2(1f, 0f)));
- vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y - positionOffset), new Vector2(0f, 1f)));
- vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y + positionOffset), new Vector2(1f, 0f)));
- vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y - positionOffset), new Vector2(0f, 1f)));
- vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y + positionOffset), new Vector2(0f, 0f)));
- }
+ var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset;
+ position = ScalePosition(new Vector2(position.X, -position.Y));
- iconVertexUVs[(blip.Texture, blip.Color)] = vertexUVs;
- }
+ var scalingCoefficient = MinmapScaleModifier * float.Sqrt(MinimapScale);
+ var positionOffset = new Vector2(scalingCoefficient * blip.Texture.Width, scalingCoefficient * blip.Texture.Height);
- foreach ((var (texture, color), var vertexUVs) in iconVertexUVs)
- {
- if (!_sRGBLookUp.TryGetValue(color, out var sRGB))
- {
- sRGB = Color.ToSrgb(color);
- _sRGBLookUp[color] = sRGB;
+ handle.DrawTextureRect(blip.Texture, new UIBox2(position - positionOffset, position + positionOffset), blip.Color);
}
-
- handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, texture, vertexUVs.Span, sRGB);
}
}
@@ -509,133 +448,270 @@ protected override void FrameUpdate(FrameEventArgs args)
protected virtual void UpdateNavMap()
{
- if (_navMap == null || _grid == null)
+ // Clear stale values
+ TilePolygons.Clear();
+ TileLines.Clear();
+ TileRects.Clear();
+
+ UpdateNavMapFloorTiles();
+ UpdateNavMapWallLines();
+ UpdateNavMapAirlocks();
+ }
+
+ private void UpdateNavMapFloorTiles()
+ {
+ if (_fixtures == null)
return;
- TileGrid = GetDecodedWallChunks(_navMap.Chunks, _grid);
+ var verts = new Vector2[8];
+
+ foreach (var fixture in _fixtures.Fixtures.Values)
+ {
+ if (fixture.Shape is not PolygonShape poly)
+ continue;
+
+ for (var i = 0; i < poly.VertexCount; i++)
+ {
+ var vert = poly.Vertices[i];
+ verts[i] = new Vector2(MathF.Round(vert.X), MathF.Round(vert.Y));
+ }
+
+ TilePolygons.Add((verts[..poly.VertexCount], TileColor));
+ }
}
- public Dictionary> GetDecodedWallChunks
- (Dictionary chunks,
- MapGridComponent grid)
+ private void UpdateNavMapWallLines()
{
- var decodedOutput = new Dictionary>();
+ if (_navMap == null || _grid == null)
+ return;
- foreach ((var chunkOrigin, var chunk) in chunks)
- {
- var list = new List();
+ // We'll use the following dictionaries to combine collinear wall lines
+ _horizLines.Clear();
+ _horizLinesReversed.Clear();
+ _vertLines.Clear();
+ _vertLinesReversed.Clear();
+
+ const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall;
+ const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall;
+ const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall;
+ const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall;
- // TODO: Okay maybe I should just use ushorts lmao...
- for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
+ foreach (var (chunkOrigin, chunk) in _navMap.Chunks)
+ {
+ for (var i = 0; i < SharedNavMapSystem.ArraySize; i++)
{
- var value = (int) Math.Pow(2, i);
+ var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask;
+ if (tileData == 0)
+ continue;
+
+ tileData >>= (int) NavMapChunkType.Wall;
- var mask = chunk.TileData & value;
+ var relativeTile = SharedNavMapSystem.GetTileFromIndex(i);
+ var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
- if (mask == 0x0)
+ if (tileData != SharedNavMapSystem.AllDirMask)
+ {
+ AddRectForThinWall(tileData, tile);
continue;
+ }
- // Alright now we'll work out our edges
- var relativeTile = SharedNavMapSystem.GetTile(mask);
- var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize;
- var position = new Vector2(tile.X, -tile.Y);
+ tile = tile with { Y = -tile.Y };
NavMapChunk? neighborChunk;
- bool neighbor;
// North edge
- if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
- {
- neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
- (neighborChunk.TileData &
- SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
- }
- else
- {
- var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
- neighbor = (chunk.TileData & flag) != 0x0;
- }
+ var neighborData = 0;
+ if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1)
+ neighborData = chunk.TileData[i+1];
+ else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk))
+ neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize];
- if (!neighbor)
+ if ((neighborData & southMask) == 0)
{
- // Add points
- list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, -grid.TileSize)));
+ AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize),
+ tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines,
+ _horizLinesReversed);
}
// East edge
- if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
- {
- neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
- (neighborChunk.TileData &
- SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
- }
- else
- {
- var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
- neighbor = (chunk.TileData & flag) != 0x0;
- }
+ neighborData = 0;
+ if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1)
+ neighborData = chunk.TileData[i+SharedNavMapSystem.ChunkSize];
+ else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk))
+ neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize];
- if (!neighbor)
+ if ((neighborData & westMask) == 0)
{
- // Add points
- list.Add(new NavMapLine(position + new Vector2(grid.TileSize, -grid.TileSize), position + new Vector2(grid.TileSize, 0f)));
+ AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize),
+ tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed);
}
// South edge
- if (relativeTile.Y == 0)
- {
- neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, -1), out neighborChunk) &&
- (neighborChunk.TileData &
- SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
- }
- else
- {
- var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, -1));
- neighbor = (chunk.TileData & flag) != 0x0;
- }
+ neighborData = 0;
+ if (relativeTile.Y != 0)
+ neighborData = chunk.TileData[i-1];
+ else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk))
+ neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize];
- if (!neighbor)
+ if ((neighborData & northMask) == 0)
{
- // Add points
- list.Add(new NavMapLine(position + new Vector2(grid.TileSize, 0f), position));
+ AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines,
+ _horizLinesReversed);
}
// West edge
- if (relativeTile.X == 0)
- {
- neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(-1, 0), out neighborChunk) &&
- (neighborChunk.TileData &
- SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
- }
- else
+ neighborData = 0;
+ if (relativeTile.X != 0)
+ neighborData = chunk.TileData[i-SharedNavMapSystem.ChunkSize];
+ else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk))
+ neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize];
+
+ if ((neighborData & eastMask) == 0)
{
- var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(-1, 0));
- neighbor = (chunk.TileData & flag) != 0x0;
+ AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, _vertLines,
+ _vertLinesReversed);
}
- if (!neighbor)
+ // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these
+ TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0)));
+ }
+ }
+
+ // Record the combined lines
+ foreach (var (origin, terminal) in _horizLines)
+ {
+ TileLines.Add((origin, terminal));
+ }
+
+ foreach (var (origin, terminal) in _vertLines)
+ {
+ TileLines.Add((origin, terminal));
+ }
+ }
+
+ private void UpdateNavMapAirlocks()
+ {
+ if (_navMap == null || _grid == null)
+ return;
+
+ foreach (var chunk in _navMap.Chunks.Values)
+ {
+ for (var i = 0; i < SharedNavMapSystem.ArraySize; i++)
+ {
+ var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask;
+ if (tileData == 0)
+ continue;
+
+ tileData >>= (int) NavMapChunkType.Airlock;
+
+ var relative = SharedNavMapSystem.GetTileFromIndex(i);
+ var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize;
+
+ // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge
+ if (tileData != SharedNavMapSystem.AllDirMask)
{
- // Add point
- list.Add(new NavMapLine(position, position + new Vector2(0f, -grid.TileSize)));
+ AddRectForThinAirlock(tileData, tile);
+ continue;
}
- // Draw a diagonal line for interiors.
- list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, 0f)));
+ // Otherwise add a single full tile airlock
+ TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep),
+ new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1)));
+
+ TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep),
+ new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1)));
}
+ }
+ }
+
+ private void AddRectForThinWall(int tileData, Vector2i tile)
+ {
+ var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness);
+ var rightBottom = new Vector2(0.5f, 0.5f);
+
+ for (var i = 0; i < SharedNavMapSystem.Directions; i++)
+ {
+ var dirMask = 1 << i;
+ if ((tileData & dirMask) == 0)
+ continue;
+
+ var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
- decodedOutput.Add(chunkOrigin, list);
+ // TODO NAVMAP
+ // Consider using faster rotation operations, given that these are always 90 degree increments
+ var angle = -((AtmosDirection) dirMask).ToAngle();
+ TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
}
+ }
+
+ private void AddRectForThinAirlock(int tileData, Vector2i tile)
+ {
+ var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness);
+ var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep);
+ var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness);
+ var centreBottom = new Vector2(0f, 0.5f - FullWallInstep);
- return decodedOutput;
+ for (var i = 0; i < SharedNavMapSystem.Directions; i++)
+ {
+ var dirMask = 1 << i;
+ if ((tileData & dirMask) == 0)
+ continue;
+
+ var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
+ var angle = -((AtmosDirection) dirMask).ToAngle();
+ TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
+ TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition));
+ }
}
- protected Vector2 Scale(Vector2 position)
+ protected void AddOrUpdateNavMapLine(
+ Vector2i origin,
+ Vector2i terminus,
+ Dictionary lookup,
+ Dictionary lookupReversed)
{
- return position * MinimapScale + MidpointVector;
+ Vector2i foundTermius;
+ Vector2i foundOrigin;
+
+ // Does our new line end at the beginning of an existing line?
+ if (lookup.Remove(terminus, out foundTermius))
+ {
+ DebugTools.Assert(lookupReversed[foundTermius] == terminus);
+
+ // Does our new line start at the end of an existing line?
+ if (lookupReversed.Remove(origin, out foundOrigin))
+ {
+ // Our new line just connects two existing lines
+ DebugTools.Assert(lookup[foundOrigin] == origin);
+ lookup[foundOrigin] = foundTermius;
+ lookupReversed[foundTermius] = foundOrigin;
+ }
+ else
+ {
+ // Our new line precedes an existing line, extending it further to the left
+ lookup[origin] = foundTermius;
+ lookupReversed[foundTermius] = origin;
+ }
+ return;
+ }
+
+ // Does our new line start at the end of an existing line?
+ if (lookupReversed.Remove(origin, out foundOrigin))
+ {
+ // Our new line just extends an existing line further to the right
+ DebugTools.Assert(lookup[foundOrigin] == origin);
+ lookup[foundOrigin] = terminus;
+ lookupReversed[terminus] = foundOrigin;
+ return;
+ }
+
+ // Completely disconnected line segment.
+ lookup.Add(origin, terminus);
+ lookupReversed.Add(terminus, origin);
}
protected Vector2 GetOffset()
{
- return _offset + (_physics != null ? _physics.LocalCenter : new Vector2());
+ return Offset + (_physics?.LocalCenter ?? new Vector2());
}
}
@@ -656,15 +732,3 @@ public NavMapBlip(EntityCoordinates coordinates, Texture texture, Color color, b
Selectable = selectable;
}
}
-
-public struct NavMapLine
-{
- public readonly Vector2 Origin;
- public readonly Vector2 Terminus;
-
- public NavMapLine(Vector2 origin, Vector2 terminus)
- {
- Origin = origin;
- Terminus = terminus;
- }
-}
diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
index f896d7fa39..bd4ac877db 100644
--- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
+++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
@@ -1,18 +1,20 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.CCVar;
using Content.Shared.Players;
+using Content.Shared.Players.JobWhitelist;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Roles;
using Robust.Client;
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Players.PlayTimeTracking;
-public sealed class JobRequirementsManager
+public sealed class JobRequirementsManager : ISharedPlaytimeManager
{
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClientNetManager _net = default!;
@@ -23,6 +25,7 @@ public sealed class JobRequirementsManager
private readonly Dictionary _roles = new();
private readonly List _roleBans = new();
+ private readonly List _jobWhitelists = new();
private ISawmill _sawmill = default!;
@@ -35,6 +38,7 @@ public void Initialize()
// Yeah the client manager handles role bans and playtime but the server ones are separate DEAL.
_net.RegisterNetMessage(RxRoleBans);
_net.RegisterNetMessage(RxPlayTime);
+ _net.RegisterNetMessage(RxJobWhitelist);
_client.RunLevelChanged += ClientOnRunLevelChanged;
}
@@ -78,6 +82,13 @@ private void RxPlayTime(MsgPlayTime message)
Updated?.Invoke();
}
+ private void RxJobWhitelist(MsgJobWhitelist message)
+ {
+ _jobWhitelists.Clear();
+ _jobWhitelists.AddRange(message.Whitelist);
+ Updated?.Invoke();
+ }
+
public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
{
reason = null;
@@ -88,24 +99,27 @@ public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessag
return false;
}
- if (job.Requirements == null ||
- !_cfg.GetCVar(CCVars.GameRoleTimers))
- {
- return true;
- }
+ if (!CheckWhitelist(job, out reason))
+ return false;
var player = _playerManager.LocalSession;
if (player == null)
return true;
- return CheckRoleTime(job.Requirements, out reason);
+ return CheckRoleTime(job, out reason);
+ }
+
+ public bool CheckRoleTime(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
+ {
+ var reqs = _entManager.System().GetJobRequirement(job);
+ return CheckRoleTime(reqs, out reason);
}
public bool CheckRoleTime(HashSet? requirements, [NotNullWhen(false)] out FormattedMessage? reason)
{
reason = null;
- if (requirements == null)
+ if (requirements == null || !_cfg.GetCVar(CCVars.GameRoleTimers))
return true;
var reasons = new List();
@@ -121,6 +135,21 @@ public bool CheckRoleTime(HashSet? requirements, [NotNullWhen(fa
return reason == null;
}
+ public bool CheckWhitelist(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
+ {
+ reason = default;
+ if (!_cfg.GetCVar(CCVars.GameRoleWhitelist))
+ return true;
+
+ if (job.Whitelisted && !_jobWhitelists.Contains(job.ID))
+ {
+ reason = FormattedMessage.FromUnformatted(Loc.GetString("role-not-whitelisted"));
+ return false;
+ }
+
+ return true;
+ }
+
public TimeSpan FetchOverallPlaytime()
{
return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero;
@@ -139,5 +168,13 @@ public IEnumerable> FetchPlaytimeByRoles()
}
}
+ public IReadOnlyDictionary GetPlayTimes(ICommonSession session)
+ {
+ if (session != _playerManager.LocalSession)
+ {
+ return new Dictionary();
+ }
+ return _roles;
+ }
}
diff --git a/Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs b/Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs
new file mode 100644
index 0000000000..5ba4878c6d
--- /dev/null
+++ b/Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs
@@ -0,0 +1,33 @@
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Polymorph.Components;
+using Content.Shared.Polymorph.Systems;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Polymorph.Systems;
+
+public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
+{
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+
+ private EntityQuery _appearanceQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _appearanceQuery = GetEntityQuery();
+
+ SubscribeLocalEvent(OnHandleState);
+ }
+
+ private void OnHandleState(Entity ent, ref AfterAutoHandleStateEvent args)
+ {
+ CopyComp(ent);
+ CopyComp(ent);
+ CopyComp(ent);
+
+ // reload appearance to hopefully prevent any invisible layers
+ if (_appearanceQuery.TryComp(ent, out var appearance))
+ _appearance.QueueUpdate(ent, appearance);
+ }
+}
diff --git a/Content.Client/Popups/PopupOverlay.cs b/Content.Client/Popups/PopupOverlay.cs
index 1305d8bb94..77eeb611f5 100644
--- a/Content.Client/Popups/PopupOverlay.cs
+++ b/Content.Client/Popups/PopupOverlay.cs
@@ -1,3 +1,4 @@
+using System.Numerics;
using Content.Shared.Examine;
using Robust.Client.Graphics;
using Robust.Client.Player;
@@ -21,7 +22,8 @@ public sealed class PopupOverlay : Overlay
private readonly IUserInterfaceManager _uiManager;
private readonly PopupSystem _popup;
private readonly PopupUIController _controller;
-
+ private readonly ExamineSystemShared _examine;
+ private readonly SharedTransformSystem _transform;
private readonly ShaderInstance _shader;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
@@ -33,12 +35,16 @@ public PopupOverlay(
IPrototypeManager protoManager,
IUserInterfaceManager uiManager,
PopupUIController controller,
+ ExamineSystemShared examine,
+ SharedTransformSystem transform,
PopupSystem popup)
{
_configManager = configManager;
_entManager = entManager;
_playerMgr = playerMgr;
_uiManager = uiManager;
+ _examine = examine;
+ _transform = transform;
_popup = popup;
_controller = controller;
@@ -50,7 +56,7 @@ protected override void Draw(in OverlayDrawArgs args)
if (args.ViewportControl == null)
return;
- args.DrawingHandle.SetTransform(Matrix3.Identity);
+ args.DrawingHandle.SetTransform(Matrix3x2.Identity);
args.DrawingHandle.UseShader(_shader);
var scale = _configManager.GetCVar(CVars.DisplayUIScale);
@@ -73,7 +79,7 @@ private void DrawWorld(DrawingHandleScreen worldHandle, OverlayDrawArgs args, fl
foreach (var popup in _popup.WorldLabels)
{
- var mapPos = popup.InitialPos.ToMap(_entManager);
+ var mapPos = popup.InitialPos.ToMap(_entManager, _transform);
if (mapPos.MapId != args.MapId)
continue;
@@ -81,11 +87,11 @@ private void DrawWorld(DrawingHandleScreen worldHandle, OverlayDrawArgs args, fl
var distance = (mapPos.Position - args.WorldBounds.Center).Length();
// Should handle fade here too wyci.
- if (!args.WorldBounds.Contains(mapPos.Position) || !ExamineSystemShared.InRangeUnOccluded(viewPos, mapPos, distance,
+ if (!args.WorldBounds.Contains(mapPos.Position) || !_examine.InRangeUnOccluded(viewPos, mapPos, distance,
e => e == popup.InitialPos.EntityId || e == ourEntity, entMan: _entManager))
continue;
- var pos = matrix.Transform(mapPos.Position);
+ var pos = Vector2.Transform(mapPos.Position, matrix);
_controller.DrawPopup(popup, worldHandle, pos, scale);
}
}
diff --git a/Content.Client/Popups/PopupSystem.cs b/Content.Client/Popups/PopupSystem.cs
index 2c923ae3a7..700f6b6d26 100644
--- a/Content.Client/Popups/PopupSystem.cs
+++ b/Content.Client/Popups/PopupSystem.cs
@@ -1,18 +1,20 @@
using System.Linq;
+using Content.Shared.Containers;
+using Content.Shared.Examine;
using Content.Shared.GameTicking;
using Content.Shared.Popups;
+using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
-using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
+using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Replays;
using Robust.Shared.Timing;
-using Robust.Shared.Utility;
namespace Content.Client.Popups
{
@@ -26,12 +28,14 @@ public sealed class PopupSystem : SharedPopupSystem
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
+ [Dependency] private readonly ExamineSystemShared _examine = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
- public IReadOnlyList WorldLabels => _aliveWorldLabels;
- public IReadOnlyList CursorLabels => _aliveCursorLabels;
+ public IReadOnlyCollection WorldLabels => _aliveWorldLabels.Values;
+ public IReadOnlyCollection CursorLabels => _aliveCursorLabels.Values;
- private readonly List _aliveWorldLabels = new();
- private readonly List _aliveCursorLabels = new();
+ private readonly Dictionary _aliveWorldLabels = new();
+ private readonly Dictionary _aliveCursorLabels = new();
public const float MinimumPopupLifetime = 0.7f;
public const float MaximumPopupLifetime = 5f;
@@ -51,6 +55,8 @@ public override void Initialize()
_prototype,
_uiManager,
_uiManager.GetUIController(),
+ _examine,
+ _transform,
this));
}
@@ -61,6 +67,15 @@ public override void Shutdown()
.RemoveOverlay();
}
+ private void WrapAndRepeatPopup(PopupLabel existingLabel, string popupMessage)
+ {
+ existingLabel.TotalTime = 0;
+ existingLabel.Repeats += 1;
+ existingLabel.Text = Loc.GetString("popup-system-repeated-popup-stacking-wrap",
+ ("popup-message", popupMessage),
+ ("count", existingLabel.Repeats));
+ }
+
private void PopupMessage(string? message, PopupType type, EntityCoordinates coordinates, EntityUid? entity, bool recordReplay)
{
if (message == null)
@@ -74,13 +89,20 @@ private void PopupMessage(string? message, PopupType type, EntityCoordinates coo
_replayRecording.RecordClientMessage(new PopupCoordinatesEvent(message, type, GetNetCoordinates(coordinates)));
}
+ var popupData = new WorldPopupData(message, type, coordinates, entity);
+ if (_aliveWorldLabels.TryGetValue(popupData, out var existingLabel))
+ {
+ WrapAndRepeatPopup(existingLabel, popupData.Message);
+ return;
+ }
+
var label = new WorldPopupLabel(coordinates)
{
Text = message,
Type = type,
};
- _aliveWorldLabels.Add(label);
+ _aliveWorldLabels.Add(popupData, label);
}
#region Abstract Method Implementations
@@ -109,13 +131,20 @@ private void PopupCursorInternal(string? message, PopupType type, bool recordRep
if (recordReplay && _replayRecording.IsRecording)
_replayRecording.RecordClientMessage(new PopupCursorEvent(message, type));
+ var popupData = new CursorPopupData(message, type);
+ if (_aliveCursorLabels.TryGetValue(popupData, out var existingLabel))
+ {
+ WrapAndRepeatPopup(existingLabel, popupData.Message);
+ return;
+ }
+
var label = new CursorPopupLabel(_inputManager.MouseScreenPosition)
{
Text = message,
Type = type,
};
- _aliveCursorLabels.Add(label);
+ _aliveCursorLabels.Add(popupData, label);
}
public override void PopupCursor(string? message, PopupType type = PopupType.Small)
@@ -150,7 +179,7 @@ public override void PopupEntity(string? message, EntityUid uid, ICommonSession
PopupEntity(message, uid, type);
}
- public override void PopupEntity(string? message, EntityUid uid, Filter filter, bool recordReplay, PopupType type=PopupType.Small)
+ public override void PopupEntity(string? message, EntityUid uid, Filter filter, bool recordReplay, PopupType type = PopupType.Small)
{
if (!filter.Recipients.Contains(_playerManager.LocalSession))
return;
@@ -158,10 +187,31 @@ public override void PopupEntity(string? message, EntityUid uid, Filter filter,
PopupEntity(message, uid, type);
}
- public override void PopupClient(string? message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small)
+ public override void PopupClient(string? message, EntityUid? recipient, PopupType type = PopupType.Small)
+ {
+ if (recipient == null)
+ return;
+
+ if (_timing.IsFirstTimePredicted)
+ PopupCursor(message, recipient.Value, type);
+ }
+
+ public override void PopupClient(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
+ {
+ if (recipient == null)
+ return;
+
+ if (_timing.IsFirstTimePredicted)
+ PopupEntity(message, uid, recipient.Value, type);
+ }
+
+ public override void PopupClient(string? message, EntityCoordinates coordinates, EntityUid? recipient, PopupType type = PopupType.Small)
{
+ if (recipient == null)
+ return;
+
if (_timing.IsFirstTimePredicted)
- PopupEntity(message, uid, recipient, type);
+ PopupCoordinates(message, coordinates, recipient.Value, type);
}
public override void PopupEntity(string? message, EntityUid uid, PopupType type = PopupType.Small)
@@ -170,6 +220,18 @@ public override void PopupEntity(string? message, EntityUid uid, PopupType type
PopupMessage(message, type, transform.Coordinates, uid, true);
}
+ public override void PopupPredicted(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
+ {
+ if (recipient != null && _timing.IsFirstTimePredicted)
+ PopupEntity(message, uid, recipient.Value, type);
+ }
+
+ public override void PopupPredicted(string? recipientMessage, string? othersMessage, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
+ {
+ if (recipient != null && _timing.IsFirstTimePredicted)
+ PopupEntity(recipientMessage, uid, recipient.Value, type);
+ }
+
#endregion
#region Network Event Handlers
@@ -212,27 +274,37 @@ public override void FrameUpdate(float frameTime)
if (_aliveWorldLabels.Count == 0 && _aliveCursorLabels.Count == 0)
return;
- for (var i = 0; i < _aliveWorldLabels.Count; i++)
+ if (_aliveWorldLabels.Count > 0)
{
- var label = _aliveWorldLabels[i];
- label.TotalTime += frameTime;
-
- if (label.TotalTime > GetPopupLifetime(label) || Deleted(label.InitialPos.EntityId))
+ var aliveWorldToRemove = new ValueList();
+ foreach (var (data, label) in _aliveWorldLabels)
+ {
+ label.TotalTime += frameTime;
+ if (label.TotalTime > GetPopupLifetime(label) || Deleted(label.InitialPos.EntityId))
+ {
+ aliveWorldToRemove.Add(data);
+ }
+ }
+ foreach (var data in aliveWorldToRemove)
{
- _aliveWorldLabels.RemoveSwap(i);
- i--;
+ _aliveWorldLabels.Remove(data);
}
}
- for (var i = 0; i < _aliveCursorLabels.Count; i++)
+ if (_aliveCursorLabels.Count > 0)
{
- var label = _aliveCursorLabels[i];
- label.TotalTime += frameTime;
-
- if (label.TotalTime > GetPopupLifetime(label))
+ var aliveCursorToRemove = new ValueList();
+ foreach (var (data, label) in _aliveCursorLabels)
+ {
+ label.TotalTime += frameTime;
+ if (label.TotalTime > GetPopupLifetime(label))
+ {
+ aliveCursorToRemove.Add(data);
+ }
+ }
+ foreach (var data in aliveCursorToRemove)
{
- _aliveCursorLabels.RemoveSwap(i);
- i--;
+ _aliveCursorLabels.Remove(data);
}
}
}
@@ -242,29 +314,32 @@ public abstract class PopupLabel
public PopupType Type = PopupType.Small;
public string Text { get; set; } = string.Empty;
public float TotalTime { get; set; }
+ public int Repeats = 1;
}
- public sealed class CursorPopupLabel : PopupLabel
- {
- public ScreenCoordinates InitialPos;
-
- public CursorPopupLabel(ScreenCoordinates screenCoords)
- {
- InitialPos = screenCoords;
- }
- }
-
- public sealed class WorldPopupLabel : PopupLabel
+ public sealed class WorldPopupLabel(EntityCoordinates coordinates) : PopupLabel
{
///
/// The original EntityCoordinates of the label.
///
- public EntityCoordinates InitialPos;
+ public EntityCoordinates InitialPos = coordinates;
+ }
- public WorldPopupLabel(EntityCoordinates coordinates)
- {
- InitialPos = coordinates;
- }
+ public sealed class CursorPopupLabel(ScreenCoordinates screenCoords) : PopupLabel
+ {
+ public ScreenCoordinates InitialPos = screenCoords;
}
+
+ [UsedImplicitly]
+ private record struct WorldPopupData(
+ string Message,
+ PopupType Type,
+ EntityCoordinates Coordinates,
+ EntityUid? Entity);
+
+ [UsedImplicitly]
+ private record struct CursorPopupData(
+ string Message,
+ PopupType Type);
}
}
diff --git a/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs b/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs
new file mode 100644
index 0000000000..5a082485a5
--- /dev/null
+++ b/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs
@@ -0,0 +1,27 @@
+using Content.Client.Power.EntitySystems;
+using Content.Shared.Popups;
+using Content.Shared.Power.Components;
+using Content.Shared.Power.EntitySystems;
+using Content.Shared.UserInterface;
+using Content.Shared.Wires;
+
+namespace Content.Client.Power;
+
+public sealed class ActivatableUIRequiresPowerSystem : SharedActivatableUIRequiresPowerSystem
+{
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ protected override void OnActivate(Entity ent, ref ActivatableUIOpenAttemptEvent args)
+ {
+ if (args.Cancelled || this.IsPowered(ent.Owner, EntityManager))
+ {
+ return;
+ }
+
+ if (TryComp(ent.Owner, out var panel) && panel.Open)
+ return;
+
+ _popup.PopupClient(Loc.GetString("base-computer-ui-component-not-powered", ("machine", ent.Owner)), args.User, args.User);
+ args.Cancel();
+ }
+}
diff --git a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs
new file mode 100644
index 0000000000..fbebcb7cf8
--- /dev/null
+++ b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs
@@ -0,0 +1,8 @@
+using Content.Shared.Power.Components;
+
+namespace Content.Client.Power.Components;
+
+[RegisterComponent]
+public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent
+{
+}
diff --git a/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs b/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs
new file mode 100644
index 0000000000..4d56592c23
--- /dev/null
+++ b/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs
@@ -0,0 +1,23 @@
+using Content.Client.Power.Components;
+using Content.Shared.Power.Components;
+using Content.Shared.Power.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Client.Power.EntitySystems;
+
+public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnHandleState);
+ }
+
+ private void OnHandleState(EntityUid uid, ApcPowerReceiverComponent component, ref ComponentHandleState args)
+ {
+ if (args.Current is not ApcPowerReceiverComponentState state)
+ return;
+
+ component.Powered = state.Powered;
+ }
+}
diff --git a/Content.Client/Power/EntitySystems/StaticPowerSystem.cs b/Content.Client/Power/EntitySystems/StaticPowerSystem.cs
new file mode 100644
index 0000000000..2ca945cbbd
--- /dev/null
+++ b/Content.Client/Power/EntitySystems/StaticPowerSystem.cs
@@ -0,0 +1,16 @@
+using Content.Client.Power.Components;
+
+namespace Content.Client.Power.EntitySystems;
+
+public static class StaticPowerSystem
+{
+ // Using this makes the call shorter.
+ // ReSharper disable once UnusedParameter.Global
+ public static bool IsPowered(this EntitySystem system, EntityUid uid, IEntityManager entManager, ApcPowerReceiverComponent? receiver = null)
+ {
+ if (receiver == null && !entManager.TryGetComponent(uid, out receiver))
+ return false;
+
+ return receiver.Powered;
+ }
+}
diff --git a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs
index 9f537f3858..d5057416cf 100644
--- a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs
+++ b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs
@@ -5,6 +5,7 @@
using Robust.Shared.Collections;
using Robust.Shared.Map.Components;
using System.Numerics;
+using static Content.Shared.Power.SharedPowerMonitoringConsoleSystem;
namespace Content.Client.Power;
@@ -23,8 +24,13 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
public PowerMonitoringCableNetworksComponent? PowerMonitoringCableNetworks;
public List HiddenLineGroups = new();
- public Dictionary>? PowerCableNetwork;
- public Dictionary>? FocusCableNetwork;
+ public List PowerCableNetwork = new();
+ public List FocusCableNetwork = new();
+
+ private Dictionary[] _horizLines = [new(), new(), new()];
+ private Dictionary[] _horizLinesReversed = [new(), new(), new()];
+ private Dictionary[] _vertLines = [new(), new(), new()];
+ private Dictionary[] _vertLinesReversed = [new(), new(), new()];
private MapGridComponent? _grid;
@@ -33,7 +39,7 @@ public PowerMonitoringConsoleNavMapControl() : base()
// Set colors
TileColor = new Color(30, 57, 67);
WallColor = new Color(102, 164, 217);
- _backgroundColor = Color.FromSrgb(TileColor.WithAlpha(_backgroundOpacity));
+ BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity));
PostWallDrawingAction += DrawAllCableNetworks;
}
@@ -48,15 +54,15 @@ protected override void UpdateNavMap()
if (!_entManager.TryGetComponent(Owner, out var cableNetworks))
return;
- if (!_entManager.TryGetComponent(MapUid, out _grid))
- return;
-
- PowerCableNetwork = GetDecodedPowerCableChunks(cableNetworks.AllChunks, _grid);
- FocusCableNetwork = GetDecodedPowerCableChunks(cableNetworks.FocusChunks, _grid);
+ PowerCableNetwork = GetDecodedPowerCableChunks(cableNetworks.AllChunks);
+ FocusCableNetwork = GetDecodedPowerCableChunks(cableNetworks.FocusChunks);
}
public void DrawAllCableNetworks(DrawingHandleScreen handle)
{
+ if (!_entManager.TryGetComponent(MapUid, out _grid))
+ return;
+
// Draw full cable network
if (PowerCableNetwork != null && PowerCableNetwork.Count > 0)
{
@@ -69,36 +75,29 @@ public void DrawAllCableNetworks(DrawingHandleScreen handle)
DrawCableNetwork(handle, FocusCableNetwork, Color.White);
}
- public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary> fullCableNetwork, Color modulator)
+ public void DrawCableNetwork(DrawingHandleScreen handle, List fullCableNetwork, Color modulator)
{
+ if (!_entManager.TryGetComponent(MapUid, out _grid))
+ return;
+
var offset = GetOffset();
- var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset);
+ offset = offset with { Y = -offset.Y };
if (WorldRange / WorldMaxRange > 0.5f)
{
var cableNetworks = new ValueList[3];
- foreach ((var chunk, var chunkedLines) in fullCableNetwork)
+ foreach (var line in fullCableNetwork)
{
- var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
-
- if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
+ if (HiddenLineGroups.Contains(line.Group))
continue;
- if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
- continue;
-
- foreach (var chunkedLine in chunkedLines)
- {
- if (HiddenLineGroups.Contains(chunkedLine.Group))
- continue;
-
- var start = Scale(chunkedLine.Origin - new Vector2(offset.X, -offset.Y));
- var end = Scale(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y));
+ var cableOffset = _powerCableOffsets[(int) line.Group];
+ var start = ScalePosition(line.Origin + cableOffset - offset);
+ var end = ScalePosition(line.Terminus + cableOffset - offset);
- cableNetworks[(int) chunkedLine.Group].Add(start);
- cableNetworks[(int) chunkedLine.Group].Add(end);
- }
+ cableNetworks[(int) line.Group].Add(start);
+ cableNetworks[(int) line.Group].Add(end);
}
for (int cableNetworkIdx = 0; cableNetworkIdx < cableNetworks.Length; cableNetworkIdx++)
@@ -124,48 +123,39 @@ public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary[3];
- foreach ((var chunk, var chunkedLines) in fullCableNetwork)
+ foreach (var line in fullCableNetwork)
{
- var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
-
- if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
+ if (HiddenLineGroups.Contains(line.Group))
continue;
- if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
- continue;
-
- foreach (var chunkedLine in chunkedLines)
- {
- if (HiddenLineGroups.Contains(chunkedLine.Group))
- continue;
-
- var leftTop = Scale(new Vector2
- (Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
- Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
- - new Vector2(offset.X, -offset.Y));
-
- var rightTop = Scale(new Vector2
- (Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
- Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
- - new Vector2(offset.X, -offset.Y));
-
- var leftBottom = Scale(new Vector2
- (Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
- Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
- - new Vector2(offset.X, -offset.Y));
-
- var rightBottom = Scale(new Vector2
- (Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
- Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
- - new Vector2(offset.X, -offset.Y));
-
- cableVertexUVs[(int) chunkedLine.Group].Add(leftBottom);
- cableVertexUVs[(int) chunkedLine.Group].Add(leftTop);
- cableVertexUVs[(int) chunkedLine.Group].Add(rightBottom);
- cableVertexUVs[(int) chunkedLine.Group].Add(leftTop);
- cableVertexUVs[(int) chunkedLine.Group].Add(rightBottom);
- cableVertexUVs[(int) chunkedLine.Group].Add(rightTop);
- }
+ var cableOffset = _powerCableOffsets[(int) line.Group];
+
+ var leftTop = ScalePosition(new Vector2
+ (Math.Min(line.Origin.X, line.Terminus.X) - 0.1f,
+ Math.Min(line.Origin.Y, line.Terminus.Y) - 0.1f)
+ + cableOffset - offset);
+
+ var rightTop = ScalePosition(new Vector2
+ (Math.Max(line.Origin.X, line.Terminus.X) + 0.1f,
+ Math.Min(line.Origin.Y, line.Terminus.Y) - 0.1f)
+ + cableOffset - offset);
+
+ var leftBottom = ScalePosition(new Vector2
+ (Math.Min(line.Origin.X, line.Terminus.X) - 0.1f,
+ Math.Max(line.Origin.Y, line.Terminus.Y) + 0.1f)
+ + cableOffset - offset);
+
+ var rightBottom = ScalePosition(new Vector2
+ (Math.Max(line.Origin.X, line.Terminus.X) + 0.1f,
+ Math.Max(line.Origin.Y, line.Terminus.Y) + 0.1f)
+ + cableOffset - offset);
+
+ cableVertexUVs[(int) line.Group].Add(leftBottom);
+ cableVertexUVs[(int) line.Group].Add(leftTop);
+ cableVertexUVs[(int) line.Group].Add(rightBottom);
+ cableVertexUVs[(int) line.Group].Add(leftTop);
+ cableVertexUVs[(int) line.Group].Add(rightBottom);
+ cableVertexUVs[(int) line.Group].Add(rightTop);
}
for (int cableNetworkIdx = 0; cableNetworkIdx < cableVertexUVs.Length; cableNetworkIdx++)
@@ -188,34 +178,43 @@ public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary>? GetDecodedPowerCableChunks(Dictionary? chunks, MapGridComponent? grid)
+ public List GetDecodedPowerCableChunks(Dictionary? chunks)
{
- if (chunks == null || grid == null)
- return null;
+ var decodedOutput = new List();
- var decodedOutput = new Dictionary>();
+ if (!_entManager.TryGetComponent(MapUid, out _grid))
+ return decodedOutput;
- foreach ((var chunkOrigin, var chunk) in chunks)
- {
- var list = new List();
+ if (chunks == null)
+ return decodedOutput;
+
+ Array.ForEach(_horizLines, x=> x.Clear());
+ Array.ForEach(_horizLinesReversed, x=> x.Clear());
+ Array.ForEach(_vertLines, x=> x.Clear());
+ Array.ForEach(_vertLinesReversed, x=> x.Clear());
- for (int cableIdx = 0; cableIdx < chunk.PowerCableData.Length; cableIdx++)
+ foreach (var (chunkOrigin, chunk) in chunks)
+ {
+ for (var cableIdx = 0; cableIdx < 3; cableIdx++)
{
- var chunkMask = chunk.PowerCableData[cableIdx];
+ var horizLines = _horizLines[cableIdx];
+ var horizLinesReversed = _horizLinesReversed[cableIdx];
+ var vertLines = _vertLines[cableIdx];
+ var vertLinesReversed = _vertLinesReversed[cableIdx];
- Vector2 offset = _powerCableOffsets[cableIdx];
+ var chunkMask = chunk.PowerCableData[cableIdx];
- for (var chunkIdx = 0; chunkIdx < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; chunkIdx++)
+ for (var chunkIdx = 0; chunkIdx < ChunkSize * ChunkSize; chunkIdx++)
{
- var value = (int) Math.Pow(2, chunkIdx);
+ var value = 1 << chunkIdx;
var mask = chunkMask & value;
if (mask == 0x0)
continue;
- var relativeTile = SharedNavMapSystem.GetTile(mask);
- var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize;
- var position = new Vector2(tile.X, -tile.Y);
+ var relativeTile = GetTileFromIndex(chunkIdx);
+ var tile = (chunk.Origin * ChunkSize + relativeTile) * _grid.TileSize;
+ tile = tile with { Y = -tile.Y };
PowerCableChunk neighborChunk;
bool neighbor;
@@ -223,56 +222,65 @@ public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary 0)
- decodedOutput.Add(chunkOrigin, list);
+ for (var index = 0; index < _vertLines.Length; index++)
+ {
+ var vertLines = _vertLines[index];
+ foreach (var (origin, terminal) in vertLines)
+ {
+ decodedOutput.Add(new PowerMonitoringConsoleLine(origin + gridOffset, terminal + gridOffset,
+ (PowerMonitoringConsoleLineGroup) index));
+ }
}
return decodedOutput;
diff --git a/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs b/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs
index 25a586a75d..74752ddc53 100644
--- a/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs
+++ b/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs
@@ -102,7 +102,8 @@ public void UpdateWindowEntryButton(NetEntity netEntity, PowerMonitoringButton b
button.ToolTip = Loc.GetString(name);
// Update power value
- button.PowerValue.Text = Loc.GetString("power-monitoring-window-value", ("value", entry.PowerValue));
+ // Don't use SI prefixes, just give the number in W, so that it is readily apparent which consumer is using a lot of power.
+ button.PowerValue.Text = Loc.GetString("power-monitoring-window-button-value", ("value", Math.Round(entry.PowerValue).ToString("N0")));
}
private void UpdateEntrySourcesOrLoads(BoxContainer masterContainer, BoxContainer currentContainer, PowerMonitoringConsoleEntry[]? entries, SpriteSpecifier.Texture icon)
@@ -480,7 +481,8 @@ public PowerMonitoringButton()
PowerValue = new Label()
{
HorizontalAlignment = HAlignment.Right,
- SetWidth = 72f,
+ Align = Label.AlignMode.Right,
+ SetWidth = 80f,
Margin = new Thickness(10, 0, 0, 0),
ClipText = true,
};
diff --git a/Content.Client/Power/PowerMonitoringWindow.xaml.cs b/Content.Client/Power/PowerMonitoringWindow.xaml.cs
index edc0eaa18a..81fe1f4d04 100644
--- a/Content.Client/Power/PowerMonitoringWindow.xaml.cs
+++ b/Content.Client/Power/PowerMonitoringWindow.xaml.cs
@@ -170,9 +170,6 @@ public void ShowEntites
NavMap.TrackedEntities[mon.Value] = blip;
}
- // Update nav map
- NavMap.ForceNavMapUpdate();
-
// If the entry group doesn't match the current tab, the data is out dated, do not use it
if (allEntries.Length > 0 && allEntries[0].Group != GetCurrentPowerMonitoringConsoleGroup())
return;
diff --git a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs b/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs
deleted file mode 100644
index f6c19edc6f..0000000000
--- a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs
+++ /dev/null
@@ -1,266 +0,0 @@
-using System.Linq;
-using System.Numerics;
-using Content.Client.Humanoid;
-using Content.Client.Info;
-using Content.Client.Info.PlaytimeStats;
-using Content.Client.Lobby.UI;
-using Content.Client.Resources;
-using Content.Client.Stylesheets;
-using Content.Shared.Humanoid;
-using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.Preferences;
-using Content.Shared.Roles;
-using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
-using Robust.Client.ResourceManagement;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Configuration;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-using Robust.Shared.Map;
-using Robust.Shared.Maths;
-using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-using Direction = Robust.Shared.Maths.Direction;
-
-namespace Content.Client.Preferences.UI
-{
- [GenerateTypedNameReferences]
- public sealed partial class CharacterSetupGui : Control
- {
- private readonly IClientPreferencesManager _preferencesManager;
- private readonly IEntityManager _entityManager;
- private readonly IPrototypeManager _prototypeManager;
- private readonly IConfigurationManager _configurationManager;
- private readonly Button _createNewCharacterButton;
- private readonly HumanoidProfileEditor _humanoidProfileEditor;
-
- public CharacterSetupGui(
- IEntityManager entityManager,
- IResourceCache resourceCache,
- IClientPreferencesManager preferencesManager,
- IPrototypeManager prototypeManager,
- IConfigurationManager configurationManager)
- {
- RobustXamlLoader.Load(this);
- _entityManager = entityManager;
- _prototypeManager = prototypeManager;
- _preferencesManager = preferencesManager;
- _configurationManager = configurationManager;
-
- var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
- var back = new StyleBoxTexture
- {
- Texture = panelTex,
- Modulate = new Color(15, 15, 15)
- };
- back.SetPatchMargin(StyleBox.Margin.All, 10);
-
- BackgroundPanel.PanelOverride = back;
-
- _createNewCharacterButton = new Button
- {
- Text = Loc.GetString("character-setup-gui-create-new-character-button"),
- };
- _createNewCharacterButton.OnPressed += args =>
- {
- preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random());
- UpdateUI();
- args.Event.Handle();
- };
-
- _humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, entityManager, configurationManager);
- _humanoidProfileEditor.OnProfileChanged += ProfileChanged;
- CharEditor.AddChild(_humanoidProfileEditor);
-
- UpdateUI();
-
- RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open();
-
- StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered();
- preferencesManager.OnServerDataLoaded += UpdateUI;
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- _preferencesManager.OnServerDataLoaded -= UpdateUI;
- }
-
- public void Save() => _humanoidProfileEditor.Save();
-
- private void ProfileChanged(ICharacterProfile profile, int profileSlot)
- {
- _humanoidProfileEditor.UpdateControls();
- UpdateUI();
- }
-
- private void UpdateUI()
- {
- var numberOfFullSlots = 0;
- var characterButtonsGroup = new ButtonGroup();
- Characters.RemoveAllChildren();
-
- if (!_preferencesManager.ServerDataLoaded)
- {
- return;
- }
-
- _createNewCharacterButton.ToolTip =
- Loc.GetString("character-setup-gui-create-new-character-button-tooltip",
- ("maxCharacters", _preferencesManager.Settings!.MaxCharacterSlots));
-
- foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
- {
- if (character is null)
- {
- continue;
- }
-
- numberOfFullSlots++;
- var characterPickerButton = new CharacterPickerButton(_entityManager,
- _preferencesManager,
- _prototypeManager,
- characterButtonsGroup,
- character);
- Characters.AddChild(characterPickerButton);
-
- var characterIndexCopy = slot;
- characterPickerButton.OnPressed += args =>
- {
- _humanoidProfileEditor.Profile = (HumanoidCharacterProfile)character;
- _humanoidProfileEditor.CharacterSlot = characterIndexCopy;
- _humanoidProfileEditor.UpdateControls();
- _preferencesManager.SelectCharacter(character);
- UpdateUI();
- args.Event.Handle();
- };
- }
-
- _createNewCharacterButton.Disabled =
- numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots;
- Characters.AddChild(_createNewCharacterButton);
- }
-
- private sealed class CharacterPickerButton : ContainerButton
- {
- private EntityUid _previewDummy;
-
- public CharacterPickerButton(
- IEntityManager entityManager,
- IClientPreferencesManager preferencesManager,
- IPrototypeManager prototypeManager,
- ButtonGroup group,
- ICharacterProfile profile)
- {
- AddStyleClass(StyleClassButton);
- ToggleMode = true;
- Group = group;
-
- var humanoid = profile as HumanoidCharacterProfile;
- if (humanoid is not null)
- {
- var dummy = prototypeManager.Index(humanoid.Species).DollPrototype;
- _previewDummy = entityManager.SpawnEntity(dummy, MapCoordinates.Nullspace);
- }
- else
- {
- _previewDummy = entityManager.SpawnEntity(prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace);
- }
-
- EntitySystem.Get().LoadProfile(_previewDummy, (HumanoidCharacterProfile)profile);
-
- if (humanoid != null)
- {
- LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy, humanoid);
- }
-
- var isSelectedCharacter = profile == preferencesManager.Preferences?.SelectedCharacter;
-
- if (isSelectedCharacter)
- Pressed = true;
-
- var view = new SpriteView
- {
- Scale = new Vector2(2, 2),
- OverrideDirection = Direction.South
- };
- view.SetEntity(_previewDummy);
-
- var description = profile.Name;
-
- var highPriorityJob = humanoid?.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
- if (highPriorityJob != null)
- {
- var jobName = IoCManager.Resolve().Index(highPriorityJob).LocalizedName;
- description = $"{description}\n{jobName}";
- }
-
- var descriptionLabel = new Label
- {
- Text = description,
- ClipText = true,
- HorizontalExpand = true
- };
- var deleteButton = new Button
- {
- Text = Loc.GetString("character-setup-gui-character-picker-button-delete-button"),
- Visible = !isSelectedCharacter,
- };
- var confirmDeleteButton = new Button
- {
- Text = Loc.GetString("character-setup-gui-character-picker-button-confirm-delete-button"),
- Visible = false,
- };
- confirmDeleteButton.ModulateSelfOverride = StyleNano.ButtonColorCautionDefault;
- confirmDeleteButton.OnPressed += _ =>
- {
- Parent?.RemoveChild(this);
- Parent?.RemoveChild(confirmDeleteButton);
- preferencesManager.DeleteCharacter(profile);
- };
- deleteButton.OnPressed += _ =>
- {
-
- deleteButton.Visible = false;
- confirmDeleteButton.Visible = true;
-
- };
-
- var internalHBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalExpand = true,
- SeparationOverride = 0,
- Children =
- {
- view,
- descriptionLabel,
- deleteButton,
- confirmDeleteButton
- }
- };
-
- AddChild(internalHBox);
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- IoCManager.Resolve().DeleteEntity(_previewDummy);
- _previewDummy = default;
- }
- }
- }
-}
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
deleted file mode 100644
index c9e184dfc2..0000000000
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Content.Shared.Preferences;
-using Robust.Shared.Prototypes;
-
-namespace Content.Client.Preferences.UI
-{
- public sealed partial class HumanoidProfileEditor
- {
- private readonly IPrototypeManager _prototypeManager;
-
- private void RandomizeEverything()
- {
- Profile = HumanoidCharacterProfile.Random();
- UpdateControls();
- IsDirty = true;
- }
-
- private void RandomizeName()
- {
- if (Profile == null) return;
- var name = HumanoidCharacterProfile.GetName(Profile.Species, Profile.Gender);
- SetName(name);
- UpdateNameEdit();
- }
- }
-}
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
deleted file mode 100644
index 3543fcd668..0000000000
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
+++ /dev/null
@@ -1,169 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
deleted file mode 100644
index b016c82d4e..0000000000
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
+++ /dev/null
@@ -1,1461 +0,0 @@
-using System.Globalization;
-using System.Linq;
-using System.Numerics;
-using Content.Client.Humanoid;
-using Content.Client.Lobby.UI;
-using Content.Client.Message;
-using Content.Client.Players.PlayTimeTracking;
-using Content.Client.Stylesheets;
-using Content.Client.UserInterface.Controls;
-using Content.Shared.CCVar;
-using Content.Shared.GameTicking;
-using Content.Shared.Humanoid;
-using Content.Shared.Humanoid.Markings;
-using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.Inventory;
-using Content.Shared.Preferences;
-using Content.Shared.Roles;
-using Content.Shared.StatusIcon;
-using Content.Shared.Traits;
-using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Client.Utility;
-using Robust.Shared.Configuration;
-using Robust.Shared.Enums;
-using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-using Direction = Robust.Shared.Maths.Direction;
-
-namespace Content.Client.Preferences.UI
-{
- public sealed class HighlightedContainer : PanelContainer
- {
- public HighlightedContainer()
- {
- PanelOverride = new StyleBoxFlat()
- {
- BackgroundColor = new Color(25, 25, 25),
- ContentMarginTopOverride = 10,
- ContentMarginBottomOverride = 10,
- ContentMarginLeftOverride = 10,
- ContentMarginRightOverride = 10
- };
- }
- }
-
- [GenerateTypedNameReferences]
- public sealed partial class HumanoidProfileEditor : Control
- {
- private readonly IClientPreferencesManager _preferencesManager;
- private readonly IEntityManager _entMan;
- private readonly IConfigurationManager _configurationManager;
- private readonly MarkingManager _markingManager;
- private readonly JobRequirementsManager _requirements;
-
- private LineEdit _ageEdit => CAgeEdit;
- private LineEdit _nameEdit => CNameEdit;
- private TextEdit _flavorTextEdit = null!;
- private Button _nameRandomButton => CNameRandomize;
- private Button _randomizeEverythingButton => CRandomizeEverything;
- private RichTextLabel _warningLabel => CWarningLabel;
- private Button _saveButton => CSaveButton;
- private OptionButton _sexButton => CSexButton;
- private OptionButton _genderButton => CPronounsButton;
- private Slider _skinColor => CSkin;
- private OptionButton _clothingButton => CClothingButton;
- private OptionButton _backpackButton => CBackpackButton;
- // private OptionButton _spawnPriorityButton => CSpawnPriorityButton;
- private SingleMarkingPicker _hairPicker => CHairStylePicker;
- private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
- private EyeColorPicker _eyesPicker => CEyeColorPicker;
-
- private TabContainer _tabContainer => CTabContainer;
- private BoxContainer _jobList => CJobList;
- private BoxContainer _antagList => CAntagList;
- private BoxContainer _traitsList => CTraitsList;
- private readonly List _jobPriorities;
- private OptionButton _preferenceUnavailableButton => CPreferenceUnavailableButton;
- private readonly Dictionary _jobCategories;
- // Mildly hacky, as I don't trust prototype order to stay consistent and don't want the UI to break should a new one get added mid-edit. --moony
- private readonly List _speciesList;
- private readonly List _antagPreferences;
- private readonly List _traitPreferences;
-
- private SpriteView _previewSpriteView => CSpriteView;
- private Button _previewRotateLeftButton => CSpriteRotateLeft;
- private Button _previewRotateRightButton => CSpriteRotateRight;
- private Direction _previewRotation = Direction.North;
- private EntityUid? _previewDummy;
-
- private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
- private ColorSelectorSliders _rgbSkinColorSelector;
-
- private bool _isDirty;
- private bool _needUpdatePreview;
- public int CharacterSlot;
- public HumanoidCharacterProfile? Profile;
- private MarkingSet _markingSet = new(); // storing this here feels iffy but a few things need it this high up
-
- public event Action? OnProfileChanged;
-
- private float _defaultHeight = 1f;
-
- public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager,
- IEntityManager entityManager, IConfigurationManager configurationManager)
- {
- RobustXamlLoader.Load(this);
- _prototypeManager = prototypeManager;
- _entMan = entityManager;
- _preferencesManager = preferencesManager;
- _configurationManager = configurationManager;
- _markingManager = IoCManager.Resolve();
-
- #region Left
-
- #region Randomize
-
- #endregion Randomize
-
- #region Name
-
- _nameEdit.OnTextChanged += args => { SetName(args.Text); };
- _nameRandomButton.OnPressed += args => RandomizeName();
- _randomizeEverythingButton.OnPressed += args => { RandomizeEverything(); };
- _warningLabel.SetMarkup($"[color=red]{Loc.GetString("humanoid-profile-editor-naming-rules-warning")}[/color]");
-
- #endregion Name
-
- #region Appearance
-
- _tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
-
- ShowClothes.OnPressed += ToggleClothes;
-
- #region Sex
-
- _sexButton.OnItemSelected += args =>
- {
- _sexButton.SelectId(args.Id);
- SetSex((Sex) args.Id);
- };
-
- #endregion Sex
-
- #region Age
-
- _ageEdit.OnTextChanged += args =>
- {
- if (!int.TryParse(args.Text, out var newAge))
- return;
- SetAge(newAge);
- };
-
- #endregion Age
-
- #region Gender
-
- _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-male-text"), (int) Gender.Male);
- _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-female-text"), (int) Gender.Female);
- _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-epicene-text"), (int) Gender.Epicene);
- _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-neuter-text"), (int) Gender.Neuter);
-
- _genderButton.OnItemSelected += args =>
- {
- _genderButton.SelectId(args.Id);
- SetGender((Gender) args.Id);
- };
-
- #endregion Gender
-
- #region Species
-
- _speciesList = prototypeManager.EnumeratePrototypes().Where(o => o.RoundStart).ToList();
- for (var i = 0; i < _speciesList.Count; i++)
- {
- var name = Loc.GetString(_speciesList[i].Name);
- CSpeciesButton.AddItem(name, i);
- }
-
- CSpeciesButton.OnItemSelected += args =>
- {
- CSpeciesButton.SelectId(args.Id);
- SetSpecies(_speciesList[args.Id].ID);
- UpdateHairPickers();
- OnSkinColorOnValueChanged();
- };
-
- #endregion Species
-
- #region Height
-
- // CHeight.OnTextChanged += args =>
- // {
- // if (!float.TryParse(args.Text, out var newHeight))
- // return;
- //
- // CHeightLabel.Text = MathF.Round(newHeight, 1).ToString("G");
- // SetHeight(newHeight);
- // };
- //
- // CHeightReset.OnPressed += _ =>
- // {
- // CHeight.Text = _defaultHeight.ToString(CultureInfo.InvariantCulture);
- // };
-
- #endregion Height
-
- #region Skin
-
-
- _skinColor.OnValueChanged += _ =>
- {
- OnSkinColorOnValueChanged();
- };
-
- _rgbSkinColorContainer.AddChild(_rgbSkinColorSelector = new ColorSelectorSliders());
- _rgbSkinColorSelector.OnColorChanged += _ =>
- {
- OnSkinColorOnValueChanged();
- };
-
- #endregion
-
- #region Hair
-
- _hairPicker.OnMarkingSelect += newStyle =>
- {
- if (Profile is null)
- return;
- Profile = Profile.WithCharacterAppearance(
- Profile.Appearance.WithHairStyleName(newStyle.id));
- IsDirty = true;
- };
-
- _hairPicker.OnColorChanged += newColor =>
- {
- if (Profile is null)
- return;
- Profile = Profile.WithCharacterAppearance(
- Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
- UpdateCMarkingsHair();
- IsDirty = true;
- };
-
- _facialHairPicker.OnMarkingSelect += newStyle =>
- {
- if (Profile is null)
- return;
- Profile = Profile.WithCharacterAppearance(
- Profile.Appearance.WithFacialHairStyleName(newStyle.id));
- IsDirty = true;
- };
-
- _facialHairPicker.OnColorChanged += newColor =>
- {
- if (Profile is null)
- return;
- Profile = Profile.WithCharacterAppearance(
- Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
- UpdateCMarkingsFacialHair();
- IsDirty = true;
- };
-
- _hairPicker.OnSlotRemove += _ =>
- {
- if (Profile is null)
- return;
- Profile = Profile.WithCharacterAppearance(
- Profile.Appearance.WithHairStyleName(HairStyles.DefaultHairStyle)
- );
- UpdateHairPickers();
- UpdateCMarkingsHair();
- IsDirty = true;
- };
-
- _facialHairPicker.OnSlotRemove += _ =>
- {
- if (Profile is null)
- return;
- Profile = Profile.WithCharacterAppearance(
- Profile.Appearance.WithFacialHairStyleName(HairStyles.DefaultFacialHairStyle)
- );
- UpdateHairPickers();
- UpdateCMarkingsFacialHair();
- IsDirty = true;
- };
-
- _hairPicker.OnSlotAdd += delegate()
- {
- if (Profile is null)
- return;
-
- var hair = _markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.Hair, Profile.Species).Keys
- .FirstOrDefault();
-
- if (string.IsNullOrEmpty(hair))
- return;
-
- Profile = Profile.WithCharacterAppearance(
- Profile.Appearance.WithHairStyleName(hair)
- );
-
- UpdateHairPickers();
- UpdateCMarkingsHair();
- IsDirty = true;
- };
-
- _facialHairPicker.OnSlotAdd += delegate()
- {
- if (Profile is null)
- return;
-
- var hair = _markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.FacialHair, Profile.Species).Keys
- .FirstOrDefault();
-
- if (string.IsNullOrEmpty(hair))
- return;
-
- Profile = Profile.WithCharacterAppearance(
- Profile.Appearance.WithFacialHairStyleName(hair)
- );
-
- UpdateHairPickers();
- UpdateCMarkingsFacialHair();
- IsDirty = true;
- };
-
- #endregion Hair
-
- #region Clothing
-
- _clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpsuit"), (int) ClothingPreference.Jumpsuit);
- _clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpskirt"), (int) ClothingPreference.Jumpskirt);
-
- _clothingButton.OnItemSelected += args =>
- {
- _clothingButton.SelectId(args.Id);
- SetClothing((ClothingPreference) args.Id);
- };
-
- #endregion Clothing
-
- #region Backpack
-
- _backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-backpack"), (int) BackpackPreference.Backpack);
- _backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-satchel"), (int) BackpackPreference.Satchel);
- _backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-duffelbag"), (int) BackpackPreference.Duffelbag);
-
- _backpackButton.OnItemSelected += args =>
- {
- _backpackButton.SelectId(args.Id);
- SetBackpack((BackpackPreference) args.Id);
- };
-
- #endregion Backpack
-
- #region SpawnPriority
-
- // foreach (var value in Enum.GetValues())
- // {
- // _spawnPriorityButton.AddItem(Loc.GetString($"humanoid-profile-editor-preference-spawn-priority-{value.ToString().ToLower()}"), (int) value);
- // }
- //
- // _spawnPriorityButton.OnItemSelected += args =>
- // {
- // _spawnPriorityButton.SelectId(args.Id);
- // SetSpawnPriority((SpawnPriorityPreference) args.Id);
- // };
-
- #endregion SpawnPriority
-
- #region Eyes
-
- _eyesPicker.OnEyeColorPicked += newColor =>
- {
- if (Profile is null)
- return;
- Profile = Profile.WithCharacterAppearance(
- Profile.Appearance.WithEyeColor(newColor));
- CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
- IsDirty = true;
- };
-
- #endregion Eyes
-
- #endregion Appearance
-
- #region Jobs
-
- _tabContainer.SetTabTitle(1, Loc.GetString("humanoid-profile-editor-jobs-tab"));
-
- _preferenceUnavailableButton.AddItem(
- Loc.GetString("humanoid-profile-editor-preference-unavailable-stay-in-lobby-button"),
- (int) PreferenceUnavailableMode.StayInLobby);
- _preferenceUnavailableButton.AddItem(
- Loc.GetString("humanoid-profile-editor-preference-unavailable-spawn-as-overflow-button",
- ("overflowJob", Loc.GetString(SharedGameTicker.FallbackOverflowJobName))),
- (int) PreferenceUnavailableMode.SpawnAsOverflow);
-
- _preferenceUnavailableButton.OnItemSelected += args =>
- {
- _preferenceUnavailableButton.SelectId(args.Id);
-
- Profile = Profile?.WithPreferenceUnavailable((PreferenceUnavailableMode) args.Id);
- IsDirty = true;
- };
-
- _jobPriorities = new List();
- _jobCategories = new Dictionary();
- _requirements = IoCManager.Resolve();
- _requirements.Updated += UpdateRoleRequirements;
- UpdateRoleRequirements();
-
- #endregion Jobs
-
- #region Antags
-
- _tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
-
- _antagPreferences = new List();
-
- foreach (var antag in prototypeManager.EnumeratePrototypes().OrderBy(a => Loc.GetString(a.Name)))
- {
- if (!antag.SetPreference)
- continue;
-
- var selector = new AntagPreferenceSelector(antag);
- _antagList.AddChild(selector);
- _antagPreferences.Add(selector);
- if (selector.Disabled)
- {
- Profile = Profile?.WithAntagPreference(antag.ID, false);
- IsDirty = true;
- }
-
- selector.PreferenceChanged += preference =>
- {
- Profile = Profile?.WithAntagPreference(antag.ID, preference);
- IsDirty = true;
- };
- }
-
- #endregion Antags
-
- #region Traits
-
- var traits = prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList();
- _traitPreferences = new List();
- _tabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
-
- if (traits.Count > 0)
- {
- foreach (var trait in traits)
- {
- var selector = new TraitPreferenceSelector(trait);
- _traitsList.AddChild(selector);
- _traitPreferences.Add(selector);
-
- selector.PreferenceChanged += preference =>
- {
- Profile = Profile?.WithTraitPreference(trait.ID, preference);
- IsDirty = true;
- };
- }
- }
- else
- {
- _traitsList.AddChild(new Label
- {
- Text = "No traits available :(",
- FontColorOverride = Color.Gray,
- });
- }
-
- #endregion
-
- #region Save
-
- _saveButton.OnPressed += _ => { Save(); };
-
- #endregion Save
-
- #region Markings
- _tabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab"));
-
- CMarkings.OnMarkingAdded += OnMarkingChange;
- CMarkings.OnMarkingRemoved += OnMarkingChange;
- CMarkings.OnMarkingColorChange += OnMarkingChange;
- CMarkings.OnMarkingRankChange += OnMarkingChange;
-
- #endregion Markings
-
- #region FlavorText
-
- if (_configurationManager.GetCVar(CCVars.FlavorText))
- {
- var flavorText = new FlavorText.FlavorText();
- _tabContainer.AddChild(flavorText);
- _tabContainer.SetTabTitle(_tabContainer.ChildCount - 1, Loc.GetString("humanoid-profile-editor-flavortext-tab"));
- _flavorTextEdit = flavorText.CFlavorTextInput;
-
- flavorText.OnFlavorTextChanged += OnFlavorTextChange;
- }
-
- #endregion FlavorText
-
- #region Dummy
-
- _previewRotateLeftButton.OnPressed += _ =>
- {
- _previewRotation = _previewRotation.TurnCw();
- _needUpdatePreview = true;
- };
- _previewRotateRightButton.OnPressed += _ =>
- {
- _previewRotation = _previewRotation.TurnCcw();
- _needUpdatePreview = true;
- };
-
- var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var dollProto = _prototypeManager.Index(species).DollPrototype;
-
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy!.Value);
-
- _previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
- _previewSpriteView.SetEntity(_previewDummy);
- #endregion Dummy
-
- #endregion Left
-
- if (preferencesManager.ServerDataLoaded)
- {
- LoadServerData();
- }
-
- preferencesManager.OnServerDataLoaded += LoadServerData;
-
-
- IsDirty = false;
- }
-
- private void ToggleClothes(BaseButton.ButtonEventArgs obj)
- {
- RebuildSpriteView();
- }
-
- private void UpdateRoleRequirements()
- {
- _jobList.DisposeAllChildren();
- _jobPriorities.Clear();
- _jobCategories.Clear();
- var firstCategory = true;
-
- var departments = _prototypeManager.EnumeratePrototypes()
- .OrderByDescending(department => department.Weight)
- .ThenBy(department => Loc.GetString($"department-{department.ID}"))
- .ToList();
-
- foreach (var department in departments)
- {
- var departmentName = Loc.GetString($"department-{department.ID}");
-
- if (!_jobCategories.TryGetValue(department.ID, out var category))
- {
- category = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Name = department.ID,
- ToolTip = Loc.GetString("humanoid-profile-editor-jobs-amount-in-department-tooltip",
- ("departmentName", departmentName))
- };
-
- if (firstCategory)
- {
- firstCategory = false;
- }
- else
- {
- category.AddChild(new Control
- {
- MinSize = new Vector2(0, 23),
- });
- }
-
- category.AddChild(new PanelContainer
- {
- PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#232323")},
- Children =
- {
- new Label
- {
- Text = Loc.GetString("humanoid-profile-editor-department-jobs-label",
- ("departmentName", departmentName)),
- Margin = new Thickness(5f, 0, 0, 0)
- }
- }
- });
-
- _jobCategories[department.ID] = category;
- _jobList.AddChild(category);
- }
-
- var jobs = department.Roles.Select(jobId => _prototypeManager.Index(jobId))
- .Where(job => job.SetPreference)
- .OrderByDescending(job => job.Weight)
- .ThenBy(job => job.LocalizedName)
- .ToList();
-
- foreach (var job in jobs)
- {
- var selector = new JobPrioritySelector(job, _prototypeManager);
-
- if (!_requirements.IsAllowed(job, out var reason))
- {
- selector.LockRequirements(reason);
- }
-
- category.AddChild(selector);
- _jobPriorities.Add(selector);
-
- selector.PriorityChanged += priority =>
- {
- Profile = Profile?.WithJobPriority(job.ID, priority);
- IsDirty = true;
-
- foreach (var jobSelector in _jobPriorities)
- {
- // Sync other selectors with the same job in case of multiple department jobs
- if (jobSelector.Proto == selector.Proto)
- {
- jobSelector.Priority = priority;
- }
- else if (priority == JobPriority.High && jobSelector.Priority == JobPriority.High)
- {
- // Lower any other high priorities to medium.
- jobSelector.Priority = JobPriority.Medium;
- Profile = Profile?.WithJobPriority(jobSelector.Proto.ID, JobPriority.Medium);
- }
- }
- };
-
- }
- }
-
- if (Profile is not null)
- {
- UpdateJobPriorities();
- }
- }
-
- private void OnFlavorTextChange(string content)
- {
- if (Profile is null)
- return;
-
- Profile = Profile.WithFlavorText(content);
- IsDirty = true;
- }
-
- private void OnMarkingChange(MarkingSet markings)
- {
- if (Profile is null)
- return;
-
- Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
- _needUpdatePreview = true;
- IsDirty = true;
- }
-
- private void OnMarkingColorChange(List markings)
- {
- if (Profile is null)
- return;
-
- Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings));
- IsDirty = true;
- }
-
-
- private void OnSkinColorOnValueChanged()
- {
- if (Profile is null) return;
-
- var skin = _prototypeManager.Index(Profile.Species).SkinColoration;
-
- switch (skin)
- {
- case HumanoidSkinColor.HumanToned:
- {
- if (!_skinColor.Visible)
- {
- _skinColor.Visible = true;
- _rgbSkinColorContainer.Visible = false;
- }
-
- var color = SkinColor.HumanSkinTone((int) _skinColor.Value);
-
- CMarkings.CurrentSkinColor = color;
- Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));//
- break;
- }
- case HumanoidSkinColor.Hues:
- {
- if (!_rgbSkinColorContainer.Visible)
- {
- _skinColor.Visible = false;
- _rgbSkinColorContainer.Visible = true;
- }
-
- CMarkings.CurrentSkinColor = _rgbSkinColorSelector.Color;
- Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(_rgbSkinColorSelector.Color));
- break;
- }
- case HumanoidSkinColor.TintedHues:
- {
- if (!_rgbSkinColorContainer.Visible)
- {
- _skinColor.Visible = false;
- _rgbSkinColorContainer.Visible = true;
- }
-
- var color = SkinColor.TintedHues(_rgbSkinColorSelector.Color);
-
- CMarkings.CurrentSkinColor = color;
- Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
- break;
- }
- }
-
- IsDirty = true;
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
-
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy.Value);
-
- _requirements.Updated -= UpdateRoleRequirements;
- _preferencesManager.OnServerDataLoaded -= LoadServerData;
- }
-
- private void RebuildSpriteView()
- {
- var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var dollProto = _prototypeManager.Index(species).DollPrototype;
-
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy!.Value);
-
- _previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
- _previewSpriteView.SetEntity(_previewDummy);
- _needUpdatePreview = true;
- }
-
- private void LoadServerData()
- {
- Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
- CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
-
- UpdateControls();
- _needUpdatePreview = true;
- }
-
- private void SetAge(int newAge)
- {
- Profile = Profile?.WithAge(newAge);
- IsDirty = true;
- }
-
- private void SetSex(Sex newSex)
- {
- Profile = Profile?.WithSex(newSex);
- // for convenience, default to most common gender when new sex is selected
- switch (newSex)
- {
- case Sex.Male:
- Profile = Profile?.WithGender(Gender.Male);
- break;
- case Sex.Female:
- Profile = Profile?.WithGender(Gender.Female);
- break;
- default:
- Profile = Profile?.WithGender(Gender.Epicene);
- break;
- }
- UpdateGenderControls();
- CMarkings.SetSex(newSex);
- IsDirty = true;
- }
-
- private void SetGender(Gender newGender)
- {
- Profile = Profile?.WithGender(newGender);
- IsDirty = true;
- }
-
- private void SetSpecies(string newSpecies)
- {
- Profile = Profile?.WithSpecies(newSpecies);
- OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
- CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
- UpdateSexControls(); // update sex for new species
- RebuildSpriteView(); // they might have different inv so we need a new dummy
- IsDirty = true;
- _needUpdatePreview = true;
- }
-
- private void SetHeight(float height)
- {
- Profile = Profile?.WithHeight(height);
- IsDirty = true;
- }
-
- private void SetName(string newName)
- {
- Profile = Profile?.WithName(newName);
- IsDirty = true;
- }
-
- private void SetClothing(ClothingPreference newClothing)
- {
- Profile = Profile?.WithClothingPreference(newClothing);
- IsDirty = true;
- }
-
- private void SetBackpack(BackpackPreference newBackpack)
- {
- Profile = Profile?.WithBackpackPreference(newBackpack);
- IsDirty = true;
- }
-
- private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
- {
- Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
- IsDirty = true;
- }
-
- public void Save()
- {
- IsDirty = false;
-
- if (Profile != null)
- {
- _preferencesManager.UpdateCharacter(Profile, CharacterSlot);
- OnProfileChanged?.Invoke(Profile, CharacterSlot);
- _needUpdatePreview = true;
- }
- }
-
- private bool IsDirty
- {
- get => _isDirty;
- set
- {
- _isDirty = value;
- _needUpdatePreview = true;
- UpdateSaveButton();
- }
- }
-
- private void UpdateNameEdit()
- {
- _nameEdit.Text = Profile?.Name ?? "";
- }
-
- private void UpdateFlavorTextEdit()
- {
- if(_flavorTextEdit != null)
- {
- _flavorTextEdit.TextRope = new Rope.Leaf(Profile?.FlavorText ?? "");
- }
- }
-
- private void UpdateAgeEdit()
- {
- _ageEdit.Text = Profile?.Age.ToString() ?? "";
- }
-
- private void UpdateSexControls()
- {
- if (Profile == null)
- return;
-
- _sexButton.Clear();
-
- var sexes = new List();
-
- // add species sex options, default to just none if we are in bizzaro world and have no species
- if (_prototypeManager.TryIndex(Profile.Species, out var speciesProto))
- {
- foreach (var sex in speciesProto.Sexes)
- {
- sexes.Add(sex);
- }
- } else
- {
- sexes.Add(Sex.Unsexed);
- }
-
- // add button for each sex
- foreach (var sex in sexes)
- {
- _sexButton.AddItem(Loc.GetString($"humanoid-profile-editor-sex-{sex.ToString().ToLower()}-text"), (int) sex);
- }
-
- if (sexes.Contains(Profile.Sex))
- _sexButton.SelectId((int) Profile.Sex);
- else
- _sexButton.SelectId((int) sexes[0]);
- }
-
- private void UpdateSkinColor()
- {
- if (Profile == null)
- return;
-
- var skin = _prototypeManager.Index(Profile.Species).SkinColoration;
-
- switch (skin)
- {
- case HumanoidSkinColor.HumanToned:
- {
- if (!_skinColor.Visible)
- {
- _skinColor.Visible = true;
- _rgbSkinColorContainer.Visible = false;
- }
-
- _skinColor.Value = SkinColor.HumanSkinToneFromColor(Profile.Appearance.SkinColor);
-
- break;
- }
- case HumanoidSkinColor.Hues:
- {
- if (!_rgbSkinColorContainer.Visible)
- {
- _skinColor.Visible = false;
- _rgbSkinColorContainer.Visible = true;
- }
-
- // set the RGB values to the direct values otherwise
- _rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
- break;
- }
- case HumanoidSkinColor.TintedHues:
- {
- if (!_rgbSkinColorContainer.Visible)
- {
- _skinColor.Visible = false;
- _rgbSkinColorContainer.Visible = true;
- }
-
- // set the RGB values to the direct values otherwise
- _rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
- break;
- }
- }
-
- }
-
- private void UpdateMarkings()
- {
- if (Profile == null)
- {
- return;
- }
-
- CMarkings.SetData(Profile.Appearance.Markings, Profile.Species,
- Profile.Sex, Profile.Appearance.SkinColor, Profile.Appearance.EyeColor
- );
- }
-
- private void UpdateSpecies()
- {
- if (Profile == null)
- {
- return;
- }
-
- CSpeciesButton.Select(_speciesList.FindIndex(x => x.ID == Profile.Species));
- }
-
- private void UpdateHeightControls()
- {
- if (Profile == null)
- {
- return;
- }
-
- var species = _speciesList.Find(x => x.ID == Profile.Species);
- if (species != null)
- _defaultHeight = species.DefaultHeight;
- }
-
- private void UpdateGenderControls()
- {
- if (Profile == null)
- {
- return;
- }
-
- _genderButton.SelectId((int) Profile.Gender);
- }
-
- private void UpdateClothingControls()
- {
- if (Profile == null)
- {
- return;
- }
-
- _clothingButton.SelectId((int) Profile.Clothing);
- }
-
- private void UpdateBackpackControls()
- {
- if (Profile == null)
- {
- return;
- }
-
- _backpackButton.SelectId((int) Profile.Backpack);
- }
-
- private void UpdateSpawnPriorityControls()
- {
- if (Profile == null)
- {
- return;
- }
-
- // _spawnPriorityButton.SelectId((int) Profile.SpawnPriority);
- }
-
- private void UpdateHairPickers()
- {
- if (Profile == null)
- {
- return;
- }
- var hairMarking = Profile.Appearance.HairStyleId switch
- {
- HairStyles.DefaultHairStyle => new List(),
- _ => new() { new(Profile.Appearance.HairStyleId, new List() { Profile.Appearance.HairColor }) },
- };
-
- var facialHairMarking = Profile.Appearance.FacialHairStyleId switch
- {
- HairStyles.DefaultFacialHairStyle => new List