diff --git a/.editorconfig b/.editorconfig index 872a068c7c6..a5dfab07a50 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 ddb66e8694b..45187260126 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/Content.Benchmarks/ComponentQueryBenchmark.cs b/Content.Benchmarks/ComponentQueryBenchmark.cs new file mode 100644 index 00000000000..11c7ab9d5f5 --- /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 0b9683a4abb..8e02b8d71de 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 cef6a5e35c5..00000000000 --- 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 261e164f175..a3319e3067f 100644 --- a/Content.Benchmarks/MapLoadBenchmark.cs +++ b/Content.Benchmarks/MapLoadBenchmark.cs @@ -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 c7f22bdb0cd..fa7f9d45422 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 8512107b69d..0638d945aa5 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; diff --git a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs index c3fac8cb92a..761f52988a9 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,7 +42,7 @@ private void OnJobChanged(string newJob) SendMessage(new AgentIDCardJobChangedMessage(newJob)); } - public void OnJobIconChanged(string newJobIconId) + public void OnJobIconChanged(ProtoId newJobIconId) { SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId)); } diff --git a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs index 9a38c0c4853..6d0b2a184f4 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, string currentJobIconId) + public void SetAllowedIcons(HashSet> icons, string currentJobIconId) { IconGrid.DisposeAllChildren(); @@ -46,10 +46,8 @@ public void SetAllowedIcons(HashSet icons, string currentJobIconId) 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,7 +75,7 @@ public void SetAllowedIcons(HashSet icons, string currentJobIconId) }; jobIconButton.AddChild(jobIconTexture); - jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIcon.ID); + jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIconId); IconGrid.AddChild(jobIconButton); if (jobIconId.Equals(currentJobIconId)) diff --git a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs index 46b6834b56d..e635478cc70 100644 --- a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs +++ b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs @@ -1,7 +1,6 @@ using System.Linq; using Content.Shared.Access; using Content.Shared.Access.Components; -using Content.Shared.Access; using Content.Shared.Access.Systems; using Content.Shared.Containers.ItemSlots; using Content.Shared.CrewManifest; diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs index b7adb7d348b..8d1dca16c34 100644 --- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs +++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs @@ -31,6 +31,9 @@ public sealed partial class IdCardConsoleWindow : DefaultWindow private string? _lastJobProto; private bool _interfaceEnabled = false; + // 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) { @@ -78,7 +81,6 @@ public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeMana } JobPresetOptionButton.OnItemSelected += SelectJobPreset; - _accessButtons.Populate(accessLevels, prototypeManager); AccessLevelControlContainer.AddChild(_accessButtons); @@ -208,11 +210,15 @@ public void UpdateState(IdCardConsoleBoundUserInterfaceState state) 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; diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 5ff003452ab..aff6c1ff7be 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -247,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/Systems/AdminFrozenSystem.cs b/Content.Client/Administration/Systems/AdminFrozenSystem.cs new file mode 100644 index 00000000000..885585f985c --- /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/UI/AdminMenuWindow.xaml b/Content.Client/Administration/UI/AdminMenuWindow.xaml index 311d67b826c..d3d3df02d93 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 c3ea67a3edb..f3aa2572f2f 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/BanPanel/BanPanel.xaml.cs b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs index dc263d6055c..588d62e5603 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs @@ -147,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); diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs index af977f763c6..ddd66623bd4 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 f8d06f758f4..30f9d24df1d 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 dbe9bae9e6e..dffde2d2e99 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(32, 38, 32)}; } + 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/SpawnExplosion/ExplosionDebugOverlay.cs b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs index eede3a6217f..d60094ad897 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 5f187cad794..b0d8a946ec5 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 00000000000..b8034faf52a --- /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 00000000000..9e1d53818f2 --- /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 00000000000..dd770c2be53 --- /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 00000000000..aa9d6ced951 --- /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 fb68e6c7908..ea89916ba8c 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 a5c30084365..c8606ca80d5 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.Default.Compare(valueA, valueB) : Comparer.Default.Compare(valueB, valueA); + }); - _objects.Clear(); - - foreach (var (name, nent) in entities) + var listData = new List(); + for (int index = 0; index < entities.Count; index++) { - var ctrl = new ObjectsTabEntry(name, nent); - _objects.Add(ctrl); - ObjectList.AddChild(ctrl); - ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args); + var info = entities[index]; + listData.Add(new ObjectsListData(info, $"{info.Name} {info.Entity}", index % 2 == 0 ? _altColor : _defaultColor)); } + + SearchList.PopulateList(listData); } - protected override void FrameUpdate(FrameEventArgs args) + private void GenerateButton(ListData data, ListContainerButton button) { - base.FrameUpdate(args); - - if (_timing.CurTime < _nextUpdate) + if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor }) return; - // I do not care for precision. - _nextUpdate = _timing.CurTime + _updateFrequency; + var entry = new ObjectsTabEntry(info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor }); + button.ToolTip = $"{info.Name}, {info.Entity}"; + + button.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(args, data); + button.AddChild(entry); + } + + private bool DataFilterCondition(string filter, ListData listData) + { + if (listData is not ObjectsListData { FilteringString: var filteringString }) + return false; + + // If the filter is empty, do not filter out any entries + if (string.IsNullOrEmpty(filter)) + return true; + + return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase); + } + + private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header) + { + return header switch + { + ObjectsTabHeader.Header.ObjectName => entity.Name, + ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(), + _ => entity.Name + }; + } + + private void HeaderClicked(ObjectsTabHeader.Header header) + { + if (_headerClicked == header) + { + _ascending = !_ascending; + } + else + { + _headerClicked = header; + _ascending = true; + } + ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending); RefreshObjectList(); } @@ -118,3 +182,4 @@ private enum ObjectsTabSelection } } +public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor) : ListData; diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml index 0f6975e3656..83c4cc5697f 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml @@ -1,6 +1,6 @@ - - + @@ -14,4 +14,4 @@ HorizontalExpand="True" ClipText="True"/> - + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs index c9b2cd8b572..aab06c6ccd0 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs @@ -1,19 +1,21 @@ using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; namespace Content.Client.Administration.UI.Tabs.ObjectsTab; [GenerateTypedNameReferences] -public sealed partial class ObjectsTabEntry : ContainerButton +public sealed partial class ObjectsTabEntry : PanelContainer { public NetEntity AssocEntity; - public ObjectsTabEntry(string name, NetEntity nent) + public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox) { RobustXamlLoader.Load(this); AssocEntity = nent; EIDLabel.Text = nent.ToString(); NameLabel.Text = name; + BackgroundColorPanel.PanelOverride = styleBox; } } diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml new file mode 100644 index 00000000000..71a1f5c7bc6 --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml @@ -0,0 +1,21 @@ + + + + + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs new file mode 100644 index 00000000000..3a91b5b9483 --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs @@ -0,0 +1,86 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Input; + +namespace Content.Client.Administration.UI.Tabs.ObjectsTab +{ + [GenerateTypedNameReferences] + public sealed partial class ObjectsTabHeader : Control + { + public event Action
? OnHeaderClicked; + + private const string ArrowUp = "↑"; + private const string ArrowDown = "↓"; + + public ObjectsTabHeader() + { + RobustXamlLoader.Load(this); + + ObjectNameLabel.OnKeyBindDown += ObjectNameClicked; + EntityIDLabel.OnKeyBindDown += EntityIDClicked; + } + + public Label GetHeader(Header header) + { + return header switch + { + Header.ObjectName => ObjectNameLabel, + Header.EntityID => EntityIDLabel, + _ => throw new ArgumentOutOfRangeException(nameof(header), header, null) + }; + } + + public void ResetHeaderText() + { + ObjectNameLabel.Text = Loc.GetString("object-tab-object-name"); + EntityIDLabel.Text = Loc.GetString("object-tab-entity-id"); + } + + public void UpdateHeaderSymbols(Header headerClicked, bool ascending) + { + ResetHeaderText(); + var arrow = ascending ? ArrowUp : ArrowDown; + GetHeader(headerClicked).Text += $" {arrow}"; + } + + private void HeaderClicked(GUIBoundKeyEventArgs args, Header header) + { + if (args.Function != EngineKeyFunctions.UIClick) + { + return; + } + + OnHeaderClicked?.Invoke(header); + args.Handle(); + } + + private void ObjectNameClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.ObjectName); + } + + private void EntityIDClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.EntityID); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked; + EntityIDLabel.OnKeyBindDown -= EntityIDClicked; + } + } + + public enum Header + { + ObjectName, + EntityID + } + } +} diff --git a/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml b/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml index 89827d06424..ee7ba4d34ff 100644 --- a/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml +++ b/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml @@ -31,12 +31,12 @@ - diff --git a/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml.cs b/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml.cs index f6212cd5ee1..c3bcf3ffa09 100644 --- a/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PanicBunkerTab/PanicBunkerTab.xaml.cs @@ -12,7 +12,7 @@ public sealed partial class PanicBunkerTab : Control [Dependency] private readonly IConsoleHost _console = default!; private string _minAccountAge; - private string _minOverallHours; + private string _minOverallMinutes; public PanicBunkerTab() { @@ -25,9 +25,9 @@ public PanicBunkerTab() MinAccountAge.OnFocusExit += args => SendMinAccountAge(args.Text); _minAccountAge = MinAccountAge.Text; - MinOverallHours.OnTextEntered += args => SendMinOverallHours(args.Text); - MinOverallHours.OnFocusExit += args => SendMinOverallHours(args.Text); - _minOverallHours = MinOverallHours.Text; + MinOverallMinutes.OnTextEntered += args => SendMinOverallMinutes(args.Text); + MinOverallMinutes.OnFocusExit += args => SendMinOverallMinutes(args.Text); + _minOverallMinutes = MinOverallMinutes.Text; } private void SendMinAccountAge(string text) @@ -42,16 +42,16 @@ private void SendMinAccountAge(string text) _console.ExecuteCommand($"panicbunker_min_account_age {minutes}"); } - private void SendMinOverallHours(string text) + private void SendMinOverallMinutes(string text) { if (string.IsNullOrWhiteSpace(text) || - text == _minOverallHours || - !int.TryParse(text, out var hours)) + text == _minOverallMinutes || + !int.TryParse(text, out var minutes)) { return; } - _console.ExecuteCommand($"panicbunker_min_overall_hours {hours}"); + _console.ExecuteCommand($"panicbunker_min_overall_minutes {minutes}"); } public void UpdateStatus(PanicBunkerStatus status) @@ -68,10 +68,10 @@ public void UpdateStatus(PanicBunkerStatus status) CountDeadminnedButton.Pressed = status.CountDeadminnedAdmins; ShowReasonButton.Pressed = status.ShowReason; - MinAccountAge.Text = status.MinAccountAgeHours.ToString(); + MinAccountAge.Text = status.MinAccountAgeMinutes.ToString(); _minAccountAge = MinAccountAge.Text; - MinOverallHours.Text = status.MinOverallHours.ToString(); - _minOverallHours = MinOverallHours.Text; + MinOverallMinutes.Text = status.MinOverallMinutes.ToString(); + _minOverallMinutes = MinOverallMinutes.Text; } } diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml index 3071bf8358b..25a96df1d37 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml @@ -1,21 +1,19 @@  + xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" + xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"> - + diff --git a/Content.Client/Chemistry/UI/ReagentCardControl.xaml.cs b/Content.Client/Chemistry/UI/ReagentCardControl.xaml.cs new file mode 100644 index 00000000000..f7cadcc264d --- /dev/null +++ b/Content.Client/Chemistry/UI/ReagentCardControl.xaml.cs @@ -0,0 +1,32 @@ +using Content.Shared.Chemistry; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Chemistry.UI; + +[GenerateTypedNameReferences] +public sealed partial class ReagentCardControl : Control +{ + public string StorageSlotId { get; } + public Action? OnPressed; + public Action? OnEjectButtonPressed; + + public ReagentCardControl(ReagentInventoryItem item) + { + RobustXamlLoader.Load(this); + + StorageSlotId = item.StorageSlotId; + ColorPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = item.ReagentColor }; + ReagentNameLabel.Text = item.ReagentLabel; + FillLabel.Text = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", item.Quantity));; + EjectButtonIcon.Text = Loc.GetString("reagent-dispenser-window-eject-container-button"); + + if (item.Quantity == 0.0) + MainButton.Disabled = true; + + MainButton.OnPressed += args => OnPressed?.Invoke(StorageSlotId); + EjectButton.OnPressed += args => OnEjectButtonPressed?.Invoke(StorageSlotId); + } +} diff --git a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs index 8244e3e6edb..99e5a3d3953 100644 --- a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs +++ b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs @@ -1,3 +1,4 @@ +using Content.Client.Guidebook.Components; using Content.Shared.Chemistry; using Content.Shared.Containers.ItemSlots; using JetBrains.Annotations; @@ -34,6 +35,7 @@ protected override void Open() _window = new() { Title = EntMan.GetComponent(Owner).EntityName, + HelpGuidebookIds = EntMan.GetComponent(Owner).Guides }; _window.OpenCentered(); @@ -42,38 +44,11 @@ protected override void Open() // Setup static button actions. _window.EjectButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentDispenser.OutputSlotName)); _window.ClearButton.OnPressed += _ => SendMessage(new ReagentDispenserClearContainerSolutionMessage()); - _window.DispenseButton1.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U1)); - _window.DispenseButton5.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U5)); - _window.DispenseButton10.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U10)); - _window.DispenseButton15.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U15)); - _window.DispenseButton20.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U20)); - _window.DispenseButton25.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U25)); - _window.DispenseButton30.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U30)); - _window.DispenseButton50.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U50)); - _window.DispenseButton100.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U100)); - // Setup reagent button actions. - _window.OnDispenseReagentButtonPressed += (args, button) => SendMessage(new ReagentDispenserDispenseReagentMessage(button.ReagentId)); - _window.OnDispenseReagentButtonMouseEntered += (args, button) => - { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; - _window.OnDispenseReagentButtonMouseExited += (args, button) => - { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; + _window.AmountGrid.OnButtonPressed += s => SendMessage(new ReagentDispenserSetDispenseAmountMessage(s)); - _window.OnEjectJugButtonPressed += (args, button) => SendMessage(new ItemSlotButtonPressedEvent(button.ReagentId)); - _window.OnEjectJugButtonMouseEntered += (args, button) => { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; - _window.OnEjectJugButtonMouseExited += (args, button) => { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; + _window.OnDispenseReagentButtonPressed += (id) => SendMessage(new ReagentDispenserDispenseReagentMessage(id)); + _window.OnEjectJugButtonPressed += (id) => SendMessage(new ItemSlotButtonPressedEvent(id)); } /// diff --git a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml index 86971062729..6e62136993f 100644 --- a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml +++ b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml @@ -1,53 +1,78 @@ - - - - [GenerateTypedNameReferences] - public sealed partial class ReagentDispenserWindow : DefaultWindow + public sealed partial class ReagentDispenserWindow : FancyWindow { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IEntityManager _entityManager = 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; + 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); } } @@ -93,36 +70,7 @@ public void UpdateState(BoundUserInterfaceState state) 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(); } /// @@ -137,23 +85,15 @@ public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state) if (state.OutputContainer is null) { + 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!) { @@ -181,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/Visualizers/SolutionContainerVisualsSystem.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs index f1e8e8d7aa4..17b88fb5a8f 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 cfbd1a99d69..6f75df46830 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/MagbootsSystem.cs b/Content.Client/Clothing/MagbootsSystem.cs deleted file mode 100644 index a3d39eafded..00000000000 --- 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/Clothing/Systems/WaddleClothingSystem.cs b/Content.Client/Clothing/Systems/WaddleClothingSystem.cs deleted file mode 100644 index b8ac3c207bf..00000000000 --- a/Content.Client/Clothing/Systems/WaddleClothingSystem.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Content.Shared.Clothing.Components; -using Content.Shared.Movement.Components; -using Content.Shared.Inventory.Events; - -namespace Content.Client.Clothing.Systems; - -public sealed class WaddleClothingSystem : EntitySystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnGotEquipped); - SubscribeLocalEvent(OnGotUnequipped); - } - - private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, GotEquippedEvent args) - { - var waddleAnimComp = EnsureComp(args.Equipee); - - waddleAnimComp.AnimationLength = comp.AnimationLength; - waddleAnimComp.HopIntensity = comp.HopIntensity; - waddleAnimComp.RunAnimationLengthMultiplier = comp.RunAnimationLengthMultiplier; - waddleAnimComp.TumbleIntensity = comp.TumbleIntensity; - } - - private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, GotUnequippedEvent args) - { - RemComp(args.Equipee); - } -} diff --git a/Content.Client/Commands/ShowHealthBarsCommand.cs b/Content.Client/Commands/ShowHealthBarsCommand.cs index bd3e21718f0..0811f966637 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/Construction/ConstructionSystem.cs b/Content.Client/Construction/ConstructionSystem.cs index 66000a8457d..453658bebf9 100644 --- a/Content.Client/Construction/ConstructionSystem.cs +++ b/Content.Client/Construction/ConstructionSystem.cs @@ -27,6 +27,7 @@ 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!; @@ -195,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, _transformSystem)); - if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate)) + 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 9a094361766..0c7912e0bcd 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 eec5c229cb5..322e4e66a9a 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 ad19bc30f42..9f3d5695bb6 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/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs b/Content.Client/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs index 2a34376f6a6..4bf9d4c3e1c 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 00000000000..c895a00c887 --- /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 00000000000..c0b98d7ce31 --- /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 901ab270fb5..41e5f39c286 100644 --- a/Content.Client/Decals/DecalSystem.cs +++ b/Content.Client/Decals/DecalSystem.cs @@ -56,34 +56,43 @@ protected override void OnDecalRemoved(EntityUid gridId, uint decalId, DecalGrid 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) diff --git a/Content.Client/Decals/Overlays/DecalOverlay.cs b/Content.Client/Decals/Overlays/DecalOverlay.cs index d9904ae80b5..0de3301e589 100644 --- a/Content.Client/Decals/Overlays/DecalOverlay.cs +++ b/Content.Client/Decals/Overlays/DecalOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Decals; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -113,7 +114,7 @@ protected override void Draw(in OverlayDrawArgs args) 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 be277448eda..845bd7c03d2 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 9f0851f0806..025ed1299d1 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 1be17510807..21b816515a4 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/Disposal/Systems/DisposalUnitSystem.cs b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs index b9e4a386604..2fe56fcce99 100644 --- a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs +++ b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs @@ -153,7 +153,7 @@ private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, Sprite } } else if (state == VisualState.OverlayCharging) - sprite.LayerSetState(DisposalUnitVisualLayers.OverlayFlush, new RSI.StateId("disposal-charging")); + sprite.LayerSetState(DisposalUnitVisualLayers.OverlayFlush, chargingState); else _animationSystem.Stop(uid, AnimationKey); diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs index 45981159f06..dfbbf108917 100644 --- a/Content.Client/DoAfter/DoAfterOverlay.cs +++ b/Content.Client/DoAfter/DoAfterOverlay.cs @@ -56,8 +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); + var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale)); + var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation); var curTime = _timing.CurTime; @@ -91,9 +91,9 @@ protected override void Draw(in OverlayDrawArgs args) ? 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; @@ -151,7 +151,7 @@ protected override void Draw(in OverlayDrawArgs args) } handle.UseShader(null); - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } public Color GetProgressColor(float progress, float alpha = 1f) diff --git a/Content.Client/Doors/FirelockSystem.cs b/Content.Client/Doors/FirelockSystem.cs index cfd84a47133..ad869391f4b 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/RainbowOverlay.cs b/Content.Client/Drugs/RainbowOverlay.cs index e62b0dfa66c..fb48c910109 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!; @@ -75,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 25eeb95c2ae..8ccef2e1f13 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -2,18 +2,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; @@ -52,7 +50,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!; @@ -119,13 +116,13 @@ public override void Init() _prototypeManager.RegisterIgnore("wireLayout"); _prototypeManager.RegisterIgnore("alertLevels"); _prototypeManager.RegisterIgnore("nukeopsRole"); + _prototypeManager.RegisterIgnore("ghostRoleRaffleDecider"); _componentFactory.GenerateNetIds(); _adminManager.Initialize(); _screenshotHook.Initialize(); _fullscreenHook.Initialize(); _changelogManager.Initialize(); - _rulesManager.Initialize(); _viewportManager.Initialize(); _ghostKick.Initialize(); _extendedDisconnectInformation.Initialize(); @@ -152,7 +149,6 @@ public override void PostInit() _parallaxManager.LoadDefaultParallax(); _overlayManager.AddOverlay(new SingularityOverlay()); - _overlayManager.AddOverlay(new FlashOverlay()); _overlayManager.AddOverlay(new RadiationPulseOverlay()); _chatManager.Initialize(); _clientPreferencesManager.Initialize(); diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index 45db4efa53c..b476971a13a 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -239,8 +239,8 @@ public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCurso if (knowTarget) { - var itemName = FormattedMessage.RemoveMarkup(Identity.Name(target, EntityManager, player)); - var labelMessage = FormattedMessage.FromMarkup($"[bold]{itemName}[/bold]"); + 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); @@ -248,7 +248,7 @@ public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCurso else { var label = new RichTextLabel(); - label.SetMessage(FormattedMessage.FromMarkup("[bold]???[/bold]")); + label.SetMessage(FormattedMessage.FromMarkupOrThrow("[bold]???[/bold]")); hBox.AddChild(label); } diff --git a/Content.Client/Explosion/ExplosionOverlay.cs b/Content.Client/Explosion/ExplosionOverlay.cs index 2d8c15f1b9f..8cf7447a5d8 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/Flash/FlashOverlay.cs b/Content.Client/Flash/FlashOverlay.cs index fe9c888227e..9ea00275e84 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 ad8f8b0b82b..9a0579f6aa3 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 ffcf653f119..91b59046a47 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 ac6661cfdd8..a8c1d355105 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/GPS/UI/HandheldGpsStatusControl.cs b/Content.Client/GPS/UI/HandheldGpsStatusControl.cs index de6a1031bad..7dcf3f29c51 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 309db2eb4e6..fcf5ae91a49 100644 --- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs +++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs @@ -4,10 +4,12 @@ using Content.Client.RoundEnd; 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.Client.UserInterface; +using Robust.Shared.Prototypes; namespace Content.Client.GameTicking.Managers { @@ -17,10 +19,9 @@ public sealed class ClientGameTicker : SharedGameTicker [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IClientAdminManager _admin = default!; [Dependency] private readonly IClyde _clyde = default!; - [Dependency] private readonly SharedMapSystem _map = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; - private Dictionary> _jobsAvailable = new(); + private Dictionary, int?>> _jobsAvailable = new(); private Dictionary _stationNames = new(); [ViewVariables] public bool AreWeReady { get; private set; } @@ -32,13 +33,13 @@ 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? LobbyLateJoinStatusUpdated; - public event Action>>? LobbyJobsAvailableUpdated; + public event Action, int?>>>? LobbyJobsAvailableUpdated; public override void Initialize() { @@ -69,7 +70,7 @@ private void OnAdminUpdated() // 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 - _map.Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning; + EntityManager.System().Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning; #endif } diff --git a/Content.Client/Gravity/GravitySystem.Shake.cs b/Content.Client/Gravity/GravitySystem.Shake.cs index c4356588d35..9b9918ca3e7 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 db19bb9dcc8..bb1d30bbc16 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 f46e319abeb..73a17e9bcc9 100644 --- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml +++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml @@ -47,6 +47,17 @@ + + + + + + + + diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs index 537494933bc..87931bf8455 100644 --- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs +++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs @@ -157,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 8dbfde3c475..69534af7f6a 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"> + + 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 b7b3b3309e6..00000000000 --- 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 partial class GuideEntryPrototype : GuideEntry, IPrototype -{ - public string ID => Id; -} diff --git a/Content.Client/Guidebook/GuidebookSystem.cs b/Content.Client/Guidebook/GuidebookSystem.cs index cb13d4ca6e5..675a025d7a8 100644 --- a/Content.Client/Guidebook/GuidebookSystem.cs +++ b/Content.Client/Guidebook/GuidebookSystem.cs @@ -2,6 +2,7 @@ using Content.Client.Guidebook.Components; using Content.Client.Light; using Content.Client.Verbs; +using Content.Shared.Guidebook; using Content.Shared.Interaction; using Content.Shared.Light.Components; using Content.Shared.Speech; @@ -13,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; @@ -31,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?>? OnGuidebookOpen; + public event Action>, + List>?, + ProtoId?, + bool, + ProtoId?>? OnGuidebookOpen; + public const string GuideEmbedTag = "GuideEmbeded"; private EntityUid _defaultUser; @@ -80,6 +87,11 @@ private void OnGetVerbs(EntityUid uid, GuideHelpComponent component, GetVerbsEve }); } + 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) @@ -143,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 ecf6cb21f70..6e18ad9c575 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 00000000000..84de300d6e0 --- /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 00000000000..b6923c3698e --- /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/Info/InfoSystem.cs b/Content.Client/Info/InfoSystem.cs deleted file mode 100644 index b6979949818..00000000000 --- 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); - Log.Debug("Requested server info."); - RaiseNetworkEvent(new RequestRulesMessage()); - } - - private void OnRulesReceived(RulesMessage message, EntitySessionEventArgs eventArgs) - { - Log.Debug("Received server rules."); - Rules = message; - _rules.UpdateRules(); - } -} diff --git a/Content.Client/Info/RulesAndInfoWindow.cs b/Content.Client/Info/RulesAndInfoWindow.cs index 7a763a1d6f4..b9131dcb3c0 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 3b247646884..04fa7191234 100644 --- a/Content.Client/Info/RulesControl.xaml +++ b/Content.Client/Info/RulesControl.xaml @@ -1,6 +1,18 @@ - + + + + + + +