From d5c4ed819cf3499f87c4f490a14d7082e8a50f4c Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 21 Aug 2020 17:41:50 +0200 Subject: [PATCH 01/88] Add some minor setup behavior to DummyGameTicker by giving it a common base with normal GameTicker. Necessary for the game to, well, function. --- Content.IntegrationTests/DummyGameTicker.cs | 7 +--- Content.Server/GameTicking/GameTicker.cs | 32 +++++++-------- Content.Server/GameTicking/GameTickerBase.cs | 41 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 Content.Server/GameTicking/GameTickerBase.cs diff --git a/Content.IntegrationTests/DummyGameTicker.cs b/Content.IntegrationTests/DummyGameTicker.cs index afe44ed8ff..5259c3de5c 100644 --- a/Content.IntegrationTests/DummyGameTicker.cs +++ b/Content.IntegrationTests/DummyGameTicker.cs @@ -2,14 +2,13 @@ using System; using System.Collections.Generic; using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; -using Content.Shared; using Robust.Server.Interfaces.Player; using Robust.Shared.Map; using Robust.Shared.Timing; namespace Content.IntegrationTests { - public class DummyGameTicker : SharedGameTicker, IGameTicker + public class DummyGameTicker : GameTickerBase, IGameTicker { public GameRunLevel RunLevel { get; } = GameRunLevel.InRound; @@ -25,10 +24,6 @@ namespace Content.IntegrationTests remove { } } - public void Initialize() - { - } - public void Update(FrameEventArgs frameEventArgs) { } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 785ec372a0..78f17b8e78 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -60,7 +60,7 @@ using Timer = Robust.Shared.Timers.Timer; namespace Content.Server.GameTicking { - public partial class GameTicker : SharedGameTicker, IGameTicker + public partial class GameTicker : GameTickerBase, IGameTicker { private static readonly Counter RoundNumberMetric = Metrics.CreateCounter( "ss14_round_number", @@ -124,8 +124,10 @@ namespace Content.Server.GameTicking private TimeSpan LobbyDuration => TimeSpan.FromSeconds(_configurationManager.GetCVar("game.lobbyduration")); - public void Initialize() + public override void Initialize() { + base.Initialize(); + DebugTools.Assert(!_initialized); _configurationManager.RegisterCVar("game.lobbyenabled", false, CVar.ARCHIVE); @@ -135,8 +137,6 @@ namespace Content.Server.GameTicking PresetSuspicion.RegisterCVars(_configurationManager); - _playerManager.PlayerStatusChanged += _handlePlayerStatusChanged; - _netManager.RegisterNetMessage(nameof(MsgTickerJoinLobby)); _netManager.RegisterNetMessage(nameof(MsgTickerJoinGame)); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyStatus)); @@ -209,7 +209,7 @@ namespace Content.Server.GameTicking } else { - if (_playerManager.PlayerCount == 0) + if (PlayerManager.PlayerCount == 0) _roundStartCountdownHasNotStartedYetDueToNoPlayers = true; else _roundStartTimeUtc = DateTime.UtcNow + LobbyDuration; @@ -222,7 +222,7 @@ namespace Content.Server.GameTicking private void ReqWindowAttentionAll() { - foreach (var player in _playerManager.GetAllPlayers()) + foreach (var player in PlayerManager.GetAllPlayers()) { player.RequestWindowAttention(); } @@ -347,7 +347,7 @@ namespace Content.Server.GameTicking //Generate a list of basic player info to display in the end round summary. var listOfPlayerInfo = new List(); - foreach (var ply in _playerManager.GetAllPlayers().OrderBy(p => p.Name)) + foreach (var ply in PlayerManager.GetAllPlayers().OrderBy(p => p.Name)) { var mind = ply.ContentData().Mind; if (mind != null) @@ -643,7 +643,7 @@ namespace Content.Server.GameTicking // Delete the minds of everybody. // TODO: Maybe move this into a separate manager? - foreach (var unCastData in _playerManager.GetAllPlayerData()) unCastData.ContentData().WipeMind(); + foreach (var unCastData in PlayerManager.GetAllPlayerData()) unCastData.ContentData().WipeMind(); // Clear up any game rules. foreach (var rule in _gameRules) rule.Removed(); @@ -651,7 +651,7 @@ namespace Content.Server.GameTicking _gameRules.Clear(); // Move everybody currently in the server to lobby. - foreach (var player in _playerManager.GetAllPlayers()) + foreach (var player in PlayerManager.GetAllPlayers()) { if (_playersInLobby.ContainsKey(player)) continue; @@ -681,8 +681,10 @@ namespace Content.Server.GameTicking Logger.InfoS("ticker", $"Loaded map in {timeSpan.TotalMilliseconds:N2}ms."); } - private void _handlePlayerStatusChanged(object sender, SessionStatusEventArgs args) + protected override void PlayerStatusChanged(object sender, SessionStatusEventArgs args) { + base.PlayerStatusChanged(sender, args); + var session = args.Session; switch (args.NewStatus) @@ -694,13 +696,6 @@ namespace Content.Server.GameTicking case SessionStatus.Connected: { - // Always make sure the client has player data. Mind gets assigned on spawn. - if (session.Data.ContentDataUncast == null) - session.Data.ContentDataUncast = new PlayerData(session.SessionId); - - // timer time must be > tick length - Timer.Spawn(0, args.Session.JoinGame); - _chatManager.DispatchServerAnnouncement($"Player {args.Session.SessionId} joined server!"); if (LobbyEnabled && _roundStartCountdownHasNotStartedYetDueToNoPlayers) @@ -761,7 +756,7 @@ namespace Content.Server.GameTicking // Can't simple check the current connected player count since that doesn't update // before PlayerStatusChanged gets fired. // So in the disconnect handler we'd still see a single player otherwise. - var playersOnline = _playerManager.GetAllPlayers().Any(p => p.Status != SessionStatus.Disconnected); + var playersOnline = PlayerManager.GetAllPlayers().Any(p => p.Status != SessionStatus.Disconnected); if (playersOnline || !_updateOnRoundEnd) { // Still somebody online. @@ -989,7 +984,6 @@ The current game mode is: [color=white]{0}[/color]. [Dependency] private IMapLoader _mapLoader; [Dependency] private IGameTiming _gameTiming; [Dependency] private IConfigurationManager _configurationManager; - [Dependency] private IPlayerManager _playerManager; [Dependency] private IChatManager _chatManager; [Dependency] private IServerNetManager _netManager; [Dependency] private IDynamicTypeFactory _dynamicTypeFactory; diff --git a/Content.Server/GameTicking/GameTickerBase.cs b/Content.Server/GameTicking/GameTickerBase.cs new file mode 100644 index 0000000000..2fc8e350df --- /dev/null +++ b/Content.Server/GameTicking/GameTickerBase.cs @@ -0,0 +1,41 @@ +using Content.Server.Players; +using Content.Shared; +using Robust.Server.Interfaces.Player; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.IoC; +using Robust.Shared.Timers; + +#nullable enable + +namespace Content.Server.GameTicking +{ + /// + /// Handles some low-level GameTicker behavior such as setting up clients when they connect. + /// Does not contain lobby/round handling mechanisms. + /// + public abstract class GameTickerBase : SharedGameTicker + { + [Dependency] protected readonly IPlayerManager PlayerManager = default!; + + public virtual void Initialize() + { + PlayerManager.PlayerStatusChanged += PlayerStatusChanged; + } + + protected virtual void PlayerStatusChanged(object sender, SessionStatusEventArgs args) + { + var session = args.Session; + + if (args.NewStatus == SessionStatus.Connected) + { + // Always make sure the client has player data. Mind gets assigned on spawn. + if (session.Data.ContentDataUncast == null) + session.Data.ContentDataUncast = new PlayerData(session.SessionId); + + // timer time must be > tick length + Timer.Spawn(0, args.Session.JoinGame); + } + } + } +} From 3f0665b9080276c4f991138d4c635188c10113c6 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 21 Aug 2020 17:43:27 +0200 Subject: [PATCH 02/88] Make deleting a visited entity not cleaning it up in the mind. --- .../Components/Mobs/VisitingMindComponent.cs | 20 +++++++++++++++++++ Content.Server/Mobs/Mind.cs | 10 ++++++++++ 2 files changed, 30 insertions(+) create mode 100644 Content.Server/GameObjects/Components/Mobs/VisitingMindComponent.cs diff --git a/Content.Server/GameObjects/Components/Mobs/VisitingMindComponent.cs b/Content.Server/GameObjects/Components/Mobs/VisitingMindComponent.cs new file mode 100644 index 0000000000..46667aff76 --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/VisitingMindComponent.cs @@ -0,0 +1,20 @@ +using Content.Server.Mobs; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Mobs +{ + [RegisterComponent] + public sealed class VisitingMindComponent : Component + { + public override string Name => "VisitingMind"; + + public Mind Mind { get; set; } + + public override void OnRemove() + { + base.OnRemove(); + + Mind?.UnVisit(); + } + } +} diff --git a/Content.Server/Mobs/Mind.cs b/Content.Server/Mobs/Mind.cs index 2c5a275bec..b0247fc202 100644 --- a/Content.Server/Mobs/Mind.cs +++ b/Content.Server/Mobs/Mind.cs @@ -231,6 +231,9 @@ namespace Content.Server.Mobs { Session?.AttachToEntity(entity); VisitingEntity = entity; + + var comp = entity.AddComponent(); + comp.Mind = this; } public void UnVisit() @@ -241,7 +244,14 @@ namespace Content.Server.Mobs } Session?.AttachToEntity(OwnedEntity); + var oldVisitingEnt = VisitingEntity; + // Null this before removing the component to avoid any infinite loops. VisitingEntity = null; + + if (oldVisitingEnt.HasComponent()) + { + oldVisitingEnt.RemoveComponent(); + } } } } From 09e7006e6dab68e818bf23cc8cc5eada9e490429 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 21 Aug 2020 17:43:42 +0200 Subject: [PATCH 03/88] Integration tests for mind entity deletion handling. --- .../Tests/MindEntityDeletionTest.cs | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 Content.IntegrationTests/Tests/MindEntityDeletionTest.cs diff --git a/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs b/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs new file mode 100644 index 0000000000..acbe780035 --- /dev/null +++ b/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs @@ -0,0 +1,161 @@ +using System.Linq; +using System.Threading.Tasks; +using Content.Server.Mobs; +using Content.Server.Players; +using NUnit.Framework; +using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; + +namespace Content.IntegrationTests.Tests +{ + // Tests various scenarios of deleting the entity that a player's mind is connected to. + [TestFixture] + public class MindEntityDeletionTest : ContentIntegrationTest + { + [Test] + public async Task TestDeleteVisiting() + { + var (_, server) = await StartConnectedServerDummyTickerClientPair(); + + IEntity playerEnt = null; + IEntity visitEnt = null; + Mind mind = null; + server.Assert(() => + { + var player = IoCManager.Resolve().GetAllPlayers().Single(); + + var mapMan = IoCManager.Resolve(); + var entMgr = IoCManager.Resolve(); + + mapMan.CreateNewMapEntity(MapId.Nullspace); + + playerEnt = entMgr.SpawnEntity(null, MapCoordinates.Nullspace); + visitEnt = entMgr.SpawnEntity(null, MapCoordinates.Nullspace); + + mind = new Mind(player.SessionId); + player.ContentData().Mind = mind; + + mind.TransferTo(playerEnt); + mind.Visit(visitEnt); + + Assert.That(player.AttachedEntity, Is.EqualTo(visitEnt)); + Assert.That(mind.VisitingEntity, Is.EqualTo(visitEnt)); + }); + + server.RunTicks(1); + + server.Assert(() => + { + visitEnt.Delete(); + + Assert.That(mind.VisitingEntity, Is.Null); + + // This used to throw so make sure it doesn't. + playerEnt.Delete(); + }); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task TestGhostOnDelete() + { + // Has to be a non-dummy ticker so we have a proper map. + var (_, server) = await StartConnectedServerClientPair(); + + IEntity playerEnt = null; + Mind mind = null; + server.Assert(() => + { + var player = IoCManager.Resolve().GetAllPlayers().Single(); + + var mapMan = IoCManager.Resolve(); + var entMgr = IoCManager.Resolve(); + + mapMan.CreateNewMapEntity(MapId.Nullspace); + + playerEnt = entMgr.SpawnEntity(null, MapCoordinates.Nullspace); + + mind = new Mind(player.SessionId); + player.ContentData().Mind = mind; + + mind.TransferTo(playerEnt); + + Assert.That(mind.CurrentEntity, Is.EqualTo(playerEnt)); + }); + + server.RunTicks(1); + + server.Post(() => + { + playerEnt.Delete(); + }); + + server.RunTicks(1); + + server.Assert(() => + { + Assert.That(mind.CurrentEntity.IsValid(), Is.True); + }); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task TestGhostOnDeleteMap() + { + // Has to be a non-dummy ticker so we have a proper map. + var (_, server) = await StartConnectedServerClientPair(); + + IEntity playerEnt = null; + Mind mind = null; + MapId map = default; + server.Assert(() => + { + var player = IoCManager.Resolve().GetAllPlayers().Single(); + + var mapMan = IoCManager.Resolve(); + + map = mapMan.CreateMap(); + var grid = mapMan.CreateGrid(map); + + var entMgr = IoCManager.Resolve(); + + mapMan.CreateNewMapEntity(MapId.Nullspace); + + playerEnt = entMgr.SpawnEntity(null, new GridCoordinates(Vector2.Zero, grid.Index)); + + mind = new Mind(player.SessionId); + player.ContentData().Mind = mind; + + mind.TransferTo(playerEnt); + + Assert.That(mind.CurrentEntity, Is.EqualTo(playerEnt)); + }); + + server.RunTicks(1); + + server.Post(() => + { + var mapMan = IoCManager.Resolve(); + + mapMan.DeleteMap(map); + }); + + server.RunTicks(1); + + server.Assert(() => + { + Assert.That(mind.CurrentEntity.IsValid(), Is.True); + Assert.That(mind.CurrentEntity, Is.Not.EqualTo(playerEnt)); + }); + + await server.WaitIdleAsync(); + } + } +} From dc602034f0a857a8ddbafbd3521caf9e0a9ff1e5 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 21 Aug 2020 17:45:39 +0200 Subject: [PATCH 04/88] Update submodule --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index f0824212da..865610df1e 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit f0824212da5d913a51c8e40dee9a6622b9d5d1b7 +Subproject commit 865610df1ea035974288b67f16d7c6baf19c2fa5 From 94e85adedb1bf953dacc1bde9e0af8263f83e1a0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 21 Aug 2020 17:48:22 +0200 Subject: [PATCH 05/88] I blame Rider. --- Content.Server/GameTicking/GameTickerBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/GameTicking/GameTickerBase.cs b/Content.Server/GameTicking/GameTickerBase.cs index 2fc8e350df..917be07cd8 100644 --- a/Content.Server/GameTicking/GameTickerBase.cs +++ b/Content.Server/GameTicking/GameTickerBase.cs @@ -23,7 +23,7 @@ namespace Content.Server.GameTicking PlayerManager.PlayerStatusChanged += PlayerStatusChanged; } - protected virtual void PlayerStatusChanged(object sender, SessionStatusEventArgs args) + protected virtual void PlayerStatusChanged(object? sender, SessionStatusEventArgs args) { var session = args.Session; From fd81e05d5bcc21cd3306fe3c52f0c129f0d4719d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= <6766154+Zumorica@users.noreply.github.com> Date: Fri, 21 Aug 2020 18:09:47 +0200 Subject: [PATCH 06/88] Add inventory helpers and an integration test for it (#1841) --- .../Tests/InventoryHelpersTest.cs | 69 +++++++++++++++++++ Content.Server/Utility/InventoryHelpers.cs | 49 +++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 Content.IntegrationTests/Tests/InventoryHelpersTest.cs create mode 100644 Content.Server/Utility/InventoryHelpers.cs diff --git a/Content.IntegrationTests/Tests/InventoryHelpersTest.cs b/Content.IntegrationTests/Tests/InventoryHelpersTest.cs new file mode 100644 index 0000000000..7c7bbfbac4 --- /dev/null +++ b/Content.IntegrationTests/Tests/InventoryHelpersTest.cs @@ -0,0 +1,69 @@ +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.Utility; +using Content.Shared.GameObjects.Components.Inventory; +using NUnit.Framework; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; + +namespace Content.IntegrationTests.Tests +{ + [TestFixture] + [TestOf(typeof(InventoryHelpers))] + public class InventoryHelpersTest : ContentIntegrationTest + { + [Test] + public async Task SpawnItemInSlotTest() + { + var server = StartServerDummyTicker(); + + IEntity human = null; + InventoryComponent inventory = null; + StunnableComponent stun = null; + + + server.Assert(() => + { + var mapMan = IoCManager.Resolve(); + + mapMan.CreateNewMapEntity(MapId.Nullspace); + + var entityMan = IoCManager.Resolve(); + + human = entityMan.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace); + inventory = human.GetComponent(); + stun = human.GetComponent(); + + // Can't do the test if this human doesn't have the slots for it. + Assert.That(inventory.HasSlot(Slots.INNERCLOTHING)); + Assert.That(inventory.HasSlot(Slots.IDCARD)); + + Assert.That(inventory.SpawnItemInSlot(Slots.INNERCLOTHING, "UniformJanitor", true)); + + // Do we actually have the uniform equipped? + Assert.That(inventory.TryGetSlotItem(Slots.INNERCLOTHING, out ItemComponent uniform)); + Assert.That(uniform.Owner.Prototype != null && uniform.Owner.Prototype.ID == "UniformJanitor"); + + stun.Stun(1f); + + // Since the mob is stunned, they can't equip this. + Assert.That(inventory.SpawnItemInSlot(Slots.IDCARD, "AssistantIDCard", true), Is.False); + + // Make sure we don't have the ID card equipped. + Assert.That(inventory.TryGetSlotItem(Slots.IDCARD, out ItemComponent _), Is.False); + + // Let's try skipping the interaction check and see if it equips it! + Assert.That(inventory.SpawnItemInSlot(Slots.IDCARD, "AssistantIDCard", false)); + Assert.That(inventory.TryGetSlotItem(Slots.IDCARD, out ItemComponent id)); + Assert.That(id.Owner.Prototype != null && id.Owner.Prototype.ID == "AssistantIDCard"); + }); + + await server.WaitIdleAsync(); + } + } +} diff --git a/Content.Server/Utility/InventoryHelpers.cs b/Content.Server/Utility/InventoryHelpers.cs new file mode 100644 index 0000000000..ec6bb9e851 --- /dev/null +++ b/Content.Server/Utility/InventoryHelpers.cs @@ -0,0 +1,49 @@ +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Shared.GameObjects.Components.Inventory; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; + +namespace Content.Server.Utility +{ + public static class InventoryHelpers + { + public static bool SpawnItemInSlot(this InventoryComponent inventory, Slots slot, string prototype, bool mobCheck = false) + { + var entityManager = inventory.Owner.EntityManager; + var protoManager = IoCManager.Resolve(); + + // Let's do nothing if the owner of the inventory has been deleted. + if (inventory.Owner.Deleted) + return false; + + // If we don't have that slot or there's already an item there, we do nothing. + if (!inventory.HasSlot(slot) || inventory.TryGetSlotItem(slot, out ItemComponent _)) + return false; + + // If the prototype in question doesn't exist, we do nothing. + if (!protoManager.HasIndex(prototype)) + return false; + + // Let's spawn this in nullspace first... + var item = entityManager.SpawnEntity(prototype, MapCoordinates.Nullspace); + + // Helper method that deletes the item and returns false. + bool DeleteItem() + { + item.Delete(); + return false; + } + + // If this doesn't have an item component, then we can't do anything with it. + if (!item.TryGetComponent(out ItemComponent itemComp)) + return DeleteItem(); + + // We finally try to equip the item, otherwise we delete it. + return inventory.Equip(slot, itemComp, mobCheck) || DeleteItem(); + } + } +} From 7887f78f0da80213101558d2b19eaea59de65d5f Mon Sep 17 00:00:00 2001 From: Swept Date: Fri, 21 Aug 2020 09:26:31 -0700 Subject: [PATCH 07/88] Restricts character age from being under 18 or over 120 years old (#1836) --- Content.Shared/Preferences/HumanoidCharacterProfile.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 58633d2a2a..0f2feac203 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -11,7 +11,7 @@ namespace Content.Shared.Preferences private readonly Dictionary _jobPriorities; private readonly List _antagPreferences; public static int MinimumAge = 18; - public static int MaximumAge = 90; + public static int MaximumAge = 120; private HumanoidCharacterProfile( string name, @@ -69,7 +69,7 @@ namespace Content.Shared.Preferences public HumanoidCharacterProfile WithAge(int age) { - return new HumanoidCharacterProfile(Name, age, Sex, Appearance, _jobPriorities, PreferenceUnavailable, _antagPreferences); + return new HumanoidCharacterProfile(Name, Math.Clamp(age, MinimumAge, MaximumAge), Sex, Appearance, _jobPriorities, PreferenceUnavailable, _antagPreferences); } public HumanoidCharacterProfile WithSex(Sex sex) From 1086474cd0bfe188e80b1f9d330a495c9a860009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Fri, 21 Aug 2020 20:29:29 +0200 Subject: [PATCH 08/88] Do not spawn the item in nullspace in InventoryHelpers --- Content.Server/Utility/InventoryHelpers.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Content.Server/Utility/InventoryHelpers.cs b/Content.Server/Utility/InventoryHelpers.cs index ec6bb9e851..ec998908ac 100644 --- a/Content.Server/Utility/InventoryHelpers.cs +++ b/Content.Server/Utility/InventoryHelpers.cs @@ -15,9 +15,10 @@ namespace Content.Server.Utility { var entityManager = inventory.Owner.EntityManager; var protoManager = IoCManager.Resolve(); + var user = inventory.Owner; // Let's do nothing if the owner of the inventory has been deleted. - if (inventory.Owner.Deleted) + if (user.Deleted) return false; // If we don't have that slot or there's already an item there, we do nothing. @@ -28,8 +29,8 @@ namespace Content.Server.Utility if (!protoManager.HasIndex(prototype)) return false; - // Let's spawn this in nullspace first... - var item = entityManager.SpawnEntity(prototype, MapCoordinates.Nullspace); + // Let's spawn this first... + var item = entityManager.SpawnEntity(prototype, user.Transform.MapPosition); // Helper method that deletes the item and returns false. bool DeleteItem() From b3156e9934b5d0680396dbdd219962d60372cee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Fri, 21 Aug 2020 21:09:24 +0200 Subject: [PATCH 09/88] Play special sound when you get chosen as traitor in SSS. --- .../GameTicking/GameRules/RuleSuspicion.cs | 7 +++++++ .../Mobs/Roles/SuspicionTraitorRole.cs | 3 +++ Resources/Audio/Misc/tatoralert.ogg | Bin 0 -> 88816 bytes 3 files changed, 10 insertions(+) create mode 100644 Resources/Audio/Misc/tatoralert.ogg diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs index 026c11516a..326614c2dd 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -3,11 +3,15 @@ using System.Threading; using Content.Server.GameObjects.Components.Suspicion; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; +using Content.Server.Mobs; using Content.Server.Mobs.Roles; using Content.Server.Players; using Content.Shared.GameObjects.Components.Damage; +using Robust.Server.GameObjects.EntitySystems; using Robust.Server.Interfaces.Player; +using Robust.Shared.Audio; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.IoC; using Timer = Robust.Shared.Timers.Timer; @@ -33,6 +37,9 @@ namespace Content.Server.GameTicking.GameRules { _chatManager.DispatchServerAnnouncement("There are traitors on the station! Find them, and kill them!"); + EntitySystem.Get().PlayGlobal("/Audio/Misc/tatoralert.ogg", AudioParams.Default, + (session) => session.ContentData().Mind?.HasRole() ?? false); + Timer.SpawnRepeating(DeadCheckDelay, _checkWinConditions, _checkTimerCancel.Token); } diff --git a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs b/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs index 8a834564fd..f10bf273e8 100644 --- a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs +++ b/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.Linq; using Content.Server.Interfaces.Chat; using Content.Shared.Roles; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Localization; namespace Content.Server.Mobs.Roles diff --git a/Resources/Audio/Misc/tatoralert.ogg b/Resources/Audio/Misc/tatoralert.ogg new file mode 100644 index 0000000000000000000000000000000000000000..ca0efa0ea0f1c986a58450675f52d92a3d79ce0e GIT binary patch literal 88816 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̞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 literal 0 HcmV?d00001 From 72e50cce942a68f136a3c82d6da9e24e03dfb705 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 22 Aug 2020 20:03:24 +1000 Subject: [PATCH 10/88] AI pickup changes (#1811) * AI pickup changes Eating and drinking isn't spammed anymore. AI can do InRangeUnobstructed checks for item pickups. AI can open drink cans. AI littering to be coded. * #nullable enable * github's nullable fails are actively shortening my lifespan * Use a const instead So it's easier to find given the performance implications. Co-authored-by: Metal Gear Sloth --- .../Inventory/OpenStorageOperator.cs | 3 +- .../Inventory/PickupEntityOperator.cs | 5 +- ...rator.cs => UseItemInInventoryOperator.cs} | 10 +-- .../Movement/MoveToEntityOperator.cs | 12 ++- .../Nutrition/UseDrinkInInventoryOperator.cs | 73 +++++++++++++++++++ .../Nutrition/UseFoodInInventoryOperator.cs | 73 +++++++++++++++++++ .../Sequences/GoPickupEntitySequence.cs | 2 +- .../Actions/Clothing/Gloves/EquipGloves.cs | 2 +- .../Actions/Clothing/Head/EquipHead.cs | 2 +- .../OuterClothing/EquipOuterClothing.cs | 2 +- .../Actions/Clothing/Shoes/EquipShoes.cs | 2 +- .../Actions/Nutrition/Drink/PickUpDrink.cs | 4 - .../Nutrition/Drink/UseDrinkInInventory.cs | 3 +- .../Nutrition/Food/UseFoodInInventory.cs | 3 +- .../Considerations/Combat/TargetHealthCon.cs | 2 +- .../Movement/TargetDistanceCon.cs | 2 +- .../Components/Nutrition/DrinkComponent.cs | 2 +- .../AI/Steering/AiSteeringSystem.cs | 36 +++++++-- .../Steering/EntityTargetSteeringRequest.cs | 14 +++- .../AI/Steering/GridTargetSteeringRequest.cs | 8 +- .../AI/Steering/IAiSteeringRequest.cs | 14 +++- 21 files changed, 233 insertions(+), 41 deletions(-) rename Content.Server/AI/Operators/Inventory/{UseItemInHandsOperator.cs => UseItemInInventoryOperator.cs} (85%) create mode 100644 Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs create mode 100644 Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs diff --git a/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs b/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs index 07eeb3e911..2916474724 100644 --- a/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs +++ b/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs @@ -1,4 +1,5 @@ -using Content.Server.AI.Utility; +#nullable enable +using Content.Server.AI.Utility; using Content.Server.AI.WorldState.States.Inventory; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.Utility; diff --git a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs index 6b5b438663..a31042b2af 100644 --- a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs +++ b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs @@ -1,3 +1,4 @@ +#nullable enable using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.EntitySystems.Click; @@ -20,11 +21,9 @@ namespace Content.Server.AI.Operators.Inventory _target = target; } - // TODO: When I spawn new entities they seem to duplicate clothing or something? public override Outcome Execute(float frameTime) { - if (_target == null || - _target.Deleted || + if (_target.Deleted || !_target.HasComponent() || ContainerHelpers.IsInContainer(_target) || !InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition)) diff --git a/Content.Server/AI/Operators/Inventory/UseItemInHandsOperator.cs b/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs similarity index 85% rename from Content.Server/AI/Operators/Inventory/UseItemInHandsOperator.cs rename to Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs index 130b139ad0..7f35892984 100644 --- a/Content.Server/AI/Operators/Inventory/UseItemInHandsOperator.cs +++ b/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs @@ -1,3 +1,4 @@ +#nullable enable using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Robust.Shared.Interfaces.GameObjects; @@ -7,12 +8,12 @@ namespace Content.Server.AI.Operators.Inventory ///

/// Will find the item in storage, put it in an active hand, then use it /// - public class UseItemInHandsOperator : AiOperator + public class UseItemInInventoryOperator : AiOperator { private readonly IEntity _owner; private readonly IEntity _target; - public UseItemInHandsOperator(IEntity owner, IEntity target) + public UseItemInInventoryOperator(IEntity owner, IEntity target) { _owner = owner; _target = target; @@ -20,11 +21,6 @@ namespace Content.Server.AI.Operators.Inventory public override Outcome Execute(float frameTime) { - if (_target == null) - { - return Outcome.Failed; - } - // TODO: Also have this check storage a la backpack etc. if (!_owner.TryGetComponent(out HandsComponent handsComponent)) { diff --git a/Content.Server/AI/Operators/Movement/MoveToEntityOperator.cs b/Content.Server/AI/Operators/Movement/MoveToEntityOperator.cs index dd1d2120c8..35a15293e7 100644 --- a/Content.Server/AI/Operators/Movement/MoveToEntityOperator.cs +++ b/Content.Server/AI/Operators/Movement/MoveToEntityOperator.cs @@ -16,12 +16,20 @@ namespace Content.Server.AI.Operators.Movement public float ArrivalDistance { get; } public float PathfindingProximity { get; } - public MoveToEntityOperator(IEntity owner, IEntity target, float arrivalDistance = 1.0f, float pathfindingProximity = 1.5f) + private bool _requiresInRangeUnobstructed; + + public MoveToEntityOperator( + IEntity owner, + IEntity target, + float arrivalDistance = 1.0f, + float pathfindingProximity = 1.5f, + bool requiresInRangeUnobstructed = false) { _owner = owner; _target = target; ArrivalDistance = arrivalDistance; PathfindingProximity = pathfindingProximity; + _requiresInRangeUnobstructed = requiresInRangeUnobstructed; } public override bool TryStartup() @@ -32,7 +40,7 @@ namespace Content.Server.AI.Operators.Movement } var steering = EntitySystem.Get(); - _request = new EntityTargetSteeringRequest(_target, ArrivalDistance, PathfindingProximity); + _request = new EntityTargetSteeringRequest(_target, ArrivalDistance, PathfindingProximity, _requiresInRangeUnobstructed); steering.Register(_owner, _request); return true; } diff --git a/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs b/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs new file mode 100644 index 0000000000..366197a2fd --- /dev/null +++ b/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs @@ -0,0 +1,73 @@ +#nullable enable +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Server.GameObjects.Components.Nutrition; +using Content.Shared.GameObjects.Components.Nutrition; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Random; + +namespace Content.Server.AI.Operators.Nutrition +{ + public class UseDrinkInInventoryOperator : AiOperator + { + private readonly IEntity _owner; + private readonly IEntity _target; + private float _interactionCooldown; + + public UseDrinkInInventoryOperator(IEntity owner, IEntity target) + { + _owner = owner; + _target = target; + } + + public override Outcome Execute(float frameTime) + { + if (_interactionCooldown >= 0) + { + _interactionCooldown -= frameTime; + return Outcome.Continuing; + } + + // TODO: Also have this check storage a la backpack etc. + if (_target.Deleted || + !_owner.TryGetComponent(out HandsComponent handsComponent) || + !_target.TryGetComponent(out ItemComponent itemComponent)) + { + return Outcome.Failed; + } + + DrinkComponent? drinkComponent = null; + + foreach (var slot in handsComponent.ActivePriorityEnumerable()) + { + if (handsComponent.GetItem(slot) != itemComponent) continue; + handsComponent.ActiveHand = slot; + if (!_target.TryGetComponent(out drinkComponent)) + { + return Outcome.Failed; + } + + // This should also implicitly open it. + handsComponent.ActivateItem(); + _interactionCooldown = IoCManager.Resolve().NextFloat() + 0.5f; + } + + if (drinkComponent == null) + { + return Outcome.Failed; + } + + if (drinkComponent.Deleted || + drinkComponent.Empty || + _owner.TryGetComponent(out ThirstComponent thirstComponent) && + thirstComponent.CurrentThirst >= thirstComponent.ThirstThresholds[ThirstThreshold.Okay]) + { + return Outcome.Success; + } + + return Outcome.Continuing; + } + } +} \ No newline at end of file diff --git a/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs b/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs new file mode 100644 index 0000000000..7814a42d7e --- /dev/null +++ b/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs @@ -0,0 +1,73 @@ +#nullable enable +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Server.GameObjects.Components.Nutrition; +using Content.Shared.GameObjects.Components.Nutrition; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Random; + +namespace Content.Server.AI.Operators.Nutrition +{ + public class UseFoodInInventoryOperator : AiOperator + { + private readonly IEntity _owner; + private readonly IEntity _target; + private float _interactionCooldown; + + public UseFoodInInventoryOperator(IEntity owner, IEntity target) + { + _owner = owner; + _target = target; + } + + public override Outcome Execute(float frameTime) + { + if (_interactionCooldown >= 0) + { + _interactionCooldown -= frameTime; + return Outcome.Continuing; + } + + // TODO: Also have this check storage a la backpack etc. + if (_target.Deleted || + !_owner.TryGetComponent(out HandsComponent handsComponent) || + !_target.TryGetComponent(out ItemComponent itemComponent)) + { + return Outcome.Failed; + } + + FoodComponent? foodComponent = null; + + foreach (var slot in handsComponent.ActivePriorityEnumerable()) + { + if (handsComponent.GetItem(slot) != itemComponent) continue; + handsComponent.ActiveHand = slot; + if (!_target.TryGetComponent(out foodComponent)) + { + return Outcome.Failed; + } + + // This should also implicitly open it. + handsComponent.ActivateItem(); + _interactionCooldown = IoCManager.Resolve().NextFloat() + 0.5f; + } + + if (foodComponent == null) + { + return Outcome.Failed; + } + + if (_target.Deleted || + foodComponent.UsesRemaining == 0 || + _owner.TryGetComponent(out HungerComponent hungerComponent) && + hungerComponent.CurrentHunger >= hungerComponent.HungerThresholds[HungerThreshold.Okay]) + { + return Outcome.Success; + } + + return Outcome.Continuing; + } + } +} \ No newline at end of file diff --git a/Content.Server/AI/Operators/Sequences/GoPickupEntitySequence.cs b/Content.Server/AI/Operators/Sequences/GoPickupEntitySequence.cs index 65922d2366..48cf084cbe 100644 --- a/Content.Server/AI/Operators/Sequences/GoPickupEntitySequence.cs +++ b/Content.Server/AI/Operators/Sequences/GoPickupEntitySequence.cs @@ -11,7 +11,7 @@ namespace Content.Server.AI.Operators.Sequences { Sequence = new Queue(new AiOperator[] { - new MoveToEntityOperator(owner, target), + new MoveToEntityOperator(owner, target, requiresInRangeUnobstructed: true), new OpenStorageOperator(owner, target), new PickupEntityOperator(owner, target), }); diff --git a/Content.Server/AI/Utility/Actions/Clothing/Gloves/EquipGloves.cs b/Content.Server/AI/Utility/Actions/Clothing/Gloves/EquipGloves.cs index fcd63a2da6..1804f2e798 100644 --- a/Content.Server/AI/Utility/Actions/Clothing/Gloves/EquipGloves.cs +++ b/Content.Server/AI/Utility/Actions/Clothing/Gloves/EquipGloves.cs @@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Gloves ActionOperators = new Queue(new AiOperator[] { new EquipEntityOperator(Owner, _entity), - new UseItemInHandsOperator(Owner, _entity), + new UseItemInInventoryOperator(Owner, _entity), }); } diff --git a/Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs b/Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs index 8e37b88f5d..144aa99430 100644 --- a/Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs +++ b/Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs @@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Head ActionOperators = new Queue(new AiOperator[] { new EquipEntityOperator(Owner, _entity), - new UseItemInHandsOperator(Owner, _entity), + new UseItemInInventoryOperator(Owner, _entity), }); } diff --git a/Content.Server/AI/Utility/Actions/Clothing/OuterClothing/EquipOuterClothing.cs b/Content.Server/AI/Utility/Actions/Clothing/OuterClothing/EquipOuterClothing.cs index 53e83765a1..4a2460e63c 100644 --- a/Content.Server/AI/Utility/Actions/Clothing/OuterClothing/EquipOuterClothing.cs +++ b/Content.Server/AI/Utility/Actions/Clothing/OuterClothing/EquipOuterClothing.cs @@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing ActionOperators = new Queue(new AiOperator[] { new EquipEntityOperator(Owner, _entity), - new UseItemInHandsOperator(Owner, _entity), + new UseItemInInventoryOperator(Owner, _entity), }); } diff --git a/Content.Server/AI/Utility/Actions/Clothing/Shoes/EquipShoes.cs b/Content.Server/AI/Utility/Actions/Clothing/Shoes/EquipShoes.cs index 07f0da5f8b..b15bb5e82b 100644 --- a/Content.Server/AI/Utility/Actions/Clothing/Shoes/EquipShoes.cs +++ b/Content.Server/AI/Utility/Actions/Clothing/Shoes/EquipShoes.cs @@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Shoes ActionOperators = new Queue(new AiOperator[] { new EquipEntityOperator(Owner, _entity), - new UseItemInHandsOperator(Owner, _entity), + new UseItemInInventoryOperator(Owner, _entity), }); } diff --git a/Content.Server/AI/Utility/Actions/Nutrition/Drink/PickUpDrink.cs b/Content.Server/AI/Utility/Actions/Nutrition/Drink/PickUpDrink.cs index 0549fc1ed7..d6a13d6794 100644 --- a/Content.Server/AI/Utility/Actions/Nutrition/Drink/PickUpDrink.cs +++ b/Content.Server/AI/Utility/Actions/Nutrition/Drink/PickUpDrink.cs @@ -40,10 +40,6 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Drink return new[] { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Nutrition), considerationsManager.Get() .PresetCurve(context, PresetCurve.Distance), considerationsManager.Get() diff --git a/Content.Server/AI/Utility/Actions/Nutrition/Drink/UseDrinkInInventory.cs b/Content.Server/AI/Utility/Actions/Nutrition/Drink/UseDrinkInInventory.cs index f05838ce22..e94cd74356 100644 --- a/Content.Server/AI/Utility/Actions/Nutrition/Drink/UseDrinkInInventory.cs +++ b/Content.Server/AI/Utility/Actions/Nutrition/Drink/UseDrinkInInventory.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Content.Server.AI.Operators; using Content.Server.AI.Operators.Inventory; +using Content.Server.AI.Operators.Nutrition; using Content.Server.AI.Utility.Considerations; using Content.Server.AI.Utility.Considerations.Inventory; using Content.Server.AI.Utility.Considerations.Nutrition.Drink; @@ -27,7 +28,7 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Drink ActionOperators = new Queue(new AiOperator[] { new EquipEntityOperator(Owner, _entity), - new UseItemInHandsOperator(Owner, _entity), + new UseDrinkInInventoryOperator(Owner, _entity), }); } diff --git a/Content.Server/AI/Utility/Actions/Nutrition/Food/UseFoodInInventory.cs b/Content.Server/AI/Utility/Actions/Nutrition/Food/UseFoodInInventory.cs index 6edcbb9c43..b12c40dbf8 100644 --- a/Content.Server/AI/Utility/Actions/Nutrition/Food/UseFoodInInventory.cs +++ b/Content.Server/AI/Utility/Actions/Nutrition/Food/UseFoodInInventory.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Content.Server.AI.Operators; using Content.Server.AI.Operators.Inventory; +using Content.Server.AI.Operators.Nutrition; using Content.Server.AI.Utility.Considerations; using Content.Server.AI.Utility.Considerations.Inventory; using Content.Server.AI.Utility.Considerations.Nutrition.Food; @@ -27,7 +28,7 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Food ActionOperators = new Queue(new AiOperator[] { new EquipEntityOperator(Owner, _entity), - new UseItemInHandsOperator(Owner, _entity), + new UseFoodInInventoryOperator(Owner, _entity), }); } diff --git a/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs b/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs index 64fce8bf56..021e35de23 100644 --- a/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs +++ b/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs @@ -11,7 +11,7 @@ namespace Content.Server.AI.Utility.Considerations.Combat { var target = context.GetState().GetValue(); - if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent)) + if (target == null || target.Deleted || !target.TryGetComponent(out IDamageableComponent damageableComponent)) { return 0.0f; } diff --git a/Content.Server/AI/Utility/Considerations/Movement/TargetDistanceCon.cs b/Content.Server/AI/Utility/Considerations/Movement/TargetDistanceCon.cs index ca63985179..a22fcc17e8 100644 --- a/Content.Server/AI/Utility/Considerations/Movement/TargetDistanceCon.cs +++ b/Content.Server/AI/Utility/Considerations/Movement/TargetDistanceCon.cs @@ -9,7 +9,7 @@ namespace Content.Server.AI.Utility.Considerations.Movement { var self = context.GetState().GetValue(); var target = context.GetState().GetValue(); - if (target == null || target.Transform.GridID != self.Transform.GridID) + if (target == null || target.Deleted || target.Transform.GridID != self.Transform.GridID) { return 0.0f; } diff --git a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs index a52ebc1d48..5bfbebb9b1 100644 --- a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs @@ -44,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Nutrition [ViewVariables] public ReagentUnit TransferAmount { get; private set; } = ReagentUnit.New(2); [ViewVariables] - protected bool Opened { get; set; } + public bool Opened { get; protected set; } [ViewVariables] public bool Empty => _contents.CurrentVolume.Float() <= 0; diff --git a/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs index 2d955dbe82..27b85bc7af 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs @@ -7,6 +7,7 @@ using Content.Server.GameObjects.Components.Movement; using Content.Server.GameObjects.EntitySystems.AI.Pathfinding; using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders; using Content.Server.GameObjects.EntitySystems.JobQueues; +using Content.Server.Utility; using Content.Shared.GameObjects.EntitySystems; using Robust.Server.Interfaces.Timing; using Robust.Shared.GameObjects.Components; @@ -42,6 +43,11 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering /// private const float TileTolerance = 0.8f; + /// + /// How long to wait between checks (if necessary). + /// + private const float InRangeUnobstructedCooldown = 0.25f; + private Dictionary RunningAgents => _agentLists[_listIndex]; // We'll cycle the running list every tick as all we're doing is getting a vector2 for the @@ -208,7 +214,8 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering foreach (var (agent, steering) in RunningAgents) { - var result = Steer(agent, steering); + // Yeah look it's not true frametime but good enough. + var result = Steer(agent, steering, frameTime * RunningAgents.Count); steering.Status = result; switch (result) @@ -236,9 +243,10 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering /// /// /// + /// /// /// - private SteeringStatus Steer(IEntity entity, IAiSteeringRequest steeringRequest) + private SteeringStatus Steer(IEntity entity, IAiSteeringRequest steeringRequest, float frameTime) { // Main optimisation to be done below is the redundant calls and adding more variables if (!entity.TryGetComponent(out AiControllerComponent controller) || !ActionBlockerSystem.CanMove(entity)) @@ -262,13 +270,27 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering // Check if we have arrived var targetDistance = (entity.Transform.MapPosition.Position - steeringRequest.TargetMap.Position).Length; - if (targetDistance <= steeringRequest.ArrivalDistance) + steeringRequest.TimeUntilInteractionCheck -= frameTime; + + if (targetDistance <= steeringRequest.ArrivalDistance && steeringRequest.TimeUntilInteractionCheck <= 0.0f) + { + if (!steeringRequest.RequiresInRangeUnobstructed || + InteractionChecks.InRangeUnobstructed(entity, steeringRequest.TargetMap, steeringRequest.ArrivalDistance, ignoredEnt: entity)) + { + // TODO: Need cruder LOS checks for ranged weaps + controller.VelocityDir = Vector2.Zero; + return SteeringStatus.Arrived; + } + + steeringRequest.TimeUntilInteractionCheck = InRangeUnobstructedCooldown; + // Welp, we'll keep on moving. + } + + // If we're really close don't swiggity swoogity back and forth and just wait for the interaction check maybe? + if (steeringRequest.TimeUntilInteractionCheck > 0.0f && targetDistance <= 0.1f) { - // TODO: If we need LOS and are moving to an entity then we may not be in range yet - // Chuck out a ray every half second or so and keep moving until we are? - // Alternatively could use tile-based LOS checks via the pathfindingsystem I guess controller.VelocityDir = Vector2.Zero; - return SteeringStatus.Arrived; + return SteeringStatus.Moving; } // Handle pathfinding job diff --git a/Content.Server/GameObjects/EntitySystems/AI/Steering/EntityTargetSteeringRequest.cs b/Content.Server/GameObjects/EntitySystems/AI/Steering/EntityTargetSteeringRequest.cs index 8b5c221a5b..06da9d3bda 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/Steering/EntityTargetSteeringRequest.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/Steering/EntityTargetSteeringRequest.cs @@ -10,20 +10,26 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering public GridCoordinates TargetGrid => _target.Transform.GridPosition; public IEntity Target => _target; private IEntity _target; + /// public float ArrivalDistance { get; } + /// public float PathfindingProximity { get; } + /// - /// How far the target can move before we re-path + /// How far the target can move before we re-path /// public float TargetMaxMove { get; } = 1.5f; - /// - /// If we need LOS on the entity first before interaction - /// + /// public bool RequiresInRangeUnobstructed { get; } + /// + /// To avoid spamming InRangeUnobstructed we'll apply a cd to it. + /// + public float TimeUntilInteractionCheck { get; set; } + public EntityTargetSteeringRequest(IEntity target, float arrivalDistance, float pathfindingProximity = 0.5f, bool requiresInRangeUnobstructed = false) { _target = target; diff --git a/Content.Server/GameObjects/EntitySystems/AI/Steering/GridTargetSteeringRequest.cs b/Content.Server/GameObjects/EntitySystems/AI/Steering/GridTargetSteeringRequest.cs index d054e77623..7a0efe74a7 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/Steering/GridTargetSteeringRequest.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/Steering/GridTargetSteeringRequest.cs @@ -14,7 +14,12 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering /// public float PathfindingProximity { get; } - public GridTargetSteeringRequest(GridCoordinates targetGrid, float arrivalDistance, float pathfindingProximity = 0.5f) + public bool RequiresInRangeUnobstructed { get; } + + public float TimeUntilInteractionCheck { get; set; } = 0.0f; + + + public GridTargetSteeringRequest(GridCoordinates targetGrid, float arrivalDistance, float pathfindingProximity = 0.5f, bool requiresInRangeUnobstructed = false) { // Get it once up front so we the manager doesn't have to continuously get it var mapManager = IoCManager.Resolve(); @@ -22,6 +27,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering TargetGrid = targetGrid; ArrivalDistance = arrivalDistance; PathfindingProximity = pathfindingProximity; + RequiresInRangeUnobstructed = requiresInRangeUnobstructed; } } } \ No newline at end of file diff --git a/Content.Server/GameObjects/EntitySystems/AI/Steering/IAiSteeringRequest.cs b/Content.Server/GameObjects/EntitySystems/AI/Steering/IAiSteeringRequest.cs index da22b77b1c..264c675b2c 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/Steering/IAiSteeringRequest.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/Steering/IAiSteeringRequest.cs @@ -8,13 +8,23 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering MapCoordinates TargetMap { get; } GridCoordinates TargetGrid { get; } /// - /// How close we have to get before we've arrived + /// How close we have to get before we've arrived /// float ArrivalDistance { get; } /// - /// How close the pathfinder needs to get. Typically you want this set lower than ArrivalDistance + /// How close the pathfinder needs to get. Typically you want this set lower than ArrivalDistance /// float PathfindingProximity { get; } + + /// + /// If we need LOS on the entity first before interaction + /// + bool RequiresInRangeUnobstructed { get; } + + /// + /// To avoid spamming InRangeUnobstructed we'll apply a cd to it. + /// + public float TimeUntilInteractionCheck { get; set; } } } \ No newline at end of file From 1c21f2c3b078bf9505e88169ce437ba5e01274f7 Mon Sep 17 00:00:00 2001 From: Julian Giebel Date: Sat, 22 Aug 2020 12:06:29 +0200 Subject: [PATCH 11/88] Rework emergency lights (#1830) * Implement emergency lights reacting to lost power * Add emergency light sprites Remove shared emergency light component * Remove unused import * Remove EmergencyLight NetID * Add rich description Change comments Add license Implement ExposeData Co-authored-by: Julian Giebel --- .../Components/EmergencyLightComponent.cs | 3 +- .../EmergencyLightComponent.cs | 143 ++++++++++++++++++ .../EntitySystems/EmergencyLightSystem.cs | 18 +++ Content.Server/IgnoredComponents.cs | 1 - .../Constructible/Walls/emergency_light.yml | 16 +- .../emergency_light_off.png | Bin 0 -> 336 bytes .../emergency_light_on.png | Bin 0 -> 1562 bytes .../Lighting/emergency_light.rsi/meta.json | 66 ++++++++ 8 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs create mode 100644 Content.Server/GameObjects/EntitySystems/EmergencyLightSystem.cs create mode 100644 Resources/Textures/Constructible/Lighting/emergency_light.rsi/emergency_light_off.png create mode 100644 Resources/Textures/Constructible/Lighting/emergency_light.rsi/emergency_light_on.png create mode 100644 Resources/Textures/Constructible/Lighting/emergency_light.rsi/meta.json diff --git a/Content.Client/GameObjects/Components/EmergencyLightComponent.cs b/Content.Client/GameObjects/Components/EmergencyLightComponent.cs index 3bf8879d02..3d893332f1 100644 --- a/Content.Client/GameObjects/Components/EmergencyLightComponent.cs +++ b/Content.Client/GameObjects/Components/EmergencyLightComponent.cs @@ -1,4 +1,4 @@ -using System; +using System; using Robust.Client.Animations; using Robust.Client.GameObjects; using Robust.Client.GameObjects.Components.Animations; @@ -13,6 +13,7 @@ namespace Content.Client.GameObjects.Components { public override string Name => "EmergencyLight"; + /// protected override void Startup() { base.Startup(); diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs new file mode 100644 index 0000000000..3fa04af25a --- /dev/null +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerReceiverUsers +{ + /// + /// Component that represents an emergency light, it has an internal battery that charges when the power is on. + /// + [RegisterComponent] + public class EmergencyLightComponent : Component, IExamine + { + public override string Name => "EmergencyLight"; + + [ViewVariables] + private EmergencyLightState _lightState = EmergencyLightState.Charging; + + [ViewVariables] + private BatteryComponent Battery => Owner.GetComponent(); + [ViewVariables] + private PointLightComponent Light => Owner.GetComponent(); + [ViewVariables] + private PowerReceiverComponent PowerReceiver => Owner.GetComponent(); + private SpriteComponent Sprite => Owner.GetComponent(); + + [ViewVariables(VVAccess.ReadWrite)] + private float _wattage; + [ViewVariables(VVAccess.ReadWrite)] + private float _chargingWattage; + [ViewVariables(VVAccess.ReadWrite)] + private float _chargingEfficiency; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _wattage, "wattage", 5); + serializer.DataField(ref _chargingWattage, "chargingWattage", 60); + serializer.DataField(ref _chargingEfficiency, "chargingEfficiency", 0.85f); + } + + /// + /// For attaching UpdateState() to events. + /// + public void UpdateState(object sender, EventArgs e) + { + UpdateState(); + } + + /// + /// Updates the light's power drain, battery drain, sprite and actual light state. + /// + public void UpdateState() + { + if (PowerReceiver.Powered) + { + PowerReceiver.Load = (int) Math.Abs(_wattage); + TurnOff(); + _lightState = EmergencyLightState.Charging; + } + else + { + TurnOn(); + _lightState = EmergencyLightState.On; + } + } + + public void OnUpdate(float frameTime) + { + if (_lightState == EmergencyLightState.Empty + || _lightState == EmergencyLightState.Full) return; + + if(_lightState == EmergencyLightState.On) + { + if (!Battery.TryUseCharge(_wattage * frameTime)) + { + _lightState = EmergencyLightState.Empty; + TurnOff(); + } + } + else + { + Battery.CurrentCharge += _chargingWattage * frameTime * _chargingEfficiency; + if (Battery.BatteryState == BatteryState.Full) + { + PowerReceiver.Load = 1; + _lightState = EmergencyLightState.Full; + } + } + } + + private void TurnOff() + { + Sprite.LayerSetState(0, "emergency_light_off"); + Light.Enabled = false; + } + + private void TurnOn() + { + Sprite.LayerSetState(0, "emergency_light_on"); + Light.Enabled = true; + } + + public override void Initialize() + { + base.Initialize(); + Owner.GetComponent().OnPowerStateChanged += UpdateState; + } + + public override void OnRemove() + { + Owner.GetComponent().OnPowerStateChanged -= UpdateState; + base.OnRemove(); + } + + void IExamine.Examine(FormattedMessage message, bool inDetailsRange) + { + message.AddMarkup(Loc.GetString($"The battery indicator displays: {BatteryStateText[_lightState]}.")); + } + + public enum EmergencyLightState + { + Charging, + Full, + Empty, + On + } + + public Dictionary BatteryStateText = new Dictionary + { + { EmergencyLightState.Full, "[color=darkgreen]Full[/color]"}, + { EmergencyLightState.Empty, "[color=darkred]Empty[/color]"}, + { EmergencyLightState.Charging, "[color=darkorange]Charging[/color]"}, + { EmergencyLightState.On, "[color=darkorange]Discharging[/color]"} + }; + } +} diff --git a/Content.Server/GameObjects/EntitySystems/EmergencyLightSystem.cs b/Content.Server/GameObjects/EntitySystems/EmergencyLightSystem.cs new file mode 100644 index 0000000000..59c83e724b --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/EmergencyLightSystem.cs @@ -0,0 +1,18 @@ +using Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerReceiverUsers; +using JetBrains.Annotations; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Server.GameObjects.EntitySystems +{ + [UsedImplicitly] + internal sealed class EmergencyLightSystem : EntitySystem + { + public override void Update(float frameTime) + { + foreach (var comp in ComponentManager.EntityQuery()) + { + comp.OnUpdate(frameTime); + } + } + } +} diff --git a/Content.Server/IgnoredComponents.cs b/Content.Server/IgnoredComponents.cs index cd9713f35d..518d680cf7 100644 --- a/Content.Server/IgnoredComponents.cs +++ b/Content.Server/IgnoredComponents.cs @@ -17,7 +17,6 @@ "AnimationsTest", "ItemStatus", "Marker", - "EmergencyLight", "Clickable", "RadiatingLight", }; diff --git a/Resources/Prototypes/Entities/Constructible/Walls/emergency_light.yml b/Resources/Prototypes/Entities/Constructible/Walls/emergency_light.yml index c3253d28d8..64a95e6bd5 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/emergency_light.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/emergency_light.yml @@ -1,17 +1,27 @@ -- type: entity +- type: entity id: EmergencyLight name: "emergency light" + description: A small red light with an internal battery that turns on as soon as it stops receiving any power. parent: WallLight components: - type: PointLight + enabled: false radius: 10 energy: 2.5 offset: "0.5, 0" color: "#FF4020" mask: /Textures/emergency_mask.png - - type: EmergencyLight - + - type: PowerReceiver + - type: Battery + maxCharge: 30000 + startingCharge: 0 + - type: Sprite + sprite: Constructible/Lighting/emergency_light.rsi + state: emergency_light_off + - type: Icon + sprite: Constructible/Lighting/emergency_light.rsi + state: emergency_light_off placement: snap: - Wallmount diff --git a/Resources/Textures/Constructible/Lighting/emergency_light.rsi/emergency_light_off.png b/Resources/Textures/Constructible/Lighting/emergency_light.rsi/emergency_light_off.png new file mode 100644 index 0000000000000000000000000000000000000000..6427384c2fe2b5141aa7382bc08e944ab57b0a09 GIT binary patch literal 336 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%zxjbDQLn`LH zy}hybkb%gtkNSelOBO6}>X;=oQ&!`KvZh31lb}WdKUbMWw~?t+h=}GU$8}yM1;=0Z z2<$uZKR)%R=CtJ9m6d)f?vF2S`K4QDC9izlu=dqXI|F0qwa>UcgN$aH{7s#gU3=qo ziQT@uvit{o7(DAQY&mUHFTo6S3gTe~DWM4f-js?e literal 0 HcmV?d00001 diff --git a/Resources/Textures/Constructible/Lighting/emergency_light.rsi/emergency_light_on.png b/Resources/Textures/Constructible/Lighting/emergency_light.rsi/emergency_light_on.png new file mode 100644 index 0000000000000000000000000000000000000000..364d44b505bce2369889774c5d09307085aef55d GIT binary patch literal 1562 zcmZuxc~BBq7{^S>BOTk)U5`B0V{%2>4m(8Y&_XM5O$$#vQnb}H6;YRvX)VXKGNn{V zM9nJ`j~#CVN;6NSw7?V6);w56K|w@xadzC8xjB{=&rBa015g-Ew&m+ce=&|1X#NJ-8}h z`^4Hgt*N4w;2N{`z{LH!y@8tPi4|4Ah->#ho8IjTNHuLs&NiD0x&m1-(TqTna@;et z_OaDkZAXe0bjOY{`9eCQOdwIFt!XE5o1_+&OqpYJu1`IQIiC;|pe+R7WZuFYLbvkU zijL}c-|(UFbI8yWl^!n!k3n@a5*(7&D;7q8rpWC4H$`RAMUSGsnn3MFQ#aBeY)=YlSP=b|UK)|bO)ICEt zkJ{Rl^kQ<=>7lZ^Egi7I;e-`frQmIkzc$^R{PZJcs^s0HgDy!eaBwwQ+CBo_8YP1B zO@#1;H8nG&?+^CISeJR8kBDF_6HFg)&t9SO=bJS2#C}(g%&pqezCv%3D6lnBio-r> zP!jglGO4ELndQE8)>Eck03n_;vymWR%qT%}E^DQnaT{BC_`t8BUy$B~vJnA~5E~f{ z%f%Bip3s2(DUv;O>E$YT`}Mv#)>0to9-~wo#L%{&H@!+Gjaf^a2i7mnbWDm&<@ho= z_M{&~yATaE{>E{Ww~*sMB7B%(4&-RaP5*IUs3LP25m`W{aUv%KqB0(}$}ggk`&}k$jmY2;Uh|hQ;{fh$ z!|B+T`@hb``*7qecX5*%?;~7mtPW9K61aBKkkTO# zgE~w6SZnxVW4(0I_~BQ^X7f~^-DMUO(`UfHA}a6W)8&%u6dofa3WUc-g;pK!kyM5_ zrozOlF+r?cb5u-41ij63wt~28hw>79mSN}346}&f=o}=+!>(HK^M2Rah;2IzF0bJj z*(aMf$fI#a@ZoN0+;dlpV1fT(kNs zcgax-0^^E3LnN_hzt=8GQF*c=Xb^~7cX=X&xm{h#EG_v@h4r#wdhN6T(g=oWnq2-F zf4CFSzF)b5DLU1j&j`z7mIp=!%+VCWWAcsCPVxjt)eY3-fB;wWqC&>{8BQLqmddQm z&SHP%i7qrKE5^oFEDxY7(f|1J^5izXJ=$=}U8-!ZO7sC&u%#6~0Y^=+(!@dto-L1O z%dz2LxbluubPO*Ib}996&IP9F^8#;q*CFbS#?U4rrJX|I+4nX}N@|C{xZ7b&2x?g0 zc_JkzLV-y(E?MV;pjg>;E@(?99499PCU3Sp1Ly3&t(tZd=}$+Cv)pko(>L>u`}&0o)ow77ZB1Jp;z`X^`&1$B0C1-;|FZ?Vldt(b$2D8Q zja6FI%Bm#1(~PXw8rIt_K&-qLopXZiW$k7DyVaW;b<8;5Fdv%$|lE414T`?=R(9yV7{C;!qSU({4ZzxnGIz_ Wy$`)>JsA_*P8Vl4r|NTocmDy0=oR1q literal 0 HcmV?d00001 diff --git a/Resources/Textures/Constructible/Lighting/emergency_light.rsi/meta.json b/Resources/Textures/Constructible/Lighting/emergency_light.rsi/meta.json new file mode 100644 index 0000000000..5e722f0710 --- /dev/null +++ b/Resources/Textures/Constructible/Lighting/emergency_light.rsi/meta.json @@ -0,0 +1,66 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "states": [ + { + "name": "emergency_light_off", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "emergency_light_on", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + } + ] +} From 8a21df21f23c78b330b2aedb46f6377cc088b317 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 22 Aug 2020 12:24:59 +0200 Subject: [PATCH 12/88] Fix nullable build errors (#1849) --- .../Inventory/OpenStorageOperator.cs | 2 +- .../Inventory/PickupEntityOperator.cs | 2 +- .../Inventory/UseItemInInventoryOperator.cs | 4 ++-- .../Nutrition/UseDrinkInInventoryOperator.cs | 20 +++++++++---------- .../Nutrition/UseFoodInInventoryOperator.cs | 18 ++++++++--------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs b/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs index 2916474724..f909842264 100644 --- a/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs +++ b/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs @@ -35,7 +35,7 @@ namespace Content.Server.AI.Operators.Inventory return Outcome.Failed; } - if (!container.Owner.TryGetComponent(out EntityStorageComponent storageComponent) || + if (!container.Owner.TryGetComponent(out EntityStorageComponent? storageComponent) || storageComponent.IsWeldedShut) { return Outcome.Failed; diff --git a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs index a31042b2af..d6631a41d7 100644 --- a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs +++ b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs @@ -31,7 +31,7 @@ namespace Content.Server.AI.Operators.Inventory return Outcome.Failed; } - if (!_owner.TryGetComponent(out HandsComponent handsComponent)) + if (!_owner.TryGetComponent(out HandsComponent? handsComponent)) { return Outcome.Failed; } diff --git a/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs b/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs index 7f35892984..1be054733d 100644 --- a/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs +++ b/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs @@ -22,12 +22,12 @@ namespace Content.Server.AI.Operators.Inventory public override Outcome Execute(float frameTime) { // TODO: Also have this check storage a la backpack etc. - if (!_owner.TryGetComponent(out HandsComponent handsComponent)) + if (!_owner.TryGetComponent(out HandsComponent? handsComponent)) { return Outcome.Failed; } - if (!_target.TryGetComponent(out ItemComponent itemComponent)) + if (!_target.TryGetComponent(out ItemComponent? itemComponent)) { return Outcome.Failed; } diff --git a/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs b/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs index 366197a2fd..7388ee13b4 100644 --- a/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs +++ b/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs @@ -21,7 +21,7 @@ namespace Content.Server.AI.Operators.Nutrition _owner = owner; _target = target; } - + public override Outcome Execute(float frameTime) { if (_interactionCooldown >= 0) @@ -29,11 +29,11 @@ namespace Content.Server.AI.Operators.Nutrition _interactionCooldown -= frameTime; return Outcome.Continuing; } - + // TODO: Also have this check storage a la backpack etc. - if (_target.Deleted || - !_owner.TryGetComponent(out HandsComponent handsComponent) || - !_target.TryGetComponent(out ItemComponent itemComponent)) + if (_target.Deleted || + !_owner.TryGetComponent(out HandsComponent? handsComponent) || + !_target.TryGetComponent(out ItemComponent? itemComponent)) { return Outcome.Failed; } @@ -58,10 +58,10 @@ namespace Content.Server.AI.Operators.Nutrition { return Outcome.Failed; } - - if (drinkComponent.Deleted || - drinkComponent.Empty || - _owner.TryGetComponent(out ThirstComponent thirstComponent) && + + if (drinkComponent.Deleted || + drinkComponent.Empty || + _owner.TryGetComponent(out ThirstComponent? thirstComponent) && thirstComponent.CurrentThirst >= thirstComponent.ThirstThresholds[ThirstThreshold.Okay]) { return Outcome.Success; @@ -70,4 +70,4 @@ namespace Content.Server.AI.Operators.Nutrition return Outcome.Continuing; } } -} \ No newline at end of file +} diff --git a/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs b/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs index 7814a42d7e..27f74d92c4 100644 --- a/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs +++ b/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs @@ -29,11 +29,11 @@ namespace Content.Server.AI.Operators.Nutrition _interactionCooldown -= frameTime; return Outcome.Continuing; } - + // TODO: Also have this check storage a la backpack etc. - if (_target.Deleted || - !_owner.TryGetComponent(out HandsComponent handsComponent) || - !_target.TryGetComponent(out ItemComponent itemComponent)) + if (_target.Deleted || + !_owner.TryGetComponent(out HandsComponent? handsComponent) || + !_target.TryGetComponent(out ItemComponent? itemComponent)) { return Outcome.Failed; } @@ -58,10 +58,10 @@ namespace Content.Server.AI.Operators.Nutrition { return Outcome.Failed; } - - if (_target.Deleted || - foodComponent.UsesRemaining == 0 || - _owner.TryGetComponent(out HungerComponent hungerComponent) && + + if (_target.Deleted || + foodComponent.UsesRemaining == 0 || + _owner.TryGetComponent(out HungerComponent? hungerComponent) && hungerComponent.CurrentHunger >= hungerComponent.HungerThresholds[HungerThreshold.Okay]) { return Outcome.Success; @@ -70,4 +70,4 @@ namespace Content.Server.AI.Operators.Nutrition return Outcome.Continuing; } } -} \ No newline at end of file +} From 28309be1613cccbabd0356b2fa5d979cdf8187c5 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 22 Aug 2020 12:25:20 +0200 Subject: [PATCH 13/88] Make nullable warnings errors locally (#1837) --- SpaceStation14.sln.DotSettings | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 9a0e2799ef..37428eb88e 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -1,6 +1,20 @@  False False + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR WARNING WARNING WARNING From 4fa4e42462f3e9ce6fcbc8b9435ac727be918b74 Mon Sep 17 00:00:00 2001 From: Swept Date: Sat, 22 Aug 2020 10:30:08 +0000 Subject: [PATCH 14/88] Tweak player BB to be more accurate (#1846) --- Resources/Prototypes/Entities/Mobs/Species/human.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index e104ad5ccb..5e1d1ebb69 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -117,7 +117,7 @@ - type: Collidable shapes: - !type:PhysShapeAabb - bounds: "-0.35,-0.35,0.35,0.35" + bounds: "-0.50,-0.30,0.40,0.30" mask: - Impassable - MobImpassable From efbd01d0bf64241f38d8890232d22a588c00a29c Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 22 Aug 2020 12:30:30 +0200 Subject: [PATCH 15/88] Add test for airlocks opening/closing and blocking entities (#1842) * Add test for airlocks opening/closing and blocking entities * Nullable fix classic --- .../ContentIntegrationTest.cs | 68 ++++++++- .../Tests/Doors/AirlockTest.cs | 143 ++++++++++++++++++ .../Components/Doors/AirlockComponent.cs | 10 +- .../Components/Doors/ServerDoorComponent.cs | 8 +- .../Atmos/GasTileOverlaySystem.cs | 7 +- Content.Shared/Physics/MoverController.cs | 4 +- 6 files changed, 230 insertions(+), 10 deletions(-) create mode 100644 Content.IntegrationTests/Tests/Doors/AirlockTest.cs diff --git a/Content.IntegrationTests/ContentIntegrationTest.cs b/Content.IntegrationTests/ContentIntegrationTest.cs index 023cf5fe0f..66141d1b9e 100644 --- a/Content.IntegrationTests/ContentIntegrationTest.cs +++ b/Content.IntegrationTests/ContentIntegrationTest.cs @@ -1,13 +1,20 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Content.Client; using Content.Client.Interfaces.Parallax; using Content.Server; using Content.Server.Interfaces.GameTicking; using NUnit.Framework; +using Robust.Server.Interfaces.Maps; +using Robust.Server.Interfaces.Timing; using Robust.Shared.ContentPack; +using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; using Robust.UnitTesting; using EntryPoint = Content.Client.EntryPoint; @@ -97,13 +104,72 @@ namespace Content.IntegrationTests return (client, server); } + protected async Task InitializeMap(ServerIntegrationInstance server, string mapPath) + { + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var pauseManager = server.ResolveDependency(); + var mapLoader = server.ResolveDependency(); + + IMapGrid grid = null; + + server.Post(() => + { + var mapId = mapManager.CreateMap(); + + pauseManager.AddUninitializedMap(mapId); + + grid = mapLoader.LoadBlueprint(mapId, mapPath); + + pauseManager.DoMapInitialize(mapId); + }); + + await server.WaitIdleAsync(); + + return grid; + } + + protected async Task TryLoadEntities(IntegrationInstance instance, params string[] yamls) + { + await instance.WaitIdleAsync(); + + var prototypeManager = instance.ResolveDependency(); + + instance.Post(() => + { + foreach (var yaml in yamls) + { + using var reader = new StringReader(yaml); + + prototypeManager.LoadFromStream(reader); + } + }); + + await instance.WaitIdleAsync(); + } + + protected async Task WaitUntil(IntegrationInstance instance, Func predicate, int tickStep = 10, int maxTicks = 600) + { + var ticksAwaited = 0; + + while (!predicate(instance) && ticksAwaited < maxTicks) + { + await instance.WaitIdleAsync(); + instance.RunTicks(tickStep); + ticksAwaited += tickStep; + } + + await instance.WaitIdleAsync(); + } + private static async Task StartConnectedPairShared(ClientIntegrationInstance client, ServerIntegrationInstance server) { await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()); client.SetConnectTarget(server); - client.Post(() => IoCManager.Resolve().ClientConnect(null, 0, null)); + client.Post(() => IoCManager.Resolve().ClientConnect(null!, 0, null!)); await RunTicksSync(client, server, 10); } diff --git a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs new file mode 100644 index 0000000000..bcf10f7940 --- /dev/null +++ b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs @@ -0,0 +1,143 @@ +using System.IO; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Doors; +using Content.Shared.Physics; +using NUnit.Framework; +using Robust.Server.Console.Commands; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Prototypes; +using static Content.Server.GameObjects.Components.Doors.ServerDoorComponent; + +namespace Content.IntegrationTests.Tests.Doors +{ + [TestFixture] + [TestOf(typeof(AirlockComponent))] + public class AirlockTest : ContentIntegrationTest + { + [Test] + public async Task OpenCloseDestroyTest() + { + var server = StartServerDummyTicker(); + + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + + IEntity airlock = null; + AirlockComponent airlockComponent = null; + + server.Assert(() => + { + mapManager.CreateNewMapEntity(MapId.Nullspace); + + airlock = entityManager.SpawnEntity("Airlock", MapCoordinates.Nullspace); + + Assert.True(airlock.TryGetComponent(out airlockComponent)); + Assert.That(airlockComponent.State, Is.EqualTo(DoorState.Closed)); + }); + + await server.WaitIdleAsync(); + + server.Assert(() => + { + airlockComponent.Open(); + Assert.That(airlockComponent.State, Is.EqualTo(DoorState.Opening)); + }); + + await server.WaitIdleAsync(); + + await WaitUntil(server, _ => airlockComponent.State == DoorState.Open); + + Assert.That(airlockComponent.State, Is.EqualTo(DoorState.Open)); + + server.Assert(() => + { + airlockComponent.Close(); + Assert.That(airlockComponent.State, Is.EqualTo(DoorState.Closing)); + }); + + await WaitUntil(server, _ => airlockComponent.State == DoorState.Closed); + + Assert.That(airlockComponent.State, Is.EqualTo(DoorState.Closed)); + + server.Assert(() => + { + Assert.DoesNotThrow(() => + { + airlock.Delete(); + }); + }); + + server.RunTicks(5); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task AirlockBlockTest() + { + var server = StartServer(); + + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + + IEntity human = null; + IEntity airlock = null; + TestController controller = null; + AirlockComponent airlockComponent = null; + + var humanStartingX = -1; + + server.Assert(() => + { + var mapId = new MapId(1); + mapManager.CreateNewMapEntity(mapId); + + var humanCoordinates = new MapCoordinates((humanStartingX, 0), mapId); + human = entityManager.SpawnEntity("HumanMob_Content", humanCoordinates); + + airlock = entityManager.SpawnEntity("Airlock", new MapCoordinates((0, 0), mapId)); + + Assert.True(human.TryGetComponent(out ICollidableComponent collidable)); + + controller = collidable.EnsureController(); + + Assert.True(airlock.TryGetComponent(out airlockComponent)); + Assert.That(airlockComponent.State, Is.EqualTo(DoorState.Closed)); + }); + + await server.WaitIdleAsync(); + + // Push the human towards the airlock + controller.LinearVelocity = (0.5f, 0); + + for (var i = 0; i < 240; i += 10) + { + // Keep the airlock awake so they collide + airlock.GetComponent().WakeBody(); + + // Ensure that it is still closed + Assert.That(airlockComponent.State, Is.EqualTo(DoorState.Closed)); + + await server.WaitRunTicks(10); + await server.WaitIdleAsync(); + } + + // Sanity check + Assert.That(human.Transform.MapPosition.X, Is.GreaterThan(humanStartingX)); + + // Blocked by the airlock + Assert.That(human.Transform.MapPosition.X, Is.Negative.Or.Zero); + } + + private class TestController : VirtualController { } + } +} diff --git a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs index 398e9230c9..1bc53f6ab6 100644 --- a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs @@ -144,9 +144,9 @@ namespace Content.Server.GameObjects.Components.Doors } } - protected override DoorState State + public override DoorState State { - set + protected set { base.State = value; // Only show the maintenance panel if the airlock is closed @@ -171,7 +171,11 @@ namespace Content.Server.GameObjects.Components.Doors public override void OnRemove() { - _powerReceiver.OnPowerStateChanged -= PowerDeviceOnOnPowerStateChanged; + if (Owner.TryGetComponent(out _powerReceiver)) + { + _powerReceiver.OnPowerStateChanged -= PowerDeviceOnOnPowerStateChanged; + } + base.OnRemove(); } diff --git a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs index ba2bc39377..8df3c2601c 100644 --- a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs @@ -33,10 +33,10 @@ namespace Content.Server.GameObjects.Components.Doors private DoorState _state = DoorState.Closed; - protected virtual DoorState State + public virtual DoorState State { get => _state; - set => _state = value; + protected set => _state = value; } protected float OpenTimeCounter; @@ -80,7 +80,7 @@ namespace Content.Server.GameObjects.Components.Doors public override void OnRemove() { - _cancellationTokenSource.Cancel(); + _cancellationTokenSource?.Cancel(); _collidableComponent = null; _appearance = null; @@ -336,7 +336,7 @@ namespace Content.Server.GameObjects.Components.Doors } } - protected enum DoorState + public enum DoorState { Closed, Open, diff --git a/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs index d70331f71e..96f93bb722 100644 --- a/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs @@ -261,7 +261,12 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos // and if not then we won't bother sending the data. foreach (var (gridId, indices) in _invalidTiles) { - var gridEntityId = _mapManager.GetGrid(gridId).GridEntityId; + if (!_mapManager.TryGetGrid(gridId, out var grid)) + { + return; + } + + var gridEntityId = grid.GridEntityId; if (!EntityManager.GetEntity(gridEntityId).TryGetComponent(out GridAtmosphereComponent? gam)) { diff --git a/Content.Shared/Physics/MoverController.cs b/Content.Shared/Physics/MoverController.cs index bf4372a3ca..5554895988 100644 --- a/Content.Shared/Physics/MoverController.cs +++ b/Content.Shared/Physics/MoverController.cs @@ -10,12 +10,14 @@ namespace Content.Shared.Physics { public class MoverController : VirtualController { + [Dependency] private readonly IPhysicsManager _physicsManager = default!; + public override ICollidableComponent? ControlledComponent { protected get; set; } public void Move(Vector2 velocityDirection, float speed) { if (ControlledComponent?.Owner.HasComponent() == false - && IoCManager.Resolve().IsWeightless(ControlledComponent.Owner.Transform.GridPosition)) + && _physicsManager.IsWeightless(ControlledComponent.Owner.Transform.GridPosition)) { return; } From 1b634739ace58bdca09fec6d40e7ac494efd51ca Mon Sep 17 00:00:00 2001 From: Exp Date: Sat, 22 Aug 2020 12:45:49 +0200 Subject: [PATCH 16/88] Observers are shown as observers in the RoundEndSummary (#1838) * Observers are shown as observers in the RoundEndSummary * Fix typo Co-authored-by: DrSmugleaf --- .../UserInterface/RoundEndSummaryWindow.cs | 25 +++++++++++++------ Content.Server/GameTicking/GameTicker.cs | 4 ++- Content.Shared/SharedGameTicker.cs | 5 +++- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Content.Client/UserInterface/RoundEndSummaryWindow.cs b/Content.Client/UserInterface/RoundEndSummaryWindow.cs index 8d40c0b8ba..2fdfac0e2c 100644 --- a/Content.Client/UserInterface/RoundEndSummaryWindow.cs +++ b/Content.Client/UserInterface/RoundEndSummaryWindow.cs @@ -69,8 +69,8 @@ namespace Content.Client.UserInterface scrollContainer.SizeFlagsVertical = SizeFlags.FillExpand; var innerScrollContainer = new VBoxContainer(); - //Put antags on top of the list. - var manifestSortedList = info.OrderBy(p => !p.Antag); + //Put observers at the bottom of the list. Put antags on top. + var manifestSortedList = info.OrderBy(p => p.Observer).ThenBy(p => !p.Antag); //Create labels for each player info. foreach (var plyinfo in manifestSortedList) { @@ -79,12 +79,21 @@ namespace Content.Client.UserInterface SizeFlagsVertical = SizeFlags.Fill, }; - //TODO: On Hover display a popup detailing more play info. - //For example: their antag goals and if they completed them sucessfully. - var icNameColor = plyinfo.Antag ? "red" : "white"; - playerInfoText.SetMarkup( - Loc.GetString("[color=gray]{0}[/color] was [color={1}]{2}[/color] playing role of [color=orange]{3}[/color].", - plyinfo.PlayerOOCName, icNameColor, plyinfo.PlayerICName, Loc.GetString(plyinfo.Role))); + if (plyinfo.Observer) + { + playerInfoText.SetMarkup( + Loc.GetString("[color=gray]{0}[/color] was [color=lightblue]{1}[/color], an observer.", + plyinfo.PlayerOOCName, plyinfo.PlayerICName)); + } + else + { + //TODO: On Hover display a popup detailing more play info. + //For example: their antag goals and if they completed them sucessfully. + var icNameColor = plyinfo.Antag ? "red" : "white"; + playerInfoText.SetMarkup( + Loc.GetString("[color=gray]{0}[/color] was [color={1}]{2}[/color] playing role of [color=orange]{3}[/color].", + plyinfo.PlayerOOCName, icNameColor, plyinfo.PlayerICName, Loc.GetString(plyinfo.Role))); + } innerScrollContainer.AddChild(playerInfoText); } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 78f17b8e78..da6f17c9da 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -352,6 +352,7 @@ namespace Content.Server.GameTicking var mind = ply.ContentData().Mind; if (mind != null) { + _playersInLobby.TryGetValue(ply, out var status); var antag = mind.AllRoles.Any(role => role.Antagonist); var playerEndRoundInfo = new RoundEndPlayerInfo() { @@ -360,7 +361,8 @@ namespace Content.Server.GameTicking Role = antag ? mind.AllRoles.First(role => role.Antagonist).Name : mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unknown"), - Antag = antag + Antag = antag, + Observer = status == PlayerStatus.Observer, }; listOfPlayerInfo.Add(playerEndRoundInfo); } diff --git a/Content.Shared/SharedGameTicker.cs b/Content.Shared/SharedGameTicker.cs index 077f4c71b0..7d2f5c8464 100644 --- a/Content.Shared/SharedGameTicker.cs +++ b/Content.Shared/SharedGameTicker.cs @@ -238,6 +238,7 @@ namespace Content.Shared public string PlayerICName; public string Role; public bool Antag; + public bool Observer; } protected class MsgRoundEndMessage : NetMessage @@ -279,7 +280,8 @@ namespace Content.Shared PlayerOOCName = buffer.ReadString(), PlayerICName = buffer.ReadString(), Role = buffer.ReadString(), - Antag = buffer.ReadBoolean() + Antag = buffer.ReadBoolean(), + Observer = buffer.ReadBoolean(), }; AllPlayersEndInfo.Add(readPlayerData); @@ -303,6 +305,7 @@ namespace Content.Shared buffer.Write(playerEndInfo.PlayerICName); buffer.Write(playerEndInfo.Role); buffer.Write(playerEndInfo.Antag); + buffer.Write(playerEndInfo.Observer); } } From 3eb22ef33e6e2ef834c9dc2800eeba5620118b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 12:59:08 +0200 Subject: [PATCH 17/88] Add missing audio license --- Resources/Audio/Effects/license.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Resources/Audio/Effects/license.txt diff --git a/Resources/Audio/Effects/license.txt b/Resources/Audio/Effects/license.txt new file mode 100644 index 0000000000..67629091c3 --- /dev/null +++ b/Resources/Audio/Effects/license.txt @@ -0,0 +1,2 @@ +hit_kick.ogg is made by Taira Komori +(https://taira-komori.jpn.org/freesounden.html) From 34bb6310faac9f91bb2000c9cf637b4e81e28acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 13:12:53 +0200 Subject: [PATCH 18/88] Fixes handmade pistol --- .../Entities/Objects/Weapons/Guns/Pistols/pistols.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml index 4107dd634c..152e96888d 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml @@ -149,17 +149,20 @@ size: 24 sprite: Objects/Weapons/Guns/Pistols/hm_pistol.rsi - type: RangedWeapon - - type: BoltActionBarrel + - type: MagazineBarrel caliber: Pistol currentSelector: Single allSelectors: - Single + magazineTypes: + - Pistol capacity: 1 fireRate: 8 minAngle: 10 maxAngle: 60 angleIncrease: 10 angleDecay: 60 + magFillPrototype: MagazinePistol soundGunshot: /Audio/Weapons/Guns/Gunshots/pistol.ogg soundEmpty: /Audio/Weapons/Guns/Empty/empty.ogg soundRack: /Audio/Weapons/Guns/Cock/pistol_cock.ogg From 92db5ad999613a6db59e3cb68adfc105f375b8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 13:15:14 +0200 Subject: [PATCH 19/88] Reduce pistol item size to half, allowing them to be held in pockets. --- .../Objects/Weapons/Guns/Pistols/pistols.yml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml index 152e96888d..c59eda9874 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml @@ -17,7 +17,7 @@ - type: Icon state: icon - type: Item - size: 24 + size: 12 state: icon - type: MagazineBarrel caliber: Pistol @@ -58,7 +58,7 @@ - type: Icon sprite: Objects/Weapons/Guns/Pistols/clarissa.rsi - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/clarissa.rsi - type: RangedWeapon - type: MagazineBarrel @@ -88,7 +88,7 @@ - type: Icon sprite: Objects/Weapons/Guns/Pistols/colt.rsi - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/colt.rsi - type: RangedWeapon - type: MagazineBarrel @@ -115,7 +115,7 @@ - type: Icon sprite: Objects/Weapons/Guns/Pistols/giskard.rsi - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/giskard.rsi - type: RangedWeapon - type: MagazineBarrel @@ -146,7 +146,7 @@ sprite: Objects/Weapons/Guns/Pistols/hm_pistol.rsi state: icon - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/hm_pistol.rsi - type: RangedWeapon - type: MagazineBarrel @@ -190,7 +190,7 @@ - type: Icon sprite: Objects/Weapons/Guns/Pistols/gyro_pistol.rsi - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/gyro_pistol.rsi - type: RangedWeapon - type: MagazineBarrel @@ -234,7 +234,7 @@ - type: Icon sprite: Objects/Weapons/Guns/Pistols/mandella.rsi - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/mandella.rsi - type: RangedWeapon - type: MagazineBarrel @@ -266,7 +266,7 @@ - type: Icon sprite: Objects/Weapons/Guns/Pistols/mk58.rsi - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/mk58.rsi - type: RangedWeapon - type: MagazineBarrel @@ -298,7 +298,7 @@ - type: Icon sprite: Objects/Weapons/Guns/Pistols/mk58_wood.rsi - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/mk58_wood.rsi - type: RangedWeapon - type: MagazineBarrel @@ -325,7 +325,7 @@ - type: Icon sprite: Objects/Weapons/Guns/Pistols/molly.rsi - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/molly.rsi - type: RangedWeapon - type: MagazineBarrel @@ -360,7 +360,7 @@ - type: Icon sprite: Objects/Weapons/Guns/Pistols/olivaw_civil.rsi - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/olivaw_civil.rsi - type: RangedWeapon - type: MagazineBarrel @@ -388,7 +388,7 @@ - type: Icon sprite: Objects/Weapons/Guns/Pistols/paco.rsi - type: Item - size: 24 + size: 12 sprite: Objects/Weapons/Guns/Pistols/paco.rsi - type: RangedWeapon - type: MagazineBarrel From f7c71b500fb8680b3655ca588872500140efbc91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 13:27:03 +0200 Subject: [PATCH 20/88] Make airlocks, lights and chairs/stools destructible. --- .../Entities/Constructible/Doors/airlock_base.yml | 2 ++ .../Entities/Constructible/Ground/furniture.yml | 9 +++++++++ .../Prototypes/Entities/Constructible/Walls/lighting.yml | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml b/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml index ad88ad239f..292eb737df 100644 --- a/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml +++ b/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml @@ -58,6 +58,8 @@ - type: Occluder - type: SnapGrid offset: Center + - type: Destructible + maxHP: 500 placement: mode: SnapgridCenter diff --git a/Resources/Prototypes/Entities/Constructible/Ground/furniture.yml b/Resources/Prototypes/Entities/Constructible/Ground/furniture.yml index 1ad0119924..39799efae4 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/furniture.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/furniture.yml @@ -17,6 +17,8 @@ position: Stand - type: Anchorable - type: Pullable + - type: Destructible + maxHP: 50 - type: entity name: bar stool @@ -48,6 +50,8 @@ position: Stand - type: Anchorable - type: Pullable + - type: Destructible + maxHP: 50 - type: entity name: dark office chair @@ -81,6 +85,8 @@ position: Stand - type: Anchorable - type: Pullable + - type: Destructible + maxHP: 50 - type: entity parent: Chair @@ -96,6 +102,7 @@ state: comfychair_preview + - type: entity name: wooden chair id: ChairWood @@ -126,5 +133,7 @@ - type: Strap position: Down rotation: -90 + - type: Destructible + maxHP: 75 placement: mode: SnapgridCenter diff --git a/Resources/Prototypes/Entities/Constructible/Walls/lighting.yml b/Resources/Prototypes/Entities/Constructible/Walls/lighting.yml index 6067f47b90..28e87a8d7a 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/lighting.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/lighting.yml @@ -45,6 +45,8 @@ - type: PoweredLight bulb: Tube - type: PowerReceiver + - type: Destructible + maxHP: 50 - type: entity name: small light @@ -66,3 +68,5 @@ - type: PoweredLight bulb: Bulb - type: PowerReceiver + - type: Destructible + maxHP: 25 From bb923aa230f163acfd54cd23ae72526e76a3d9f2 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 22 Aug 2020 13:40:22 +0200 Subject: [PATCH 21/88] As discussed on the Discord, xenos are not humans (#1840) * As discussed on the Discord, xenos are not humans * Add living component for living beings without a defined body * Merge LivingDamageable and Damageable components * Fix ruinable and state manager inconsistencies * Fix ruinable exposedata * Fix new destructibles yamls * Fix healing not healing * Fix alive not being a valid state * Fix valid state checking --- .../Components/Body/BodyManagerComponent.cs | 7 -- .../Components/Damage/BreakableComponent.cs | 2 - .../Components/Damage/RuinableComponent.cs | 31 ++----- .../Components/Mobs/MobStateManager.cs | 15 ++- .../Body/SharedBodyManagerComponent.cs | 10 -- .../Components/Damage/DamageableComponent.cs | 91 ++++++++++++++++++- .../Constructible/Doors/airlock_base.yml | 2 +- .../Constructible/Ground/furniture.yml | 8 +- .../Constructible/Ground/instruments.yml | 4 +- .../Constructible/Ground/pilot_chair.yml | 2 +- .../Entities/Constructible/Ground/table.yml | 4 +- .../Constructible/Power/gravity_generator.yml | 2 +- .../Constructible/Power/medical_scanner.yml | 2 +- .../Entities/Constructible/Power/power.yml | 4 +- .../Constructible/Power/vending_machines.yml | 2 +- .../Constructible/Storage/Closets/closet.yml | 2 +- .../Storage/StorageTanks/storage_tank.yml | 2 +- .../Constructible/Storage/crate_base.yml | 2 +- .../Entities/Constructible/Walls/asteroid.yml | 2 +- .../Entities/Constructible/Walls/girder.yml | 2 +- .../Entities/Constructible/Walls/lighting.yml | 4 +- .../Entities/Constructible/Walls/low_wall.yml | 2 +- .../Entities/Constructible/Walls/walls.yml | 38 ++++---- .../Entities/Constructible/Walls/windows.yml | 2 +- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 6 +- .../Prototypes/Entities/Mobs/NPCs/carp.yml | 6 +- .../Prototypes/Entities/Mobs/NPCs/mimic.yml | 6 +- .../Prototypes/Entities/Mobs/NPCs/pets.yml | 6 +- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 6 +- .../Entities/Mobs/Species/human.yml | 4 + .../Weapons/Guns/Explosives/grenades.yml | 6 +- SpaceStation14.sln.DotSettings | 1 + 32 files changed, 175 insertions(+), 108 deletions(-) diff --git a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs index 5c14e4da0c..aad68a5e9b 100644 --- a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs +++ b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs @@ -18,14 +18,12 @@ using Content.Shared.Body.Template; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Movement; -using Robust.Server.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.IoC; using Robust.Shared.Log; -using Robust.Shared.Maths; using Robust.Shared.Players; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -138,11 +136,6 @@ namespace Content.Server.GameObjects.Components.Body base.Initialize(); LoadBodyPreset(Preset); - - foreach (var behavior in Owner.GetAllComponents()) - { - HealthChangedEvent += behavior.OnHealthChanged; - } } protected override void Startup() diff --git a/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs b/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs index 15ad3e8a35..5ea60a7fab 100644 --- a/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs @@ -41,8 +41,6 @@ namespace Content.Server.GameObjects.Components.Damage switch (eventArgs.Severity) { case ExplosionSeverity.Destruction: - PerformDestruction(); - break; case ExplosionSeverity.Heavy: PerformDestruction(); break; diff --git a/Content.Server/GameObjects/Components/Damage/RuinableComponent.cs b/Content.Server/GameObjects/Components/Damage/RuinableComponent.cs index 4d94d29f1a..713448fd1a 100644 --- a/Content.Server/GameObjects/Components/Damage/RuinableComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/RuinableComponent.cs @@ -5,7 +5,6 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Damage { @@ -18,13 +17,6 @@ namespace Content.Server.GameObjects.Components.Damage { private DamageState _currentDamageState; - /// - /// How much HP this component can sustain before triggering - /// . - /// - [ViewVariables(VVAccess.ReadWrite)] - public int MaxHp { get; private set; } - /// /// Sound played upon destruction. /// @@ -35,29 +27,24 @@ namespace Content.Server.GameObjects.Components.Damage public override DamageState CurrentDamageState => _currentDamageState; - public override void Initialize() - { - base.Initialize(); - HealthChangedEvent += OnHealthChanged; - } - public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - serializer.DataField(this, ruinable => ruinable.MaxHp, "maxHP", 100); + serializer.DataReadWriteFunction( + "deadThreshold", + 100, + t => DeadThreshold = t , + () => DeadThreshold ?? -1); + serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty); } - public override void OnRemove() + protected override void EnterState(DamageState state) { - base.OnRemove(); - HealthChangedEvent -= OnHealthChanged; - } + base.EnterState(state); - private void OnHealthChanged(HealthChangedEventArgs e) - { - if (CurrentDamageState != DamageState.Dead && TotalDamage >= MaxHp) + if (state == DamageState.Dead) { PerformDestruction(); } diff --git a/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs b/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs index acb501a1ea..d1e01e208c 100644 --- a/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs +++ b/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs @@ -187,7 +187,12 @@ namespace Content.Server.GameObjects.Components.Mobs { case RuinableComponent ruinable: { - var modifier = (int) (ruinable.TotalDamage / (ruinable.MaxHp / 7f)); + if (ruinable.DeadThreshold == null) + { + break; + } + + var modifier = (int) (ruinable.TotalDamage / (ruinable.DeadThreshold / 7f)); status.ChangeStatusEffectIcon(StatusEffect.Health, "/Textures/Interface/StatusEffects/Human/human" + modifier + ".png"); @@ -196,8 +201,12 @@ namespace Content.Server.GameObjects.Components.Mobs } case BodyManagerComponent body: { - // TODO: Declare body max normal damage (currently 100) - var modifier = (int) (body.TotalDamage / (100f / 7f)); + if (body.CriticalThreshold == null) + { + return; + } + + var modifier = (int) (body.TotalDamage / (body.CriticalThreshold / 7f)); status.ChangeStatusEffectIcon(StatusEffect.Health, "/Textures/Interface/StatusEffects/Human/human" + modifier + ".png"); diff --git a/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs b/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs index dcc3ae7dc5..d78b93320f 100644 --- a/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs +++ b/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Content.Shared.GameObjects.Components.Damage; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; @@ -11,15 +10,6 @@ namespace Content.Shared.GameObjects.Components.Body public override string Name => "BodyManager"; public override uint? NetID => ContentNetIDs.BODY_MANAGER; - - public override List SupportedDamageStates => new List {DamageState.Alive, DamageState.Critical, DamageState.Dead}; - - public override DamageState CurrentDamageState => - CurrentDamageState = TotalDamage > 200 - ? DamageState.Dead - : TotalDamage > 100 - ? DamageState.Critical - : DamageState.Alive; } [Serializable, NetSerializable] diff --git a/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs index 2d31eeb42b..f621c3f636 100644 --- a/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs +++ b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs @@ -27,15 +27,62 @@ namespace Content.Shared.GameObjects.Components.Damage public override string Name => "Damageable"; - public event Action HealthChangedEvent = default!; + private DamageState _currentDamageState; + + public event Action? HealthChangedEvent; + + /// + /// The threshold of damage, if any, above which the entity enters crit. + /// -1 means that this entity cannot go into crit. + /// + [ViewVariables(VVAccess.ReadWrite)] + public int? CriticalThreshold { get; set; } + + /// + /// The threshold of damage, if any, above which the entity dies. + /// -1 means that this entity cannot die. + /// + [ViewVariables(VVAccess.ReadWrite)] + public int? DeadThreshold { get; set; } [ViewVariables] private ResistanceSet Resistance { get; set; } = default!; [ViewVariables] private DamageContainer Damage { get; set; } = default!; - public virtual List SupportedDamageStates => new List {DamageState.Alive}; + public virtual List SupportedDamageStates + { + get + { + var states = new List {DamageState.Alive}; - public virtual DamageState CurrentDamageState { get; protected set; } = DamageState.Alive; + if (CriticalThreshold != null) + { + states.Add(DamageState.Critical); + } + + if (DeadThreshold != null) + { + states.Add(DamageState.Dead); + } + + return states; + } + } + + public virtual DamageState CurrentDamageState + { + get => _currentDamageState; + set + { + var old = _currentDamageState; + _currentDamageState = value; + + if (old != value) + { + EnterState(value); + } + } + } [ViewVariables] public int TotalDamage => Damage.TotalDamage; @@ -47,6 +94,18 @@ namespace Content.Shared.GameObjects.Components.Damage { base.ExposeData(serializer); + serializer.DataReadWriteFunction( + "criticalThreshold", + -1, + t => CriticalThreshold = t == -1 ? (int?) null : t, + () => CriticalThreshold ?? -1); + + serializer.DataReadWriteFunction( + "deadThreshold", + -1, + t => DeadThreshold = t == -1 ? (int?) null : t, + () => DeadThreshold ?? -1); + if (serializer.Reading) { // Doesn't write to file, TODO? @@ -75,6 +134,16 @@ namespace Content.Shared.GameObjects.Components.Damage } } + public override void Initialize() + { + base.Initialize(); + + foreach (var behavior in Owner.GetAllComponents()) + { + HealthChangedEvent += behavior.OnHealthChanged; + } + } + public bool TryGetDamage(DamageType type, out int damage) { return Damage.TryGetDamageValue(type, out damage); @@ -218,10 +287,26 @@ namespace Content.Shared.GameObjects.Components.Damage OnHealthChanged(args); } + protected virtual void EnterState(DamageState state) { } + protected virtual void OnHealthChanged(HealthChangedEventArgs e) { + if (DeadThreshold != -1 && TotalDamage > DeadThreshold) + { + CurrentDamageState = DamageState.Dead; + } + else if (CriticalThreshold != -1 && TotalDamage > CriticalThreshold) + { + CurrentDamageState = DamageState.Critical; + } + else + { + CurrentDamageState = DamageState.Alive; + } + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, e); HealthChangedEvent?.Invoke(e); + Dirty(); } } diff --git a/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml b/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml index 292eb737df..931791379e 100644 --- a/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml +++ b/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml @@ -59,7 +59,7 @@ - type: SnapGrid offset: Center - type: Destructible - maxHP: 500 + deadThreshold: 500 placement: mode: SnapgridCenter diff --git a/Resources/Prototypes/Entities/Constructible/Ground/furniture.yml b/Resources/Prototypes/Entities/Constructible/Ground/furniture.yml index 39799efae4..a46c09b640 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/furniture.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/furniture.yml @@ -18,7 +18,7 @@ - type: Anchorable - type: Pullable - type: Destructible - maxHP: 50 + deadThreshold: 50 - type: entity name: bar stool @@ -51,7 +51,7 @@ - type: Anchorable - type: Pullable - type: Destructible - maxHP: 50 + deadThreshold: 50 - type: entity name: dark office chair @@ -86,7 +86,7 @@ - type: Anchorable - type: Pullable - type: Destructible - maxHP: 50 + deadThreshold: 50 - type: entity parent: Chair @@ -134,6 +134,6 @@ position: Down rotation: -90 - type: Destructible - maxHP: 75 + deadThreshold: 75 placement: mode: SnapgridCenter diff --git a/Resources/Prototypes/Entities/Constructible/Ground/instruments.yml b/Resources/Prototypes/Entities/Constructible/Ground/instruments.yml index 2d4fb5b564..0d6f8fd52b 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/instruments.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/instruments.yml @@ -18,7 +18,7 @@ - type: SnapGrid offset: Center - type: Destructible - maxHP: 50 + deadThreshold: 50 - type: UserInterface interfaces: - key: enum.InstrumentUiKey.Key @@ -43,7 +43,7 @@ name: minimoog parent: BasePlaceableInstrument id: MinimoogInstrument - description: 'This is a minimoog, like a space piano, but more spacey!' + description: 'This is a minimoog, like a space piano, but more spacey!' components: - type: Instrument program: 81 diff --git a/Resources/Prototypes/Entities/Constructible/Ground/pilot_chair.yml b/Resources/Prototypes/Entities/Constructible/Ground/pilot_chair.yml index f986636ee2..e0e94ba172 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/pilot_chair.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/pilot_chair.yml @@ -14,7 +14,7 @@ - type: Clickable - type: InteractionOutline - type: Destructible - maxHP: 100 + deadThreshold: 100 - type: Physics - type: ShuttleController - type: Strap diff --git a/Resources/Prototypes/Entities/Constructible/Ground/table.yml b/Resources/Prototypes/Entities/Constructible/Ground/table.yml index 1dd92f308e..dbf64465ec 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/table.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/table.yml @@ -24,7 +24,7 @@ base: solid_ - type: Climbable - type: Destructible - maxHP: 50 + deadThreshold: 50 spawnOnDestroy: SteelSheet1 # TODO: drop wood instead of steel when destroyed when that's added @@ -40,5 +40,5 @@ key: wood base: wood_ - type: Destructible - maxHP: 50 + deadThreshold: 50 spawnOnDestroy: SteelSheet1 diff --git a/Resources/Prototypes/Entities/Constructible/Power/gravity_generator.yml b/Resources/Prototypes/Entities/Constructible/Power/gravity_generator.yml index 4163f5dfdd..d783129f87 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/gravity_generator.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/gravity_generator.yml @@ -29,7 +29,7 @@ - type: Clickable - type: InteractionOutline - type: Breakable - maxHP: 150 + deadThreshold: 150 - type: GravityGenerator - type: UserInterface interfaces: diff --git a/Resources/Prototypes/Entities/Constructible/Power/medical_scanner.yml b/Resources/Prototypes/Entities/Constructible/Power/medical_scanner.yml index 390cdca4d0..221b93b43c 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/medical_scanner.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/medical_scanner.yml @@ -35,7 +35,7 @@ offset: Center - type: MedicalScanner - type: Destructible - maxHP: 100 + deadThreshold: 100 - type: Appearance visuals: - type: MedicalScannerVisualizer diff --git a/Resources/Prototypes/Entities/Constructible/Power/power.yml b/Resources/Prototypes/Entities/Constructible/Power/power.yml index 3c7b740fbd..70c472224d 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/power.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/power.yml @@ -51,7 +51,7 @@ - type: PowerConsumer drawRate: 50 - type: Breakable - maxHP: 100 + deadThreshold: 100 - type: Anchorable - type: entity @@ -290,7 +290,7 @@ - type: SnapGrid offset: Center - type: Breakable - maxHP: 100 + deadThreshold: 100 #Depriciated, to be removed from maps diff --git a/Resources/Prototypes/Entities/Constructible/Power/vending_machines.yml b/Resources/Prototypes/Entities/Constructible/Power/vending_machines.yml index 3042c86ce9..c4b616b3e3 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/vending_machines.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/vending_machines.yml @@ -31,7 +31,7 @@ - type: SnapGrid offset: Center - type: Breakable - maxHP: 50 + deadThreshold: 50 - type: UserInterface interfaces: - key: enum.VendingMachineUiKey.Key diff --git a/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml b/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml index 21f2f49e97..020f99c236 100644 --- a/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml +++ b/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml @@ -42,7 +42,7 @@ - type: EntityStorage - type: PlaceableSurface - type: Destructible - maxHP: 100 + deadThreshold: 100 - type: Appearance visuals: - type: StorageVisualizer diff --git a/Resources/Prototypes/Entities/Constructible/Storage/StorageTanks/storage_tank.yml b/Resources/Prototypes/Entities/Constructible/Storage/StorageTanks/storage_tank.yml index 44f8c3180f..f13e61dbe2 100644 --- a/Resources/Prototypes/Entities/Constructible/Storage/StorageTanks/storage_tank.yml +++ b/Resources/Prototypes/Entities/Constructible/Storage/StorageTanks/storage_tank.yml @@ -26,7 +26,7 @@ mass: 15 Anchored: false - type: Destructible - maxHP: 10 + deadThreshold: 10 - type: Solution maxVol: 1500 caps: 2 diff --git a/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml b/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml index 6c4047f99c..77aa013b3c 100644 --- a/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml +++ b/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml @@ -37,7 +37,7 @@ CanWeldShut: false - type: PlaceableSurface - type: Destructible - maxHP: 100 + deadThreshold: 100 - type: Appearance visuals: - type: StorageVisualizer diff --git a/Resources/Prototypes/Entities/Constructible/Walls/asteroid.yml b/Resources/Prototypes/Entities/Constructible/Walls/asteroid.yml index 73437dc525..03226aa632 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/asteroid.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/asteroid.yml @@ -17,7 +17,7 @@ - !type:PhysShapeAabb layer: [MobMask] - type: Destructible - maxHP: 100 + deadThreshold: 100 - type: Occluder sizeX: 32 sizeY: 32 diff --git a/Resources/Prototypes/Entities/Constructible/Walls/girder.yml b/Resources/Prototypes/Entities/Constructible/Walls/girder.yml index 50a4dfa3f2..730451a73a 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/girder.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/girder.yml @@ -15,7 +15,7 @@ - !type:PhysShapeAabb layer: [MobMask, Opaque] - type: Destructible - maxHP: 50 + deadThreshold: 50 spawnOnDestroy: SteelSheet1 - type: SnapGrid offset: Edge diff --git a/Resources/Prototypes/Entities/Constructible/Walls/lighting.yml b/Resources/Prototypes/Entities/Constructible/Walls/lighting.yml index 28e87a8d7a..06ee76322b 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/lighting.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/lighting.yml @@ -46,7 +46,7 @@ bulb: Tube - type: PowerReceiver - type: Destructible - maxHP: 50 + deadThreshold: 50 - type: entity name: small light @@ -69,4 +69,4 @@ bulb: Bulb - type: PowerReceiver - type: Destructible - maxHP: 25 + deadThreshold: 25 diff --git a/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml b/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml index 7c1bb1e5a4..2a391add8c 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml @@ -25,7 +25,7 @@ - VaultImpassable - SmallImpassable - type: Destructible - maxHP: 100 + deadThreshold: 100 - type: SnapGrid offset: Center - type: LowWall diff --git a/Resources/Prototypes/Entities/Constructible/Walls/walls.yml b/Resources/Prototypes/Entities/Constructible/Walls/walls.yml index a33763fa3d..f1b40df43d 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/walls.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/walls.yml @@ -26,7 +26,7 @@ - VaultImpassable - SmallImpassable - type: Destructible - maxHP: 500 + deadThreshold: 500 spawnOnDestroy: Girder - type: Occluder sizeX: 32 @@ -49,7 +49,7 @@ - type: Icon sprite: Constructible/Structures/Walls/brick.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -65,7 +65,7 @@ - type: Icon sprite: Constructible/Structures/Walls/clock.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -81,7 +81,7 @@ - type: Icon sprite: Constructible/Structures/Walls/clown.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -98,7 +98,7 @@ - type: Icon sprite: Constructible/Structures/Walls/cult.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -114,7 +114,7 @@ - type: Icon sprite: Constructible/Structures/Walls/debug.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -130,7 +130,7 @@ - type: Icon sprite: Constructible/Structures/Walls/diamond.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -147,7 +147,7 @@ - type: Icon sprite: Constructible/Structures/Walls/gold.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -163,7 +163,7 @@ - type: Icon sprite: Constructible/Structures/Walls/ice.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -179,7 +179,7 @@ - type: Icon sprite: Constructible/Structures/Walls/metal.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -195,7 +195,7 @@ - type: Icon sprite: Constructible/Structures/Walls/plasma.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -211,7 +211,7 @@ - type: Icon sprite: Constructible/Structures/Walls/plastic.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -229,7 +229,7 @@ sprite: Constructible/Structures/Walls/solid.rsi state: rgeneric - type: Destructible - maxHP: 600 + deadThreshold: 600 spawnOnDestroy: Girder - type: ReinforcedWall key: walls @@ -247,7 +247,7 @@ - type: Icon sprite: Constructible/Structures/Walls/riveted.rsi - type: Destructible - maxHP: 1000 + deadThreshold: 1000 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -263,7 +263,7 @@ - type: Icon sprite: Constructible/Structures/Walls/sandstone.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -279,7 +279,7 @@ - type: Icon sprite: Constructible/Structures/Walls/silver.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -296,7 +296,7 @@ - type: Icon sprite: Constructible/Structures/Walls/solid.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder destroySound: /Audio/Effects/metalbreak.ogg - type: IconSmooth @@ -313,7 +313,7 @@ - type: Icon sprite: Constructible/Structures/Walls/uranium.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls @@ -329,7 +329,7 @@ - type: Icon sprite: Constructible/Structures/Walls/wood.rsi - type: Destructible - maxHP: 300 + deadThreshold: 300 spawnOnDestroy: Girder - type: IconSmooth key: walls diff --git a/Resources/Prototypes/Entities/Constructible/Walls/windows.yml b/Resources/Prototypes/Entities/Constructible/Walls/windows.yml index e0f231f161..773e41a95a 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/windows.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/windows.yml @@ -28,7 +28,7 @@ - VaultImpassable - SmallImpassable - type: Destructible - maxHP: 100 + deadThreshold: 100 - type: SnapGrid offset: Center - type: Airtight diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 9ab31e8bcb..2e60662acc 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -39,9 +39,9 @@ layer: - Opaque - MobImpassable - - type: BodyManager - BaseTemplate: bodyTemplate.Humanoid - BasePreset: bodyPreset.BasicHuman + - type: Damageable + criticalThreshold: 50 + deadThreshold: 100 - type: HeatResistance - type: CombatMode - type: Teleportable diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index 00d7371892..3ba27aad56 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -34,9 +34,9 @@ layer: - Opaque - MobImpassable - - type: BodyManager - BaseTemplate: bodyTemplate.Humanoid - BasePreset: bodyPreset.BasicHuman + - type: Damageable + criticalThreshold: 50 + deadThreshold: 100 - type: HeatResistance - type: CombatMode - type: Teleportable diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml index 9f34c6b0a8..9ac97a7d3a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml @@ -34,9 +34,9 @@ layer: - Opaque - MobImpassable - - type: BodyManager - baseTemplate: bodyTemplate.Humanoid - basePreset: bodyPreset.BasicHuman + - type: Damageable + criticalThreshold: 100 + deadThreshold: 200 - type: MobStateManager - type: HeatResistance - type: CombatMode diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index fc01a43eb8..06943ffcf5 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -31,9 +31,9 @@ layer: - Opaque - MobImpassable - - type: BodyManager - BaseTemplate: bodyTemplate.Humanoid - BasePreset: bodyPreset.BasicHuman + - type: Damageable + criticalThreshold: 50 + deadThreshold: 100 - type: HeatResistance - type: CombatMode - type: Teleportable diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index ada753f860..a6214337cd 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -36,9 +36,9 @@ layer: - Opaque - MobImpassable - - type: BodyManager - baseTemplate: bodyTemplate.Humanoid - basePreset: bodyPreset.BasicHuman + - type: Damageable + criticalThreshold: 150 + deadThreshold: 200 - type: Metabolism - type: MobStateManager - type: HeatResistance diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 5e1d1ebb69..aabc6022cc 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -126,6 +126,8 @@ - Opaque - MobImpassable - type: BodyManager + criticalThreshold: 100 + deadThreshold: 200 baseTemplate: bodyTemplate.Humanoid basePreset: bodyPreset.BasicHuman - type: Metabolism @@ -256,6 +258,8 @@ layer: - MobImpassable - type: BodyManager + criticalThreshold: 100 + deadThreshold: 200 baseTemplate: bodyTemplate.Humanoid basePreset: bodyPreset.BasicHuman - type: MobStateManager diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/grenades.yml index ffe5c80017..9bc3144e0f 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/grenades.yml @@ -22,7 +22,7 @@ lightImpactRange: 4 flashRange: 7 - type: Destructible - maxHP: 10 + deadThreshold: 10 - type: Appearance visuals: - type: TimerTriggerVisualizer @@ -48,7 +48,7 @@ delay: 3.5 - type: FlashExplosive - type: Destructible - maxHP: 10 + deadThreshold: 10 - type: Appearance visuals: - type: TimerTriggerVisualizer @@ -78,7 +78,7 @@ lightImpactRange: 7 flashRange: 10 - type: Destructible - maxHP: 10 + deadThreshold: 10 - type: Appearance visuals: - type: TimerTriggerVisualizer diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 37428eb88e..e802b4285f 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -65,6 +65,7 @@ <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Lidgren.Network" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> True True + True True True True From 8e54ea42e6cc4514397724d472699df9968f94dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 13:41:35 +0200 Subject: [PATCH 22/88] Fix rare crash when deleting airlock while the deny animation is playing --- Content.Client/GameObjects/Components/Wires/WiresVisualizer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Content.Client/GameObjects/Components/Wires/WiresVisualizer.cs b/Content.Client/GameObjects/Components/Wires/WiresVisualizer.cs index b914242184..ad1132652b 100644 --- a/Content.Client/GameObjects/Components/Wires/WiresVisualizer.cs +++ b/Content.Client/GameObjects/Components/Wires/WiresVisualizer.cs @@ -10,6 +10,9 @@ namespace Content.Client.GameObjects.Components.Wires { base.OnChangeData(component); + if (component.Owner.Deleted) + return; + var sprite = component.Owner.GetComponent(); if (component.TryGetData(WiresVisuals.MaintenancePanelState, out var state)) { From 265afc992904e6d393b025b0470d50d6290c889d Mon Sep 17 00:00:00 2001 From: Julian Giebel Date: Sat, 22 Aug 2020 13:55:58 +0200 Subject: [PATCH 23/88] Change NextTube in DisposalTubeComponent to consider multiple tube (#1851) Co-authored-by: Julian Giebel --- .../Components/Disposal/DisposalTubeComponent.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs index 94dccce0a7..3326b4fd0e 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs @@ -69,21 +69,11 @@ namespace Content.Server.GameObjects.Components.Disposal { var nextDirection = NextDirection(holder); var snapGrid = Owner.GetComponent(); + var oppositeDirection = new Angle(nextDirection.ToAngle().Theta + Math.PI).GetDir(); var tube = snapGrid .GetInDir(nextDirection) .Select(x => x.TryGetComponent(out IDisposalTubeComponent? c) ? c : null) - .FirstOrDefault(x => x != null && x != this); - - if (tube == null) - { - return null; - } - - var oppositeDirection = new Angle(nextDirection.ToAngle().Theta + Math.PI).GetDir(); - if (!tube.CanConnect(oppositeDirection, this)) - { - return null; - } + .FirstOrDefault(x => x != null && x != this && x.CanConnect(oppositeDirection, this)); return tube; } From c87a8d5b51766a689f8eb3eaca80558f9321a495 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 22 Aug 2020 15:18:04 +0200 Subject: [PATCH 24/88] Fix NRE when pointing happens without player data or a mind (#1855) --- .../GameObjects/EntitySystems/PointingSystem.cs | 6 ++---- Content.Server/Players/PlayerData.cs | 11 ++++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Content.Server/GameObjects/EntitySystems/PointingSystem.cs b/Content.Server/GameObjects/EntitySystems/PointingSystem.cs index 301d55db95..26d34004f4 100644 --- a/Content.Server/GameObjects/EntitySystems/PointingSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/PointingSystem.cs @@ -3,12 +3,10 @@ using System; using System.Collections.Generic; using Content.Server.GameObjects.Components.Pointing; using Content.Server.Players; -using Content.Server.Utility; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Input; using Content.Shared.Interfaces; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Diagnostics; using Robust.Server.GameObjects.Components; using Robust.Server.Interfaces.Player; using Robust.Server.Player; @@ -84,7 +82,7 @@ namespace Content.Server.GameObjects.EntitySystems public bool TryPoint(ICommonSession? session, GridCoordinates coords, EntityUid uid) { - var player = (session as IPlayerSession)?.ContentData().Mind.CurrentEntity; + var player = (session as IPlayerSession)?.ContentData()?.Mind?.CurrentEntity; if (player == null) { return false; @@ -132,7 +130,7 @@ namespace Content.Server.GameObjects.EntitySystems if ((playerSession.VisibilityMask & layer) == 0) return false; - var ent = playerSession.ContentData().Mind.CurrentEntity; + var ent = playerSession.ContentData()?.Mind?.CurrentEntity; return ent != null && ent.Transform.MapPosition.InRange(player.Transform.MapPosition, PointingRange); diff --git a/Content.Server/Players/PlayerData.cs b/Content.Server/Players/PlayerData.cs index 8e54cf47f3..2ef562d5de 100644 --- a/Content.Server/Players/PlayerData.cs +++ b/Content.Server/Players/PlayerData.cs @@ -1,4 +1,5 @@ -using Content.Server.Mobs; +#nullable enable +using Content.Server.Mobs; using Robust.Server.Interfaces.Player; using Robust.Shared.Network; using Robust.Shared.ViewVariables; @@ -22,7 +23,7 @@ namespace Content.Server.Players /// DO NOT DIRECTLY SET THIS UNLESS YOU KNOW WHAT YOU'RE DOING. /// [ViewVariables] - public Mind Mind { get; set; } + public Mind? Mind { get; set; } public void WipeMind() { @@ -41,15 +42,15 @@ namespace Content.Server.Players /// /// Gets the correctly cast instance of content player data from an engine player data storage. /// - public static PlayerData ContentData(this IPlayerData data) + public static PlayerData? ContentData(this IPlayerData data) { - return (PlayerData)data.ContentDataUncast; + return (PlayerData?) data.ContentDataUncast; } /// /// Gets the correctly cast instance of content player data from an engine player data storage. /// - public static PlayerData ContentData(this IPlayerSession session) + public static PlayerData? ContentData(this IPlayerSession session) { return session.Data.ContentData(); } From 092dd7c9469a92b1c442eb5d72f9cb1dfc142ecc Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 22 Aug 2020 15:51:35 +0200 Subject: [PATCH 25/88] Fix xenos not changing sprites when going into crit or dying (#1854) --- .../Components/Mobs/DamageStateVisualizer.cs | 17 +++++++++-------- .../Components/Mobs/MobStateManager.cs | 16 ++++++++++++++++ .../Components/Damage/DamageState.cs | 5 ++++- .../Components/Mobs/SharedDamageState.cs | 10 +--------- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 1 + .../Prototypes/Entities/Mobs/NPCs/carp.yml | 1 + .../Prototypes/Entities/Mobs/NPCs/pets.yml | 1 + .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 1 + 8 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Content.Client/GameObjects/Components/Mobs/DamageStateVisualizer.cs b/Content.Client/GameObjects/Components/Mobs/DamageStateVisualizer.cs index 83a3e4f70c..725ba807bc 100644 --- a/Content.Client/GameObjects/Components/Mobs/DamageStateVisualizer.cs +++ b/Content.Client/GameObjects/Components/Mobs/DamageStateVisualizer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Mobs; using JetBrains.Annotations; using Robust.Client.GameObjects; @@ -12,26 +13,26 @@ namespace Content.Client.GameObjects.Components.Mobs [UsedImplicitly] public sealed class DamageStateVisualizer : AppearanceVisualizer { - private DamageStateVisualData _data = DamageStateVisualData.Normal; - private Dictionary _stateMap = new Dictionary(); - private int? _originalDrawDepth = null; + private DamageState _data = DamageState.Alive; + private readonly Dictionary _stateMap = new Dictionary(); + private int? _originalDrawDepth; public override void LoadData(YamlMappingNode node) { base.LoadData(node); if (node.TryGetNode("normal", out var normal)) { - _stateMap.Add(DamageStateVisualData.Normal, normal.AsString()); + _stateMap.Add(DamageState.Alive, normal.AsString()); } if (node.TryGetNode("crit", out var crit)) { - _stateMap.Add(DamageStateVisualData.Crit, crit.AsString()); + _stateMap.Add(DamageState.Critical, crit.AsString()); } if (node.TryGetNode("dead", out var dead)) { - _stateMap.Add(DamageStateVisualData.Dead, dead.AsString()); + _stateMap.Add(DamageState.Dead, dead.AsString()); } } @@ -39,7 +40,7 @@ namespace Content.Client.GameObjects.Components.Mobs { base.OnChangeData(component); var sprite = component.Owner.GetComponent(); - if (!component.TryGetData(DamageStateVisuals.State, out DamageStateVisualData data)) + if (!component.TryGetData(DamageStateVisuals.State, out DamageState data)) { return; } @@ -57,7 +58,7 @@ namespace Content.Client.GameObjects.Components.Mobs } // So they don't draw over mobs anymore - if (_data == DamageStateVisualData.Dead) + if (_data == DamageState.Dead) { _originalDrawDepth = sprite.DrawDepth; sprite.DrawDepth = (int) DrawDepth.FloorObjects; diff --git a/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs b/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs index d1e01e208c..beb3bfa2d3 100644 --- a/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs +++ b/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs @@ -5,6 +5,7 @@ using Content.Server.Mobs; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.EntitySystems; +using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; @@ -163,6 +164,11 @@ namespace Content.Server.GameObjects.Components.Mobs { public void EnterState(IEntity entity) { + if (entity.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Alive); + } + UpdateState(entity); } @@ -290,6 +296,11 @@ namespace Content.Server.GameObjects.Components.Mobs { public void EnterState(IEntity entity) { + if (entity.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Critical); + } + if (entity.TryGetComponent(out ServerStatusEffectsComponent status)) { status.ChangeStatusEffectIcon(StatusEffect.Health, @@ -391,6 +402,11 @@ namespace Content.Server.GameObjects.Components.Mobs { public void EnterState(IEntity entity) { + if (entity.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Dead); + } + if (entity.TryGetComponent(out ServerStatusEffectsComponent status)) { status.ChangeStatusEffectIcon(StatusEffect.Health, diff --git a/Content.Shared/GameObjects/Components/Damage/DamageState.cs b/Content.Shared/GameObjects/Components/Damage/DamageState.cs index 843c97be57..7ec6a260b7 100644 --- a/Content.Shared/GameObjects/Components/Damage/DamageState.cs +++ b/Content.Shared/GameObjects/Components/Damage/DamageState.cs @@ -1,4 +1,6 @@ -using Robust.Shared.Interfaces.GameObjects; +using System; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Serialization; namespace Content.Shared.GameObjects.Components.Damage { @@ -11,6 +13,7 @@ namespace Content.Shared.GameObjects.Components.Damage /// and , /// as inanimate objects don't go into crit. /// + [Serializable, NetSerializable] public enum DamageState { Alive, diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedDamageState.cs b/Content.Shared/GameObjects/Components/Mobs/SharedDamageState.cs index b6cc81cef2..87f6d6469c 100644 --- a/Content.Shared/GameObjects/Components/Mobs/SharedDamageState.cs +++ b/Content.Shared/GameObjects/Components/Mobs/SharedDamageState.cs @@ -8,12 +8,4 @@ namespace Content.Shared.GameObjects.Components.Mobs { State } - - [Serializable, NetSerializable] - public enum DamageStateVisualData - { - Normal, - Crit, - Dead - } -} \ No newline at end of file +} diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 2e60662acc..59c5531583 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -42,6 +42,7 @@ - type: Damageable criticalThreshold: 50 deadThreshold: 100 + - type: MobStateManager - type: HeatResistance - type: CombatMode - type: Teleportable diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index 3ba27aad56..bf711b3462 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -37,6 +37,7 @@ - type: Damageable criticalThreshold: 50 deadThreshold: 100 + - type: MobStateManager - type: HeatResistance - type: CombatMode - type: Teleportable diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 06943ffcf5..d7520299b8 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -34,6 +34,7 @@ - type: Damageable criticalThreshold: 50 deadThreshold: 100 + - type: MobStateManager - type: HeatResistance - type: CombatMode - type: Teleportable diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index a6214337cd..e0abe4df3c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -39,6 +39,7 @@ - type: Damageable criticalThreshold: 150 deadThreshold: 200 + - type: MobStateManager - type: Metabolism - type: MobStateManager - type: HeatResistance From 7e957ceff1a93248be5bff92025437d18450b997 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 22 Aug 2020 17:07:41 +0200 Subject: [PATCH 26/88] Fix not dropping your items when dying while buckled (#1856) * Fix not dropping your items when dying while buckled * Add test for dropping items while buckled and dead --- Content.IntegrationTests/Tests/BuckleTest.cs | 92 ++++++++++++++++++++ Content.Server/Mobs/StandingStateHelper.cs | 10 +-- SpaceStation14.sln.DotSettings | 1 + 3 files changed, 98 insertions(+), 5 deletions(-) diff --git a/Content.IntegrationTests/Tests/BuckleTest.cs b/Content.IntegrationTests/Tests/BuckleTest.cs index e2ada13caa..93836aa903 100644 --- a/Content.IntegrationTests/Tests/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/BuckleTest.cs @@ -1,7 +1,11 @@ using System.Threading.Tasks; using Content.Server.GameObjects.Components.Buckle; +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Strap; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Buckle; +using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.EntitySystems; using NUnit.Framework; using Robust.Shared.Interfaces.GameObjects; @@ -166,5 +170,93 @@ namespace Content.IntegrationTests.Tests await server.WaitIdleAsync(); } + + [Test] + public async Task BuckledDyingDropItemsTest() + { + var server = StartServer(); + + IEntity human = null; + IEntity chair = null; + BuckleComponent buckle = null; + StrapComponent strap = null; + HandsComponent hands = null; + IDamageableComponent humanDamageable = null; + + server.Assert(() => + { + var mapManager = IoCManager.Resolve(); + + var mapId = new MapId(1); + mapManager.CreateNewMapEntity(mapId); + + var entityManager = IoCManager.Resolve(); + var gridId = new GridId(1); + var grid = mapManager.CreateGrid(mapId, gridId); + var coordinates = new GridCoordinates((0, 0), gridId); + var tileManager = IoCManager.Resolve(); + var tileId = tileManager["underplating"].TileId; + var tile = new Tile(tileId); + + grid.SetTile(coordinates, tile); + + human = entityManager.SpawnEntity("HumanMob_Content", coordinates); + chair = entityManager.SpawnEntity("ChairWood", coordinates); + + // Component sanity check + Assert.True(human.TryGetComponent(out buckle)); + Assert.True(chair.TryGetComponent(out strap)); + Assert.True(human.TryGetComponent(out hands)); + Assert.True(human.TryGetComponent(out humanDamageable)); + + // Buckle + Assert.True(buckle.TryBuckle(human, chair)); + Assert.NotNull(buckle.BuckledTo); + Assert.True(buckle.Buckled); + + // Put an item into every hand + for (var i = 0; i < hands.Count; i++) + { + var akms = entityManager.SpawnEntity("RifleAk", coordinates); + + // Equip items + Assert.True(akms.TryGetComponent(out ItemComponent item)); + Assert.True(hands.PutInHand(item)); + } + }); + + server.RunTicks(10); + + server.Assert(() => + { + // Still buckled + Assert.True(buckle.Buckled); + + // With items in all hands + foreach (var slot in hands.Hands) + { + Assert.NotNull(hands.GetItem(slot)); + } + + // Banish our guy into the shadow realm + humanDamageable.ChangeDamage(DamageClass.Brute, 1000000, true); + }); + + server.RunTicks(10); + + server.Assert(() => + { + // Still buckled + Assert.True(buckle.Buckled); + + // Now with no item in any hand + foreach (var slot in hands.Hands) + { + Assert.Null(hands.GetItem(slot)); + } + }); + + await server.WaitIdleAsync(); + } } } diff --git a/Content.Server/Mobs/StandingStateHelper.cs b/Content.Server/Mobs/StandingStateHelper.cs index f008b6ccc9..3169b80503 100644 --- a/Content.Server/Mobs/StandingStateHelper.cs +++ b/Content.Server/Mobs/StandingStateHelper.cs @@ -21,6 +21,11 @@ namespace Content.Server.Mobs /// False if the mob was already downed or couldn't set the state public static bool Down(IEntity entity, bool playSound = true, bool dropItems = true, bool force = false) { + if (dropItems) + { + DropAllItemsInHands(entity, false); + } + if (!force && !EffectBlockerSystem.CanFall(entity)) { return false; @@ -45,11 +50,6 @@ namespace Content.Server.Mobs .PlayFromEntity(AudioHelpers.GetRandomFileFromSoundCollection("bodyfall"), entity, AudioHelpers.WithVariation(0.25f)); } - if(dropItems) - { - DropAllItemsInHands(entity, false); - } - return true; } diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index e802b4285f..ac230e6680 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -57,6 +57,7 @@ True <data /> <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="*.UnitTesting" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> + True True True True From 35e963a0ea611fe991565fb1b522e073df2c4a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 19:37:23 +0200 Subject: [PATCH 27/88] Lights now show a popup message when you burn yourself. --- .../PowerReceiverUsers/PoweredLightComponent.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs index 5f6edb59e6..716e72ee7a 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs @@ -18,6 +18,7 @@ using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -29,6 +30,8 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece [RegisterComponent] public class PoweredLightComponent : Component, IInteractHand, IInteractUsing, IMapInit { + [Dependency] private IServerNotifyManager _notifyManager = default!; + public override string Name => "PoweredLight"; private static readonly TimeSpan _thunkDelay = TimeSpan.FromSeconds(2); @@ -100,6 +103,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece void Burn() { + _notifyManager.PopupMessage(Owner, eventArgs.User, Loc.GetString("You burn your hand!")); damageableComponent.ChangeDamage(DamageType.Heat, 20, false, Owner); var audioSystem = EntitySystem.Get(); audioSystem.PlayFromEntity("/Audio/Effects/lightburn.ogg", Owner); From bec72195a6c763c5fa50f888247ff2a9badca76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 19:37:31 +0200 Subject: [PATCH 28/88] Update submodule. --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 865610df1e..ad2545d83d 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 865610df1ea035974288b67f16d7c6baf19c2fa5 +Subproject commit ad2545d83da3bad0e87b2b28c1a0d3088f3ebce2 From 7794b2cff45115260b9fe54f654187b6a8aee311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 19:40:17 +0200 Subject: [PATCH 29/88] Low walls can now be climbed. --- Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml b/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml index 2a391add8c..cde4c568aa 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml @@ -28,6 +28,7 @@ deadThreshold: 100 - type: SnapGrid offset: Center + - type: Climbable - type: LowWall key: walls base: metal_ From fffff537629844f26288b0ee4e28558cdf35eda0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 19:57:15 +0200 Subject: [PATCH 30/88] Remove duplicate component in xeno prototype. --- Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index e0abe4df3c..2985794d4e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -41,7 +41,6 @@ deadThreshold: 200 - type: MobStateManager - type: Metabolism - - type: MobStateManager - type: HeatResistance - type: CombatMode - type: Teleportable From 5de139d6fd77230dab616dd32d51f1cb49442f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 20:07:35 +0200 Subject: [PATCH 31/88] Improves examine for ranged weapons, magazines and ammo. --- .../Ranged/Ammunition/AmmoBoxComponent.cs | 11 +++++- .../Weapon/Ranged/Ammunition/AmmoComponent.cs | 10 ++++- .../Ammunition/RangedMagazineComponent.cs | 10 ++++- .../Barrels/BoltActionBarrelComponent.cs | 10 ++++- .../Ranged/Barrels/PumpBarrelComponent.cs | 11 +++++- .../Barrels/ServerMagazineBarrelComponent.cs | 39 +++++++++++-------- 6 files changed, 69 insertions(+), 22 deletions(-) diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs index 839c356ecf..f4843118b7 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs @@ -16,11 +16,12 @@ using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition { [RegisterComponent] - public sealed class AmmoBoxComponent : Component, IInteractUsing, IUse, IInteractHand, IMapInit + public sealed class AmmoBoxComponent : Component, IInteractUsing, IUse, IInteractHand, IMapInit, IExamine { public override string Name => "AmmoBox"; @@ -197,6 +198,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition return TryUse(eventArgs.User); } + + // So if you have 200 rounds in a box and that suddenly creates 200 entities you're not having a fun time [Verb] private sealed class DumpVerb : Verb @@ -218,5 +221,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition component.EjectContents(10); } } + + public void Examine(FormattedMessage message, bool inDetailsRange) + { + message.AddMarkup(Loc.GetString("\nIt's a [color=white]{0}[/color] ammo box.", _caliber)); + message.AddMarkup(Loc.GetString("\nIt has [color=white]{0}[/color] out of [color=white]{1}[/color] ammo left.", AmmoLeft, _capacity)); + } } } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoComponent.cs index e293e690d5..e97f95973b 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoComponent.cs @@ -1,5 +1,6 @@ using System; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; +using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects; @@ -8,6 +9,7 @@ using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -21,7 +23,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition /// Generally used for bullets but can be used for other things like bananas /// [RegisterComponent] - public class AmmoComponent : Component + public class AmmoComponent : Component, IExamine { public override string Name => "Ammo"; public BallisticCaliber Caliber => _caliber; @@ -152,6 +154,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition }; EntitySystem.Get().CreateParticle(message); } + + public void Examine(FormattedMessage message, bool inDetailsRange) + { + var text = Loc.GetString("It's [color=white]{0}[/color] ammo.", Caliber); + message.AddMarkup(text); + } } public enum BallisticCaliber diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs index 5747132bb8..79f6121d3d 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs @@ -5,6 +5,7 @@ using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; +using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; @@ -14,11 +15,12 @@ using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition { [RegisterComponent] - public class RangedMagazineComponent : Component, IMapInit, IInteractUsing, IUse + public class RangedMagazineComponent : Component, IMapInit, IInteractUsing, IUse, IExamine { public override string Name => "RangedMagazine"; @@ -168,5 +170,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition return true; } + + public void Examine(FormattedMessage message, bool inDetailsRange) + { + var text = Loc.GetString("It's a [color=white]{0}[/color] magazine of [color=white]{1}[/color] caliber.", MagazineType, Caliber); + message.AddMarkup(text); + } } } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs index 913b56027f..cc0d0d74b2 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs @@ -18,6 +18,7 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { @@ -25,7 +26,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels /// Shotguns mostly /// [RegisterComponent] - public sealed class BoltActionBarrelComponent : ServerRangedBarrelComponent, IMapInit + public sealed class BoltActionBarrelComponent : ServerRangedBarrelComponent, IMapInit, IExamine { // Originally I had this logic shared with PumpBarrel and used a couple of variables to control things // but it felt a lot messier to play around with, especially when adding verbs @@ -297,6 +298,13 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels return TryInsertBullet(eventArgs.User, eventArgs.Using); } + public override void Examine(FormattedMessage message, bool inDetailsRange) + { + base.Examine(message, inDetailsRange); + + message.AddMarkup(Loc.GetString("\nIt uses [color=white]{0}[/color] ammo.", _caliber)); + } + [Verb] private sealed class OpenBoltVerb : Verb { diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs index 2139767dc0..6859c0c258 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; +using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; @@ -15,6 +16,7 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { @@ -22,7 +24,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels /// Bolt-action rifles /// [RegisterComponent] - public sealed class PumpBarrelComponent : ServerRangedBarrelComponent, IMapInit + public sealed class PumpBarrelComponent : ServerRangedBarrelComponent, IMapInit, IExamine { public override string Name => "PumpBarrel"; @@ -211,5 +213,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { return TryInsertBullet(eventArgs); } + + public override void Examine(FormattedMessage message, bool inDetailsRange) + { + base.Examine(message, inDetailsRange); + + message.AddMarkup(Loc.GetString("\nIt uses [color=white]{0}[/color] ammo.", _caliber)); + } } } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs index ef8b35ca90..7999fce221 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs @@ -104,21 +104,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels serializer.DataReadWriteFunction( "magazineTypes", new List(), - types => types.ForEach(mag => _magazineTypes |= mag), - () => - { - var types = new List(); - - foreach (MagazineType mag in Enum.GetValues(typeof(MagazineType))) - { - if ((_magazineTypes & mag) != 0) - { - types.Add(mag); - } - } - - return types; - }); + types => types.ForEach(mag => _magazineTypes |= mag), GetMagazineTypes); serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified); serializer.DataField(ref _magFillPrototype, "magFillPrototype", null); serializer.DataField(ref _autoEjectMag, "autoEjectMag", false); @@ -131,6 +117,21 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels serializer.DataField(ref _soundAutoEject, "soundAutoEject", "/Audio/Weapons/Guns/EmptyAlarm/smg_empty_alarm.ogg"); } + private List GetMagazineTypes() + { + var types = new List(); + + foreach (MagazineType mag in Enum.GetValues(typeof(MagazineType))) + { + if ((_magazineTypes & mag) != 0) + { + types.Add(mag); + } + } + + return types; + } + public override ComponentState GetComponentState() { (int, int)? count = null; @@ -420,8 +421,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { base.Examine(message, inDetailsRange); - var text = Loc.GetString("\nIt uses {0} ammo.", Caliber); - message.AddText(text); + message.AddMarkup(Loc.GetString("\nIt uses [color=white]{0}[/color] ammo.", Caliber)); + + foreach (var magazineType in GetMagazineTypes()) + { + message.AddMarkup(Loc.GetString("\nIt accepts [color=white]{0}[/color] magazines.", magazineType)); + } } [Verb] From 1119dabfee30771167b6c356a948b0cef458267f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 20:30:43 +0200 Subject: [PATCH 32/88] Balance SSS weapon spawners. --- .../Effects/Markers/gamemode_conditional_spawners.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml b/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml index 1fe1cb94e2..aa8e264353 100644 --- a/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml +++ b/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml @@ -264,6 +264,7 @@ - GrenadeBlast - GrenadeFrag - GrenadeBaton + - SoapSyndie # shhh! chance: 0.75 gameRules: - RuleSuspicion @@ -285,6 +286,8 @@ prototypes: - MagazineSRifle - MagazineClRifle + - MagazineClRifle10x24 + - MagazineClRiflePistol - MagazineLRifle chance: 0.95 gameRules: @@ -327,8 +330,6 @@ prototypes: - MagazinePistol - MagazineHCPistol - - MagazinePistolSmg - - MagazineClRiflePistol chance: 0.95 gameRules: - RuleSuspicion From c8178550b8b43ef0294bfc42c09da5ba846837a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sat, 22 Aug 2020 20:30:54 +0200 Subject: [PATCH 33/88] Balance dallas to spawn with less ammunition. --- .../Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml index eb298ef72b..f5b6503ddd 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml @@ -174,7 +174,7 @@ caliber: ClRifle magazineTypes: - Rifle - magFillPrototype: MagazineClRifle10x24 + magFillPrototype: MagazineClRifle fireRate: 8 minAngle: 10 maxAngle: 60 From b9196d0a10da1f73d4ad6f2f5f055e61212cb38f Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 22 Aug 2020 22:29:20 +0200 Subject: [PATCH 34/88] Add a test that puts all components on an entity and checks for no exceptions (#1815) * Add test that puts all components on an entity and checks for no exceptions Also fix all the exceptions that happened because of this * Add comments to the test * Fix nullable errors * Fix more nullable errors * More nullable error fixes * Unignore basic actor component * Fix more nullable errors * NULLABLE ERROR * Add string interpolation * Merge if checks * Remove redundant pragma warning disable 649 * Address reviews * Remove null wrappers around TryGetComponent * Merge conflict fixes * APC battery component error fix * Fix power test * Fix atmos mapgrid usages --- .../Components/Body/BodyManagerComponent.cs | 2 - .../Instruments/InstrumentComponent.cs | 4 - .../Components/Items/HandsComponent.cs | 2 - .../Tests/Doors/AirlockTest.cs | 7 +- Content.IntegrationTests/Tests/EntityTest.cs | 132 +++++++++++++++++- Content.IntegrationTests/Tests/PowerTest.cs | 3 +- Content.Server/Atmos/GasSprayerComponent.cs | 2 - .../Access/IdCardConsoleComponent.cs | 73 +++++++--- .../Components/Atmos/AirtightComponent.cs | 53 ++++--- .../Components/Atmos/GasAnalyzerComponent.cs | 42 ++++-- ...ponent.cs => GasMixtureHolderComponent.cs} | 11 +- .../Atmos/GridAtmosphereComponent.cs | 95 +++++++------ .../Components/BarSign/BarSignComponent.cs | 53 +++---- .../Components/Body/BodyScannerComponent.cs | 33 +++-- .../Body/Digestive/StomachComponent.cs | 47 ++++--- .../Body/DroppedBodyPartComponent.cs | 73 ++++++---- .../Body/DroppedMechanismComponent.cs | 72 ++++++---- .../Components/Body/SurgeryToolComponent.cs | 90 +++++++----- .../Components/Buckle/BuckleComponent.cs | 2 - .../Components/Cargo/CargoConsoleComponent.cs | 81 +++++++---- .../Chemistry/ChemMasterComponent.cs | 79 +++++++---- .../Components/Chemistry/InjectorComponent.cs | 50 ++++--- .../Chemistry/ReagentDispenserComponent.cs | 64 ++++++--- .../TransformableContainerComponent.cs | 49 ++++--- .../Command/CommunicationsConsoleComponent.cs | 33 +++-- .../Components/Conveyor/ConveyorComponent.cs | 2 - .../Damage/DamageOnToolInteractComponent.cs | 9 +- .../Disposal/DisposalRouterComponent.cs | 49 ++++--- .../Disposal/DisposalTaggerComponent.cs | 44 +++--- .../Disposal/DisposalUnitComponent.cs | 30 ++-- .../Components/Doors/AirlockComponent.cs | 101 +++++++++----- .../Components/Doors/ServerDoorComponent.cs | 76 +++++----- .../Components/Fluids/BucketComponent.cs | 43 ++++-- .../Components/Fluids/PuddleComponent.cs | 15 +- .../Components/Fluids/SprayComponent.cs | 3 +- .../Components/GUI/HandsComponent.cs | 2 - .../GUI/HumanInventoryControllerComponent.cs | 2 +- .../Components/GUI/StrippableComponent.cs | 61 +++++--- .../Gravity/GravityGeneratorComponent.cs | 65 +++++---- .../Instruments/InstrumentComponent.cs | 58 ++++---- .../Interactable/HandheldLightComponent.cs | 41 +++--- .../Interactable/WelderComponent.cs | 2 - .../Items/FloorTileItemComponent.cs | 8 +- .../Items/Storage/ServerStorageComponent.cs | 2 - .../Components/Kitchen/MicrowaveComponent.cs | 128 ++++++++++------- .../Components/MagicMirrorComponent.cs | 44 ++++-- .../Medical/MedicalScannerComponent.cs | 54 ++++--- .../Mining/AsteroidRockComponent.cs | 3 +- .../Movement/AiControllerComponent.cs | 20 +-- .../Components/Movement/ClimbableComponent.cs | 8 +- .../Movement/ServerPortalComponent.cs | 52 +++---- .../Movement/ServerTeleporterComponent.cs | 58 ++++---- .../Movement/ShuttleControllerComponent.cs | 2 - .../NodeGroups/ApcNetNodeGroup.cs | 7 +- .../Components/Nutrition/FoodComponent.cs | 65 +++++---- .../Components/PDA/PDAComponent.cs | 42 ++++-- .../Components/Paper/PaperComponent.cs | 36 +++-- .../Power/ApcNetComponents/ApcComponent.cs | 81 +++++++---- .../PowerReceiverUsers/BaseCharger.cs | 76 ++++++---- .../PowerReceiverUsers/LightBulbComponent.cs | 6 +- .../PoweredLightComponent.cs | 8 +- .../Components/Power/PowerCellComponent.cs | 8 +- .../BatteryDischargerComponent.cs | 5 +- .../BatteryStorageComponent.cs | 5 +- .../Power/PowerNetComponents/SmesComponent.cs | 39 ++++-- .../SolarControlConsoleComponent.cs | 36 +++-- .../ExplosiveProjectileComponent.cs | 15 +- .../Projectiles/FlashProjectileComponent.cs | 5 +- .../GameObjects/Components/RadioComponent.cs | 2 - .../Components/Recycling/RecyclerComponent.cs | 2 - .../Components/Research/LatheComponent.cs | 77 +++++----- .../Research/ResearchClientComponent.cs | 39 +++--- .../Research/ResearchConsoleComponent.cs | 68 +++++---- .../Research/ResearchServerComponent.cs | 2 +- .../Rotatable/FlippableComponent.cs | 2 - .../VendingMachineComponent.cs | 84 ++++++----- .../Barrels/ServerRangedBarrelComponent.cs | 3 +- .../GameObjects/Components/WiresComponent.cs | 22 +-- .../GameObjects/EntitySystems/AI/AiSystem.cs | 12 +- .../Atmos/GasTileOverlaySystem.cs | 11 +- .../GameObjects/EntitySystems/MoverSystem.cs | 2 - .../Components/Damage/DamageableComponent.cs | 2 - .../Movement/SharedSlipperyComponent.cs | 16 ++- SpaceStation14.sln.DotSettings | 1 + 84 files changed, 1790 insertions(+), 1123 deletions(-) rename Content.Server/GameObjects/Components/Atmos/{GasMixtureComponent.cs => GasMixtureHolderComponent.cs} (60%) diff --git a/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs index 61049ec7a0..767f354f4b 100644 --- a/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs +++ b/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs @@ -17,9 +17,7 @@ namespace Content.Client.GameObjects.Components.Body [ComponentReference(typeof(IBodyManagerComponent))] public class BodyManagerComponent : SharedBodyManagerComponent, IClientDraggable { -#pragma warning disable 649 [Dependency] private readonly IEntityManager _entityManager = default!; -#pragma warning restore 649 public bool ClientCanDropOn(CanDropEventArgs eventArgs) { diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index eeae34cbde..ab86148401 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -27,13 +27,9 @@ namespace Content.Client.GameObjects.Components.Instruments /// public event Action? OnMidiPlaybackEnded; -#pragma warning disable 649 [Dependency] private readonly IMidiManager _midiManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IClientNetManager _netManager = default!; -#pragma warning restore 649 private IMidiRenderer? _renderer; diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs index 30e32afad3..f023c467ee 100644 --- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs +++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs @@ -19,9 +19,7 @@ namespace Content.Client.GameObjects.Components.Items { private HandsGui? _gui; -#pragma warning disable 649 [Dependency] private readonly IGameHud _gameHud = default!; -#pragma warning restore 649 /// private readonly List _hands = new List(); diff --git a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs index bcf10f7940..939ebce941 100644 --- a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs +++ b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs @@ -1,16 +1,11 @@ -using System.IO; -using System.Threading.Tasks; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Doors; -using Content.Shared.Physics; using NUnit.Framework; -using Robust.Server.Console.Commands; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; -using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Physics; -using Robust.Shared.Prototypes; using static Content.Server.GameObjects.Components.Doors.ServerDoorComponent; namespace Content.IntegrationTests.Tests.Doors diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 639a2f78b6..4fd9846263 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; using System.Threading.Tasks; using NUnit.Framework; using Robust.Server.Interfaces.Maps; @@ -11,6 +14,7 @@ using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Prototypes; +using Logger = Robust.Shared.Log.Logger; namespace Content.IntegrationTests.Tests { @@ -30,7 +34,7 @@ namespace Content.IntegrationTests.Tests var pauseMan = server.ResolveDependency(); var prototypes = new List(); IMapGrid grid = default; - IEntity testEntity = null; + IEntity testEntity; //Build up test environment server.Post(() => @@ -59,7 +63,7 @@ namespace Content.IntegrationTests.Tests { try { - Logger.LogS(LogLevel.Debug, "EntityTest", "Testing: " + prototype.ID); + Logger.LogS(LogLevel.Debug, "EntityTest", $"Testing: {prototype.ID}"); testEntity = entityMan.SpawnEntity(prototype.ID, testLocation); server.RunTicks(2); Assert.That(testEntity.Initialized); @@ -69,8 +73,8 @@ namespace Content.IntegrationTests.Tests //Fail any exceptions thrown on spawn catch (Exception e) { - Logger.LogS(LogLevel.Error, "EntityTest", "Entity '" + prototype.ID + "' threw: " + e.Message); - //Assert.Fail(); + Logger.LogS(LogLevel.Error, "EntityTest", $"Entity '{prototype.ID}' threw: {e.Message}"); + Assert.Fail(); throw; } } @@ -101,5 +105,125 @@ namespace Content.IntegrationTests.Tests await client.WaitIdleAsync(); } + + [Test] + public async Task AllComponentsOneEntityDeleteTest() + { + var skipComponents = new[] + { + "DebugExceptionOnAdd", // Debug components that explicitly throw exceptions + "DebugExceptionExposeData", + "DebugExceptionInitialize", + "DebugExceptionStartup", + "Map", // We aren't testing a map entity in this test + "MapGrid" + }; + + var testEntity = @" +- type: entity + id: AllComponentsOneEntityDeleteTestEntity"; + + var server = StartServerDummyTicker(); + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + var mapLoader = server.ResolveDependency(); + var pauseManager = server.ResolveDependency(); + var componentFactory = server.ResolveDependency(); + var prototypeManager = server.ResolveDependency(); + + IMapGrid grid = default; + + server.Post(() => + { + // Load test entity + using var reader = new StringReader(testEntity); + prototypeManager.LoadFromStream(reader); + + // Load test map + var mapId = mapManager.CreateMap(); + pauseManager.AddUninitializedMap(mapId); + grid = mapLoader.LoadBlueprint(mapId, "Maps/stationstation.yml"); + pauseManager.DoMapInitialize(mapId); + }); + + var distinctComponents = new List<(List components, List references)> + { + (new List(), new List()) + }; + + // Split components into groups, ensuring that their references don't conflict + foreach (var type in componentFactory.AllRegisteredTypes) + { + var registration = componentFactory.GetRegistration(type); + + for (var i = 0; i < distinctComponents.Count; i++) + { + var distinct = distinctComponents[i]; + + if (distinct.references.Intersect(registration.References).Any()) + { + // Ensure the next list if this one has conflicting references + if (i + 1 >= distinctComponents.Count) + { + distinctComponents.Add((new List(), new List())); + } + + continue; + } + + // Add the component and its references if no conflicting references were found + distinct.components.Add(type); + distinct.references.AddRange(registration.References); + } + } + + // Sanity check + Assert.That(distinctComponents, Is.Not.Empty); + + server.Assert(() => + { + Assert.DoesNotThrow(() => + { + foreach (var distinct in distinctComponents) + { + var testLocation = new GridCoordinates(new Vector2(0, 0), grid); + var entity = entityManager.SpawnEntity("AllComponentsOneEntityDeleteTestEntity", testLocation); + + Assert.That(entity.Initialized); + + foreach (var type in distinct.components) + { + var component = (Component) componentFactory.GetComponent(type); + + // If the entity already has this component, if it was ensured or added by another + if (entity.HasComponent(component.GetType())) + { + continue; + } + + // If this component is ignored + if (skipComponents.Contains(component.Name)) + { + continue; + } + + component.Owner = entity; + + Logger.LogS(LogLevel.Debug, "EntityTest", $"Adding component: {component.Name}"); + + entityManager.ComponentManager.AddComponent(entity, component); + } + + server.RunTicks(48); // Run one full second on the server + + entityManager.DeleteEntity(entity.Uid); + } + }); + }); + + await server.WaitIdleAsync(); + } } } diff --git a/Content.IntegrationTests/Tests/PowerTest.cs b/Content.IntegrationTests/Tests/PowerTest.cs index b53fa69734..5b41142b7d 100644 --- a/Content.IntegrationTests/Tests/PowerTest.cs +++ b/Content.IntegrationTests/Tests/PowerTest.cs @@ -49,7 +49,7 @@ namespace Content.IntegrationTests.Tests }); server.RunTicks(1); //let run a tick for PowerNet to process power - + server.Assert(() => { Assert.That(consumer1.DrawRate, Is.EqualTo(consumer1.ReceivedPower)); //first should be fully powered @@ -127,6 +127,7 @@ namespace Content.IntegrationTests.Tests Assert.That(apcEnt.TryGetComponent(out var apc)); Assert.That(apcExtensionEnt.TryGetComponent(out var provider)); Assert.That(powerReceiverEnt.TryGetComponent(out receiver)); + Assert.NotNull(apc.Battery); provider.PowerTransferRange = 5; //arbitrary range to reach receiver receiver.PowerReceptionRange = 5; //arbitrary range to reach provider diff --git a/Content.Server/Atmos/GasSprayerComponent.cs b/Content.Server/Atmos/GasSprayerComponent.cs index 4b78d413a9..d414ce1714 100644 --- a/Content.Server/Atmos/GasSprayerComponent.cs +++ b/Content.Server/Atmos/GasSprayerComponent.cs @@ -20,10 +20,8 @@ namespace Content.Server.Atmos [RegisterComponent] public class GasSprayerComponent : Component, IAfterInteract { -#pragma warning disable 649 [Dependency] private readonly IServerNotifyManager _notifyManager = default!; [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; -#pragma warning restore 649 //TODO: create a function that can create a gas based on a solution mix public override string Name => "GasSprayer"; diff --git a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs index 841b4dd176..9fa417bc1a 100644 --- a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.Interfaces; @@ -15,6 +16,7 @@ using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Prototypes; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Access { @@ -22,16 +24,19 @@ namespace Content.Server.GameObjects.Components.Access [ComponentReference(typeof(IActivate))] public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; - [Dependency] private readonly ILocalizationManager _localizationManager; - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; + [Dependency] private readonly ILocalizationManager _localizationManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - private BoundUserInterface _userInterface; - private ContainerSlot _privilegedIdContainer; - private ContainerSlot _targetIdContainer; - private AccessReader _accessReader; + private ContainerSlot _privilegedIdContainer = default!; + private ContainerSlot _targetIdContainer = default!; + + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(IdCardConsoleUiKey.Key, out var boundUi) + ? boundUi + : null; public override void Initialize() { @@ -40,16 +45,30 @@ namespace Content.Server.GameObjects.Components.Access _privilegedIdContainer = ContainerManagerComponent.Ensure($"{Name}-privilegedId", Owner); _targetIdContainer = ContainerManagerComponent.Ensure($"{Name}-targetId", Owner); - _accessReader = Owner.GetComponent(); + if (!Owner.EnsureComponent(out AccessReader _)) + { + Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(AccessReader)}"); + } + + if (UserInterface == null) + { + Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} doesn't have a {nameof(ServerUserInterfaceComponent)}"); + } + else + { + UserInterface.OnReceiveMessage += OnUiReceiveMessage; + } - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(IdCardConsoleUiKey.Key); - _userInterface.OnReceiveMessage += OnUiReceiveMessage; UpdateUserInterface(); } private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) { + if (obj.Session.AttachedEntity == null) + { + return; + } + switch (obj.Message) { case IdButtonPressedMessage msg: @@ -72,13 +91,19 @@ namespace Content.Server.GameObjects.Components.Access } /// - /// Returns true if there is an ID in and said ID satisfies the requirements of . + /// Returns true if there is an ID in and said ID satisfies the requirements of . /// private bool PrivilegedIdIsAuthorized() { + if (!Owner.TryGetComponent(out AccessReader? reader)) + { + return true; + } + var privilegedIdEntity = _privilegedIdContainer.ContainedEntity; - return privilegedIdEntity != null && _accessReader.IsAllowed(privilegedIdEntity); + return privilegedIdEntity != null && reader.IsAllowed(privilegedIdEntity); } + /// /// Called when the "Submit" button in the UI gets pressed. /// Writes data passed from the UI into the ID stored in , if present. @@ -110,7 +135,7 @@ namespace Content.Server.GameObjects.Components.Access /// private void HandleId(IEntity user, ContainerSlot container) { - if (!user.TryGetComponent(out IHandsComponent hands)) + if (!user.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("You have no hands.")); return; @@ -133,7 +158,13 @@ namespace Content.Server.GameObjects.Components.Access { return; } - if(!hands.Drop(hands.ActiveHand, container)) + + if (hands.ActiveHand == null) + { + return; + } + + if (!hands.Drop(hands.ActiveHand, container)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("You can't let go of the ID card!")); return; @@ -185,17 +216,17 @@ namespace Content.Server.GameObjects.Components.Access _privilegedIdContainer.ContainedEntity?.Name ?? "", _targetIdContainer.ContainedEntity?.Name ?? ""); } - _userInterface.SetState(newState); + UserInterface?.SetState(newState); } public void Activate(ActivateEventArgs eventArgs) { - if(!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if(!eventArgs.User.TryGetComponent(out IActorComponent? actor)) { return; } - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } } } diff --git a/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs b/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs index 07c6ed66e9..5e50bd0c64 100644 --- a/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs @@ -1,9 +1,10 @@ -using System; +#nullable enable using Content.Server.GameObjects.EntitySystems; using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -13,7 +14,6 @@ namespace Content.Server.GameObjects.Components.Atmos [RegisterComponent] public class AirtightComponent : Component, IMapInit { - private SnapGridComponent _snapGrid; private (GridId, MapIndices) _lastPosition; public override string Name => "Airtight"; @@ -28,7 +28,11 @@ namespace Content.Server.GameObjects.Components.Atmos set { _airBlocked = value; - EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)?.Invalidate(_snapGrid.Position); + + if (Owner.TryGetComponent(out SnapGridComponent? snapGrid)) + { + EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)?.Invalidate(snapGrid.Position); + } } } @@ -48,19 +52,23 @@ namespace Content.Server.GameObjects.Components.Atmos base.Initialize(); // Using the SnapGrid is critical for the performance of the room builder, and thus if - // it is absent the component will not be airtight. An exception is much easier to track - // down than the object magically not being airtight, so throw one if the SnapGrid component + // it is absent the component will not be airtight. A warning is much easier to track + // down than the object magically not being airtight, so log one if the SnapGrid component // is missing. - if (!Owner.TryGetComponent(out _snapGrid)) - throw new Exception("Airtight entities must have a SnapGrid component"); + if (!Owner.EnsureComponent(out SnapGridComponent _)) + Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition.ToString()} doesn't have a {nameof(SnapGridComponent)}"); UpdatePosition(); } public void MapInit() { - _snapGrid.OnPositionChanged += OnTransformMove; - _lastPosition = (Owner.Transform.GridID, _snapGrid.Position); + if (Owner.TryGetComponent(out SnapGridComponent? snapGrid)) + { + snapGrid.OnPositionChanged += OnTransformMove; + _lastPosition = (Owner.Transform.GridID, snapGrid.Position); + } + UpdatePosition(); } @@ -70,11 +78,15 @@ namespace Content.Server.GameObjects.Components.Atmos _airBlocked = false; - _snapGrid.OnPositionChanged -= OnTransformMove; + if (Owner.TryGetComponent(out SnapGridComponent? snapGrid)) + { + snapGrid.OnPositionChanged -= OnTransformMove; + + if (_fixVacuum) + EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)? + .FixVacuum(snapGrid.Position); + } - if(_fixVacuum) - EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)? - .FixVacuum(_snapGrid.Position); UpdatePosition(); } @@ -83,15 +95,24 @@ namespace Content.Server.GameObjects.Components.Atmos { UpdatePosition(_lastPosition.Item1, _lastPosition.Item2); UpdatePosition(); - _lastPosition = (Owner.Transform.GridID, _snapGrid.Position); + + if (Owner.TryGetComponent(out SnapGridComponent? snapGrid)) + { + _lastPosition = (Owner.Transform.GridID, snapGrid.Position); + } } - private void UpdatePosition() => UpdatePosition(Owner.Transform.GridID, _snapGrid.Position); + private void UpdatePosition() + { + if (Owner.TryGetComponent(out SnapGridComponent? snapGrid)) + { + UpdatePosition(Owner.Transform.GridID, snapGrid.Position); + } + } private void UpdatePosition(GridId gridId, MapIndices pos) { EntitySystem.Get().GetGridAtmosphere(gridId)?.Invalidate(pos); } - } } diff --git a/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs b/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs index 2a6a2be135..511b26df1c 100644 --- a/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs @@ -16,30 +16,37 @@ using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Atmos { [RegisterComponent] public class GasAnalyzerComponent : SharedGasAnalyzerComponent, IAfterInteract, IDropped, IUse { -#pragma warning disable 649 - [Dependency] private IServerNotifyManager _notifyManager = default!; - [Dependency] private IMapManager _mapManager = default!; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; - private BoundUserInterface _userInterface = default!; private GasAnalyzerDanger _pressureDanger; private float _timeSinceSync; private const float TimeBetweenSyncs = 2f; private bool _checkPlayer = false; // Check at the player pos or at some other tile? private GridCoordinates? _position; // The tile that we scanned + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(GasAnalyzerUiKey.Key, out var boundUi) + ? boundUi + : null; + public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(GasAnalyzerUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + } } public override ComponentState GetComponentState() @@ -56,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Atmos { _checkPlayer = true; _position = null; - _userInterface.Open(session); + UserInterface?.Open(session); UpdateUserInterface(); Resync(); } @@ -71,7 +78,7 @@ namespace Content.Server.GameObjects.Components.Atmos { _checkPlayer = false; _position = pos; - _userInterface.Open(session); + UserInterface?.Open(session); UpdateUserInterface(); Resync(); } @@ -79,7 +86,7 @@ namespace Content.Server.GameObjects.Components.Atmos public void CloseInterface(IPlayerSession session) { _position = null; - _userInterface.Close(session); + UserInterface?.Close(session); Resync(); } @@ -123,10 +130,15 @@ namespace Content.Server.GameObjects.Components.Atmos private void UpdateUserInterface() { + if (UserInterface == null) + { + return; + } + string? error = null; // Check if the player is still holding the gas analyzer => if not, don't update - foreach (var session in _userInterface.SubscribedSessions) + foreach (var session in UserInterface.SubscribedSessions) { if (session.AttachedEntity == null) return; @@ -156,7 +168,7 @@ namespace Content.Server.GameObjects.Components.Atmos if (tile == null) { error = "No Atmosphere!"; - _userInterface.SetState( + UserInterface.SetState( new GasAnalyzerBoundUserInterfaceState( 0, 0, @@ -166,7 +178,7 @@ namespace Content.Server.GameObjects.Components.Atmos } var gases = new List(); - for (int i = 0; i < Atmospherics.TotalNumberOfGases; i++) + for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { var gas = Atmospherics.GetGas(i); @@ -175,7 +187,7 @@ namespace Content.Server.GameObjects.Components.Atmos gases.Add(new GasEntry(gas.Name, tile.Gases[i], gas.Color)); } - _userInterface.SetState( + UserInterface.SetState( new GasAnalyzerBoundUserInterfaceState( tile.Pressure, tile.Temperature, diff --git a/Content.Server/GameObjects/Components/Atmos/GasMixtureComponent.cs b/Content.Server/GameObjects/Components/Atmos/GasMixtureHolderComponent.cs similarity index 60% rename from Content.Server/GameObjects/Components/Atmos/GasMixtureComponent.cs rename to Content.Server/GameObjects/Components/Atmos/GasMixtureHolderComponent.cs index c60ad08efa..5aeaff4fc9 100644 --- a/Content.Server/GameObjects/Components/Atmos/GasMixtureComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GasMixtureHolderComponent.cs @@ -6,16 +6,21 @@ using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Atmos { [RegisterComponent] - public class GasMixtureComponent : Component + public class GasMixtureHolderComponent : Component { - public override string Name => "GasMixture"; + public override string Name => "GasMixtureHolder"; [ViewVariables] public GasMixture GasMixture { get; set; } = new GasMixture(); public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - serializer.DataField(this, x => GasMixture.Volume, "volume", 0f); + + serializer.DataReadWriteFunction( + "volume", + 0f, + vol => GasMixture.Volume = vol, + () => GasMixture.Volume); } } } diff --git a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs index 05ba999b0b..6af07c6eba 100644 --- a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -50,7 +51,6 @@ namespace Content.Server.GameObjects.Components.Atmos private float _timer = 0f; private Stopwatch _stopwatch = new Stopwatch(); public int UpdateCounter { get; private set; } = 0; - private IMapGrid _grid; [ViewVariables] private readonly HashSet _excitedGroups = new HashSet(1000); @@ -89,42 +89,40 @@ namespace Content.Server.GameObjects.Components.Atmos /// public void PryTile(MapIndices indices) { + if (!Owner.TryGetComponent(out IMapGridComponent? mapGridComponent)) return; if (IsSpace(indices) || IsAirBlocked(indices)) return; - var tile = _grid.GetTileRef(indices).Tile; + var mapGrid = mapGridComponent.Grid; + var tile = mapGrid.GetTileRef(indices).Tile; var tileDefinitionManager = IoCManager.Resolve(); var tileDef = (ContentTileDefinition)tileDefinitionManager[tile.TypeId]; var underplating = tileDefinitionManager["underplating"]; - _grid.SetTile(indices, new Tile(underplating.TileId)); + mapGrid.SetTile(indices, new Tile(underplating.TileId)); //Actually spawn the relevant tile item at the right position and give it some offset to the corner. - var tileItem = IoCManager.Resolve().SpawnEntity(tileDef.ItemDropPrototypeName, new GridCoordinates(indices.X, indices.Y, _grid)); + var tileItem = IoCManager.Resolve().SpawnEntity(tileDef.ItemDropPrototypeName, new GridCoordinates(indices.X, indices.Y, mapGrid)); tileItem.Transform.WorldPosition += (0.2f, 0.2f); } public override void Initialize() { base.Initialize(); - - _grid = Owner.GetComponent().Grid; - RepopulateTiles(); } public override void OnAdd() { base.OnAdd(); - - _grid = Owner.GetComponent().Grid; - RepopulateTiles(); } public void RepopulateTiles() { - foreach (var tile in _grid.GetAllTiles()) + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; + + foreach (var tile in mapGrid.Grid.GetAllTiles()) { if(!_tiles.ContainsKey(tile.GridIndices)) _tiles.Add(tile.GridIndices, new TileAtmosphere(this, tile.GridIndex, tile.GridIndices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C})); @@ -145,6 +143,8 @@ namespace Content.Server.GameObjects.Components.Atmos private void Revalidate() { + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; + foreach (var indices in _invalidatedCoords.ToArray()) { var tile = GetTile(indices); @@ -152,7 +152,7 @@ namespace Content.Server.GameObjects.Components.Atmos if (tile == null) { - tile = new TileAtmosphere(this, _grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}); + tile = new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}); _tiles[indices] = tile; } @@ -199,8 +199,9 @@ namespace Content.Server.GameObjects.Components.Atmos /// public void FixVacuum(MapIndices indices) { + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; var tile = GetTile(indices); - if (tile?.GridIndex != _grid.Index) return; + if (tile?.GridIndex != mapGrid.Grid.Index) return; var adjacent = GetAdjacentTiles(indices); tile.Air = new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}; _tiles[indices] = tile; @@ -217,16 +218,17 @@ namespace Content.Server.GameObjects.Components.Atmos /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddActiveTile(TileAtmosphere tile) + public void AddActiveTile(TileAtmosphere? tile) { - if (tile?.GridIndex != _grid.Index || tile?.Air == null) return; + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; + if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return; tile.Excited = true; _activeTiles.Add(tile); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveActiveTile(TileAtmosphere tile) + public void RemoveActiveTile(TileAtmosphere? tile) { if (tile == null) return; _activeTiles.Remove(tile); @@ -236,27 +238,29 @@ namespace Content.Server.GameObjects.Components.Atmos /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddHotspotTile(TileAtmosphere tile) + public void AddHotspotTile(TileAtmosphere? tile) { - if (tile?.GridIndex != _grid.Index || tile?.Air == null) return; + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; + if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return; _hotspotTiles.Add(tile); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveHotspotTile(TileAtmosphere tile) + public void RemoveHotspotTile(TileAtmosphere? tile) { if (tile == null) return; _hotspotTiles.Remove(tile); } - public void AddSuperconductivityTile(TileAtmosphere tile) + public void AddSuperconductivityTile(TileAtmosphere? tile) { - if (tile?.GridIndex != _grid.Index) return; + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; + if (tile?.GridIndex != mapGrid.Grid.Index) return; _superconductivityTiles.Add(tile); } - public void RemoveSuperconductivityTile(TileAtmosphere tile) + public void RemoveSuperconductivityTile(TileAtmosphere? tile) { if (tile == null) return; _superconductivityTiles.Remove(tile); @@ -264,9 +268,10 @@ namespace Content.Server.GameObjects.Components.Atmos /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddHighPressureDelta(TileAtmosphere tile) + public void AddHighPressureDelta(TileAtmosphere? tile) { - if (tile?.GridIndex != _grid.Index) return; + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; + if (tile?.GridIndex != mapGrid.Grid.Index) return; _highPressureDelta.Add(tile); } @@ -292,20 +297,22 @@ namespace Content.Server.GameObjects.Components.Atmos } /// - public TileAtmosphere GetTile(GridCoordinates coordinates) + public TileAtmosphere? GetTile(GridCoordinates coordinates) { return GetTile(coordinates.ToMapIndices(_mapManager)); } /// - public TileAtmosphere GetTile(MapIndices indices) + public TileAtmosphere? GetTile(MapIndices indices) { + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return null; + if (_tiles.TryGetValue(indices, out var tile)) return tile; // We don't have that tile! if (IsSpace(indices)) { - var space = new TileAtmosphere(this, _grid.Index, indices, new GasMixture(int.MaxValue){Temperature = Atmospherics.TCMB}); + var space = new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(int.MaxValue){Temperature = Atmospherics.TCMB}); space.Air.MarkImmutable(); return space; } @@ -324,7 +331,9 @@ namespace Content.Server.GameObjects.Components.Atmos public bool IsSpace(MapIndices indices) { // TODO ATMOS use ContentTileDefinition to define in YAML whether or not a tile is considered space - return _grid.GetTileRef(indices).Tile.IsEmpty; + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default; + + return mapGrid.Grid.GetTileRef(indices).Tile.IsEmpty; } public Dictionary GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false) @@ -334,7 +343,7 @@ namespace Content.Server.GameObjects.Components.Atmos { var side = indices.Offset(dir); var tile = GetTile(side); - if(tile?.Air != null || includeAirBlocked) + if (tile != null && (tile.Air != null || includeAirBlocked)) sides[dir] = tile; } @@ -349,7 +358,9 @@ namespace Content.Server.GameObjects.Components.Atmos /// public float GetVolumeForCells(int cellCount) { - return _grid.TileSize * cellCount * Atmospherics.CellVolume; + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default; + + return mapGrid.Grid.TileSize * cellCount * Atmospherics.CellVolume; } /// @@ -509,9 +520,11 @@ namespace Content.Server.GameObjects.Components.Atmos } } - private AirtightComponent GetObstructingComponent(MapIndices indices) + private AirtightComponent? GetObstructingComponent(MapIndices indices) { - foreach (var v in _grid.GetSnapGridCell(indices, SnapGridOffset.Center)) + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default; + + foreach (var v in mapGrid.Grid.GetSnapGridCell(indices, SnapGridOffset.Center)) { if (v.Owner.TryGetComponent(out var ac)) return ac; @@ -534,22 +547,24 @@ namespace Content.Server.GameObjects.Components.Atmos public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - if (serializer.Reading) + if (serializer.Reading && + Owner.TryGetComponent(out IMapGridComponent? mapGrid)) { - var gridId = Owner.GetComponent().Grid.Index; + var gridId = mapGrid.Grid.Index; - if (!serializer.TryReadDataField("uniqueMixes", out List uniqueMixes) || - !serializer.TryReadDataField("tiles", out Dictionary tiles)) + if (!serializer.TryReadDataField("uniqueMixes", out List? uniqueMixes) || + !serializer.TryReadDataField("tiles", out Dictionary? tiles)) return; _tiles.Clear(); - foreach (var (indices, mix) in tiles) + foreach (var (indices, mix) in tiles!) { - _tiles.Add(indices, new TileAtmosphere(this, gridId, indices, (GasMixture)uniqueMixes[mix].Clone())); + _tiles.Add(indices, new TileAtmosphere(this, gridId, indices, (GasMixture)uniqueMixes![mix].Clone())); Invalidate(indices); } - } else if (serializer.Writing) + } + else if (serializer.Writing) { var uniqueMixes = new List(); var uniqueMixHash = new Dictionary(); diff --git a/Content.Server/GameObjects/Components/BarSign/BarSignComponent.cs b/Content.Server/GameObjects/Components/BarSign/BarSignComponent.cs index d6b1ae3233..16827c0dc6 100644 --- a/Content.Server/GameObjects/Components/BarSign/BarSignComponent.cs +++ b/Content.Server/GameObjects/Components/BarSign/BarSignComponent.cs @@ -1,4 +1,5 @@ -using System.Linq; +#nullable enable +using System.Linq; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Robust.Server.GameObjects; using Robust.Server.Interfaces.GameObjects; @@ -19,18 +20,13 @@ namespace Content.Server.GameObjects.Components.BarSign { public override string Name => "BarSign"; -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IRobustRandom _robustRandom; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; - private string _currentSign; - - private PowerReceiverComponent _power; - private SpriteComponent _sprite; + private string? _currentSign; [ViewVariables(VVAccess.ReadWrite)] - public string CurrentSign + public string? CurrentSign { get => _currentSign; set @@ -40,6 +36,8 @@ namespace Content.Server.GameObjects.Components.BarSign } } + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; + private void UpdateSignInfo() { if (_currentSign == null) @@ -53,15 +51,18 @@ namespace Content.Server.GameObjects.Components.BarSign return; } - if (!_power.Powered) + if (Owner.TryGetComponent(out SpriteComponent? sprite)) { - _sprite.LayerSetState(0, "empty"); - _sprite.LayerSetShader(0, "shaded"); - } - else - { - _sprite.LayerSetState(0, prototype.Icon); - _sprite.LayerSetShader(0, "unshaded"); + if (!Powered) + { + sprite.LayerSetState(0, "empty"); + sprite.LayerSetShader(0, "shaded"); + } + else + { + sprite.LayerSetState(0, prototype.Icon); + sprite.LayerSetShader(0, "unshaded"); + } } if (!string.IsNullOrEmpty(prototype.Name)) @@ -80,21 +81,25 @@ namespace Content.Server.GameObjects.Components.BarSign { base.Initialize(); - _power = Owner.GetComponent(); - _sprite = Owner.GetComponent(); - - _power.OnPowerStateChanged += PowerOnOnPowerStateChanged; + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) + { + receiver.OnPowerStateChanged += PowerOnOnPowerStateChanged; + } UpdateSignInfo(); } public override void OnRemove() { - _power.OnPowerStateChanged -= PowerOnOnPowerStateChanged; + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) + { + receiver.OnPowerStateChanged -= PowerOnOnPowerStateChanged; + } + base.OnRemove(); } - private void PowerOnOnPowerStateChanged(object sender, PowerStateEventArgs e) + private void PowerOnOnPowerStateChanged(object? sender, PowerStateEventArgs e) { UpdateSignInfo(); } diff --git a/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs index 2c49665757..be4d6c99c9 100644 --- a/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs +++ b/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs @@ -1,10 +1,13 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using Content.Server.Body; using Content.Shared.Body.Scanner; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; +using Robust.Shared.Log; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Body { @@ -12,32 +15,44 @@ namespace Content.Server.GameObjects.Components.Body [ComponentReference(typeof(IActivate))] public class BodyScannerComponent : Component, IActivate { - private BoundUserInterface _userInterface; public sealed override string Name => "BodyScanner"; + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(BodyScannerUiKey.Key, out var boundUi) + ? boundUi + : null; + void IActivate.Activate(ActivateEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor) || + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor) || actor.playerSession.AttachedEntity == null) { return; } - if (actor.playerSession.AttachedEntity.TryGetComponent(out BodyManagerComponent attempt)) + if (actor.playerSession.AttachedEntity.TryGetComponent(out BodyManagerComponent? attempt)) { var state = InterfaceState(attempt.Template, attempt.Parts); - _userInterface.SetState(state); + UserInterface?.SetState(state); } - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(BodyScannerUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + + if (UserInterface == null) + { + Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} doesn't have a {nameof(ServerUserInterfaceComponent)}"); + } + else + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } } private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) { } diff --git a/Content.Server/GameObjects/Components/Body/Digestive/StomachComponent.cs b/Content.Server/GameObjects/Components/Body/Digestive/StomachComponent.cs index fa6b76caea..15c6d01e82 100644 --- a/Content.Server/GameObjects/Components/Body/Digestive/StomachComponent.cs +++ b/Content.Server/GameObjects/Components/Body/Digestive/StomachComponent.cs @@ -1,12 +1,11 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Body.Circulatory; using Content.Server.GameObjects.Components.Chemistry; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Nutrition; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -20,25 +19,21 @@ namespace Content.Server.GameObjects.Components.Body.Digestive [RegisterComponent] public class StomachComponent : SharedStomachComponent { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 - /// /// Max volume of internal solution storage /// public ReagentUnit MaxVolume { - get => _stomachContents.MaxVolume; - set => _stomachContents.MaxVolume = value; + get => Owner.TryGetComponent(out SolutionComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero; + set + { + if (Owner.TryGetComponent(out SolutionComponent? solution)) + { + solution.MaxVolume = value; + } + } } - /// - /// Internal solution storage - /// - [ViewVariables] - private SolutionComponent _stomachContents; - /// /// Initial internal solution storage volume /// @@ -68,20 +63,29 @@ namespace Content.Server.GameObjects.Components.Body.Digestive { base.Startup(); - _stomachContents = Owner.GetComponent(); - _stomachContents.MaxVolume = _initialMaxVolume; + if (!Owner.EnsureComponent(out SolutionComponent solution)) + { + Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}"); + } + + solution.MaxVolume = _initialMaxVolume; } public bool TryTransferSolution(Solution solution) { + if (!Owner.TryGetComponent(out SolutionComponent? solutionComponent)) + { + return false; + } + // TODO: For now no partial transfers. Potentially change by design - if (solution.TotalVolume + _stomachContents.CurrentVolume > _stomachContents.MaxVolume) + if (solution.TotalVolume + solutionComponent.CurrentVolume > solutionComponent.MaxVolume) { return false; } // Add solution to _stomachContents - _stomachContents.TryAddSolution(solution, false, true); + solutionComponent.TryAddSolution(solution, false, true); // Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach foreach (var reagent in solution.Contents) { @@ -99,7 +103,8 @@ namespace Content.Server.GameObjects.Components.Body.Digestive /// The time since the last update in seconds. public void Update(float frameTime) { - if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream)) + if (!Owner.TryGetComponent(out SolutionComponent? solutionComponent) || + !Owner.TryGetComponent(out BloodstreamComponent? bloodstream)) { return; } @@ -114,7 +119,7 @@ namespace Content.Server.GameObjects.Components.Body.Digestive delta.Increment(frameTime); if (delta.Lifetime > _digestionDelay) { - _stomachContents.TryRemoveReagent(delta.ReagentId, delta.Quantity); + solutionComponent.TryRemoveReagent(delta.ReagentId, delta.Quantity); transferSolution.AddReagent(delta.ReagentId, delta.Quantity); _reagentDeltas.Remove(delta); } diff --git a/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs b/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs index 431f60e026..cce6566879 100644 --- a/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs +++ b/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using System.Linq; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; @@ -6,6 +7,7 @@ using Content.Server.Body; using Content.Shared.Body.Surgery; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; @@ -21,20 +23,23 @@ namespace Content.Server.GameObjects.Components.Body [RegisterComponent] public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer { -#pragma warning disable 649 - [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager; -#pragma warning restore 649 + [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!; private readonly Dictionary _optionsCache = new Dictionary(); - private BodyManagerComponent _bodyManagerComponentCache; + private BodyManagerComponent? _bodyManagerComponentCache; private int _idHash; - private IEntity _performerCache; - - private BoundUserInterface _userInterface; + private IEntity? _performerCache; public sealed override string Name => "DroppedBodyPart"; - [ViewVariables] public BodyPart ContainedBodyPart { get; private set; } + [ViewVariables] public BodyPart ContainedBodyPart { get; private set; } = default!; + + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(GenericSurgeryUiKey.Key, out var boundUi) + ? boundUi + : null; void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { @@ -48,7 +53,7 @@ namespace Content.Server.GameObjects.Components.Body _performerCache = null; _bodyManagerComponentCache = null; - if (eventArgs.Target.TryGetComponent(out BodyManagerComponent bodyManager)) + if (eventArgs.Target.TryGetComponent(out BodyManagerComponent? bodyManager)) { SendBodySlotListToUser(eventArgs, bodyManager); } @@ -58,9 +63,10 @@ namespace Content.Server.GameObjects.Components.Body { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(GenericSurgeryUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } } public void TransferBodyPartData(BodyPart data) @@ -68,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Body ContainedBodyPart = data; Owner.Name = Loc.GetString(ContainedBodyPart.Name); - if (Owner.TryGetComponent(out SpriteComponent component)) + if (Owner.TryGetComponent(out SpriteComponent? component)) { component.LayerSetRSI(0, data.RSIPath); component.LayerSetState(0, data.RSIState); @@ -91,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Body foreach (var slot in unoccupiedSlots) { if (!bodyManager.TryGetSlotType(slot, out var typeResult) || - typeResult != ContainedBodyPart.PartType || + typeResult != ContainedBodyPart?.PartType || !bodyManager.TryGetBodyPartConnections(slot, out var parts)) { continue; @@ -129,7 +135,18 @@ namespace Content.Server.GameObjects.Components.Body /// private void HandleReceiveBodyPartSlot(int key) { - CloseSurgeryUI(_performerCache.GetComponent().playerSession); + if (_performerCache == null || + !_performerCache.TryGetComponent(out IActorComponent? actor)) + { + return; + } + + CloseSurgeryUI(actor.playerSession); + + if (_bodyManagerComponentCache == null) + { + return; + } // TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc if (!_optionsCache.TryGetValue(key, out var targetObject)) @@ -138,34 +155,42 @@ namespace Content.Server.GameObjects.Components.Body Loc.GetString("You see no useful way to attach {0:theName} anymore.", Owner)); } - var target = targetObject as string; + var target = (string) targetObject!; + string message; + + if (_bodyManagerComponentCache.InstallDroppedBodyPart(this, target)) + { + message = Loc.GetString("You attach {0:theName}.", ContainedBodyPart); + } + else + { + message = Loc.GetString("You can't attach it!"); + } _sharedNotifyManager.PopupMessage( _bodyManagerComponentCache.Owner, _performerCache, - !_bodyManagerComponentCache.InstallDroppedBodyPart(this, target) - ? Loc.GetString("You can't attach it!") - : Loc.GetString("You attach {0:theName}.", ContainedBodyPart)); + message); } private void OpenSurgeryUI(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } private void UpdateSurgeryUIBodyPartSlotRequest(IPlayerSession session, Dictionary options) { - _userInterface.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session); + UserInterface?.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session); } private void CloseSurgeryUI(IPlayerSession session) { - _userInterface.Close(session); + UserInterface?.Close(session); } private void CloseAllSurgeryUIs() { - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) diff --git a/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs b/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs index 58cab75d74..92af831132 100644 --- a/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs +++ b/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs @@ -1,4 +1,4 @@ -using System; +#nullable enable using System.Collections.Generic; using Content.Server.Body; using Content.Server.Body.Mechanisms; @@ -8,14 +8,15 @@ using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Log; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; +using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Body @@ -26,24 +27,27 @@ namespace Content.Server.GameObjects.Components.Body [RegisterComponent] public class DroppedMechanismComponent : Component, IAfterInteract { -#pragma warning disable 649 - [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager; - [Dependency] private IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public sealed override string Name => "DroppedMechanism"; private readonly Dictionary _optionsCache = new Dictionary(); - private BodyManagerComponent _bodyManagerComponentCache; + private BodyManagerComponent? _bodyManagerComponentCache; private int _idHash; - private IEntity _performerCache; + private IEntity? _performerCache; - private BoundUserInterface _userInterface; + [ViewVariables] public Mechanism ContainedMechanism { get; private set; } = default!; - [ViewVariables] public Mechanism ContainedMechanism { get; private set; } + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(GenericSurgeryUiKey.Key, out var boundUi) + ? boundUi + : null; void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { @@ -63,12 +67,7 @@ namespace Content.Server.GameObjects.Components.Body } else if (eventArgs.Target.TryGetComponent(out var droppedBodyPart)) { - if (droppedBodyPart.ContainedBodyPart == null) - { - Logger.Debug( - "Installing a mechanism was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!"); - throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!"); - } + DebugTools.AssertNotNull(droppedBodyPart.ContainedBodyPart); if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this)) { @@ -82,9 +81,10 @@ namespace Content.Server.GameObjects.Components.Body { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(GenericSurgeryUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } } public void InitializeDroppedMechanism(Mechanism data) @@ -92,7 +92,7 @@ namespace Content.Server.GameObjects.Components.Body ContainedMechanism = data; Owner.Name = Loc.GetString(ContainedMechanism.Name); - if (Owner.TryGetComponent(out SpriteComponent component)) + if (Owner.TryGetComponent(out SpriteComponent? component)) { component.LayerSetRSI(0, data.RSIPath); component.LayerSetState(0, data.RSIState); @@ -111,7 +111,7 @@ namespace Content.Server.GameObjects.Components.Body if (serializer.Reading && debugLoadMechanismData != "") { - _prototypeManager.TryIndex(debugLoadMechanismData, out MechanismPrototype data); + _prototypeManager.TryIndex(debugLoadMechanismData!, out MechanismPrototype data); var mechanism = new Mechanism(data); mechanism.EnsureInitialize(); @@ -155,7 +155,18 @@ namespace Content.Server.GameObjects.Components.Body /// private void HandleReceiveBodyPart(int key) { - CloseSurgeryUI(_performerCache.GetComponent().playerSession); + if (_performerCache == null || + !_performerCache.TryGetComponent(out IActorComponent? actor)) + { + return; + } + + CloseSurgeryUI(actor.playerSession); + + if (_bodyManagerComponentCache == null) + { + return; + } // TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc if (!_optionsCache.TryGetValue(key, out var targetObject)) @@ -165,36 +176,37 @@ namespace Content.Server.GameObjects.Components.Body return; } - var target = targetObject as BodyPart; + var target = (BodyPart) targetObject; + var message = target.TryInstallDroppedMechanism(this) + ? Loc.GetString("You jam the {0} inside {1:them}.", ContainedMechanism.Name, _performerCache) + : Loc.GetString("You can't fit it in!"); _sharedNotifyManager.PopupMessage( _bodyManagerComponentCache.Owner, _performerCache, - !target.TryInstallDroppedMechanism(this) - ? Loc.GetString("You can't fit it in!") - : Loc.GetString("You jam the {1} inside {0:them}.", _performerCache, ContainedMechanism.Name)); + message); // TODO: {1:theName} } private void OpenSurgeryUI(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary options) { - _userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session); + UserInterface?.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session); } private void CloseSurgeryUI(IPlayerSession session) { - _userInterface.Close(session); + UserInterface?.Close(session); } private void CloseAllSurgeryUIs() { - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) diff --git a/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs b/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs index 7f4ece43a0..9f55f87a49 100644 --- a/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs +++ b/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using Content.Server.Body; using Content.Server.Body.Mechanisms; @@ -18,6 +19,8 @@ using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Body { @@ -30,9 +33,7 @@ namespace Content.Server.GameObjects.Components.Body [RegisterComponent] public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract { -#pragma warning disable 649 - [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager; -#pragma warning restore 649 + [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!; public override string Name => "SurgeryTool"; public override uint? NetID => ContentNetIDs.SURGERY; @@ -41,17 +42,22 @@ namespace Content.Server.GameObjects.Components.Body private float _baseOperateTime; - private BodyManagerComponent _bodyManagerComponentCache; + private BodyManagerComponent? _bodyManagerComponentCache; - private ISurgeon.MechanismRequestCallback _callbackCache; + private ISurgeon.MechanismRequestCallback? _callbackCache; private int _idHash; - private IEntity _performerCache; + private IEntity? _performerCache; private SurgeryType _surgeryType; - private BoundUserInterface _userInterface; + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(GenericSurgeryUiKey.Key, out var boundUi) + ? boundUi + : null; void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { @@ -60,7 +66,7 @@ namespace Content.Server.GameObjects.Components.Body return; } - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) { return; } @@ -73,7 +79,7 @@ namespace Content.Server.GameObjects.Components.Body _callbackCache = null; // Attempt surgery on a BodyManagerComponent by sending a list of operable BodyParts to the client to choose from - if (eventArgs.Target.TryGetComponent(out BodyManagerComponent body)) + if (eventArgs.Target.TryGetComponent(out BodyManagerComponent? body)) { // Create dictionary to send to client (text to be shown : data sent back if selected) var toSend = new Dictionary(); @@ -105,13 +111,7 @@ namespace Content.Server.GameObjects.Components.Body // Attempt surgery on a DroppedBodyPart - there's only one possible target so no need for selection UI _performerCache = eventArgs.User; - if (droppedBodyPart.ContainedBodyPart == null) - { - // Throw error if the DroppedBodyPart has no data in it. - Logger.Debug( - "Surgery was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!"); - throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!"); - } + DebugTools.AssertNotNull(droppedBodyPart.ContainedBodyPart); // If surgery can be performed... if (!droppedBodyPart.ContainedBodyPart.SurgeryCheck(_surgeryType)) @@ -144,7 +144,7 @@ namespace Content.Server.GameObjects.Components.Body toSend.Add(mechanism.Name, _idHash++); } - if (_optionsCache.Count > 0) + if (_optionsCache.Count > 0 && _performerCache != null) { OpenSurgeryUI(_performerCache.GetComponent().playerSession); UpdateSurgeryUIMechanismRequest(_performerCache.GetComponent().playerSession, @@ -162,34 +162,35 @@ namespace Content.Server.GameObjects.Components.Body { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(GenericSurgeryUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } } private void OpenSurgeryUI(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary options) { - _userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session); + UserInterface?.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session); } private void UpdateSurgeryUIMechanismRequest(IPlayerSession session, Dictionary options) { - _userInterface.SendMessage(new RequestMechanismSurgeryUIMessage(options), session); + UserInterface?.SendMessage(new RequestMechanismSurgeryUIMessage(options), session); } private void CloseSurgeryUI(IPlayerSession session) { - _userInterface.Close(session); + UserInterface?.Close(session); } private void CloseAllSurgeryUIs() { - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) @@ -211,14 +212,22 @@ namespace Content.Server.GameObjects.Components.Body /// private void HandleReceiveBodyPart(int key) { - CloseSurgeryUI(_performerCache.GetComponent().playerSession); - // TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc - if (!_optionsCache.TryGetValue(key, out var targetObject)) + if (_performerCache == null || + !_performerCache.TryGetComponent(out IActorComponent? actor)) { - SendNoUsefulWayToUseAnymorePopup(); + return; } - var target = targetObject as BodyPart; + CloseSurgeryUI(actor.playerSession); + // TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc + if (!_optionsCache.TryGetValue(key, out var targetObject) || + _bodyManagerComponentCache == null) + { + SendNoUsefulWayToUseAnymorePopup(); + return; + } + + var target = (BodyPart) targetObject!; if (!target.AttemptSurgery(_surgeryType, _bodyManagerComponentCache, this, _performerCache)) { @@ -233,19 +242,27 @@ namespace Content.Server.GameObjects.Components.Body private void HandleReceiveMechanism(int key) { // TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc - if (!_optionsCache.TryGetValue(key, out var targetObject)) + if (!_optionsCache.TryGetValue(key, out var targetObject) || + _performerCache == null || + !_performerCache.TryGetComponent(out IActorComponent? actor)) { SendNoUsefulWayToUseAnymorePopup(); + return; } var target = targetObject as Mechanism; - CloseSurgeryUI(_performerCache.GetComponent().playerSession); - _callbackCache(target, _bodyManagerComponentCache, this, _performerCache); + CloseSurgeryUI(actor.playerSession); + _callbackCache?.Invoke(target, _bodyManagerComponentCache, this, _performerCache); } private void SendNoUsefulWayToUsePopup() { + if (_bodyManagerComponentCache == null) + { + return; + } + _sharedNotifyManager.PopupMessage( _bodyManagerComponentCache.Owner, _performerCache, @@ -254,6 +271,11 @@ namespace Content.Server.GameObjects.Components.Body private void SendNoUsefulWayToUseAnymorePopup() { + if (_bodyManagerComponentCache == null) + { + return; + } + _sharedNotifyManager.PopupMessage( _bodyManagerComponentCache.Owner, _performerCache, diff --git a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs index fcb1144b47..0b15560869 100644 --- a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs +++ b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs @@ -34,13 +34,11 @@ namespace Content.Server.GameObjects.Components.Buckle [RegisterComponent] public class BuckleComponent : SharedBuckleComponent, IInteractHand, IDragDrop { -#pragma warning disable 649 [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IServerNotifyManager _notifyManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; -#pragma warning restore 649 private int _size; diff --git a/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs b/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs index ca6445e5e6..658fcec180 100644 --- a/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs @@ -10,6 +10,7 @@ using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.IoC; +using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -19,21 +20,11 @@ namespace Content.Server.GameObjects.Components.Cargo [ComponentReference(typeof(IActivate))] public class CargoConsoleComponent : SharedCargoConsoleComponent, IActivate { -#pragma warning disable 649 [Dependency] private readonly ICargoOrderDataManager _cargoOrderDataManager = default!; -#pragma warning restore 649 [ViewVariables] public int Points = 1000; - private BoundUserInterface _userInterface = default!; - - [ViewVariables] - public GalacticMarketComponent Market { get; private set; } = default!; - - [ViewVariables] - public CargoOrderDatabaseComponent Orders { get; private set; } = default!; - private CargoBankAccount? _bankAccount; [ViewVariables] @@ -65,22 +56,49 @@ namespace Content.Server.GameObjects.Components.Cargo private bool _requestOnly = false; - private PowerReceiverComponent _powerReceiver = default!; - private bool Powered => _powerReceiver.Powered; + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; private CargoConsoleSystem _cargoConsoleSystem = default!; + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(CargoConsoleUiKey.Key, out var boundUi) + ? boundUi + : null; + public override void Initialize() { base.Initialize(); - Market = Owner.GetComponent(); - Orders = Owner.GetComponent(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(CargoConsoleUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - _powerReceiver = Owner.GetComponent(); + + if (!Owner.EnsureComponent(out GalacticMarketComponent _)) + { + Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} had no {nameof(GalacticMarketComponent)}"); + } + + if (!Owner.EnsureComponent(out CargoOrderDatabaseComponent _)) + { + Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} had no {nameof(GalacticMarketComponent)}"); + } + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } + _cargoConsoleSystem = EntitySystem.Get(); BankAccount = _cargoConsoleSystem.StationAccount; } + public override void OnRemove() + { + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } + + base.OnRemove(); + } + /// /// Reads data from YAML /// @@ -93,8 +111,13 @@ namespace Content.Server.GameObjects.Components.Cargo private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) { + if (!Owner.TryGetComponent(out CargoOrderDatabaseComponent? orders)) + { + return; + } + var message = serverMsg.Message; - if (!Orders.ConnectedToDatabase) + if (!orders.ConnectedToDatabase) return; if (!Powered) return; @@ -107,39 +130,39 @@ namespace Content.Server.GameObjects.Components.Cargo break; } - _cargoOrderDataManager.AddOrder(Orders.Database.Id, msg.Requester, msg.Reason, msg.ProductId, msg.Amount, _bankAccount.Id); + _cargoOrderDataManager.AddOrder(orders.Database.Id, msg.Requester, msg.Reason, msg.ProductId, msg.Amount, _bankAccount.Id); break; } case CargoConsoleRemoveOrderMessage msg: { - _cargoOrderDataManager.RemoveOrder(Orders.Database.Id, msg.OrderNumber); + _cargoOrderDataManager.RemoveOrder(orders.Database.Id, msg.OrderNumber); break; } case CargoConsoleApproveOrderMessage msg: { if (_requestOnly || - !Orders.Database.TryGetOrder(msg.OrderNumber, out var order) || + !orders.Database.TryGetOrder(msg.OrderNumber, out var order) || _bankAccount == null) { break; } _prototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product); - if (product == null) + if (product == null!) break; - var capacity = _cargoOrderDataManager.GetCapacity(Orders.Database.Id); + var capacity = _cargoOrderDataManager.GetCapacity(orders.Database.Id); if (capacity.CurrentCapacity == capacity.MaxCapacity) break; if (!_cargoConsoleSystem.ChangeBalance(_bankAccount.Id, (-product.PointCost) * order.Amount)) break; - _cargoOrderDataManager.ApproveOrder(Orders.Database.Id, msg.OrderNumber); + _cargoOrderDataManager.ApproveOrder(orders.Database.Id, msg.OrderNumber); UpdateUIState(); break; } case CargoConsoleShuttleMessage _: { - var approvedOrders = _cargoOrderDataManager.RemoveAndGetApprovedFrom(Orders.Database); - Orders.Database.ClearOrderCapacity(); + var approvedOrders = _cargoOrderDataManager.RemoveAndGetApprovedFrom(orders.Database); + orders.Database.ClearOrderCapacity(); // TODO replace with shuttle code // TEMPORARY loop for spawning stuff on top of console @@ -166,12 +189,12 @@ namespace Content.Server.GameObjects.Components.Cargo if (!Powered) return; - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } private void UpdateUIState() { - if (_bankAccount == null) + if (_bankAccount == null || !Owner.IsValid()) { return; } @@ -180,7 +203,7 @@ namespace Content.Server.GameObjects.Components.Cargo var name = _bankAccount.Name; var balance = _bankAccount.Balance; var capacity = _cargoOrderDataManager.GetCapacity(id); - _userInterface.SetState(new CargoConsoleInterfaceState(_requestOnly, id, name, balance, capacity)); + UserInterface?.SetState(new CargoConsoleInterfaceState(_requestOnly, id, name, balance, capacity)); } } } diff --git a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs index 156b9efefb..84cc2ca642 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; @@ -40,24 +42,26 @@ namespace Content.Server.GameObjects.Components.Chemistry [ComponentReference(typeof(IInteractUsing))] public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing, ISolutionChange { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; + [Dependency] private readonly ILocalizationManager _localizationManager = default!; - [ViewVariables] private BoundUserInterface _userInterface; - [ViewVariables] private ContainerSlot _beakerContainer; - [ViewVariables] private string _packPrototypeId; + [ViewVariables] private ContainerSlot _beakerContainer = default!; + [ViewVariables] private string _packPrototypeId = ""; [ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null; - [ViewVariables] private bool BufferModeTransfer = true; + [ViewVariables] private bool _bufferModeTransfer = true; - private PowerReceiverComponent _powerReceiver; - private bool Powered => _powerReceiver.Powered; + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; private readonly SolutionComponent BufferSolution = new SolutionComponent(); + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(ChemMasterUiKey.Key, out var boundUi) + ? boundUi + : null; /// /// Shows the serializer how to save/load this components yaml prototype. @@ -77,14 +81,19 @@ namespace Content.Server.GameObjects.Components.Chemistry public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(ChemMasterUiKey.Key); - _userInterface.OnReceiveMessage += OnUiReceiveMessage; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += OnUiReceiveMessage; + } _beakerContainer = ContainerManagerComponent.Ensure($"{Name}-reagentContainerContainer", Owner); - _powerReceiver = Owner.GetComponent(); - _powerReceiver.OnPowerStateChanged += OnPowerChanged; + + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) + { + receiver.OnPowerStateChanged += OnPowerChanged; + } //BufferSolution = Owner.BufferSolution BufferSolution.Solution = new Solution(); @@ -93,7 +102,7 @@ namespace Content.Server.GameObjects.Components.Chemistry UpdateUserInterface(); } - private void OnPowerChanged(object sender, PowerStateEventArgs e) + private void OnPowerChanged(object? sender, PowerStateEventArgs e) { UpdateUserInterface(); } @@ -105,6 +114,11 @@ namespace Content.Server.GameObjects.Components.Chemistry /// A user interface message from the client. private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) { + if (obj.Session.AttachedEntity == null) + { + return; + } + var msg = (UiActionMessage) obj.Message; var needsPower = msg.action switch { @@ -124,11 +138,11 @@ namespace Content.Server.GameObjects.Components.Chemistry TransferReagent(msg.id, msg.amount, msg.isBuffer); break; case UiAction.Transfer: - BufferModeTransfer = true; + _bufferModeTransfer = true; UpdateUserInterface(); break; case UiAction.Discard: - BufferModeTransfer = false; + _bufferModeTransfer = false; UpdateUserInterface(); break; case UiAction.CreatePills: @@ -147,7 +161,7 @@ namespace Content.Server.GameObjects.Components.Chemistry /// /// The player entity. /// Returns true if the entity can use the chem master, and false if it cannot. - private bool PlayerCanUseChemMaster(IEntity playerEntity, bool needsPower = true) + private bool PlayerCanUseChemMaster(IEntity? playerEntity, bool needsPower = true) { //Need player entity to check if they are still able to use the chem master if (playerEntity == null) @@ -172,18 +186,18 @@ namespace Content.Server.GameObjects.Components.Chemistry if (beaker == null) { return new ChemMasterBoundUserInterfaceState(Powered, false, ReagentUnit.New(0), ReagentUnit.New(0), - "", Owner.Name, null, BufferSolution.ReagentList.ToList(), BufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume); + "", Owner.Name, new List(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume); } var solution = beaker.GetComponent(); return new ChemMasterBoundUserInterfaceState(Powered, true, solution.CurrentVolume, solution.MaxVolume, - beaker.Name, Owner.Name, solution.ReagentList.ToList(), BufferSolution.ReagentList.ToList(), BufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume); + beaker.Name, Owner.Name, solution.ReagentList.ToList(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume); } private void UpdateUserInterface() { var state = GetUserInterfaceState(); - _userInterface.SetState(state); + UserInterface?.SetState(state); } /// @@ -207,7 +221,7 @@ namespace Content.Server.GameObjects.Components.Chemistry private void TransferReagent(string id, ReagentUnit amount, bool isBuffer) { - if (!HasBeaker && BufferModeTransfer) return; + if (!HasBeaker && _bufferModeTransfer) return; var beaker = _beakerContainer.ContainedEntity; var beakerSolution = beaker.GetComponent(); if (isBuffer) @@ -227,7 +241,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } BufferSolution.Solution.RemoveReagent(id, actualAmount); - if (BufferModeTransfer) + if (_bufferModeTransfer) { beakerSolution.Solution.AddReagent(id, actualAmount); } @@ -351,12 +365,12 @@ namespace Content.Server.GameObjects.Components.Chemistry /// Data relevant to the event such as the actor which triggered it. void IActivate.Activate(ActivateEventArgs args) { - if (!args.User.TryGetComponent(out IActorComponent actor)) + if (!args.User.TryGetComponent(out IActorComponent? actor)) { return; } - if (!args.User.TryGetComponent(out IHandsComponent hands)) + if (!args.User.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, _localizationManager.GetString("You have no hands.")); @@ -366,7 +380,7 @@ namespace Content.Server.GameObjects.Components.Chemistry var activeHandEntity = hands.GetActiveHand?.Owner; if (activeHandEntity == null) { - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } } @@ -379,13 +393,20 @@ namespace Content.Server.GameObjects.Components.Chemistry /// async Task IInteractUsing.InteractUsing(InteractUsingEventArgs args) { - if (!args.User.TryGetComponent(out IHandsComponent hands)) + if (!args.User.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, _localizationManager.GetString("You have no hands.")); return true; } + if (hands.GetActiveHand == null) + { + _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, + Loc.GetString("You have nothing on your hand.")); + return false; + } + var activeHandEntity = hands.GetActiveHand.Owner; if (activeHandEntity.TryGetComponent(out var solution)) { diff --git a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs index 03ac3855e7..a3a9959ca1 100644 --- a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using Content.Server.GameObjects.Components.Body.Circulatory; using Content.Server.Interfaces; using Content.Server.Utility; @@ -22,9 +23,7 @@ namespace Content.Server.GameObjects.Components.Chemistry [RegisterComponent] public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; /// /// Whether or not the injector is able to draw from containers or if it's a single use @@ -53,11 +52,6 @@ namespace Content.Server.GameObjects.Components.Chemistry /// [ViewVariables(VVAccess.ReadWrite)] private InjectorToggleMode _toggleState; - /// - /// Internal solution container - /// - [ViewVariables] - private SolutionComponent _internalContents; public override void ExposeData(ObjectSerializer serializer) { @@ -69,9 +63,15 @@ namespace Content.Server.GameObjects.Components.Chemistry protected override void Startup() { base.Startup(); - _internalContents = Owner.GetComponent(); - _internalContents.Capabilities |= SolutionCaps.Injector; - //Set _toggleState based on prototype + + Owner.EnsureComponent(); + + if (Owner.TryGetComponent(out SolutionComponent? solution)) + { + solution.Capabilities |= SolutionCaps.Injector; + } + + // Set _toggleState based on prototype _toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw; } @@ -114,7 +114,7 @@ namespace Content.Server.GameObjects.Components.Chemistry if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return; //Make sure we have the attacking entity - if (eventArgs.Target == null || !_internalContents.Injector) + if (eventArgs.Target == null || !Owner.TryGetComponent(out SolutionComponent? solution) || !solution.Injector) { return; } @@ -134,7 +134,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } else //Handle injecting into bloodstream { - if (targetEntity.TryGetComponent(out BloodstreamComponent bloodstream) && + if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) && _toggleState == InjectorToggleMode.Inject) { TryInjectIntoBloodstream(bloodstream, eventArgs.User); @@ -155,7 +155,8 @@ namespace Content.Server.GameObjects.Components.Chemistry private void TryInjectIntoBloodstream(BloodstreamComponent targetBloodstream, IEntity user) { - if (_internalContents.CurrentVolume == 0) + if (!Owner.TryGetComponent(out SolutionComponent? solution) || + solution.CurrentVolume == 0) { return; } @@ -170,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } //Move units from attackSolution to targetSolution - var removedSolution = _internalContents.SplitSolution(realTransferAmount); + var removedSolution = solution.SplitSolution(realTransferAmount); if (!targetBloodstream.TryTransferSolution(removedSolution)) { return; @@ -183,7 +184,8 @@ namespace Content.Server.GameObjects.Components.Chemistry private void TryInject(SolutionComponent targetSolution, IEntity user) { - if (_internalContents.CurrentVolume == 0) + if (!Owner.TryGetComponent(out SolutionComponent? solution) || + solution.CurrentVolume == 0) { return; } @@ -198,7 +200,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } //Move units from attackSolution to targetSolution - var removedSolution = _internalContents.SplitSolution(realTransferAmount); + var removedSolution = solution.SplitSolution(realTransferAmount); if (!targetSolution.TryAddSolution(removedSolution)) { return; @@ -211,7 +213,8 @@ namespace Content.Server.GameObjects.Components.Chemistry private void TryDraw(SolutionComponent targetSolution, IEntity user) { - if (_internalContents.EmptyVolume == 0) + if (!Owner.TryGetComponent(out SolutionComponent? solution) || + solution.EmptyVolume == 0) { return; } @@ -227,7 +230,7 @@ namespace Content.Server.GameObjects.Components.Chemistry //Move units from attackSolution to targetSolution var removedSolution = targetSolution.SplitSolution(realTransferAmount); - if (!_internalContents.TryAddSolution(removedSolution)) + if (!solution.TryAddSolution(removedSolution)) { return; } @@ -239,7 +242,12 @@ namespace Content.Server.GameObjects.Components.Chemistry public override ComponentState GetComponentState() { - return new InjectorComponentState(_internalContents.CurrentVolume, _internalContents.MaxVolume, _toggleState); + Owner.TryGetComponent(out SolutionComponent? solution); + + var currentVolume = solution?.CurrentVolume ?? ReagentUnit.Zero; + var maxVolume = solution?.MaxVolume ?? ReagentUnit.Zero; + + return new InjectorComponentState(currentVolume, maxVolume, _toggleState); } } } diff --git a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs index d32ae288ad..ae227b58b5 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Linq; using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; @@ -38,14 +39,11 @@ namespace Content.Server.GameObjects.Components.Chemistry [ComponentReference(typeof(IInteractUsing))] public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing, ISolutionChange { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; + [Dependency] private readonly ILocalizationManager _localizationManager = default!; - [ViewVariables] private BoundUserInterface _userInterface; - [ViewVariables] private ContainerSlot _beakerContainer; - [ViewVariables] private string _packPrototypeId; + [ViewVariables] private ContainerSlot _beakerContainer = default!; + [ViewVariables] private string _packPrototypeId = ""; [ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null; [ViewVariables] private ReagentUnit _dispenseAmount = ReagentUnit.New(10); @@ -53,9 +51,14 @@ namespace Content.Server.GameObjects.Components.Chemistry [ViewVariables] private SolutionComponent Solution => _beakerContainer.ContainedEntity.GetComponent(); - private PowerReceiverComponent _powerReceiver; - private bool Powered => _powerReceiver.Powered; + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(ReagentDispenserUiKey.Key, out var boundUi) + ? boundUi + : null; /// /// Shows the serializer how to save/load this components yaml prototype. @@ -75,14 +78,19 @@ namespace Content.Server.GameObjects.Components.Chemistry public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(ReagentDispenserUiKey.Key); - _userInterface.OnReceiveMessage += OnUiReceiveMessage; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += OnUiReceiveMessage; + } _beakerContainer = ContainerManagerComponent.Ensure($"{Name}-reagentContainerContainer", Owner); - _powerReceiver = Owner.GetComponent(); - _powerReceiver.OnPowerStateChanged += OnPowerChanged; + + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) + { + receiver.OnPowerStateChanged += OnPowerChanged; + } InitializeFromPrototype(); UpdateUserInterface(); @@ -108,7 +116,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } } - private void OnPowerChanged(object sender, PowerStateEventArgs e) + private void OnPowerChanged(object? sender, PowerStateEventArgs e) { UpdateUserInterface(); } @@ -120,6 +128,11 @@ namespace Content.Server.GameObjects.Components.Chemistry /// A user interface message from the client. private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) { + if (obj.Session.AttachedEntity == null) + { + return; + } + var msg = (UiButtonPressedMessage) obj.Message; var needsPower = msg.Button switch { @@ -175,7 +188,7 @@ namespace Content.Server.GameObjects.Components.Chemistry /// /// The player entity. /// Returns true if the entity can use the dispenser, and false if it cannot. - private bool PlayerCanUseDispenser(IEntity playerEntity, bool needsPower = true) + private bool PlayerCanUseDispenser(IEntity? playerEntity, bool needsPower = true) { //Need player entity to check if they are still able to use the dispenser if (playerEntity == null) @@ -211,7 +224,7 @@ namespace Content.Server.GameObjects.Components.Chemistry private void UpdateUserInterface() { var state = GetUserInterfaceState(); - _userInterface.SetState(state); + UserInterface?.SetState(state); } /// @@ -265,12 +278,12 @@ namespace Content.Server.GameObjects.Components.Chemistry /// Data relevant to the event such as the actor which triggered it. void IActivate.Activate(ActivateEventArgs args) { - if (!args.User.TryGetComponent(out IActorComponent actor)) + if (!args.User.TryGetComponent(out IActorComponent? actor)) { return; } - if (!args.User.TryGetComponent(out IHandsComponent hands)) + if (!args.User.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, _localizationManager.GetString("You have no hands.")); @@ -280,7 +293,7 @@ namespace Content.Server.GameObjects.Components.Chemistry var activeHandEntity = hands.GetActiveHand?.Owner; if (activeHandEntity == null) { - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } } @@ -293,13 +306,20 @@ namespace Content.Server.GameObjects.Components.Chemistry /// async Task IInteractUsing.InteractUsing(InteractUsingEventArgs args) { - if (!args.User.TryGetComponent(out IHandsComponent hands)) + if (!args.User.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, _localizationManager.GetString("You have no hands.")); return true; } + if (hands.GetActiveHand == null) + { + _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, + Loc.GetString("You have nothing on your hand.")); + return false; + } + var activeHandEntity = hands.GetActiveHand.Owner; if (activeHandEntity.TryGetComponent(out var solution)) { diff --git a/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs b/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs index 94d18c734e..571e78f64f 100644 --- a/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.EntitySystems; +#nullable enable +using Content.Server.GameObjects.EntitySystems; using Content.Shared.Chemistry; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; @@ -11,28 +12,27 @@ namespace Content.Server.GameObjects.Components.Chemistry [RegisterComponent] public class TransformableContainerComponent : Component, ISolutionChange { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public override string Name => "TransformableContainer"; - private bool _transformed = false; - public bool Transformed { get => _transformed; } + private SpriteSpecifier? _initialSprite; + private string _initialName = default!; + private string _initialDescription = default!; + private ReagentPrototype? _currentReagent; - private SpriteSpecifier _initialSprite; - private string _initialName; - private string _initialDescription; - private SpriteComponent _sprite; - - private ReagentPrototype _currentReagent; + public bool Transformed { get; private set; } public override void Initialize() { base.Initialize(); - _sprite = Owner.GetComponent(); - _initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(_sprite.BaseRSIPath), "icon"); + if (Owner.TryGetComponent(out SpriteComponent? sprite) && + sprite.BaseRSIPath != null) + { + _initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(sprite.BaseRSIPath), "icon"); + } + _initialName = Owner.Name; _initialDescription = Owner.Description; } @@ -40,14 +40,20 @@ namespace Content.Server.GameObjects.Components.Chemistry protected override void Startup() { base.Startup(); - Owner.GetComponent().Capabilities |= SolutionCaps.FitsInDispenser;; + Owner.GetComponent().Capabilities |= SolutionCaps.FitsInDispenser; } public void CancelTransformation() { _currentReagent = null; - _transformed = false; - _sprite.LayerSetSprite(0, _initialSprite); + Transformed = false; + + if (Owner.TryGetComponent(out SpriteComponent? sprite) && + _initialSprite != null) + { + sprite.LayerSetSprite(0, _initialSprite); + } + Owner.Name = _initialName; Owner.Description = _initialDescription; } @@ -76,11 +82,16 @@ namespace Content.Server.GameObjects.Components.Chemistry !string.IsNullOrWhiteSpace(proto.SpriteReplacementPath)) { var spriteSpec = new SpriteSpecifier.Rsi(new ResourcePath("Objects/Drinks/" + proto.SpriteReplacementPath),"icon"); - _sprite.LayerSetSprite(0, spriteSpec); + + if (Owner.TryGetComponent(out SpriteComponent? sprite)) + { + sprite?.LayerSetSprite(0, spriteSpec); + } + Owner.Name = proto.Name + " glass"; Owner.Description = proto.Description; _currentReagent = proto; - _transformed = true; + Transformed = true; } } } diff --git a/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs index d982b1fb06..4a26ee6a4a 100644 --- a/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Power.ApcNetComponents; +#nullable enable +using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects.Components.Command; using Content.Shared.Interfaces.GameObjects.Components; @@ -8,6 +9,7 @@ using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Command { @@ -15,22 +17,27 @@ namespace Content.Server.GameObjects.Components.Command [ComponentReference(typeof(IActivate))] public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IActivate { -#pragma warning disable 649 - [Dependency] private IEntitySystemManager _entitySystemManager; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; - private BoundUserInterface _userInterface; - private PowerReceiverComponent _powerReceiver; - private bool Powered => _powerReceiver.Powered; private RoundEndSystem RoundEndSystem => _entitySystemManager.GetEntitySystem(); + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(CommunicationsConsoleUiKey.Key, out var boundUi) + ? boundUi + : null; + public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(CommunicationsConsoleUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - _powerReceiver = Owner.GetComponent(); + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } RoundEndSystem.OnRoundEndCountdownStarted += UpdateBoundInterface; RoundEndSystem.OnRoundEndCountdownCancelled += UpdateBoundInterface; @@ -39,7 +46,7 @@ namespace Content.Server.GameObjects.Components.Command private void UpdateBoundInterface() { - _userInterface.SetState(new CommunicationsConsoleInterfaceState(RoundEndSystem.ExpectedCountdownEnd)); + UserInterface?.SetState(new CommunicationsConsoleInterfaceState(RoundEndSystem.ExpectedCountdownEnd)); } private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj) @@ -58,12 +65,12 @@ namespace Content.Server.GameObjects.Components.Command public void OpenUserInterface(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } void IActivate.Activate(ActivateEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return; if (!Powered) diff --git a/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs b/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs index 689e3f8bea..01a29b4a05 100644 --- a/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs +++ b/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs @@ -28,10 +28,8 @@ namespace Content.Server.GameObjects.Components.Conveyor [RegisterComponent] public class ConveyorComponent : Component, IInteractUsing { -#pragma warning disable 649 [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IRobustRandom _random = default!; -#pragma warning restore 649 public override string Name => "Conveyor"; diff --git a/Content.Server/GameObjects/Components/Damage/DamageOnToolInteractComponent.cs b/Content.Server/GameObjects/Components/Damage/DamageOnToolInteractComponent.cs index 72043c215a..6a8c680fd6 100644 --- a/Content.Server/GameObjects/Components/Damage/DamageOnToolInteractComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/DamageOnToolInteractComponent.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Content.Server.GameObjects.Components.Interactable; using Content.Shared.Damage; +using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; @@ -27,12 +28,6 @@ namespace Content.Server.GameObjects.Components.Damage serializer.DataField(ref _tools, "tools", new List()); } - public override void Initialize() - { - base.Initialize(); - Owner.EnsureComponent(); - } - public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (eventArgs.Using.TryGetComponent(out var tool)) @@ -56,7 +51,7 @@ namespace Content.Server.GameObjects.Components.Damage protected bool CallDamage(InteractUsingEventArgs eventArgs, ToolComponent tool) { - if (eventArgs.Target.TryGetComponent(out var damageable)) + if (eventArgs.Target.TryGetComponent(out var damageable)) { damageable.ChangeDamage(tool.HasQuality(ToolQuality.Welding) ? DamageType.Heat diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs index 1d3662a85c..9c85698e77 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.Interfaces; +#nullable enable +using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; @@ -25,22 +26,24 @@ namespace Content.Server.GameObjects.Components.Disposal [ComponentReference(typeof(IDisposalTubeComponent))] public class DisposalRouterComponent : DisposalJunctionComponent, IActivate { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; public override string Name => "DisposalRouter"; [ViewVariables] - private BoundUserInterface _userInterface; - - [ViewVariables] - private HashSet _tags; + private readonly HashSet _tags = new HashSet(); [ViewVariables] public bool Anchored => - !Owner.TryGetComponent(out CollidableComponent collidable) || + !Owner.TryGetComponent(out ICollidableComponent? collidable) || collidable.Anchored; + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(DisposalRouterUiKey.Key, out var boundUi) + ? boundUi + : null; + public override Direction NextDirection(DisposalHolderComponent holder) { var directions = ConnectableDirections(); @@ -53,15 +56,14 @@ namespace Content.Server.GameObjects.Components.Disposal return Owner.Transform.LocalRotation.GetDir(); } - public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(DisposalRouterUiKey.Key); - _userInterface.OnReceiveMessage += OnUiReceiveMessage; - _tags = new HashSet(); + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += OnUiReceiveMessage; + } UpdateUserInterface(); } @@ -73,6 +75,11 @@ namespace Content.Server.GameObjects.Components.Disposal /// A user interface message from the client. private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) { + if (obj.Session.AttachedEntity == null) + { + return; + } + var msg = (UiActionMessage) obj.Message; if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity)) @@ -112,10 +119,10 @@ namespace Content.Server.GameObjects.Components.Disposal /// /// Gets component data to be used to update the user interface client-side. /// - /// Returns a + /// Returns a private DisposalRouterUserInterfaceState GetUserInterfaceState() { - if(_tags == null || _tags.Count <= 0) + if(_tags.Count <= 0) { return new DisposalRouterUserInterfaceState(""); } @@ -136,7 +143,7 @@ namespace Content.Server.GameObjects.Components.Disposal private void UpdateUserInterface() { var state = GetUserInterfaceState(); - _userInterface.SetState(state); + UserInterface?.SetState(state); } private void ClickSound() @@ -150,12 +157,12 @@ namespace Content.Server.GameObjects.Components.Disposal /// Data relevant to the event such as the actor which triggered it. void IActivate.Activate(ActivateEventArgs args) { - if (!args.User.TryGetComponent(out IActorComponent actor)) + if (!args.User.TryGetComponent(out IActorComponent? actor)) { return; } - if (!args.User.TryGetComponent(out IHandsComponent hands)) + if (!args.User.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Loc.GetString("You have no hands.")); @@ -166,13 +173,13 @@ namespace Content.Server.GameObjects.Components.Disposal if (activeHandEntity == null) { UpdateUserInterface(); - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } } public override void OnRemove() { - _userInterface.CloseAll(); + UserInterface?.CloseAll(); base.OnRemove(); } } diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs index 3c1ab7bf81..34bb94e19b 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.Interfaces; +#nullable enable +using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; @@ -23,35 +24,38 @@ namespace Content.Server.GameObjects.Components.Disposal [ComponentReference(typeof(IDisposalTubeComponent))] public class DisposalTaggerComponent : DisposalTransitComponent, IActivate { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; public override string Name => "DisposalTagger"; - [ViewVariables] - private BoundUserInterface _userInterface; - [ViewVariables(VVAccess.ReadWrite)] private string _tag = ""; [ViewVariables] public bool Anchored => - !Owner.TryGetComponent(out CollidableComponent collidable) || + !Owner.TryGetComponent(out CollidableComponent? collidable) || collidable.Anchored; + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(DisposalTaggerUiKey.Key, out var boundUi) + ? boundUi + : null; + public override Direction NextDirection(DisposalHolderComponent holder) { holder.Tags.Add(_tag); return base.NextDirection(holder); } - public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(DisposalTaggerUiKey.Key); - _userInterface.OnReceiveMessage += OnUiReceiveMessage; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += OnUiReceiveMessage; + } UpdateUserInterface(); } @@ -70,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Disposal //Check for correct message and ignore maleformed strings if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tag)) - { + { _tag = msg.Tag; ClickSound(); } @@ -81,7 +85,7 @@ namespace Content.Server.GameObjects.Components.Disposal /// /// The player entity. /// Returns true if the entity can use the configuration interface, and false if it cannot. - private bool PlayerCanUseDisposalTagger(IEntity playerEntity) + private bool PlayerCanUseDisposalTagger(IEntity? playerEntity) { //Need player entity to check if they are still able to use the configuration interface if (playerEntity == null) @@ -98,7 +102,7 @@ namespace Content.Server.GameObjects.Components.Disposal /// /// Gets component data to be used to update the user interface client-side. /// - /// Returns a + /// Returns a private DisposalTaggerUserInterfaceState GetUserInterfaceState() { return new DisposalTaggerUserInterfaceState(_tag); @@ -107,7 +111,7 @@ namespace Content.Server.GameObjects.Components.Disposal private void UpdateUserInterface() { var state = GetUserInterfaceState(); - _userInterface.SetState(state); + UserInterface?.SetState(state); } private void ClickSound() @@ -121,12 +125,12 @@ namespace Content.Server.GameObjects.Components.Disposal /// Data relevant to the event such as the actor which triggered it. void IActivate.Activate(ActivateEventArgs args) { - if (!args.User.TryGetComponent(out IActorComponent actor)) + if (!args.User.TryGetComponent(out IActorComponent? actor)) { return; } - if (!args.User.TryGetComponent(out IHandsComponent hands)) + if (!args.User.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Loc.GetString("You have no hands.")); @@ -137,14 +141,14 @@ namespace Content.Server.GameObjects.Components.Disposal if (activeHandEntity == null) { UpdateUserInterface(); - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } } public override void OnRemove() { base.OnRemove(); - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } } } diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs index 2e420ef91e..24ca2e6ea5 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -39,10 +39,8 @@ namespace Content.Server.GameObjects.Components.Disposal [ComponentReference(typeof(IInteractUsing))] public class DisposalUnitComponent : SharedDisposalUnitComponent, IInteractHand, IInteractUsing, IDragDropOn { -#pragma warning disable 649 [Dependency] private readonly IServerNotifyManager _notifyManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; -#pragma warning restore 649 public override string Name => "DisposalUnit"; @@ -81,9 +79,6 @@ namespace Content.Server.GameObjects.Components.Disposal [ViewVariables] public IReadOnlyList ContainedEntities => _container.ContainedEntities; - [ViewVariables] - private BoundUserInterface _userInterface = default!; - [ViewVariables] public bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || @@ -115,6 +110,13 @@ namespace Content.Server.GameObjects.Components.Disposal } } + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(DisposalUnitUiKey.Key, out var boundUi) + ? boundUi + : null; + public bool CanInsert(IEntity entity) { if (!Anchored) @@ -161,7 +163,7 @@ namespace Content.Server.GameObjects.Components.Disposal if (entity.TryGetComponent(out IActorComponent? actor)) { - _userInterface.Close(actor.playerSession); + UserInterface?.Close(actor.playerSession); } UpdateVisualState(); @@ -291,10 +293,10 @@ namespace Content.Server.GameObjects.Components.Disposal private void UpdateInterface() { var state = GetInterfaceState(); - _userInterface.SetState(state); + UserInterface?.SetState(state); } - private bool PlayerCanUse(IEntity player) + private bool PlayerCanUse(IEntity? player) { if (player == null) { @@ -472,9 +474,11 @@ namespace Content.Server.GameObjects.Components.Disposal base.Initialize(); _container = ContainerManagerComponent.Ensure(Name, Owner); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(DisposalUnitUiKey.Key); - _userInterface.OnReceiveMessage += OnUiReceiveMessage; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += OnUiReceiveMessage; + } UpdateInterface(); } @@ -513,7 +517,7 @@ namespace Content.Server.GameObjects.Components.Disposal _container.ForceRemove(entity); } - _userInterface.CloseAll(); + UserInterface?.CloseAll(); _automaticEngageToken?.Cancel(); _automaticEngageToken = null; @@ -571,7 +575,7 @@ namespace Content.Server.GameObjects.Components.Disposal return false; } - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); return true; } diff --git a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs index 1bc53f6ab6..e33ab84758 100644 --- a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Threading; using System.Threading.Tasks; using Content.Server.GameObjects.Components.Interactable; @@ -34,10 +35,7 @@ namespace Content.Server.GameObjects.Components.Doors /// private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0); - private PowerReceiverComponent _powerReceiver; - private WiresComponent _wires; - - private CancellationTokenSource _powerWiresPulsedTimerCancel; + private CancellationTokenSource _powerWiresPulsedTimerCancel = new CancellationTokenSource(); private bool _powerWiresPulsed; @@ -89,13 +87,15 @@ namespace Content.Server.GameObjects.Components.Doors private void UpdateWiresStatus() { + WiresComponent? wires; var powerLight = new StatusLightData(Color.Yellow, StatusLightState.On, "POWR"); if (PowerWiresPulsed) { powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR"); } - else if (_wires.IsWireCut(Wires.MainPower) && - _wires.IsWireCut(Wires.BackupPower)) + else if (Owner.TryGetComponent(out wires) && + wires.IsWireCut(Wires.MainPower) && + wires.IsWireCut(Wires.BackupPower)) { powerLight = new StatusLightData(Color.Red, StatusLightState.On, "POWR"); } @@ -114,12 +114,17 @@ namespace Content.Server.GameObjects.Components.Doors var safetyStatus = new StatusLightData(Color.Red, Safety ? StatusLightState.On : StatusLightState.Off, "SAFE"); - _wires.SetStatus(AirlockWireStatus.PowerIndicator, powerLight); - _wires.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus); - _wires.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus); - _wires.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT")); - _wires.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus); - _wires.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus); + if (!Owner.TryGetComponent(out wires)) + { + return; + } + + wires.SetStatus(AirlockWireStatus.PowerIndicator, powerLight); + wires.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus); + wires.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus); + wires.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT")); + wires.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus); + wires.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus); /* _wires.SetStatus(6, powerLight); _wires.SetStatus(7, powerLight); @@ -131,14 +136,30 @@ namespace Content.Server.GameObjects.Components.Doors private void UpdatePowerCutStatus() { - _powerReceiver.PowerDisabled = PowerWiresPulsed || - _wires.IsWireCut(Wires.MainPower) || - _wires.IsWireCut(Wires.BackupPower); + if (!Owner.TryGetComponent(out PowerReceiverComponent? receiver)) + { + return; + } + + if (PowerWiresPulsed) + { + receiver.PowerDisabled = true; + return; + } + + if (!Owner.TryGetComponent(out WiresComponent? wires)) + { + return; + } + + receiver.PowerDisabled = + wires.IsWireCut(Wires.MainPower) || + wires.IsWireCut(Wires.BackupPower); } private void UpdateBoltLightStatus() { - if (Owner.TryGetComponent(out AppearanceComponent appearance)) + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) { appearance.SetData(DoorVisuals.BoltLights, BoltLightsVisible); } @@ -150,7 +171,10 @@ namespace Content.Server.GameObjects.Components.Doors { base.State = value; // Only show the maintenance panel if the airlock is closed - _wires.IsPanelVisible = value != DoorState.Open; + if (Owner.TryGetComponent(out WiresComponent? wires)) + { + wires.IsPanelVisible = value != DoorState.Open; + } // If the door is closed, we should look if the bolt was locked while closing UpdateBoltLightStatus(); } @@ -159,29 +183,32 @@ namespace Content.Server.GameObjects.Components.Doors public override void Initialize() { base.Initialize(); - _powerReceiver = Owner.GetComponent(); - _wires = Owner.GetComponent(); - _powerReceiver.OnPowerStateChanged += PowerDeviceOnOnPowerStateChanged; - if (Owner.TryGetComponent(out AppearanceComponent appearance)) + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) { - appearance.SetData(DoorVisuals.Powered, _powerReceiver.Powered); + receiver.OnPowerStateChanged += PowerDeviceOnOnPowerStateChanged; + + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + + appearance.SetData(DoorVisuals.Powered, receiver.Powered); + } } } public override void OnRemove() { - if (Owner.TryGetComponent(out _powerReceiver)) + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) { - _powerReceiver.OnPowerStateChanged -= PowerDeviceOnOnPowerStateChanged; + receiver.OnPowerStateChanged -= PowerDeviceOnOnPowerStateChanged; } base.OnRemove(); } - private void PowerDeviceOnOnPowerStateChanged(object sender, PowerStateEventArgs e) + private void PowerDeviceOnOnPowerStateChanged(object? sender, PowerStateEventArgs e) { - if (Owner.TryGetComponent(out AppearanceComponent appearance)) + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) { appearance.SetData(DoorVisuals.Powered, e.Powered); } @@ -192,11 +219,12 @@ namespace Content.Server.GameObjects.Components.Doors protected override void ActivateImpl(ActivateEventArgs args) { - if (_wires.IsPanelOpen) + if (Owner.TryGetComponent(out WiresComponent? wires) && + wires.IsPanelOpen) { - if (args.User.TryGetComponent(out IActorComponent actor)) + if (args.User.TryGetComponent(out IActorComponent? actor)) { - _wires.OpenInterface(actor.playerSession); + wires.OpenInterface(actor.playerSession); } } else @@ -276,8 +304,7 @@ namespace Content.Server.GameObjects.Components.Doors case Wires.MainPower: case Wires.BackupPower: PowerWiresPulsed = true; - _powerWiresPulsedTimerCancel?.Cancel(); - _powerWiresPulsedTimerCancel = new CancellationTokenSource(); + _powerWiresPulsedTimerCancel.Cancel(); Timer.Spawn(PowerWiresTimeout, () => PowerWiresPulsed = false, _powerWiresPulsedTimerCancel.Token); @@ -381,7 +408,8 @@ namespace Content.Server.GameObjects.Components.Doors private bool IsPowered() { - return _powerReceiver.Powered; + return !Owner.TryGetComponent(out PowerReceiverComponent? receiver) + || receiver.Powered; } public async Task InteractUsing(InteractUsingEventArgs eventArgs) @@ -392,11 +420,12 @@ namespace Content.Server.GameObjects.Components.Doors if (tool.HasQuality(ToolQuality.Cutting) || tool.HasQuality(ToolQuality.Multitool)) { - if (_wires.IsPanelOpen) + if (Owner.TryGetComponent(out WiresComponent? wires) + && wires.IsPanelOpen) { - if (eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (eventArgs.User.TryGetComponent(out IActorComponent? actor)) { - _wires.OpenInterface(actor.playerSession); + wires.OpenInterface(actor.playerSession); return true; } } diff --git a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs index 8df3c2601c..d4579037db 100644 --- a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Linq; using System.Threading; using Content.Server.GameObjects.Components.Access; @@ -44,10 +45,7 @@ namespace Content.Server.GameObjects.Components.Doors protected const float AutoCloseDelay = 5; protected float CloseSpeed = AutoCloseDelay; - private AirtightComponent airtightComponent; - private ICollidableComponent _collidableComponent; - private AppearanceComponent _appearance; - private CancellationTokenSource _cancellationTokenSource; + private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private static readonly TimeSpan CloseTimeOne = TimeSpan.FromSeconds(0.3f); private static readonly TimeSpan CloseTimeTwo = TimeSpan.FromSeconds(0.9f); @@ -68,21 +66,9 @@ namespace Content.Server.GameObjects.Components.Doors serializer.DataField(ref _occludes, "occludes", true); } - public override void Initialize() - { - base.Initialize(); - - airtightComponent = Owner.GetComponent(); - _collidableComponent = Owner.GetComponent(); - _appearance = Owner.GetComponent(); - _cancellationTokenSource = new CancellationTokenSource(); - } - public override void OnRemove() { _cancellationTokenSource?.Cancel(); - _collidableComponent = null; - _appearance = null; base.OnRemove(); } @@ -104,7 +90,6 @@ namespace Content.Server.GameObjects.Components.Doors ActivateImpl(eventArgs); } - void ICollideBehavior.CollideWith(IEntity entity) { if (State != DoorState.Closed) @@ -135,8 +120,10 @@ namespace Content.Server.GameObjects.Components.Doors private void SetAppearance(DoorVisualState state) { - if (_appearance != null || Owner.TryGetComponent(out _appearance)) - _appearance.SetData(DoorVisuals.VisualState, state); + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(DoorVisuals.VisualState, state); + } } public virtual bool CanOpen() @@ -147,7 +134,7 @@ namespace Content.Server.GameObjects.Components.Doors public bool CanOpen(IEntity user) { if (!CanOpen()) return false; - if (!Owner.TryGetComponent(out AccessReader accessReader)) + if (!Owner.TryGetComponent(out AccessReader? accessReader)) { return true; } @@ -165,7 +152,7 @@ namespace Content.Server.GameObjects.Components.Doors Open(); - if (user.TryGetComponent(out HandsComponent hands) && hands.Count == 0) + if (user.TryGetComponent(out HandsComponent? hands) && hands.Count == 0) { EntitySystem.Get().PlayFromEntity("/Audio/Effects/bang.ogg", Owner, AudioParams.Default.WithVolume(-2)); @@ -181,15 +168,22 @@ namespace Content.Server.GameObjects.Components.Doors State = DoorState.Opening; SetAppearance(DoorVisualState.Opening); - if (_occludes && Owner.TryGetComponent(out OccluderComponent occluder)) + if (_occludes && Owner.TryGetComponent(out OccluderComponent? occluder)) { occluder.Enabled = false; } Timer.Spawn(OpenTimeOne, async () => { - airtightComponent.AirBlocked = false; - _collidableComponent.Hard = false; + if (Owner.TryGetComponent(out AirtightComponent? airtight)) + { + airtight.AirBlocked = false; + } + + if (Owner.TryGetComponent(out ICollidableComponent? collidable)) + { + collidable.Hard = false; + } await Timer.Delay(OpenTimeTwo, _cancellationTokenSource.Token); @@ -208,7 +202,7 @@ namespace Content.Server.GameObjects.Components.Doors public bool CanClose(IEntity user) { if (!CanClose()) return false; - if (!Owner.TryGetComponent(out AccessReader accessReader)) + if (!Owner.TryGetComponent(out AccessReader? accessReader)) { return true; } @@ -229,18 +223,22 @@ namespace Content.Server.GameObjects.Components.Doors private void CheckCrush() { + if (!Owner.TryGetComponent(out ICollidableComponent? body)) + { + return; + } + // Check if collides with something - var collidesWith = _collidableComponent.GetCollidingEntities(Vector2.Zero, false); + var collidesWith = body.GetCollidingEntities(Vector2.Zero, false); if (collidesWith.Count() != 0) { // Crush bool hitSomeone = false; foreach (var e in collidesWith) { - if (!e.TryGetComponent(out StunnableComponent stun) - || !e.TryGetComponent(out IDamageableComponent damage) - || !e.TryGetComponent(out ICollidableComponent otherBody) - || !Owner.TryGetComponent(out ICollidableComponent body)) + if (!e.TryGetComponent(out StunnableComponent? stun) + || !e.TryGetComponent(out IDamageableComponent? damage) + || !e.TryGetComponent(out ICollidableComponent? otherBody)) continue; var percentage = otherBody.WorldAABB.IntersectPercentage(body.WorldAABB); @@ -264,7 +262,8 @@ namespace Content.Server.GameObjects.Components.Doors public bool Close() { bool shouldCheckCrush = false; - if (_collidableComponent.IsColliding(Vector2.Zero, false)) + + if (Owner.TryGetComponent(out ICollidableComponent? collidable) && collidable.IsColliding(Vector2.Zero, false)) { if (Safety) return false; @@ -276,7 +275,7 @@ namespace Content.Server.GameObjects.Components.Doors State = DoorState.Closing; OpenTimeCounter = 0; SetAppearance(DoorVisualState.Closing); - if (_occludes && Owner.TryGetComponent(out OccluderComponent occluder)) + if (_occludes && Owner.TryGetComponent(out OccluderComponent? occluder)) { occluder.Enabled = true; } @@ -288,8 +287,15 @@ namespace Content.Server.GameObjects.Components.Doors CheckCrush(); } - airtightComponent.AirBlocked = true; - _collidableComponent.Hard = true; + if (Owner.TryGetComponent(out AirtightComponent? airtight)) + { + airtight.AirBlocked = true; + } + + if (Owner.TryGetComponent(out ICollidableComponent? body)) + { + body.Hard = true; + } await Timer.Delay(CloseTimeTwo, _cancellationTokenSource.Token); diff --git a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs index 11c3281a94..4f9afed4af 100644 --- a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Threading.Tasks; using Content.Server.GameObjects.Components.Chemistry; using Content.Shared.Chemistry; @@ -19,23 +20,27 @@ namespace Content.Server.GameObjects.Components.Fluids [RegisterComponent] public class BucketComponent : Component, IInteractUsing { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly ILocalizationManager _localizationManager = default!; public override string Name => "Bucket"; public ReagentUnit MaxVolume { - get => _contents.MaxVolume; - set => _contents.MaxVolume = value; + get => Owner.TryGetComponent(out SolutionComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero; + set + { + if (Owner.TryGetComponent(out SolutionComponent? solution)) + { + solution.MaxVolume = value; + } + } } - public ReagentUnit CurrentVolume => _contents.CurrentVolume; + public ReagentUnit CurrentVolume => Owner.TryGetComponent(out SolutionComponent? solution) + ? solution.CurrentVolume + : ReagentUnit.Zero; - private SolutionComponent _contents; - - private string _sound; + private string? _sound; /// public override void ExposeData(ObjectSerializer serializer) @@ -47,15 +52,20 @@ namespace Content.Server.GameObjects.Components.Fluids public override void Initialize() { base.Initialize(); - _contents = Owner.GetComponent(); + Owner.EnsureComponent(); } private bool TryGiveToMop(MopComponent mopComponent) { + if (!Owner.TryGetComponent(out SolutionComponent? contents)) + { + return false; + } + // Let's fill 'er up // If this is called the mop should be empty but just in case we'll do Max - Current var transferAmount = ReagentUnit.Min(mopComponent.MaxVolume - mopComponent.CurrentVolume, CurrentVolume); - var solution = _contents.SplitSolution(transferAmount); + var solution = contents.SplitSolution(transferAmount); if (!mopComponent.Contents.TryAddSolution(solution) || mopComponent.CurrentVolume == 0) { return false; @@ -73,7 +83,12 @@ namespace Content.Server.GameObjects.Components.Fluids public async Task InteractUsing(InteractUsingEventArgs eventArgs) { - if (!eventArgs.Using.TryGetComponent(out MopComponent mopComponent)) + if (!Owner.TryGetComponent(out SolutionComponent? contents)) + { + return false; + } + + if (!eventArgs.Using.TryGetComponent(out MopComponent? mopComponent)) { return false; } @@ -97,7 +112,7 @@ namespace Content.Server.GameObjects.Components.Fluids } var solution = mopComponent.Contents.SplitSolution(transferAmount); - if (!_contents.TryAddSolution(solution)) + if (!contents.TryAddSolution(solution)) { //This really shouldn't happen throw new InvalidOperationException(); diff --git a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs index 598ee20754..cc598e7455 100644 --- a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs @@ -126,18 +126,25 @@ namespace Content.Server.GameObjects.Components.Fluids _contents.Initialize(); } - _snapGrid = Owner.GetComponent(); + _snapGrid = Owner.EnsureComponent(); // Smaller than 1m^3 for now but realistically this shouldn't be hit MaxVolume = ReagentUnit.New(1000); // Random sprite state set server-side so it's consistent across all clients - _spriteComponent = Owner.GetComponent(); + _spriteComponent = Owner.EnsureComponent(); + var robustRandom = IoCManager.Resolve(); var randomVariant = robustRandom.Next(0, _spriteVariants - 1); - var baseName = new ResourcePath(_spriteComponent.BaseRSIPath).FilenameWithoutExtension; - _spriteComponent.LayerSetState(0, $"{baseName}-{randomVariant}"); // TODO: Remove hardcode + if (_spriteComponent.BaseRSIPath != null) + { + var baseName = new ResourcePath(_spriteComponent.BaseRSIPath).FilenameWithoutExtension; + + _spriteComponent.LayerSetState(0, $"{baseName}-{randomVariant}"); // TODO: Remove hardcode + + } + // UpdateAppearance should get called soon after this so shouldn't need to call Dirty() here UpdateStatus(); diff --git a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs index ffb9cbdaa0..d1e9c6f098 100644 --- a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs @@ -16,10 +16,9 @@ namespace Content.Server.GameObjects.Components.Fluids [RegisterComponent] class SprayComponent : Component, IAfterInteract { -#pragma warning disable 649 [Dependency] private readonly IServerNotifyManager _notifyManager = default!; [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; -#pragma warning restore 649 + public override string Name => "Spray"; private ReagentUnit _transferAmount; diff --git a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs index 69fe0be734..ade7beee0d 100644 --- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -37,9 +37,7 @@ namespace Content.Server.GameObjects.Components.GUI [ComponentReference(typeof(ISharedHandsComponent))] public class HandsComponent : SharedHandsComponent, IHandsComponent, IBodyPartAdded, IBodyPartRemoved { -#pragma warning disable 649 [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; -#pragma warning restore 649 private string? _activeHand; private uint _nextHand; diff --git a/Content.Server/GameObjects/Components/GUI/HumanInventoryControllerComponent.cs b/Content.Server/GameObjects/Components/GUI/HumanInventoryControllerComponent.cs index 80eb1e9d75..9a72da1520 100644 --- a/Content.Server/GameObjects/Components/GUI/HumanInventoryControllerComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HumanInventoryControllerComponent.cs @@ -21,7 +21,7 @@ namespace Content.Server.GameObjects.Components.GUI { base.Initialize(); - _inventory = Owner.GetComponent(); + _inventory = Owner.EnsureComponent(); } bool IInventoryController.CanEquip(Slots slot, IEntity entity, bool flagsCheck, out string reason) diff --git a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs index 0363fee424..38270dcf46 100644 --- a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs @@ -1,11 +1,10 @@ -using System; +#nullable enable using System.Collections.Generic; using System.Threading; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Server.Interfaces; using Content.Shared.GameObjects.Components.GUI; -using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects.Components.UserInterface; @@ -16,7 +15,6 @@ using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Log; using Robust.Shared.ViewVariables; using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; @@ -25,27 +23,33 @@ namespace Content.Server.GameObjects.Components.GUI [RegisterComponent] public sealed class StrippableComponent : SharedStrippableComponent, IDragDrop { - [Dependency] private IServerNotifyManager _notifyManager = default!; + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; public const float StripDelay = 2f; [ViewVariables] - private BoundUserInterface _userInterface; - - private InventoryComponent _inventoryComponent; - private HandsComponent _handsComponent; + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(StrippingUiKey.Key, out var boundUi) + ? boundUi + : null; public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(StrippingUiKey.Key); - _userInterface.OnReceiveMessage += HandleUserInterfaceMessage; + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += HandleUserInterfaceMessage; + } - _inventoryComponent = Owner.GetComponent(); - _handsComponent = Owner.GetComponent(); + Owner.EnsureComponent(); + Owner.EnsureComponent(); - _inventoryComponent.OnItemChanged += UpdateSubscribed; + if (Owner.TryGetComponent(out InventoryComponent? inventory)) + { + inventory.OnItemChanged += UpdateSubscribed; + } // Initial update. UpdateSubscribed(); @@ -53,10 +57,15 @@ namespace Content.Server.GameObjects.Components.GUI private void UpdateSubscribed() { + if (UserInterface == null) + { + return; + } + var inventory = GetInventorySlots(); var hands = GetHandSlots(); - _userInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands)); + UserInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands)); } public bool CanDragDrop(DragDropEventArgs eventArgs) @@ -67,7 +76,7 @@ namespace Content.Server.GameObjects.Components.GUI public bool DragDrop(DragDropEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) return false; + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false; OpenUserInterface(actor.playerSession); return true; @@ -77,9 +86,14 @@ namespace Content.Server.GameObjects.Components.GUI { var dictionary = new Dictionary(); - foreach (var slot in _inventoryComponent.Slots) + if (!Owner.TryGetComponent(out InventoryComponent? inventory)) { - dictionary[slot] = _inventoryComponent.GetSlotItem(slot)?.Owner.Name ?? "None"; + return dictionary; + } + + foreach (var slot in inventory.Slots) + { + dictionary[slot] = inventory.GetSlotItem(slot)?.Owner.Name ?? "None"; } return dictionary; @@ -89,9 +103,14 @@ namespace Content.Server.GameObjects.Components.GUI { var dictionary = new Dictionary(); - foreach (var hand in _handsComponent.Hands) + if (!Owner.TryGetComponent(out HandsComponent? hands)) { - dictionary[hand] = _handsComponent.GetItem(hand)?.Owner.Name ?? "None"; + return dictionary; + } + + foreach (var hand in hands.Hands) + { + dictionary[hand] = hands.GetItem(hand)?.Owner.Name ?? "None"; } return dictionary; @@ -99,7 +118,7 @@ namespace Content.Server.GameObjects.Components.GUI private void OpenUserInterface(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } /// @@ -336,7 +355,7 @@ namespace Content.Server.GameObjects.Components.GUI private void HandleUserInterfaceMessage(ServerBoundUserInterfaceMessage obj) { var user = obj.Session.AttachedEntity; - if (user == null || !(user.TryGetComponent(out HandsComponent userHands))) return; + if (user == null || !(user.TryGetComponent(out HandsComponent? userHands))) return; var placingItem = userHands.GetActiveHand != null; diff --git a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs index 55e6acfe92..7aa3a9df81 100644 --- a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs +++ b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs @@ -1,8 +1,8 @@ -using System.Threading.Tasks; +#nullable enable +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Power.ApcNetComponents; -using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Shared.GameObjects.Components.Gravity; using Content.Shared.GameObjects.Components.Interactable; @@ -22,11 +22,6 @@ namespace Content.Server.GameObjects.Components.Gravity [RegisterComponent] public class GravityGeneratorComponent : SharedGravityGeneratorComponent, IInteractUsing, IBreakAct, IInteractHand { - private BoundUserInterface _userInterface; - - private PowerReceiverComponent _powerReceiver; - - private SpriteComponent _sprite; private bool _switchedOn; @@ -34,7 +29,7 @@ namespace Content.Server.GameObjects.Components.Gravity private GravityGeneratorStatus _status; - public bool Powered => _powerReceiver.Powered; + public bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; public bool SwitchedOn => _switchedOn; @@ -64,15 +59,21 @@ namespace Content.Server.GameObjects.Components.Gravity public override string Name => "GravityGenerator"; + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(GravityGeneratorUiKey.Key, out var boundUi) + ? boundUi + : null; + public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(GravityGeneratorUiKey.Key); - _userInterface.OnReceiveMessage += HandleUIMessage; - _powerReceiver = Owner.GetComponent(); - _sprite = Owner.GetComponent(); + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += HandleUIMessage; + } + _switchedOn = true; _intact = true; _status = GravityGeneratorStatus.On; @@ -101,7 +102,7 @@ namespace Content.Server.GameObjects.Components.Gravity public async Task InteractUsing(InteractUsingEventArgs eventArgs) { - if (!eventArgs.Using.TryGetComponent(out WelderComponent tool)) + if (!eventArgs.Using.TryGetComponent(out WelderComponent? tool)) return false; if (!await tool.UseTool(eventArgs.User, Owner, 2f, ToolQuality.Welding, 5f)) @@ -150,7 +151,7 @@ namespace Content.Server.GameObjects.Components.Gravity switch (message.Message) { case GeneratorStatusRequestMessage _: - _userInterface.SetState(new GeneratorState(Status == GravityGeneratorStatus.On)); + UserInterface?.SetState(new GeneratorState(Status == GravityGeneratorStatus.On)); break; case SwitchGeneratorMessage msg: _switchedOn = msg.On; @@ -163,35 +164,51 @@ namespace Content.Server.GameObjects.Components.Gravity private void OpenUserInterface(IPlayerSession playerSession) { - _userInterface.Open(playerSession); + UserInterface?.Open(playerSession); } private void MakeBroken() { _status = GravityGeneratorStatus.Broken; - _sprite.LayerSetState(0, "broken"); - _sprite.LayerSetVisible(1, false); + + if (Owner.TryGetComponent(out SpriteComponent? sprite)) + { + sprite.LayerSetState(0, "broken"); + sprite.LayerSetVisible(1, false); + } } private void MakeUnpowered() { _status = GravityGeneratorStatus.Unpowered; - _sprite.LayerSetState(0, "off"); - _sprite.LayerSetVisible(1, false); + + if (Owner.TryGetComponent(out SpriteComponent? sprite)) + { + sprite.LayerSetState(0, "off"); + sprite.LayerSetVisible(1, false); + } } private void MakeOff() { _status = GravityGeneratorStatus.Off; - _sprite.LayerSetState(0, "off"); - _sprite.LayerSetVisible(1, false); + + if (Owner.TryGetComponent(out SpriteComponent? sprite)) + { + sprite.LayerSetState(0, "off"); + sprite.LayerSetVisible(1, false); + } } private void MakeOn() { _status = GravityGeneratorStatus.On; - _sprite.LayerSetState(0, "on"); - _sprite.LayerSetVisible(1, true); + + if (Owner.TryGetComponent(out SpriteComponent? sprite)) + { + sprite.LayerSetState(0, "on"); + sprite.LayerSetVisible(1, true); + } } } diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index f73f8827c4..833db0d87b 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Linq; using Content.Server.GameObjects.Components.Mobs; using Content.Server.Interfaces; @@ -34,12 +35,8 @@ namespace Content.Server.GameObjects.Components.Instruments IUse, IThrown { - -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; - - [Dependency] private readonly IGameTiming _gameTiming; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; private static readonly TimeSpan OneSecAgo = TimeSpan.FromSeconds(-1); @@ -47,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Instruments /// The client channel currently playing the instrument, or null if there's none. /// [ViewVariables] - private IPlayerSession _instrumentPlayer; + private IPlayerSession? _instrumentPlayer; private bool _handheld; @@ -72,9 +69,6 @@ namespace Content.Server.GameObjects.Components.Instruments [ViewVariables] private int _midiEventCount = 0; - [ViewVariables] - private BoundUserInterface _userInterface; - /// /// Whether the instrument is an item which can be held or not. /// @@ -95,7 +89,7 @@ namespace Content.Server.GameObjects.Components.Instruments } } - public IPlayerSession InstrumentPlayer + public IPlayerSession? InstrumentPlayer { get => _instrumentPlayer; private set @@ -108,11 +102,18 @@ namespace Content.Server.GameObjects.Components.Instruments _instrumentPlayer = value; if (value != null) - _instrumentPlayer.PlayerStatusChanged += OnPlayerStatusChanged; + _instrumentPlayer!.PlayerStatusChanged += OnPlayerStatusChanged; } } - private void OnPlayerStatusChanged(object sender, SessionStatusEventArgs e) + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(InstrumentUiKey.Key, out var boundUi) + ? boundUi + : null; + + private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) { if (e.Session != _instrumentPlayer || e.NewStatus != SessionStatus.Disconnected) return; InstrumentPlayer = null; @@ -122,8 +123,11 @@ namespace Content.Server.GameObjects.Components.Instruments public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(InstrumentUiKey.Key); - _userInterface.OnClosed += UserInterfaceOnClosed; + + if (UserInterface != null) + { + UserInterface.OnClosed += UserInterfaceOnClosed; + } } public override void ExposeData(ObjectSerializer serializer) @@ -137,14 +141,14 @@ namespace Content.Server.GameObjects.Components.Instruments return new InstrumentState(Playing, _lastSequencerTick); } - public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null) + public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null) { base.HandleNetworkMessage(message, channel, session); switch (message) { case InstrumentMidiEventMessage midiEventMsg: - if (!Playing || session != _instrumentPlayer) return; + if (!Playing || session != _instrumentPlayer || InstrumentPlayer == null) return; var send = true; @@ -231,7 +235,7 @@ namespace Content.Server.GameObjects.Components.Instruments Clean(); SendNetworkMessage(new InstrumentStopMidiMessage()); InstrumentPlayer = null; - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } public void Thrown(ThrownEventArgs eventArgs) @@ -239,7 +243,7 @@ namespace Content.Server.GameObjects.Components.Instruments Clean(); SendNetworkMessage(new InstrumentStopMidiMessage()); InstrumentPlayer = null; - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } public void HandSelected(HandSelectedEventArgs eventArgs) @@ -255,12 +259,12 @@ namespace Content.Server.GameObjects.Components.Instruments { Clean(); SendNetworkMessage(new InstrumentStopMidiMessage()); - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } public void Activate(ActivateEventArgs eventArgs) { - if (Handheld || !eventArgs.User.TryGetComponent(out IActorComponent actor)) return; + if (Handheld || !eventArgs.User.TryGetComponent(out IActorComponent? actor)) return; if (InstrumentPlayer != null) return; @@ -270,7 +274,7 @@ namespace Content.Server.GameObjects.Components.Instruments public bool UseEntity(UseEntityEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) return false; + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false; if (InstrumentPlayer == actor.playerSession) { @@ -291,7 +295,7 @@ namespace Content.Server.GameObjects.Components.Instruments private void OpenUserInterface(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } public override void Update(float delta) @@ -302,7 +306,7 @@ namespace Content.Server.GameObjects.Components.Instruments { InstrumentPlayer = null; Clean(); - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } if ((_batchesDropped >= MaxMidiBatchDropped @@ -314,9 +318,9 @@ namespace Content.Server.GameObjects.Components.Instruments SendNetworkMessage(new InstrumentStopMidiMessage()); Playing = false; - _userInterface.CloseAll(); + UserInterface?.CloseAll(); - if (mob.TryGetComponent(out StunnableComponent stun)) + if (mob != null && mob.TryGetComponent(out StunnableComponent? stun)) { stun.Stun(1); Clean(); diff --git a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs index e1a0bd2415..f581c1957e 100644 --- a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +#nullable enable +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Clothing; using Content.Server.GameObjects.Components.Items.Storage; @@ -29,25 +30,20 @@ namespace Content.Server.GameObjects.Components.Interactable [RegisterComponent] internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing, IMapInit { -#pragma warning disable 649 - [Dependency] private readonly ISharedNotifyManager _notifyManager; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly ISharedNotifyManager _notifyManager = default!; + [Dependency] private readonly ILocalizationManager _localizationManager = default!; [ViewVariables(VVAccess.ReadWrite)] public float Wattage { get; set; } = 10; - [ViewVariables] private ContainerSlot _cellContainer; - private PointLightComponent _pointLight; - private SpriteComponent _spriteComponent; - private ClothingComponent _clothingComponent; + [ViewVariables] private ContainerSlot _cellContainer = default!; [ViewVariables] - private BatteryComponent Cell + private BatteryComponent? Cell { get { if (_cellContainer.ContainedEntity == null) return null; - _cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent cell); + _cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent? cell); return cell; } } @@ -98,11 +94,10 @@ namespace Content.Server.GameObjects.Components.Interactable { base.Initialize(); - _pointLight = Owner.GetComponent(); - _spriteComponent = Owner.GetComponent(); - Owner.TryGetComponent(out _clothingComponent); + Owner.EnsureComponent(); _cellContainer = ContainerManagerComponent.Ensure("flashlight_cell_container", Owner, out _); + Dirty(); } @@ -179,11 +174,19 @@ namespace Content.Server.GameObjects.Components.Interactable private void SetState(bool on) { - _spriteComponent.LayerSetVisible(1, on); - _pointLight.Enabled = on; - if (_clothingComponent != null) + if (Owner.TryGetComponent(out SpriteComponent? sprite)) { - _clothingComponent.ClothingEquippedPrefix = on ? "On" : "Off"; + sprite.LayerSetVisible(1, on); + } + + if (Owner.TryGetComponent(out PointLightComponent? light)) + { + light.Enabled = on; + } + + if (Owner.TryGetComponent(out ClothingComponent? clothing)) + { + clothing.ClothingEquippedPrefix = on ? "On" : "Off"; } } @@ -211,7 +214,7 @@ namespace Content.Server.GameObjects.Components.Interactable return; } - if (!user.TryGetComponent(out HandsComponent hands)) + if (!user.TryGetComponent(out HandsComponent? hands)) { return; } diff --git a/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs b/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs index 785e19e6b0..7f37eef2e5 100644 --- a/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs @@ -29,10 +29,8 @@ namespace Content.Server.GameObjects.Components.Interactable [ComponentReference(typeof(IToolComponent))] public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct, ISolutionChange { -#pragma warning disable 649 [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IServerNotifyManager _notifyManager = default!; -#pragma warning restore 649 public override string Name => "Welder"; public override uint? NetID => ContentNetIDs.WELDER; diff --git a/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs b/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs index f15948fc22..4bab06757b 100644 --- a/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs +++ b/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs @@ -21,10 +21,8 @@ namespace Content.Server.GameObjects.Components.Items #pragma warning restore 649 public override string Name => "FloorTile"; - private StackComponent _stack; private string _outputTile; - public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); @@ -34,18 +32,20 @@ namespace Content.Server.GameObjects.Components.Items public override void Initialize() { base.Initialize(); - _stack = Owner.GetComponent(); + Owner.EnsureComponent(); } + public void AfterInteract(AfterInteractEventArgs eventArgs) { if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return; + if (!Owner.TryGetComponent(out StackComponent stack)) return; var attacked = eventArgs.Target; var mapGrid = _mapManager.GetGrid(eventArgs.ClickLocation.GridID); var tile = mapGrid.GetTileRef(eventArgs.ClickLocation); var tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.Tile.TypeId]; - if (tileDef.IsSubFloor && attacked == null && _stack.Use(1)) + if (tileDef.IsSubFloor && attacked == null && stack.Use(1)) { var desiredTile = _tileDefinitionManager[_outputTile]; mapGrid.SetTile(eventArgs.ClickLocation, new Tile(desiredTile.TileId)); diff --git a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs index 8d6f4650ae..719c361083 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs @@ -37,10 +37,8 @@ namespace Content.Server.GameObjects.Components.Items.Storage public class ServerStorageComponent : SharedStorageComponent, IInteractUsing, IUse, IActivate, IStorageComponent, IDestroyAct, IExAct, IDragDrop { -#pragma warning disable 649 [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; -#pragma warning restore 649 private const string LoggerName = "Storage"; diff --git a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs index 470fb19ec6..a3e049510c 100644 --- a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs +++ b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -39,25 +40,19 @@ namespace Content.Server.GameObjects.Components.Kitchen [ComponentReference(typeof(IActivate))] public class MicrowaveComponent : SharedMicrowaveComponent, IActivate, IInteractUsing, ISolutionChange, ISuicideAct { -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; - [Dependency] private readonly RecipeManager _recipeManager; - [Dependency] private readonly IServerNotifyManager _notifyManager; -#pragma warning restore 649 + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly RecipeManager _recipeManager = default!; + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; #region YAMLSERIALIZE private int _cookTimeDefault; private int _cookTimeMultiplier; //For upgrades and stuff I guess? - private string _badRecipeName; - private string _startCookingSound; - private string _cookingCompleteSound; + private string _badRecipeName = ""; + private string _startCookingSound = ""; + private string _cookingCompleteSound = ""; #endregion -#region VIEWVARIABLES - [ViewVariables] - private SolutionComponent _solution; - - [ViewVariables] +[ViewVariables] private bool _busy = false; /// @@ -67,20 +62,23 @@ namespace Content.Server.GameObjects.Components.Kitchen /// [ViewVariables] private uint _currentCookTimerTime = 1; -#endregion - private bool _powered => _powerReceiver.Powered; - private bool _hasContents => _solution.ReagentList.Count > 0 || _storage.ContainedEntities.Count > 0; + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; + private bool _hasContents => Owner.TryGetComponent(out SolutionComponent? solution) && (solution.ReagentList.Count > 0 || _storage.ContainedEntities.Count > 0); private bool _uiDirty = true; private bool _lostPower = false; private int _currentCookTimeButtonIndex = 0; void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => _uiDirty = true; - private AudioSystem _audioSystem; - private AppearanceComponent _appearance; - private PowerReceiverComponent _powerReceiver; - private BoundUserInterface _userInterface; - private Container _storage; + private AudioSystem _audioSystem = default!; + private Container _storage = default!; + + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(MicrowaveUiKey.Key, out var boundUi) + ? boundUi + : null; public override void ExposeData(ObjectSerializer serializer) { @@ -95,22 +93,21 @@ namespace Content.Server.GameObjects.Components.Kitchen public override void Initialize() { base.Initialize(); - _solution ??= Owner.TryGetComponent(out SolutionComponent solutionComponent) - ? solutionComponent - : Owner.AddComponent(); + + Owner.EnsureComponent(); _storage = ContainerManagerComponent.Ensure("microwave_entity_container", Owner, out var existed); - _appearance = Owner.GetComponent(); - _powerReceiver = Owner.GetComponent(); _audioSystem = EntitySystem.Get(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(MicrowaveUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + } } private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage message) { - if (!_powered || _busy) + if (!Powered || _busy) { return; } @@ -158,13 +155,13 @@ namespace Content.Server.GameObjects.Components.Kitchen public void OnUpdate() { - if (!_powered) + if (!Powered) { //TODO:If someone cuts power currently, microwave magically keeps going. FIX IT! SetAppearance(MicrowaveVisualState.Idle); } - if (_busy && !_powered) + if (_busy && !Powered) { //we lost power while we were cooking/busy! _lostPower = true; @@ -174,11 +171,11 @@ namespace Content.Server.GameObjects.Components.Kitchen _uiDirty = true; } - if (_uiDirty) + if (_uiDirty && Owner.TryGetComponent(out SolutionComponent? solution)) { - _userInterface.SetState(new MicrowaveUpdateUserInterfaceState + UserInterface?.SetState(new MicrowaveUpdateUserInterfaceState ( - _solution.Solution.Contents.ToArray(), + solution.Solution.Contents.ToArray(), _storage.ContainedEntities.Select(item => item.Uid).ToArray(), _busy, _currentCookTimeButtonIndex, @@ -190,26 +187,26 @@ namespace Content.Server.GameObjects.Components.Kitchen private void SetAppearance(MicrowaveVisualState state) { - if (_appearance != null || Owner.TryGetComponent(out _appearance)) + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) { - _appearance.SetData(PowerDeviceVisuals.VisualState, state); + appearance.SetData(PowerDeviceVisuals.VisualState, state); } - } void IActivate.Activate(ActivateEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor) || !_powered) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor) || !Powered) { return; } + _uiDirty = true; - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } public async Task InteractUsing(InteractUsingEventArgs eventArgs) { - if (!_powered) + if (!Powered) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User, Loc.GetString("It has no power!")); @@ -232,8 +229,13 @@ namespace Content.Server.GameObjects.Components.Kitchen return false; } + if (!Owner.TryGetComponent(out SolutionComponent? solution)) + { + return false; + } + //Get transfer amount. May be smaller than _transferAmount if not enough room - var realTransferAmount = ReagentUnit.Min(attackPourable.TransferAmount, _solution.EmptyVolume); + var realTransferAmount = ReagentUnit.Min(attackPourable.TransferAmount, solution.EmptyVolume); if (realTransferAmount <= 0) //Special message if container is full { _notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User, @@ -243,7 +245,7 @@ namespace Content.Server.GameObjects.Components.Kitchen //Move units from attackSolution to targetSolution var removedSolution = attackSolution.SplitSolution(realTransferAmount); - if (!_solution.TryAddSolution(removedSolution)) + if (!solution.TryAddSolution(removedSolution)) { return false; } @@ -280,6 +282,11 @@ namespace Content.Server.GameObjects.Components.Kitchen var solidsDict = new Dictionary(); foreach(var item in _storage.ContainedEntities) { + if (item.Prototype == null) + { + continue; + } + if(solidsDict.ContainsKey(item.Prototype.ID)) { solidsDict[item.Prototype.ID]++; @@ -303,7 +310,7 @@ namespace Content.Server.GameObjects.Components.Kitchen } // Check recipes - FoodRecipePrototype recipeToCook = null; + FoodRecipePrototype? recipeToCook = null; foreach (var r in _recipeManager.Recipes.Where(r => CanSatisfyRecipe(r, solidsDict) == MicrowaveSuccessState.RecipePass)) { recipeToCook = r; @@ -330,7 +337,7 @@ namespace Content.Server.GameObjects.Components.Kitchen { if (goodMeal) { - SubtractContents(recipeToCook); + SubtractContents(recipeToCook!); } else { @@ -357,12 +364,18 @@ namespace Content.Server.GameObjects.Components.Kitchen private void VaporizeReagents() { - _solution.RemoveAllSolution(); + if (Owner.TryGetComponent(out SolutionComponent? solution)) + { + solution.RemoveAllSolution(); + } } private void VaporizeReagentQuantity(Solution.ReagentQuantity reagentQuantity) { - _solution.TryRemoveReagent(reagentQuantity.ReagentId, reagentQuantity.Quantity); + if (Owner.TryGetComponent(out SolutionComponent? solution)) + { + solution?.TryRemoveReagent(reagentQuantity.ReagentId, reagentQuantity.Quantity); + } } private void VaporizeSolids() @@ -395,9 +408,14 @@ namespace Content.Server.GameObjects.Components.Kitchen private void SubtractContents(FoodRecipePrototype recipe) { + if (!Owner.TryGetComponent(out SolutionComponent? solution)) + { + return; + } + foreach(var recipeReagent in recipe.IngredientsReagents) { - _solution.TryRemoveReagent(recipeReagent.Key, ReagentUnit.New(recipeReagent.Value)); + solution?.TryRemoveReagent(recipeReagent.Key, ReagentUnit.New(recipeReagent.Value)); } foreach (var recipeSolid in recipe.IngredientsSolids) @@ -406,6 +424,11 @@ namespace Content.Server.GameObjects.Components.Kitchen { foreach (var item in _storage.ContainedEntities) { + if (item.Prototype == null) + { + continue; + } + if (item.Prototype.ID == recipeSolid.Key) { _storage.Remove(item); @@ -420,9 +443,14 @@ namespace Content.Server.GameObjects.Components.Kitchen private MicrowaveSuccessState CanSatisfyRecipe(FoodRecipePrototype recipe, Dictionary solids) { + if (!Owner.TryGetComponent(out SolutionComponent? solution)) + { + return MicrowaveSuccessState.RecipeFail; + } + foreach (var reagent in recipe.IngredientsReagents) { - if (!_solution.ContainsReagent(reagent.Key, out var amount)) + if (!solution.ContainsReagent(reagent.Key, out var amount)) { return MicrowaveSuccessState.RecipeFail; } diff --git a/Content.Server/GameObjects/Components/MagicMirrorComponent.cs b/Content.Server/GameObjects/Components/MagicMirrorComponent.cs index e14b07384c..c03126cf9f 100644 --- a/Content.Server/GameObjects/Components/MagicMirrorComponent.cs +++ b/Content.Server/GameObjects/Components/MagicMirrorComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Mobs; +#nullable enable +using Content.Server.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; @@ -8,6 +9,7 @@ using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components { @@ -15,19 +17,41 @@ namespace Content.Server.GameObjects.Components [ComponentReference(typeof(IActivate))] public class MagicMirrorComponent : SharedMagicMirrorComponent, IActivate { - private BoundUserInterface _userInterface; + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(MagicMirrorUiKey.Key, out var boundUi) + ? boundUi + : null; public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(MagicMirrorUiKey.Key); - _userInterface.OnReceiveMessage += OnUiReceiveMessage; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += OnUiReceiveMessage; + } + } + + public override void OnRemove() + { + if (UserInterface != null) + { + UserInterface.OnReceiveMessage -= OnUiReceiveMessage; + } + + base.OnRemove(); } private static void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) { - if (!obj.Session.AttachedEntity.TryGetComponent(out HumanoidAppearanceComponent looks)) + if (obj.Session.AttachedEntity == null) + { + return; + } + + if (!obj.Session.AttachedEntity.TryGetComponent(out HumanoidAppearanceComponent? looks)) { return; } @@ -70,23 +94,23 @@ namespace Content.Server.GameObjects.Components public void Activate(ActivateEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) { return; } - if (!eventArgs.User.TryGetComponent(out HumanoidAppearanceComponent looks)) + if (!eventArgs.User.TryGetComponent(out HumanoidAppearanceComponent? looks)) { Owner.PopupMessage(eventArgs.User, Loc.GetString("You can't have any hair!")); return; } - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); var msg = new MagicMirrorInitialDataMessage(looks.Appearance.HairColor, looks.Appearance.FacialHairColor, looks.Appearance.HairStyleName, looks.Appearance.FacialHairStyleName); - _userInterface.SendMessage(msg, actor.playerSession); + UserInterface?.SendMessage(msg, actor.playerSession); } } } diff --git a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs index d50513e374..d16d821e78 100644 --- a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs +++ b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; @@ -16,6 +17,7 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Maths; using Content.Shared.Damage; using Robust.Shared.Localization; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Medical { @@ -23,29 +25,34 @@ namespace Content.Server.GameObjects.Components.Medical [ComponentReference(typeof(IActivate))] public class MedicalScannerComponent : SharedMedicalScannerComponent, IActivate { - private AppearanceComponent _appearance; - private BoundUserInterface _userInterface; - private ContainerSlot _bodyContainer; + private ContainerSlot _bodyContainer = default!; private readonly Vector2 _ejectOffset = new Vector2(-0.5f, 0f); public bool IsOccupied => _bodyContainer.ContainedEntity != null; - private PowerReceiverComponent _powerReceiver; - private bool Powered => _powerReceiver.Powered; + [ViewVariables] + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; + + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(MedicalScannerUiKey.Key, out var boundUi) + ? boundUi + : null; public override void Initialize() { base.Initialize(); - _appearance = Owner.GetComponent(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(MedicalScannerUiKey.Key); - _userInterface.OnReceiveMessage += OnUiReceiveMessage; - _bodyContainer = ContainerManagerComponent.Ensure($"{Name}-bodyContainer", Owner); - _powerReceiver = Owner.GetComponent(); + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += OnUiReceiveMessage; + } - //TODO: write this so that it checks for a change in power events and acts accordingly. + _bodyContainer = ContainerManagerComponent.Ensure($"{Name}-bodyContainer", Owner); + + // TODO: write this so that it checks for a change in power events and acts accordingly. var newState = GetUserInterfaceState(); - _userInterface.SetState(newState); + UserInterface?.SetState(newState); UpdateUserInterface(); } @@ -62,11 +69,15 @@ namespace Content.Server.GameObjects.Components.Medical var body = _bodyContainer.ContainedEntity; if (body == null) { - _appearance.SetData(MedicalScannerVisuals.Status, MedicalScannerStatus.Open); + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance?.SetData(MedicalScannerVisuals.Status, MedicalScannerStatus.Open); + }; + return EmptyUIState; } - if (!body.TryGetComponent(out IDamageableComponent damageable) || + if (!body.TryGetComponent(out IDamageableComponent? damageable) || damageable.CurrentDamageState == DamageState.Dead) { return EmptyUIState; @@ -86,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Medical } var newState = GetUserInterfaceState(); - _userInterface.SetState(newState); + UserInterface?.SetState(newState); } private MedicalScannerStatus GetStatusFromDamageState(DamageState damageState) @@ -115,12 +126,15 @@ namespace Content.Server.GameObjects.Components.Medical private void UpdateAppearance() { - _appearance.SetData(MedicalScannerVisuals.Status, GetStatus()); + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(MedicalScannerVisuals.Status, GetStatus()); + } } public void Activate(ActivateEventArgs args) { - if (!args.User.TryGetComponent(out IActorComponent actor)) + if (!args.User.TryGetComponent(out IActorComponent? actor)) { return; } @@ -128,7 +142,7 @@ namespace Content.Server.GameObjects.Components.Medical if (!Powered) return; - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } [Verb] diff --git a/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs b/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs index 08a4bffd48..298b5858ec 100644 --- a/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs +++ b/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs @@ -27,7 +27,8 @@ namespace Content.Server.GameObjects.Components.Mining public override void Initialize() { base.Initialize(); - var spriteComponent = Owner.GetComponent(); + + var spriteComponent = Owner.EnsureComponent(); spriteComponent.LayerSetState(0, _random.Pick(SpriteStates)); } diff --git a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs index ac635153e2..e613b33f50 100644 --- a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.EntitySystems.AI; +#nullable enable +using Content.Server.GameObjects.EntitySystems.AI; using Content.Shared.GameObjects.Components.Movement; using Robust.Server.AI; using Robust.Shared.GameObjects; @@ -14,23 +15,23 @@ namespace Content.Server.GameObjects.Components.Movement [RegisterComponent, ComponentReference(typeof(IMoverComponent))] public class AiControllerComponent : Component, IMoverComponent { - private string _logicName; + private string? _logicName; private float _visionRadius; public override string Name => "AiController"; [ViewVariables(VVAccess.ReadWrite)] - public string LogicName + public string? LogicName { get => _logicName; set { _logicName = value; - Processor = null; + Processor = null!; } } - public AiLogicProcessor Processor { get; set; } + public AiLogicProcessor? Processor { get; set; } [ViewVariables(VVAccess.ReadWrite)] public float VisionRadius @@ -45,9 +46,8 @@ namespace Content.Server.GameObjects.Components.Movement base.Initialize(); // This component requires a collidable component. - if (!Owner.HasComponent()) - Owner.AddComponent(); - + Owner.EnsureComponent(); + EntitySystem.Get().ProcessorInitialize(this); } @@ -74,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Movement { get { - if (Owner.TryGetComponent(out MovementSpeedModifierComponent component)) + if (Owner.TryGetComponent(out MovementSpeedModifierComponent? component)) { return component.CurrentWalkSpeed; } @@ -91,7 +91,7 @@ namespace Content.Server.GameObjects.Components.Movement { get { - if (Owner.TryGetComponent(out MovementSpeedModifierComponent component)) + if (Owner.TryGetComponent(out MovementSpeedModifierComponent? component)) { return component.CurrentSprintSpeed; } diff --git a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs index a6bb5ed2fb..45baeb54e7 100644 --- a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs @@ -24,10 +24,8 @@ namespace Content.Server.GameObjects.Components.Movement [ComponentReference(typeof(IClimbable))] public class ClimbableComponent : SharedClimbableComponent, IDragDropOn { -#pragma warning disable 649 [Dependency] private readonly IServerNotifyManager _notifyManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; -#pragma warning restore 649 /// /// The range from which this entity can be climbed. @@ -81,7 +79,7 @@ namespace Content.Server.GameObjects.Components.Movement var bodyManager = eventArgs.User.GetComponent(); if (bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 || - bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0) + bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0) { _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are unable to climb!")); @@ -102,10 +100,10 @@ namespace Content.Server.GameObjects.Components.Movement } else // user is dragging some other entity onto a climbable { - if (eventArgs.Target == null || !eventArgs.Dropped.HasComponent()) + if (eventArgs.Target == null || !eventArgs.Dropped.HasComponent()) { _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!")); - + return false; } diff --git a/Content.Server/GameObjects/Components/Movement/ServerPortalComponent.cs b/Content.Server/GameObjects/Components/Movement/ServerPortalComponent.cs index 1c5abcdfe0..c80771c2de 100644 --- a/Content.Server/GameObjects/Components/Movement/ServerPortalComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ServerPortalComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using Content.Shared.GameObjects.Components.Movement; using Robust.Server.GameObjects; @@ -18,9 +19,7 @@ namespace Content.Server.GameObjects.Components.Movement [RegisterComponent] public class ServerPortalComponent : SharedPortalComponent { -#pragma warning disable 649 - [Dependency] private readonly IServerEntityManager _serverEntityManager; -#pragma warning restore 649 + [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; // Potential improvements: Different sounds, // Add Gateways @@ -28,15 +27,14 @@ namespace Content.Server.GameObjects.Components.Movement // Put portal above most other things layer-wise // Add telefragging (get entities on connecting portal and force brute damage) - private AppearanceComponent _appearanceComponent; - private IEntity _connectingTeleporter; + private IEntity? _connectingTeleporter; private PortalState _state = PortalState.Pending; [ViewVariables(VVAccess.ReadWrite)] private float _individualPortalCooldown; [ViewVariables] private float _overallPortalCooldown; [ViewVariables] private bool _onCooldown; - [ViewVariables] private string _departureSound; - [ViewVariables] private string _arrivalSound; - public List immuneEntities = new List(); // K + [ViewVariables] private string _departureSound = ""; + [ViewVariables] private string _arrivalSound = ""; + public readonly List ImmuneEntities = new List(); // K [ViewVariables(VVAccess.ReadWrite)] private float _aliveTime; public override void ExposeData(ObjectSerializer serializer) @@ -52,12 +50,6 @@ namespace Content.Server.GameObjects.Components.Movement serializer.DataField(ref _arrivalSound, "arrival_sound", "/Audio/Effects/teleport_arrival.ogg"); } - public override void Initialize() - { - base.Initialize(); - _appearanceComponent = Owner.GetComponent(); - } - public override void OnAdd() { // This will blow up an entity it's attached to @@ -74,13 +66,6 @@ namespace Content.Server.GameObjects.Components.Movement } } - public override void OnRemove() - { - _appearanceComponent = null; - - base.OnRemove(); - } - public bool CanBeConnected() { if (_connectingTeleporter == null) @@ -108,23 +93,24 @@ namespace Content.Server.GameObjects.Components.Movement } _state = targetState; - if (_appearanceComponent != null) + + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) { - _appearanceComponent.SetData(PortalVisuals.State, _state); + appearance.SetData(PortalVisuals.State, _state); } } - private void releaseCooldown(IEntity entity) + private void ReleaseCooldown(IEntity entity) { - if (immuneEntities.Contains(entity)) + if (ImmuneEntities.Contains(entity)) { - immuneEntities.Remove(entity); + ImmuneEntities.Remove(entity); } if (_connectingTeleporter != null && _connectingTeleporter.TryGetComponent(out var otherPortal)) { - otherPortal.immuneEntities.Remove(entity); + otherPortal.ImmuneEntities.Remove(entity); } } @@ -142,7 +128,7 @@ namespace Content.Server.GameObjects.Components.Movement private bool IsEntityPortable(IEntity entity) { // TODO: Check if it's slotted etc. Otherwise the slot item itself gets ported. - if (!immuneEntities.Contains(entity) && entity.HasComponent()) + if (!ImmuneEntities.Contains(entity) && entity.HasComponent()) { return true; } @@ -192,7 +178,7 @@ namespace Content.Server.GameObjects.Components.Movement public void TryPortalEntity(IEntity entity) { - if (immuneEntities.Contains(entity) || _connectingTeleporter == null) + if (ImmuneEntities.Contains(entity) || _connectingTeleporter == null) { return; } @@ -208,9 +194,9 @@ namespace Content.Server.GameObjects.Components.Movement soundPlayer.PlayAtCoords(_arrivalSound, entity.Transform.GridPosition); TryChangeState(PortalState.RecentlyTeleported); // To stop spam teleporting. Could potentially look at adding a timer to flush this from the portal - immuneEntities.Add(entity); - _connectingTeleporter.GetComponent().immuneEntities.Add(entity); - Timer.Spawn(TimeSpan.FromSeconds(_individualPortalCooldown), () => releaseCooldown(entity)); + ImmuneEntities.Add(entity); + _connectingTeleporter.GetComponent().ImmuneEntities.Add(entity); + Timer.Spawn(TimeSpan.FromSeconds(_individualPortalCooldown), () => ReleaseCooldown(entity)); StartCooldown(); } } diff --git a/Content.Server/GameObjects/Components/Movement/ServerTeleporterComponent.cs b/Content.Server/GameObjects/Components/Movement/ServerTeleporterComponent.cs index e1e44cec21..f731f04646 100644 --- a/Content.Server/GameObjects/Components/Movement/ServerTeleporterComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ServerTeleporterComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Linq; using Content.Shared.GameObjects.Components.Movement; using Content.Shared.Interfaces.GameObjects.Components; @@ -24,11 +25,10 @@ namespace Content.Server.GameObjects.Components.Movement [RegisterComponent] public class ServerTeleporterComponent : Component, IAfterInteract { -#pragma warning disable 649 - [Dependency] private readonly IMapManager _mapManager; - [Dependency] private readonly IServerEntityManager _serverEntityManager; - [Dependency] private readonly IRobustRandom _spreadRandom; -#pragma warning restore 649 + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; + [Dependency] private readonly IRobustRandom _spreadRandom = default!; + // TODO: Look at MapManager.Map for Beacons to get all entities on grid public ItemTeleporterState State => _state; @@ -39,15 +39,13 @@ namespace Content.Server.GameObjects.Components.Movement [ViewVariables] private int _range; [ViewVariables] private ItemTeleporterState _state; [ViewVariables] private TeleporterType _teleporterType; - [ViewVariables] private string _departureSound; - [ViewVariables] private string _arrivalSound; - [ViewVariables] private string _cooldownSound; + [ViewVariables] private string _departureSound = ""; + [ViewVariables] private string _arrivalSound = ""; + [ViewVariables] private string? _cooldownSound; // If the direct OR random teleport will try to avoid hitting collidables [ViewVariables] private bool _avoidCollidable; [ViewVariables] private float _portalAliveTime; - private AppearanceComponent _appearanceComponent; - public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); @@ -63,22 +61,20 @@ namespace Content.Server.GameObjects.Components.Movement serializer.DataField(ref _portalAliveTime, "portal_alive_time", 5.0f); // TODO: Change this to 0 before PR? } - public override void OnRemove() - { - _appearanceComponent = null; - - base.OnRemove(); - } - private void SetState(ItemTeleporterState newState) { + if (!Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + return; + } + if (newState == ItemTeleporterState.Cooldown) { - _appearanceComponent.SetData(TeleporterVisuals.VisualState, TeleporterVisualState.Charging); + appearance.SetData(TeleporterVisuals.VisualState, TeleporterVisualState.Charging); } else { - _appearanceComponent.SetData(TeleporterVisuals.VisualState, TeleporterVisualState.Ready); + appearance.SetData(TeleporterVisuals.VisualState, TeleporterVisualState.Ready); } _state = newState; } @@ -149,12 +145,11 @@ namespace Content.Server.GameObjects.Components.Movement public override void Initialize() { - _appearanceComponent = Owner.GetComponent(); - _state = ItemTeleporterState.Off; base.Initialize(); + _state = ItemTeleporterState.Off; } - private bool emptySpace(IEntity user, Vector2 target) + private bool EmptySpace(IEntity user, Vector2 target) { // TODO: Check the user's spot? Upside is no stacking TPs but downside is they can't unstuck themselves from walls. foreach (var entity in _serverEntityManager.GetEntitiesIntersecting(user.Transform.MapID, target)) @@ -167,7 +162,7 @@ namespace Content.Server.GameObjects.Components.Movement return true; } - private Vector2 randomEmptySpot(IEntity user, int range) + private Vector2 RandomEmptySpot(IEntity user, int range) { Vector2 targetVector = user.Transform.GridPosition.Position; // Definitely a better way to do this @@ -176,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Movement var randomRange = _spreadRandom.Next(0, range); var angle = Angle.FromDegrees(_spreadRandom.Next(0, 359)); targetVector = user.Transform.GridPosition.Position + angle.ToVec() * randomRange; - if (emptySpace(user, targetVector)) + if (EmptySpace(user, targetVector)) { return targetVector; } @@ -200,7 +195,7 @@ namespace Content.Server.GameObjects.Components.Movement Vector2 targetVector; if (_avoidCollidable) { - targetVector = randomEmptySpot(user, _range); + targetVector = RandomEmptySpot(user, _range); } else { @@ -227,7 +222,7 @@ namespace Content.Server.GameObjects.Components.Movement public void Teleport(IEntity user, Vector2 vector) { // Messy maybe? - GridCoordinates targetGrid = new GridCoordinates(vector, user.Transform.GridID); + var targetGrid = new GridCoordinates(vector, user.Transform.GridID); var soundPlayer = EntitySystem.Get(); // If portals use those, otherwise just move em over @@ -240,10 +235,11 @@ namespace Content.Server.GameObjects.Components.Movement // Arrival portal var arrivalPortal = _serverEntityManager.SpawnEntity("Portal", targetGrid); - arrivalPortal.TryGetComponent(out var arrivalComponent); - - // Connect. TODO: If the OnUpdate in ServerPortalComponent is changed this may need to change as well. - arrivalComponent.TryConnectPortal(departurePortal); + if (arrivalPortal.TryGetComponent(out var arrivalComponent)) + { + // Connect. TODO: If the OnUpdate in ServerPortalComponent is changed this may need to change as well. + arrivalComponent.TryConnectPortal(departurePortal); + } } else { diff --git a/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs index 1b39598c2f..e0b7f49f66 100644 --- a/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs @@ -22,10 +22,8 @@ namespace Content.Server.GameObjects.Components.Movement [ComponentReference(typeof(IMoverComponent))] internal class ShuttleControllerComponent : Component, IMoverComponent { -#pragma warning disable 649 [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; -#pragma warning restore 649 private bool _movingUp; private bool _movingDown; diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/ApcNetNodeGroup.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/ApcNetNodeGroup.cs index ff80b1a74f..8e1d103c15 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/ApcNetNodeGroup.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/ApcNetNodeGroup.cs @@ -48,7 +48,12 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups public void AddApc(ApcComponent apc) { - _apcBatteries.Add(apc, apc.Battery); + if (!apc.Owner.TryGetComponent(out BatteryComponent battery)) + { + return; + } + + _apcBatteries.Add(apc, battery); } public void RemoveApc(ApcComponent apc) diff --git a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs index e4440aefa9..cec956d208 100644 --- a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using Content.Server.GameObjects.Components.Body.Digestive; using Content.Server.GameObjects.Components.Chemistry; @@ -25,24 +26,30 @@ namespace Content.Server.GameObjects.Components.Nutrition [ComponentReference(typeof(IAfterInteract))] public class FoodComponent : Component, IUse, IAfterInteract { -#pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystem; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystem = default!; + public override string Name => "Food"; - [ViewVariables] - private string _useSound; - [ViewVariables] - private string _trashPrototype; - [ViewVariables] - private SolutionComponent _contents; - [ViewVariables] - private ReagentUnit _transferAmount; + [ViewVariables] private string _useSound = ""; + [ViewVariables] private string? _trashPrototype; + [ViewVariables] private ReagentUnit _transferAmount; private UtensilType _utensilsNeeded; - public int UsesRemaining => _contents.CurrentVolume == 0 - ? - 0 : Math.Max(1, (int)Math.Ceiling((_contents.CurrentVolume / _transferAmount).Float())); + [ViewVariables] + public int UsesRemaining + { + get + { + if (!Owner.TryGetComponent(out SolutionComponent? solution)) + { + return 0; + } + + return solution.CurrentVolume == 0 + ? 0 + : Math.Max(1, (int)Math.Ceiling((solution.CurrentVolume / _transferAmount).Float())); + } + } public override void ExposeData(ObjectSerializer serializer) { @@ -60,7 +67,7 @@ namespace Content.Server.GameObjects.Components.Nutrition { var types = new List(); - foreach (UtensilType type in Enum.GetValues(typeof(UtensilType))) + foreach (var type in (UtensilType[]) Enum.GetValues(typeof(UtensilType))) { if ((_utensilsNeeded & type) != 0) { @@ -75,8 +82,7 @@ namespace Content.Server.GameObjects.Components.Nutrition public override void Initialize() { base.Initialize(); - _contents = Owner.GetComponent(); - + Owner.EnsureComponent(); } bool IUse.UseEntity(UseEntityEventArgs eventArgs) @@ -101,8 +107,13 @@ namespace Content.Server.GameObjects.Components.Nutrition TryUseFood(eventArgs.User, eventArgs.Target); } - public virtual bool TryUseFood(IEntity user, IEntity target, UtensilComponent utensilUsed = null) + public virtual bool TryUseFood(IEntity? user, IEntity? target, UtensilComponent? utensilUsed = null) { + if (!Owner.TryGetComponent(out SolutionComponent? solution)) + { + return false; + } + if (user == null) { return false; @@ -116,7 +127,7 @@ namespace Content.Server.GameObjects.Components.Nutrition var trueTarget = target ?? user; - if (!trueTarget.TryGetComponent(out StomachComponent stomach)) + if (!trueTarget.TryGetComponent(out StomachComponent? stomach)) { return false; } @@ -130,11 +141,11 @@ namespace Content.Server.GameObjects.Components.Nutrition utensils = new List(); var types = UtensilType.None; - if (user.TryGetComponent(out HandsComponent hands)) + if (user.TryGetComponent(out HandsComponent? hands)) { foreach (var item in hands.GetAllHeldItems()) { - if (!item.Owner.TryGetComponent(out UtensilComponent utensil)) + if (!item.Owner.TryGetComponent(out UtensilComponent? utensil)) { continue; } @@ -156,11 +167,11 @@ namespace Content.Server.GameObjects.Components.Nutrition return false; } - var transferAmount = ReagentUnit.Min(_transferAmount, _contents.CurrentVolume); - var split = _contents.SplitSolution(transferAmount); + var transferAmount = ReagentUnit.Min(_transferAmount, solution.CurrentVolume); + var split = solution.SplitSolution(transferAmount); if (!stomach.TryTransferSolution(split)) { - _contents.TryAddSolution(split); + solution.TryAddSolution(split); trueTarget.PopupMessage(user, Loc.GetString("You can't eat any more!")); return false; } @@ -194,13 +205,13 @@ namespace Content.Server.GameObjects.Components.Nutrition var finisher = Owner.EntityManager.SpawnEntity(_trashPrototype, position); // If the user is holding the item - if (user.TryGetComponent(out HandsComponent handsComponent) && + if (user.TryGetComponent(out HandsComponent? handsComponent) && handsComponent.IsHolding(Owner)) { Owner.Delete(); // Put the trash in the user's hand - if (finisher.TryGetComponent(out ItemComponent item) && + if (finisher.TryGetComponent(out ItemComponent? item) && handsComponent.CanPutInHand(item)) { handsComponent.PutInHand(item); diff --git a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs index 1aa9f2f776..e3e4f810cf 100644 --- a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs +++ b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs @@ -37,21 +37,24 @@ namespace Content.Server.GameObjects.Components.PDA [Dependency] private readonly IEntityManager _entityManager = default!; [ViewVariables] private Container _idSlot = default!; - [ViewVariables] private PointLightComponent _pdaLight = default!; [ViewVariables] private bool _lightOn; - [ViewVariables] private BoundUserInterface _interface = default!; [ViewVariables] private string _startingIdCard = default!; [ViewVariables] public bool IdSlotEmpty => _idSlot.ContainedEntities.Count < 1; [ViewVariables] public string? OwnerName { get; private set; } [ViewVariables] public IdCardComponent? ContainedID { get; private set; } - [ViewVariables] private AppearanceComponent _appearance = default!; - [ViewVariables] private UplinkAccount? _syndicateUplinkAccount; [ViewVariables] private readonly PdaAccessSet _accessSet; + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(PDAUiKey.Key, out var boundUi) + ? boundUi + : null; + public PDAComponent() { _accessSet = new PdaAccessSet(this); @@ -67,11 +70,12 @@ namespace Content.Server.GameObjects.Components.PDA { base.Initialize(); _idSlot = ContainerManagerComponent.Ensure("pda_entity_container", Owner, out var existed); - _pdaLight = Owner.GetComponent(); - _appearance = Owner.GetComponent(); - _interface = Owner.GetComponent() - .GetBoundUserInterface(PDAUiKey.Key); - _interface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + } + var idCard = _entityManager.SpawnEntity(_startingIdCard, Owner.Transform.GridPosition); var idCardComponent = idCard.GetComponent(); _idSlot.Insert(idCardComponent.Owner); @@ -129,11 +133,11 @@ namespace Content.Server.GameObjects.Components.PDA var accData = new UplinkAccountData(_syndicateUplinkAccount.AccountHolder, _syndicateUplinkAccount.Balance); var listings = _uplinkManager.FetchListings.ToArray(); - _interface.SetState(new PDAUpdateState(_lightOn, ownerInfo, accData, listings)); + UserInterface?.SetState(new PDAUpdateState(_lightOn, ownerInfo, accData, listings)); } else { - _interface.SetState(new PDAUpdateState(_lightOn, ownerInfo)); + UserInterface?.SetState(new PDAUpdateState(_lightOn, ownerInfo)); } UpdatePDAAppearance(); @@ -141,7 +145,10 @@ namespace Content.Server.GameObjects.Components.PDA private void UpdatePDAAppearance() { - _appearance?.SetData(PDAVisuals.FlashlightLit, _lightOn); + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(PDAVisuals.FlashlightLit, _lightOn); + } } public async Task InteractUsing(InteractUsingEventArgs eventArgs) @@ -169,7 +176,7 @@ namespace Content.Server.GameObjects.Components.PDA return; } - _interface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); UpdatePDAAppearance(); } @@ -180,7 +187,7 @@ namespace Content.Server.GameObjects.Components.PDA return false; } - _interface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); UpdatePDAAppearance(); return true; } @@ -217,8 +224,13 @@ namespace Content.Server.GameObjects.Components.PDA private void ToggleLight() { + if (!Owner.TryGetComponent(out PointLightComponent? light)) + { + return; + } + _lightOn = !_lightOn; - _pdaLight.Enabled = _lightOn; + light.Enabled = _lightOn; EntitySystem.Get().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner); UpdatePDAUserInterface(); } diff --git a/Content.Server/GameObjects/Components/Paper/PaperComponent.cs b/Content.Server/GameObjects/Components/Paper/PaperComponent.cs index 54193918d9..b3b9a8a459 100644 --- a/Content.Server/GameObjects/Components/Paper/PaperComponent.cs +++ b/Content.Server/GameObjects/Components/Paper/PaperComponent.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +#nullable enable +using System.Threading.Tasks; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; @@ -13,24 +14,30 @@ namespace Content.Server.GameObjects.Components.Paper [RegisterComponent] public class PaperComponent : SharedPaperComponent, IExamine, IInteractUsing, IUse { - - private BoundUserInterface _userInterface; - private string _content; + private string _content = ""; private PaperAction _mode; + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(PaperUiKey.Key, out var boundUi) + ? boundUi + : null; + public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(PaperUiKey.Key); - _userInterface.OnReceiveMessage += OnUiReceiveMessage; - _content = ""; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += OnUiReceiveMessage; + } + _mode = PaperAction.Read; UpdateUserInterface(); } private void UpdateUserInterface() { - _userInterface.SetState(new PaperBoundUserInterfaceState(_content, _mode)); + UserInterface?.SetState(new PaperBoundUserInterfaceState(_content, _mode)); } public void Examine(FormattedMessage message, bool inDetailsRange) @@ -43,11 +50,12 @@ namespace Content.Server.GameObjects.Components.Paper public bool UseEntity(UseEntityEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false; + _mode = PaperAction.Read; UpdateUserInterface(); - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); return true; } @@ -59,7 +67,7 @@ namespace Content.Server.GameObjects.Components.Paper _content += msg.Text + '\n'; - if (Owner.TryGetComponent(out SpriteComponent sprite)) + if (Owner.TryGetComponent(out SpriteComponent? sprite)) { sprite.LayerSetState(1, "paper_words"); } @@ -71,12 +79,12 @@ namespace Content.Server.GameObjects.Components.Paper { if (!eventArgs.Using.HasComponent()) return false; - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false; _mode = PaperAction.Write; UpdateUserInterface(); - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); return true; } } diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs index f55ded352a..7643db7112 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Server.GameObjects.Components.Power.PowerNetComponents; using Content.Shared.GameObjects.Components.Power; @@ -20,17 +21,12 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents [ComponentReference(typeof(IActivate))] public class ApcComponent : BaseApcNetComponent, IActivate { + [Dependency] private readonly IGameTiming _gameTiming = default!; + public override string Name => "Apc"; - [ViewVariables] - public BatteryComponent Battery { get; private set; } - public bool MainBreakerEnabled { get; private set; } = true; - private BoundUserInterface _userInterface; - - private AppearanceComponent _appearance; - private ApcChargeState _lastChargeState; private TimeSpan _lastChargeStateChange; @@ -39,7 +35,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents private TimeSpan _lastExternalPowerStateChange; - private float _lastCharge = 0f; + private float _lastCharge; private TimeSpan _lastChargeChange; @@ -49,17 +45,27 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents private const int VisualsChangeDelay = 1; -#pragma warning disable 649 - [Dependency] private readonly IGameTiming _gameTiming; -#pragma warning restore 649 + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(ApcUiKey.Key, out var boundUi) + ? boundUi + : null; + + public BatteryComponent? Battery => Owner.TryGetComponent(out BatteryComponent? batteryComponent) ? batteryComponent : null; public override void Initialize() { base.Initialize(); - Battery = Owner.GetComponent(); - _appearance = Owner.GetComponent(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(ApcUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + + Owner.EnsureComponent(); + Owner.EnsureComponent(); + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + } + Update(); } @@ -90,15 +96,23 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents { _lastChargeState = newState; _lastChargeStateChange = _gameTiming.CurTime; - _appearance.SetData(ApcVisuals.ChargeState, newState); + + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(ApcVisuals.ChargeState, newState); + } } - var newCharge = Battery.CurrentCharge; - if (newCharge != _lastCharge && _lastChargeChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime) + + Owner.TryGetComponent(out BatteryComponent? battery); + + var newCharge = battery?.CurrentCharge; + if (newCharge != null && newCharge != _lastCharge && _lastChargeChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime) { - _lastCharge = newCharge; + _lastCharge = newCharge.Value; _lastChargeChange = _gameTiming.CurTime; _uiDirty = true; } + var extPowerState = CalcExtPowerState(); if (extPowerState != _lastExternalPowerState && _lastExternalPowerStateChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime) { @@ -106,21 +120,33 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents _lastExternalPowerStateChange = _gameTiming.CurTime; _uiDirty = true; } - if (_uiDirty) + + if (_uiDirty && battery != null && newCharge != null) { - _userInterface.SetState(new ApcBoundInterfaceState(MainBreakerEnabled, extPowerState, newCharge / Battery.MaxCharge)); + UserInterface?.SetState(new ApcBoundInterfaceState(MainBreakerEnabled, extPowerState, newCharge.Value / battery.MaxCharge)); _uiDirty = false; } } private ApcChargeState CalcChargeState() { - var chargeFraction = Battery.CurrentCharge / Battery.MaxCharge; + if (!Owner.TryGetComponent(out BatteryComponent? battery)) + { + return ApcChargeState.Lack; + } + + var chargeFraction = battery.CurrentCharge / battery.MaxCharge; + if (chargeFraction > HighPowerThreshold) { return ApcChargeState.Full; } - var consumer = Owner.GetComponent(); + + if (!Owner.TryGetComponent(out PowerConsumerComponent? consumer)) + { + return ApcChargeState.Full; + } + if (consumer.DrawRate == consumer.ReceivedPower) { return ApcChargeState.Charging; @@ -133,7 +159,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents private ApcExternalPowerState CalcExtPowerState() { - if (!Owner.TryGetComponent(out BatteryStorageComponent batteryStorage)) + if (!Owner.TryGetComponent(out BatteryStorageComponent? batteryStorage)) { return ApcExternalPowerState.None; } @@ -154,11 +180,12 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents void IActivate.Activate(ActivateEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) { return; } - _userInterface.Open(actor.playerSession); + + UserInterface?.Open(actor.playerSession); } } } diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs index 674a33dd1f..4b78446d42 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; @@ -24,19 +25,14 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece public abstract class BaseCharger : Component, IActivate, IInteractUsing { [ViewVariables] - private BatteryComponent _heldBattery; + private BatteryComponent? _heldBattery; [ViewVariables] - private ContainerSlot _container; - - [ViewVariables] - private PowerReceiverComponent _powerReceiver; + private ContainerSlot _container = default!; [ViewVariables] private CellChargerStatus _status; - private AppearanceComponent _appearanceComponent; - [ViewVariables] private int _chargeRate; @@ -53,16 +49,25 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece public override void Initialize() { base.Initialize(); - _powerReceiver = Owner.GetComponent(); + + Owner.EnsureComponent(); _container = ContainerManagerComponent.Ensure($"{Name}-powerCellContainer", Owner); - _appearanceComponent = Owner.GetComponent(); // Default state in the visualizer is OFF, so when this gets powered on during initialization it will generally show empty - _powerReceiver.OnPowerStateChanged += PowerUpdate; + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) + { + receiver.OnPowerStateChanged += PowerUpdate; + } } public override void OnRemove() { - _powerReceiver.OnPowerStateChanged -= PowerUpdate; + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) + { + receiver.OnPowerStateChanged -= PowerUpdate; + } + + _heldBattery = null; + base.OnRemove(); } @@ -97,12 +102,12 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece _container.Remove(heldItem); _heldBattery = null; - if (user.TryGetComponent(out HandsComponent handsComponent)) + if (user.TryGetComponent(out HandsComponent? handsComponent)) { handsComponent.PutInHandOrDrop(heldItem.GetComponent()); } - if (heldItem.TryGetComponent(out ServerBatteryBarrelComponent batteryBarrelComponent)) + if (heldItem.TryGetComponent(out ServerBatteryBarrelComponent? batteryBarrelComponent)) { batteryBarrelComponent.UpdateAppearance(); } @@ -110,7 +115,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece UpdateStatus(); } - private void PowerUpdate(object sender, PowerStateEventArgs eventArgs) + private void PowerUpdate(object? sender, PowerStateEventArgs eventArgs) { UpdateStatus(); } @@ -125,7 +130,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece data.Visibility = VerbVisibility.Invisible; return; } - if (!user.TryGetComponent(out HandsComponent handsComponent)) + if (!user.TryGetComponent(out HandsComponent? handsComponent)) { data.Visibility = VerbVisibility.Invisible; return; @@ -143,7 +148,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece protected override void Activate(IEntity user, BaseCharger component) { - if (!user.TryGetComponent(out HandsComponent handsComponent)) + if (!user.TryGetComponent(out HandsComponent? handsComponent)) { return; } @@ -186,7 +191,8 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece private CellChargerStatus GetStatus() { - if (!_powerReceiver.Powered) + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver) && + !receiver.Powered) { return CellChargerStatus.Off; } @@ -227,34 +233,39 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece { // Not called UpdateAppearance just because it messes with the load var status = GetStatus(); - if (_status == status) + if (_status == status || + !Owner.TryGetComponent(out PowerReceiverComponent? receiver)) { return; } + _status = status; + Owner.TryGetComponent(out AppearanceComponent? appearance); + switch (_status) { // Update load just in case case CellChargerStatus.Off: - _powerReceiver.Load = 0; - _appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Off); + receiver.Load = 0; + appearance?.SetData(CellVisual.Light, CellChargerStatus.Off); break; case CellChargerStatus.Empty: - _powerReceiver.Load = 0; - _appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Empty); ; + receiver.Load = 0; + appearance?.SetData(CellVisual.Light, CellChargerStatus.Empty); break; case CellChargerStatus.Charging: - _powerReceiver.Load = (int) (_chargeRate / _transferEfficiency); - _appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Charging); + receiver.Load = (int) (_chargeRate / _transferEfficiency); + appearance?.SetData(CellVisual.Light, CellChargerStatus.Charging); break; case CellChargerStatus.Charged: - _powerReceiver.Load = 0; - _appearanceComponent?.SetData(CellVisual.Light, CellChargerStatus.Charged); + receiver.Load = 0; + appearance?.SetData(CellVisual.Light, CellChargerStatus.Charged); break; default: throw new ArgumentOutOfRangeException(); } - _appearanceComponent?.SetData(CellVisual.Occupied, _container.ContainedEntity != null); + + appearance?.SetData(CellVisual.Occupied, _container.ContainedEntity != null); } public void OnUpdate(float frameTime) //todo: make single system for this @@ -268,10 +279,17 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece private void TransferPower(float frameTime) { - if (!_powerReceiver.Powered) + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver) && + !receiver.Powered) { return; } + + if (_heldBattery == null) + { + return; + } + _heldBattery.CurrentCharge += _chargeRate * frameTime; // Just so the sprite won't be set to 99.99999% visibility if (_heldBattery.MaxCharge - _heldBattery.CurrentCharge < 0.01) diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs index b33be166af..461698422d 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs @@ -108,7 +108,11 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece public void UpdateColor() { - var sprite = Owner.GetComponent(); + if (!Owner.TryGetComponent(out SpriteComponent sprite)) + { + return; + } + sprite.Color = Color; } diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs index 716e72ee7a..71befe71bc 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs @@ -225,14 +225,18 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece { base.Initialize(); - Owner.GetComponent().OnPowerStateChanged += UpdateLight; + Owner.EnsureComponent().OnPowerStateChanged += UpdateLight; _lightBulbContainer = ContainerManagerComponent.Ensure("light_bulb", Owner); } public override void OnRemove() { - Owner.GetComponent().OnPowerStateChanged -= UpdateLight; + if (Owner.TryGetComponent(out PowerReceiverComponent receiver)) + { + receiver.OnPowerStateChanged -= UpdateLight; + } + base.OnRemove(); } diff --git a/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs b/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs index ca14c4aa44..4165b82fbe 100644 --- a/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs @@ -13,12 +13,9 @@ namespace Content.Server.GameObjects.Components.Power { public override string Name => "PowerCell"; - private AppearanceComponent _appearance; - public override void Initialize() { base.Initialize(); - _appearance = Owner.GetComponent(); CurrentCharge = MaxCharge; UpdateVisuals(); } @@ -31,7 +28,10 @@ namespace Content.Server.GameObjects.Components.Power private void UpdateVisuals() { - _appearance?.SetData(PowerCellVisuals.ChargeLevel, CurrentCharge / MaxCharge); + if (Owner.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(PowerCellVisuals.ChargeLevel, CurrentCharge / MaxCharge); + } } } } diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryDischargerComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryDischargerComponent.cs index af543ae52d..65eaafb285 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryDischargerComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryDischargerComponent.cs @@ -31,8 +31,9 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents public override void Initialize() { base.Initialize(); - _battery = Owner.GetComponent(); - _supplier = Owner.GetComponent(); + + _battery = Owner.EnsureComponent(); + _supplier = Owner.EnsureComponent(); UpdateSupplyRate(); } diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryStorageComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryStorageComponent.cs index d94452a977..416ac17a5c 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryStorageComponent.cs @@ -31,8 +31,9 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents public override void Initialize() { base.Initialize(); - _battery = Owner.GetComponent(); - Consumer = Owner.GetComponent(); + + _battery = Owner.EnsureComponent(); + Consumer = Owner.EnsureComponent(); UpdateDrawRate(); } diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SmesComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SmesComponent.cs index 666befcf28..3cacb3ad86 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SmesComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SmesComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using Content.Shared.GameObjects.Components.Power; using Content.Shared.Utility; using Robust.Server.GameObjects; @@ -16,13 +17,11 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents [RegisterComponent] public class SmesComponent : Component { + [Dependency] private readonly IGameTiming _gameTiming = default!; + public override string Name => "Smes"; - private BatteryComponent _battery; - - private AppearanceComponent _appearance; - - private int _lastChargeLevel = 0; + private int _lastChargeLevel; private TimeSpan _lastChargeLevelChange; @@ -32,15 +31,12 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents private const int VisualsChangeDelay = 1; -#pragma warning disable 649 - [Dependency] private readonly IGameTiming _gameTiming; -#pragma warning restore 649 - public override void Initialize() { base.Initialize(); - _battery = Owner.GetComponent(); - _appearance = Owner.GetComponent(); + + Owner.EnsureComponent(); + Owner.EnsureComponent(); } public void OnUpdate() @@ -50,7 +46,11 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents { _lastChargeLevel = newLevel; _lastChargeLevelChange = _gameTiming.CurTime; - _appearance.SetData(SmesVisuals.LastChargeLevel, newLevel); + + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(SmesVisuals.LastChargeLevel, newLevel); + } } var newChargeState = GetNewChargeState(); @@ -58,13 +58,22 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents { _lastChargeState = newChargeState; _lastChargeStateChange = _gameTiming.CurTime; - _appearance.SetData(SmesVisuals.LastChargeState, newChargeState); + + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(SmesVisuals.LastChargeState, newChargeState); + } } } private int GetNewChargeLevel() { - return ContentHelpers.RoundToLevels(_battery.CurrentCharge, _battery.MaxCharge, 6); + if (!Owner.TryGetComponent(out BatteryComponent? battery)) + { + return 0; + } + + return ContentHelpers.RoundToLevels(battery.CurrentCharge, battery.MaxCharge, 6); } private ChargeState GetNewChargeState() diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarControlConsoleComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarControlConsoleComponent.cs index 6c6d63203c..1670cb23dc 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarControlConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarControlConsoleComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Power.ApcNetComponents; +#nullable enable +using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects.Components.Power; using Content.Shared.Interfaces.GameObjects.Components; @@ -7,6 +8,7 @@ using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Power.PowerNetComponents { @@ -14,28 +16,34 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents [ComponentReference(typeof(IActivate))] public class SolarControlConsoleComponent : SharedSolarControlConsoleComponent, IActivate { -#pragma warning disable 649 - [Dependency] private IEntitySystemManager _entitySystemManager; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - private BoundUserInterface _userInterface; - private PowerReceiverComponent _powerReceiver; - private PowerSolarSystem _powerSolarSystem; - private bool Powered => _powerReceiver.Powered; + private PowerSolarSystem _powerSolarSystem = default!; + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; + + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(SolarControlConsoleUiKey.Key, out var boundUi) + ? boundUi + : null; public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(SolarControlConsoleUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; - _powerReceiver = Owner.GetComponent(); + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + } + + Owner.EnsureComponent(); _powerSolarSystem = _entitySystemManager.GetEntitySystem(); } public void UpdateUIState() { - _userInterface.SetState(new SolarControlConsoleBoundInterfaceState(_powerSolarSystem.TargetPanelRotation, _powerSolarSystem.TargetPanelVelocity, _powerSolarSystem.TotalPanelPower, _powerSolarSystem.TowardsSun)); + UserInterface?.SetState(new SolarControlConsoleBoundInterfaceState(_powerSolarSystem.TargetPanelRotation, _powerSolarSystem.TargetPanelVelocity, _powerSolarSystem.TotalPanelPower, _powerSolarSystem.TowardsSun)); } private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage obj) @@ -57,7 +65,7 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents void IActivate.Activate(ActivateEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) { return; } @@ -69,7 +77,7 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents // always update the UI immediately before opening, just in case UpdateUIState(); - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } } } diff --git a/Content.Server/GameObjects/Components/Projectiles/ExplosiveProjectileComponent.cs b/Content.Server/GameObjects/Components/Projectiles/ExplosiveProjectileComponent.cs index 231e4ab707..2252ad1ea2 100644 --- a/Content.Server/GameObjects/Components/Projectiles/ExplosiveProjectileComponent.cs +++ b/Content.Server/GameObjects/Components/Projectiles/ExplosiveProjectileComponent.cs @@ -15,17 +15,16 @@ namespace Content.Server.GameObjects.Components.Projectiles public override void Initialize() { base.Initialize(); - if (!Owner.HasComponent()) - { - Logger.Error("ExplosiveProjectiles need an ExplosiveComponent"); - throw new InvalidOperationException(); - } + + Owner.EnsureComponent(); } void ICollideBehavior.CollideWith(IEntity entity) { - var explosiveComponent = Owner.GetComponent(); - explosiveComponent.Explosion(); + if (Owner.TryGetComponent(out ExplosiveComponent explosive)) + { + explosive.Explosion(); + } } // Projectile should handle the deleting @@ -34,4 +33,4 @@ namespace Content.Server.GameObjects.Components.Projectiles return; } } -} \ No newline at end of file +} diff --git a/Content.Server/GameObjects/Components/Projectiles/FlashProjectileComponent.cs b/Content.Server/GameObjects/Components/Projectiles/FlashProjectileComponent.cs index d4e8dab9eb..3a9c2f9a4f 100644 --- a/Content.Server/GameObjects/Components/Projectiles/FlashProjectileComponent.cs +++ b/Content.Server/GameObjects/Components/Projectiles/FlashProjectileComponent.cs @@ -31,10 +31,7 @@ namespace Content.Server.GameObjects.Components.Projectiles { base.Initialize(); // Shouldn't be using this without a ProjectileComponent because it will just immediately collide with thrower - if (!Owner.HasComponent()) - { - throw new InvalidOperationException(); - } + Owner.EnsureComponent(); } void ICollideBehavior.CollideWith(IEntity entity) diff --git a/Content.Server/GameObjects/Components/RadioComponent.cs b/Content.Server/GameObjects/Components/RadioComponent.cs index daef1622b0..c2df969c05 100644 --- a/Content.Server/GameObjects/Components/RadioComponent.cs +++ b/Content.Server/GameObjects/Components/RadioComponent.cs @@ -12,10 +12,8 @@ namespace Content.Server.GameObjects.Components [RegisterComponent] class RadioComponent : Component, IUse, IListen { -#pragma warning disable 649 [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IServerNotifyManager _notifyManager = default!; -#pragma warning restore 649 public override string Name => "Radio"; diff --git a/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs b/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs index 124911e98d..93229746f5 100644 --- a/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs +++ b/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs @@ -28,9 +28,7 @@ namespace Content.Server.GameObjects.Components.Recycling [RegisterComponent] public class RecyclerComponent : Component, ICollideBehavior { -#pragma warning disable 649 [Dependency] private readonly IEntityManager _entityManager = default!; -#pragma warning restore 649 public override string Name => "Recycler"; diff --git a/Content.Server/GameObjects/Components/Research/LatheComponent.cs b/Content.Server/GameObjects/Components/Research/LatheComponent.cs index 55b2ddbbcb..f9e434929f 100644 --- a/Content.Server/GameObjects/Components/Research/LatheComponent.cs +++ b/Content.Server/GameObjects/Components/Research/LatheComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -25,15 +26,12 @@ namespace Content.Server.GameObjects.Components.Research { public const int VolumePerSheet = 3750; - private BoundUserInterface _userInterface; - [ViewVariables] public Queue Queue { get; } = new Queue(); [ViewVariables] - public bool Producing { get; private set; } = false; + public bool Producing { get; private set; } - private AppearanceComponent _appearance; private LatheState _state = LatheState.Base; protected virtual LatheState State @@ -42,19 +40,26 @@ namespace Content.Server.GameObjects.Components.Research set => _state = value; } - private LatheRecipePrototype _producingRecipe = null; - private PowerReceiverComponent _powerReceiver; - private bool Powered => _powerReceiver.Powered; + private LatheRecipePrototype? _producingRecipe; + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; private static readonly TimeSpan InsertionTime = TimeSpan.FromSeconds(0.9f); + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(LatheUiKey.Key, out var boundUi) + ? boundUi + : null; + public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(LatheUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - _powerReceiver = Owner.GetComponent(); - _appearance = Owner.GetComponent(); + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } } private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) @@ -66,28 +71,28 @@ namespace Content.Server.GameObjects.Components.Research { case LatheQueueRecipeMessage msg: _prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe); - if (recipe != null) + if (recipe != null!) for (var i = 0; i < msg.Quantity; i++) { Queue.Enqueue(recipe); - _userInterface.SendMessage(new LatheFullQueueMessage(GetIDQueue())); + UserInterface?.SendMessage(new LatheFullQueueMessage(GetIdQueue())); } break; - case LatheSyncRequestMessage msg: - if (!Owner.TryGetComponent(out MaterialStorageComponent storage)) return; - _userInterface.SendMessage(new LatheFullQueueMessage(GetIDQueue())); + case LatheSyncRequestMessage _: + if (!Owner.HasComponent()) return; + UserInterface?.SendMessage(new LatheFullQueueMessage(GetIdQueue())); if (_producingRecipe != null) - _userInterface.SendMessage(new LatheProducingRecipeMessage(_producingRecipe.ID)); + UserInterface?.SendMessage(new LatheProducingRecipeMessage(_producingRecipe.ID)); break; - case LatheServerSelectionMessage msg: - if (!Owner.TryGetComponent(out ResearchClientComponent researchClient)) return; + case LatheServerSelectionMessage _: + if (!Owner.TryGetComponent(out ResearchClientComponent? researchClient)) return; researchClient.OpenUserInterface(message.Session); break; - case LatheServerSyncMessage msg: - if (!Owner.TryGetComponent(out TechnologyDatabaseComponent database) - || !Owner.TryGetComponent(out ProtolatheDatabaseComponent protoDatabase)) return; + case LatheServerSyncMessage _: + if (!Owner.TryGetComponent(out TechnologyDatabaseComponent? database) + || !Owner.TryGetComponent(out ProtolatheDatabaseComponent? protoDatabase)) return; if (database.SyncWithServer()) protoDatabase.Sync(); @@ -103,9 +108,9 @@ namespace Content.Server.GameObjects.Components.Research { return false; } - if (Producing || !CanProduce(recipe) || !Owner.TryGetComponent(out MaterialStorageComponent storage)) return false; + if (Producing || !CanProduce(recipe) || !Owner.TryGetComponent(out MaterialStorageComponent? storage)) return false; - _userInterface.SendMessage(new LatheFullQueueMessage(GetIDQueue())); + UserInterface?.SendMessage(new LatheFullQueueMessage(GetIdQueue())); Producing = true; _producingRecipe = recipe; @@ -116,7 +121,7 @@ namespace Content.Server.GameObjects.Components.Research storage.RemoveMaterial(material, amount); } - _userInterface.SendMessage(new LatheProducingRecipeMessage(recipe.ID)); + UserInterface?.SendMessage(new LatheProducingRecipeMessage(recipe.ID)); State = LatheState.Producing; SetAppearance(LatheVisualState.Producing); @@ -126,7 +131,7 @@ namespace Content.Server.GameObjects.Components.Research Producing = false; _producingRecipe = null; Owner.EntityManager.SpawnEntity(recipe.Result, Owner.Transform.GridPosition); - _userInterface.SendMessage(new LatheStoppedProducingRecipeMessage()); + UserInterface?.SendMessage(new LatheStoppedProducingRecipeMessage()); State = LatheState.Base; SetAppearance(LatheVisualState.Idle); }); @@ -136,12 +141,12 @@ namespace Content.Server.GameObjects.Components.Research public void OpenUserInterface(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } void IActivate.Activate(ActivateEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return; if (!Powered) { @@ -153,12 +158,12 @@ namespace Content.Server.GameObjects.Components.Research async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { - if (!Owner.TryGetComponent(out MaterialStorageComponent storage) - || !eventArgs.Using.TryGetComponent(out MaterialComponent material)) return false; + if (!Owner.TryGetComponent(out MaterialStorageComponent? storage) + || !eventArgs.Using.TryGetComponent(out MaterialComponent? material)) return false; var multiplier = 1; - if (eventArgs.Using.TryGetComponent(out StackComponent stack)) multiplier = stack.Count; + if (eventArgs.Using.TryGetComponent(out StackComponent? stack)) multiplier = stack.Count; var totalAmount = 0; @@ -205,11 +210,13 @@ namespace Content.Server.GameObjects.Components.Research private void SetAppearance(LatheVisualState state) { - if (_appearance != null || Owner.TryGetComponent(out _appearance)) - _appearance.SetData(PowerDeviceVisuals.VisualState, state); + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(PowerDeviceVisuals.VisualState, state); + } } - private Queue GetIDQueue() + private Queue GetIdQueue() { var queue = new Queue(); foreach (var recipePrototype in Queue) diff --git a/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs b/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs index b57d249d3b..fb5de72bd0 100644 --- a/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs +++ b/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.EntitySystems; +#nullable enable +using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects.Components.Research; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects.Components.UserInterface; @@ -14,20 +15,21 @@ namespace Content.Server.GameObjects.Components.Research [RegisterComponent] public class ResearchClientComponent : SharedResearchClientComponent, IActivate { + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + // TODO: Create GUI for changing RD server. - - private BoundUserInterface _userInterface; - -#pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystemManager; -#pragma warning restore 649 + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(ResearchClientUiKey.Key, out var boundUi) + ? boundUi + : null; public bool ConnectedToServer => Server != null; [ViewVariables(VVAccess.ReadOnly)] - public ResearchServerComponent Server { get; set; } + public ResearchServerComponent? Server { get; set; } - public bool RegisterServer(ResearchServerComponent server) + public bool RegisterServer(ResearchServerComponent? server) { var result = server != null && server.RegisterClient(this); return result; @@ -41,23 +43,28 @@ namespace Content.Server.GameObjects.Components.Research public override void Initialize() { base.Initialize(); + // For now it just registers on the first server it can find. var servers = _entitySystemManager.GetEntitySystem().Servers; - if(servers.Count > 0) + + if (servers.Count > 0) RegisterServer(servers[0]); - _userInterface = Owner.GetComponent().GetBoundUserInterface(ResearchClientUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } } public void OpenUserInterface(IPlayerSession session) { UpdateUserInterface(); - _userInterface.Open(session); + UserInterface?.Open(session); } void IActivate.Activate(ActivateEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return; OpenUserInterface(actor.playerSession); @@ -65,7 +72,7 @@ namespace Content.Server.GameObjects.Components.Research public void UpdateUserInterface() { - _userInterface?.SetState(GetNewUiState()); + UserInterface?.SetState(GetNewUiState()); } private ResearchClientBoundInterfaceState GetNewUiState() @@ -73,7 +80,7 @@ namespace Content.Server.GameObjects.Components.Research var rd = _entitySystemManager.GetEntitySystem(); return new ResearchClientBoundInterfaceState(rd.Servers.Count, rd.GetServerNames(), - rd.GetServerIds(), ConnectedToServer ? Server.Id : -1); + rd.GetServerIds(), ConnectedToServer ? Server!.Id : -1); } private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage msg) diff --git a/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs b/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs index 6d2c3380de..4c7aa4a468 100644 --- a/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Power.ApcNetComponents; +#nullable enable +using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Shared.Audio; using Content.Shared.GameObjects.Components.Research; using Content.Shared.Interfaces.GameObjects.Components; @@ -14,6 +15,7 @@ using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; using Robust.Shared.Prototypes; using Robust.Shared.Random; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Research { @@ -21,31 +23,38 @@ namespace Content.Server.GameObjects.Components.Research [ComponentReference(typeof(IActivate))] public class ResearchConsoleComponent : SharedResearchConsoleComponent, IActivate { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IRobustRandom _random; -#pragma warning restore 649 + private const string SoundCollectionName = "keyboard"; - private BoundUserInterface _userInterface; - private ResearchClientComponent _client; - private PowerReceiverComponent _powerReceiver; - private const string _soundCollectionName = "keyboard"; + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; - private bool Powered => _powerReceiver.Powered; + [ViewVariables] + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(ResearchConsoleUiKey.Key, out var boundUi) + ? boundUi + : null; public override void Initialize() { base.Initialize(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(ResearchConsoleUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - _client = Owner.GetComponent(); - _powerReceiver = Owner.GetComponent(); + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } + + Owner.EnsureComponent(); } private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) { - if (!Owner.TryGetComponent(out TechnologyDatabaseComponent database)) return; + if (!Owner.TryGetComponent(out TechnologyDatabaseComponent? database)) + return; + if (!Owner.TryGetComponent(out ResearchClientComponent? client)) + return; if (!Powered) return; @@ -54,8 +63,9 @@ namespace Content.Server.GameObjects.Components.Research case ConsoleUnlockTechnologyMessage msg: var protoMan = IoCManager.Resolve(); if (!protoMan.TryIndex(msg.Id, out TechnologyPrototype tech)) break; - if(!_client.Server.CanUnlockTechnology(tech)) break; - if (_client.Server.UnlockTechnology(tech)) + if (client.Server == null) break; + if (!client.Server.CanUnlockTechnology(tech)) break; + if (client.Server.UnlockTechnology(tech)) { database.SyncWithServer(); database.Dirty(); @@ -64,13 +74,12 @@ namespace Content.Server.GameObjects.Components.Research break; - case ConsoleServerSyncMessage msg: + case ConsoleServerSyncMessage _: database.SyncWithServer(); UpdateUserInterface(); break; - case ConsoleServerSelectionMessage msg: - if (!Owner.TryGetComponent(out ResearchClientComponent client)) break; + case ConsoleServerSelectionMessage _: client.OpenUserInterface(message.Session); break; } @@ -81,13 +90,17 @@ namespace Content.Server.GameObjects.Components.Research /// public void UpdateUserInterface() { - _userInterface.SetState(GetNewUiState()); + UserInterface?.SetState(GetNewUiState()); } private ResearchConsoleBoundInterfaceState GetNewUiState() { - var points = _client.ConnectedToServer ? _client.Server.Point : 0; - var pointsPerSecond = _client.ConnectedToServer ? _client.Server.PointsPerSecond : 0; + if (!Owner.TryGetComponent(out ResearchClientComponent? client) || + client.Server == null) + return new ResearchConsoleBoundInterfaceState(default, default); + + var points = client.ConnectedToServer ? client.Server.Point : 0; + var pointsPerSecond = client.ConnectedToServer ? client.Server.PointsPerSecond : 0; return new ResearchConsoleBoundInterfaceState(points, pointsPerSecond); } @@ -98,12 +111,12 @@ namespace Content.Server.GameObjects.Components.Research /// Session where the UI will be shown public void OpenUserInterface(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } void IActivate.Activate(ActivateEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return; if (!Powered) { @@ -112,17 +125,14 @@ namespace Content.Server.GameObjects.Components.Research OpenUserInterface(actor.playerSession); PlayKeyboardSound(); - return; } private void PlayKeyboardSound() { - var soundCollection = _prototypeManager.Index(_soundCollectionName); + var soundCollection = _prototypeManager.Index(SoundCollectionName); var file = _random.Pick(soundCollection.PickFiles); var audioSystem = EntitySystem.Get(); audioSystem.PlayFromEntity(file,Owner,AudioParams.Default); } - - } } diff --git a/Content.Server/GameObjects/Components/Research/ResearchServerComponent.cs b/Content.Server/GameObjects/Components/Research/ResearchServerComponent.cs index cf3811f8f2..4b117a7f7f 100644 --- a/Content.Server/GameObjects/Components/Research/ResearchServerComponent.cs +++ b/Content.Server/GameObjects/Components/Research/ResearchServerComponent.cs @@ -73,7 +73,7 @@ namespace Content.Server.GameObjects.Components.Research base.Initialize(); Id = ServerCount++; EntitySystem.Get()?.RegisterServer(this); - Database = Owner.GetComponent(); + Database = Owner.EnsureComponent(); Owner.TryGetComponent(out _powerReceiver); } diff --git a/Content.Server/GameObjects/Components/Rotatable/FlippableComponent.cs b/Content.Server/GameObjects/Components/Rotatable/FlippableComponent.cs index ad524e517c..43bd0ac6c2 100644 --- a/Content.Server/GameObjects/Components/Rotatable/FlippableComponent.cs +++ b/Content.Server/GameObjects/Components/Rotatable/FlippableComponent.cs @@ -14,9 +14,7 @@ namespace Content.Server.GameObjects.Components.Rotatable [RegisterComponent] public class FlippableComponent : Component { -#pragma warning disable 649 [Dependency] private readonly IServerNotifyManager _notifyManager = default!; -#pragma warning restore 649 public override string Name => "Flippable"; diff --git a/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs b/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs index 6929d9f981..2e1d47a898 100644 --- a/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs +++ b/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs @@ -1,8 +1,8 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Power.ApcNetComponents; -using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects.Components.VendingMachines; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; @@ -29,27 +29,28 @@ namespace Content.Server.GameObjects.Components.VendingMachines [ComponentReference(typeof(IActivate))] public class VendingMachineComponent : SharedVendingMachineComponent, IActivate, IExamine, IBreakAct, IWires { -#pragma warning disable 649 - [Dependency] private readonly IRobustRandom _random; -#pragma warning restore 649 - private AppearanceComponent _appearance; - private BoundUserInterface _userInterface; - private PowerReceiverComponent _powerReceiver; + [Dependency] private readonly IRobustRandom _random = default!; - private bool _ejecting = false; + private bool _ejecting; private TimeSpan _animationDuration = TimeSpan.Zero; - private string _packPrototypeId; - private string _description; - private string _spriteName; + private string _packPrototypeId = ""; + private string? _description; + private string _spriteName = ""; - private bool Powered => _powerReceiver.Powered; - private bool _broken = false; + private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; + private bool _broken; - private string _soundVend; + private string _soundVend = ""; + + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(VendingMachineUiKey.Key, out var boundUi) + ? boundUi + : null; public void Activate(ActivateEventArgs eventArgs) { - if(!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if(!eventArgs.User.TryGetComponent(out IActorComponent? actor)) { return; } @@ -62,7 +63,7 @@ namespace Content.Server.GameObjects.Components.VendingMachines wires.OpenInterface(actor.playerSession); } else { - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } } @@ -106,25 +107,32 @@ namespace Content.Server.GameObjects.Components.VendingMachines public override void Initialize() { base.Initialize(); - _appearance = Owner.GetComponent(); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(VendingMachineUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - _powerReceiver = Owner.GetComponent(); - _powerReceiver.OnPowerStateChanged += UpdatePower; - TrySetVisualState(_powerReceiver.Powered ? VendingMachineVisualState.Normal : VendingMachineVisualState.Off); + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } + + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) + { + receiver.OnPowerStateChanged += UpdatePower; + TrySetVisualState(receiver.Powered ? VendingMachineVisualState.Normal : VendingMachineVisualState.Off); + } + InitializeFromPrototype(); } public override void OnRemove() { - _appearance = null; - _powerReceiver.OnPowerStateChanged -= UpdatePower; - _powerReceiver = null; + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) + { + receiver.OnPowerStateChanged -= UpdatePower; + } + base.OnRemove(); } - private void UpdatePower(object sender, PowerStateEventArgs args) + private void UpdatePower(object? sender, PowerStateEventArgs args) { var state = args.Powered ? VendingMachineVisualState.Normal : VendingMachineVisualState.Off; TrySetVisualState(state); @@ -141,8 +149,8 @@ namespace Content.Server.GameObjects.Components.VendingMachines case VendingMachineEjectMessage msg: TryEject(msg.ID); break; - case InventorySyncRequestMessage msg: - _userInterface.SendMessage(new VendingMachineInventoryMessage(Inventory)); + case InventorySyncRequestMessage _: + UserInterface?.SendMessage(new VendingMachineInventoryMessage(Inventory)); break; } } @@ -160,7 +168,7 @@ namespace Content.Server.GameObjects.Components.VendingMachines return; } - VendingMachineInventoryEntry entry = Inventory.Find(x => x.ID == id); + var entry = Inventory.Find(x => x.ID == id); if (entry == null) { FlickDenyAnimation(); @@ -175,7 +183,7 @@ namespace Content.Server.GameObjects.Components.VendingMachines _ejecting = true; entry.Amount--; - _userInterface.SendMessage(new VendingMachineInventoryMessage(Inventory)); + UserInterface?.SendMessage(new VendingMachineInventoryMessage(Inventory)); TrySetVisualState(VendingMachineVisualState.Eject); Timer.Spawn(_animationDuration, () => @@ -204,14 +212,20 @@ namespace Content.Server.GameObjects.Components.VendingMachines if (_broken) { finalState = VendingMachineVisualState.Broken; - } else if (_ejecting) + } + else if (_ejecting) { finalState = VendingMachineVisualState.Eject; - } else if (!Powered) + } + else if (!Powered) { finalState = VendingMachineVisualState.Off; } - _appearance.SetData(VendingMachineVisuals.VisualState, finalState); + + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(VendingMachineVisuals.VisualState, finalState); + } } public void OnBreak(BreakageEventArgs eventArgs) diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs index aca55f1921..dfc2f61b8a 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs @@ -158,7 +158,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { base.OnAdd(); var rangedWeaponComponent = Owner.GetComponent(); - rangedWeaponComponent.Barrel = this; + + rangedWeaponComponent.Barrel ??= this; rangedWeaponComponent.FireHandler += Fire; rangedWeaponComponent.WeaponCanFireHandler += WeaponCanFire; } diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs index 6867052599..aa8120e16e 100644 --- a/Content.Server/GameObjects/Components/WiresComponent.cs +++ b/Content.Server/GameObjects/Components/WiresComponent.cs @@ -33,13 +33,11 @@ namespace Content.Server.GameObjects.Components [RegisterComponent] public class WiresComponent : SharedWiresComponent, IInteractUsing, IExamine, IMapInit { -#pragma warning disable 649 [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IServerNotifyManager _notifyManager = default!; -#pragma warning restore 649 + private AudioSystem _audioSystem = default!; private AppearanceComponent _appearance = default!; - private BoundUserInterface _userInterface = default!; private bool _isPanelOpen; @@ -140,15 +138,23 @@ namespace Content.Server.GameObjects.Components [ViewVariables] private string? _layoutId; + private BoundUserInterface? UserInterface => + Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && + ui.TryGetBoundUserInterface(WiresUiKey.Key, out var boundUi) + ? boundUi + : null; + public override void Initialize() { base.Initialize(); _audioSystem = EntitySystem.Get(); _appearance = Owner.GetComponent(); _appearance.SetData(WiresVisuals.MaintenancePanelState, IsPanelOpen); - _userInterface = Owner.GetComponent() - .GetBoundUserInterface(WiresUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + } } private void GenerateSerialNumber() @@ -357,7 +363,7 @@ namespace Content.Server.GameObjects.Components /// public void OpenInterface(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) @@ -450,7 +456,7 @@ namespace Content.Server.GameObjects.Components entry.Letter)); } - _userInterface.SetState( + UserInterface?.SetState( new WiresBoundUserInterfaceState( clientList.ToArray(), _statuses.Select(p => new StatusEntry(p.Key, p.Value)).ToArray(), diff --git a/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs index b04aca780e..e8470bff1e 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs @@ -24,17 +24,17 @@ namespace Content.Server.GameObjects.EntitySystems.AI [Dependency] private readonly IReflectionManager _reflectionManager = default!; private readonly Dictionary _processorTypes = new Dictionary(); - + /// /// To avoid iterating over dead AI continuously they can wake and sleep themselves when necessary. /// private readonly HashSet _awakeAi = new HashSet(); - + // To avoid modifying awakeAi while iterating over it. private readonly List _queuedSleepMessages = new List(); public bool IsAwake(AiLogicProcessor processor) => _awakeAi.Contains(processor); - + /// public override void Initialize() { @@ -66,9 +66,9 @@ namespace Content.Server.GameObjects.EntitySystems.AI break; } } - + _queuedSleepMessages.Clear(); - + foreach (var processor in _awakeAi) { processor.Update(frameTime); @@ -87,7 +87,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI /// public void ProcessorInitialize(AiControllerComponent controller) { - if (controller.Processor != null) return; + if (controller.Processor != null || controller.LogicName == null) return; controller.Processor = CreateProcessor(controller.LogicName); controller.Processor.SelfEntity = controller.Owner; controller.Processor.Setup(); diff --git a/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs index 96f93bb722..47f0bc50f3 100644 --- a/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs @@ -153,13 +153,20 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos private bool TryRefreshTile(GridAtmosphereComponent gam, GasOverlayData oldTile, MapIndices indices, out GasOverlayData overlayData) { var tile = gam.GetTile(indices); + + if (tile == null) + { + overlayData = default; + return false; + } + var tileData = new List(); for (byte i = 0; i < Atmospherics.TotalNumberOfGases; i++) { var gas = Atmospherics.GetGas(i); var overlay = Atmospherics.GetOverlay(i); - if (overlay == null || tile.Air == null) continue; + if (overlay == null || tile?.Air == null) continue; var moles = tile.Air.Gases[i]; @@ -169,7 +176,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos tileData.Add(data); } - overlayData = new GasOverlayData(tile.Hotspot.State, tile.Hotspot.Temperature, tileData.Count == 0 ? null : tileData.ToArray()); + overlayData = new GasOverlayData(tile!.Hotspot.State, tile.Hotspot.Temperature, tileData.Count == 0 ? null : tileData.ToArray()); if (overlayData.Equals(oldTile)) { diff --git a/Content.Server/GameObjects/EntitySystems/MoverSystem.cs b/Content.Server/GameObjects/EntitySystems/MoverSystem.cs index 41ded38a03..31087803c6 100644 --- a/Content.Server/GameObjects/EntitySystems/MoverSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/MoverSystem.cs @@ -30,13 +30,11 @@ namespace Content.Server.GameObjects.EntitySystems [UsedImplicitly] internal class MoverSystem : SharedMoverSystem { -#pragma warning disable 649 [Dependency] private readonly IPauseManager _pauseManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; -#pragma warning restore 649 private AudioSystem _audioSystem = default!; diff --git a/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs index f621c3f636..8557e738df 100644 --- a/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs +++ b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs @@ -21,9 +21,7 @@ namespace Content.Shared.GameObjects.Components.Damage [ComponentReference(typeof(IDamageableComponent))] public class DamageableComponent : Component, IDamageableComponent { -#pragma warning disable 649 [Dependency] private readonly IPrototypeManager _prototypeManager = default!; -#pragma warning restore 649 public override string Name => "Damageable"; diff --git a/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs index 4d05195dba..d83b7b89d9 100644 --- a/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs +++ b/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Physics; @@ -7,6 +8,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Physics; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -116,12 +118,18 @@ namespace Content.Shared.GameObjects.Components.Movement public override void Initialize() { base.Initialize(); - var collidable = Owner.GetComponent(); + + var collidable = Owner.EnsureComponent(); collidable.Hard = false; - var shape = collidable.PhysicsShapes[0]; - shape.CollisionLayer |= (int) CollisionGroup.SmallImpassable; - shape.CollisionMask = (int)CollisionGroup.None; + + var shape = collidable.PhysicsShapes.FirstOrDefault(); + + if (shape != null) + { + shape.CollisionLayer |= (int) CollisionGroup.SmallImpassable; + shape.CollisionMask = (int) CollisionGroup.None; + } } public override void ExposeData(ObjectSerializer serializer) diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index ac230e6680..632333c3cc 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -66,6 +66,7 @@ <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Lidgren.Network" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> True True + True True True True From 0b4ca168d4690d813b61fe77462562e363abfc23 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 23 Aug 2020 20:42:59 +1000 Subject: [PATCH 35/88] Fix AI steering throw (#1872) Entity steering wasn't properly checking if the target was deleted. Co-authored-by: Metal Gear Sloth --- .../AI/Steering/AiSteeringSystem.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs index 27b85bc7af..2411fcc984 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs @@ -249,11 +249,19 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering private SteeringStatus Steer(IEntity entity, IAiSteeringRequest steeringRequest, float frameTime) { // Main optimisation to be done below is the redundant calls and adding more variables - if (!entity.TryGetComponent(out AiControllerComponent controller) || !ActionBlockerSystem.CanMove(entity)) + if (entity.Deleted || !entity.TryGetComponent(out AiControllerComponent controller) || !ActionBlockerSystem.CanMove(entity)) { return SteeringStatus.NoPath; } + var entitySteering = steeringRequest as EntityTargetSteeringRequest; + + if (entitySteering != null && entitySteering.Target.Deleted) + { + controller.VelocityDir = Vector2.Zero; + return SteeringStatus.NoPath; + } + if (_pauseManager.IsGridPaused(entity.Transform.GridID)) { controller.VelocityDir = Vector2.Zero; @@ -320,9 +328,9 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering UpdatePath(entity, path); // If we're targeting entity get a fixed tile; if they move from it then re-path (at least til we get a better solution) - if (steeringRequest is EntityTargetSteeringRequest entitySteeringRequest) + if (entitySteering != null) { - _entityTargetPosition[entity] = entitySteeringRequest.TargetGrid; + _entityTargetPosition[entity] = entitySteering.TargetGrid; } // Move next tick @@ -342,22 +350,17 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering // Check if the target entity has moved - If so then re-path // TODO: Patch the path from the target's position back towards us, stopping if it ever intersects the current path // Probably need a separate "PatchPath" job - if (steeringRequest is EntityTargetSteeringRequest entitySteer) + if (entitySteering != null) { - if (entitySteer.Target.Deleted) - { - controller.VelocityDir = Vector2.Zero; - return SteeringStatus.NoPath; - } - // Check if target's moved too far - if (_entityTargetPosition.TryGetValue(entity, out var targetGrid) && (entitySteer.TargetGrid.Position - targetGrid.Position).Length >= entitySteer.TargetMaxMove) + if (_entityTargetPosition.TryGetValue(entity, out var targetGrid) && + (entitySteering.TargetGrid.Position - targetGrid.Position).Length >= entitySteering.TargetMaxMove) { // We'll just repath and keep following the existing one until we get a new one RequestPath(entity, steeringRequest); } - ignoredCollision.Add(entitySteer.Target); + ignoredCollision.Add(entitySteering.Target); } HandleStuck(entity); From a4a25a9975ce9559fdacd967d36950f39b8ffc0c Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sun, 23 Aug 2020 12:53:09 +0200 Subject: [PATCH 36/88] Remove localization manager dependencies from components (#1864) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> --- .../Chemistry/ChemMaster/ChemMasterWindow.cs | 41 +++++++++---------- .../ReagentDispenserBoundUserInterface.cs | 6 +-- .../ReagentDispenserWindow.cs | 25 ++++++----- .../ConstructionGhostComponent.cs | 6 +-- .../ResearchClientServerSelectionMenu.cs | 6 +-- .../Access/IdCardConsoleComponent.cs | 5 +-- .../Chemistry/ChemMasterComponent.cs | 11 +++-- .../Components/Chemistry/PourableComponent.cs | 5 +-- .../Chemistry/ReagentDispenserComponent.cs | 11 +++-- .../Components/Chemistry/SolutionComponent.cs | 7 ++-- .../Components/Fluids/BucketComponent.cs | 6 +-- .../Components/Fluids/MopComponent.cs | 7 +--- .../Components/Fluids/PuddleComponent.cs | 3 +- .../Interactable/HandheldLightComponent.cs | 9 ++-- .../Components/Items/DiceComponent.cs | 6 +-- .../PowerReceiverUsers/BaseCharger.cs | 4 +- .../Rotatable/RotatableComponent.cs | 3 +- .../Components/Stack/StackComponent.cs | 3 +- .../Components/Weapon/Melee/FlashComponent.cs | 5 +-- .../Weapon/Melee/StunbatonComponent.cs | 9 ++-- .../GameObjects/Components/WiresComponent.cs | 4 +- .../EntitySystems/SharedConstructionSystem.cs | 8 +--- 22 files changed, 73 insertions(+), 117 deletions(-) diff --git a/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs b/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs index 08ec838c88..4bf3482a2c 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs @@ -21,6 +21,10 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster /// public class ChemMasterWindow : SS14Window { +#pragma warning disable 649 + [Dependency] private readonly IPrototypeManager _prototypeManager; +#pragma warning restore 649 + /// Contains info about the reagent container such as it's contents, if one is loaded into the dispenser. private readonly VBoxContainer ContainerInfo; @@ -45,11 +49,6 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster public Button CreatePills { get; } public Button CreateBottles { get; } -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 - protected override Vector2? CustomSize => (400, 200); /// @@ -69,9 +68,9 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { Children = { - new Label {Text = _localizationManager.GetString("Container")}, + new Label {Text = Loc.GetString("Container")}, new Control {SizeFlagsHorizontal = SizeFlags.FillExpand}, - (EjectButton = new Button {Text = _localizationManager.GetString("Eject")}) + (EjectButton = new Button {Text = Loc.GetString("Eject")}) } }, //Wrap the container info in a PanelContainer so we can color it's background differently. @@ -94,7 +93,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { new Label { - Text = _localizationManager.GetString("No container loaded.") + Text = Loc.GetString("No container loaded.") } } }), @@ -109,10 +108,10 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { Children = { - new Label {Text = _localizationManager.GetString("Buffer")}, + new Label {Text = Loc.GetString("Buffer")}, new Control {SizeFlagsHorizontal = SizeFlags.FillExpand}, - (BufferTransferButton = new Button {Text = _localizationManager.GetString("Transfer"), Pressed = BufferModeTransfer, StyleClasses = { StyleBase.ButtonOpenRight }}), - (BufferDiscardButton = new Button {Text = _localizationManager.GetString("Discard"), Pressed = !BufferModeTransfer, StyleClasses = { StyleBase.ButtonOpenLeft }}) + (BufferTransferButton = new Button {Text = Loc.GetString("Transfer"), Pressed = BufferModeTransfer, StyleClasses = { StyleBase.ButtonOpenRight }}), + (BufferDiscardButton = new Button {Text = Loc.GetString("Discard"), Pressed = !BufferModeTransfer, StyleClasses = { StyleBase.ButtonOpenLeft }}) } }, @@ -136,7 +135,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { new Label { - Text = _localizationManager.GetString("Buffer empty.") + Text = Loc.GetString("Buffer empty.") } } }), @@ -151,7 +150,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { Children = { - new Label {Text = _localizationManager.GetString("Packaging ")}, + new Label {Text = Loc.GetString("Packaging ")}, } }, @@ -185,7 +184,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { new Label { - Text = _localizationManager.GetString("Pills:") + Text = Loc.GetString("Pills:") }, }, @@ -212,7 +211,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster }; PillInfo.AddChild((pillVolume)); - CreatePills = new Button {Text = _localizationManager.GetString("Create")}; + CreatePills = new Button {Text = Loc.GetString("Create")}; PillInfo.AddChild(CreatePills); //Bottles @@ -222,7 +221,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { new Label { - Text = _localizationManager.GetString("Bottles:") + Text = Loc.GetString("Bottles:") }, }, @@ -249,7 +248,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster }; BottleInfo.AddChild((bottleVolume)); - CreateBottles = new Button {Text = _localizationManager.GetString("Create")}; + CreateBottles = new Button {Text = Loc.GetString("Create")}; BottleInfo.AddChild(CreateBottles); } @@ -314,7 +313,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster if (!state.HasBeaker) { - ContainerInfo.Children.Add(new Label {Text = _localizationManager.GetString("No container loaded.")}); + ContainerInfo.Children.Add(new Label {Text = Loc.GetString("No container loaded.")}); return; } @@ -333,7 +332,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster foreach (var reagent in state.ContainerReagents) { - var name = _localizationManager.GetString("Unknown reagent"); + var name = Loc.GetString("Unknown reagent"); //Try to the prototype for the given reagent. This gives us it's name. if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto)) { @@ -370,7 +369,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster if (!state.BufferReagents.Any()) { - BufferInfo.Children.Add(new Label {Text = _localizationManager.GetString("Buffer empty.")}); + BufferInfo.Children.Add(new Label {Text = Loc.GetString("Buffer empty.")}); return; } @@ -388,7 +387,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster foreach (var reagent in state.BufferReagents) { - var name = _localizationManager.GetString("Unknown reagent"); + var name = Loc.GetString("Unknown reagent"); //Try to the prototype for the given reagent. This gives us it's name. if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto)) { diff --git a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserBoundUserInterface.cs b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserBoundUserInterface.cs index ccf3a0f3b9..07ecba5030 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserBoundUserInterface.cs @@ -17,10 +17,6 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser [UsedImplicitly] public class ReagentDispenserBoundUserInterface : BoundUserInterface { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 - private ReagentDispenserWindow _window; private ReagentDispenserBoundUserInterfaceState _lastState; @@ -41,7 +37,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser //Setup window layout/elements _window = new ReagentDispenserWindow { - Title = _localizationManager.GetString("Reagent dispenser"), + Title = Loc.GetString("Reagent dispenser"), }; _window.OpenCentered(); diff --git a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs index 0389b9aa35..76b168eae3 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs @@ -20,6 +20,10 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser /// public class ReagentDispenserWindow : SS14Window { +#pragma warning disable 649 + [Dependency] private readonly IPrototypeManager _prototypeManager; +#pragma warning restore 649 + /// Contains info about the reagent container such as it's contents, if one is loaded into the dispenser. private readonly VBoxContainer ContainerInfo; @@ -50,11 +54,6 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser /// A grid of buttons for each reagent which can be dispensed. public GridContainer ChemicalList { get; } -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 - protected override Vector2? CustomSize => (500, 600); /// @@ -76,7 +75,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser { Children = { - new Label {Text = _localizationManager.GetString("Amount")}, + new Label {Text = Loc.GetString("Amount")}, //Padding new Control {CustomMinimumSize = (20, 0)}, (DispenseButton1 = new Button {Text = "1", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenRight }}), @@ -100,9 +99,9 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser { Children = { - new Label {Text = _localizationManager.GetString("Container: ")}, - (ClearButton = new Button {Text = _localizationManager.GetString("Clear"), StyleClasses = {StyleBase.ButtonOpenRight}}), - (EjectButton = new Button {Text = _localizationManager.GetString("Eject"), StyleClasses = {StyleBase.ButtonOpenLeft}}) + new Label {Text = Loc.GetString("Container: ")}, + (ClearButton = new Button {Text = Loc.GetString("Clear"), StyleClasses = {StyleBase.ButtonOpenRight}}), + (EjectButton = new Button {Text = Loc.GetString("Eject"), StyleClasses = {StyleBase.ButtonOpenLeft}}) } }, //Wrap the container info in a PanelContainer so we can color it's background differently. @@ -125,7 +124,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser { new Label { - Text = _localizationManager.GetString("No container loaded.") + Text = Loc.GetString("No container loaded.") } } }), @@ -155,7 +154,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser } else { - ChemicalList.AddChild(new Button {Text = _localizationManager.GetString("Reagent name not found")}); + ChemicalList.AddChild(new Button {Text = Loc.GetString("Reagent name not found")}); } } } @@ -243,7 +242,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser if (!state.HasBeaker) { - ContainerInfo.Children.Add(new Label {Text = _localizationManager.GetString("No container loaded.")}); + ContainerInfo.Children.Add(new Label {Text = Loc.GetString("No container loaded.")}); return; } @@ -267,7 +266,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser foreach (var reagent in state.ContainerReagents) { - var name = _localizationManager.GetString("Unknown reagent"); + var name = Loc.GetString("Unknown reagent"); //Try to the prototype for the given reagent. This gives us it's name. if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto)) { diff --git a/Content.Client/GameObjects/Components/Construction/ConstructionGhostComponent.cs b/Content.Client/GameObjects/Components/Construction/ConstructionGhostComponent.cs index 11fd598acf..28adc6a316 100644 --- a/Content.Client/GameObjects/Components/Construction/ConstructionGhostComponent.cs +++ b/Content.Client/GameObjects/Components/Construction/ConstructionGhostComponent.cs @@ -2,7 +2,6 @@ using Content.Shared.GameObjects.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; -using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; @@ -12,9 +11,6 @@ namespace Content.Client.GameObjects.Components.Construction [RegisterComponent] public class ConstructionGhostComponent : Component, IExamine { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _loc; -#pragma warning restore 649 public override string Name => "ConstructionGhost"; [ViewVariables] public ConstructionPrototype Prototype { get; set; } @@ -22,7 +18,7 @@ namespace Content.Client.GameObjects.Components.Construction void IExamine.Examine(FormattedMessage message, bool inDetailsRange) { - message.AddText(_loc.GetString("Building: {0}\n", Prototype.Name)); + message.AddText(Loc.GetString("Building: {0}\n", Prototype.Name)); EntitySystem.Get().DoExamine(message, Prototype, 0, inDetailsRange); } } diff --git a/Content.Client/GameObjects/Components/Research/ResearchClientServerSelectionMenu.cs b/Content.Client/GameObjects/Components/Research/ResearchClientServerSelectionMenu.cs index f6b39e12da..052f48c452 100644 --- a/Content.Client/GameObjects/Components/Research/ResearchClientServerSelectionMenu.cs +++ b/Content.Client/GameObjects/Components/Research/ResearchClientServerSelectionMenu.cs @@ -14,10 +14,6 @@ namespace Content.Client.GameObjects.Components.Research private int[] _serverIds = new int[]{}; private int _selectedServerId = -1; -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 - protected override Vector2? CustomSize => (300, 300); public ResearchClientBoundUserInterface Owner { get; set; } @@ -25,7 +21,7 @@ namespace Content.Client.GameObjects.Components.Research { IoCManager.InjectDependencies(this); - Title = _localizationManager.GetString("Research Server Selection"); + Title = Loc.GetString("Research Server Selection"); _servers = new ItemList() {SelectMode = ItemList.ItemListSelectMode.Single}; diff --git a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs index 9fa417bc1a..058a501b03 100644 --- a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs @@ -25,7 +25,6 @@ namespace Content.Server.GameObjects.Components.Access public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate { [Dependency] private readonly IServerNotifyManager _notifyManager = default!; - [Dependency] private readonly ILocalizationManager _localizationManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; private ContainerSlot _privilegedIdContainer = default!; @@ -137,7 +136,7 @@ namespace Content.Server.GameObjects.Components.Access { if (!user.TryGetComponent(out IHandsComponent? hands)) { - _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("You have no hands.")); + _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("You have no hands.")); return; } @@ -166,7 +165,7 @@ namespace Content.Server.GameObjects.Components.Access if (!hands.Drop(hands.ActiveHand, container)) { - _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("You can't let go of the ID card!")); + _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("You can't let go of the ID card!")); return; } UpdateUserInterface(); diff --git a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs index 84cc2ca642..14a6413a4c 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs @@ -43,7 +43,6 @@ namespace Content.Server.GameObjects.Components.Chemistry public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing, ISolutionChange { [Dependency] private readonly IServerNotifyManager _notifyManager = default!; - [Dependency] private readonly ILocalizationManager _localizationManager = default!; [ViewVariables] private ContainerSlot _beakerContainer = default!; [ViewVariables] private string _packPrototypeId = ""; @@ -373,7 +372,7 @@ namespace Content.Server.GameObjects.Components.Chemistry if (!args.User.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, - _localizationManager.GetString("You have no hands.")); + Loc.GetString("You have no hands.")); return; } @@ -396,7 +395,7 @@ namespace Content.Server.GameObjects.Components.Chemistry if (!args.User.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, - _localizationManager.GetString("You have no hands.")); + Loc.GetString("You have no hands.")); return true; } @@ -413,13 +412,13 @@ namespace Content.Server.GameObjects.Components.Chemistry if (HasBeaker) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, - _localizationManager.GetString("This ChemMaster already has a container in it.")); + Loc.GetString("This ChemMaster already has a container in it.")); } else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0) //Close enough to a chem master... { //If it can't fit in the chem master, don't put it in. For example, buckets and mop buckets can't fit. _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, - _localizationManager.GetString("That can't fit in the ChemMaster.")); + Loc.GetString("That can't fit in the ChemMaster.")); } else { @@ -430,7 +429,7 @@ namespace Content.Server.GameObjects.Components.Chemistry else { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, - _localizationManager.GetString("You can't put this in the ChemMaster.")); + Loc.GetString("You can't put this in the ChemMaster.")); } return true; diff --git a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs b/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs index e11d7d7438..4f6350f0ab 100644 --- a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs @@ -21,7 +21,6 @@ namespace Content.Server.GameObjects.Components.Chemistry { #pragma warning disable 649 [Dependency] private readonly IServerNotifyManager _notifyManager; - [Dependency] private readonly ILocalizationManager _localizationManager; #pragma warning restore 649 public override string Name => "Pourable"; @@ -91,7 +90,7 @@ namespace Content.Server.GameObjects.Components.Chemistry if (realTransferAmount <= 0) //Special message if container is full { _notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User, - _localizationManager.GetString("Container is full")); + Loc.GetString("Container is full")); return false; } @@ -101,7 +100,7 @@ namespace Content.Server.GameObjects.Components.Chemistry return false; _notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User, - _localizationManager.GetString("Transferred {0}u", removedSolution.TotalVolume)); + Loc.GetString("Transferred {0}u", removedSolution.TotalVolume)); return true; } diff --git a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs index ae227b58b5..f161403c82 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs @@ -40,7 +40,6 @@ namespace Content.Server.GameObjects.Components.Chemistry public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing, ISolutionChange { [Dependency] private readonly IServerNotifyManager _notifyManager = default!; - [Dependency] private readonly ILocalizationManager _localizationManager = default!; [ViewVariables] private ContainerSlot _beakerContainer = default!; [ViewVariables] private string _packPrototypeId = ""; @@ -286,7 +285,7 @@ namespace Content.Server.GameObjects.Components.Chemistry if (!args.User.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, - _localizationManager.GetString("You have no hands.")); + Loc.GetString("You have no hands.")); return; } @@ -309,7 +308,7 @@ namespace Content.Server.GameObjects.Components.Chemistry if (!args.User.TryGetComponent(out IHandsComponent? hands)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, - _localizationManager.GetString("You have no hands.")); + Loc.GetString("You have no hands.")); return true; } @@ -326,13 +325,13 @@ namespace Content.Server.GameObjects.Components.Chemistry if (HasBeaker) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, - _localizationManager.GetString("This dispenser already has a container in it.")); + Loc.GetString("This dispenser already has a container in it.")); } else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0) { //If it can't fit in the dispenser, don't put it in. For example, buckets and mop buckets can't fit. _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, - _localizationManager.GetString("That can't fit in the dispenser.")); + Loc.GetString("That can't fit in the dispenser.")); } else { @@ -343,7 +342,7 @@ namespace Content.Server.GameObjects.Components.Chemistry else { _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, - _localizationManager.GetString("You can't put this in the dispenser.")); + Loc.GetString("You can't put this in the dispenser.")); } return true; diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs index 53e3ca2463..248d18143a 100644 --- a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs @@ -31,7 +31,6 @@ namespace Content.Server.GameObjects.Components.Chemistry { #pragma warning disable 649 [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly ILocalizationManager _loc; [Dependency] private readonly IEntitySystemManager _entitySystemManager; #pragma warning restore 649 @@ -276,7 +275,7 @@ namespace Content.Server.GameObjects.Components.Chemistry return; } - message.AddText(_loc.GetString("Contains:\n")); + message.AddText(Loc.GetString("Contains:\n")); if (ReagentList.Count == 0) { message.AddText("Nothing.\n"); @@ -303,12 +302,12 @@ namespace Content.Server.GameObjects.Components.Chemistry colorIsh = "Blue"; } - message.AddText(_loc.GetString("A {0} liquid\n", colorIsh)); + message.AddText(Loc.GetString("A {0} liquid\n", colorIsh)); } } else { - message.AddText(_loc.GetString("Unknown reagent: {0}u\n", reagent.Quantity)); + message.AddText(Loc.GetString("Unknown reagent: {0}u\n", reagent.Quantity)); } } } diff --git a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs index 4f9afed4af..e94cc91f43 100644 --- a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs @@ -20,8 +20,6 @@ namespace Content.Server.GameObjects.Components.Fluids [RegisterComponent] public class BucketComponent : Component, IInteractUsing { - [Dependency] private readonly ILocalizationManager _localizationManager = default!; - public override string Name => "Bucket"; public ReagentUnit MaxVolume @@ -101,7 +99,7 @@ namespace Content.Server.GameObjects.Components.Fluids return false; } - Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Splish")); + Owner.PopupMessage(eventArgs.User, Loc.GetString("Splish")); return true; } @@ -119,7 +117,7 @@ namespace Content.Server.GameObjects.Components.Fluids } // Give some visual feedback shit's happening (for anyone who can't hear sound) - Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Sploosh")); + Owner.PopupMessage(eventArgs.User, Loc.GetString("Sploosh")); if (_sound == null) { diff --git a/Content.Server/GameObjects/Components/Fluids/MopComponent.cs b/Content.Server/GameObjects/Components/Fluids/MopComponent.cs index b24b529b43..11188bcd5f 100644 --- a/Content.Server/GameObjects/Components/Fluids/MopComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/MopComponent.cs @@ -6,7 +6,6 @@ using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; -using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Serialization; @@ -18,10 +17,6 @@ namespace Content.Server.GameObjects.Components.Fluids [RegisterComponent] public class MopComponent : Component, IAfterInteract { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 - public override string Name => "Mop"; internal SolutionComponent Contents => _contents; private SolutionComponent _contents; @@ -115,7 +110,7 @@ namespace Content.Server.GameObjects.Components.Fluids } // Give some visual feedback shit's happening (for anyone who can't hear sound) - Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Swish")); + Owner.PopupMessage(eventArgs.User, Loc.GetString("Swish")); if (_pickupSound == null) { diff --git a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs index cc598e7455..a3703a5e21 100644 --- a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs @@ -48,7 +48,6 @@ namespace Content.Server.GameObjects.Components.Fluids #pragma warning disable 649 [Dependency] private readonly IMapManager _mapManager; - [Dependency] private readonly ILocalizationManager _loc; #pragma warning restore 649 public override string Name => "Puddle"; @@ -160,7 +159,7 @@ namespace Content.Server.GameObjects.Components.Fluids { if(_slippery) { - message.AddText(_loc.GetString("It looks slippery.")); + message.AddText(Loc.GetString("It looks slippery.")); } } diff --git a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs index f581c1957e..aa9e44ee83 100644 --- a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs @@ -31,7 +31,6 @@ namespace Content.Server.GameObjects.Components.Interactable internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing, IMapInit { [Dependency] private readonly ISharedNotifyManager _notifyManager = default!; - [Dependency] private readonly ILocalizationManager _localizationManager = default!; [ViewVariables(VVAccess.ReadWrite)] public float Wattage { get; set; } = 10; [ViewVariables] private ContainerSlot _cellContainer = default!; @@ -77,11 +76,9 @@ namespace Content.Server.GameObjects.Components.Interactable void IExamine.Examine(FormattedMessage message, bool inDetailsRange) { - var loc = IoCManager.Resolve(); - if (Activated) { - message.AddMarkup(loc.GetString("The light is currently [color=darkgreen]on[/color].")); + message.AddMarkup(Loc.GetString("The light is currently [color=darkgreen]on[/color].")); } } @@ -151,7 +148,7 @@ namespace Content.Server.GameObjects.Components.Interactable EntitySystem.Get().PlayFromEntity("/Audio/Machines/button.ogg", Owner); - _notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Cell missing...")); + _notifyManager.PopupMessage(Owner, user, Loc.GetString("Cell missing...")); return; } @@ -161,7 +158,7 @@ namespace Content.Server.GameObjects.Components.Interactable if (Wattage > cell.CurrentCharge) { EntitySystem.Get().PlayFromEntity("/Audio/Machines/button.ogg", Owner); - _notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Dead cell...")); + _notifyManager.PopupMessage(Owner, user, Loc.GetString("Dead cell...")); return; } diff --git a/Content.Server/GameObjects/Components/Items/DiceComponent.cs b/Content.Server/GameObjects/Components/Items/DiceComponent.cs index 227c0e3c4e..ca12cfd0fd 100644 --- a/Content.Server/GameObjects/Components/Items/DiceComponent.cs +++ b/Content.Server/GameObjects/Components/Items/DiceComponent.cs @@ -85,9 +85,9 @@ namespace Content.Server.GameObjects.Components.Items void IExamine.Examine(FormattedMessage message, bool inDetailsRange) { //No details check, since the sprite updates to show the side. - var loc = IoCManager.Resolve(); - message.AddMarkup(loc.GetString("A dice with [color=lightgray]{0}[/color] sides.\n" + - "It has landed on a [color=white]{1}[/color].", _sides, _currentSide)); + message.AddMarkup(Loc.GetString( + "A dice with [color=lightgray]{0}[/color] sides.\n" + "It has landed on a [color=white]{1}[/color].", + _sides, _currentSide)); } } } diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs index 4b78446d42..e0f0f436ed 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs @@ -13,7 +13,6 @@ using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -76,8 +75,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece var result = TryInsertItem(eventArgs.Using); if (!result) { - var localizationManager = IoCManager.Resolve(); - eventArgs.User.PopupMessage(Owner, localizationManager.GetString("Unable to insert capacitor")); + eventArgs.User.PopupMessage(Owner, Loc.GetString("Unable to insert capacitor")); } return result; diff --git a/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs b/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs index ffbcfbe4cc..6a754cadac 100644 --- a/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs +++ b/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs @@ -15,7 +15,6 @@ namespace Content.Server.GameObjects.Components.Rotatable { #pragma warning disable 649 [Dependency] private readonly IServerNotifyManager _notifyManager; - [Dependency] private readonly ILocalizationManager _localizationManager; #pragma warning restore 649 public override string Name => "Rotatable"; @@ -25,7 +24,7 @@ namespace Content.Server.GameObjects.Components.Rotatable { if (collidable.Anchored) { - _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("It's stuck.")); + _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("It's stuck.")); return; } } diff --git a/Content.Server/GameObjects/Components/Stack/StackComponent.cs b/Content.Server/GameObjects/Components/Stack/StackComponent.cs index e38af7a857..d5105efdeb 100644 --- a/Content.Server/GameObjects/Components/Stack/StackComponent.cs +++ b/Content.Server/GameObjects/Components/Stack/StackComponent.cs @@ -107,8 +107,7 @@ namespace Content.Server.GameObjects.Components.Stack { if (inDetailsRange) { - var loc = IoCManager.Resolve(); - message.AddMarkup(loc.GetPluralString( + message.AddMarkup(Loc.GetPluralString( "There is [color=lightgray]1[/color] thing in the stack", "There are [color=lightgray]{0}[/color] things in the stack.", Count, Count)); } diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs index 9d12014cda..96468e6e7f 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs @@ -23,7 +23,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee public class FlashComponent : MeleeWeaponComponent, IUse, IExamine { #pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _localizationManager; [Dependency] private readonly IEntityManager _entityManager; [Dependency] private readonly ISharedNotifyManager _notifyManager; #pragma warning restore 649 @@ -173,10 +172,10 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee if (inDetailsRange) { message.AddMarkup( - _localizationManager.GetString( + Loc.GetString( "The flash has [color=green]{0}[/color] {1} remaining.", Uses, - _localizationManager.GetPluralString("use", "uses", Uses) + Loc.GetPluralString("use", "uses", Uses) ) ); } diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs index 2a3fc9df37..8da76dd33a 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs @@ -33,7 +33,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee #pragma warning disable 649 [Dependency] private IRobustRandom _robustRandom; [Dependency] private readonly ISharedNotifyManager _notifyManager; - [Dependency] private readonly ILocalizationManager _localizationManager; #pragma warning restore 649 public override string Name => "Stunbaton"; @@ -168,14 +167,14 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee { EntitySystem.Get().PlayAtCoords("/Audio/Machines/button.ogg", Owner.Transform.GridPosition, AudioHelpers.WithVariation(0.25f)); - _notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Cell missing...")); + _notifyManager.PopupMessage(Owner, user, Loc.GetString("Cell missing...")); return; } if (cell.CurrentCharge < EnergyPerUse) { EntitySystem.Get().PlayAtCoords("/Audio/Machines/button.ogg", Owner.Transform.GridPosition, AudioHelpers.WithVariation(0.25f)); - _notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Dead cell...")); + _notifyManager.PopupMessage(Owner, user, Loc.GetString("Dead cell...")); return; } @@ -242,11 +241,9 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee public void Examine(FormattedMessage message, bool inDetailsRange) { - var loc = IoCManager.Resolve(); - if (Activated) { - message.AddMarkup(loc.GetString("The light is currently [color=darkgreen]on[/color].")); + message.AddMarkup(Loc.GetString("The light is currently [color=darkgreen]on[/color].")); } } diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs index aa8120e16e..39dfc9c064 100644 --- a/Content.Server/GameObjects/Components/WiresComponent.cs +++ b/Content.Server/GameObjects/Components/WiresComponent.cs @@ -491,9 +491,7 @@ namespace Content.Server.GameObjects.Components void IExamine.Examine(FormattedMessage message, bool inDetailsRange) { - var loc = IoCManager.Resolve(); - - message.AddMarkup(loc.GetString(IsPanelOpen + message.AddMarkup(Loc.GetString(IsPanelOpen ? "The [color=lightgray]maintenance panel[/color] is [color=darkgreen]open[/color]." : "The [color=lightgray]maintenance panel[/color] is [color=darkred]closed[/color].")); } diff --git a/Content.Shared/GameObjects/EntitySystems/SharedConstructionSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedConstructionSystem.cs index eb046d4902..b19ed13e6d 100644 --- a/Content.Shared/GameObjects/EntitySystems/SharedConstructionSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/SharedConstructionSystem.cs @@ -2,7 +2,6 @@ using Content.Shared.Construction; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; -using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -13,9 +12,6 @@ namespace Content.Shared.GameObjects.EntitySystems { public class SharedConstructionSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _loc; -#pragma warning restore 649 /// /// Sent client -> server to to tell the server that we started building /// a structure-construction. @@ -90,7 +86,7 @@ namespace Content.Shared.GameObjects.EntitySystems if (curStage.Backward != null && curStage.Backward is ConstructionStepTool) { var backward = (ConstructionStepTool) curStage.Backward; - message.AddText(_loc.GetString("To deconstruct: {0}x {1} Tool", backward.Amount, backward.ToolQuality)); + message.AddText(Loc.GetString("To deconstruct: {0}x {1} Tool", backward.Amount, backward.ToolQuality)); } if (curStage.Forward != null && curStage.Forward is ConstructionStepMaterial) { @@ -99,7 +95,7 @@ namespace Content.Shared.GameObjects.EntitySystems message.AddText("\n"); } var forward = (ConstructionStepMaterial) curStage.Forward; - message.AddText(_loc.GetString("To construct: {0}x {1}", forward.Amount, forward.Material)); + message.AddText(Loc.GetString("To construct: {0}x {1}", forward.Amount, forward.Material)); } } } From 814daaba4cffd1b29275cb875e436c2314c5a275 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sun, 23 Aug 2020 13:19:40 +0200 Subject: [PATCH 37/88] Fix the flashlight's power bar and status with no battery (#1862) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> --- .../GameObjects/Components/HandheldLightComponent.cs | 4 ++++ .../Components/Interactable/HandheldLightComponent.cs | 10 +++++++--- .../Components/SharedHandheldLightComponent.cs | 7 ++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Content.Client/GameObjects/Components/HandheldLightComponent.cs b/Content.Client/GameObjects/Components/HandheldLightComponent.cs index 2b2d43b1a7..fb5be31391 100644 --- a/Content.Client/GameObjects/Components/HandheldLightComponent.cs +++ b/Content.Client/GameObjects/Components/HandheldLightComponent.cs @@ -13,7 +13,10 @@ namespace Content.Client.GameObjects.Components [RegisterComponent] public sealed class HandheldLightComponent : SharedHandheldLightComponent, IItemStatus { + private bool _hasCell; + [ViewVariables] public float? Charge { get; private set; } + [ViewVariables] protected override bool HasCell => _hasCell; public Control MakeControl() { @@ -26,6 +29,7 @@ namespace Content.Client.GameObjects.Components return; Charge = cast.Charge; + _hasCell = cast.HasCell; } private sealed class StatusControl : Control diff --git a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs index aa9e44ee83..b6de8165ab 100644 --- a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs @@ -53,6 +53,8 @@ namespace Content.Server.GameObjects.Components.Interactable [ViewVariables] public bool Activated { get; private set; } + [ViewVariables] protected override bool HasCell => Cell != null; + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { if (!eventArgs.Using.HasComponent()) return false; @@ -211,6 +213,8 @@ namespace Content.Server.GameObjects.Components.Interactable return; } + Dirty(); + if (!user.TryGetComponent(out HandsComponent? hands)) { return; @@ -229,17 +233,17 @@ namespace Content.Server.GameObjects.Components.Interactable { if (Cell == null) { - return new HandheldLightComponentState(null); + return new HandheldLightComponentState(null, false); } if (Wattage > Cell.CurrentCharge) { // Practically zero. // This is so the item status works correctly. - return new HandheldLightComponentState(0); + return new HandheldLightComponentState(0, HasCell); } - return new HandheldLightComponentState(Cell.CurrentCharge / Cell.MaxCharge); + return new HandheldLightComponentState(Cell.CurrentCharge / Cell.MaxCharge, HasCell); } [Verb] diff --git a/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs b/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs index ecc9229693..760bd134cf 100644 --- a/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs +++ b/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs @@ -9,15 +9,20 @@ namespace Content.Shared.GameObjects.Components public sealed override string Name => "HandheldLight"; public sealed override uint? NetID => ContentNetIDs.HANDHELD_LIGHT; + protected abstract bool HasCell { get; } + [Serializable, NetSerializable] protected sealed class HandheldLightComponentState : ComponentState { - public HandheldLightComponentState(float? charge) : base(ContentNetIDs.HANDHELD_LIGHT) + public HandheldLightComponentState(float? charge, bool hasCell) : base(ContentNetIDs.HANDHELD_LIGHT) { Charge = charge; + HasCell = hasCell; } public float? Charge { get; } + + public bool HasCell { get; } } } } From 34e7ae6c7ab30263da046f661e634b64819976ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sun, 23 Aug 2020 14:47:37 +0200 Subject: [PATCH 38/88] Add pressure protection to hardsuits, cleanup hardsuit prototypes --- Content.Client/IgnoredComponents.cs | 1 + .../Clothing/Head/hardsuit-helmets.yml | 96 +++++++++---- .../Clothing/OuterClothing/hardsuits.yml | 132 ++++++++++++------ 3 files changed, 160 insertions(+), 69 deletions(-) diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 6e67d79995..8626c0e972 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -162,6 +162,7 @@ "GasVapor", "MobStateManager", "Metabolism", + "PressureProtection", }; } } diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index acbfb6eaba..810982fc66 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -1,7 +1,17 @@ - type: entity parent: HatBase - id: HatHardsuit-old - name: hardsuit-old + id: HelmetHardsuitBase + name: base hardsuit helmet + abstract: true + components: + - type: PressureProtection + highPressureMultiplier: 0.75 + lowPressureMultiplier: 100 + +- type: entity + parent: HelmetHardsuitBase + id: HatHardsuitOld + name: old hardsuit helmet description: An old helmet from a hardsuit. Still functional, for now. components: - type: Sprite @@ -10,11 +20,15 @@ sprite: Clothing/Head/hardsuit-old.rsi - type: Clothing sprite: Clothing/Head/hardsuit-old.rsi + - type: PressureProtection + highPressureMultiplier: 0.85 + lowPressureMultiplier: 45 + - type: entity - parent: HatBase - id: HatHardsuit-atmos - name: hardsuit-atmos + parent: HelmetHardsuitBase + id: HelmetHardsuitAtmos + name: atmos hardsuit helmet description: A special hardsuit helmet designed for working in low-pressure, high thermal environments. components: - type: Sprite @@ -23,11 +37,14 @@ sprite: Clothing/Head/hardsuit-atmos.rsi - type: Clothing sprite: Clothing/Head/hardsuit-atmos.rsi + - type: PressureProtection + highPressureMultiplier: 0.5 + lowPressureMultiplier: 100 - type: entity - parent: HatBase - id: HatHardsuit-engineering - name: hardsuit-engineering + parent: HelmetHardsuitBase + id: HelmetHardsuitEngineering + name: engineering hardsuit helmet description: An engineering hardsuit helmet designed for working in low-pressure, high radioactive environments. components: - type: Sprite @@ -36,11 +53,14 @@ sprite: Clothing/Head/hardsuit-engineering.rsi - type: Clothing sprite: Clothing/Head/hardsuit-engineering.rsi + - type: PressureProtection + highPressureMultiplier: 0.65 + lowPressureMultiplier: 100 - type: entity - parent: HatBase - id: HatHardsuit-hazardhardsuit - name: hardsuit-hazardhardsuit + parent: HelmetHardsuitBase + id: HelmetHardsuitHazard + name: hazard hardsuit helmet description: Robust hardsuit helmet made for dangerous and hazardous situations. components: - type: Sprite @@ -51,9 +71,9 @@ sprite: Clothing/Head/hardsuit-hazardhardsuit.rsi - type: entity - parent: HatBase - id: HatHardsuit-medical - name: hardsuit-medical + parent: HelmetHardsuitBase + id: HelmetHardsuitMedical + name: medical hardsuit helmet description: Lightweight medical hardsuit helmet that doesn't restrict your head movements. components: - type: Sprite @@ -62,11 +82,14 @@ sprite: Clothing/Head/hardsuit-medical.rsi - type: Clothing sprite: Clothing/Head/hardsuit-medical.rsi + - type: PressureProtection + highPressureMultiplier: 0.80 + lowPressureMultiplier: 55 - type: entity - parent: HatBase - id: HatHardsuit-mining - name: hardsuit-mining + parent: HelmetHardsuitBase + id: HelmetHardsuitMining + name: mining hardsuit helmet description: A special helmet designed for work in a hazardous, low pressure environment. Has reinforced plating for wildlife encounters and dual floodlights. components: - type: Sprite @@ -75,11 +98,14 @@ sprite: Clothing/Head/hardsuit-mining.rsi - type: Clothing sprite: Clothing/Head/hardsuit-mining.rsi + - type: PressureProtection + highPressureMultiplier: 0.70 + lowPressureMultiplier: 100 - type: entity - parent: HatBase - id: HatHardsuit-sectg - name: hardsuit-sectg + parent: HelmetHardsuitBase + id: HelmetHardsuitSecurity + name: security hardsuit helmet description: Armored hardsuit helmet for security needs. components: - type: Sprite @@ -88,11 +114,14 @@ sprite: Clothing/Head/hardsuit-sectg.rsi - type: Clothing sprite: Clothing/Head/hardsuit-sectg.rsi + - type: PressureProtection + highPressureMultiplier: 0.70 + lowPressureMultiplier: 100 - type: entity - parent: HatBase - id: HatHardsuit-syndie - name: hardsuit-syndie + parent: HelmetHardsuitBase + id: HelmetHardsuitSyndie + name: blood red hardsuit helmet description: An advanced red hardsuit helmet designed for work in special operations. components: - type: Sprite @@ -103,11 +132,14 @@ state: icon - type: Clothing sprite: Clothing/Head/hardsuit-syndie.rsi + - type: PressureProtection + highPressureMultiplier: 0.45 + lowPressureMultiplier: 100 - type: entity - parent: HatBase - id: HatHardsuit-ce - name: hardsuit-ce + parent: HelmetHardsuitBase + id: HelmetHardsuitCE + name: CE hardsuit helmet description: Special hardsuit helmet, made for the chief engineer of the station. components: - type: Sprite @@ -116,11 +148,14 @@ sprite: Clothing/Head/hardsuit-white.rsi - type: Clothing sprite: Clothing/Head/hardsuit-white.rsi + - type: PressureProtection + highPressureMultiplier: 0.5 + lowPressureMultiplier: 100 - type: entity - parent: HatBase - id: HatHardsuit-wiz - name: hardsuit-wiz + parent: HelmetHardsuitBase + id: HelmetHardsuitWizard + name: wizard hardsuit helmet description: A bizarre gem-encrusted helmet that radiates magical energies. components: - type: Sprite @@ -129,3 +164,6 @@ sprite: Clothing/Head/hardsuit-wiz.rsi - type: Clothing sprite: Clothing/Head/hardsuit-wiz.rsi + - type: PressureProtection + highPressureMultiplier: 0.45 + lowPressureMultiplier: 100 diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index 45976cde74..cd86874caa 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -1,7 +1,17 @@ - type: entity parent: OuterclothingBase - id: OuterclothingDeathsquad - name: deathsquad + id: HardsuitBase + name: base hardsuit + abstract: true + components: + - type: PressureProtection + highPressureMultiplier: 0.75 + lowPressureMultiplier: 100 + +- type: entity + parent: HardsuitBase + id: HardsuitDeathsquad + name: deathsquad hardsuit description: An advanced hardsuit favored by commandos for use in special operations. components: - type: Sprite @@ -10,10 +20,13 @@ sprite: Clothing/OuterClothing/deathsquad.rsi - type: Clothing sprite: Clothing/OuterClothing/deathsquad.rsi + - type: PressureProtection + highPressureMultiplier: 0.45 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterclothingIhvoidsuit + parent: HardsuitBase + id: IHVoidsuit name: IH voidsuit description: A special void suit that protects against hazardous, low pressure environments. components: @@ -23,11 +36,14 @@ sprite: Clothing/OuterClothing/ihvoidsuit.rsi - type: Clothing sprite: Clothing/OuterClothing/ihvoidsuit.rsi + - type: PressureProtection + highPressureMultiplier: 1 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterClothingHardsuitatmos - name: hardsuit atmos + parent: HardsuitBase + id: HardsuitAtmos + name: atmos hardsuit description: A special suit that protects against hazardous, low pressure environments. Has thermal shielding. components: - type: Sprite @@ -36,11 +52,14 @@ sprite: Clothing/OuterClothing/hardsuit_atmos.rsi - type: Clothing sprite: Clothing/OuterClothing/hardsuit_atmos.rsi + - type: PressureProtection + highPressureMultiplier: 0.5 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterClothingHardsuitengineering - name: hardsuit engineering + parent: HardsuitBase + id: HardsuitEngineering + name: engineering hardsuit description: A special suit that protects against hazardous, low pressure environments. Has radiation shielding. components: - type: Sprite @@ -49,11 +68,14 @@ sprite: Clothing/OuterClothing/hardsuit_engineering.rsi - type: Clothing sprite: Clothing/OuterClothing/hardsuit_engineering.rsi + - type: PressureProtection + highPressureMultiplier: 0.65 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterClothingHardsuithazardhardsuit - name: hardsuit hazard hardsuit + parent: HardsuitBase + id: HardsuitHazard + name: hazard hardsuit description: A robust hardsuit made for dangerous and hazardous situations. components: - type: Sprite @@ -62,11 +84,14 @@ sprite: Clothing/OuterClothing/hardsuit_hazardhardsuit.rsi - type: Clothing sprite: Clothing/OuterClothing/hardsuit_hazardhardsuit.rsi + - type: PressureProtection + highPressureMultiplier: 0.75 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterClothingHardsuitmedical - name: hardsuit medical + parent: HardsuitBase + id: HardsuitMedical + name: medical hardsuit description: A special suit that protects against hazardous, low pressure environments. Built with lightweight materials for easier movement. components: - type: Sprite @@ -75,11 +100,14 @@ sprite: Clothing/OuterClothing/hardsuit_medical.rsi - type: Clothing sprite: Clothing/OuterClothing/hardsuit_medical.rsi + - type: PressureProtection + highPressureMultiplier: 0.75 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterClothingHardsuitmining - name: hardsuit mining + parent: HardsuitBase + id: HardsuitMining + name: mining hardsuit description: A special suit that protects against hazardous, low pressure environments. Has reinforced plating for wildlife encounters. components: - type: Sprite @@ -88,11 +116,14 @@ sprite: Clothing/OuterClothing/hardsuit_mining.rsi - type: Clothing sprite: Clothing/OuterClothing/hardsuit_mining.rsi + - type: PressureProtection + highPressureMultiplier: 0.75 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterClothingHardsuitsectg - name: hardsuit sectg + parent: HardsuitBase + id: HardsuitSecurity + name: security hardsuit description: A special suit that protects against hazardous, low pressure environments. Has an additional layer of armor. components: - type: Sprite @@ -101,11 +132,14 @@ sprite: Clothing/OuterClothing/hardsuit_sectg.rsi - type: Clothing sprite: Clothing/OuterClothing/hardsuit_sectg.rsi + - type: PressureProtection + highPressureMultiplier: 0.75 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterClothingHardsuitsyndie - name: hardsuit syndie + parent: HardsuitBase + id: HardsuitSyndie + name: blood red hardsuit description: A dual-mode advanced hardsuit designed for work in special operations. components: - type: Sprite @@ -114,12 +148,15 @@ sprite: Clothing/OuterClothing/hardsuit_syndie.rsi - type: Clothing sprite: Clothing/OuterClothing/hardsuit_syndie.rsi + - type: PressureProtection + highPressureMultiplier: 0.45 + lowPressureMultiplier: 100 # Some massive smoothbrain had this named "hardsuit white" yeah thanks fuckhead really useful. - type: entity - parent: OuterclothingBase - id: OuterClothingHardsuitce - name: hardsuit ce + parent: HardsuitBase + id: HardsuitCE + name: CE hardsuit description: A special hardsuit that protects against hazardous, low pressure environments, made for the chief engineer of the station. components: - type: Sprite @@ -128,11 +165,14 @@ sprite: Clothing/OuterClothing/hardsuit_ce.rsi - type: Clothing sprite: Clothing/OuterClothing/hardsuit_ce.rsi + - type: PressureProtection + highPressureMultiplier: 0.45 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterClothingHardsuitwiz - name: hardsuit wiz + parent: HardsuitBase + id: HardsuitWizard + name: wizard hardsuit description: A bizarre gem-encrusted suit that radiates magical energies. components: - type: Sprite @@ -141,11 +181,14 @@ sprite: Clothing/OuterClothing/hardsuit_wiz.rsi - type: Clothing sprite: Clothing/OuterClothing/hardsuit_wiz.rsi + - type: PressureProtection + highPressureMultiplier: 0.45 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterclothingSpace - name: space + parent: HardsuitBase + id: Spacesuit + name: spacesuit description: A basic space suit. components: - type: Sprite @@ -154,12 +197,15 @@ sprite: Clothing/OuterClothing/space.rsi - type: Clothing sprite: Clothing/OuterClothing/space.rsi + - type: PressureProtection + highPressureMultiplier: 1 + lowPressureMultiplier: 100 # I don't know what the hell "td" is but whatever i'll dump it here. - type: entity - parent: OuterclothingBase - id: OuterclothingTdgreen - name: td green + parent: HardsuitBase + id: HardsuitTDGreen + name: green TD hardsuit description: Heavily armored black and green suit for long combat situations, also provides protection from low-pressure/vacuum damage. components: - type: Sprite @@ -168,11 +214,14 @@ sprite: Clothing/OuterClothing/tdgreen.rsi - type: Clothing sprite: Clothing/OuterClothing/tdgreen.rsi + - type: PressureProtection + highPressureMultiplier: 1 + lowPressureMultiplier: 100 - type: entity - parent: OuterclothingBase - id: OuterclothingTdred - name: td red + parent: HardsuitBase + id: HardsuitTDRed + name: red TD hardsuit description: Heavily armored black and red suit for long combat situations, also provides protection from low-pressure/vacuum damage components: - type: Sprite @@ -181,3 +230,6 @@ sprite: Clothing/OuterClothing/tdred.rsi - type: Clothing sprite: Clothing/OuterClothing/tdred.rsi + - type: PressureProtection + highPressureMultiplier: 1 + lowPressureMultiplier: 100 From fad6cdd4d1aee96136247d6338b7cfc245bd3203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sun, 23 Aug 2020 14:58:28 +0200 Subject: [PATCH 39/88] Give maintenance access to jobs that didn't have it. --- Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml | 1 + Resources/Prototypes/Roles/Jobs/Civilian/chef.yml | 1 + Resources/Prototypes/Roles/Jobs/Civilian/clown.yml | 1 + Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml | 1 + Resources/Prototypes/Roles/Jobs/Science/scientist.yml | 1 + 5 files changed, 5 insertions(+) diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml b/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml index 8006ed4416..14f9ea8bcd 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml @@ -8,6 +8,7 @@ icon: "Bartender" access: - Service + - Maintenance - type: startingGear id: BartenderGear diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml b/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml index d4342ccd9b..8cd3ed63e8 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml @@ -8,6 +8,7 @@ icon: "Chef" access: - Service + - Maintenance - type: startingGear id: ChefGear diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml b/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml index 6070cdbf40..ff6ce07861 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml @@ -8,6 +8,7 @@ icon: "Clown" access: - Theatre + - Maintenance special: !type:ClownSpecial {} - type: startingGear diff --git a/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml b/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml index ba2aac1b52..3371416216 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml @@ -9,6 +9,7 @@ icon: "MedicalDoctor" access: - Medical + - Maintenance - type: startingGear id: DoctorGear diff --git a/Resources/Prototypes/Roles/Jobs/Science/scientist.yml b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml index 5e8017c34e..7ae835700b 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/scientist.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml @@ -9,6 +9,7 @@ icon: "Scientist" access: - Research + - Maintenance - type: startingGear id: ScientistGear From 39f346ed420f69f85347f21be17f66a4da92890b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Sun, 23 Aug 2020 15:03:01 +0200 Subject: [PATCH 40/88] Fix stripping menu hands' items not showing correctly. --- .../GameObjects/Components/GUI/StrippableComponent.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs index 38270dcf46..484a60dd53 100644 --- a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs @@ -43,14 +43,16 @@ namespace Content.Server.GameObjects.Components.GUI UserInterface.OnReceiveMessage += HandleUserInterfaceMessage; } - Owner.EnsureComponent(); - Owner.EnsureComponent(); - if (Owner.TryGetComponent(out InventoryComponent? inventory)) { inventory.OnItemChanged += UpdateSubscribed; } + if (Owner.TryGetComponent(out HandsComponent? hands)) + { + hands.OnItemChanged += UpdateSubscribed; + } + // Initial update. UpdateSubscribed(); } From 42d2334334e496c5a137d4ed4d2917d179a402ce Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sun, 23 Aug 2020 17:17:19 +0200 Subject: [PATCH 41/88] Fix airtight entities not getting invalidated and their vacuum not being fixed (#1876) --- .../Components/Atmos/AirtightComponent.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs b/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs index 5e50bd0c64..3703eff58d 100644 --- a/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs @@ -4,6 +4,8 @@ using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Serialization; @@ -14,6 +16,8 @@ namespace Content.Server.GameObjects.Components.Atmos [RegisterComponent] public class AirtightComponent : Component, IMapInit { + [Dependency] private readonly IMapManager _mapManager = default!; + private (GridId, MapIndices) _lastPosition; public override string Name => "Airtight"; @@ -56,7 +60,7 @@ namespace Content.Server.GameObjects.Components.Atmos // down than the object magically not being airtight, so log one if the SnapGrid component // is missing. if (!Owner.EnsureComponent(out SnapGridComponent _)) - Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition.ToString()} doesn't have a {nameof(SnapGridComponent)}"); + Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition.ToString()} didn't have a {nameof(SnapGridComponent)}"); UpdatePosition(); } @@ -81,12 +85,13 @@ namespace Content.Server.GameObjects.Components.Atmos if (Owner.TryGetComponent(out SnapGridComponent? snapGrid)) { snapGrid.OnPositionChanged -= OnTransformMove; - - if (_fixVacuum) - EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)? - .FixVacuum(snapGrid.Position); } + if (_fixVacuum) + { + var mapIndices = Owner.Transform.GridPosition.ToMapIndices(_mapManager); + EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)?.FixVacuum(mapIndices); + } UpdatePosition(); } @@ -104,10 +109,8 @@ namespace Content.Server.GameObjects.Components.Atmos private void UpdatePosition() { - if (Owner.TryGetComponent(out SnapGridComponent? snapGrid)) - { - UpdatePosition(Owner.Transform.GridID, snapGrid.Position); - } + var mapIndices = Owner.Transform.GridPosition.ToMapIndices(_mapManager); + UpdatePosition(Owner.Transform.GridID, mapIndices); } private void UpdatePosition(GridId gridId, MapIndices pos) From 99e0f2019de87690f552e3f3f1d1f76ce906fa4a Mon Sep 17 00:00:00 2001 From: Exp Date: Mon, 24 Aug 2020 12:01:47 +0200 Subject: [PATCH 42/88] Remove duplicate PhysicalConstants.cs (#1890) --- Content.Shared/GameObjects/PhysicalConstants.cs | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 Content.Shared/GameObjects/PhysicalConstants.cs diff --git a/Content.Shared/GameObjects/PhysicalConstants.cs b/Content.Shared/GameObjects/PhysicalConstants.cs deleted file mode 100644 index bbe806ff0e..0000000000 --- a/Content.Shared/GameObjects/PhysicalConstants.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Shared.GameObjects -{ - /// - /// Contains physical constants used in calculations. - /// - class PhysicalConstants - { - public const float ZERO_CELCIUS = 273.15f; - } -} From 769a371be63a5be73e14599fb2231c4ab56f8d22 Mon Sep 17 00:00:00 2001 From: Exp Date: Mon, 24 Aug 2020 12:11:53 +0200 Subject: [PATCH 43/88] Make the lobby player list include the ready indicator and not hack it together (#1860) * Started new Lobby * -Proper styling -Use a scrollcontainer :smilethink: * Too lazy to add a stylerule, too young to optimize css * Fix typo Co-authored-by: DrSmugleaf --- Content.Client/State/LobbyState.cs | 13 ++-- Content.Client/UserInterface/LobbyGui.cs | 93 +++++++++++++++++++++--- 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/Content.Client/State/LobbyState.cs b/Content.Client/State/LobbyState.cs index d3f756a0c8..6f04765caa 100644 --- a/Content.Client/State/LobbyState.cs +++ b/Content.Client/State/LobbyState.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Content.Client.Interfaces; +using Content.Client.Interfaces; using Content.Client.Interfaces.Chat; using Content.Client.UserInterface; using Content.Shared.Input; @@ -18,6 +16,8 @@ using Robust.Shared.Localization; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.ViewVariables; +using System; +using System.Linq; using static Content.Shared.SharedGameTicker; namespace Content.Client.State @@ -211,12 +211,11 @@ namespace Content.Client.State private void UpdatePlayerList() { - _lobby.OnlinePlayerItemList.Clear(); - _lobby.PlayerReadyList.Clear(); + _lobby.OnlinePlayerList.Clear(); foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name)) { - _lobby.OnlinePlayerItemList.AddItem(session.Name); + var readyState = ""; // Don't show ready state if we're ingame @@ -236,7 +235,7 @@ namespace Content.Client.State _ => "", }; } - _lobby.PlayerReadyList.AddItem(readyState, null, false); + _lobby.OnlinePlayerList.AddItem(session.Name, readyState); } } diff --git a/Content.Client/UserInterface/LobbyGui.cs b/Content.Client/UserInterface/LobbyGui.cs index 2f8caf3574..9c2ec94a34 100644 --- a/Content.Client/UserInterface/LobbyGui.cs +++ b/Content.Client/UserInterface/LobbyGui.cs @@ -9,6 +9,8 @@ using Robust.Client.UserInterface.Controls; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Maths; +using System; +using System.Collections.Generic; namespace Content.Client.UserInterface { @@ -21,8 +23,7 @@ namespace Content.Client.UserInterface public Button CreditsButton { get; } public Button LeaveButton { get; } public ChatBox Chat { get; } - public ItemList OnlinePlayerItemList { get; } - public ItemList PlayerReadyList { get; } + public LobbyPlayerList OnlinePlayerList { get; } public ServerInfo ServerInfo { get; } public LobbyCharacterPreviewPanel CharacterPreview { get; } @@ -226,17 +227,11 @@ namespace Content.Client.UserInterface CustomMinimumSize = (50,50), Children = { - (OnlinePlayerItemList = new ItemList + (OnlinePlayerList = new LobbyPlayerList { SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsHorizontal = SizeFlags.FillExpand, - }), - (PlayerReadyList = new ItemList - { - SizeFlagsVertical = SizeFlags.FillExpand, - SizeFlagsHorizontal = SizeFlags.FillExpand, - SizeFlagsStretchRatio = 0.2f - }), + }) } } } @@ -262,4 +257,82 @@ namespace Content.Client.UserInterface } } } + + public class LobbyPlayerList : Control + { + private ScrollContainer _scroll; + private VBoxContainer _vBox; + + public LobbyPlayerList() + { + var panel = new PanelContainer() + { + PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#202028") }, + }; + _vBox = new VBoxContainer(); + _scroll = new ScrollContainer(); + _scroll.AddChild(_vBox); + panel.AddChild(_scroll); + AddChild(panel); + } + + // Adds a row + public void AddItem(string name, string status) + { + var hbox = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + }; + + // Player Name + hbox.AddChild(new PanelContainer() + { + PanelOverride = new StyleBoxFlat + { + BackgroundColor = Color.FromHex("#373744"), + ContentMarginBottomOverride = 2, + ContentMarginLeftOverride = 4, + ContentMarginRightOverride = 4, + ContentMarginTopOverride = 2 + }, + Children = + { + new Label + { + Text = name + } + }, + SizeFlagsHorizontal = SizeFlags.FillExpand + }); + // Status + hbox.AddChild(new PanelContainer() + { + PanelOverride = new StyleBoxFlat + { + BackgroundColor = Color.FromHex("#373744"), + ContentMarginBottomOverride = 2, + ContentMarginLeftOverride = 4, + ContentMarginRightOverride = 4, + ContentMarginTopOverride = 2 + }, + Children = + { + new Label + { + Text = status + } + }, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 0.2f, + }); + + _vBox.AddChild(hbox); + } + + // Deletes all rows + public void Clear() + { + _vBox.RemoveAllChildren(); + } + } } From df823d224500e407a610b4b90d3768a257493978 Mon Sep 17 00:00:00 2001 From: SoulSloth <67545203+SoulSloth@users.noreply.github.com> Date: Mon, 24 Aug 2020 06:32:18 -0400 Subject: [PATCH 44/88] Add Flashlight Visualizer/states (#1861) * Add art assets for cloning * Added a 'Scan DNA' button to the medical scanner * Made the UI update unconditional for the medical scanner until checks for power changes are in place * Update Medical scanner to reflect powered status and fix #1774 * added a 'scan dna' button the the medical scanner that will add the contained bodies Uid to a list in CloningSystem, fixed an issue with the menu not populating if the scanner starts in an unpowered state * Add disabling logic to 'Scan DNA' button on medical scanner * Removed un-used libraries * Added playing parameter to radiatingLightComponent, changed it's animation key to radiatingLight * refactored RadiatingLight into a visualizer * Added different light animations for differnt power states of a flashlight * split out the radiating light visualizer into two seperate visualizers * further refactored and tweaked handheldlight animations * further lantern light tweaks * removed un-used attributes in flashlight and lantern prototypes * fix null check in handheldlightcomponent --- .../Components/FlashLightVisualizer.cs | 119 ++++++++++++++++++ .../Components/LanternVisualizer.cs | 56 +++++++++ .../Components/RadiatingLightComponent.cs | 45 ------- Content.Server/Atmos/GasSprayerComponent.cs | 1 - .../Interactable/HandheldLightComponent.cs | 34 +++-- .../Components/Power/BatteryComponent.cs | 9 +- .../SharedHandheldLightComponent.cs | 16 +++ .../Entities/Objects/Tools/flashlight.yml | 39 +++--- .../Entities/Objects/Tools/lantern.yml | 4 +- 9 files changed, 243 insertions(+), 80 deletions(-) create mode 100644 Content.Client/GameObjects/Components/FlashLightVisualizer.cs create mode 100644 Content.Client/GameObjects/Components/LanternVisualizer.cs delete mode 100644 Content.Client/GameObjects/Components/RadiatingLightComponent.cs diff --git a/Content.Client/GameObjects/Components/FlashLightVisualizer.cs b/Content.Client/GameObjects/Components/FlashLightVisualizer.cs new file mode 100644 index 0000000000..a8851f2e1e --- /dev/null +++ b/Content.Client/GameObjects/Components/FlashLightVisualizer.cs @@ -0,0 +1,119 @@ +using System; +using Content.Shared.GameObjects.Components; +using JetBrains.Annotations; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.Components.Animations; +using Robust.Shared.Animations; +using Robust.Shared.GameObjects; + +namespace Content.Client.GameObjects.Components +{ + [UsedImplicitly] + public class FlashLightVisualizer : AppearanceVisualizer + { + private readonly Animation _radiatingLightAnimation = new Animation + { + Length = TimeSpan.FromSeconds(1), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(PointLightComponent), + InterpolationMode = AnimationInterpolationMode.Linear, + Property = nameof(PointLightComponent.Radius), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(3.0f, 0), + new AnimationTrackProperty.KeyFrame(2.0f, 0.5f), + new AnimationTrackProperty.KeyFrame(3.0f, 1) + } + } + } + }; + + private readonly Animation _blinkingLightAnimation = new Animation + { + Length = TimeSpan.FromSeconds(1), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(PointLightComponent), + //To create the blinking effect we go from nearly zero radius, to the light radius, and back + //We do this instead of messing with the `PointLightComponent.enabled` because we don't want the animation to affect component behavior + InterpolationMode = AnimationInterpolationMode.Nearest, + Property = nameof(PointLightComponent.Radius), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(0.1f, 0), + new AnimationTrackProperty.KeyFrame(2f, 0.5f), + new AnimationTrackProperty.KeyFrame(0.1f, 1) + } + } + } + }; + + private Action _radiatingCallback; + private Action _blinkingCallback; + + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + if (component.Deleted) + { + return; + } + + if (component.TryGetData(HandheldLightVisuals.Power, + out HandheldLightPowerStates state)) + { + PlayAnimation(component, state); + } + } + + private void PlayAnimation(AppearanceComponent component, HandheldLightPowerStates state) + { + component.Owner.EnsureComponent(out AnimationPlayerComponent animationPlayer); + + switch (state) + { + case HandheldLightPowerStates.LowPower: + if (!animationPlayer.HasRunningAnimation("radiatingLight")) + { + animationPlayer.Play(_radiatingLightAnimation, "radiatingLight"); + _radiatingCallback = (s) => animationPlayer.Play(_radiatingLightAnimation, s); + animationPlayer.AnimationCompleted += _radiatingCallback; + } + + break; + case HandheldLightPowerStates.Dying: + animationPlayer.Stop("radiatingLight"); + animationPlayer.AnimationCompleted -= _radiatingCallback; + if (!animationPlayer.HasRunningAnimation("blinkingLight")) + { + animationPlayer.Play(_blinkingLightAnimation, "blinkingLight"); + _blinkingCallback = (s) => animationPlayer.Play(_blinkingLightAnimation, s); + animationPlayer.AnimationCompleted += _blinkingCallback; + } + + break; + case HandheldLightPowerStates.FullPower: + if (animationPlayer.HasRunningAnimation("blinkingLight")) + { + animationPlayer.Stop("blinkingLight"); + animationPlayer.AnimationCompleted -= _blinkingCallback; + } + + if (animationPlayer.HasRunningAnimation("radiatingLight")) + { + animationPlayer.Stop("radiatingLight"); + animationPlayer.AnimationCompleted -= _radiatingCallback; + } + + break; + } + } + } +} diff --git a/Content.Client/GameObjects/Components/LanternVisualizer.cs b/Content.Client/GameObjects/Components/LanternVisualizer.cs new file mode 100644 index 0000000000..4508150c3a --- /dev/null +++ b/Content.Client/GameObjects/Components/LanternVisualizer.cs @@ -0,0 +1,56 @@ +using System; +using Content.Shared.GameObjects.Components; +using JetBrains.Annotations; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.Components.Animations; +using Robust.Shared.Animations; +using Robust.Shared.GameObjects; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; + +namespace Content.Client.GameObjects.Components +{ + [UsedImplicitly] + public class LanternVisualizer : AppearanceVisualizer + { + private readonly Animation _radiatingLightAnimation = new Animation + { + Length = TimeSpan.FromSeconds(5), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(PointLightComponent), + InterpolationMode = AnimationInterpolationMode.Linear, + Property = nameof(PointLightComponent.Radius), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(3.0f, 0), + new AnimationTrackProperty.KeyFrame(2.0f, 1.5f), + new AnimationTrackProperty.KeyFrame(3.0f, 3f) + } + } + } + }; + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + if (component.Deleted) + { + return; + } + + PlayAnimation(component); + } + + private void PlayAnimation(AppearanceComponent component) + { + component.Owner.EnsureComponent(out AnimationPlayerComponent animationPlayer); + if (animationPlayer.HasRunningAnimation("radiatingLight")) return; + animationPlayer.Play(_radiatingLightAnimation, "radiatingLight"); + animationPlayer.AnimationCompleted += s => animationPlayer.Play(_radiatingLightAnimation, s); + } + } +} diff --git a/Content.Client/GameObjects/Components/RadiatingLightComponent.cs b/Content.Client/GameObjects/Components/RadiatingLightComponent.cs deleted file mode 100644 index 9cfa272136..0000000000 --- a/Content.Client/GameObjects/Components/RadiatingLightComponent.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Robust.Client.Animations; -using Robust.Client.GameObjects; -using Robust.Client.GameObjects.Components.Animations; -using Robust.Shared.Animations; -using Robust.Shared.GameObjects; - -namespace Content.Client.GameObjects.Components -{ - [RegisterComponent] - public class RadiatingLightComponent : Component - { - public override string Name => "RadiatingLight"; - - protected override void Startup() - { - base.Startup(); - - var animation = new Animation - { - Length = TimeSpan.FromSeconds(4), - AnimationTracks = - { - new AnimationTrackComponentProperty - { - ComponentType = typeof(PointLightComponent), - InterpolationMode = AnimationInterpolationMode.Linear, - Property = nameof(PointLightComponent.Radius), - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(3.0f, 0), - new AnimationTrackProperty.KeyFrame(2.0f, 1), - new AnimationTrackProperty.KeyFrame(3.0f, 2) - } - } - } - }; - - var playerComponent = Owner.EnsureComponent(); - playerComponent.Play(animation, "emergency"); - - playerComponent.AnimationCompleted += s => playerComponent.Play(animation, s); - } - } -} diff --git a/Content.Server/Atmos/GasSprayerComponent.cs b/Content.Server/Atmos/GasSprayerComponent.cs index d414ce1714..b8460f91dd 100644 --- a/Content.Server/Atmos/GasSprayerComponent.cs +++ b/Content.Server/Atmos/GasSprayerComponent.cs @@ -2,7 +2,6 @@ using Content.Server.Interfaces; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components; -using Content.Shared.GameObjects.Components.Pointing; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.GameObjects.EntitySystems; diff --git a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs index b6de8165ab..b2daa8e37c 100644 --- a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs @@ -28,7 +28,8 @@ namespace Content.Server.GameObjects.Components.Interactable /// Component that represents a handheld lightsource which can be toggled on and off. /// [RegisterComponent] - internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing, IMapInit + internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing, + IMapInit { [Dependency] private readonly ISharedNotifyManager _notifyManager = default!; @@ -41,9 +42,12 @@ namespace Content.Server.GameObjects.Components.Interactable get { if (_cellContainer.ContainedEntity == null) return null; + if (_cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent? cell)) + { + return cell; + } - _cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent? cell); - return cell; + return null; } } @@ -134,7 +138,6 @@ namespace Content.Server.GameObjects.Components.Interactable Activated = false; EntitySystem.Get().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner); - } private void TurnOn(IEntity user) @@ -147,7 +150,6 @@ namespace Content.Server.GameObjects.Components.Interactable var cell = Cell; if (cell == null) { - EntitySystem.Get().PlayFromEntity("/Audio/Machines/button.ogg", Owner); _notifyManager.PopupMessage(Owner, user, Loc.GetString("Cell missing...")); @@ -168,7 +170,6 @@ namespace Content.Server.GameObjects.Components.Interactable SetState(true); EntitySystem.Get().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner); - } private void SetState(bool on) @@ -191,10 +192,24 @@ namespace Content.Server.GameObjects.Components.Interactable public void OnUpdate(float frameTime) { - if (!Activated) return; + if (!Activated || Cell == null) return; - var cell = Cell; - if (cell == null || !cell.TryUseCharge(Wattage * frameTime)) TurnOff(); + var appearanceComponent = Owner.GetComponent(); + + if (Cell.MaxCharge - Cell.CurrentCharge < Cell.MaxCharge * 0.70) + { + appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.FullPower); + } + else if (Cell.MaxCharge - Cell.CurrentCharge < Cell.MaxCharge * 0.90) + { + appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.LowPower); + } + else + { + appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.Dying); + } + + if (Cell == null || !Cell.TryUseCharge(Wattage * frameTime)) TurnOff(); Dirty(); } @@ -226,7 +241,6 @@ namespace Content.Server.GameObjects.Components.Interactable } EntitySystem.Get().PlayFromEntity("/Audio/Items/pistol_magout.ogg", Owner); - } public override ComponentState GetComponentState() diff --git a/Content.Server/GameObjects/Components/Power/BatteryComponent.cs b/Content.Server/GameObjects/Components/Power/BatteryComponent.cs index de481e77ec..e6224bf4bf 100644 --- a/Content.Server/GameObjects/Components/Power/BatteryComponent.cs +++ b/Content.Server/GameObjects/Components/Power/BatteryComponent.cs @@ -11,16 +11,15 @@ namespace Content.Server.GameObjects.Components.Power { public override string Name => "Battery"; - [ViewVariables(VVAccess.ReadWrite)] - public int MaxCharge { get => _maxCharge; set => SetMaxCharge(value); } + [ViewVariables(VVAccess.ReadWrite)] public int MaxCharge { get => _maxCharge; set => SetMaxCharge(value); } private int _maxCharge; [ViewVariables(VVAccess.ReadWrite)] public float CurrentCharge { get => _currentCharge; set => SetCurrentCharge(value); } + private float _currentCharge; - [ViewVariables] - public BatteryState BatteryState { get; private set; } + [ViewVariables] public BatteryState BatteryState { get; private set; } public override void ExposeData(ObjectSerializer serializer) { @@ -93,7 +92,7 @@ namespace Content.Server.GameObjects.Components.Power private void SetMaxCharge(int newMax) { _maxCharge = Math.Max(newMax, 0); - _currentCharge = Math.Min( _currentCharge, MaxCharge); + _currentCharge = Math.Min(_currentCharge, MaxCharge); UpdateStorageState(); OnChargeChanged(); } diff --git a/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs b/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs index 760bd134cf..f6dd37fcef 100644 --- a/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs +++ b/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs @@ -25,4 +25,20 @@ namespace Content.Shared.GameObjects.Components public bool HasCell { get; } } } + + [Serializable, NetSerializable] + public enum HandheldLightVisuals + { + Power + } + + [Serializable, NetSerializable] + public enum HandheldLightPowerStates + { + FullPower, + LowPower, + Dying, + } + + } diff --git a/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml b/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml index a3c589549b..cd4c5f7768 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml @@ -4,21 +4,24 @@ id: FlashlightLantern description: They light the way to freedom components: - - type: HandheldLight - - type: Sprite - sprite: Objects/Tools/flashlight.rsi - layers: - - state: lantern_off - - state: HandheldLightOnOverlay - shader: unshaded - visible: false - - type: Icon - sprite: Objects/Tools/flashlight.rsi - state: lantern_off - - type: Item - sprite: Objects/Tools/flashlight.rsi - HeldPrefix: off - - type: PointLight - enabled: false - radius: 3 - - type: LoopingSound + - type: HandheldLight + - type: Sprite + sprite: Objects/Tools/flashlight.rsi + layers: + - state: lantern_off + - state: HandheldLightOnOverlay + shader: unshaded + visible: false + - type: Icon + sprite: Objects/Tools/flashlight.rsi + state: lantern_off + - type: Item + sprite: Objects/Tools/flashlight.rsi + HeldPrefix: off + - type: PointLight + enabled: false + radius: 3 + - type: LoopingSound + - type: Appearance + visuals: + - type: FlashLightVisualizer diff --git a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml index e1346cac53..d974dce9aa 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml @@ -23,5 +23,7 @@ radius: 3 energy: 2.5 color: "#FFC458" - - type: RadiatingLight - type: LoopingSound + - type: Appearance + visuals: + - type: LanternVisualizer From 969eeb5528c8b67a36594b0847d16c0b183d6d61 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 24 Aug 2020 20:33:03 +1000 Subject: [PATCH 45/88] Add AI factions (#1807) * Add NPC faction tags Some stuff isn't easy to represent by the existence of components so tags are intended to provide that functionality for AI usage. I was 50/50 on having all tags in the 1 component or splitting it into 2. I'm leaning towards 2. This would be for stuff like say "CanMimic" so the mimic knows it's allowed to look like a specific prototype, or something like "smg" on a gun so it can say smg-specific barks for instance (as currently smgs and pistols look the same from a component perspective). This also means combat behaviors aren't hardcoded per faction, plus it makes it easy to update faction relations during events. * Factions command Update faction relationships via commands. * Remove command TODO * Woops Forgot to commit these items * Serializer writing and parsing * linq me up fam Co-authored-by: Metal Gear Sloth --- Content.Client/IgnoredComponents.cs | 1 + .../BehaviorSets/SpirateBehaviorSet.cs | 2 +- ...byPlayerExp.cs => MeleeAttackNearbyExp.cs} | 19 +- .../Melee/MeleeAttackNearbySpeciesExp.cs | 24 --- .../Melee/UnarmedAttackNearbyPlayerExp.cs | 11 +- .../Components/AI/AiFactionTagComponent.cs | 49 +++++ .../EntitySystems/AI/AiFactionTagSystem.cs | 175 ++++++++++++++++++ Resources/Groups/groups.yml | 3 + .../Prototypes/Entities/Mobs/NPCs/animals.yml | 3 + .../Prototypes/Entities/Mobs/NPCs/carp.yml | 3 + .../Prototypes/Entities/Mobs/NPCs/human.yml | 4 + .../Prototypes/Entities/Mobs/NPCs/mimic.yml | 3 + .../Prototypes/Entities/Mobs/NPCs/pets.yml | 3 + .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 3 + .../Prototypes/Entities/Mobs/Player/human.yml | 3 + 15 files changed, 265 insertions(+), 41 deletions(-) rename Content.Server/AI/Utility/ExpandableActions/Combat/Melee/{MeleeAttackNearbyPlayerExp.cs => MeleeAttackNearbyExp.cs} (69%) delete mode 100644 Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbySpeciesExp.cs create mode 100644 Content.Server/GameObjects/Components/AI/AiFactionTagComponent.cs create mode 100644 Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 8626c0e972..72785bce6f 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -162,6 +162,7 @@ "GasVapor", "MobStateManager", "Metabolism", + "AiFactionTag", "PressureProtection", }; } diff --git a/Content.Server/AI/Utility/BehaviorSets/SpirateBehaviorSet.cs b/Content.Server/AI/Utility/BehaviorSets/SpirateBehaviorSet.cs index 0c6360a6dd..29f0ec72cb 100644 --- a/Content.Server/AI/Utility/BehaviorSets/SpirateBehaviorSet.cs +++ b/Content.Server/AI/Utility/BehaviorSets/SpirateBehaviorSet.cs @@ -14,7 +14,7 @@ namespace Content.Server.AI.Utility.BehaviorSets // TODO: Ideally long-term we should just store the weapons in backpack new EquipMeleeExp(), new PickUpMeleeWeaponExp(), - new MeleeAttackNearbyPlayerExp(), + new MeleeAttackNearbyExp(), }; } } diff --git a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyPlayerExp.cs b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyExp.cs similarity index 69% rename from Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyPlayerExp.cs rename to Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyExp.cs index cde979be3e..3d44f0429b 100644 --- a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyPlayerExp.cs +++ b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyExp.cs @@ -4,17 +4,19 @@ using Content.Server.AI.Utility.Actions; using Content.Server.AI.Utility.Actions.Combat.Melee; using Content.Server.AI.Utility.Considerations; using Content.Server.AI.Utility.Considerations.Combat.Melee; -using Content.Server.AI.Utils; using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States; +using Content.Server.GameObjects.Components.AI; using Content.Server.GameObjects.Components.Movement; -using Content.Shared.GameObjects.Components.Damage; -using Robust.Server.GameObjects; +using Content.Server.GameObjects.EntitySystems.AI; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee { - public sealed class MeleeAttackNearbyPlayerExp : ExpandableUtilityAction + public sealed class MeleeAttackNearbyExp : ExpandableUtilityAction { public override float Bonus => UtilityAction.CombatBonus; @@ -37,13 +39,10 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee throw new InvalidOperationException(); } - foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(IDamageableComponent), - controller.VisionRadius)) + foreach (var target in EntitySystem.Get() + .GetNearbyHostiles(owner, controller.VisionRadius)) { - if (entity.HasComponent() && entity != owner) - { - yield return new MeleeWeaponAttackEntity(owner, entity, Bonus); - } + yield return new MeleeWeaponAttackEntity(owner, target, Bonus); } } } diff --git a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbySpeciesExp.cs b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbySpeciesExp.cs deleted file mode 100644 index bd78ab74ec..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbySpeciesExp.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Combat.Melee; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Mobs; - -namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee -{ - public sealed class MeleeAttackNearbySpeciesExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.CombatBonus; - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - yield return new MeleeWeaponAttackEntity(owner, entity, Bonus); - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyPlayerExp.cs b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyPlayerExp.cs index 46f1f79479..e1d1a7af0f 100644 --- a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyPlayerExp.cs +++ b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyPlayerExp.cs @@ -8,8 +8,10 @@ using Content.Server.AI.Utils; using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States; using Content.Server.GameObjects.Components.Movement; +using Content.Server.GameObjects.EntitySystems.AI; using Content.Shared.GameObjects.Components.Body; using Robust.Server.GameObjects; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.IoC; namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee @@ -37,13 +39,10 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee throw new InvalidOperationException(); } - foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(IBodyManagerComponent), - controller.VisionRadius)) + foreach (var target in EntitySystem.Get() + .GetNearbyHostiles(owner, controller.VisionRadius)) { - if (entity.HasComponent() && entity != owner) - { - yield return new UnarmedAttackEntity(owner, entity, Bonus); - } + yield return new UnarmedAttackEntity(owner, target, Bonus); } } } diff --git a/Content.Server/GameObjects/Components/AI/AiFactionTagComponent.cs b/Content.Server/GameObjects/Components/AI/AiFactionTagComponent.cs new file mode 100644 index 0000000000..78902e8404 --- /dev/null +++ b/Content.Server/GameObjects/Components/AI/AiFactionTagComponent.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Server.GameObjects.Components.AI +{ + [RegisterComponent] + public sealed class AiFactionTagComponent : Component + { + public override string Name => "AiFactionTag"; + + public Faction Factions { get; private set; } = Faction.None; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataReadWriteFunction( + "factions", + new List(), + factions => factions.ForEach(faction => Factions |= faction), + () => + { + var writeFactions = new List(); + foreach (Faction fac in Enum.GetValues(typeof(Faction))) + { + if ((Factions & fac) != 0) + { + writeFactions.Add(fac); + } + } + + return writeFactions; + }); + } + } + + [Flags] + public enum Faction + { + None = 0, + NanoTransen = 1 << 0, + SimpleHostile = 1 << 1, + SimpleNeutral = 1 << 2, + Syndicate = 1 << 3, + Xeno = 1 << 4, + } +} \ No newline at end of file diff --git a/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs new file mode 100644 index 0000000000..e12fc1633e --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Content.Server.GameObjects.Components.AI; +using Robust.Server.Interfaces.Console; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.GameObjects.EntitySystems.AI +{ + /// + /// Outlines faction relationships with each other for AI. + /// + public sealed class AiFactionTagSystem : EntitySystem + { + /* + * Currently factions are implicitly friendly if they are not hostile. + * This may change where specified friendly factions are listed. (e.g. to get number of friendlies in area). + */ + + public Faction GetHostileFactions(Faction faction) => _hostileFactions.TryGetValue(faction, out var hostiles) ? hostiles : Faction.None; + + private Dictionary _hostileFactions = new Dictionary + { + {Faction.NanoTransen, + Faction.SimpleHostile | Faction.Syndicate | Faction.Xeno}, + {Faction.SimpleHostile, + Faction.NanoTransen | Faction.Syndicate + }, + // What makes a man turn neutral? + {Faction.SimpleNeutral, + Faction.None + }, + {Faction.Syndicate, + Faction.NanoTransen | Faction.SimpleHostile | Faction.Xeno}, + {Faction.Xeno, + Faction.NanoTransen | Faction.Syndicate}, + }; + + public Faction GetFactions(IEntity entity) => + entity.TryGetComponent(out AiFactionTagComponent factionTags) + ? factionTags.Factions + : Faction.None; + + public IEnumerable GetNearbyHostiles(IEntity entity, float range) + { + var ourFaction = GetFactions(entity); + var hostile = GetHostileFactions(ourFaction); + if (ourFaction == Faction.None || hostile == Faction.None) + { + yield break; + } + + foreach (var component in ComponentManager.EntityQuery()) + { + if ((component.Factions & hostile) == 0) + continue; + if (component.Owner.Transform.MapID != entity.Transform.MapID) + continue; + if (!component.Owner.Transform.MapPosition.InRange(entity.Transform.MapPosition, range)) + continue; + + yield return component.Owner; + } + } + + public void MakeFriendly(Faction source, Faction target) + { + if (!_hostileFactions.TryGetValue(source, out var hostileFactions)) + { + return; + } + + hostileFactions &= ~target; + _hostileFactions[source] = hostileFactions; + } + + public void MakeHostile(Faction source, Faction target) + { + if (!_hostileFactions.TryGetValue(source, out var hostileFactions)) + { + _hostileFactions[source] = target; + return; + } + + hostileFactions |= target; + _hostileFactions[source] = hostileFactions; + } + } + + public sealed class FactionCommand : IClientCommand + { + public string Command => "factions"; + public string Description => "Update / list factional relationships for NPCs."; + public string Help => "faction target\n" + + "faction list: hostile factions"; + + public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + { + if (args.Length == 0) + { + var result = new StringBuilder(); + foreach (Faction value in Enum.GetValues(typeof(Faction))) + { + if (value == Faction.None) + continue; + result.Append(value + "\n"); + } + + shell.SendText(player, result.ToString()); + return; + } + + if (args.Length < 2) + { + shell.SendText(player, Loc.GetString("Need more args")); + return; + } + + if (!Enum.TryParse(args[0], true, out Faction faction)) + { + shell.SendText(player, Loc.GetString("Invalid faction")); + return; + } + + Faction targetFaction; + + switch (args[1]) + { + case "friendly": + if (args.Length < 3) + { + shell.SendText(player, Loc.GetString("Need to supply a target faction")); + return; + } + + if (!Enum.TryParse(args[2], true, out targetFaction)) + { + shell.SendText(player, Loc.GetString("Invalid target faction")); + return; + } + + EntitySystem.Get().MakeFriendly(faction, targetFaction); + shell.SendText(player, Loc.GetString("Command successful")); + break; + case "hostile": + if (args.Length < 3) + { + shell.SendText(player, Loc.GetString("Need to supply a target faction")); + return; + } + + if (!Enum.TryParse(args[2], true, out targetFaction)) + { + shell.SendText(player, Loc.GetString("Invalid target faction")); + return; + } + + EntitySystem.Get().MakeHostile(faction, targetFaction); + shell.SendText(player, Loc.GetString("Command successful")); + break; + case "list": + shell.SendText(player, EntitySystem.Get().GetHostileFactions(faction).ToString()); + break; + default: + shell.SendText(player, Loc.GetString("Unknown faction arg")); + break; + } + + return; + } + } +} \ No newline at end of file diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index e18adbfd95..71787fbcaa 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -37,6 +37,7 @@ - loc - hostlogin - events + - factions - Index: 100 Name: Administrator @@ -97,6 +98,7 @@ - events - destroymechanism - readyall + - factions CanViewVar: true CanAdminPlace: true @@ -188,6 +190,7 @@ - events - destroymechanism - readyall + - factions CanViewVar: true CanAdminPlace: true CanScript: true diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 59c5531583..70fba4d857 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -11,6 +11,9 @@ components: - type: AiController logic: Civilian + - type: AiFactionTag + factions: + - SimpleNeutral - type: MovementSpeedModifier baseWalkSpeed : 4 baseSprintSpeed : 4 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index bf711b3462..20f6b34c4f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -8,6 +8,9 @@ components: - type: AiController logic: Xeno + - type: AiFactionTag + factions: + - SimpleHostile - type: MovementSpeedModifier - type: InteractionOutline - type: Sprite diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml index cd5cdbc22e..37caacb018 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml @@ -8,6 +8,10 @@ components: - type: AiController logic: Civilian + - type: AiFactionTag + factions: + - NanoTransen + - type: entity save: false diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml index 9ac97a7d3a..6e3ed9bee7 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml @@ -9,6 +9,9 @@ components: - type: AiController logic: Mimic + - type: AiFactionTag + factions: + - SimpleHostile - type: Hands - type: MovementSpeedModifier - type: InteractionOutline diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index d7520299b8..37be27fe7b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -12,6 +12,9 @@ components: - type: AiController logic: Civilian + - type: AiFactionTag + factions: + - SimpleNeutral - type: MovementSpeedModifier baseWalkSpeed : 5 baseSprintSpeed : 5 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 2985794d4e..5100d62b5c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -9,6 +9,9 @@ components: - type: AiController logic: Xeno + - type: AiFactionTag + factions: + - Xeno - type: Hands - type: MovementSpeedModifier - type: InteractionOutline diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index 129866f5bd..7a5ddd085c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -17,3 +17,6 @@ - type: CameraRecoil - type: Examiner - type: HumanInventoryController + - type: AiFactionTag + factions: + - NanoTransen From 56ebde7f4583f05358127bed4e7e6afa15a21764 Mon Sep 17 00:00:00 2001 From: Exp Date: Mon, 24 Aug 2020 13:13:26 +0200 Subject: [PATCH 46/88] Add IItemStatus to some weapon types (#1879) * Bolt Action * Shotguns * Revolver + Outline * In Magazines: show bullets as numbers plus the usual bullet stuff * Empty bullets have another texture --- .../ClientBoltActionBarrelComponent.cs | 206 +++++++++++++++++ .../Barrels/ClientMagazineBarrelComponent.cs | 105 ++++----- .../Barrels/ClientPumpBarrelComponent.cs | 208 ++++++++++++++++++ .../Barrels/ClientRevolverBarrelComponent.cs | 175 +++++++++++++++ Content.Client/IgnoredComponents.cs | 3 - .../Barrels/BoltActionBarrelComponent.cs | 29 ++- .../Ranged/Barrels/PumpBarrelComponent.cs | 29 ++- .../Ranged/Barrels/RevolverBarrelComponent.cs | 31 ++- .../SharedBoltActionBarrelComponent.cs | 30 +++ .../Barrels/SharedPumpBarrelComponent.cs | 30 +++ .../Barrels/SharedRevolverBarrelComponent.cs | 30 +++ Content.Shared/GameObjects/ContentNetIDs.cs | 3 + .../ItemStatus/Bullets/chambered_rotated.png | Bin 0 -> 496 bytes .../Interface/ItemStatus/Bullets/empty.png | Bin 0 -> 524 bytes 14 files changed, 810 insertions(+), 69 deletions(-) create mode 100644 Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBoltActionBarrelComponent.cs create mode 100644 Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientPumpBarrelComponent.cs create mode 100644 Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientRevolverBarrelComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBoltActionBarrelComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedPumpBarrelComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedRevolverBarrelComponent.cs create mode 100644 Resources/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png create mode 100644 Resources/Textures/Interface/ItemStatus/Bullets/empty.png diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBoltActionBarrelComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBoltActionBarrelComponent.cs new file mode 100644 index 0000000000..778cb19599 --- /dev/null +++ b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBoltActionBarrelComponent.cs @@ -0,0 +1,206 @@ +using Content.Client.UserInterface.Stylesheets; +using Content.Client.Utility; +using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; +using System; + +namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels +{ + [RegisterComponent] + public class ClientBoltActionBarrelComponent : Component, IItemStatus + { + public override string Name => "BoltActionBarrel"; + public override uint? NetID => ContentNetIDs.BOLTACTION_BARREL; + + private StatusControl _statusControl; + + /// + /// chambered is true when a bullet is chambered + /// spent is true when the chambered bullet is spent + /// + [ViewVariables] + public (bool chambered, bool spent) Chamber { get; private set; } + + /// + /// Count of bullets in the magazine. + /// + /// + /// Null if no magazine is inserted. + /// + [ViewVariables] + public (int count, int max)? MagazineCount { get; private set; } + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + if (!(curState is BoltActionBarrelComponentState cast)) + return; + + Chamber = cast.Chamber; + MagazineCount = cast.Magazine; + _statusControl?.Update(); + } + + public Control MakeControl() + { + _statusControl = new StatusControl(this); + _statusControl.Update(); + return _statusControl; + } + + public void DestroyControl(Control control) + { + if (_statusControl == control) + { + _statusControl = null; + } + } + + private sealed class StatusControl : Control + { + private readonly ClientBoltActionBarrelComponent _parent; + private readonly HBoxContainer _bulletsListTop; + private readonly HBoxContainer _bulletsListBottom; + private readonly TextureRect _chamberedBullet; + private readonly Label _noMagazineLabel; + + public StatusControl(ClientBoltActionBarrelComponent parent) + { + _parent = parent; + SizeFlagsHorizontal = SizeFlags.FillExpand; + SizeFlagsVertical = SizeFlags.ShrinkCenter; + AddChild(new VBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SeparationOverride = 0, + Children = + { + (_bulletsListTop = new HBoxContainer {SeparationOverride = 0}), + new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + new Control + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + (_bulletsListBottom = new HBoxContainer + { + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SeparationOverride = 0 + }), + (_noMagazineLabel = new Label + { + Text = "No Magazine!", + StyleClasses = {StyleNano.StyleClassItemStatus} + }) + } + }, + (_chamberedBullet = new TextureRect + { + Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"), + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill, + }) + } + } + } + }); + } + + public void Update() + { + _chamberedBullet.ModulateSelfOverride = + _parent.Chamber.chambered ? + _parent.Chamber.spent ? Color.Red : Color.FromHex("#d7df60") + : Color.Black; + + _bulletsListTop.RemoveAllChildren(); + _bulletsListBottom.RemoveAllChildren(); + + if (_parent.MagazineCount == null) + { + _noMagazineLabel.Visible = true; + return; + } + + var (count, capacity) = _parent.MagazineCount.Value; + + _noMagazineLabel.Visible = false; + + string texturePath; + if (capacity <= 20) + { + texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png"; + } + else if (capacity <= 30) + { + texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png"; + } + else + { + texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png"; + } + + var texture = StaticIoC.ResC.GetTexture(texturePath); + + const int tinyMaxRow = 60; + + if (capacity > tinyMaxRow) + { + FillBulletRow(_bulletsListBottom, Math.Min(tinyMaxRow, count), tinyMaxRow, texture); + FillBulletRow(_bulletsListTop, Math.Max(0, count - tinyMaxRow), capacity - tinyMaxRow, texture); + } + else + { + FillBulletRow(_bulletsListBottom, count, capacity, texture); + } + } + + private static void FillBulletRow(Control container, int count, int capacity, Texture texture) + { + var colorA = Color.FromHex("#b68f0e"); + var colorB = Color.FromHex("#d7df60"); + var colorGoneA = Color.FromHex("#000000"); + var colorGoneB = Color.FromHex("#222222"); + + var altColor = false; + + for (var i = count; i < capacity; i++) + { + container.AddChild(new TextureRect + { + Texture = texture, + ModulateSelfOverride = altColor ? colorGoneA : colorGoneB + }); + + altColor ^= true; + } + + for (var i = 0; i < count; i++) + { + container.AddChild(new TextureRect + { + Texture = texture, + ModulateSelfOverride = altColor ? colorA : colorB + }); + + altColor ^= true; + } + } + + protected override Vector2 CalculateMinimumSize() + { + return Vector2.ComponentMax((0, 15), base.CalculateMinimumSize()); + } + } + } +} diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientMagazineBarrelComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientMagazineBarrelComponent.cs index f879c7c8f0..f02d184cad 100644 --- a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientMagazineBarrelComponent.cs +++ b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientMagazineBarrelComponent.cs @@ -1,4 +1,4 @@ -using System; +using System; using Content.Client.Animations; using Content.Client.UserInterface.Stylesheets; using Content.Client.Utility; @@ -138,54 +138,52 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels private sealed class StatusControl : Control { private readonly ClientMagazineBarrelComponent _parent; - private readonly HBoxContainer _bulletsListTop; - private readonly HBoxContainer _bulletsListBottom; + private readonly HBoxContainer _bulletsList; private readonly TextureRect _chamberedBullet; private readonly Label _noMagazineLabel; + private readonly Label _ammoCount; public StatusControl(ClientMagazineBarrelComponent parent) { _parent = parent; SizeFlagsHorizontal = SizeFlags.FillExpand; SizeFlagsVertical = SizeFlags.ShrinkCenter; - AddChild(new VBoxContainer + + AddChild(new HBoxContainer { SizeFlagsHorizontal = SizeFlags.FillExpand, - SizeFlagsVertical = SizeFlags.ShrinkCenter, - SeparationOverride = 0, Children = { - (_bulletsListTop = new HBoxContainer {SeparationOverride = 0}), - new HBoxContainer + (_chamberedBullet = new TextureRect + { + Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png"), + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill, + }), + new Control() { CustomMinimumSize = (5,0) }, + new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, Children = { - new Control + (_bulletsList = new HBoxContainer { - SizeFlagsHorizontal = SizeFlags.FillExpand, - Children = - { - (_bulletsListBottom = new HBoxContainer - { - SizeFlagsVertical = SizeFlags.ShrinkCenter, - SeparationOverride = 0 - }), - (_noMagazineLabel = new Label - { - Text = "No Magazine!", - StyleClasses = {StyleNano.StyleClassItemStatus} - }) - } - }, - (_chamberedBullet = new TextureRect - { - Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"), SizeFlagsVertical = SizeFlags.ShrinkCenter, - SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill, + SeparationOverride = 0 + }), + (_noMagazineLabel = new Label + { + Text = "No Magazine!", + StyleClasses = {StyleNano.StyleClassItemStatus} }) } - } + }, + new Control() { CustomMinimumSize = (5,0) }, + (_ammoCount = new Label + { + StyleClasses = {StyleNano.StyleClassItemStatus}, + SizeFlagsHorizontal = SizeFlags.ShrinkEnd, + }), } }); } @@ -195,46 +193,26 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels _chamberedBullet.ModulateSelfOverride = _parent.Chambered ? Color.FromHex("#d7df60") : Color.Black; - _bulletsListTop.RemoveAllChildren(); - _bulletsListBottom.RemoveAllChildren(); + _bulletsList.RemoveAllChildren(); if (_parent.MagazineCount == null) { _noMagazineLabel.Visible = true; + _ammoCount.Visible = false; return; } var (count, capacity) = _parent.MagazineCount.Value; _noMagazineLabel.Visible = false; + _ammoCount.Visible = true; - string texturePath; - if (capacity <= 20) - { - texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png"; - } - else if (capacity <= 30) - { - texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png"; - } - else - { - texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png"; - } - + var texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png"; var texture = StaticIoC.ResC.GetTexture(texturePath); - const int tinyMaxRow = 60; - - if (capacity > tinyMaxRow) - { - FillBulletRow(_bulletsListBottom, Math.Min(tinyMaxRow, count), tinyMaxRow, texture); - FillBulletRow(_bulletsListTop, Math.Max(0, count - tinyMaxRow), capacity - tinyMaxRow, texture); - } - else - { - FillBulletRow(_bulletsListBottom, count, capacity, texture); - } + _ammoCount.Text = $"x{count:00}"; + capacity = Math.Min(capacity, 20); + FillBulletRow(_bulletsList, count, capacity, texture); } private static void FillBulletRow(Control container, int count, int capacity, Texture texture) @@ -246,23 +224,32 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels var altColor = false; + // Draw the empty ones for (var i = count; i < capacity; i++) { container.AddChild(new TextureRect { Texture = texture, - ModulateSelfOverride = altColor ? colorGoneA : colorGoneB + ModulateSelfOverride = altColor ? colorGoneA : colorGoneB, + SizeFlagsHorizontal = SizeFlags.Fill, + SizeFlagsVertical = SizeFlags.Fill, + Stretch = TextureRect.StretchMode.KeepCentered }); altColor ^= true; } + // Draw the full ones, but limit the count to the capacity + count = Math.Min(count, capacity); for (var i = 0; i < count; i++) { container.AddChild(new TextureRect { Texture = texture, - ModulateSelfOverride = altColor ? colorA : colorB + ModulateSelfOverride = altColor ? colorA : colorB, + SizeFlagsHorizontal = SizeFlags.Fill, + SizeFlagsVertical = SizeFlags.Fill, + Stretch = TextureRect.StretchMode.KeepCentered }); altColor ^= true; @@ -281,4 +268,4 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels } } } -} \ No newline at end of file +} diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientPumpBarrelComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientPumpBarrelComponent.cs new file mode 100644 index 0000000000..a2c58b8bbb --- /dev/null +++ b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientPumpBarrelComponent.cs @@ -0,0 +1,208 @@ +using Content.Client.UserInterface.Stylesheets; +using Content.Client.Utility; +using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels +{ + [RegisterComponent] + public class ClientPumpBarrelComponent : Component, IItemStatus + { + public override string Name => "PumpBarrel"; + public override uint? NetID => ContentNetIDs.PUMP_BARREL; + + private StatusControl _statusControl; + + /// + /// chambered is true when a bullet is chambered + /// spent is true when the chambered bullet is spent + /// + [ViewVariables] + public (bool chambered, bool spent) Chamber { get; private set; } + + /// + /// Count of bullets in the magazine. + /// + /// + /// Null if no magazine is inserted. + /// + [ViewVariables] + public (int count, int max)? MagazineCount { get; private set; } + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + if (!(curState is PumpBarrelComponentState cast)) + return; + + Chamber = cast.Chamber; + MagazineCount = cast.Magazine; + _statusControl?.Update(); + } + + public Control MakeControl() + { + _statusControl = new StatusControl(this); + _statusControl.Update(); + return _statusControl; + } + + public void DestroyControl(Control control) + { + if (_statusControl == control) + { + _statusControl = null; + } + } + + private sealed class StatusControl : Control + { + private readonly ClientPumpBarrelComponent _parent; + private readonly HBoxContainer _bulletsListTop; + private readonly HBoxContainer _bulletsListBottom; + private readonly TextureRect _chamberedBullet; + private readonly Label _noMagazineLabel; + + public StatusControl(ClientPumpBarrelComponent parent) + { + _parent = parent; + SizeFlagsHorizontal = SizeFlags.FillExpand; + SizeFlagsVertical = SizeFlags.ShrinkCenter; + AddChild(new VBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SeparationOverride = 0, + Children = + { + (_bulletsListTop = new HBoxContainer {SeparationOverride = 0}), + new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + new Control + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + (_bulletsListBottom = new HBoxContainer + { + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SeparationOverride = 0 + }), + (_noMagazineLabel = new Label + { + Text = "No Magazine!", + StyleClasses = {StyleNano.StyleClassItemStatus} + }) + } + }, + (_chamberedBullet = new TextureRect + { + Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"), + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill, + }) + } + } + } + }); + } + + public void Update() + { + _chamberedBullet.ModulateSelfOverride = + _parent.Chamber.chambered ? + _parent.Chamber.spent ? Color.Red : Color.FromHex("#d7df60") + : Color.Black; + + _bulletsListTop.RemoveAllChildren(); + _bulletsListBottom.RemoveAllChildren(); + + if (_parent.MagazineCount == null) + { + _noMagazineLabel.Visible = true; + return; + } + + var (count, capacity) = _parent.MagazineCount.Value; + + _noMagazineLabel.Visible = false; + + string texturePath; + if (capacity <= 20) + { + texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png"; + } + else if (capacity <= 30) + { + texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png"; + } + else + { + texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png"; + } + + var texture = StaticIoC.ResC.GetTexture(texturePath); + + const int tinyMaxRow = 60; + + if (capacity > tinyMaxRow) + { + FillBulletRow(_bulletsListBottom, Math.Min(tinyMaxRow, count), tinyMaxRow, texture); + FillBulletRow(_bulletsListTop, Math.Max(0, count - tinyMaxRow), capacity - tinyMaxRow, texture); + } + else + { + FillBulletRow(_bulletsListBottom, count, capacity, texture); + } + } + + private static void FillBulletRow(Control container, int count, int capacity, Texture texture) + { + var colorA = Color.FromHex("#b68f0e"); + var colorB = Color.FromHex("#d7df60"); + var colorGoneA = Color.FromHex("#000000"); + var colorGoneB = Color.FromHex("#222222"); + + var altColor = false; + + for (var i = count; i < capacity; i++) + { + container.AddChild(new TextureRect + { + Texture = texture, + ModulateSelfOverride = altColor ? colorGoneA : colorGoneB + }); + + altColor ^= true; + } + + for (var i = 0; i < count; i++) + { + container.AddChild(new TextureRect + { + Texture = texture, + ModulateSelfOverride = altColor ? colorA : colorB + }); + + altColor ^= true; + } + } + + protected override Vector2 CalculateMinimumSize() + { + return Vector2.ComponentMax((0, 15), base.CalculateMinimumSize()); + } + } + } +} diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientRevolverBarrelComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientRevolverBarrelComponent.cs new file mode 100644 index 0000000000..3d5d94f5a8 --- /dev/null +++ b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientRevolverBarrelComponent.cs @@ -0,0 +1,175 @@ +using Content.Client.UserInterface.Stylesheets; +using Content.Client.Utility; +using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices.ComTypes; +using System.Text; + +namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels +{ + [RegisterComponent] + public class ClientRevolverBarrelComponent : Component, IItemStatus + { + public override string Name => "RevolverBarrel"; + public override uint? NetID => ContentNetIDs.REVOLVER_BARREL; + + private StatusControl _statusControl; + + /// + /// A array that lists the bullet states + /// true means a spent bullet + /// false means a "shootable" bullet + /// null means no bullet + /// + [ViewVariables] + public bool?[] Bullets { get; private set; } + + [ViewVariables] + public int CurrentSlot { get; private set; } + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + if (!(curState is RevolverBarrelComponentState cast)) + return; + + CurrentSlot = cast.CurrentSlot; + Bullets = cast.Bullets; + _statusControl?.Update(); + } + + public Control MakeControl() + { + _statusControl = new StatusControl(this); + _statusControl.Update(); + return _statusControl; + } + + public void DestroyControl(Control control) + { + if (_statusControl == control) + { + _statusControl = null; + } + } + + private sealed class StatusControl : Control + { + private readonly ClientRevolverBarrelComponent _parent; + private readonly HBoxContainer _bulletsList; + + public StatusControl(ClientRevolverBarrelComponent parent) + { + _parent = parent; + SizeFlagsHorizontal = SizeFlags.FillExpand; + SizeFlagsVertical = SizeFlags.ShrinkCenter; + AddChild((_bulletsList = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SeparationOverride = 0 + })); + } + + public void Update() + { + _bulletsList.RemoveAllChildren(); + + var capacity = _parent.Bullets.Length; + + string texturePath; + if (capacity <= 20) + { + texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png"; + } + else if (capacity <= 30) + { + texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png"; + } + else + { + texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png"; + } + + var texture = StaticIoC.ResC.GetTexture(texturePath); + var spentTexture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/empty.png"); + + FillBulletRow(_bulletsList, texture, spentTexture); + } + + private void FillBulletRow(Control container, Texture texture, Texture emptyTexture) + { + var colorA = Color.FromHex("#b68f0e"); + var colorB = Color.FromHex("#d7df60"); + var colorSpentA = Color.FromHex("#b50e25"); + var colorSpentB = Color.FromHex("#d3745f"); + var colorGoneA = Color.FromHex("#000000"); + var colorGoneB = Color.FromHex("#222222"); + + var altColor = false; + var scale = 1.3f; + + for (var i = 0; i < _parent.Bullets.Length; i++) + { + var bulletSpent = _parent.Bullets[i]; + // Add a outline + var box = new Control() + { + CustomMinimumSize = texture.Size * scale, + }; + if (i == _parent.CurrentSlot) + { + box.AddChild(new TextureRect + { + Texture = texture, + TextureScale = (scale, scale), + ModulateSelfOverride = Color.Green, + }); + } + Color color; + Texture bulletTexture = texture; + + if (bulletSpent.HasValue) + { + if (bulletSpent.Value) + { + color = altColor ? colorSpentA : colorSpentB; + bulletTexture = emptyTexture; + } + else + { + color = altColor ? colorA : colorB; + } + } + else + { + color = altColor ? colorGoneA : colorGoneB; + } + + box.AddChild(new TextureRect + { + SizeFlagsHorizontal = SizeFlags.Fill, + SizeFlagsVertical = SizeFlags.Fill, + Stretch = TextureRect.StretchMode.KeepCentered, + Texture = bulletTexture, + ModulateSelfOverride = color, + }); + altColor ^= true; + container.AddChild(box); + } + } + + protected override Vector2 CalculateMinimumSize() + { + return Vector2.ComponentMax((0, 15), base.CalculateMinimumSize()); + } + } + } +} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 72785bce6f..9a72a27e22 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -86,9 +86,6 @@ "CanSpill", "SpeedLoader", "Hitscan", - "BoltActionBarrel", - "PumpBarrel", - "RevolverBarrel", "ExplosiveProjectile", "StunnableProjectile", "RandomPottedPlant", diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs index cc0d0d74b2..3e4b6362fc 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; +using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.Verbs; @@ -32,6 +33,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels // but it felt a lot messier to play around with, especially when adding verbs public override string Name => "BoltActionBarrel"; + public override uint? NetID => ContentNetIDs.BOLTACTION_BARREL; public override int ShotsLeft { @@ -123,6 +125,24 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels UpdateAppearance(); } + public override ComponentState GetComponentState() + { + (int, int)? count = (ShotsLeft, Capacity); + var chamberedExists = _chamberContainer.ContainedEntity != null; + // (Is one chambered?, is the bullet spend) + var chamber = (chamberedExists, false); + if (chamberedExists && _chamberContainer.ContainedEntity.TryGetComponent(out var ammo)) + { + chamber.Item2 = ammo.Spent; + } + + return new BoltActionBarrelComponentState( + chamber, + FireRateSelector, + count, + SoundGunshot); + } + public override void Initialize() { // TODO: Add existing ammo support on revolvers @@ -170,6 +190,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { Cycle(); } + else + { + Dirty(); + } + return chamberEntity?.GetComponent().TakeBullet(spawnAtGrid, spawnAtMap); } @@ -256,7 +281,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { EntitySystem.Get().PlayAtCoords(_soundInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); } - // Dirty(); + Dirty(); UpdateAppearance(); return true; } @@ -269,7 +294,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { EntitySystem.Get().PlayAtCoords(_soundInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); } - // Dirty(); + Dirty(); UpdateAppearance(); return true; } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs index 6859c0c258..9dd3d9c7b5 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; +using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; @@ -27,6 +28,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels public sealed class PumpBarrelComponent : ServerRangedBarrelComponent, IMapInit, IExamine { public override string Name => "PumpBarrel"; + public override uint? NetID => ContentNetIDs.PUMP_BARREL; public override int ShotsLeft { @@ -81,6 +83,23 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels UpdateAppearance(); } + public override ComponentState GetComponentState() + { + (int, int)? count = (ShotsLeft, Capacity); + var chamberedExists = _chamberContainer.ContainedEntity != null; + // (Is one chambered?, is the bullet spend) + var chamber = (chamberedExists, false); + if (chamberedExists && _chamberContainer.ContainedEntity.TryGetComponent(out var ammo)) + { + chamber.Item2 = ammo.Spent; + } + return new PumpBarrelComponentState( + chamber, + FireRateSelector, + count, + SoundGunshot); + } + public override void Initialize() { base.Initialize(); @@ -110,6 +129,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } _appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true); + Dirty(); UpdateAppearance(); } @@ -131,6 +151,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { Cycle(); } + else + { + Dirty(); + } + return chamberEntity?.GetComponent().TakeBullet(spawnAtGrid, spawnAtMap); } @@ -168,7 +193,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } } - // Dirty(); + Dirty(); UpdateAppearance(); } @@ -189,7 +214,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { _ammoContainer.Insert(eventArgs.Using); _spawnedAmmo.Push(eventArgs.Using); - // Dirty(); + Dirty(); UpdateAppearance(); if (_soundInsert != null) { diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs index 004e742b71..e976c6aeb3 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; +using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.Verbs; @@ -25,6 +26,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels public sealed class RevolverBarrelComponent : ServerRangedBarrelComponent { public override string Name => "RevolverBarrel"; + public override uint? NetID => ContentNetIDs.REVOLVER_BARREL; + private BallisticCaliber _caliber; private Container _ammoContainer; private int _currentSlot = 0; @@ -60,6 +63,26 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels serializer.DataField(ref _soundSpin, "soundSpin", "/Audio/Weapons/Guns/Misc/revolver_spin.ogg"); } + public override ComponentState GetComponentState() + { + var slotsSpent = new bool?[Capacity]; + for (var i = 0; i < Capacity; i++) + { + slotsSpent[i] = null; + if (_ammoSlots[i] != null && _ammoSlots[i].TryGetComponent(out AmmoComponent ammo)) + { + slotsSpent[i] = ammo.Spent; + } + } + + //TODO: make yaml var to not sent currentSlot/UI? (for russian roulette) + return new RevolverBarrelComponentState( + _currentSlot, + FireRateSelector, + slotsSpent, + SoundGunshot); + } + public override void Initialize() { base.Initialize(); @@ -90,6 +113,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } _appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true); + Dirty(); } private void UpdateAppearance() @@ -129,7 +153,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels EntitySystem.Get().PlayAtCoords(_soundInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); } - // Dirty(); + Dirty(); UpdateAppearance(); return true; } @@ -143,7 +167,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { // Move up a slot _currentSlot = (_currentSlot + 1) % _ammoSlots.Length; - // Dirty(); + Dirty(); UpdateAppearance(); } @@ -158,6 +182,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { EntitySystem.Get().PlayAtCoords(_soundSpin, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); } + Dirty(); } public override IEntity PeekAmmo() @@ -227,7 +252,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels public override bool UseEntity(UseEntityEventArgs eventArgs) { EjectAllSlots(); - //Dirty(); + Dirty(); UpdateAppearance(); return true; } diff --git a/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBoltActionBarrelComponent.cs b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBoltActionBarrelComponent.cs new file mode 100644 index 0000000000..f2e8e52b80 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBoltActionBarrelComponent.cs @@ -0,0 +1,30 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels +{ + [Serializable, NetSerializable] + public class BoltActionBarrelComponentState : ComponentState + { + public (bool chambered, bool spent) Chamber { get; } + public FireRateSelector FireRateSelector { get; } + public (int count, int max)? Magazine { get; } + public string SoundGunshot { get; } + + public BoltActionBarrelComponentState( + (bool chambered, bool spent) chamber, + FireRateSelector fireRateSelector, + (int count, int max)? magazine, + string soundGunshot) : + base(ContentNetIDs.BOLTACTION_BARREL) + { + Chamber = chamber; + FireRateSelector = fireRateSelector; + Magazine = magazine; + SoundGunshot = soundGunshot; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedPumpBarrelComponent.cs b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedPumpBarrelComponent.cs new file mode 100644 index 0000000000..a577e11ad0 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedPumpBarrelComponent.cs @@ -0,0 +1,30 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels +{ + [Serializable, NetSerializable] + public class PumpBarrelComponentState : ComponentState + { + public (bool chambered, bool spent) Chamber { get; } + public FireRateSelector FireRateSelector { get; } + public (int count, int max)? Magazine { get; } + public string SoundGunshot { get; } + + public PumpBarrelComponentState( + (bool chambered, bool spent) chamber, + FireRateSelector fireRateSelector, + (int count, int max)? magazine, + string soundGunshot) : + base(ContentNetIDs.PUMP_BARREL) + { + Chamber = chamber; + FireRateSelector = fireRateSelector; + Magazine = magazine; + SoundGunshot = soundGunshot; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedRevolverBarrelComponent.cs b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedRevolverBarrelComponent.cs new file mode 100644 index 0000000000..3ed085101b --- /dev/null +++ b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedRevolverBarrelComponent.cs @@ -0,0 +1,30 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels +{ + [Serializable, NetSerializable] + public class RevolverBarrelComponentState : ComponentState + { + public int CurrentSlot { get; } + public FireRateSelector FireRateSelector { get; } + public bool?[] Bullets { get; } + public string SoundGunshot { get; } + + public RevolverBarrelComponentState( + int currentSlot, + FireRateSelector fireRateSelector, + bool?[] bullets, + string soundGunshot) : + base(ContentNetIDs.REVOLVER_BARREL) + { + CurrentSlot = currentSlot; + FireRateSelector = fireRateSelector; + Bullets = bullets; + SoundGunshot = soundGunshot; + } + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 7d7bcfdc4f..2eb1e59b58 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -65,6 +65,9 @@ public const uint RADIATION_PULSE = 1059; public const uint BODY_MANAGER = 1060; public const uint CLIMBING = 1061; + public const uint BOLTACTION_BARREL = 1062; + public const uint PUMP_BARREL = 1063; + public const uint REVOLVER_BARREL = 1064; // Net IDs for integration tests. public const uint PREDICTION_TEST = 10001; diff --git a/Resources/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png b/Resources/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png new file mode 100644 index 0000000000000000000000000000000000000000..44d729046940d1446a4559956a8f893898e22919 GIT binary patch literal 496 zcmVaTcyQTI_MxGlA(%23&nysY8|>1DYb%Cm*h2Rg(M9xDN66CLUxJ zxkP+O9OraE;wzym9zP2%d;Bh#7xHs-H!)Kxcd^pNtilcA3F4Tc*I4^J;wBN*TwI_& zOlt%-^2i_!2?}ahgANA?>Lz9OyD!=D&7(>lMsEvsaB`GUA#a@28ckKHYox36=%|a_ zf97dxsY!2oBn|XFx$fH-5ZnQp4cGlTa^2=J5O@SG%#OcGIZc0%-sott10cEyoLzP_ zWfwT#0)`$enaeFHK+CUb!0Rc{UjznjfW8$ktM+%C+yjLT`bF2k-aar+`=hJh<2Gmg zO!Jt800009a7bBm000kR000kR0jNKxX#fBKCP_p=R2WxdWIzJ{fdIr?rK2*00004Tx04UFuk-ba9P!z>aTcx6v3Ob00WT@iMLQxP$twWb0rB<-&lBP*3Bxy)e zq__$$4h09%#jnBtKv%(4K@b!{#KFJ7uSH5cX$mc(gZFWBf1JE?dG9{JpHd~w>WBlH zZkw58LRiWz3&9uo5M+*^Pn0ZUHl0r4&-lFKPR4D_?S=nEPeHaMz}H2dk_^)(&yZ8= zw&C(Nc|axpu$FsRp-op_r)8&d6emq_%%?4Nk?*S= z)MYLeRd?<`=V>XE6JGaF0_c2loVQURxC7K{j`MZoIQ3&7@CaP!Eq|GFn*N|(YiW@K zAiN2jUADCRE^xjD3_cj5DK?}4)2}GN>nYHi1Nv`(?iFuV&Hp&L2eKP{qHADp9~k5Q z@ai{zs%Os$mSnF0000SaNLh0L01sdQ01sdRdU!>R0000xNkl Date: Mon, 24 Aug 2020 21:16:23 +1000 Subject: [PATCH 47/88] Slight DisposalUnit optimisation (#1874) UI was being updated every tick even when not necessary. Ideally you'd just update the UI based on events but this is fine for now. Co-authored-by: Metal Gear Sloth --- .../Disposal/DisposalUnitComponent.cs | 29 +++++++++++++++++-- .../Disposal/SharedDisposalUnitComponent.cs | 13 ++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs index 24ca2e6ea5..bb16efdbdf 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -117,6 +117,13 @@ namespace Content.Server.GameObjects.Components.Disposal ? boundUi : null; + private DisposalUnitBoundUserInterfaceState? _lastUiState; + + /// + /// Store the translated state. + /// + private (PressureState State, string Localized) _locState; + public bool CanInsert(IEntity entity) { if (!Anchored) @@ -286,13 +293,31 @@ namespace Content.Server.GameObjects.Components.Disposal private DisposalUnitBoundUserInterfaceState GetInterfaceState() { - var state = Loc.GetString($"{State}"); - return new DisposalUnitBoundUserInterfaceState(Owner.Name, state, _pressure, Powered, Engaged); + string stateString; + + if (_locState.State != State) + { + stateString = Loc.GetString($"{State}"); + _locState = (State, stateString); + } + else + { + stateString = _locState.Localized; + } + + return new DisposalUnitBoundUserInterfaceState(Owner.Name, stateString, _pressure, Powered, Engaged); } private void UpdateInterface() { var state = GetInterfaceState(); + + if (_lastUiState != null && _lastUiState.Equals(state)) + { + return; + } + + _lastUiState = state; UserInterface?.SetState(state); } diff --git a/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs index 9bcd7edbf0..d223b1a605 100644 --- a/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs +++ b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs @@ -58,7 +58,7 @@ namespace Content.Shared.GameObjects.Components.Disposal } [Serializable, NetSerializable] - public class DisposalUnitBoundUserInterfaceState : BoundUserInterfaceState + public class DisposalUnitBoundUserInterfaceState : BoundUserInterfaceState, IEquatable { public readonly string UnitName; public readonly string UnitState; @@ -75,6 +75,17 @@ namespace Content.Shared.GameObjects.Components.Disposal Powered = powered; Engaged = engaged; } + + public bool Equals(DisposalUnitBoundUserInterfaceState other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return UnitName == other.UnitName && + UnitState == other.UnitState && + Powered == other.Powered && + Engaged == other.Engaged && + Pressure.Equals(other.Pressure); + } } /// From 9e6459ac79359d1b51756f2d320a143ed5c77182 Mon Sep 17 00:00:00 2001 From: py01 <60152240+collinlunn@users.noreply.github.com> Date: Mon, 24 Aug 2020 05:20:11 -0600 Subject: [PATCH 48/88] Gun verb changes (#1888) * Bolt action ver fixes * Fixes bolt-action playing two sounds when cycling on empty magazine * MagazineBarrel open/close bolt verb visibility * mag stuff * Dirty checks on guns Co-authored-by: py01 --- .../Barrels/BoltActionBarrelComponent.cs | 78 +++++---- .../Barrels/ServerMagazineBarrelComponent.cs | 158 +++++++++++------- 2 files changed, 144 insertions(+), 92 deletions(-) diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs index 3e4b6362fc..3755370ea7 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs @@ -69,6 +69,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels if (value) { + TryEjectChamber(); if (_soundBoltOpen != null) { soundSystem.PlayAtCoords(_soundBoltOpen, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); @@ -76,6 +77,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } else { + TryFeedChamber(); if (_soundBoltClosed != null) { soundSystem.PlayAtCoords(_soundBoltClosed, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); @@ -84,6 +86,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels _boltOpen = value; UpdateAppearance(); + Dirty(); } } private bool _boltOpen; @@ -210,29 +213,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels private void Cycle(bool manual = false) { - var chamberedEntity = _chamberContainer.ContainedEntity; - if (chamberedEntity != null) - { - _chamberContainer.Remove(chamberedEntity); - var ammoComponent = chamberedEntity.GetComponent(); - if (!ammoComponent.Caseless) - { - EjectCasing(chamberedEntity); - } - } - - if (_spawnedAmmo.TryPop(out var next)) - { - _ammoContainer.Remove(next); - _chamberContainer.Insert(next); - } - - if (_unspawnedCount > 0) - { - _unspawnedCount--; - var ammoEntity = Owner.EntityManager.SpawnEntity(_fillPrototype, Owner.Transform.GridPosition); - _chamberContainer.Insert(ammoEntity); - } + TryEjectChamber(); + TryFeedChamber(); if (_chamberContainer.ContainedEntity == null && manual) { @@ -241,9 +223,9 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { Owner.PopupMessage(container.Owner, Loc.GetString("Bolt opened")); } + return; } - - if (manual) + else { if (_soundCycle != null) { @@ -310,11 +292,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { BoltOpen = false; Owner.PopupMessage(eventArgs.User, Loc.GetString("Bolt closed")); - // Dirty(); return true; } Cycle(true); + return true; } @@ -323,6 +305,46 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels return TryInsertBullet(eventArgs.User, eventArgs.Using); } + private bool TryEjectChamber() + { + var chamberedEntity = _chamberContainer.ContainedEntity; + if (chamberedEntity != null) + { + if (!_chamberContainer.Remove(chamberedEntity)) + { + return false; + } + if (!chamberedEntity.GetComponent().Caseless) + { + EjectCasing(chamberedEntity); + } + return true; + } + return false; + } + + private bool TryFeedChamber() + { + if (_chamberContainer.ContainedEntity != null) + { + return false; + } + if (_spawnedAmmo.TryPop(out var next)) + { + _ammoContainer.Remove(next); + _chamberContainer.Insert(next); + return true; + } + else if (_unspawnedCount > 0) + { + _unspawnedCount--; + var ammoEntity = Owner.EntityManager.SpawnEntity(_fillPrototype, Owner.Transform.GridPosition); + _chamberContainer.Insert(ammoEntity); + return true; + } + return false; + } + public override void Examine(FormattedMessage message, bool inDetailsRange) { base.Examine(message, inDetailsRange); @@ -342,7 +364,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } data.Text = Loc.GetString("Open bolt"); - data.Visibility = component.BoltOpen ? VerbVisibility.Disabled : VerbVisibility.Visible; + data.Visibility = component.BoltOpen ? VerbVisibility.Invisible : VerbVisibility.Visible; } protected override void Activate(IEntity user, BoltActionBarrelComponent component) @@ -363,7 +385,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } data.Text = Loc.GetString("Close bolt"); - data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Disabled; + data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Invisible; } protected override void Activate(IEntity user, BoltActionBarrelComponent component) diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs index 7999fce221..ed7ec35452 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs @@ -81,7 +81,42 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels private string _magFillPrototype; - public bool BoltOpen { get; private set; } = true; + public bool BoltOpen + { + get => _boltOpen; + set + { + if (_boltOpen == value) + { + return; + } + + var soundSystem = EntitySystem.Get(); + + if (value) + { + TryEjectChamber(); + if (_soundBoltOpen != null) + { + soundSystem.PlayAtCoords(_soundBoltOpen, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); + } + } + else + { + TryFeedChamber(); + if (_soundBoltClosed != null) + { + soundSystem.PlayAtCoords(_soundBoltClosed, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); + } + } + + _boltOpen = value; + UpdateAppearance(); + Dirty(); + } + } + private bool _boltOpen = true; + private bool _autoEjectMag; // If the bolt needs to be open before we can insert / remove the mag (i.e. for LMGs) public bool MagNeedsOpenBolt => _magNeedsOpenBolt; @@ -170,30 +205,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels UpdateAppearance(); } - public void ToggleBolt() - { - // For magazines only when we normally set BoltOpen we'll defer the UpdateAppearance until everything is done - // Whereas this will just call it straight up. - BoltOpen = !BoltOpen; - var soundSystem = EntitySystem.Get(); - if (BoltOpen) - { - if (_soundBoltOpen != null) - { - soundSystem.PlayAtCoords(_soundBoltOpen, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-5)); - } - } - else - { - if (_soundBoltClosed != null) - { - soundSystem.PlayAtCoords(_soundBoltClosed, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-5)); - } - } - Dirty(); - UpdateAppearance(); - } - public override IEntity PeekAmmo() { return BoltOpen ? null : _chamberContainer.ContainedEntity; @@ -218,41 +229,13 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels return; } - var chamberEntity = _chamberContainer.ContainedEntity; - if (chamberEntity != null) - { - _chamberContainer.Remove(chamberEntity); - var ammoComponent = chamberEntity.GetComponent(); - if (!ammoComponent.Caseless) - { - EjectCasing(chamberEntity); - } - } + TryEjectChamber(); - // Try and pull a round from the magazine to replace the chamber if possible - var magazine = _magazineContainer.ContainedEntity; - var nextRound = magazine?.GetComponent().TakeAmmo(); - - if (nextRound != null) - { - // If you're really into gunporn you could put a sound here - _chamberContainer.Insert(nextRound); - } + TryFeedChamber(); var soundSystem = EntitySystem.Get(); - if (_autoEjectMag && magazine != null && magazine.GetComponent().ShotsLeft == 0) - { - if (_soundAutoEject != null) - { - soundSystem.PlayAtCoords(_soundAutoEject, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); - } - - _magazineContainer.Remove(magazine); - SendNetworkMessage(new MagazineAutoEjectMessage()); - } - - if (nextRound == null && !BoltOpen) + if (_chamberContainer.ContainedEntity == null && !BoltOpen) { if (_soundBoltOpen != null) { @@ -264,8 +247,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels Owner.PopupMessage(container.Owner, Loc.GetString("Bolt open")); } BoltOpen = true; - Dirty(); - UpdateAppearance(); return; } @@ -305,8 +286,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } Owner.PopupMessage(eventArgs.User, Loc.GetString("Bolt closed")); BoltOpen = false; - Dirty(); - UpdateAppearance(); return true; } @@ -316,6 +295,57 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels return true; } + public bool TryEjectChamber() + { + var chamberEntity = _chamberContainer.ContainedEntity; + if (chamberEntity != null) + { + if (!_chamberContainer.Remove(chamberEntity)) + { + return false; + } + var ammoComponent = chamberEntity.GetComponent(); + if (!ammoComponent.Caseless) + { + EjectCasing(chamberEntity); + } + return true; + } + return false; + } + + public bool TryFeedChamber() + { + if (_chamberContainer.ContainedEntity != null) + { + return false; + } + + // Try and pull a round from the magazine to replace the chamber if possible + var magazine = _magazineContainer.ContainedEntity; + var nextRound = magazine?.GetComponent().TakeAmmo(); + + if (nextRound == null) + { + return false; + } + + _chamberContainer.Insert(nextRound); + + if (_autoEjectMag && magazine != null && magazine.GetComponent().ShotsLeft == 0) + { + if (_soundAutoEject != null) + { + var soundSystem = EntitySystem.Get(); + soundSystem.PlayAtCoords(_soundAutoEject, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); + } + + _magazineContainer.Remove(magazine); + SendNetworkMessage(new MagazineAutoEjectMessage()); + } + return true; + } + public void RemoveMagazine(IEntity user) { var mag = _magazineContainer.ContainedEntity; @@ -470,12 +500,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } data.Text = Loc.GetString("Open bolt"); - data.Visibility = component.BoltOpen ? VerbVisibility.Disabled : VerbVisibility.Visible; + data.Visibility = component.BoltOpen ? VerbVisibility.Invisible : VerbVisibility.Visible; } protected override void Activate(IEntity user, ServerMagazineBarrelComponent component) { - component.ToggleBolt(); + component.BoltOpen = true; } } @@ -491,12 +521,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } data.Text = Loc.GetString("Close bolt"); - data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Disabled; + data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Invisible; } protected override void Activate(IEntity user, ServerMagazineBarrelComponent component) { - component.ToggleBolt(); + component.BoltOpen = false; } } } From a4f527351e5e984dc400f1f85fb3a497d4460133 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Mon, 24 Aug 2020 13:39:00 +0200 Subject: [PATCH 49/88] Add test for adding each component individually to an entity (#1870) * Add test for adding each component individually to entities * Put one-to-one test before all-at-once test --- Content.IntegrationTests/Tests/EntityTest.cs | 171 +++++++++++++----- .../TransformableContainerComponent.cs | 10 +- .../Components/Chemistry/VaporComponent.cs | 27 ++- .../Components/Fluids/BucketComponent.cs | 19 +- .../Components/Fluids/MopComponent.cs | 40 ++-- .../Components/Fluids/PuddleComponent.cs | 2 +- .../Components/Fluids/SprayComponent.cs | 16 +- .../Components/Movement/ClimbableComponent.cs | 8 +- .../EmergencyLightComponent.cs | 74 +++++--- .../PowerNetComponents/SolarPanelComponent.cs | 15 +- .../StunnableProjectileComponent.cs | 8 +- .../Barrels/ServerRangedBarrelComponent.cs | 8 +- .../GameObjects/Components/WiresComponent.cs | 13 +- 13 files changed, 297 insertions(+), 114 deletions(-) diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 4fd9846263..ae458d078b 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -61,22 +60,15 @@ namespace Content.IntegrationTests.Tests //Iterate list of prototypes to spawn foreach (var prototype in prototypes) { - try - { - Logger.LogS(LogLevel.Debug, "EntityTest", $"Testing: {prototype.ID}"); - testEntity = entityMan.SpawnEntity(prototype.ID, testLocation); - server.RunTicks(2); - Assert.That(testEntity.Initialized); - entityMan.DeleteEntity(testEntity.Uid); - } - - //Fail any exceptions thrown on spawn - catch (Exception e) - { - Logger.LogS(LogLevel.Error, "EntityTest", $"Entity '{prototype.ID}' threw: {e.Message}"); - Assert.Fail(); - throw; - } + Assert.DoesNotThrow(() => + { + Logger.LogS(LogLevel.Debug, "EntityTest", $"Testing: {prototype.ID}"); + testEntity = entityMan.SpawnEntity(prototype.ID, testLocation); + server.RunTicks(2); + Assert.That(testEntity.Initialized); + entityMan.DeleteEntity(testEntity.Uid); + }, "Entity '{0}' threw an exception.", + prototype.ID); } }); @@ -106,6 +98,92 @@ namespace Content.IntegrationTests.Tests await client.WaitIdleAsync(); } + [Test] + public async Task AllComponentsOneToOneDeleteTest() + { + var skipComponents = new[] + { + "DebugExceptionOnAdd", // Debug components that explicitly throw exceptions + "DebugExceptionExposeData", + "DebugExceptionInitialize", + "DebugExceptionStartup", + "Map", // We aren't testing a map entity in this test + "MapGrid" + }; + + var testEntity = @" +- type: entity + id: AllComponentsOneToOneDeleteTestEntity"; + + var server = StartServerDummyTicker(); + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + var mapLoader = server.ResolveDependency(); + var pauseManager = server.ResolveDependency(); + var componentFactory = server.ResolveDependency(); + var prototypeManager = server.ResolveDependency(); + + IMapGrid grid = default; + + server.Post(() => + { + // Load test entity + using var reader = new StringReader(testEntity); + prototypeManager.LoadFromStream(reader); + + // Load test map + var mapId = mapManager.CreateMap(); + pauseManager.AddUninitializedMap(mapId); + grid = mapLoader.LoadBlueprint(mapId, "Maps/stationstation.yml"); + pauseManager.DoMapInitialize(mapId); + }); + + server.Assert(() => + { + var testLocation = new GridCoordinates(new Vector2(0, 0), grid); + + foreach (var type in componentFactory.AllRegisteredTypes) + { + var component = (Component) componentFactory.GetComponent(type); + + // If this component is ignored + if (skipComponents.Contains(component.Name)) + { + continue; + } + + var entity = entityManager.SpawnEntity("AllComponentsOneToOneDeleteTestEntity", testLocation); + + Assert.That(entity.Initialized); + + // The component may already exist if it is a mandatory component + // such as MetaData or Transform + if (entity.HasComponent(type)) + { + continue; + } + + component.Owner = entity; + + Logger.LogS(LogLevel.Debug, "EntityTest", $"Adding component: {component.Name}"); + + Assert.DoesNotThrow(() => + { + entityManager.ComponentManager.AddComponent(entity, component); + }, "Component '{0}' threw an exception.", + component.Name); + + server.RunTicks(10); + + entityManager.DeleteEntity(entity.Uid); + } + }); + + await server.WaitIdleAsync(); + } + [Test] public async Task AllComponentsOneEntityDeleteTest() { @@ -184,43 +262,44 @@ namespace Content.IntegrationTests.Tests server.Assert(() => { - Assert.DoesNotThrow(() => + foreach (var distinct in distinctComponents) { - foreach (var distinct in distinctComponents) + var testLocation = new GridCoordinates(new Vector2(0, 0), grid); + var entity = entityManager.SpawnEntity("AllComponentsOneEntityDeleteTestEntity", testLocation); + + Assert.That(entity.Initialized); + + foreach (var type in distinct.components) { - var testLocation = new GridCoordinates(new Vector2(0, 0), grid); - var entity = entityManager.SpawnEntity("AllComponentsOneEntityDeleteTestEntity", testLocation); + var component = (Component) componentFactory.GetComponent(type); - Assert.That(entity.Initialized); - - foreach (var type in distinct.components) + // If the entity already has this component, if it was ensured or added by another + if (entity.HasComponent(component.GetType())) { - var component = (Component) componentFactory.GetComponent(type); - - // If the entity already has this component, if it was ensured or added by another - if (entity.HasComponent(component.GetType())) - { - continue; - } - - // If this component is ignored - if (skipComponents.Contains(component.Name)) - { - continue; - } - - component.Owner = entity; - - Logger.LogS(LogLevel.Debug, "EntityTest", $"Adding component: {component.Name}"); - - entityManager.ComponentManager.AddComponent(entity, component); + continue; } - server.RunTicks(48); // Run one full second on the server + // If this component is ignored + if (skipComponents.Contains(component.Name)) + { + continue; + } - entityManager.DeleteEntity(entity.Uid); + component.Owner = entity; + + Logger.LogS(LogLevel.Debug, "EntityTest", $"Adding component: {component.Name}"); + + Assert.DoesNotThrow(() => + { + entityManager.ComponentManager.AddComponent(entity, component); + }, "Component '{0}' threw an exception.", + component.Name); } - }); + + server.RunTicks(48); // Run one full second on the server + + entityManager.DeleteEntity(entity.Uid); + } }); await server.WaitIdleAsync(); diff --git a/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs b/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs index 571e78f64f..24f4879d55 100644 --- a/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs @@ -4,6 +4,7 @@ using Content.Shared.Chemistry; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Log; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -40,7 +41,14 @@ namespace Content.Server.GameObjects.Components.Chemistry protected override void Startup() { base.Startup(); - Owner.GetComponent().Capabilities |= SolutionCaps.FitsInDispenser; + + if (!Owner.EnsureComponent(out SolutionComponent solution)) + { + Logger.Warning( + $"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}"); + } + + solution.Capabilities |= SolutionCaps.FitsInDispenser; } public void CancelTransformation() diff --git a/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs b/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs index 066cf21836..ace097cce5 100644 --- a/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs @@ -7,6 +7,7 @@ using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; +using Robust.Shared.Log; using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -19,8 +20,6 @@ namespace Content.Server.GameObjects.Components.Chemistry [Dependency] private readonly IMapManager _mapManager = default!; public override string Name => "Vapor"; - [ViewVariables] - private SolutionComponent _contents; [ViewVariables] private ReagentUnit _transferAmount; @@ -28,11 +27,15 @@ namespace Content.Server.GameObjects.Components.Chemistry private Vector2 _direction; private float _velocity; - public override void Initialize() { base.Initialize(); - _contents = Owner.GetComponent(); + + if (!Owner.EnsureComponent(out SolutionComponent _)) + { + Logger.Warning( + $"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}"); + } } public void Start(Vector2 dir, float velocity) @@ -56,6 +59,9 @@ namespace Content.Server.GameObjects.Components.Chemistry public void Update() { + if (!Owner.TryGetComponent(out SolutionComponent contents)) + return; + if (!_running) return; @@ -70,11 +76,11 @@ namespace Content.Server.GameObjects.Components.Chemistry foreach (var tile in tiles) { var pos = tile.GridIndices.ToGridCoordinates(_mapManager, tile.GridIndex); - SpillHelper.SpillAt(pos, _contents.SplitSolution(amount), "PuddleSmear", false); //make non PuddleSmear? + SpillHelper.SpillAt(pos, contents.SplitSolution(amount), "PuddleSmear", false); //make non PuddleSmear? } } - if (_contents.CurrentVolume == 0) + if (contents.CurrentVolume == 0) { // Delete this Owner.Delete(); @@ -87,7 +93,14 @@ namespace Content.Server.GameObjects.Components.Chemistry { return false; } - var result = _contents.TryAddSolution(solution); + + if (!Owner.TryGetComponent(out SolutionComponent contents)) + { + return false; + } + + var result = contents.TryAddSolution(solution); + if (!result) { return false; diff --git a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs index e94cc91f43..ee190c6461 100644 --- a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs @@ -60,11 +60,18 @@ namespace Content.Server.GameObjects.Components.Fluids return false; } + var mopContents = mopComponent.Contents; + + if (mopContents == null) + { + return false; + } + // Let's fill 'er up // If this is called the mop should be empty but just in case we'll do Max - Current var transferAmount = ReagentUnit.Min(mopComponent.MaxVolume - mopComponent.CurrentVolume, CurrentVolume); var solution = contents.SplitSolution(transferAmount); - if (!mopComponent.Contents.TryAddSolution(solution) || mopComponent.CurrentVolume == 0) + if (!mopContents.TryAddSolution(solution) || mopComponent.CurrentVolume == 0) { return false; } @@ -109,7 +116,14 @@ namespace Content.Server.GameObjects.Components.Fluids return false; } - var solution = mopComponent.Contents.SplitSolution(transferAmount); + var mopContents = mopComponent.Contents; + + if (mopContents == null) + { + return false; + } + + var solution = mopContents.SplitSolution(transferAmount); if (!contents.TryAddSolution(solution)) { //This really shouldn't happen @@ -127,7 +141,6 @@ namespace Content.Server.GameObjects.Components.Fluids EntitySystem.Get().PlayFromEntity(_sound, Owner); return true; - } } } diff --git a/Content.Server/GameObjects/Components/Fluids/MopComponent.cs b/Content.Server/GameObjects/Components/Fluids/MopComponent.cs index 11188bcd5f..338af8611c 100644 --- a/Content.Server/GameObjects/Components/Fluids/MopComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/MopComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Chemistry; +#nullable enable +using Content.Server.GameObjects.Components.Chemistry; using Content.Server.Utility; using Content.Shared.Chemistry; using Content.Shared.Interfaces; @@ -7,6 +8,7 @@ using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Localization; +using Robust.Shared.Log; using Robust.Shared.Serialization; namespace Content.Server.GameObjects.Components.Fluids @@ -18,16 +20,23 @@ namespace Content.Server.GameObjects.Components.Fluids public class MopComponent : Component, IAfterInteract { public override string Name => "Mop"; - internal SolutionComponent Contents => _contents; - private SolutionComponent _contents; + + public SolutionComponent? Contents => Owner.GetComponentOrNull(); public ReagentUnit MaxVolume { - get => _contents.MaxVolume; - set => _contents.MaxVolume = value; + get => Owner.GetComponentOrNull()?.MaxVolume ?? ReagentUnit.Zero; + set + { + if (Owner.TryGetComponent(out SolutionComponent? solution)) + { + solution.MaxVolume = value; + } + } } - public ReagentUnit CurrentVolume => _contents.CurrentVolume; + public ReagentUnit CurrentVolume => + Owner.GetComponentOrNull()?.CurrentVolume ?? ReagentUnit.Zero; // Currently there's a separate amount for pickup and dropoff so // Picking up a puddle requires multiple clicks @@ -36,7 +45,7 @@ namespace Content.Server.GameObjects.Components.Fluids public ReagentUnit PickupAmount => _pickupAmount; private ReagentUnit _pickupAmount; - private string _pickupSound; + private string _pickupSound = ""; /// public override void ExposeData(ObjectSerializer serializer) @@ -49,12 +58,16 @@ namespace Content.Server.GameObjects.Components.Fluids public override void Initialize() { base.Initialize(); - _contents = Owner.GetComponent(); + if (!Owner.EnsureComponent(out SolutionComponent _)) + { + Logger.Warning($"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}"); + } } void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { + if (!Owner.TryGetComponent(out SolutionComponent? contents)) return; if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return; if (CurrentVolume <= 0) @@ -66,12 +79,12 @@ namespace Content.Server.GameObjects.Components.Fluids if (eventArgs.Target == null) { // Drop the liquid on the mop on to the ground - SpillHelper.SpillAt(eventArgs.ClickLocation, _contents.SplitSolution(CurrentVolume), "PuddleSmear"); + SpillHelper.SpillAt(eventArgs.ClickLocation, contents.SplitSolution(CurrentVolume), "PuddleSmear"); return; } - if (!eventArgs.Target.TryGetComponent(out PuddleComponent puddleComponent)) + if (!eventArgs.Target.TryGetComponent(out PuddleComponent? puddleComponent)) { return; } @@ -102,23 +115,22 @@ namespace Content.Server.GameObjects.Components.Fluids if (puddleCleaned) //After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly. { - SpillHelper.SpillAt(eventArgs.ClickLocation, _contents.SplitSolution(transferAmount), "PuddleSmear"); + SpillHelper.SpillAt(eventArgs.ClickLocation, contents.SplitSolution(transferAmount), "PuddleSmear"); } else { - _contents.SplitSolution(transferAmount); + contents.SplitSolution(transferAmount); } // Give some visual feedback shit's happening (for anyone who can't hear sound) Owner.PopupMessage(eventArgs.User, Loc.GetString("Swish")); - if (_pickupSound == null) + if (string.IsNullOrWhiteSpace(_pickupSound)) { return; } EntitySystem.Get().PlayFromEntity(_pickupSound, Owner); - } } } diff --git a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs index a3703a5e21..62551505c2 100644 --- a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs @@ -115,6 +115,7 @@ namespace Content.Server.GameObjects.Components.Fluids public override void Initialize() { base.Initialize(); + if (Owner.TryGetComponent(out SolutionComponent solutionComponent)) { _contents = solutionComponent; @@ -122,7 +123,6 @@ namespace Content.Server.GameObjects.Components.Fluids else { _contents = Owner.AddComponent(); - _contents.Initialize(); } _snapGrid = Owner.EnsureComponent(); diff --git a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs index d1e9c6f098..e10ae9c7df 100644 --- a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs @@ -8,6 +8,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.IoC; using Robust.Shared.Localization; +using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -45,13 +46,17 @@ namespace Content.Server.GameObjects.Components.Fluids set => _sprayVelocity = value; } - private SolutionComponent _contents; - public ReagentUnit CurrentVolume => _contents.CurrentVolume; + public ReagentUnit CurrentVolume => Owner.GetComponentOrNull()?.CurrentVolume ?? ReagentUnit.Zero; public override void Initialize() { base.Initialize(); - _contents = Owner.GetComponent(); + + if (!Owner.EnsureComponent(out SolutionComponent _)) + { + Logger.Warning( + $"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}"); + } } public override void ExposeData(ObjectSerializer serializer) @@ -74,8 +79,11 @@ namespace Content.Server.GameObjects.Components.Fluids if (eventArgs.ClickLocation.GridID != playerPos.GridID) return; + if (!Owner.TryGetComponent(out SolutionComponent contents)) + return; + var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized; - var solution = _contents.SplitSolution(_transferAmount); + var solution = contents.SplitSolution(_transferAmount); playerPos = playerPos.Offset(direction); // Move a bit so we don't hit the player //TODO: check for wall? diff --git a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs index 45baeb54e7..22d9e8a775 100644 --- a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs @@ -17,6 +17,7 @@ using Content.Server.GameObjects.Components.Body; using Content.Server.GameObjects.EntitySystems.DoAfter; using Robust.Shared.Maths; using System; +using Robust.Shared.Log; namespace Content.Server.GameObjects.Components.Movement { @@ -39,14 +40,17 @@ namespace Content.Server.GameObjects.Components.Movement [ViewVariables] private float _climbDelay; - private ICollidableComponent _collidableComponent; private DoAfterSystem _doAfterSystem; public override void Initialize() { base.Initialize(); - _collidableComponent = Owner.GetComponent(); + if (!Owner.EnsureComponent(out CollidableComponent _)) + { + Logger.Warning($"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(CollidableComponent)}"); + } + _doAfterSystem = EntitySystem.Get(); } diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs index 3fa04af25a..58a53cf253 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Localization; +using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; @@ -22,14 +22,6 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece [ViewVariables] private EmergencyLightState _lightState = EmergencyLightState.Charging; - [ViewVariables] - private BatteryComponent Battery => Owner.GetComponent(); - [ViewVariables] - private PointLightComponent Light => Owner.GetComponent(); - [ViewVariables] - private PowerReceiverComponent PowerReceiver => Owner.GetComponent(); - private SpriteComponent Sprite => Owner.GetComponent(); - [ViewVariables(VVAccess.ReadWrite)] private float _wattage; [ViewVariables(VVAccess.ReadWrite)] @@ -58,9 +50,14 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece /// public void UpdateState() { - if (PowerReceiver.Powered) + if (!Owner.TryGetComponent(out PowerReceiverComponent receiver)) { - PowerReceiver.Load = (int) Math.Abs(_wattage); + return; + } + + if (receiver.Powered) + { + receiver.Load = (int) Math.Abs(_wattage); TurnOff(); _lightState = EmergencyLightState.Charging; } @@ -76,9 +73,14 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece if (_lightState == EmergencyLightState.Empty || _lightState == EmergencyLightState.Full) return; + if (!Owner.TryGetComponent(out BatteryComponent battery)) + { + return; + } + if(_lightState == EmergencyLightState.On) { - if (!Battery.TryUseCharge(_wattage * frameTime)) + if (!battery.TryUseCharge(_wattage * frameTime)) { _lightState = EmergencyLightState.Empty; TurnOff(); @@ -86,10 +88,14 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece } else { - Battery.CurrentCharge += _chargingWattage * frameTime * _chargingEfficiency; - if (Battery.BatteryState == BatteryState.Full) + battery.CurrentCharge += _chargingWattage * frameTime * _chargingEfficiency; + if (battery.BatteryState == BatteryState.Full) { - PowerReceiver.Load = 1; + if (Owner.TryGetComponent(out PowerReceiverComponent receiver)) + { + receiver.Load = 1; + } + _lightState = EmergencyLightState.Full; } } @@ -97,25 +103,49 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece private void TurnOff() { - Sprite.LayerSetState(0, "emergency_light_off"); - Light.Enabled = false; + if (Owner.TryGetComponent(out SpriteComponent sprite)) + { + sprite.LayerSetState(0, "emergency_light_off"); + } + + if (Owner.TryGetComponent(out PointLightComponent light)) + { + light.Enabled = false; + } } private void TurnOn() { - Sprite.LayerSetState(0, "emergency_light_on"); - Light.Enabled = true; + if (Owner.TryGetComponent(out SpriteComponent sprite)) + { + sprite.LayerSetState(0, "emergency_light_on"); + } + + if (Owner.TryGetComponent(out PointLightComponent light)) + { + light.Enabled = true; + } } public override void Initialize() { base.Initialize(); - Owner.GetComponent().OnPowerStateChanged += UpdateState; + + if (!Owner.EnsureComponent(out PowerReceiverComponent receiver)) + { + Logger.Warning($"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(PowerReceiverComponent)}"); + } + + receiver.OnPowerStateChanged += UpdateState; } public override void OnRemove() { - Owner.GetComponent().OnPowerStateChanged -= UpdateState; + if (Owner.TryGetComponent(out PowerReceiverComponent receiver)) + { + receiver.OnPowerStateChanged -= UpdateState; + } + base.OnRemove(); } @@ -132,7 +162,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece On } - public Dictionary BatteryStateText = new Dictionary + public Dictionary BatteryStateText = new Dictionary { { EmergencyLightState.Full, "[color=darkgreen]Full[/color]"}, { EmergencyLightState.Empty, "[color=darkred]Empty[/color]"}, diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs index 6f63859193..fc79c97485 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs @@ -4,6 +4,7 @@ using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.Timing; +using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -19,8 +20,6 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents { public override string Name => "SolarPanel"; - private PowerSupplierComponent _powerSupplier; - /// /// Maximum supply output by this panel (coverage = 1) /// @@ -64,15 +63,21 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents private void UpdateSupply() { - if (_powerSupplier != null) - _powerSupplier.SupplyRate = (int) (_maxSupply * _coverage); + if (Owner.TryGetComponent(out PowerSupplierComponent supplier)) + { + supplier.SupplyRate = (int) (_maxSupply * _coverage); + } } public override void Initialize() { base.Initialize(); - _powerSupplier = Owner.GetComponent(); + if (!Owner.EnsureComponent(out PowerSupplierComponent _)) + { + Logger.Warning($"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(PowerSupplierComponent)}"); + } + UpdateSupply(); } diff --git a/Content.Server/GameObjects/Components/Projectiles/StunnableProjectileComponent.cs b/Content.Server/GameObjects/Components/Projectiles/StunnableProjectileComponent.cs index b09881d2af..95ab0b07ee 100644 --- a/Content.Server/GameObjects/Components/Projectiles/StunnableProjectileComponent.cs +++ b/Content.Server/GameObjects/Components/Projectiles/StunnableProjectileComponent.cs @@ -1,4 +1,3 @@ -using System; using Content.Server.GameObjects.Components.Mobs; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; @@ -32,10 +31,11 @@ namespace Content.Server.GameObjects.Components.Projectiles public override void Initialize() { base.Initialize(); - if (!Owner.HasComponent()) + + if (!Owner.EnsureComponent(out ProjectileComponent _)) { - Logger.Error("StunProjectile entity must have a ProjectileComponent"); - throw new InvalidOperationException(); + Logger.Warning( + $"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(ProjectileComponent)}"); } } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs index dfc2f61b8a..7ee34c2175 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs @@ -28,6 +28,7 @@ using Robust.Shared.Random; using Robust.Shared.Serialization; using Robust.Shared.Utility; using Content.Shared.GameObjects.Components.Damage; +using Robust.Shared.GameObjects; namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { @@ -157,7 +158,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels public override void OnAdd() { base.OnAdd(); - var rangedWeaponComponent = Owner.GetComponent(); + + if (!Owner.EnsureComponent(out ServerRangedWeaponComponent rangedWeaponComponent)) + { + Logger.Warning( + $"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(ServerRangedWeaponComponent)}"); + } rangedWeaponComponent.Barrel ??= this; rangedWeaponComponent.FireHandler += Fire; diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs index 39dfc9c064..bd0276ab62 100644 --- a/Content.Server/GameObjects/Components/WiresComponent.cs +++ b/Content.Server/GameObjects/Components/WiresComponent.cs @@ -37,7 +37,6 @@ namespace Content.Server.GameObjects.Components [Dependency] private readonly IServerNotifyManager _notifyManager = default!; private AudioSystem _audioSystem = default!; - private AppearanceComponent _appearance = default!; private bool _isPanelOpen; @@ -105,7 +104,10 @@ namespace Content.Server.GameObjects.Components private void UpdateAppearance() { - _appearance.SetData(WiresVisuals.MaintenancePanelState, IsPanelOpen && IsPanelVisible); + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(WiresVisuals.MaintenancePanelState, IsPanelOpen && IsPanelVisible); + } } /// @@ -148,8 +150,11 @@ namespace Content.Server.GameObjects.Components { base.Initialize(); _audioSystem = EntitySystem.Get(); - _appearance = Owner.GetComponent(); - _appearance.SetData(WiresVisuals.MaintenancePanelState, IsPanelOpen); + + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(WiresVisuals.MaintenancePanelState, IsPanelOpen); + } if (UserInterface != null) { From 8a27a5322acac3f9dc1120667c81dfa9aa1ff031 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Mon, 24 Aug 2020 14:10:28 +0200 Subject: [PATCH 50/88] Replace pragma warning 649 disable/restore with default! --- Content.Client/Chat/ChatManager.cs | 14 ++++---- Content.Client/ClientNotifyManager.cs | 14 ++++---- Content.Client/ClientPreferencesManager.cs | 4 +-- .../Command/CommunicationsConsoleMenu.cs | 4 +-- .../Construction/ConstructionMenu.cs | 8 ++--- Content.Client/EntryPoint.cs | 14 ++++---- Content.Client/EscapeMenuOwner.cs | 22 ++++++------ .../Access/IdCardConsoleBoundUserInterface.cs | 7 ++-- .../Actor/CharacterInfoComponent.cs | 8 ++--- .../Components/Actor/CharacterInterface.cs | 7 ++-- .../Cargo/GalacticMarketComponent.cs | 4 +-- .../Chemistry/ChemMaster/ChemMasterWindow.cs | 4 +-- .../ReagentDispenserWindow.cs | 4 +-- ...CommunicationsConsoleBoundUserInterface.cs | 7 ++-- .../HumanInventoryInterfaceController.cs | 14 ++++---- .../Inventory/InventoryInterfaceController.cs | 10 +++--- .../Components/InteractionOutlineComponent.cs | 6 ++-- .../Kitchen/MicrowaveBoundUserInterface.cs | 7 ++-- .../Mobs/ClientOverlayEffectsComponent.cs | 11 +++--- .../Mobs/ClientStatusEffectsComponent.cs | 10 +++--- .../Components/Mobs/CombatModeComponent.cs | 6 ++-- .../Components/Observer/GhostComponent.cs | 10 +++--- .../Components/PDA/PDABoundUserInterface.cs | 7 ++-- .../Research/LatheBoundUserInterface.cs | 6 ++-- .../Research/LatheDatabaseComponent.cs | 5 +-- .../Research/ProtolatheDatabaseComponent.cs | 5 +-- .../Components/Sound/LoopingSoundComponent.cs | 5 ++- .../EntitySystems/AI/ClientAiDebugSystem.cs | 4 +-- .../EntitySystems/CharacterInterfaceSystem.cs | 6 ++-- .../EntitySystems/ClientInventorySystem.cs | 6 ++-- .../EntitySystems/CombatModeSystem.cs | 8 ++--- .../EntitySystems/ConstructionSystem.cs | 8 ++--- .../EntitySystems/DragDropSystem.cs | 16 ++++----- .../EntitySystems/ExamineSystem.cs | 12 +++---- .../EntitySystems/IconSmoothSystem.cs | 4 +-- .../EntitySystems/MeleeWeaponSystem.cs | 4 +-- .../EntitySystems/RangedWeaponSystem.cs | 13 +++---- .../EntitySystems/StatusEffectsSystem.cs | 4 +-- .../EntitySystems/SubFloorHideSystem.cs | 8 ++--- .../GameObjects/EntitySystems/VerbSystem.cs | 18 +++++----- .../GameTicking/ClientGameTicker.cs | 6 ++-- .../Graphics/Overlays/CircleMaskOverlay.cs | 6 ++-- .../Graphics/Overlays/FlashOverlay.cs | 8 ++--- .../Graphics/Overlays/GradientCircleMask.cs | 7 ++-- Content.Client/Instruments/InstrumentMenu.cs | 6 ++-- Content.Client/Parallax/ParallaxManager.cs | 8 ++--- Content.Client/Parallax/ParallaxOverlay.cs | 10 +++--- Content.Client/Research/LatheMenu.cs | 6 ++-- .../Research/ResearchConsoleMenu.cs | 4 +-- Content.Client/Sandbox/SandboxManager.cs | 21 ++++++----- Content.Client/State/GameScreen.cs | 10 +++--- Content.Client/State/GameScreenBase.cs | 20 +++++------ Content.Client/State/LauncherConnecting.cs | 12 +++---- Content.Client/State/LobbyState.cs | 26 +++++++------- Content.Client/State/MainMenu.cs | 24 ++++++------- .../UserInterface/Cargo/CargoConsoleMenu.cs | 6 ++-- .../Cargo/CargoConsoleOrderMenu.cs | 4 +-- .../Cargo/GalacticBankSelectionMenu.cs | 6 ++-- Content.Client/UserInterface/GameHud.cs | 8 ++--- Content.Client/UserInterface/HandsGui.cs | 8 ++--- .../UserInterface/ItemSlotManager.cs | 16 ++++----- Content.Client/UserInterface/LateJoinGui.cs | 6 ++-- .../Stylesheets/StylesheetManager.cs | 6 ++-- .../VendingMachines/VendingMachineMenu.cs | 9 ++--- Content.Server/Chat/ChatManager.cs | 14 ++++---- .../Components/Body/BodyManagerComponent.cs | 2 -- .../Components/Cargo/CargoConsoleComponent.cs | 4 +-- .../Cargo/CargoOrderDatabaseComponent.cs | 4 +-- .../Components/Chemistry/PillComponent.cs | 5 ++- .../Components/Chemistry/PourableComponent.cs | 4 +-- .../Components/Chemistry/SolutionComponent.cs | 6 ++-- .../Components/Damage/BreakableComponent.cs | 6 ++-- .../Damage/DestructibleComponent.cs | 4 +-- .../Disposal/DisposalJunctionComponent.cs | 4 +-- .../Components/Fluids/PuddleComponent.cs | 4 +-- .../Components/GUI/InventoryComponent.cs | 6 ++-- .../Interactable/TilePryingComponent.cs | 8 ++--- .../Items/Clothing/ClothingComponent.cs | 4 +-- .../Components/Items/DiceComponent.cs | 6 ++-- .../Items/FloorTileItemComponent.cs | 6 ++-- .../Components/Items/RCD/RCDAmmoComponent.cs | 4 +-- .../Components/Items/RCD/RCDComponent.cs | 12 +++---- .../Items/Storage/Fill/MedkitFillComponent.cs | 6 ++-- .../Storage/Fill/StorageFillComponent.cs | 6 ++-- .../Fill/ToolboxElectricalFillComponent.cs | 6 ++-- .../Fill/ToolboxEmergencyFillComponent.cs | 6 ++-- .../Storage/Fill/ToolboxGoldFillComponent.cs | 6 ++-- .../Fill/UtilityBeltClothingFillComponent.cs | 6 ++-- .../Components/Items/Storage/ItemComponent.cs | 6 ++-- .../Components/Items/ToysComponent.cs | 6 ++-- .../Markers/ConditionalSpawnerComponent.cs | 12 +++---- .../Components/Markers/SpawnPointComponent.cs | 4 +-- .../Markers/TimedSpawnerComponent.cs | 6 ++-- .../Markers/TrashSpawnerComponent.cs | 8 ++--- .../Metabolism/MetabolismComponent.cs | 4 +-- .../Mining/AsteroidRockComponent.cs | 6 ++-- .../Components/Mobs/StunnableComponent.cs | 4 +-- .../NodeGroups/NodeGroupFactory.cs | 8 ++--- .../NodeGroups/PowerNetNodeGroup.cs | 6 ++-- .../Components/Nutrition/DrinkComponent.cs | 7 ++-- .../Nutrition/FoodContainerComponent.cs | 7 ++-- .../PowerReceiverUsers/LightBulbComponent.cs | 7 ++-- .../Components/Power/WirePlacerComponent.cs | 6 ++-- .../Components/Research/LatheComponent.cs | 2 +- .../Rotatable/RotatableComponent.cs | 5 ++- .../Sound/FootstepModifierComponent.cs | 9 ++--- .../Components/Stack/StackComponent.cs | 4 +-- .../OnUseTimerTriggerComponent.cs | 4 +-- .../Components/Utensil/UtensilComponent.cs | 6 ++-- .../Components/Weapon/Melee/FlashComponent.cs | 6 ++-- .../Weapon/Melee/MeleeWeaponComponent.cs | 10 +++--- .../Weapon/Melee/StunbatonComponent.cs | 6 ++-- .../Barrels/ServerRangedBarrelComponent.cs | 6 ++-- .../Accessible/AiReachableSystem.cs | 22 ++++++------ .../AI/Steering/AiSteeringSystem.cs | 14 ++++---- .../EntitySystems/Click/ExamineSystem.cs | 4 +-- .../EntitySystems/Click/InteractionSystem.cs | 4 +-- .../EntitySystems/ConstructionSystem.cs | 6 ++-- .../GameObjects/EntitySystems/HandsSystem.cs | 6 ++-- .../EntitySystems/NodeGroupSystem.cs | 4 +-- .../EntitySystems/PowerNetSystem.cs | 4 +-- .../EntitySystems/RoundEndSystem.cs | 6 ++-- .../StationEvents/StationEventSystem.cs | 36 +++++++++---------- .../GameObjects/EntitySystems/VerbSystem.cs | 4 +-- .../GamePresets/PresetDeathMatch.cs | 4 +-- .../GameTicking/GamePresets/PresetSandbox.cs | 4 +-- .../GamePresets/PresetSuspicion.cs | 12 +++---- .../GameTicking/GameRules/RuleDeathMatch.cs | 10 +++--- .../GameTicking/GameRules/RuleSuspicion.cs | 8 ++--- Content.Server/GameTicking/GameTicker.cs | 30 ++++++++-------- .../GameTicking/GameTickerCommands.cs | 5 ++- Content.Server/MoMMILink.cs | 10 +++--- Content.Server/Mobs/Commands.cs | 9 ++--- Content.Server/PDA/PDAUplinkManager.cs | 6 ++-- .../Preferences/ServerPreferencesManager.cs | 9 +++-- Content.Server/Sandbox/SandboxManager.cs | 16 ++++----- Content.Server/ServerNotifyManager.cs | 4 +-- Content.Shared/Chemistry/ReagentPrototype.cs | 4 +-- Content.Shared/EntryPoint.cs | 8 ++--- .../Cargo/SharedCargoConsoleComponent.cs | 7 ++-- .../Mobs/SharedStunnableComponent.cs | 4 +-- .../Movement/SharedSlipperyComponent.cs | 4 +-- .../Research/SharedLatheComponent.cs | 9 ++--- .../EntitySystems/SharedInteractionSystem.cs | 4 +-- Content.Shared/Kitchen/RecipeManager.cs | 6 ++-- Content.Shared/Physics/SlipController.cs | 4 +-- SpaceStation14.sln.DotSettings | 1 + 147 files changed, 435 insertions(+), 724 deletions(-) diff --git a/Content.Client/Chat/ChatManager.cs b/Content.Client/Chat/ChatManager.cs index 8553c6ed58..b59d039581 100644 --- a/Content.Client/Chat/ChatManager.cs +++ b/Content.Client/Chat/ChatManager.cs @@ -70,14 +70,12 @@ namespace Content.Client.Chat // Flag Enums for holding filtered channels private ChatChannel _filteredChannels; -#pragma warning disable 649 - [Dependency] private readonly IClientNetManager _netManager; - [Dependency] private readonly IClientConsole _console; - [Dependency] private readonly IEntityManager _entityManager; - [Dependency] private readonly IEyeManager _eyeManager; - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager; + [Dependency] private readonly IClientNetManager _netManager = default!; + [Dependency] private readonly IClientConsole _console = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IClientConGroupController _groupController = default!; -#pragma warning restore 649 private ChatBox _currentChatBox; private Control _speechBubbleRoot; @@ -231,7 +229,7 @@ namespace Content.Client.Chat { string locWarning = Loc.GetString("Your message exceeds {0} character limit", _maxMessageLength); _currentChatBox?.AddLine(locWarning, ChatChannel.Server, Color.Orange); - _currentChatBox.ClearOnEnter = false; // The text shouldn't be cleared if it hasn't been sent + _currentChatBox.ClearOnEnter = false; // The text shouldn't be cleared if it hasn't been sent return; } diff --git a/Content.Client/ClientNotifyManager.cs b/Content.Client/ClientNotifyManager.cs index 1a5e6d82a0..4481945b21 100644 --- a/Content.Client/ClientNotifyManager.cs +++ b/Content.Client/ClientNotifyManager.cs @@ -21,14 +21,12 @@ namespace Content.Client { public class ClientNotifyManager : SharedNotifyManager, IClientNotifyManager { -#pragma warning disable 649 - [Dependency] private IPlayerManager _playerManager; - [Dependency] private IUserInterfaceManager _userInterfaceManager; - [Dependency] private IInputManager _inputManager; - [Dependency] private IEyeManager _eyeManager; - [Dependency] private IClientNetManager _netManager; - [Dependency] private IEntityManager _entityManager; -#pragma warning restore 649 + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IClientNetManager _netManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; private readonly List _aliveLabels = new List(); private bool _initialized; diff --git a/Content.Client/ClientPreferencesManager.cs b/Content.Client/ClientPreferencesManager.cs index 5b0296dcde..e1e35704aa 100644 --- a/Content.Client/ClientPreferencesManager.cs +++ b/Content.Client/ClientPreferencesManager.cs @@ -14,9 +14,7 @@ namespace Content.Client /// public class ClientPreferencesManager : SharedPreferencesManager, IClientPreferencesManager { -#pragma warning disable 649 - [Dependency] private readonly IClientNetManager _netManager; -#pragma warning restore 649 + [Dependency] private readonly IClientNetManager _netManager = default!; public event Action OnServerDataLoaded; public GameSettings Settings { get; private set; } diff --git a/Content.Client/Command/CommunicationsConsoleMenu.cs b/Content.Client/Command/CommunicationsConsoleMenu.cs index a04bb56edb..15dfd36b56 100644 --- a/Content.Client/Command/CommunicationsConsoleMenu.cs +++ b/Content.Client/Command/CommunicationsConsoleMenu.cs @@ -12,9 +12,7 @@ namespace Content.Client.Command { public class CommunicationsConsoleMenu : SS14Window { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly ILocalizationManager _localizationManager = default!; protected override Vector2? CustomSize => new Vector2(600, 400); diff --git a/Content.Client/Construction/ConstructionMenu.cs b/Content.Client/Construction/ConstructionMenu.cs index 9b9a487243..089d2245f1 100644 --- a/Content.Client/Construction/ConstructionMenu.cs +++ b/Content.Client/Construction/ConstructionMenu.cs @@ -22,11 +22,9 @@ namespace Content.Client.Construction { public class ConstructionMenu : SS14Window { -#pragma warning disable CS0649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IResourceCache _resourceCache; - [Dependency] private readonly IEntitySystemManager _systemManager; -#pragma warning restore + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IEntitySystemManager _systemManager = default!; private readonly Button BuildButton; private readonly Button EraseButton; diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index ef26c72a49..912440baac 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -39,14 +39,12 @@ namespace Content.Client { public class EntryPoint : GameClient { -#pragma warning disable 649 - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IBaseClient _baseClient; - [Dependency] private readonly IEscapeMenuOwner _escapeMenuOwner; - [Dependency] private readonly IGameController _gameController; - [Dependency] private readonly IStateManager _stateManager; - [Dependency] private readonly IConfigurationManager _configurationManager; -#pragma warning restore 649 + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IBaseClient _baseClient = default!; + [Dependency] private readonly IEscapeMenuOwner _escapeMenuOwner = default!; + [Dependency] private readonly IGameController _gameController = default!; + [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; public override void Init() { diff --git a/Content.Client/EscapeMenuOwner.cs b/Content.Client/EscapeMenuOwner.cs index 93acf5b298..11a4ebdacb 100644 --- a/Content.Client/EscapeMenuOwner.cs +++ b/Content.Client/EscapeMenuOwner.cs @@ -17,18 +17,16 @@ namespace Content.Client { internal sealed class EscapeMenuOwner : IEscapeMenuOwner { -#pragma warning disable 649 - [Dependency] private readonly IClientConsole _clientConsole; - [Dependency] private readonly IConfigurationManager _configurationManager; - [Dependency] private readonly IInputManager _inputManager; - [Dependency] private readonly IPlacementManager _placementManager; - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IResourceCache _resourceCache; - [Dependency] private readonly IStateManager _stateManager; - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager; - [Dependency] private readonly IGameHud _gameHud; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly IClientConsole _clientConsole = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IPlacementManager _placementManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly ILocalizationManager _localizationManager = default!; private EscapeMenu _escapeMenu; diff --git a/Content.Client/GameObjects/Components/Access/IdCardConsoleBoundUserInterface.cs b/Content.Client/GameObjects/Components/Access/IdCardConsoleBoundUserInterface.cs index 9df2ab0d16..1c66237da2 100644 --- a/Content.Client/GameObjects/Components/Access/IdCardConsoleBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Access/IdCardConsoleBoundUserInterface.cs @@ -10,10 +10,9 @@ namespace Content.Client.GameObjects.Components.Access { public class IdCardConsoleBoundUserInterface : BoundUserInterface { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _localizationManager; - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly ILocalizationManager _localizationManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + public IdCardConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { } diff --git a/Content.Client/GameObjects/Components/Actor/CharacterInfoComponent.cs b/Content.Client/GameObjects/Components/Actor/CharacterInfoComponent.cs index dfb7e3c796..2ed1e9c1dd 100644 --- a/Content.Client/GameObjects/Components/Actor/CharacterInfoComponent.cs +++ b/Content.Client/GameObjects/Components/Actor/CharacterInfoComponent.cs @@ -14,12 +14,10 @@ namespace Content.Client.GameObjects.Components.Actor [RegisterComponent] public sealed class CharacterInfoComponent : Component, ICharacterUI { - private CharacterInfoControl _control; + [Dependency] private readonly ILocalizationManager _loc = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _loc; - [Dependency] private readonly IResourceCache _resourceCache; -#pragma warning restore 649 + private CharacterInfoControl _control; public override string Name => "CharacterInfo"; diff --git a/Content.Client/GameObjects/Components/Actor/CharacterInterface.cs b/Content.Client/GameObjects/Components/Actor/CharacterInterface.cs index c2b66a9b07..f776411554 100644 --- a/Content.Client/GameObjects/Components/Actor/CharacterInterface.cs +++ b/Content.Client/GameObjects/Components/Actor/CharacterInterface.cs @@ -20,12 +20,9 @@ namespace Content.Client.GameObjects.Components.Actor [RegisterComponent] public class CharacterInterface : Component { - public override string Name => "Character Interface Component"; + [Dependency] private readonly IGameHud _gameHud = default!; - [Dependency] -#pragma warning disable 649 - private readonly IGameHud _gameHud; -#pragma warning restore 649 + public override string Name => "Character Interface Component"; /// /// Window to hold each of the character interfaces diff --git a/Content.Client/GameObjects/Components/Cargo/GalacticMarketComponent.cs b/Content.Client/GameObjects/Components/Cargo/GalacticMarketComponent.cs index 1311900514..786c0c3d6e 100644 --- a/Content.Client/GameObjects/Components/Cargo/GalacticMarketComponent.cs +++ b/Content.Client/GameObjects/Components/Cargo/GalacticMarketComponent.cs @@ -10,9 +10,7 @@ namespace Content.Client.GameObjects.Components.Cargo [RegisterComponent] public class GalacticMarketComponent : SharedGalacticMarketComponent { -#pragma warning disable CS0649 - [Dependency] private IPrototypeManager _prototypeManager; -#pragma warning restore + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; /// /// Event called when the database is updated. diff --git a/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs b/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs index 4bf3482a2c..0524a87ff7 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs @@ -21,9 +21,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster /// public class ChemMasterWindow : SS14Window { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; /// Contains info about the reagent container such as it's contents, if one is loaded into the dispenser. private readonly VBoxContainer ContainerInfo; diff --git a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs index 76b168eae3..d32d10f776 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs @@ -20,9 +20,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser /// public class ReagentDispenserWindow : SS14Window { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; /// Contains info about the reagent container such as it's contents, if one is loaded into the dispenser. private readonly VBoxContainer ContainerInfo; diff --git a/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs b/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs index 20c060cc3c..e4790a1ae9 100644 --- a/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs @@ -11,12 +11,9 @@ namespace Content.Client.GameObjects.Components.Command { public class CommunicationsConsoleBoundUserInterface : BoundUserInterface { - [ViewVariables] - private CommunicationsConsoleMenu _menu; + [Dependency] private readonly IGameTiming _gameTiming = default!; -#pragma warning disable 649 - [Dependency] private IGameTiming _gameTiming; -#pragma warning restore 649 + [ViewVariables] private CommunicationsConsoleMenu _menu; public bool CountdownStarted { get; private set; } diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs b/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs index d08d3e1211..8057ec4d0d 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs @@ -18,11 +18,9 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory [UsedImplicitly] public class HumanInventoryInterfaceController : InventoryInterfaceController { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _loc; - [Dependency] private readonly IResourceCache _resourceCache; - [Dependency] private readonly IItemSlotManager _itemSlotManager; -#pragma warning restore 649 + [Dependency] private readonly ILocalizationManager _loc = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IItemSlotManager _itemSlotManager = default!; private readonly Dictionary> _inventoryButtons = new Dictionary>(); @@ -43,7 +41,7 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory base.Initialize(); _window = new HumanInventoryWindow(_loc, _resourceCache); - _window.OnClose += () => _gameHud.InventoryButtonDown = false; + _window.OnClose += () => GameHud.InventoryButtonDown = false; foreach (var (slot, button) in _window.Buttons) { button.OnPressed = (e) => AddToInventory(e, slot); @@ -153,7 +151,7 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory { base.PlayerAttached(); - _gameHud.InventoryQuickButtonContainer.AddChild(_quickButtonsContainer); + GameHud.InventoryQuickButtonContainer.AddChild(_quickButtonsContainer); // Update all the buttons to make sure they check out. @@ -175,7 +173,7 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory { base.PlayerDetached(); - _gameHud.InventoryQuickButtonContainer.RemoveChild(_quickButtonsContainer); + GameHud.InventoryQuickButtonContainer.RemoveChild(_quickButtonsContainer); foreach (var (slot, list) in _inventoryButtons) { diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs b/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs index 12cf3375c2..73c18852f5 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs @@ -12,9 +12,7 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory { public abstract class InventoryInterfaceController : IDisposable { -#pragma warning disable 649 - [Dependency] protected readonly IGameHud _gameHud; -#pragma warning restore 649 + [Dependency] protected readonly IGameHud GameHud = default!; protected InventoryInterfaceController(ClientInventoryComponent owner) { @@ -31,8 +29,8 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory public virtual void PlayerAttached() { - _gameHud.InventoryButtonVisible = true; - _gameHud.InventoryButtonToggled = b => + GameHud.InventoryButtonVisible = true; + GameHud.InventoryButtonToggled = b => { if (b) { @@ -47,7 +45,7 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory public virtual void PlayerDetached() { - _gameHud.InventoryButtonVisible = false; + GameHud.InventoryButtonVisible = false; Window.Close(); } diff --git a/Content.Client/GameObjects/Components/InteractionOutlineComponent.cs b/Content.Client/GameObjects/Components/InteractionOutlineComponent.cs index 5529642be9..b04aa0afb9 100644 --- a/Content.Client/GameObjects/Components/InteractionOutlineComponent.cs +++ b/Content.Client/GameObjects/Components/InteractionOutlineComponent.cs @@ -9,15 +9,13 @@ namespace Content.Client.GameObjects.Components [RegisterComponent] public class InteractionOutlineComponent : Component { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + private const string ShaderInRange = "SelectionOutlineInrange"; private const string ShaderOutOfRange = "SelectionOutline"; public override string Name => "InteractionOutline"; -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 - private ShaderInstance _selectionShaderInstance; private ShaderInstance _selectionShaderInRangeInstance; diff --git a/Content.Client/GameObjects/Components/Kitchen/MicrowaveBoundUserInterface.cs b/Content.Client/GameObjects/Components/Kitchen/MicrowaveBoundUserInterface.cs index 174b21e5b6..ef909d9009 100644 --- a/Content.Client/GameObjects/Components/Kitchen/MicrowaveBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Kitchen/MicrowaveBoundUserInterface.cs @@ -20,10 +20,9 @@ namespace Content.Client.GameObjects.Components.Kitchen { public class MicrowaveBoundUserInterface : BoundUserInterface { -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + private MicrowaveMenu _menu; private Dictionary _solids = new Dictionary(); diff --git a/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs index c294fa68cd..8aae18facf 100644 --- a/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs +++ b/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs @@ -24,6 +24,10 @@ namespace Content.Client.GameObjects.Components.Mobs [ComponentReference(typeof(SharedOverlayEffectsComponent))] public sealed class ClientOverlayEffectsComponent : SharedOverlayEffectsComponent//, ICharacterUI { + [Dependency] private readonly IOverlayManager _overlayManager = default!; + [Dependency] private readonly IReflectionManager _reflectionManager = default!; + [Dependency] private readonly IClientNetManager _netManager = default!; + /// /// A list of overlay containers representing the current overlays applied /// @@ -36,13 +40,6 @@ namespace Content.Client.GameObjects.Components.Mobs set => SetEffects(value); } -#pragma warning disable 649 - // Required dependencies - [Dependency] private readonly IOverlayManager _overlayManager; - [Dependency] private readonly IReflectionManager _reflectionManager; - [Dependency] private readonly IClientNetManager _netManager; -#pragma warning restore 649 - public override void Initialize() { base.Initialize(); diff --git a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs index 8c9344d5fd..fc2984f3ae 100644 --- a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs +++ b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs @@ -23,12 +23,10 @@ namespace Content.Client.GameObjects.Components.Mobs [ComponentReference(typeof(SharedStatusEffectsComponent))] public sealed class ClientStatusEffectsComponent : SharedStatusEffectsComponent { -#pragma warning disable 649 - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IResourceCache _resourceCache; - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager; - [Dependency] private readonly IGameTiming _gameTiming; -#pragma warning restore 649 + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; private StatusEffectsUI _ui; private Dictionary _status = new Dictionary(); diff --git a/Content.Client/GameObjects/Components/Mobs/CombatModeComponent.cs b/Content.Client/GameObjects/Components/Mobs/CombatModeComponent.cs index bcedfa8487..1612c10960 100644 --- a/Content.Client/GameObjects/Components/Mobs/CombatModeComponent.cs +++ b/Content.Client/GameObjects/Components/Mobs/CombatModeComponent.cs @@ -12,10 +12,8 @@ namespace Content.Client.GameObjects.Components.Mobs [ComponentReference(typeof(SharedCombatModeComponent))] public sealed class CombatModeComponent : SharedCombatModeComponent { -#pragma warning disable 649 - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IGameHud _gameHud; -#pragma warning restore 649 + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameHud _gameHud = default!; public override bool IsInCombatMode { diff --git a/Content.Client/GameObjects/Components/Observer/GhostComponent.cs b/Content.Client/GameObjects/Components/Observer/GhostComponent.cs index e0f2407a89..077eae387c 100644 --- a/Content.Client/GameObjects/Components/Observer/GhostComponent.cs +++ b/Content.Client/GameObjects/Components/Observer/GhostComponent.cs @@ -12,6 +12,10 @@ namespace Content.Client.GameObjects.Components.Observer [RegisterComponent] public class GhostComponent : SharedGhostComponent { + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IComponentManager _componentManager = default!; + private GhostGui _gui; [ViewVariables(VVAccess.ReadOnly)] @@ -19,12 +23,6 @@ namespace Content.Client.GameObjects.Components.Observer private bool _isAttached; -#pragma warning disable 649 - [Dependency] private readonly IGameHud _gameHud; - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private IComponentManager _componentManager; -#pragma warning restore 649 - public override void OnRemove() { base.OnRemove(); diff --git a/Content.Client/GameObjects/Components/PDA/PDABoundUserInterface.cs b/Content.Client/GameObjects/Components/PDA/PDABoundUserInterface.cs index 1c724bceee..e581f8a021 100644 --- a/Content.Client/GameObjects/Components/PDA/PDABoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/PDA/PDABoundUserInterface.cs @@ -19,10 +19,9 @@ namespace Content.Client.GameObjects.Components.PDA { public class PDABoundUserInterface : BoundUserInterface { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + private PDAMenu _menu; private PDAMenuPopup failPopup; diff --git a/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs b/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs index 9e318f04a6..923b798e39 100644 --- a/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs @@ -12,10 +12,8 @@ namespace Content.Client.GameObjects.Components.Research { public class LatheBoundUserInterface : BoundUserInterface { -#pragma warning disable CS0649 - [Dependency] - private IPrototypeManager _prototypeManager; -#pragma warning restore + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [ViewVariables] private LatheMenu _menu; [ViewVariables] diff --git a/Content.Client/GameObjects/Components/Research/LatheDatabaseComponent.cs b/Content.Client/GameObjects/Components/Research/LatheDatabaseComponent.cs index 962948e7e8..67163e0e18 100644 --- a/Content.Client/GameObjects/Components/Research/LatheDatabaseComponent.cs +++ b/Content.Client/GameObjects/Components/Research/LatheDatabaseComponent.cs @@ -10,10 +10,7 @@ namespace Content.Client.GameObjects.Components.Research [ComponentReference(typeof(SharedLatheDatabaseComponent))] public class LatheDatabaseComponent : SharedLatheDatabaseComponent { -#pragma warning disable CS0649 - [Dependency] - private IPrototypeManager _prototypeManager; -#pragma warning restore + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public override void HandleComponentState(ComponentState curState, ComponentState nextState) { diff --git a/Content.Client/GameObjects/Components/Research/ProtolatheDatabaseComponent.cs b/Content.Client/GameObjects/Components/Research/ProtolatheDatabaseComponent.cs index 71aeb4411f..c10c2bdc65 100644 --- a/Content.Client/GameObjects/Components/Research/ProtolatheDatabaseComponent.cs +++ b/Content.Client/GameObjects/Components/Research/ProtolatheDatabaseComponent.cs @@ -11,10 +11,7 @@ namespace Content.Client.GameObjects.Components.Research [ComponentReference(typeof(SharedLatheDatabaseComponent))] public class ProtolatheDatabaseComponent : SharedProtolatheDatabaseComponent { -#pragma warning disable CS0649 - [Dependency] - private IPrototypeManager _prototypeManager; -#pragma warning restore + [Dependency] private IPrototypeManager _prototypeManager = default!; /// /// Invoked when the database gets updated. diff --git a/Content.Client/GameObjects/Components/Sound/LoopingSoundComponent.cs b/Content.Client/GameObjects/Components/Sound/LoopingSoundComponent.cs index f521c0c3a7..c9caf04338 100644 --- a/Content.Client/GameObjects/Components/Sound/LoopingSoundComponent.cs +++ b/Content.Client/GameObjects/Components/Sound/LoopingSoundComponent.cs @@ -16,11 +16,10 @@ namespace Content.Client.GameObjects.Components.Sound [RegisterComponent] public class LoopingSoundComponent : SharedLoopingSoundComponent { + [Dependency] private readonly IRobustRandom _random = default!; + private readonly Dictionary _audioStreams = new Dictionary(); private AudioSystem _audioSystem; - #pragma warning disable 649 - [Dependency] private readonly IRobustRandom _random; - #pragma warning restore 649 public override void StopAllSounds() { diff --git a/Content.Client/GameObjects/EntitySystems/AI/ClientAiDebugSystem.cs b/Content.Client/GameObjects/EntitySystems/AI/ClientAiDebugSystem.cs index e40ae0740f..688e3116aa 100644 --- a/Content.Client/GameObjects/EntitySystems/AI/ClientAiDebugSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/AI/ClientAiDebugSystem.cs @@ -15,9 +15,7 @@ namespace Content.Client.GameObjects.EntitySystems.AI #if DEBUG public class ClientAiDebugSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private IEyeManager _eyeManager; -#pragma warning restore 649 + [Dependency] private readonly IEyeManager _eyeManager = default!; private AiDebugMode _tooltips = AiDebugMode.None; private readonly Dictionary _aiBoxes = new Dictionary(); diff --git a/Content.Client/GameObjects/EntitySystems/CharacterInterfaceSystem.cs b/Content.Client/GameObjects/EntitySystems/CharacterInterfaceSystem.cs index a46f038e67..bdafc4d1a5 100644 --- a/Content.Client/GameObjects/EntitySystems/CharacterInterfaceSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/CharacterInterfaceSystem.cs @@ -11,10 +11,8 @@ namespace Content.Client.GameObjects.EntitySystems { public sealed class CharacterInterfaceSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly IGameHud _gameHud; - [Dependency] private readonly IPlayerManager _playerManager; -#pragma warning restore 649 + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; public override void Initialize() { diff --git a/Content.Client/GameObjects/EntitySystems/ClientInventorySystem.cs b/Content.Client/GameObjects/EntitySystems/ClientInventorySystem.cs index 80cd1fbb0f..b6ac6bbe2f 100644 --- a/Content.Client/GameObjects/EntitySystems/ClientInventorySystem.cs +++ b/Content.Client/GameObjects/EntitySystems/ClientInventorySystem.cs @@ -11,10 +11,8 @@ namespace Content.Client.GameObjects.EntitySystems { public sealed class ClientInventorySystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly IGameHud _gameHud; - [Dependency] private readonly IPlayerManager _playerManager; -#pragma warning restore 649 + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; public override void Initialize() { diff --git a/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs b/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs index 9daa25095b..c68f1d8134 100644 --- a/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs @@ -17,11 +17,9 @@ namespace Content.Client.GameObjects.EntitySystems [UsedImplicitly] public sealed class CombatModeSystem : SharedCombatModeSystem { -#pragma warning disable 649 - [Dependency] private readonly IGameHud _gameHud; - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IGameTiming _gameTiming; -#pragma warning restore 649 + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; public override void Initialize() { diff --git a/Content.Client/GameObjects/EntitySystems/ConstructionSystem.cs b/Content.Client/GameObjects/EntitySystems/ConstructionSystem.cs index 09a4beea42..f079208d85 100644 --- a/Content.Client/GameObjects/EntitySystems/ConstructionSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/ConstructionSystem.cs @@ -24,11 +24,9 @@ namespace Content.Client.GameObjects.EntitySystems [UsedImplicitly] public class ConstructionSystem : SharedConstructionSystem { -#pragma warning disable 649 - [Dependency] private readonly IGameHud _gameHud; - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; private int _nextId; private readonly Dictionary _ghosts = new Dictionary(); diff --git a/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs b/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs index 66a5f1592c..6458c6edbc 100644 --- a/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs @@ -30,6 +30,13 @@ namespace Content.Client.GameObjects.EntitySystems [UsedImplicitly] public class DragDropSystem : EntitySystem { + [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + // drag will be triggered when mouse leaves this deadzone around the click position. private const float DragDeadzone = 2f; // how often to recheck possible targets (prevents calling expensive @@ -44,15 +51,6 @@ namespace Content.Client.GameObjects.EntitySystems private const string ShaderDropTargetInRange = "SelectionOutlineInrange"; private const string ShaderDropTargetOutOfRange = "SelectionOutline"; -#pragma warning disable 649 - [Dependency] private readonly IStateManager _stateManager; - [Dependency] private readonly IEntityManager _entityManager; - [Dependency] private readonly IInputManager _inputManager; - [Dependency] private readonly IEyeManager _eyeManager; - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IMapManager _mapManager; -#pragma warning restore 649 - // entity performing the drag action private IEntity _dragger; private IEntity _draggedEntity; diff --git a/Content.Client/GameObjects/EntitySystems/ExamineSystem.cs b/Content.Client/GameObjects/EntitySystems/ExamineSystem.cs index 86f0f9e52d..a9e4fb4187 100644 --- a/Content.Client/GameObjects/EntitySystems/ExamineSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/ExamineSystem.cs @@ -25,14 +25,12 @@ namespace Content.Client.GameObjects.EntitySystems [UsedImplicitly] internal sealed class ExamineSystem : ExamineSystemShared { - public const string StyleClassEntityTooltip = "entity-tooltip"; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; -#pragma warning disable 649 - [Dependency] private IInputManager _inputManager; - [Dependency] private IUserInterfaceManager _userInterfaceManager; - [Dependency] private IEntityManager _entityManager; - [Dependency] private IPlayerManager _playerManager; -#pragma warning restore 649 + public const string StyleClassEntityTooltip = "entity-tooltip"; private Popup _examineTooltipOpen; private CancellationTokenSource _requestCancelTokenSource; diff --git a/Content.Client/GameObjects/EntitySystems/IconSmoothSystem.cs b/Content.Client/GameObjects/EntitySystems/IconSmoothSystem.cs index 901178e9ec..330abf38d8 100644 --- a/Content.Client/GameObjects/EntitySystems/IconSmoothSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/IconSmoothSystem.cs @@ -19,9 +19,7 @@ namespace Content.Client.GameObjects.EntitySystems [UsedImplicitly] internal sealed class IconSmoothSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly IMapManager _mapManager; -#pragma warning restore 649 + [Dependency] private readonly IMapManager _mapManager = default!; private readonly Queue _dirtyEntities = new Queue(); diff --git a/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs b/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs index ca7a9d79e4..1a4b1212ee 100644 --- a/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs @@ -17,9 +17,7 @@ namespace Content.Client.GameObjects.EntitySystems [UsedImplicitly] public sealed class MeleeWeaponSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public override void Initialize() { diff --git a/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs b/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs index 027f6c6014..2fb57315eb 100644 --- a/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs @@ -17,14 +17,11 @@ namespace Content.Client.GameObjects.EntitySystems { public class RangedWeaponSystem : EntitySystem { - -#pragma warning disable 649 - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IEyeManager _eyeManager; - [Dependency] private readonly IMapManager _mapManager; - [Dependency] private readonly IInputManager _inputManager; - [Dependency] private readonly IGameTiming _gameTiming; -#pragma warning restore 649 + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; private InputSystem _inputSystem; private CombatModeSystem _combatModeSystem; diff --git a/Content.Client/GameObjects/EntitySystems/StatusEffectsSystem.cs b/Content.Client/GameObjects/EntitySystems/StatusEffectsSystem.cs index 367c49f0b9..f7a2388368 100644 --- a/Content.Client/GameObjects/EntitySystems/StatusEffectsSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/StatusEffectsSystem.cs @@ -7,9 +7,7 @@ namespace Content.Client.GameObjects.EntitySystems { public class StatusEffectsSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private IGameTiming _gameTiming; -#pragma warning restore 649 + [Dependency] private readonly IGameTiming _gameTiming = default!; public override void FrameUpdate(float frameTime) { diff --git a/Content.Client/GameObjects/EntitySystems/SubFloorHideSystem.cs b/Content.Client/GameObjects/EntitySystems/SubFloorHideSystem.cs index 1cc538fd68..95e0cfe963 100644 --- a/Content.Client/GameObjects/EntitySystems/SubFloorHideSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/SubFloorHideSystem.cs @@ -15,12 +15,10 @@ namespace Content.Client.GameObjects.EntitySystems /// internal sealed class SubFloorHideSystem : EntitySystem { - private bool _enableAll; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; -#pragma warning disable 649 - [Dependency] private readonly IMapManager _mapManager; - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager; -#pragma warning restore 649 + private bool _enableAll; [ViewVariables(VVAccess.ReadWrite)] public bool EnableAll diff --git a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs index 486d491d1c..fb9c2c441d 100644 --- a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs @@ -42,16 +42,14 @@ namespace Content.Client.GameObjects.EntitySystems [UsedImplicitly] public sealed class VerbSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly IStateManager _stateManager; - [Dependency] private readonly IEntityManager _entityManager; - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IInputManager _inputManager; - [Dependency] private readonly IItemSlotManager _itemSlotManager; - [Dependency] private readonly IGameTiming _gameTiming; - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager; - [Dependency] private readonly IMapManager _mapManager; -#pragma warning restore 649 + [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IItemSlotManager _itemSlotManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; private EntityList _currentEntityList; private VerbPopup _currentVerbListRoot; diff --git a/Content.Client/GameTicking/ClientGameTicker.cs b/Content.Client/GameTicking/ClientGameTicker.cs index 3eeda60421..be3e791a2a 100644 --- a/Content.Client/GameTicking/ClientGameTicker.cs +++ b/Content.Client/GameTicking/ClientGameTicker.cs @@ -17,10 +17,8 @@ namespace Content.Client.GameTicking { public class ClientGameTicker : SharedGameTicker, IClientGameTicker { -#pragma warning disable 649 - [Dependency] private IClientNetManager _netManager; - [Dependency] private IStateManager _stateManager; -#pragma warning restore 649 + [Dependency] private readonly IClientNetManager _netManager = default!; + [Dependency] private readonly IStateManager _stateManager = default!; [ViewVariables] private bool _initialized; diff --git a/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs b/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs index f16cbf52e4..9e300ce897 100644 --- a/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs +++ b/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs @@ -11,10 +11,8 @@ namespace Content.Client.Graphics.Overlays { public class CircleMaskOverlay : Overlay { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IEyeManager _eyeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; public override OverlaySpace Space => OverlaySpace.WorldSpace; private readonly ShaderInstance _shader; diff --git a/Content.Client/Graphics/Overlays/FlashOverlay.cs b/Content.Client/Graphics/Overlays/FlashOverlay.cs index cf56eb796e..d8c37ad376 100644 --- a/Content.Client/Graphics/Overlays/FlashOverlay.cs +++ b/Content.Client/Graphics/Overlays/FlashOverlay.cs @@ -16,11 +16,9 @@ namespace Content.Client.Graphics.Overlays { public class FlashOverlay : Overlay, IConfigurable { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IClyde _displayManager; - [Dependency] private readonly IGameTiming _gameTiming; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IClyde _displayManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; public override OverlaySpace Space => OverlaySpace.ScreenSpace; private readonly ShaderInstance _shader; diff --git a/Content.Client/Graphics/Overlays/GradientCircleMask.cs b/Content.Client/Graphics/Overlays/GradientCircleMask.cs index 28dce7b66b..5dec976b53 100644 --- a/Content.Client/Graphics/Overlays/GradientCircleMask.cs +++ b/Content.Client/Graphics/Overlays/GradientCircleMask.cs @@ -11,10 +11,9 @@ namespace Content.Client.Graphics.Overlays { public class GradientCircleMaskOverlay : Overlay { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IEyeManager _eyeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + public override OverlaySpace Space => OverlaySpace.WorldSpace; private readonly ShaderInstance _shader; diff --git a/Content.Client/Instruments/InstrumentMenu.cs b/Content.Client/Instruments/InstrumentMenu.cs index 3acacb3f34..166ecfaf04 100644 --- a/Content.Client/Instruments/InstrumentMenu.cs +++ b/Content.Client/Instruments/InstrumentMenu.cs @@ -20,10 +20,8 @@ namespace Content.Client.Instruments { public class InstrumentMenu : SS14Window { -#pragma warning disable 649 - [Dependency] private IMidiManager _midiManager; - [Dependency] private IFileDialogManager _fileDialogManager; -#pragma warning restore 649 + [Dependency] private readonly IMidiManager _midiManager = default!; + [Dependency] private readonly IFileDialogManager _fileDialogManager = default!; private InstrumentBoundUserInterface _owner; private Button midiLoopButton; diff --git a/Content.Client/Parallax/ParallaxManager.cs b/Content.Client/Parallax/ParallaxManager.cs index d289ef16cc..441dbcaf08 100644 --- a/Content.Client/Parallax/ParallaxManager.cs +++ b/Content.Client/Parallax/ParallaxManager.cs @@ -19,11 +19,9 @@ namespace Content.Client.Parallax { internal sealed class ParallaxManager : IParallaxManager, IPostInjectInit { -#pragma warning disable 649 - [Dependency] private readonly IResourceCache _resourceCache; - [Dependency] private readonly ILogManager _logManager; - [Dependency] private readonly IConfigurationManager _configurationManager; -#pragma warning restore 649 + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; private static readonly ResourcePath ParallaxConfigPath = new ResourcePath("/parallax_config.toml"); diff --git a/Content.Client/Parallax/ParallaxOverlay.cs b/Content.Client/Parallax/ParallaxOverlay.cs index a5dd984b72..c4bc5e8ebf 100644 --- a/Content.Client/Parallax/ParallaxOverlay.cs +++ b/Content.Client/Parallax/ParallaxOverlay.cs @@ -13,12 +13,10 @@ namespace Content.Client.Parallax { public class ParallaxOverlay : Overlay { -#pragma warning disable 649 - [Dependency] private readonly IParallaxManager _parallaxManager; - [Dependency] private readonly IEyeManager _eyeManager; - [Dependency] private readonly IClyde _displayManager; - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IParallaxManager _parallaxManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IClyde _displayManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public override bool AlwaysDirty => true; private const float Slowness = 0.5f; diff --git a/Content.Client/Research/LatheMenu.cs b/Content.Client/Research/LatheMenu.cs index 002f8dd031..c275be681b 100644 --- a/Content.Client/Research/LatheMenu.cs +++ b/Content.Client/Research/LatheMenu.cs @@ -14,9 +14,7 @@ namespace Content.Client.Research { public class LatheMenu : SS14Window { -#pragma warning disable CS0649 - [Dependency] private IPrototypeManager PrototypeManager; -#pragma warning restore + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; private ItemList Items; private ItemList Materials; @@ -174,7 +172,7 @@ namespace Content.Client.Research foreach (var (id, amount) in Owner.Storage) { - if (!PrototypeManager.TryIndex(id, out MaterialPrototype materialPrototype)) continue; + if (!_prototypeManager.TryIndex(id, out MaterialPrototype materialPrototype)) continue; var material = materialPrototype.Material; Materials.AddItem($"{material.Name} {amount} cm³", material.Icon.Frame0(), false); } diff --git a/Content.Client/Research/ResearchConsoleMenu.cs b/Content.Client/Research/ResearchConsoleMenu.cs index 5ece8da8f7..ad86933ede 100644 --- a/Content.Client/Research/ResearchConsoleMenu.cs +++ b/Content.Client/Research/ResearchConsoleMenu.cs @@ -32,9 +32,7 @@ namespace Content.Client.Research private ItemList _unlockableTechnologies; private ItemList _futureTechnologies; -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly ILocalizationManager _localizationManager = default!; public Button UnlockButton { get; private set; } public Button ServerSelectionButton { get; private set; } diff --git a/Content.Client/Sandbox/SandboxManager.cs b/Content.Client/Sandbox/SandboxManager.cs index fddcf313cf..4696c6005e 100644 --- a/Content.Client/Sandbox/SandboxManager.cs +++ b/Content.Client/Sandbox/SandboxManager.cs @@ -74,19 +74,18 @@ namespace Content.Client.Sandbox vBox.AddChild(ShowBbButton); } } + internal class SandboxManager : SharedSandboxManager, ISandboxManager { -#pragma warning disable 649 - [Dependency] private readonly IClientConsole _console; - [Dependency] private readonly IGameHud _gameHud; - [Dependency] private readonly IClientNetManager _netManager; - [Dependency] private readonly ILocalizationManager _localization; - [Dependency] private readonly IPlacementManager _placementManager; - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IResourceCache _resourceCache; - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager; - [Dependency] private readonly IInputManager _inputManager; -#pragma warning restore 649 + [Dependency] private readonly IClientConsole _console = default!; + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly IClientNetManager _netManager = default!; + [Dependency] private readonly ILocalizationManager _localization = default!; + [Dependency] private readonly IPlacementManager _placementManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; public bool SandboxAllowed { get; private set; } diff --git a/Content.Client/State/GameScreen.cs b/Content.Client/State/GameScreen.cs index 98425026b5..7a00879c55 100644 --- a/Content.Client/State/GameScreen.cs +++ b/Content.Client/State/GameScreen.cs @@ -14,12 +14,10 @@ namespace Content.Client.State { public class GameScreen : GameScreenBase { -#pragma warning disable 649 - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager; - [Dependency] private readonly IGameHud _gameHud; - [Dependency] private readonly IInputManager _inputManager; - [Dependency] private readonly IChatManager _chatManager; -#pragma warning restore 649 + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; [ViewVariables] private ChatBox _gameChat; diff --git a/Content.Client/State/GameScreenBase.cs b/Content.Client/State/GameScreenBase.cs index 64e62d2b16..8b88d26ebc 100644 --- a/Content.Client/State/GameScreenBase.cs +++ b/Content.Client/State/GameScreenBase.cs @@ -28,17 +28,15 @@ namespace Content.Client.State // Instantiated dynamically through the StateManager, Dependencies will be resolved. public partial class GameScreenBase : Robust.Client.State.State { -#pragma warning disable 649 - [Dependency] protected readonly IClientEntityManager EntityManager; - [Dependency] protected readonly IInputManager InputManager; - [Dependency] protected readonly IPlayerManager PlayerManager; - [Dependency] protected readonly IEyeManager EyeManager; - [Dependency] protected readonly IEntitySystemManager EntitySystemManager; - [Dependency] protected readonly IGameTiming Timing; - [Dependency] protected readonly IMapManager MapManager; - [Dependency] protected readonly IUserInterfaceManager UserInterfaceManager; - [Dependency] protected readonly IConfigurationManager ConfigurationManager; -#pragma warning restore 649 + [Dependency] protected readonly IClientEntityManager EntityManager = default!; + [Dependency] protected readonly IInputManager InputManager = default!; + [Dependency] protected readonly IPlayerManager PlayerManager = default!; + [Dependency] protected readonly IEyeManager EyeManager = default!; + [Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!; + [Dependency] protected readonly IGameTiming Timing = default!; + [Dependency] protected readonly IMapManager MapManager = default!; + [Dependency] protected readonly IUserInterfaceManager UserInterfaceManager = default!; + [Dependency] protected readonly IConfigurationManager ConfigurationManager = default!; private IEntity _lastHoveredEntity; diff --git a/Content.Client/State/LauncherConnecting.cs b/Content.Client/State/LauncherConnecting.cs index adfcc5a505..ff1a39ab7a 100644 --- a/Content.Client/State/LauncherConnecting.cs +++ b/Content.Client/State/LauncherConnecting.cs @@ -16,13 +16,11 @@ namespace Content.Client.State { public class LauncherConnecting : Robust.Client.State.State { -#pragma warning disable 649 - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager; - [Dependency] private readonly IStylesheetManager _stylesheetManager; - [Dependency] private readonly IClientNetManager _clientNetManager; - [Dependency] private readonly IGameController _gameController; - [Dependency] private readonly IBaseClient _baseClient; -#pragma warning restore 649 + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IStylesheetManager _stylesheetManager = default!; + [Dependency] private readonly IClientNetManager _clientNetManager = default!; + [Dependency] private readonly IGameController _gameController = default!; + [Dependency] private readonly IBaseClient _baseClient = default!; private Control _control; private Label _connectStatus; diff --git a/Content.Client/State/LobbyState.cs b/Content.Client/State/LobbyState.cs index 6f04765caa..3059eb9529 100644 --- a/Content.Client/State/LobbyState.cs +++ b/Content.Client/State/LobbyState.cs @@ -24,19 +24,17 @@ namespace Content.Client.State { public class LobbyState : Robust.Client.State.State { -#pragma warning disable 649 - [Dependency] private readonly IBaseClient _baseClient; - [Dependency] private readonly IClientConsole _console; - [Dependency] private readonly IChatManager _chatManager; - [Dependency] private readonly IInputManager _inputManager; - [Dependency] private readonly IEntityManager _entityManager; - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IResourceCache _resourceCache; - [Dependency] private readonly IClientGameTicker _clientGameTicker; - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager; - [Dependency] private readonly IClientPreferencesManager _preferencesManager; -#pragma warning restore 649 + [Dependency] private readonly IBaseClient _baseClient = default!; + [Dependency] private readonly IClientConsole _console = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IClientGameTicker _clientGameTicker = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; [ViewVariables] private CharacterSetupGui _characterSetup; [ViewVariables] private LobbyGui _lobby; @@ -215,7 +213,7 @@ namespace Content.Client.State foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name)) { - + var readyState = ""; // Don't show ready state if we're ingame diff --git a/Content.Client/State/MainMenu.cs b/Content.Client/State/MainMenu.cs index d8e12e1b66..06c7e46a3a 100644 --- a/Content.Client/State/MainMenu.cs +++ b/Content.Client/State/MainMenu.cs @@ -27,15 +27,13 @@ namespace Content.Client.State { private const string PublicServerAddress = "server.spacestation14.io"; -#pragma warning disable 649 - [Dependency] private readonly IBaseClient _client; - [Dependency] private readonly IClientNetManager _netManager; - [Dependency] private readonly IConfigurationManager _configurationManager; - [Dependency] private readonly IGameController _controllerProxy; - [Dependency] private readonly ILocalizationManager _loc; - [Dependency] private readonly IResourceCache _resourceCache; - [Dependency] private readonly IUserInterfaceManager userInterfaceManager; -#pragma warning restore 649 + [Dependency] private readonly IBaseClient _client = default!; + [Dependency] private readonly IClientNetManager _netManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IGameController _controllerProxy = default!; + [Dependency] private readonly ILocalizationManager _loc = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; private MainMenuControl _mainMenuControl; private OptionsMenu OptionsMenu; @@ -48,7 +46,7 @@ namespace Content.Client.State public override void Startup() { _mainMenuControl = new MainMenuControl(_resourceCache, _configurationManager); - userInterfaceManager.StateRoot.AddChild(_mainMenuControl); + _userInterfaceManager.StateRoot.AddChild(_mainMenuControl); _mainMenuControl.QuitButton.OnPressed += QuitButtonPressed; _mainMenuControl.OptionsButton.OnPressed += OptionsButtonPressed; @@ -108,7 +106,7 @@ namespace Content.Client.State if (!UsernameHelpers.IsNameValid(inputName, out var reason)) { var invalidReason = _loc.GetString(reason.ToText()); - userInterfaceManager.Popup( + _userInterfaceManager.Popup( _loc.GetString("Invalid username:\n{0}", invalidReason), _loc.GetString("Invalid Username")); return; @@ -130,7 +128,7 @@ namespace Content.Client.State } catch (ArgumentException e) { - userInterfaceManager.Popup($"Unable to connect: {e.Message}", "Connection error."); + _userInterfaceManager.Popup($"Unable to connect: {e.Message}", "Connection error."); Logger.Warning(e.ToString()); _netManager.ConnectFailed -= _onConnectFailed; } @@ -185,7 +183,7 @@ namespace Content.Client.State private void _onConnectFailed(object _, NetConnectFailArgs args) { - userInterfaceManager.Popup($"Failed to connect:\n{args.Reason}"); + _userInterfaceManager.Popup($"Failed to connect:\n{args.Reason}"); _netManager.ConnectFailed -= _onConnectFailed; _setConnectingState(false); } diff --git a/Content.Client/UserInterface/Cargo/CargoConsoleMenu.cs b/Content.Client/UserInterface/Cargo/CargoConsoleMenu.cs index f5f05f50c0..2298e3807c 100644 --- a/Content.Client/UserInterface/Cargo/CargoConsoleMenu.cs +++ b/Content.Client/UserInterface/Cargo/CargoConsoleMenu.cs @@ -15,9 +15,7 @@ namespace Content.Client.UserInterface.Cargo { public class CargoConsoleMenu : SS14Window { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _loc; -#pragma warning restore 649 + [Dependency] private readonly ILocalizationManager _loc = default!; protected override Vector2? CustomSize => (400, 600); @@ -287,7 +285,7 @@ namespace Content.Client.UserInterface.Cargo public void PopulateOrders() { _orders.RemoveAllChildren(); - _requests.RemoveAllChildren(); + _requests.RemoveAllChildren(); foreach (var order in Owner.Orders.Orders) { var row = new CargoOrderRow(); diff --git a/Content.Client/UserInterface/Cargo/CargoConsoleOrderMenu.cs b/Content.Client/UserInterface/Cargo/CargoConsoleOrderMenu.cs index 5c2b4460f0..c093dccca4 100644 --- a/Content.Client/UserInterface/Cargo/CargoConsoleOrderMenu.cs +++ b/Content.Client/UserInterface/Cargo/CargoConsoleOrderMenu.cs @@ -8,9 +8,7 @@ namespace Content.Client.UserInterface.Cargo { class CargoConsoleOrderMenu : SS14Window { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _loc; -#pragma warning restore 649 + [Dependency] private readonly ILocalizationManager _loc = default!; public LineEdit Requester { get; set; } public LineEdit Reason { get; set; } diff --git a/Content.Client/UserInterface/Cargo/GalacticBankSelectionMenu.cs b/Content.Client/UserInterface/Cargo/GalacticBankSelectionMenu.cs index 25c90ee7f8..b096a2f888 100644 --- a/Content.Client/UserInterface/Cargo/GalacticBankSelectionMenu.cs +++ b/Content.Client/UserInterface/Cargo/GalacticBankSelectionMenu.cs @@ -10,16 +10,14 @@ namespace Content.Client.UserInterface.Cargo { public class GalacticBankSelectionMenu : SS14Window { + [Dependency] private readonly ILocalizationManager _loc = default!; + private ItemList _accounts; private int _accountCount = 0; private string[] _accountNames = new string[] { }; private int[] _accountIds = new int[] { }; private int _selectedAccountId = -1; -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _loc; -#pragma warning restore 649 - protected override Vector2? CustomSize => (300, 300); public CargoConsoleBoundUserInterface Owner; diff --git a/Content.Client/UserInterface/GameHud.cs b/Content.Client/UserInterface/GameHud.cs index fa2c6cf000..ced6e8a15b 100644 --- a/Content.Client/UserInterface/GameHud.cs +++ b/Content.Client/UserInterface/GameHud.cs @@ -74,11 +74,9 @@ namespace Content.Client.UserInterface private Button _combatModeButton; private VBoxContainer _combatPanelContainer; -#pragma warning disable 649 - [Dependency] private readonly IResourceCache _resourceCache; - [Dependency] private readonly ILocalizationManager _loc; - [Dependency] private readonly IInputManager _inputManager; -#pragma warning restore 649 + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly ILocalizationManager _loc = default!; + [Dependency] private readonly IInputManager _inputManager = default!; public Control HandsContainer { get; private set; } public Control InventoryQuickButtonContainer { get; private set; } diff --git a/Content.Client/UserInterface/HandsGui.cs b/Content.Client/UserInterface/HandsGui.cs index 28301e9e9d..3297fb467b 100644 --- a/Content.Client/UserInterface/HandsGui.cs +++ b/Content.Client/UserInterface/HandsGui.cs @@ -17,11 +17,9 @@ namespace Content.Client.UserInterface { public class HandsGui : Control { -#pragma warning disable 0649 - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IResourceCache _resourceCache; - [Dependency] private readonly IItemSlotManager _itemSlotManager; -#pragma warning restore 0649 + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IItemSlotManager _itemSlotManager = default!; private readonly TextureRect _activeHandRect; diff --git a/Content.Client/UserInterface/ItemSlotManager.cs b/Content.Client/UserInterface/ItemSlotManager.cs index 2ac6588f73..e3fcbb3e56 100644 --- a/Content.Client/UserInterface/ItemSlotManager.cs +++ b/Content.Client/UserInterface/ItemSlotManager.cs @@ -21,15 +21,13 @@ namespace Content.Client.UserInterface { public class ItemSlotManager : IItemSlotManager { -#pragma warning disable 0649 - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IGameTiming _gameTiming; - [Dependency] private readonly IInputManager _inputManager; - [Dependency] private readonly IEntitySystemManager _entitySystemManager; - [Dependency] private readonly IEntityManager _entityManager; - [Dependency] private readonly IEyeManager _eyeManager; - [Dependency] private readonly IMapManager _mapManager; -#pragma warning restore 0649 + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; public bool SetItemSlot(ItemSlotButton button, IEntity entity) { diff --git a/Content.Client/UserInterface/LateJoinGui.cs b/Content.Client/UserInterface/LateJoinGui.cs index 30f662ae80..dfeb49eb81 100644 --- a/Content.Client/UserInterface/LateJoinGui.cs +++ b/Content.Client/UserInterface/LateJoinGui.cs @@ -16,10 +16,8 @@ namespace Content.Client.UserInterface { public sealed class LateJoinGui : SS14Window { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IClientConsole _console; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IClientConsole _console = default!; protected override Vector2? CustomSize => (360, 560); diff --git a/Content.Client/UserInterface/Stylesheets/StylesheetManager.cs b/Content.Client/UserInterface/Stylesheets/StylesheetManager.cs index 49147d841e..c9b8a07047 100644 --- a/Content.Client/UserInterface/Stylesheets/StylesheetManager.cs +++ b/Content.Client/UserInterface/Stylesheets/StylesheetManager.cs @@ -7,10 +7,8 @@ namespace Content.Client.UserInterface.Stylesheets { public sealed class StylesheetManager : IStylesheetManager { -#pragma warning disable 649 - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager; - [Dependency] private readonly IResourceCache _resourceCache; -#pragma warning restore 649 + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; public Stylesheet SheetNano { get; private set; } public Stylesheet SheetSpace { get; private set; } diff --git a/Content.Client/VendingMachines/VendingMachineMenu.cs b/Content.Client/VendingMachines/VendingMachineMenu.cs index 4d279144d1..64052c39be 100644 --- a/Content.Client/VendingMachines/VendingMachineMenu.cs +++ b/Content.Client/VendingMachines/VendingMachineMenu.cs @@ -15,17 +15,14 @@ namespace Content.Client.VendingMachines { class VendingMachineMenu : SS14Window { + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + protected override Vector2? CustomSize => (300, 450); private readonly ItemList _items; private List _cachedInventory; - #pragma warning disable CS0649 - [Dependency] - private IResourceCache _resourceCache; - [Dependency] - private readonly IPrototypeManager _prototypeManager; - #pragma warning restore public VendingMachineBoundUserInterface Owner { get; set; } public VendingMachineMenu() diff --git a/Content.Server/Chat/ChatManager.cs b/Content.Server/Chat/ChatManager.cs index a6e0296b04..b2e13053d8 100644 --- a/Content.Server/Chat/ChatManager.cs +++ b/Content.Server/Chat/ChatManager.cs @@ -33,14 +33,12 @@ namespace Content.Server.Chat /// private const string MaxLengthExceededMessage = "Your message exceeded {0} character limit"; -#pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystemManager; - [Dependency] private readonly IServerNetManager _netManager; - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly ILocalizationManager _localizationManager; - [Dependency] private readonly IMoMMILink _mommiLink; - [Dependency] private readonly IConGroupController _conGroupController; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + [Dependency] private readonly IServerNetManager _netManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly ILocalizationManager _localizationManager = default!; + [Dependency] private readonly IMoMMILink _mommiLink = default!; + [Dependency] private readonly IConGroupController _conGroupController = default!; public void Initialize() { diff --git a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs index aad68a5e9b..3c65da2fd1 100644 --- a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs +++ b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs @@ -41,11 +41,9 @@ namespace Content.Server.GameObjects.Components.Body [ComponentReference(typeof(IBodyManagerComponent))] public class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput { -#pragma warning disable CS0649 [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IBodyNetworkFactory _bodyNetworkFactory = default!; [Dependency] private readonly IReflectionManager _reflectionManager = default!; -#pragma warning restore [ViewVariables] private string _presetName = default!; diff --git a/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs b/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs index 658fcec180..e0149dc1c0 100644 --- a/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs @@ -147,7 +147,7 @@ namespace Content.Server.GameObjects.Components.Cargo break; } - _prototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product); + PrototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product); if (product == null!) break; var capacity = _cargoOrderDataManager.GetCapacity(orders.Database.Id); @@ -168,7 +168,7 @@ namespace Content.Server.GameObjects.Components.Cargo // TEMPORARY loop for spawning stuff on top of console foreach (var order in approvedOrders) { - if (!_prototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product)) + if (!PrototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product)) continue; for (var i = 0; i < order.Amount; i++) { diff --git a/Content.Server/GameObjects/Components/Cargo/CargoOrderDatabaseComponent.cs b/Content.Server/GameObjects/Components/Cargo/CargoOrderDatabaseComponent.cs index db32f1a3d3..09fff675c2 100644 --- a/Content.Server/GameObjects/Components/Cargo/CargoOrderDatabaseComponent.cs +++ b/Content.Server/GameObjects/Components/Cargo/CargoOrderDatabaseComponent.cs @@ -8,9 +8,7 @@ namespace Content.Server.GameObjects.Components.Cargo [RegisterComponent] public class CargoOrderDatabaseComponent : SharedCargoOrderDatabaseComponent { -#pragma warning disable 649 - [Dependency] private readonly ICargoOrderDataManager _cargoOrderDataManager; -#pragma warning restore 649 + [Dependency] private readonly ICargoOrderDataManager _cargoOrderDataManager = default!; public CargoOrderDatabase Database { get; set; } public bool ConnectedToDatabase => Database != null; diff --git a/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs b/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs index 7ff05c4398..0704436578 100644 --- a/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs @@ -20,9 +20,8 @@ namespace Content.Server.GameObjects.Components.Chemistry [ComponentReference(typeof(IAfterInteract))] public class PillComponent : FoodComponent, IUse, IAfterInteract { -#pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystem; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystem = default!; + public override string Name => "Pill"; [ViewVariables] diff --git a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs b/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs index 4f6350f0ab..1a507f2bcb 100644 --- a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs @@ -19,9 +19,7 @@ namespace Content.Server.GameObjects.Components.Chemistry [RegisterComponent] class PourableComponent : Component, IInteractUsing { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; public override string Name => "Pourable"; diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs index 248d18143a..241d103dc9 100644 --- a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs @@ -29,10 +29,8 @@ namespace Content.Server.GameObjects.Components.Chemistry [RegisterComponent] public class SolutionComponent : SharedSolutionComponent, IExamine { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IEntitySystemManager _entitySystemManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; private IEnumerable _reactions; private AudioSystem _audioSystem; diff --git a/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs b/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs index 5ea60a7fab..d1425cce58 100644 --- a/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs @@ -21,10 +21,8 @@ namespace Content.Server.GameObjects.Components.Damage [ComponentReference(typeof(IDamageableComponent))] public class BreakableComponent : RuinableComponent, IExAct { -#pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystemManager; - [Dependency] private readonly IRobustRandom _random; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; public override string Name => "Breakable"; diff --git a/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs b/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs index 1d62b1670e..7ba91324a5 100644 --- a/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs @@ -16,9 +16,7 @@ namespace Content.Server.GameObjects.Components.Damage [ComponentReference(typeof(IDamageableComponent))] public class DestructibleComponent : RuinableComponent, IDestroyAct { -#pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystemManager; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; protected ActSystem ActSystem; diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalJunctionComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalJunctionComponent.cs index f4ad5530fa..0fa5940c45 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalJunctionComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalJunctionComponent.cs @@ -14,9 +14,7 @@ namespace Content.Server.GameObjects.Components.Disposal [ComponentReference(typeof(IDisposalTubeComponent))] public class DisposalJunctionComponent : DisposalTubeComponent { -#pragma warning disable 649 - [Dependency] private readonly IRobustRandom _random; -#pragma warning restore 649 + [Dependency] private readonly IRobustRandom _random = default!; /// /// The angles to connect to. diff --git a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs index a3703a5e21..7c454cc792 100644 --- a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs @@ -46,9 +46,7 @@ namespace Content.Server.GameObjects.Components.Fluids // based on behaviour (e.g. someone being punched vs slashed with a sword would have different blood sprite) // to check for low volumes for evaporation or whatever -#pragma warning disable 649 - [Dependency] private readonly IMapManager _mapManager; -#pragma warning restore 649 + [Dependency] private readonly IMapManager _mapManager = default!; public override string Name => "Puddle"; diff --git a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs index a0b86f0625..bc58b80601 100644 --- a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs @@ -28,10 +28,8 @@ namespace Content.Server.GameObjects.Components.GUI [RegisterComponent] public class InventoryComponent : SharedInventoryComponent, IExAct, IEffectBlocker, IPressureProtection { -#pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystemManager; - [Dependency] private readonly IServerNotifyManager _serverNotifyManager; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + [Dependency] private readonly IServerNotifyManager _serverNotifyManager = default!; [ViewVariables] private readonly Dictionary _slotContainers = new Dictionary(); diff --git a/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs b/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs index b2d6cbb98c..954c1ed639 100644 --- a/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs @@ -14,11 +14,9 @@ namespace Content.Server.GameObjects.Components.Interactable [RegisterComponent] public class TilePryingComponent : Component, IAfterInteract { -#pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystemManager; - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager; - [Dependency] private readonly IMapManager _mapManager; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; public override string Name => "TilePrying"; private bool _toolComponentNeeded = true; diff --git a/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs b/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs index 7de3236ec9..0690744f42 100644 --- a/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs @@ -20,9 +20,7 @@ namespace Content.Server.GameObjects.Components.Items.Clothing [ComponentReference(typeof(IItemComponent))] public class ClothingComponent : ItemComponent, IUse { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _serverNotifyManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _serverNotifyManager = default!; public override string Name => "Clothing"; public override uint? NetID => ContentNetIDs.CLOTHING; diff --git a/Content.Server/GameObjects/Components/Items/DiceComponent.cs b/Content.Server/GameObjects/Components/Items/DiceComponent.cs index ca12cfd0fd..3df295bc0a 100644 --- a/Content.Server/GameObjects/Components/Items/DiceComponent.cs +++ b/Content.Server/GameObjects/Components/Items/DiceComponent.cs @@ -20,10 +20,8 @@ namespace Content.Server.GameObjects.Components.Items [RegisterComponent] public class DiceComponent : Component, IActivate, IUse, ILand, IExamine { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IRobustRandom _random; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; public override string Name => "Dice"; diff --git a/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs b/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs index 4bab06757b..8321016565 100644 --- a/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs +++ b/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs @@ -15,10 +15,8 @@ namespace Content.Server.GameObjects.Components.Items [RegisterComponent] public class FloorTileItemComponent : Component, IAfterInteract { -#pragma warning disable 649 - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager; - [Dependency] private readonly IMapManager _mapManager; -#pragma warning restore 649 + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; public override string Name => "FloorTile"; private string _outputTile; diff --git a/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs b/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs index 16d2e5557e..3cb63d1aa2 100644 --- a/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs +++ b/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs @@ -15,10 +15,8 @@ namespace Content.Server.GameObjects.Components.Items.RCD [RegisterComponent] public class RCDAmmoComponent : Component, IAfterInteract, IExamine { + [Dependency] private IServerNotifyManager _serverNotifyManager = default!; -#pragma warning disable 649 - [Dependency] private IServerNotifyManager _serverNotifyManager; -#pragma warning restore 649 public override string Name => "RCDAmmo"; //How much ammo we refill diff --git a/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs b/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs index d479c31d02..40f2b36722 100644 --- a/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs +++ b/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs @@ -25,14 +25,12 @@ namespace Content.Server.GameObjects.Components.Items.RCD [RegisterComponent] public class RCDComponent : Component, IAfterInteract, IUse, IExamine { + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; + [Dependency] private readonly IServerNotifyManager _serverNotifyManager = default!; -#pragma warning disable 649 - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager; - [Dependency] private readonly IEntitySystemManager _entitySystemManager; - [Dependency] private readonly IMapManager _mapManager; - [Dependency] private readonly IServerEntityManager _serverEntityManager; - [Dependency] private IServerNotifyManager _serverNotifyManager; -#pragma warning restore 649 public override string Name => "RCD"; private RcdMode _mode = 0; //What mode are we on? Can be floors, walls, deconstruct. private readonly RcdMode[] _modes = (RcdMode[]) Enum.GetValues(typeof(RcdMode)); diff --git a/Content.Server/GameObjects/Components/Items/Storage/Fill/MedkitFillComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/Fill/MedkitFillComponent.cs index 713154661c..94dabf7576 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/Fill/MedkitFillComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/Fill/MedkitFillComponent.cs @@ -8,11 +8,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill [RegisterComponent] internal sealed class MedkitFillComponent : Component, IMapInit { - public override string Name => "MedkitFill"; + [Dependency] private readonly IEntityManager _entityManager = default!; -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 + public override string Name => "MedkitFill"; void IMapInit.MapInit() { diff --git a/Content.Server/GameObjects/Components/Items/Storage/Fill/StorageFillComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/Fill/StorageFillComponent.cs index 2b11eb2503..66b13739e1 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/Fill/StorageFillComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/Fill/StorageFillComponent.cs @@ -10,14 +10,12 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill [RegisterComponent] internal sealed class StorageFillComponent : Component, IMapInit { + [Dependency] private readonly IEntityManager _entityManager = default!; + public override string Name => "StorageFill"; private List _contents = new List(); -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 - public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); diff --git a/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxElectricalFillComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxElectricalFillComponent.cs index ca3516f5fd..39e74b5556 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxElectricalFillComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxElectricalFillComponent.cs @@ -10,11 +10,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill [RegisterComponent] internal sealed class ToolboxElectricalFillComponent : Component, IMapInit { - public override string Name => "ToolboxElectricalFill"; + [Dependency] private readonly IEntityManager _entityManager = default!; -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 + public override string Name => "ToolboxElectricalFill"; void IMapInit.MapInit() { diff --git a/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxEmergencyFillComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxEmergencyFillComponent.cs index 31af8f5f8d..1dd6c7a882 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxEmergencyFillComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxEmergencyFillComponent.cs @@ -10,11 +10,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill [RegisterComponent] internal sealed class ToolboxEmergencyFillComponent : Component, IMapInit { - public override string Name => "ToolboxEmergencyFill"; + [Dependency] private readonly IEntityManager _entityManager = default!; -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 + public override string Name => "ToolboxEmergencyFill"; void IMapInit.MapInit() { diff --git a/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxGoldFillComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxGoldFillComponent.cs index 8f6dfdf5ee..11f2a5bfbd 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxGoldFillComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/Fill/ToolboxGoldFillComponent.cs @@ -10,11 +10,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill [RegisterComponent] internal sealed class ToolboxGoldFillComponent : Component, IMapInit { - public override string Name => "ToolboxGoldFill"; + [Dependency] private readonly IEntityManager _entityManager = default!; -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 + public override string Name => "ToolboxGoldFill"; void IMapInit.MapInit() { diff --git a/Content.Server/GameObjects/Components/Items/Storage/Fill/UtilityBeltClothingFillComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/Fill/UtilityBeltClothingFillComponent.cs index 876d5c9a84..9fe010fe8c 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/Fill/UtilityBeltClothingFillComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/Fill/UtilityBeltClothingFillComponent.cs @@ -8,11 +8,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill [RegisterComponent] internal sealed class UtilityBeltClothingFillComponent : Component, IMapInit { - public override string Name => "UtilityBeltClothingFill"; + [Dependency] private readonly IEntityManager _entityManager = default!; -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 + public override string Name => "UtilityBeltClothingFill"; void IMapInit.MapInit() { diff --git a/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs index a82183da4f..93260ae1bb 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs @@ -24,13 +24,11 @@ namespace Content.Server.GameObjects.Components.Items.Storage [ComponentReference(typeof(IItemComponent))] public class ItemComponent : StorableComponent, IInteractHand, IExAct, IEquipped, IUnequipped, IItemComponent { + [Dependency] private readonly IMapManager _mapManager = default!; + public override string Name => "Item"; public override uint? NetID => ContentNetIDs.ITEM; - #pragma warning disable 649 - [Dependency] private readonly IMapManager _mapManager; - #pragma warning restore 649 - private string _equippedPrefix; public string EquippedPrefix diff --git a/Content.Server/GameObjects/Components/Items/ToysComponent.cs b/Content.Server/GameObjects/Components/Items/ToysComponent.cs index 73d1c06032..8c379e9b11 100644 --- a/Content.Server/GameObjects/Components/Items/ToysComponent.cs +++ b/Content.Server/GameObjects/Components/Items/ToysComponent.cs @@ -16,10 +16,8 @@ namespace Content.Server.GameObjects.Components.Items [RegisterComponent] public class ToysComponent : Component, IActivate, IUse, ILand { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IRobustRandom _random; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; public override string Name => "Toys"; diff --git a/Content.Server/GameObjects/Components/Markers/ConditionalSpawnerComponent.cs b/Content.Server/GameObjects/Components/Markers/ConditionalSpawnerComponent.cs index 7521a25650..1e22218ae8 100644 --- a/Content.Server/GameObjects/Components/Markers/ConditionalSpawnerComponent.cs +++ b/Content.Server/GameObjects/Components/Markers/ConditionalSpawnerComponent.cs @@ -18,14 +18,12 @@ namespace Content.Server.GameObjects.Components.Markers [RegisterComponent] public class ConditionalSpawnerComponent : Component, IMapInit { - public override string Name => "ConditionalSpawner"; + [Dependency] private readonly IGameTicker _gameTicker = default!; + [Dependency] private readonly IReflectionManager _reflectionManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; -#pragma warning disable 649 - [Dependency] private IGameTicker _gameTicker; - [Dependency] private IReflectionManager _reflectionManager; - [Dependency] private IEntityManager _entityManager; - [Dependency] private IRobustRandom _robustRandom; -#pragma warning restore 649 + public override string Name => "ConditionalSpawner"; [ViewVariables(VVAccess.ReadWrite)] public List Prototypes { get; set; } = new List(); diff --git a/Content.Server/GameObjects/Components/Markers/SpawnPointComponent.cs b/Content.Server/GameObjects/Components/Markers/SpawnPointComponent.cs index c1eed02761..a2d145ec49 100644 --- a/Content.Server/GameObjects/Components/Markers/SpawnPointComponent.cs +++ b/Content.Server/GameObjects/Components/Markers/SpawnPointComponent.cs @@ -12,9 +12,7 @@ namespace Content.Server.GameObjects.Components.Markers [ComponentReference(typeof(SharedSpawnPointComponent))] public sealed class SpawnPointComponent : SharedSpawnPointComponent { -#pragma warning disable 649 - [Dependency] private IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [ViewVariables(VVAccess.ReadWrite)] private SpawnPointType _spawnType; diff --git a/Content.Server/GameObjects/Components/Markers/TimedSpawnerComponent.cs b/Content.Server/GameObjects/Components/Markers/TimedSpawnerComponent.cs index 96aa996204..a374a35816 100644 --- a/Content.Server/GameObjects/Components/Markers/TimedSpawnerComponent.cs +++ b/Content.Server/GameObjects/Components/Markers/TimedSpawnerComponent.cs @@ -15,10 +15,8 @@ namespace Content.Server.GameObjects.Components.Markers [RegisterComponent] public class TimedSpawnerComponent : Component { -#pragma warning disable 649 - [Dependency] private IEntityManager _entityManager; - [Dependency] private IRobustRandom _robustRandom; -#pragma warning restore 649 + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; public override string Name => "TimedSpawner"; diff --git a/Content.Server/GameObjects/Components/Markers/TrashSpawnerComponent.cs b/Content.Server/GameObjects/Components/Markers/TrashSpawnerComponent.cs index 0476fb8b99..1a193b4974 100644 --- a/Content.Server/GameObjects/Components/Markers/TrashSpawnerComponent.cs +++ b/Content.Server/GameObjects/Components/Markers/TrashSpawnerComponent.cs @@ -14,12 +14,10 @@ namespace Content.Server.GameObjects.Components.Markers [RegisterComponent] public class TrashSpawnerComponent : ConditionalSpawnerComponent { - public override string Name => "TrashSpawner"; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; -#pragma warning disable 649 - [Dependency] private IEntityManager _entityManager; - [Dependency] private IRobustRandom _robustRandom; -#pragma warning restore 649 + public override string Name => "TrashSpawner"; [ViewVariables(VVAccess.ReadWrite)] public List RarePrototypes { get; set; } = new List(); diff --git a/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs b/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs index 10e9e2c592..d9c1051e27 100644 --- a/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs +++ b/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs @@ -20,9 +20,7 @@ namespace Content.Server.GameObjects.Components.Metabolism [RegisterComponent] public class MetabolismComponent : Component { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public override string Name => "Metabolism"; diff --git a/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs b/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs index 298b5858ec..eb289fdc40 100644 --- a/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs +++ b/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs @@ -17,13 +17,11 @@ namespace Content.Server.GameObjects.Components.Mining [RegisterComponent] public class AsteroidRockComponent : Component, IInteractUsing { + [Dependency] private readonly IRobustRandom _random = default!; + public override string Name => "AsteroidRock"; private static readonly string[] SpriteStates = {"0", "1", "2", "3", "4"}; -#pragma warning disable 649 - [Dependency] private readonly IRobustRandom _random; -#pragma warning restore 649 - public override void Initialize() { base.Initialize(); diff --git a/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs b/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs index 1a29536a5f..82d42a098e 100644 --- a/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs @@ -12,9 +12,7 @@ namespace Content.Server.GameObjects.Components.Mobs [ComponentReference(typeof(SharedStunnableComponent))] public class StunnableComponent : SharedStunnableComponent { -#pragma warning disable 649 - [Dependency] private IGameTiming _gameTiming; -#pragma warning restore 649 + [Dependency] private readonly IGameTiming _gameTiming = default!; protected override void OnKnockdown() { diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs index 7c8963544c..630437f04e 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs @@ -22,12 +22,10 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups public class NodeGroupFactory : INodeGroupFactory { - private readonly Dictionary _groupTypes = new Dictionary(); + [Dependency] private readonly IReflectionManager _reflectionManager = default!; + [Dependency] private readonly IDynamicTypeFactory _typeFactory = default!; -#pragma warning disable 649 - [Dependency] private readonly IReflectionManager _reflectionManager; - [Dependency] private readonly IDynamicTypeFactory _typeFactory; -#pragma warning restore 649 + private readonly Dictionary _groupTypes = new Dictionary(); public void Initialize() { diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/PowerNetNodeGroup.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/PowerNetNodeGroup.cs index 69c39b1ec9..64557b3c34 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/PowerNetNodeGroup.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/PowerNetNodeGroup.cs @@ -29,6 +29,8 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups [NodeGroup(NodeGroupID.HVPower, NodeGroupID.MVPower)] public class PowerNetNodeGroup : BaseNetConnectorNodeGroup, IPowerNet { + [Dependency] private readonly IPowerNetManager _powerNetManager = default!; + [ViewVariables] private readonly List _suppliers = new List(); @@ -43,10 +45,6 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups public static readonly IPowerNet NullNet = new NullPowerNet(); -#pragma warning disable 649 - [Dependency] private readonly IPowerNetManager _powerNetManager; -#pragma warning restore 649 - public PowerNetNodeGroup() { foreach (Priority priority in Enum.GetValues(typeof(Priority))) diff --git a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs index 5bfbebb9b1..73e7efaf20 100644 --- a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs @@ -29,10 +29,9 @@ namespace Content.Server.GameObjects.Components.Nutrition [ComponentReference(typeof(IAfterInteract))] public class DrinkComponent : Component, IUse, IAfterInteract, ISolutionChange, IExamine, ILand { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IRobustRandom _random; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + public override string Name => "Drink"; [ViewVariables] diff --git a/Content.Server/GameObjects/Components/Nutrition/FoodContainerComponent.cs b/Content.Server/GameObjects/Components/Nutrition/FoodContainerComponent.cs index 7769fbba05..24fd20008d 100644 --- a/Content.Server/GameObjects/Components/Nutrition/FoodContainerComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/FoodContainerComponent.cs @@ -22,10 +22,9 @@ namespace Content.Server.GameObjects.Components.Nutrition [RegisterComponent] public sealed class FoodContainer : SharedFoodContainerComponent, IUse { -#pragma warning disable 649 - [Dependency] private readonly IRobustRandom _random; - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + public override string Name => "FoodContainer"; private AppearanceComponent _appearance; diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs index 461698422d..ed4748fbf1 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs @@ -34,11 +34,8 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece [RegisterComponent] public class LightBulbComponent : Component, ILand { - -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IRobustRandom _random; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; /// /// Invoked whenever the state of the light bulb changes. diff --git a/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs b/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs index 81333b2e07..bed8295a06 100644 --- a/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs +++ b/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs @@ -14,10 +14,8 @@ namespace Content.Server.GameObjects.Components.Power [RegisterComponent] internal class WirePlacerComponent : Component, IAfterInteract { -#pragma warning disable 649 - [Dependency] private readonly IServerEntityManager _entityManager; - [Dependency] private readonly IMapManager _mapManager; -#pragma warning restore 649 + [Dependency] private readonly IServerEntityManager _entityManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; /// public override string Name => "WirePlacer"; diff --git a/Content.Server/GameObjects/Components/Research/LatheComponent.cs b/Content.Server/GameObjects/Components/Research/LatheComponent.cs index f9e434929f..987f6846c9 100644 --- a/Content.Server/GameObjects/Components/Research/LatheComponent.cs +++ b/Content.Server/GameObjects/Components/Research/LatheComponent.cs @@ -70,7 +70,7 @@ namespace Content.Server.GameObjects.Components.Research switch (message.Message) { case LatheQueueRecipeMessage msg: - _prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe); + PrototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe); if (recipe != null!) for (var i = 0; i < msg.Quantity; i++) { diff --git a/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs b/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs index 6a754cadac..564decbb9c 100644 --- a/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs +++ b/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs @@ -13,9 +13,8 @@ namespace Content.Server.GameObjects.Components.Rotatable [RegisterComponent] public class RotatableComponent : Component { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; + public override string Name => "Rotatable"; private void TryRotate(IEntity user, Angle angle) diff --git a/Content.Server/GameObjects/Components/Sound/FootstepModifierComponent.cs b/Content.Server/GameObjects/Components/Sound/FootstepModifierComponent.cs index 56e8eb19ce..dc6c2eebd9 100644 --- a/Content.Server/GameObjects/Components/Sound/FootstepModifierComponent.cs +++ b/Content.Server/GameObjects/Components/Sound/FootstepModifierComponent.cs @@ -17,13 +17,10 @@ namespace Content.Server.GameObjects.Components.Sound [RegisterComponent] public class FootstepModifierComponent : Component { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IRobustRandom _footstepRandom; -#pragma warning restore 649 - /// - /// + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _footstepRandom = default!; + /// public override string Name => "FootstepModifier"; public string _soundCollectionName; diff --git a/Content.Server/GameObjects/Components/Stack/StackComponent.cs b/Content.Server/GameObjects/Components/Stack/StackComponent.cs index d5105efdeb..b6c5d37b9b 100644 --- a/Content.Server/GameObjects/Components/Stack/StackComponent.cs +++ b/Content.Server/GameObjects/Components/Stack/StackComponent.cs @@ -19,9 +19,7 @@ namespace Content.Server.GameObjects.Components.Stack [RegisterComponent] public class StackComponent : SharedStackComponent, IInteractUsing, IExamine { -#pragma warning disable 649 - [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager; -#pragma warning restore 649 + [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!; private bool _throwIndividually = false; diff --git a/Content.Server/GameObjects/Components/Trigger/TimerTrigger/OnUseTimerTriggerComponent.cs b/Content.Server/GameObjects/Components/Trigger/TimerTrigger/OnUseTimerTriggerComponent.cs index 406e7d0535..4093af2b95 100644 --- a/Content.Server/GameObjects/Components/Trigger/TimerTrigger/OnUseTimerTriggerComponent.cs +++ b/Content.Server/GameObjects/Components/Trigger/TimerTrigger/OnUseTimerTriggerComponent.cs @@ -13,9 +13,7 @@ namespace Content.Server.GameObjects.Components.Trigger.TimerTrigger [RegisterComponent] public class OnUseTimerTriggerComponent : Component, IUse { - #pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystemManager; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; public override string Name => "OnUseTimerTrigger"; diff --git a/Content.Server/GameObjects/Components/Utensil/UtensilComponent.cs b/Content.Server/GameObjects/Components/Utensil/UtensilComponent.cs index da8f272b28..b530ce5d22 100644 --- a/Content.Server/GameObjects/Components/Utensil/UtensilComponent.cs +++ b/Content.Server/GameObjects/Components/Utensil/UtensilComponent.cs @@ -19,10 +19,8 @@ namespace Content.Server.GameObjects.Components.Utensil [RegisterComponent] public class UtensilComponent : SharedUtensilComponent, IAfterInteract { -#pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystem; - [Dependency] private readonly IRobustRandom _random; -#pragma warning restore 649 + [Dependency] private readonly IEntitySystemManager _entitySystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; protected UtensilType _types = UtensilType.None; diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs index 96468e6e7f..104e20924d 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs @@ -22,10 +22,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee [RegisterComponent] public class FlashComponent : MeleeWeaponComponent, IUse, IExamine { -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; - [Dependency] private readonly ISharedNotifyManager _notifyManager; -#pragma warning restore 649 + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly ISharedNotifyManager _notifyManager = default!; public override string Name => "Flash"; diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs index 5d055f8d4a..ce4a5bf376 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs @@ -23,15 +23,13 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee [RegisterComponent] public class MeleeWeaponComponent : Component, IAttack { + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + [Dependency] private readonly IPhysicsManager _physicsManager = default!; + public override string Name => "MeleeWeapon"; private TimeSpan _lastAttackTime; -#pragma warning disable 649 - [Dependency] private readonly IMapManager _mapManager; - [Dependency] private readonly IEntitySystemManager _entitySystemManager; - [Dependency] private readonly IPhysicsManager _physicsManager; -#pragma warning restore 649 - private int _damage; private float _range; private float _arcWidth; diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs index 8da76dd33a..6ede88e76a 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs @@ -30,10 +30,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee [RegisterComponent] public class StunbatonComponent : MeleeWeaponComponent, IUse, IExamine, IMapInit, IInteractUsing { -#pragma warning disable 649 - [Dependency] private IRobustRandom _robustRandom; - [Dependency] private readonly ISharedNotifyManager _notifyManager; -#pragma warning restore 649 + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly ISharedNotifyManager _notifyManager = default!; public override string Name => "Stunbaton"; diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs index dfc2f61b8a..dfccb1307e 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs @@ -39,10 +39,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { // There's still some of py01 and PJB's work left over, especially in underlying shooting logic, // it's just when I re-organised it changed me as the contributor -#pragma warning disable 649 - [Dependency] private IGameTiming _gameTiming; - [Dependency] private IRobustRandom _robustRandom; -#pragma warning restore 649 + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; public override FireRateSelector FireRateSelector => _fireRateSelector; private FireRateSelector _fireRateSelector; diff --git a/Content.Server/GameObjects/EntitySystems/AI/Pathfinding/Accessible/AiReachableSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/Pathfinding/Accessible/AiReachableSystem.cs index 3531dcdde1..3470365282 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/Pathfinding/Accessible/AiReachableSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/Pathfinding/Accessible/AiReachableSystem.cs @@ -36,11 +36,9 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible * There's probably a better data structure to use though you'd need to benchmark multiple ones to compare, * at the very least on the memory side it could definitely be better. */ + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; -#pragma warning disable 649 - [Dependency] private IMapManager _mapmanager; - [Dependency] private IGameTiming _gameTiming; -#pragma warning restore 649 private PathfindingSystem _pathfindingSystem; /// @@ -87,7 +85,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible #if DEBUG SubscribeLocalEvent(SendDebugMessage); #endif - _mapmanager.OnGridRemoved += GridRemoved; + _mapManager.OnGridRemoved += GridRemoved; } private void GridRemoved(GridId gridId) @@ -161,7 +159,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible /// public bool CanAccess(IEntity entity, IEntity target, float range = 0.0f) { - var targetTile = _mapmanager.GetGrid(target.Transform.GridID).GetTileRef(target.Transform.GridPosition); + var targetTile = _mapManager.GetGrid(target.Transform.GridID).GetTileRef(target.Transform.GridPosition); var targetNode = _pathfindingSystem.GetNode(targetTile); var collisionMask = 0; @@ -199,7 +197,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible return false; } - var entityTile = _mapmanager.GetGrid(entity.Transform.GridID).GetTileRef(entity.Transform.GridPosition); + var entityTile = _mapManager.GetGrid(entity.Transform.GridID).GetTileRef(entity.Transform.GridPosition); var entityNode = _pathfindingSystem.GetNode(entityTile); var entityRegion = GetRegion(entityNode); var targetRegion = GetRegion(targetNode); @@ -409,7 +407,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible /// public PathfindingRegion GetRegion(IEntity entity) { - var entityTile = _mapmanager.GetGrid(entity.Transform.GridID).GetTileRef(entity.Transform.GridPosition); + var entityTile = _mapManager.GetGrid(entity.Transform.GridID).GetTileRef(entity.Transform.GridPosition); var entityNode = _pathfindingSystem.GetNode(entityTile); return GetRegion(entityNode); } @@ -686,7 +684,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible private void SendRegionsDebugMessage(GridId gridId) { - var grid = _mapmanager.GetGrid(gridId); + var grid = _mapManager.GetGrid(gridId); // Chunk / Regions / Nodes var debugResult = new Dictionary>>(); var chunkIdx = 0; @@ -709,7 +707,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible foreach (var node in region.Nodes) { - var nodeVector = grid.GridTileToLocal(node.TileRef.GridIndices).ToMapPos(_mapmanager); + var nodeVector = grid.GridTileToLocal(node.TileRef.GridIndices).ToMapPos(_mapManager); debugRegionNodes.Add(nodeVector); } @@ -729,7 +727,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible /// private void SendRegionCacheMessage(GridId gridId, IEnumerable regions, bool cached) { - var grid = _mapmanager.GetGrid(gridId); + var grid = _mapManager.GetGrid(gridId); var debugResult = new Dictionary>(); foreach (var region in regions) @@ -738,7 +736,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible foreach (var node in region.Nodes) { - var nodeVector = grid.GridTileToLocal(node.TileRef.GridIndices).ToMapPos(_mapmanager); + var nodeVector = grid.GridTileToLocal(node.TileRef.GridIndices).ToMapPos(_mapManager); debugResult[_runningCacheIdx].Add(nodeVector); } diff --git a/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs index 2411fcc984..fbb36045ab 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs @@ -25,11 +25,9 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering public sealed class AiSteeringSystem : EntitySystem { // http://www.red3d.com/cwr/papers/1999/gdc99steer.html for a steering overview + [Dependency] private IMapManager _mapManager = default!; + [Dependency] private IPauseManager _pauseManager = default!; -#pragma warning disable 649 - [Dependency] private IMapManager _mapManager; - [Dependency] private IPauseManager _pauseManager; -#pragma warning restore 649 private PathfindingSystem _pathfindingSystem; /// @@ -255,7 +253,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering } var entitySteering = steeringRequest as EntityTargetSteeringRequest; - + if (entitySteering != null && entitySteering.Target.Deleted) { controller.VelocityDir = Vector2.Zero; @@ -279,10 +277,10 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering // Check if we have arrived var targetDistance = (entity.Transform.MapPosition.Position - steeringRequest.TargetMap.Position).Length; steeringRequest.TimeUntilInteractionCheck -= frameTime; - + if (targetDistance <= steeringRequest.ArrivalDistance && steeringRequest.TimeUntilInteractionCheck <= 0.0f) { - if (!steeringRequest.RequiresInRangeUnobstructed || + if (!steeringRequest.RequiresInRangeUnobstructed || InteractionChecks.InRangeUnobstructed(entity, steeringRequest.TargetMap, steeringRequest.ArrivalDistance, ignoredEnt: entity)) { // TODO: Need cruder LOS checks for ranged weaps @@ -353,7 +351,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering if (entitySteering != null) { // Check if target's moved too far - if (_entityTargetPosition.TryGetValue(entity, out var targetGrid) && + if (_entityTargetPosition.TryGetValue(entity, out var targetGrid) && (entitySteering.TargetGrid.Position - targetGrid.Position).Length >= entitySteering.TargetMaxMove) { // We'll just repath and keep following the existing one until we get a new one diff --git a/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs index 1b83b0b3fe..969b1a28a7 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/ExamineSystem.cs @@ -11,9 +11,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click { public class ExamineSystem : ExamineSystemShared { -#pragma warning disable 649 - [Dependency] private IEntityManager _entityManager; -#pragma warning restore 649 + [Dependency] private IEntityManager _entityManager = default!; private static readonly FormattedMessage _entityNotFoundMessage; diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index d607e79d59..c46ffa7bb7 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -39,9 +39,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click [UsedImplicitly] public sealed class InteractionSystem : SharedInteractionSystem { -#pragma warning disable 649 - [Dependency] private readonly IMapManager _mapManager; -#pragma warning restore 649 + [Dependency] private readonly IMapManager _mapManager = default!; public override void Initialize() { diff --git a/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs b/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs index 2138e559dc..e766994372 100644 --- a/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs @@ -36,10 +36,8 @@ namespace Content.Server.GameObjects.EntitySystems [UsedImplicitly] internal class ConstructionSystem : SharedConstructionSystem { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IMapManager _mapManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; private readonly Dictionary _craftRecipes = new Dictionary(); diff --git a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs index 8243fca463..68d95e7028 100644 --- a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs @@ -27,10 +27,8 @@ namespace Content.Server.GameObjects.EntitySystems [UsedImplicitly] internal sealed class HandsSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly IMapManager _mapManager; - [Dependency] private readonly IServerNotifyManager _notifyManager; -#pragma warning restore 649 + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; private const float ThrowForce = 1.5f; // Throwing force of mobs in Newtons diff --git a/Content.Server/GameObjects/EntitySystems/NodeGroupSystem.cs b/Content.Server/GameObjects/EntitySystems/NodeGroupSystem.cs index c1c719ad22..9b455e8cec 100644 --- a/Content.Server/GameObjects/EntitySystems/NodeGroupSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/NodeGroupSystem.cs @@ -6,9 +6,7 @@ namespace Content.Server.GameObjects.EntitySystems { public class NodeGroupSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly INodeGroupManager _groupManager; -#pragma warning restore 649 + [Dependency] private readonly INodeGroupManager _groupManager = default!; public override void Update(float frameTime) { diff --git a/Content.Server/GameObjects/EntitySystems/PowerNetSystem.cs b/Content.Server/GameObjects/EntitySystems/PowerNetSystem.cs index 74297f8670..40244597f5 100644 --- a/Content.Server/GameObjects/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/PowerNetSystem.cs @@ -6,9 +6,7 @@ namespace Content.Server.GameObjects.EntitySystems { public class PowerNetSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly IPowerNetManager _powerNetManager; -#pragma warning restore 649 + [Dependency] private readonly IPowerNetManager _powerNetManager = default!; public override void Update(float frameTime) { diff --git a/Content.Server/GameObjects/EntitySystems/RoundEndSystem.cs b/Content.Server/GameObjects/EntitySystems/RoundEndSystem.cs index 9077e94184..f912a63ec5 100644 --- a/Content.Server/GameObjects/EntitySystems/RoundEndSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/RoundEndSystem.cs @@ -10,10 +10,8 @@ namespace Content.Server.GameObjects.EntitySystems { public class RoundEndSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private IGameTicker _gameTicker; - [Dependency] private IGameTiming _gameTiming; -#pragma warning restore 649 + [Dependency] private readonly IGameTicker _gameTicker = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; private CancellationTokenSource _roundEndCancellationTokenSource = new CancellationTokenSource(); public bool IsRoundEndCountdownStarted { get; private set; } diff --git a/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs b/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs index c34a502b21..dd02a67a76 100644 --- a/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs @@ -21,11 +21,9 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents // Somewhat based off of TG's implementation of events public sealed class StationEventSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly IServerNetManager _netManager; - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IGameTicker _gameTicker; -#pragma warning restore 649 + [Dependency] private readonly IServerNetManager _netManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTicker _gameTicker = default!; public StationEvent CurrentEvent { get; private set; } public IReadOnlyCollection StationEvents => _stationEvents; @@ -33,7 +31,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents private List _stationEvents = new List(); private const float MinimumTimeUntilFirstEvent = 600; - + /// /// How long until the next check for an event runs /// @@ -77,7 +75,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents return result.ToString(); } - + /// /// Admins can forcibly run events by passing in the Name /// @@ -88,14 +86,14 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents // Could use a dictionary but it's such a minor thing, eh. // Wasn't sure on whether to localize this given it's a command var upperName = name.ToUpperInvariant(); - + foreach (var stationEvent in _stationEvents) { if (stationEvent.Name.ToUpperInvariant() != upperName) { continue; } - + CurrentEvent?.Shutdown(); CurrentEvent = stationEvent; stationEvent.Startup(); @@ -114,12 +112,12 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents { var availableEvents = AvailableEvents(true); var randomEvent = FindEvent(availableEvents); - + if (randomEvent == null) { return Loc.GetString("No valid events available"); } - + CurrentEvent?.Shutdown(); CurrentEvent = randomEvent; CurrentEvent.Startup(); @@ -134,7 +132,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents public string StopEvent() { string resultText; - + if (CurrentEvent == null) { resultText = Loc.GetString("No event running currently"); @@ -145,11 +143,11 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents CurrentEvent.Shutdown(); CurrentEvent = null; } - + ResetTimer(); return resultText; } - + public override void Initialize() { base.Initialize(); @@ -212,7 +210,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents if (CurrentEvent != null) { CurrentEvent.Update(frameTime); - + // Shutdown the event and set the timer for the next event if (!CurrentEvent.Running) { @@ -262,14 +260,14 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents { return null; } - + var sumOfWeights = 0; foreach (var stationEvent in availableEvents) { sumOfWeights += (int) stationEvent.Weight; } - + var robustRandom = IoCManager.Resolve(); sumOfWeights = robustRandom.Next(sumOfWeights); @@ -295,7 +293,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents { TimeSpan currentTime; var playerCount = IoCManager.Resolve().PlayerCount; - + // playerCount does a lock so we'll just keep the variable here if (!ignoreEarliestStart) { @@ -346,7 +344,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents CurrentEvent.Shutdown(); CurrentEvent = null; } - + foreach (var stationEvent in _stationEvents) { stationEvent.Occurrences = 0; diff --git a/Content.Server/GameObjects/EntitySystems/VerbSystem.cs b/Content.Server/GameObjects/EntitySystems/VerbSystem.cs index 63775b3e44..6b625cdf25 100644 --- a/Content.Server/GameObjects/EntitySystems/VerbSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/VerbSystem.cs @@ -14,9 +14,7 @@ namespace Content.Server.GameObjects.EntitySystems { public class VerbSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 + [Dependency] private readonly IEntityManager _entityManager = default!; public override void Initialize() { diff --git a/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs b/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs index 04e663b6da..163f3a0d85 100644 --- a/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs +++ b/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs @@ -8,9 +8,7 @@ namespace Content.Server.GameTicking.GamePresets { public sealed class PresetDeathMatch : GamePreset { -#pragma warning disable 649 - [Dependency] private readonly IGameTicker _gameTicker; -#pragma warning restore 649 + [Dependency] private readonly IGameTicker _gameTicker = default!; public override bool Start(IReadOnlyList readyPlayers, bool force = false) { diff --git a/Content.Server/GameTicking/GamePresets/PresetSandbox.cs b/Content.Server/GameTicking/GamePresets/PresetSandbox.cs index 7ceebaf108..4c8c61267a 100644 --- a/Content.Server/GameTicking/GamePresets/PresetSandbox.cs +++ b/Content.Server/GameTicking/GamePresets/PresetSandbox.cs @@ -7,9 +7,7 @@ namespace Content.Server.GameTicking.GamePresets { public sealed class PresetSandbox : GamePreset { -#pragma warning disable 649 - [Dependency] private readonly ISandboxManager _sandboxManager; -#pragma warning restore 649 + [Dependency] private readonly ISandboxManager _sandboxManager = default!; public override bool Start(IReadOnlyList readyPlayers, bool force = false) { diff --git a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs index f79abde6b2..81eeb43d22 100644 --- a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs +++ b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs @@ -21,13 +21,11 @@ namespace Content.Server.GameTicking.GamePresets { public class PresetSuspicion : GamePreset { -#pragma warning disable 649 - [Dependency] private readonly IChatManager _chatManager; - [Dependency] private readonly IGameTicker _gameTicker; - [Dependency] private readonly IRobustRandom _random; - [Dependency] private readonly IConfigurationManager _cfg; - [Dependency] private IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IGameTicker _gameTicker = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public int MinPlayers { get; set; } public int MinTraitors { get; set; } diff --git a/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs b/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs index cd108324e0..7c59c9dd04 100644 --- a/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs +++ b/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs @@ -21,12 +21,10 @@ namespace Content.Server.GameTicking.GameRules { private static readonly TimeSpan DeadCheckDelay = TimeSpan.FromSeconds(5); -#pragma warning disable 649 - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IEntityManager _entityManager; - [Dependency] private readonly IChatManager _chatManager; - [Dependency] private readonly IGameTicker _gameTicker; -#pragma warning restore 649 + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IGameTicker _gameTicker = default!; private CancellationTokenSource _checkTimerCancel; diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs index 326614c2dd..f004eed7d4 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -25,11 +25,9 @@ namespace Content.Server.GameTicking.GameRules { private static readonly TimeSpan DeadCheckDelay = TimeSpan.FromSeconds(1); -#pragma warning disable 649 - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IChatManager _chatManager; - [Dependency] private readonly IGameTicker _gameTicker; -#pragma warning restore 649 + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IGameTicker _gameTicker = default!; private readonly CancellationTokenSource _checkTimerCancel = new CancellationTokenSource(); diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index da6f17c9da..a38aad78c2 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -980,22 +980,20 @@ The current game mode is: [color=white]{0}[/color]. return preset; } -#pragma warning disable 649 - [Dependency] private IEntityManager _entityManager; - [Dependency] private IMapManager _mapManager; - [Dependency] private IMapLoader _mapLoader; - [Dependency] private IGameTiming _gameTiming; - [Dependency] private IConfigurationManager _configurationManager; - [Dependency] private IChatManager _chatManager; - [Dependency] private IServerNetManager _netManager; - [Dependency] private IDynamicTypeFactory _dynamicTypeFactory; - [Dependency] private IPrototypeManager _prototypeManager; - [Dependency] private readonly ILocalizationManager _localization; - [Dependency] private readonly IRobustRandom _robustRandom; - [Dependency] private readonly IServerPreferencesManager _prefsManager; - [Dependency] private readonly IBaseServer _baseServer; - [Dependency] private readonly IWatchdogApi _watchdogApi; -#pragma warning restore 649 + [Dependency] private IEntityManager _entityManager = default!; + [Dependency] private IMapManager _mapManager = default!; + [Dependency] private IMapLoader _mapLoader = default!; + [Dependency] private IGameTiming _gameTiming = default!; + [Dependency] private IConfigurationManager _configurationManager = default!; + [Dependency] private IChatManager _chatManager = default!; + [Dependency] private IServerNetManager _netManager = default!; + [Dependency] private IDynamicTypeFactory _dynamicTypeFactory = default!; + [Dependency] private IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ILocalizationManager _localization = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly IServerPreferencesManager _prefsManager = default!; + [Dependency] private readonly IBaseServer _baseServer = default!; + [Dependency] private readonly IWatchdogApi _watchdogApi = default!; } public enum GameRunLevel diff --git a/Content.Server/GameTicking/GameTickerCommands.cs b/Content.Server/GameTicking/GameTickerCommands.cs index a43f2d9498..2e3b0d78d0 100644 --- a/Content.Server/GameTicking/GameTickerCommands.cs +++ b/Content.Server/GameTicking/GameTickerCommands.cs @@ -183,9 +183,8 @@ namespace Content.Server.GameTicking class JoinGameCommand : IClientCommand { -#pragma warning disable 649 - [Dependency] private IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + public string Command => "joingame"; public string Description => ""; public string Help => ""; diff --git a/Content.Server/MoMMILink.cs b/Content.Server/MoMMILink.cs index 318d98151a..0ea3befd25 100644 --- a/Content.Server/MoMMILink.cs +++ b/Content.Server/MoMMILink.cs @@ -18,12 +18,10 @@ namespace Content.Server { internal sealed class MoMMILink : IMoMMILink, IPostInjectInit { -#pragma warning disable 649 - [Dependency] private readonly IConfigurationManager _configurationManager; - [Dependency] private readonly IStatusHost _statusHost; - [Dependency] private readonly IChatManager _chatManager; - [Dependency] private readonly ITaskManager _taskManager; -#pragma warning restore 649 + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IStatusHost _statusHost = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly ITaskManager _taskManager = default!; private readonly HttpClient _httpClient = new HttpClient(); diff --git a/Content.Server/Mobs/Commands.cs b/Content.Server/Mobs/Commands.cs index a759d8d5b4..81efc2a4eb 100644 --- a/Content.Server/Mobs/Commands.cs +++ b/Content.Server/Mobs/Commands.cs @@ -51,9 +51,7 @@ namespace Content.Server.Mobs public class AddRoleCommand : IClientCommand { -#pragma warning disable 649 - [Dependency] private IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public string Command => "addrole"; @@ -85,10 +83,7 @@ namespace Content.Server.Mobs public class RemoveRoleCommand : IClientCommand { - -#pragma warning disable 649 - [Dependency] private IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public string Command => "rmrole"; diff --git a/Content.Server/PDA/PDAUplinkManager.cs b/Content.Server/PDA/PDAUplinkManager.cs index 0d93c95dc5..ab2b63213a 100644 --- a/Content.Server/PDA/PDAUplinkManager.cs +++ b/Content.Server/PDA/PDAUplinkManager.cs @@ -14,10 +14,8 @@ namespace Content.Server.PDA { public class PDAUplinkManager : IPDAUplinkManager { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; private List _accounts; private List _listings; diff --git a/Content.Server/Preferences/ServerPreferencesManager.cs b/Content.Server/Preferences/ServerPreferencesManager.cs index ac9e944f56..f673a4e7b2 100644 --- a/Content.Server/Preferences/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/ServerPreferencesManager.cs @@ -19,11 +19,10 @@ namespace Content.Server.Preferences /// public class ServerPreferencesManager : SharedPreferencesManager, IServerPreferencesManager { -#pragma warning disable 649 - [Dependency] private readonly IServerNetManager _netManager; - [Dependency] private readonly IConfigurationManager _configuration; - [Dependency] private readonly IResourceManager _resourceManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNetManager _netManager = default!; + [Dependency] private readonly IConfigurationManager _configuration = default!; + [Dependency] private readonly IResourceManager _resourceManager = default!; + private PreferencesDatabase _preferencesDb; private Task _prefsDbLoadTask; diff --git a/Content.Server/Sandbox/SandboxManager.cs b/Content.Server/Sandbox/SandboxManager.cs index 76cfc201f0..78b89871dd 100644 --- a/Content.Server/Sandbox/SandboxManager.cs +++ b/Content.Server/Sandbox/SandboxManager.cs @@ -19,15 +19,13 @@ namespace Content.Server.Sandbox { internal sealed class SandboxManager : SharedSandboxManager, ISandboxManager { -#pragma warning disable 649 - [Dependency] private readonly IPlayerManager _playerManager; - [Dependency] private readonly IServerNetManager _netManager; - [Dependency] private readonly IGameTicker _gameTicker; - [Dependency] private readonly IPlacementManager _placementManager; - [Dependency] private readonly IConGroupController _conGroupController; - [Dependency] private readonly IEntityManager _entityManager; - [Dependency] private readonly IConsoleShell _shell; -#pragma warning restore 649 + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IServerNetManager _netManager = default!; + [Dependency] private readonly IGameTicker _gameTicker = default!; + [Dependency] private readonly IPlacementManager _placementManager = default!; + [Dependency] private readonly IConGroupController _conGroupController = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IConsoleShell _shell = default!; private bool _isSandboxEnabled; diff --git a/Content.Server/ServerNotifyManager.cs b/Content.Server/ServerNotifyManager.cs index c4e8068106..ee3129d4c1 100644 --- a/Content.Server/ServerNotifyManager.cs +++ b/Content.Server/ServerNotifyManager.cs @@ -15,9 +15,7 @@ namespace Content.Server { public class ServerNotifyManager : SharedNotifyManager, IServerNotifyManager { -#pragma warning disable 649 - [Dependency] private IServerNetManager _netManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNetManager _netManager = default!; private bool _initialized; diff --git a/Content.Shared/Chemistry/ReagentPrototype.cs b/Content.Shared/Chemistry/ReagentPrototype.cs index 1f476c47fc..e863895650 100644 --- a/Content.Shared/Chemistry/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/ReagentPrototype.cs @@ -14,9 +14,7 @@ namespace Content.Shared.Chemistry { private const float CelsiusToKelvin = 273.15f; -#pragma warning disable 649 - [Dependency] private readonly IModuleManager _moduleManager; -#pragma warning restore 649 + [Dependency] private readonly IModuleManager _moduleManager = default!; private string _id; private string _name; diff --git a/Content.Shared/EntryPoint.cs b/Content.Shared/EntryPoint.cs index 25976f6379..ad446b324d 100644 --- a/Content.Shared/EntryPoint.cs +++ b/Content.Shared/EntryPoint.cs @@ -16,11 +16,9 @@ namespace Content.Shared // If you want to change your codebase's language, do it here. private const string Culture = "en-US"; -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly ILocalizationManager _localizationManager = default!; public override void PreInit() { diff --git a/Content.Shared/GameObjects/Components/Cargo/SharedCargoConsoleComponent.cs b/Content.Shared/GameObjects/Components/Cargo/SharedCargoConsoleComponent.cs index 66f5041184..b8d3088e64 100644 --- a/Content.Shared/GameObjects/Components/Cargo/SharedCargoConsoleComponent.cs +++ b/Content.Shared/GameObjects/Components/Cargo/SharedCargoConsoleComponent.cs @@ -9,15 +9,12 @@ namespace Content.Shared.GameObjects.Components.Cargo { public class SharedCargoConsoleComponent : Component { -#pragma warning disable CS0649 - [Dependency] - protected IPrototypeManager _prototypeManager; -#pragma warning restore + [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; public sealed override string Name => "CargoConsole"; /// - /// Sends away or requests shuttle + /// Sends away or requests shuttle /// [Serializable, NetSerializable] public class CargoConsoleShuttleMessage : BoundUserInterfaceMessage diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs index 0c11e7393a..1d324ba7d4 100644 --- a/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs +++ b/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs @@ -14,9 +14,7 @@ namespace Content.Shared.GameObjects.Components.Mobs { public abstract class SharedStunnableComponent : Component, IMoveSpeedModifier, IActionBlocker, IInteractHand { -#pragma warning disable 649 - [Dependency] private IGameTiming _gameTiming; -#pragma warning restore 649 + [Dependency] private readonly IGameTiming _gameTiming = default!; public sealed override string Name => "Stunnable"; public override uint? NetID => ContentNetIDs.STUNNABLE; diff --git a/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs index d83b7b89d9..013b40e59e 100644 --- a/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs +++ b/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs @@ -16,9 +16,7 @@ namespace Content.Shared.GameObjects.Components.Movement { public abstract class SharedSlipperyComponent : Component, ICollideBehavior { -#pragma warning disable 649 - [Dependency] private readonly IEntityManager _entityManager; -#pragma warning restore 649 + [Dependency] private readonly IEntityManager _entityManager = default!; public sealed override string Name => "Slippery"; diff --git a/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs b/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs index 94480ea9db..79ae8e0d61 100644 --- a/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs +++ b/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs @@ -11,14 +11,11 @@ namespace Content.Shared.GameObjects.Components.Research { public class SharedLatheComponent : Component { + [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; + public override string Name => "Lathe"; public override uint? NetID => ContentNetIDs.LATHE; -#pragma warning disable CS0649 - [Dependency] - protected IPrototypeManager _prototypeManager; -#pragma warning restore - public bool CanProduce(LatheRecipePrototype recipe, int quantity = 1) { if (!Owner.TryGetComponent(out SharedMaterialStorageComponent storage) @@ -36,7 +33,7 @@ namespace Content.Shared.GameObjects.Components.Research public bool CanProduce(string ID, int quantity = 1) { - return _prototypeManager.TryIndex(ID, out LatheRecipePrototype recipe) && CanProduce(recipe, quantity); + return PrototypeManager.TryIndex(ID, out LatheRecipePrototype recipe) && CanProduce(recipe, quantity); } /// diff --git a/Content.Shared/GameObjects/EntitySystems/SharedInteractionSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedInteractionSystem.cs index ca1fc0a5b9..610b414c65 100644 --- a/Content.Shared/GameObjects/EntitySystems/SharedInteractionSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/SharedInteractionSystem.cs @@ -17,9 +17,7 @@ namespace Content.Shared.GameObjects.EntitySystems [UsedImplicitly] public class SharedInteractionSystem : EntitySystem { - #pragma warning disable 649 - [Dependency] private readonly IPhysicsManager _physicsManager; - #pragma warning restore 649 + [Dependency] private readonly IPhysicsManager _physicsManager = default!; public const float InteractionRange = 2; public const float InteractionRangeSquared = InteractionRange * InteractionRange; diff --git a/Content.Shared/Kitchen/RecipeManager.cs b/Content.Shared/Kitchen/RecipeManager.cs index c5f7657a69..08c30cb646 100644 --- a/Content.Shared/Kitchen/RecipeManager.cs +++ b/Content.Shared/Kitchen/RecipeManager.cs @@ -5,12 +5,10 @@ using Robust.Shared.Prototypes; namespace Content.Shared.Kitchen { - public class RecipeManager { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + public List Recipes { get; private set; } public void Initialize() diff --git a/Content.Shared/Physics/SlipController.cs b/Content.Shared/Physics/SlipController.cs index f5f3004a3f..4339e3de47 100644 --- a/Content.Shared/Physics/SlipController.cs +++ b/Content.Shared/Physics/SlipController.cs @@ -6,9 +6,7 @@ namespace Content.Shared.Physics { public class SlipController : VirtualController { -#pragma warning disable 649 - [Dependency] private readonly IPhysicsManager _physicsManager; -#pragma warning restore 649 + [Dependency] private readonly IPhysicsManager _physicsManager = default!; public SlipController() { diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 632333c3cc..9a4d47bb02 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -78,6 +78,7 @@ True True True + True True True True From abc446109e70286940d899efbbf104a826202e24 Mon Sep 17 00:00:00 2001 From: Swept Date: Mon, 24 Aug 2020 18:10:57 +0000 Subject: [PATCH 51/88] Fixed sawn-off inhand-right sprite (#1869) * Fixed * Dunno what that file was --- .../Guns/Shotguns/sawn.rsi/inhand-right.png | Bin 610 -> 598 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn.rsi/inhand-right.png index b40953313cbe73a26713725b26bfdbc2576cd512..eea5242eee2bfdbfbeabdf69cea218fc33ea9ca8 100644 GIT binary patch delta 573 zcmV-D0>b^`1l9zQB!3-AL_t(|ob8&uN(3GrI1FI#{KiZBuGi~D-WM9s)(A5Am4D8n(Qr%Z3tUVC17tbd zB;9T|aWvE5L2Fh(Q8En}U)}|HNJlUOtMhaEE%2C-Y5)S?-rAhMV|mFuqYQMuU-Sbt zjx4!}1`J1MVero{Z(=8Siu}+e>Yrc28f&yhKNT#DKxDALfd1c;@5CI1m@&i}Pm_bb++1unwLV zomajJ6VPB4Hh_QztFQqCG+2cVAfUl2YybfbR$&7OXn(K@8$dvVRoDOm8mz(w5YS*1 zZ3A2=ixol3fVKt?v{!(u1(G~?577BY*51`ru+ckE$9P67KurS_8q>;6brA1Ys0!m0 zPm~1vK*hqg-04&!wU)v zS1ev>cq~wUVOwM8QvL;;{7x@f7Hfp5%2X{X1ntQ}Ii%!?+guX@OL z!fR2((W2LS({7(yVu=ok|7%Y4o{D9&pD%2mf5+*c zw!`K0y#3cz4{v#K^e><6imr<8l`l7US1!57eCzVmEA!T=T+|H}0~+%7rI(kqy6b<@ zZ#lVT)l#SH)va#x@jiQfg?Z}KX1gNMosRL}jBEGopO_f6WTkVxU8nVe%UNkvOQs0A z<^?Rj+Vj^|MPb)37nLo&r+TK>>&(yCd^>pmb>6!I!sQ3vzg~1{@^_C}?j8-Rd`}qY zDy{l=%KhWB33kr{%8Ed4?(N0bTR;er>mdKI;Vst04M(vM*si- From 997d3dcdd471b3fb2f3720f4eda0ef7ca85373c7 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 25 Aug 2020 04:11:32 +1000 Subject: [PATCH 52/88] startingGear for NPCs (#1877) Need to cover up the lewds. Co-authored-by: Metal Gear Sloth --- Content.IntegrationTests/DummyGameTicker.cs | 6 +++++ .../Movement/AiControllerComponent.cs | 24 +++++++++++++++++++ Content.Server/GameTicking/GameTicker.cs | 9 +++++-- .../Interfaces/GameTicking/IGameTicker.cs | 4 ++++ .../Entities/Mobs/NPCs/dummy_npcs.yml | 1 + .../Prototypes/Entities/Mobs/NPCs/human.yml | 4 +++- 6 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Content.IntegrationTests/DummyGameTicker.cs b/Content.IntegrationTests/DummyGameTicker.cs index 5259c3de5c..e6b7e2a3ef 100644 --- a/Content.IntegrationTests/DummyGameTicker.cs +++ b/Content.IntegrationTests/DummyGameTicker.cs @@ -2,7 +2,9 @@ using System; using System.Collections.Generic; using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; +using Content.Shared.Roles; using Robust.Server.Interfaces.Player; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Map; using Robust.Shared.Timing; @@ -59,6 +61,10 @@ namespace Content.IntegrationTests public GridCoordinates GetLateJoinSpawnPoint() => GridCoordinates.InvalidGrid; public GridCoordinates GetJobSpawnPoint(string jobId) => GridCoordinates.InvalidGrid; public GridCoordinates GetObserverSpawnPoint() => GridCoordinates.InvalidGrid; + + public void EquipStartingGear(IEntity entity, StartingGearPrototype startingGear) + { + } public T AddGameRule() where T : GameRule, new() { diff --git a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs index e613b33f50..a1d595062d 100644 --- a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs @@ -1,12 +1,16 @@ #nullable enable using Content.Server.GameObjects.EntitySystems.AI; +using Content.Server.Interfaces.GameTicking; using Content.Shared.GameObjects.Components.Movement; +using Content.Shared.Roles; using Robust.Server.AI; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Systems; +using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -32,6 +36,9 @@ namespace Content.Server.GameObjects.Components.Movement } public AiLogicProcessor? Processor { get; set; } + + [ViewVariables(VVAccess.ReadWrite)] + public string? StartingGearPrototype { get; set; } [ViewVariables(VVAccess.ReadWrite)] public float VisionRadius @@ -51,12 +58,29 @@ namespace Content.Server.GameObjects.Components.Movement EntitySystem.Get().ProcessorInitialize(this); } + protected override void Startup() + { + base.Startup(); + + if (StartingGearPrototype != null) + { + var startingGear = IoCManager.Resolve().Index(StartingGearPrototype); + IoCManager.Resolve().EquipStartingGear(Owner, startingGear); + } + + } + /// public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); serializer.DataField(ref _logicName, "logic", null); + serializer.DataReadWriteFunction( + "startingGear", + null, + startingGear => StartingGearPrototype = startingGear, + () => StartingGearPrototype); serializer.DataField(ref _visionRadius, "vision", 8.0f); } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index a38aad78c2..0f3bf337a7 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -541,6 +541,13 @@ namespace Content.Server.GameTicking GridCoordinates coordinates = lateJoin ? GetLateJoinSpawnPoint() : GetJobSpawnPoint(job.Prototype.ID); var entity = _entityManager.SpawnEntity(PlayerPrototypeName, coordinates); var startingGear = _prototypeManager.Index(job.StartingGear); + EquipStartingGear(entity, startingGear); + + return entity; + } + + public void EquipStartingGear(IEntity entity, StartingGearPrototype startingGear) + { if (entity.TryGetComponent(out InventoryComponent inventory)) { var gear = startingGear.Equipment; @@ -561,8 +568,6 @@ namespace Content.Server.GameTicking handsComponent.PutInHand(inhandEntity.GetComponent(), hand); } } - - return entity; } private void ApplyCharacterProfile(IEntity entity, ICharacterProfile profile) diff --git a/Content.Server/Interfaces/GameTicking/IGameTicker.cs b/Content.Server/Interfaces/GameTicking/IGameTicker.cs index a4e045e2ee..8f218a4850 100644 --- a/Content.Server/Interfaces/GameTicking/IGameTicker.cs +++ b/Content.Server/Interfaces/GameTicking/IGameTicker.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using Content.Server.GameTicking; +using Content.Shared.Roles; using Robust.Server.Interfaces.Player; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Map; using Robust.Shared.Timing; @@ -33,6 +35,8 @@ namespace Content.Server.Interfaces.GameTicking GridCoordinates GetJobSpawnPoint(string jobId); GridCoordinates GetObserverSpawnPoint(); + void EquipStartingGear(IEntity entity, StartingGearPrototype startingGear); + // GameRule system. T AddGameRule() where T : GameRule, new(); bool HasGameRule(Type type); diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/dummy_npcs.yml b/Resources/Prototypes/Entities/Mobs/NPCs/dummy_npcs.yml index 60dab85b6f..31c801f5cc 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/dummy_npcs.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/dummy_npcs.yml @@ -5,6 +5,7 @@ id: HumanMob_PathDummy description: A miserable pile of secrets drawdepth: Mobs + suffix: AI components: - type: AiController logic: PathingDummy diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml index 37caacb018..6df6db7fab 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml @@ -5,20 +5,22 @@ id: HumanMob_Civilian description: A miserable pile of secrets drawdepth: Mobs + suffix: AI components: - type: AiController logic: Civilian + startingGear: AssistantGear - type: AiFactionTag factions: - NanoTransen - - type: entity save: false name: Spirate parent: BaseHumanMob_Content id: HumanMob_Spirate description: Yarr + suffix: AI components: - type: AiController logic: Spirate From 9019079d798c53d4eb4f14163c3f296030115b27 Mon Sep 17 00:00:00 2001 From: Swept Date: Mon, 24 Aug 2020 18:27:32 +0000 Subject: [PATCH 53/88] Adds a cat and then a gat (#1868) * Adds the cat * Adds the gat * Adds the calico to suspicion spawner * Fixed physics thing * Updated BB * Added Calico mag to ammo spawner --- .../Barrels/ServerMagazineBarrelComponent.cs | 1 + .../Markers/gamemode_conditional_spawners.yml | 2 + .../Prototypes/Entities/Mobs/NPCs/pets.yml | 36 ++++++++++++++++ .../Guns/Ammunition/Pistol/magazines.yml | 26 ++++++++++++ .../Objects/Weapons/Guns/Rifles/rifles.yml | 40 ++++++++++++++++++ .../Magazine/Pistol/calico_mag.rsi/base.png | Bin 0 -> 128 bytes .../Magazine/Pistol/calico_mag.rsi/icon.png | Bin 0 -> 128 bytes .../Magazine/Pistol/calico_mag.rsi/mag-1.png | Bin 0 -> 128 bytes .../Magazine/Pistol/calico_mag.rsi/meta.json | 23 ++++++++++ .../Weapons/Guns/Rifles/calico.rsi/base.png | Bin 0 -> 209 bytes .../Weapons/Guns/Rifles/calico.rsi/icon.png | Bin 0 -> 215 bytes .../Guns/Rifles/calico.rsi/inhand-left.png | Bin 0 -> 918 bytes .../Guns/Rifles/calico.rsi/inhand-right.png | Bin 0 -> 843 bytes .../Weapons/Guns/Rifles/calico.rsi/mag-0.png | Bin 0 -> 171 bytes .../Weapons/Guns/Rifles/calico.rsi/meta.json | 31 ++++++++++++++ 15 files changed, 159 insertions(+) create mode 100644 Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/base.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/icon.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/mag-1.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/meta.json create mode 100644 Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/base.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/icon.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/inhand-left.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/inhand-right.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/mag-0.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/meta.json diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs index ed7ec35452..cdda71816d 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs @@ -546,5 +546,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels Box = 1 << 7, Pan = 1 << 8, Dart = 1 << 9, // Placeholder + CalicoTopMounted = 1 << 10, } } diff --git a/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml b/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml index aa8e264353..f026ba3d01 100644 --- a/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml +++ b/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml @@ -41,6 +41,7 @@ - RifleSTS - RifleVintorez - RifleWintermute + - RifleCalico chance: 0.75 gameRules: - RuleSuspicion @@ -289,6 +290,7 @@ - MagazineClRifle10x24 - MagazineClRiflePistol - MagazineLRifle + - MagazinePistolCalicoTopMounted chance: 0.95 gameRules: - RuleSuspicion diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 37be27fe7b..b24b2d0d92 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -169,6 +169,42 @@ normal: cat dead: cat_dead +- type: entity + save: false + name: calico cat + parent: PetBaseMob_Content + id: CatCalicoMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Pets/cat.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: cat2 + - type: Icon + sprite: Mobs/Pets/cat.rsi + state: cat + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.50,-0.25,0.30,0.25" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: cat2 + dead: cat2_dead + - type: entity save: false name: sloth diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Pistol/magazines.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Pistol/magazines.yml index 2cd1763cd3..85bd3f04ed 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Pistol/magazines.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Pistol/magazines.yml @@ -103,6 +103,32 @@ steps: 6 zeroVisible: false +- type: entity + id: MagazinePistolCalicoTopMounted + name: Calico Magazine (.35 auto top-mounted) + parent: MagazinePistolSmgBase + components: + - type: RangedMagazine + caliber: Pistol + magazineType: CalicoTopMounted + fillPrototype: CartridgePistol + capacity: 100 + - type: Icon + sprite: Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi + - type: Sprite + sprite: Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi + layers: + - state: base + map: ["enum.RangedBarrelVisualLayers.Base"] + - state: mag-1 + shader: unshaded + - type: Appearance + visuals: + - type: MagVisualizer + magState: mag + steps: 1 + zeroVisible: false + - type: entity id: MagazinePistol name: magazine (.35 auto) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml index f5b6503ddd..1d144ed9e7 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml @@ -315,3 +315,43 @@ magState: mag steps: 1 zeroVisible: true + +- type: entity + name: calico m900 + parent: RifleBase + id: RifleCalico + description: A carbine with a unique cylindrical magazine design which allows for high capacity loads. + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Rifles/calico.rsi + layers: + - state: base + map: ["enum.RangedBarrelVisualLayers.Base"] + - state: mag-0 + map: ["enum.RangedBarrelVisualLayers.Mag"] + - type: Icon + sprite: Objects/Weapons/Guns/Rifles/calico.rsi + - type: Item + size: 24 + sprite: Objects/Weapons/Guns/Rifles/calico.rsi + - type: RangedWeapon + - type: MagazineBarrel + magFillPrototype: MagazinePistolCalicoTopMounted + caliber: Pistol + magazineTypes: + - CalicoTopMounted + fireRate: 3 + minAngle: 0 + maxAngle: 25 + angleIncrease: 15 + angleDecay: 25 + soundGunshot: /Audio/Weapons/Guns/Gunshots/rifle2.ogg + soundRack: /Audio/Weapons/Guns/Cock/ltrifle_cock.ogg + soundMagInsert: /Audio/Weapons/Guns/MagIn/ltrifle_magin.ogg + soundMagEject: /Audio/Weapons/Guns/MagOut/ltrifle_magout.ogg + - type: Appearance + visuals: + - type: MagVisualizer + magState: mag + steps: 1 + zeroVisible: true diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/base.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/base.png new file mode 100644 index 0000000000000000000000000000000000000000..c52c68456214b2135786b2739bf065c37c92daab GIT binary patch literal 128 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^*8>L*gapN#7(1w{nz$S} z?+6rUED7=pW^j0RBMrz=@^oMzWb7Ex3 XQDloyP}lPZs%G$X^>bP0l+XkKMTQ{3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/icon.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c52c68456214b2135786b2739bf065c37c92daab GIT binary patch literal 128 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^*8>L*gapN#7(1w{nz$S} z?+6rUED7=pW^j0RBMrz=@^oMzWb7Ex3 XQDloyP}lPZs%G$X^>bP0l+XkKMTQ{3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/mag-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/mag-1.png new file mode 100644 index 0000000000000000000000000000000000000000..c52c68456214b2135786b2739bf065c37c92daab GIT binary patch literal 128 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^*8>L*gapN#7(1w{nz$S} z?+6rUED7=pW^j0RBMrz=@^oMzWb7Ex3 XQDloyP}lPZs%G$X^>bP0l+XkKMTQ{3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/meta.json new file mode 100644 index 0000000000..e4f2abdee0 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/discordia-space/CEV-Eris/raw/aed9cbddbf9039dae1e4f02bab592248b0539431/icons/obj/ammo_mags.dmi", + "states": [ + { + "name": "icon", + "directions": 1 + }, + { + "name": "base", + "directions": 1 + }, + { + "name": "mag-1", + "directions": 1 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/base.png b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/base.png new file mode 100644 index 0000000000000000000000000000000000000000..6b5c09158c0ad6f8edba2af1694be30d905797a6 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvp#Yx{*8>L*gapN#7&{0F%BiZF zB*d0}YyABVD9cz9-6l55OQFl^IVz^h&I zKU74z_a)n2_BuN~_5%g8uJN(FWq9~6{wz-%xAf5+SF%ptW?Z-`ODru>P}}+f&z@Bx z)=O6$pD=yShi$pKf7!PDL*gapN#7&{0F%BiZF zB*d0}YyABVD9cz9*FcE_Zhy^BFhq%6XcOJ_ zUrS6{zL43UY3Xuvkq6-=OJ}`Qy>apRy|zOQr*%)r>`=-PIW%MQ+Pa(zX`#J`baaw3 z8DBN3Pfgja^40W-?z)Ji;=2(nXaBW@KG3Z3`WpXbS$tvo|09fNzw<4b)~&x6Xg!0c LtDnm{r-UW|D11?L literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..157e2a6d644915fd29cc0aab2ea5f151b2b56156 GIT binary patch literal 918 zcmV;H18Mw;P)fQ?d{u zRlkJ?PhwhJ2~(V%wxXNO#`f@DJYL>)-5_@btJzkk&kvA4e{mv769 zv(whlv6E1;Z9o{b{Ox&j>fedSf&SqbOc4*F`NZV3$=DIrR{#VS&JLTtIV11+elzK8 zN%7eK{QR%{-ERL@+wfc(0MF;6jr>;{k@uCY*Cuv`$w$X@ke&(cK-C;o4Vo2{;iW^ zarfRset58TWyyy-fiW%&U}%ExdknL7x8rL+0w5mx)pyn-^Gc;_%=WeoxH16Xm<-Vb zoWQ{|eha(F_K@(@{oWPUw?TEeUtIi?qCt%3%`%OwEudYH?M2vM9m_t}SHR^=20$W% zgoa5i+znXo8yu#z#p_;NBtw^v0K<%qV&Qs_FfdIKAGLM8`~es51bb0Ma8+c?edpq> zzew5-L$1*IFf@)8kZZ_*9K?_*WIzsL$P_Xl2Qg#{8IXe*GKCDtK@6Ef2IL@yOd$hu z5JRSr0Xc{vQwnc@>n}x(c)j*MRrlTchM2FeXnoyh)c~c?!L%;G2sl^)6Lh}wbW$H+ zSQik>Zhd{^*vhF=EKYI&9BR5U0mQ@DXhLoCi_vRXH2^C*;7Hts2`Cs0$d0Djvp$Ybd@LbQaNQ{L~)T_AVf>I6|i` zAPMu;RSqWL8Dm&G0I#E+@dshs0F%0aSm0P)DV&fiZfWJE96QOZt2n6;Nm3V}%jU-C zzQT#MH}8zZ1I{W$&;&T|w{g;o;$e$}Bxfs7mv4nRp86`gL8=`gNPw4x6Q4eQ5IE~5 zvk#2ptuP-}7Z69V?1S2Y!-FkZeX5RQDjpcuX91q(F;EL9a9@Yj06T)Mz^oeZ>*&X* sei_@%07*qoM6N<$f=f2EdjJ3c literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..1b179d55a0eaa15af69e397b8c003598c8ccf730 GIT binary patch literal 843 zcmV-R1GM~!P)&=4h$j<``d4@~{wE$xJdqgv52R^GyqT7W3WY>X zR@;EyWzBcCZ=IdpZnx}gXBOTkA*E$@`u)C{dCTTK!7vQNFbu;m48xp5JXn74@Oh=3 zZ@1nA7cb3NzQ{#jo@Dj=Kjcmzpnp^l5&=-nciQje1b3D< z#Gl_i=j<;v)?;TL8oT=+qxaB1DhLZ(eHf^!XxYr{G^lE0j=g{Fg0D5XQ|WP630ir zc=Y(0oK*cH7XtzF^Ygdl`&M&P_&_nG-HbROUUIH18Q8~_J2ScL=NfCj5@036U@6%K#{8mz(ra6p4qH~ms0+8~-Is19#B2vo=*^JVOULPr3}5&Z*X970Xk z6ZKvNl(|jK4HEqUqfmDOnkGU)OH!ckcV$0;Dbjis(rWxgD6|8R3s8qg2MGjH9p2O` z>Aec3&4aObKpNCU@$T(QWB)JwFKu_FdA%XZ6omqSEp literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/mag-0.png b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/mag-0.png new file mode 100644 index 0000000000000000000000000000000000000000..e37aa9a0e8ab82a210bf10b4d71ae51adceb903c GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJB2O2`kcie~3D(64B0Wul{1P8o z4>U5fODyx5_1|7yUvRyZKtVx)g0@a1hi1ix4~ov7g@@~Fvh3=99dbBfyn`XmSM3 Date: Mon, 24 Aug 2020 20:41:15 +0200 Subject: [PATCH 54/88] The disposals pushing and pulling update (#1875) * Add collision to disposal unit and pipes Make disposal unit and pipes pullable Implement proper handling of collisions in disposals * Implement IsExiting Move DisposalSystem to shared * Change SharedDisosalUnitComponent to call manager.ContainsEntity directly * Update saltern.yml Co-authored-by: Julian Giebel --- .../Disposal/DisposalUnitComponent.cs | 1 + .../Disposal/DisposalTubeComponent.cs | 5 +- .../Disposal/DisposalUnitComponent.cs | 16 +- .../EntitySystems/DisposalUnitSystem.cs | 18 - .../Disposal/SharedDisposalUnitComponent.cs | 53 +- .../EntitySystems/SharedDisposalUnitSystem.cs | 18 + Resources/Maps/saltern.yml | 779 ++++++------------ .../Entities/Constructible/disposal.yml | 38 +- 8 files changed, 374 insertions(+), 554 deletions(-) delete mode 100644 Content.Server/GameObjects/EntitySystems/DisposalUnitSystem.cs create mode 100644 Content.Shared/GameObjects/EntitySystems/SharedDisposalUnitSystem.cs diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalUnitComponent.cs b/Content.Client/GameObjects/Components/Disposal/DisposalUnitComponent.cs index 1058b3044a..1e28af762d 100644 --- a/Content.Client/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Client/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -4,6 +4,7 @@ using Robust.Shared.GameObjects; namespace Content.Client.GameObjects.Components.Disposal { [RegisterComponent] + [ComponentReference(typeof(SharedDisposalUnitComponent))] public class DisposalUnitComponent : SharedDisposalUnitComponent { } diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs index 3326b4fd0e..5055312f56 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs @@ -24,7 +24,6 @@ using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Disposal { - // TODO: Make unanchored pipes pullable public abstract class DisposalTubeComponent : Component, IDisposalTubeComponent, IBreakAct { [Dependency] private readonly IGameTiming _gameTiming = default!; @@ -182,6 +181,8 @@ namespace Content.Server.GameObjects.Components.Disposal return; } + collidable.CanCollide = !collidable.Anchored; + if (collidable.Anchored) { OnAnchor(); @@ -220,6 +221,8 @@ namespace Content.Server.GameObjects.Components.Disposal var collidable = Owner.EnsureComponent(); collidable.AnchoredChanged += AnchoredChanged; + + collidable.CanCollide = !collidable.Anchored; } protected override void Startup() diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs index bb16efdbdf..97e6bccf97 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -29,6 +29,7 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Localization; +using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; using Timer = Robust.Shared.Timers.Timer; @@ -36,6 +37,7 @@ using Timer = Robust.Shared.Timers.Timer; namespace Content.Server.GameObjects.Components.Disposal { [RegisterComponent] + [ComponentReference(typeof(SharedDisposalUnitComponent))] [ComponentReference(typeof(IInteractUsing))] public class DisposalUnitComponent : SharedDisposalUnitComponent, IInteractHand, IInteractUsing, IDragDropOn { @@ -429,8 +431,9 @@ namespace Content.Server.GameObjects.Components.Disposal : LightState.Ready); } - public void Update(float frameTime) + public override void Update(float frameTime) { + base.Update(frameTime); if (!Powered) { return; @@ -512,10 +515,15 @@ namespace Content.Server.GameObjects.Components.Disposal { base.Startup(); - Owner.EnsureComponent(); + if(!Owner.HasComponent()) + { + Logger.WarningS("VitalComponentMissing", $"Disposal unit {Owner.Uid} is missing an anchorable component"); + } - var collidable = Owner.EnsureComponent(); - collidable.AnchoredChanged += UpdateVisualState; + if (Owner.TryGetComponent(out CollidableComponent? collidable)) + { + collidable.AnchoredChanged += UpdateVisualState; + } if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) { diff --git a/Content.Server/GameObjects/EntitySystems/DisposalUnitSystem.cs b/Content.Server/GameObjects/EntitySystems/DisposalUnitSystem.cs deleted file mode 100644 index ecac19f19e..0000000000 --- a/Content.Server/GameObjects/EntitySystems/DisposalUnitSystem.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Server.GameObjects.Components.Disposal; -using JetBrains.Annotations; -using Robust.Shared.GameObjects.Systems; - -namespace Content.Server.GameObjects.EntitySystems -{ - [UsedImplicitly] - internal sealed class DisposalUnitSystem : EntitySystem - { - public override void Update(float frameTime) - { - foreach (var comp in ComponentManager.EntityQuery()) - { - comp.Update(frameTime); - } - } - } -} diff --git a/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs index d223b1a605..1638c7c7cc 100644 --- a/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs +++ b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs @@ -1,14 +1,25 @@ using System; +using System.Collections.Generic; +using System.Linq; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.IoC; +using Robust.Shared.Physics; using Robust.Shared.Serialization; namespace Content.Shared.GameObjects.Components.Disposal { - public abstract class SharedDisposalUnitComponent : Component + public abstract class SharedDisposalUnitComponent : Component, ICollideSpecial { + [Dependency] private readonly IEntityManager _entityManager = default!; + public override string Name => "DisposalUnit"; + private readonly List _intersecting = new List(); + [Serializable, NetSerializable] public enum Visuals { @@ -57,6 +68,46 @@ namespace Content.Shared.GameObjects.Components.Disposal Pressurizing } + bool ICollideSpecial.PreventCollide(IPhysBody collided) + { + if (IsExiting(collided.Entity)) return true; + if (!Owner.TryGetComponent(out IContainerManager manager)) return false; + + if (manager.ContainsEntity(collided.Entity)) + { + if (!_intersecting.Contains(collided.Entity)) + { + _intersecting.Add(collided.Entity); + } + return true; + } + return false; + } + + public virtual void Update(float frameTime) + { + UpdateIntersecting(); + } + + private bool IsExiting(IEntity entity) + { + return _intersecting.Contains(entity); + } + + private void UpdateIntersecting() + { + if(_intersecting.Count == 0) return; + + var intersectingEntities = _entityManager.GetEntitiesIntersecting(Owner); + for (var i = _intersecting.Count - 1; i >= 0; i--) + { + if (!intersectingEntities.Contains(_intersecting[i])) + { + _intersecting.RemoveAt(i); + } + } + } + [Serializable, NetSerializable] public class DisposalUnitBoundUserInterfaceState : BoundUserInterfaceState, IEquatable { diff --git a/Content.Shared/GameObjects/EntitySystems/SharedDisposalUnitSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedDisposalUnitSystem.cs new file mode 100644 index 0000000000..c019620ef0 --- /dev/null +++ b/Content.Shared/GameObjects/EntitySystems/SharedDisposalUnitSystem.cs @@ -0,0 +1,18 @@ +using Content.Shared.GameObjects.Components.Disposal; +using JetBrains.Annotations; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Shared.GameObjects.EntitySystems +{ + [UsedImplicitly] + public sealed class SharedDisposalUnitSystem : EntitySystem + { + public override void Update(float frameTime) + { + foreach (var comp in ComponentManager.EntityQuery()) + { + comp.Update(frameTime); + } + } + } +} diff --git a/Resources/Maps/saltern.yml b/Resources/Maps/saltern.yml index f3ccd2b9b1..146f642c62 100644 --- a/Resources/Maps/saltern.yml +++ b/Resources/Maps/saltern.yml @@ -1,4 +1,4 @@ -meta: +meta: format: 2 name: DemoStation author: Space-Wizards @@ -139,8 +139,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 1 type: DisposalBend components: @@ -152,8 +151,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2 type: DisposalBend components: @@ -165,8 +163,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3 type: DisposalBend components: @@ -177,8 +174,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 4 type: DisposalPipe components: @@ -190,8 +186,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 5 type: DisposalPipe components: @@ -203,8 +198,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 6 type: DisposalTrunk components: @@ -216,8 +210,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 7 type: DisposalUnit components: @@ -225,8 +218,7 @@ entities: pos: -5.5,-14.5 rot: -1.5707963267948966 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -242,8 +234,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 9 type: DisposalPipe components: @@ -254,8 +245,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 10 type: DisposalPipe components: @@ -266,8 +256,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 11 type: DisposalPipe components: @@ -278,8 +267,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 12 type: DisposalPipe components: @@ -290,8 +278,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 13 type: DisposalPipe components: @@ -302,8 +289,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 14 type: DisposalPipe components: @@ -315,8 +301,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 15 components: - name: Saltern Station @@ -22347,8 +22332,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 1596 type: VendingMachineSovietSoda components: @@ -27782,8 +27766,7 @@ entities: - parent: 15 pos: 18.5,-0.5 type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -27799,8 +27782,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2220 type: LargeBeaker components: @@ -33976,8 +33958,7 @@ entities: pos: -10.5,-17.5 rot: -1.5707963267948966 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -33993,8 +33974,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2951 type: DisposalPipe components: @@ -34006,8 +33986,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2952 type: DisposalPipe components: @@ -34019,8 +33998,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2953 type: DisposalUnit components: @@ -34028,8 +34006,7 @@ entities: pos: -27.5,-8.5 rot: 1.5707963267948966 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -34045,8 +34022,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2955 type: DisposalPipe components: @@ -34058,8 +34034,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2956 type: DisposalPipe components: @@ -34071,8 +34046,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2957 type: DisposalPipe components: @@ -34084,8 +34058,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2958 type: DisposalBend components: @@ -34097,8 +34070,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2959 type: DisposalPipe components: @@ -34110,8 +34082,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2960 type: DisposalPipe components: @@ -34123,8 +34094,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2961 type: DisposalPipe components: @@ -34136,8 +34106,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2962 type: DisposalPipe components: @@ -34149,8 +34118,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2963 type: DisposalPipe components: @@ -34162,8 +34130,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2964 type: DisposalPipe components: @@ -34175,8 +34142,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2965 type: DisposalPipe components: @@ -34188,8 +34154,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2966 type: DisposalPipe components: @@ -34201,8 +34166,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2967 type: DisposalPipe components: @@ -34214,8 +34178,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2968 type: DisposalPipe components: @@ -34227,8 +34190,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2969 type: DisposalPipe components: @@ -34240,8 +34202,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2970 type: DisposalPipe components: @@ -34253,8 +34214,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2971 type: DisposalPipe components: @@ -34266,8 +34226,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2972 type: DisposalPipe components: @@ -34279,8 +34238,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2973 type: DisposalPipe components: @@ -34292,8 +34250,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2974 type: DisposalPipe components: @@ -34305,8 +34262,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2975 type: DisposalPipe components: @@ -34318,8 +34274,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2976 type: DisposalPipe components: @@ -34331,8 +34286,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2977 type: DisposalPipe components: @@ -34344,8 +34298,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2978 type: DisposalPipe components: @@ -34357,8 +34310,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2979 type: DisposalPipe components: @@ -34370,8 +34322,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2980 type: DisposalPipe components: @@ -34383,8 +34334,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2981 type: DisposalUnit components: @@ -34392,8 +34342,7 @@ entities: pos: -22.5,6.5 rot: 3.141592653589793 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -34409,8 +34358,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2983 type: DisposalPipe components: @@ -34422,8 +34370,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2984 type: DisposalPipe components: @@ -34435,8 +34382,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2985 type: DisposalPipe components: @@ -34448,8 +34394,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2986 type: DisposalPipe components: @@ -34461,8 +34406,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2987 type: DisposalPipe components: @@ -34474,8 +34418,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2988 type: DisposalPipe components: @@ -34487,8 +34430,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2989 type: DisposalPipe components: @@ -34500,8 +34442,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2990 type: DisposalPipe components: @@ -34513,8 +34454,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2991 type: DisposalUnit components: @@ -34522,8 +34462,7 @@ entities: pos: -12.5,1.5 rot: -1.5707963267948966 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -34539,8 +34478,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2993 type: DisposalPipe components: @@ -34552,8 +34490,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2994 type: DisposalPipe components: @@ -34565,8 +34502,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2995 type: DisposalPipe components: @@ -34578,8 +34514,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2996 type: DisposalPipe components: @@ -34591,8 +34526,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2997 type: DisposalPipe components: @@ -34604,8 +34538,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2998 type: DisposalPipe components: @@ -34617,8 +34550,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 2999 type: DisposalPipe components: @@ -34630,8 +34562,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3000 type: DisposalPipe components: @@ -34643,8 +34574,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3001 type: DisposalPipe components: @@ -34656,8 +34586,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3002 type: DisposalPipe components: @@ -34669,8 +34598,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3003 type: DisposalPipe components: @@ -34682,8 +34610,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3004 type: DisposalPipe components: @@ -34695,8 +34622,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3005 type: DisposalPipe components: @@ -34708,8 +34634,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3006 type: DisposalBend components: @@ -34721,8 +34646,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3007 type: DisposalTrunk components: @@ -34734,8 +34658,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3008 type: DisposalPipe components: @@ -34747,8 +34670,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3009 type: DisposalPipe components: @@ -34760,8 +34682,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3010 type: DisposalBend components: @@ -34772,8 +34693,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3011 type: DisposalPipe components: @@ -34785,8 +34705,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3012 type: DisposalPipe components: @@ -34798,8 +34717,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3013 type: DisposalPipe components: @@ -34810,8 +34728,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3014 type: DisposalTrunk components: @@ -34823,8 +34740,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3015 type: DisposalPipe components: @@ -34836,8 +34752,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3016 type: DisposalPipe components: @@ -34848,8 +34763,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3017 type: DisposalTrunk components: @@ -34861,8 +34775,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3018 type: DisposalPipe components: @@ -34874,8 +34787,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3019 type: DisposalUnit components: @@ -34883,8 +34795,7 @@ entities: pos: 0.5,1.5 rot: -1.5707963267948966 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -34900,8 +34811,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3021 type: DisposalPipe components: @@ -34912,8 +34822,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3022 type: DisposalPipe components: @@ -34925,8 +34834,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3023 type: DisposalPipe components: @@ -34938,8 +34846,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3024 type: DisposalPipe components: @@ -34951,8 +34858,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3025 type: DisposalPipe components: @@ -34964,8 +34870,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3026 type: DisposalPipe components: @@ -34977,8 +34882,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3027 type: DisposalPipe components: @@ -34990,8 +34894,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3028 type: DisposalPipe components: @@ -35003,8 +34906,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3029 type: DisposalPipe components: @@ -35016,8 +34918,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3030 type: DisposalPipe components: @@ -35029,8 +34930,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3031 type: DisposalPipe components: @@ -35042,8 +34942,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3032 type: DisposalPipe components: @@ -35055,8 +34954,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3033 type: DisposalPipe components: @@ -35068,8 +34966,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3034 type: DisposalPipe components: @@ -35081,8 +34978,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3035 type: DisposalPipe components: @@ -35094,8 +34990,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3036 type: DisposalPipe components: @@ -35107,8 +35002,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3037 type: DisposalPipe components: @@ -35120,8 +35014,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3038 type: DisposalPipe components: @@ -35133,8 +35026,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3039 type: DisposalPipe components: @@ -35146,8 +35038,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3040 type: DisposalPipe components: @@ -35159,8 +35050,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3041 type: DisposalPipe components: @@ -35172,8 +35062,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3042 type: DisposalPipe components: @@ -35185,8 +35074,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3043 type: DisposalPipe components: @@ -35198,8 +35086,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3044 type: DisposalPipe components: @@ -35211,8 +35098,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3045 type: DisposalPipe components: @@ -35224,8 +35110,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3046 type: DisposalPipe components: @@ -35237,8 +35122,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3047 type: DisposalPipe components: @@ -35250,8 +35134,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3048 type: DisposalPipe components: @@ -35263,8 +35146,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3049 type: DisposalPipe components: @@ -35276,8 +35158,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3050 type: DisposalUnit components: @@ -35285,8 +35166,7 @@ entities: pos: 12.5,-2.5 rot: 3.141592653589793 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -35302,8 +35182,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3052 type: ConveyorBelt components: @@ -35321,8 +35200,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3054 type: DisposalPipe components: @@ -35334,8 +35212,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3055 type: DisposalPipe components: @@ -35347,8 +35224,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3056 type: DisposalPipe components: @@ -35359,8 +35235,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3057 type: DisposalUnit components: @@ -35368,8 +35243,7 @@ entities: pos: 11.5,-11.5 rot: 3.141592653589793 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -35385,8 +35259,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3059 type: DisposalPipe components: @@ -35398,8 +35271,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3060 type: DisposalPipe components: @@ -35411,8 +35283,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3061 type: DisposalPipe components: @@ -35424,8 +35295,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3062 type: DisposalPipe components: @@ -35437,8 +35307,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3063 type: DisposalPipe components: @@ -35450,8 +35319,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3064 type: DisposalPipe components: @@ -35463,8 +35331,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3065 type: DisposalPipe components: @@ -35476,8 +35343,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3066 type: DisposalBend components: @@ -35489,8 +35355,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3067 type: DisposalPipe components: @@ -35502,8 +35367,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3068 type: DisposalPipe components: @@ -35515,8 +35379,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3069 type: DisposalPipe components: @@ -35528,8 +35391,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3070 type: DisposalPipe components: @@ -35541,8 +35403,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3071 type: DisposalPipe components: @@ -35554,8 +35415,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3072 type: DisposalPipe components: @@ -35567,8 +35427,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3073 type: DisposalPipe components: @@ -35580,8 +35439,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3074 type: DisposalPipe components: @@ -35593,8 +35451,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3075 type: DisposalPipe components: @@ -35606,8 +35463,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3076 type: DisposalPipe components: @@ -35619,8 +35475,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3077 type: DisposalPipe components: @@ -35632,8 +35487,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3078 type: DisposalPipe components: @@ -35645,8 +35499,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3079 type: DisposalPipe components: @@ -35658,8 +35511,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3080 type: DisposalPipe components: @@ -35670,8 +35522,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3081 type: DisposalUnit components: @@ -35679,8 +35530,7 @@ entities: pos: -2.5,29.5 rot: 3.141592653589793 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -35695,8 +35545,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3083 type: DisposalPipe components: @@ -35707,8 +35556,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3084 type: DisposalPipe components: @@ -35719,8 +35567,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3085 type: DisposalPipe components: @@ -35731,8 +35578,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3086 type: DisposalPipe components: @@ -35743,8 +35589,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3087 type: DisposalPipe components: @@ -35756,8 +35601,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3088 type: DisposalPipe components: @@ -35769,8 +35613,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3089 type: DisposalPipe components: @@ -35782,8 +35625,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3090 type: DisposalPipe components: @@ -35795,8 +35637,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3091 type: DisposalPipe components: @@ -35808,8 +35649,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3092 type: DisposalPipe components: @@ -35821,8 +35661,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3093 type: DisposalPipe components: @@ -35834,8 +35673,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3094 type: DisposalPipe components: @@ -35847,8 +35685,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3095 type: DisposalPipe components: @@ -35860,8 +35697,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3096 type: DisposalPipe components: @@ -35873,8 +35709,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3097 type: DisposalPipe components: @@ -35886,8 +35721,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3098 type: DisposalPipe components: @@ -35899,8 +35733,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3099 type: DisposalPipe components: @@ -35911,8 +35744,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3100 type: DisposalPipe components: @@ -35924,8 +35756,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3101 type: DisposalPipe components: @@ -35937,8 +35768,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3102 type: DisposalPipe components: @@ -35950,8 +35780,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3103 type: DisposalPipe components: @@ -35963,8 +35792,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3104 type: DisposalPipe components: @@ -35976,8 +35804,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3105 type: DisposalPipe components: @@ -35989,8 +35816,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3106 type: DisposalPipe components: @@ -36002,8 +35828,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3107 type: DisposalPipe components: @@ -36015,8 +35840,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3108 type: DisposalPipe components: @@ -36028,8 +35852,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3109 type: DisposalPipe components: @@ -36041,8 +35864,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3110 type: DisposalPipe components: @@ -36054,8 +35876,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3111 type: DisposalPipe components: @@ -36067,8 +35888,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3112 type: DisposalBend components: @@ -36080,8 +35900,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3113 type: DisposalUnit components: @@ -36089,8 +35908,7 @@ entities: pos: 37.5,6.5 rot: -1.5707963267948966 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -36106,8 +35924,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3115 type: DisposalPipe components: @@ -36119,8 +35936,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3116 type: DisposalPipe components: @@ -36132,8 +35948,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3117 type: DisposalBend components: @@ -36145,8 +35960,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3118 type: DisposalPipe components: @@ -36158,8 +35972,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3119 type: DisposalPipe components: @@ -36171,8 +35984,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3120 type: DisposalPipe components: @@ -36184,8 +35996,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3121 type: DisposalPipe components: @@ -36197,8 +36008,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3122 type: DisposalPipe components: @@ -36210,8 +36020,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3123 type: DisposalPipe components: @@ -36223,8 +36032,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3124 type: DisposalPipe components: @@ -36236,8 +36044,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3125 type: DisposalUnit components: @@ -36245,8 +36052,7 @@ entities: pos: 34.5,-4.5 rot: 3.141592653589793 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -36262,8 +36068,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3127 type: DisposalPipe components: @@ -36275,8 +36080,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3128 type: DisposalPipe components: @@ -36288,8 +36092,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3129 type: DisposalPipe components: @@ -36301,8 +36104,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3130 type: DisposalPipe components: @@ -36314,8 +36116,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3131 type: DisposalPipe components: @@ -36327,8 +36128,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3132 type: DisposalPipe components: @@ -36340,8 +36140,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3133 type: DisposalPipe components: @@ -36353,8 +36152,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3134 type: Recycler components: @@ -36372,8 +36170,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3136 type: DisposalPipe components: @@ -36384,8 +36181,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3137 type: DisposalPipe components: @@ -36396,8 +36192,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3138 type: DisposalPipe components: @@ -36408,8 +36203,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3139 type: DisposalUnit components: @@ -36417,8 +36211,7 @@ entities: pos: 24.5,8.5 rot: -1.5707963267948966 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -36434,8 +36227,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3141 type: DisposalPipe components: @@ -36447,8 +36239,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3142 type: DisposalPipe components: @@ -36460,8 +36251,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3143 type: DisposalPipe components: @@ -36473,8 +36263,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3144 type: DisposalPipe components: @@ -36486,8 +36275,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3145 type: DisposalUnit components: @@ -36495,8 +36283,7 @@ entities: pos: 6.5,11.5 rot: 3.141592653589793 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -36512,8 +36299,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3147 type: DisposalPipe components: @@ -36525,8 +36311,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3148 type: DisposalPipe components: @@ -36538,8 +36323,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3149 type: DisposalPipe components: @@ -36551,8 +36335,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3150 type: DisposalUnit components: @@ -36560,8 +36343,7 @@ entities: pos: -5.5,17.5 rot: -1.5707963267948966 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -36576,8 +36358,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3152 type: DisposalPipe components: @@ -36588,8 +36369,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3153 type: DisposalPipe components: @@ -36600,8 +36380,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3154 type: DisposalPipe components: @@ -36612,8 +36391,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3155 type: DisposalPipe components: @@ -36624,8 +36402,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3156 type: DisposalPipe components: @@ -36636,8 +36413,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3157 type: DisposalPipe components: @@ -36648,8 +36424,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3158 type: DisposalPipe components: @@ -36661,8 +36436,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3159 type: DisposalBend components: @@ -36674,8 +36448,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3160 type: DisposalUnit components: @@ -36683,8 +36456,7 @@ entities: pos: -29.5,11.5 rot: -1.5707963267948966 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -36707,8 +36479,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3163 type: DisposalPipe components: @@ -36720,8 +36491,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3164 type: DisposalPipe components: @@ -36733,8 +36503,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3165 type: DisposalPipe components: @@ -36746,8 +36515,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3166 type: DisposalPipe components: @@ -36759,8 +36527,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3167 type: DisposalPipe components: @@ -36771,8 +36538,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3168 type: DisposalUnit components: @@ -36780,8 +36546,7 @@ entities: pos: -17.5,-22.5 rot: 3.141592653589793 rad type: Transform - - shapes: [] - type: Collidable + - containers: DisposalUnit: type: Robust.Server.GameObjects.Components.Container.Container @@ -36797,8 +36562,7 @@ entities: DisposalEntry: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3170 type: DisposalPipe components: @@ -36810,8 +36574,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3171 type: DisposalPipe components: @@ -36823,8 +36586,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3172 type: DisposalPipe components: @@ -36836,8 +36598,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3173 type: DisposalPipe components: @@ -36849,8 +36610,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3174 type: DisposalJunctionFlipped components: @@ -36862,8 +36622,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3175 type: DisposalJunction components: @@ -36875,8 +36634,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3176 type: DisposalJunctionFlipped components: @@ -36888,8 +36646,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3177 type: DisposalJunctionFlipped components: @@ -36901,8 +36658,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3178 type: DisposalJunction components: @@ -36914,8 +36670,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3179 type: DisposalJunction components: @@ -36927,8 +36682,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3180 type: DisposalJunctionFlipped components: @@ -36939,8 +36693,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3181 type: DisposalYJunction components: @@ -36952,8 +36705,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3182 type: DisposalJunction components: @@ -36965,8 +36717,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3183 type: DisposalJunctionFlipped components: @@ -36978,8 +36729,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3184 type: DisposalJunctionFlipped components: @@ -36991,8 +36741,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3185 type: DisposalJunction components: @@ -37004,8 +36753,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3186 type: DisposalJunctionFlipped components: @@ -37017,8 +36765,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3187 type: DisposalJunction components: @@ -37030,8 +36777,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3188 type: DisposalJunction components: @@ -37043,8 +36789,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3189 type: DisposalJunctionFlipped components: @@ -37056,8 +36801,7 @@ entities: DisposalJunction: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3190 type: ConveyorSwitch components: @@ -37078,8 +36822,7 @@ entities: DisposalTransit: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3192 type: DisposalBend components: @@ -37091,8 +36834,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3193 type: DisposalBend components: @@ -37103,8 +36845,7 @@ entities: DisposalBend: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - - shapes: [] - type: Collidable + - uid: 3194 type: Beaker components: diff --git a/Resources/Prototypes/Entities/Constructible/disposal.yml b/Resources/Prototypes/Entities/Constructible/disposal.yml index 1d85dd33a6..d9e67582e7 100644 --- a/Resources/Prototypes/Entities/Constructible/disposal.yml +++ b/Resources/Prototypes/Entities/Constructible/disposal.yml @@ -8,13 +8,28 @@ components: - type: Clickable - type: InteractionOutline - - type: Physics + - type: Collidable anchored: true + shapes: + - !type:PhysShapeAabb + bounds: "-0.3,-0.3,0.3,0.3" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable - type: SnapGrid offset: Center - type: Anchorable - type: Breakable - type: Rotatable + - type: Pullable - type: entity id: DisposalHolder @@ -117,22 +132,22 @@ flushTime: 2 - type: Clickable - type: InteractionOutline - - type: Physics - anchored: true - shapes: - - !type:PhysShapeAabb - bounds: "-0.3,-0.3,0.3,0.3" - layer: - - Impassable - - MobImpassable - type: Collidable anchored: true shapes: - !type:PhysShapeAabb - bounds: "-0.3,-0.3,0.3,0.3" - layer: + bounds: "-0.35,-0.3,0.35,0.3" + mask: - Impassable - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable - type: SnapGrid offset: Center - type: Anchorable @@ -155,6 +170,7 @@ interfaces: - key: enum.DisposalUnitUiKey.Key type: DisposalUnitBoundUserInterface + - type: Pullable - type: entity id: DisposalRouter From 520e523d30940d8110b7338b8a4e67acff887a48 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Mon, 24 Aug 2020 20:47:17 +0200 Subject: [PATCH 55/88] Refactor UserInterface properties to use a helper (#1896) --- .../Components/Access/IdCardConsoleComponent.cs | 8 ++------ .../Components/Atmos/GasAnalyzerComponent.cs | 8 ++------ .../Components/Body/BodyScannerComponent.cs | 8 ++------ .../Components/Body/DroppedBodyPartComponent.cs | 8 ++------ .../Components/Body/DroppedMechanismComponent.cs | 8 ++------ .../Components/Body/SurgeryToolComponent.cs | 8 ++------ .../Components/Cargo/CargoConsoleComponent.cs | 8 ++------ .../Components/Chemistry/ChemMasterComponent.cs | 8 ++------ .../Chemistry/ReagentDispenserComponent.cs | 8 ++------ .../Command/CommunicationsConsoleComponent.cs | 8 ++------ .../Components/Disposal/DisposalRouterComponent.cs | 8 ++------ .../Components/Disposal/DisposalTaggerComponent.cs | 8 ++------ .../Components/Disposal/DisposalUnitComponent.cs | 14 +++++--------- .../Components/GUI/StrippableComponent.cs | 8 ++------ .../Gravity/GravityGeneratorComponent.cs | 8 +++----- .../Components/Instruments/InstrumentComponent.cs | 8 ++------ .../Components/Kitchen/MicrowaveComponent.cs | 8 ++------ .../GameObjects/Components/MagicMirrorComponent.cs | 8 ++------ .../Components/Medical/MedicalScannerComponent.cs | 8 ++------ .../GameObjects/Components/PDA/PDAComponent.cs | 8 ++------ .../GameObjects/Components/Paper/PaperComponent.cs | 8 +++----- .../Power/ApcNetComponents/ApcComponent.cs | 8 ++------ .../SolarControlConsoleComponent.cs | 8 ++------ .../Components/Research/LatheComponent.cs | 8 ++------ .../Components/Research/ResearchClientComponent.cs | 7 ++----- .../Research/ResearchConsoleComponent.cs | 8 ++------ .../VendingMachines/VendingMachineComponent.cs | 8 +++----- .../GameObjects/Components/WiresComponent.cs | 7 ++----- Content.Server/Utility/UserInterfaceHelpers.cs | 14 ++++++++++++++ 29 files changed, 76 insertions(+), 166 deletions(-) create mode 100644 Content.Server/Utility/UserInterfaceHelpers.cs diff --git a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs index 058a501b03..1183e461b2 100644 --- a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs @@ -4,6 +4,7 @@ using System.Linq; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Server.Utility; using Content.Shared.Access; using Content.Shared.GameObjects.Components.Access; using Content.Shared.Interfaces.GameObjects.Components; @@ -30,12 +31,7 @@ namespace Content.Server.GameObjects.Components.Access private ContainerSlot _privilegedIdContainer = default!; private ContainerSlot _targetIdContainer = default!; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(IdCardConsoleUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(IdCardConsoleUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs b/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs index 511b26df1c..a8d61d1f41 100644 --- a/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Server.Utility; using Content.Shared.Atmos; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.EntitySystems; @@ -32,12 +33,7 @@ namespace Content.Server.GameObjects.Components.Atmos private bool _checkPlayer = false; // Check at the player pos or at some other tile? private GridCoordinates? _position; // The tile that we scanned - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(GasAnalyzerUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GasAnalyzerUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs index be4d6c99c9..dfc1cb5198 100644 --- a/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs +++ b/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Generic; using Content.Server.Body; +using Content.Server.Utility; using Content.Shared.Body.Scanner; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects.Components.UserInterface; @@ -17,12 +18,7 @@ namespace Content.Server.GameObjects.Components.Body { public sealed override string Name => "BodyScanner"; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(BodyScannerUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(BodyScannerUiKey.Key); void IActivate.Activate(ActivateEventArgs eventArgs) { diff --git a/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs b/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs index cce6566879..f7fb29c6e7 100644 --- a/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs +++ b/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs @@ -4,6 +4,7 @@ using System.Linq; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Content.Server.Body; +using Content.Server.Utility; using Content.Shared.Body.Surgery; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.UserInterface; @@ -34,12 +35,7 @@ namespace Content.Server.GameObjects.Components.Body [ViewVariables] public BodyPart ContainedBodyPart { get; private set; } = default!; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(GenericSurgeryUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key); void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { diff --git a/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs b/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs index 92af831132..939222ed2e 100644 --- a/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs +++ b/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Content.Server.Body; using Content.Server.Body.Mechanisms; +using Content.Server.Utility; using Content.Shared.Body.Mechanism; using Content.Shared.Body.Surgery; using Content.Shared.Interfaces; @@ -42,12 +43,7 @@ namespace Content.Server.GameObjects.Components.Body [ViewVariables] public Mechanism ContainedMechanism { get; private set; } = default!; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(GenericSurgeryUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key); void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { diff --git a/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs b/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs index 9f55f87a49..0c937b5a12 100644 --- a/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs +++ b/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Content.Server.Body; using Content.Server.Body.Mechanisms; using Content.Server.Body.Surgery; +using Content.Server.Utility; using Content.Shared.Body.Surgery; using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.Body; @@ -52,12 +53,7 @@ namespace Content.Server.GameObjects.Components.Body private SurgeryType _surgeryType; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(GenericSurgeryUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key); void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { diff --git a/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs b/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs index e0149dc1c0..9f630edd1a 100644 --- a/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs @@ -2,6 +2,7 @@ using Content.Server.Cargo; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.Cargo; using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Prototypes.Cargo; @@ -59,12 +60,7 @@ namespace Content.Server.GameObjects.Components.Cargo private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; private CargoConsoleSystem _cargoConsoleSystem = default!; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(CargoConsoleUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CargoConsoleUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs index 14a6413a4c..c3a7dc4764 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs @@ -9,6 +9,7 @@ using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Server.Utility; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Chemistry.ChemMaster; using Content.Shared.GameObjects.EntitySystems; @@ -55,12 +56,7 @@ namespace Content.Server.GameObjects.Components.Chemistry private readonly SolutionComponent BufferSolution = new SolutionComponent(); - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(ChemMasterUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ChemMasterUiKey.Key); /// /// Shows the serializer how to save/load this components yaml prototype. diff --git a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs index f161403c82..3ec235caf7 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs @@ -8,6 +8,7 @@ using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Server.Utility; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser; using Content.Shared.GameObjects.EntitySystems; @@ -52,12 +53,7 @@ namespace Content.Server.GameObjects.Components.Chemistry private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(ReagentDispenserUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ReagentDispenserUiKey.Key); /// /// Shows the serializer how to save/load this components yaml prototype. diff --git a/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs index 4a26ee6a4a..09cd7c3137 100644 --- a/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs @@ -1,6 +1,7 @@ #nullable enable using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.Command; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects.Components.UserInterface; @@ -23,12 +24,7 @@ namespace Content.Server.GameObjects.Components.Command private RoundEndSystem RoundEndSystem => _entitySystemManager.GetEntitySystem(); - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(CommunicationsConsoleUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CommunicationsConsoleUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs index 9c85698e77..6cf6689e75 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs @@ -17,6 +17,7 @@ using Robust.Shared.Maths; using Robust.Shared.ViewVariables; using System; using System.Collections.Generic; +using Content.Server.Utility; using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent; namespace Content.Server.GameObjects.Components.Disposal @@ -37,12 +38,7 @@ namespace Content.Server.GameObjects.Components.Disposal !Owner.TryGetComponent(out ICollidableComponent? collidable) || collidable.Anchored; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(DisposalRouterUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalRouterUiKey.Key); public override Direction NextDirection(DisposalHolderComponent holder) { diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs index 34bb94e19b..76d913e2cf 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs @@ -1,6 +1,7 @@ #nullable enable using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Server.Utility; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects.Components.UserInterface; @@ -35,12 +36,7 @@ namespace Content.Server.GameObjects.Components.Disposal !Owner.TryGetComponent(out CollidableComponent? collidable) || collidable.Anchored; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(DisposalTaggerUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalTaggerUiKey.Key); public override Direction NextDirection(DisposalHolderComponent holder) { diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs index 97e6bccf97..521615f4b9 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -9,6 +9,7 @@ using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Disposal; using Content.Shared.GameObjects.EntitySystems; @@ -112,15 +113,10 @@ namespace Content.Server.GameObjects.Components.Disposal } } - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(DisposalUnitUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalUnitUiKey.Key); private DisposalUnitBoundUserInterfaceState? _lastUiState; - + /// /// Store the translated state. /// @@ -296,7 +292,7 @@ namespace Content.Server.GameObjects.Components.Disposal private DisposalUnitBoundUserInterfaceState GetInterfaceState() { string stateString; - + if (_locState.State != State) { stateString = Loc.GetString($"{State}"); @@ -306,7 +302,7 @@ namespace Content.Server.GameObjects.Components.Disposal { stateString = _locState.Localized; } - + return new DisposalUnitBoundUserInterfaceState(Owner.Name, stateString, _pressure, Powered, Engaged); } diff --git a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs index 484a60dd53..ec8ba05e42 100644 --- a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs @@ -4,6 +4,7 @@ using System.Threading; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Server.Interfaces; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.GUI; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; @@ -27,12 +28,7 @@ namespace Content.Server.GameObjects.Components.GUI public const float StripDelay = 2f; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(StrippingUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(StrippingUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs index 7aa3a9df81..3a766f985a 100644 --- a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs +++ b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs @@ -4,6 +4,7 @@ using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.Interfaces; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.Gravity; using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.GameObjects.EntitySystems; @@ -16,6 +17,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Gravity { @@ -59,11 +61,7 @@ namespace Content.Server.GameObjects.Components.Gravity public override string Name => "GravityGenerator"; - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(GravityGeneratorUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GravityGeneratorUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index 833db0d87b..931b95288a 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -4,6 +4,7 @@ using System.Linq; using Content.Server.GameObjects.Components.Mobs; using Content.Server.Interfaces; using Content.Server.Mobs; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.Instruments; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; @@ -106,12 +107,7 @@ namespace Content.Server.GameObjects.Components.Instruments } } - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(InstrumentUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(InstrumentUiKey.Key); private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) { diff --git a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs index a3e049510c..21ef4a521a 100644 --- a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs +++ b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs @@ -15,6 +15,7 @@ using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameObjects; +using Content.Server.Utility; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Power; using Content.Shared.Interfaces; @@ -73,12 +74,7 @@ namespace Content.Server.GameObjects.Components.Kitchen private AudioSystem _audioSystem = default!; private Container _storage = default!; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(MicrowaveUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(MicrowaveUiKey.Key); public override void ExposeData(ObjectSerializer serializer) { diff --git a/Content.Server/GameObjects/Components/MagicMirrorComponent.cs b/Content.Server/GameObjects/Components/MagicMirrorComponent.cs index c03126cf9f..48aaadd613 100644 --- a/Content.Server/GameObjects/Components/MagicMirrorComponent.cs +++ b/Content.Server/GameObjects/Components/MagicMirrorComponent.cs @@ -1,5 +1,6 @@ #nullable enable using Content.Server.GameObjects.Components.Mobs; +using Content.Server.Utility; using Content.Shared.GameObjects.Components; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; @@ -17,12 +18,7 @@ namespace Content.Server.GameObjects.Components [ComponentReference(typeof(IActivate))] public class MagicMirrorComponent : SharedMagicMirrorComponent, IActivate { - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(MagicMirrorUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(MagicMirrorUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs index d16d821e78..cb2a11fb40 100644 --- a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs +++ b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Medical; using Content.Shared.GameObjects.EntitySystems; @@ -32,12 +33,7 @@ namespace Content.Server.GameObjects.Components.Medical [ViewVariables] private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(MedicalScannerUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(MedicalScannerUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs index e3e4f810cf..fc87d0ddf5 100644 --- a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs +++ b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs @@ -9,6 +9,7 @@ using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.Interfaces; using Content.Server.Interfaces.PDA; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.PDA; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.Verbs; @@ -48,12 +49,7 @@ namespace Content.Server.GameObjects.Components.PDA [ViewVariables] private readonly PdaAccessSet _accessSet; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(PDAUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(PDAUiKey.Key); public PDAComponent() { diff --git a/Content.Server/GameObjects/Components/Paper/PaperComponent.cs b/Content.Server/GameObjects/Components/Paper/PaperComponent.cs index b3b9a8a459..ecae29f0a7 100644 --- a/Content.Server/GameObjects/Components/Paper/PaperComponent.cs +++ b/Content.Server/GameObjects/Components/Paper/PaperComponent.cs @@ -1,5 +1,6 @@ #nullable enable using System.Threading.Tasks; +using Content.Server.Utility; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; @@ -8,6 +9,7 @@ using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Paper { @@ -17,11 +19,7 @@ namespace Content.Server.GameObjects.Components.Paper private string _content = ""; private PaperAction _mode; - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(PaperUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(PaperUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs index 7643db7112..fdb9d14133 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs @@ -2,6 +2,7 @@ using System; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Server.GameObjects.Components.Power.PowerNetComponents; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.Power; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; @@ -45,12 +46,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents private const int VisualsChangeDelay = 1; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(ApcUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ApcUiKey.Key); public BatteryComponent? Battery => Owner.TryGetComponent(out BatteryComponent? batteryComponent) ? batteryComponent : null; diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarControlConsoleComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarControlConsoleComponent.cs index 1670cb23dc..6eefdc32af 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarControlConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarControlConsoleComponent.cs @@ -1,6 +1,7 @@ #nullable enable using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.Power; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects.Components.UserInterface; @@ -21,12 +22,7 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents private PowerSolarSystem _powerSolarSystem = default!; private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(SolarControlConsoleUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(SolarControlConsoleUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/Research/LatheComponent.cs b/Content.Server/GameObjects/Components/Research/LatheComponent.cs index 987f6846c9..be55677be9 100644 --- a/Content.Server/GameObjects/Components/Research/LatheComponent.cs +++ b/Content.Server/GameObjects/Components/Research/LatheComponent.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.Components.Stack; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.Materials; using Content.Shared.GameObjects.Components.Power; using Content.Shared.GameObjects.Components.Research; @@ -45,12 +46,7 @@ namespace Content.Server.GameObjects.Components.Research private static readonly TimeSpan InsertionTime = TimeSpan.FromSeconds(0.9f); - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(LatheUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(LatheUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs b/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs index fb5de72bd0..44a5069dcf 100644 --- a/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs +++ b/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs @@ -1,5 +1,6 @@ #nullable enable using Content.Server.GameObjects.EntitySystems; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.Research; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects.Components.UserInterface; @@ -18,11 +19,7 @@ namespace Content.Server.GameObjects.Components.Research [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; // TODO: Create GUI for changing RD server. - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(ResearchClientUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ResearchClientUiKey.Key); public bool ConnectedToServer => Server != null; diff --git a/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs b/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs index 4c7aa4a468..4a1adc57c1 100644 --- a/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs @@ -1,5 +1,6 @@ #nullable enable using Content.Server.GameObjects.Components.Power.ApcNetComponents; +using Content.Server.Utility; using Content.Shared.Audio; using Content.Shared.GameObjects.Components.Research; using Content.Shared.Interfaces.GameObjects.Components; @@ -30,12 +31,7 @@ namespace Content.Server.GameObjects.Components.Research private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; - [ViewVariables] - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(ResearchConsoleUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ResearchConsoleUiKey.Key); public override void Initialize() { diff --git a/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs b/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs index 2e1d47a898..3fd2c7177a 100644 --- a/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs +++ b/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Power.ApcNetComponents; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.VendingMachines; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; @@ -21,6 +22,7 @@ using Robust.Shared.Random; using Robust.Shared.Serialization; using Robust.Shared.Timers; using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; using static Content.Shared.GameObjects.Components.SharedWiresComponent; namespace Content.Server.GameObjects.Components.VendingMachines @@ -42,11 +44,7 @@ namespace Content.Server.GameObjects.Components.VendingMachines private string _soundVend = ""; - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(VendingMachineUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(VendingMachineUiKey.Key); public void Activate(ActivateEventArgs eventArgs) { diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs index bd0276ab62..50ea153a2d 100644 --- a/Content.Server/GameObjects/Components/WiresComponent.cs +++ b/Content.Server/GameObjects/Components/WiresComponent.cs @@ -8,6 +8,7 @@ using Content.Server.GameObjects.Components.VendingMachines; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Server.Utility; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.GameObjects.EntitySystems; @@ -140,11 +141,7 @@ namespace Content.Server.GameObjects.Components [ViewVariables] private string? _layoutId; - private BoundUserInterface? UserInterface => - Owner.TryGetComponent(out ServerUserInterfaceComponent? ui) && - ui.TryGetBoundUserInterface(WiresUiKey.Key, out var boundUi) - ? boundUi - : null; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(WiresUiKey.Key); public override void Initialize() { diff --git a/Content.Server/Utility/UserInterfaceHelpers.cs b/Content.Server/Utility/UserInterfaceHelpers.cs new file mode 100644 index 0000000000..2c87ebdfc5 --- /dev/null +++ b/Content.Server/Utility/UserInterfaceHelpers.cs @@ -0,0 +1,14 @@ +#nullable enable +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.Utility +{ + public static class UserInterfaceHelpers + { + public static BoundUserInterface? GetUIOrNull(this IEntity entity, object uiKey) + { + return entity.GetComponentOrNull()?.GetBoundUserInterfaceOrNull(uiKey); + } + } +} From b5a68748eab7c481de9b2e795cdb5709608b6d9d Mon Sep 17 00:00:00 2001 From: Exp Date: Tue, 25 Aug 2020 12:44:06 +0200 Subject: [PATCH 56/88] Fix strip menu not closing properly (#1908) --- .../Components/HUD/Inventory/StrippableBoundUserInterface.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs b/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs index afd87e0e06..2b4f518501 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs @@ -28,6 +28,8 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory base.Open(); _strippingMenu = new StrippingMenu($"{Owner.Owner.Name}'s inventory"); + + _strippingMenu.OnClose += Close; _strippingMenu.OpenCentered(); UpdateMenu(); } From 318f051fb9fc611b9629d20fcd30979003c5c8b6 Mon Sep 17 00:00:00 2001 From: Visne <39844191+Visne@users.noreply.github.com> Date: Tue, 25 Aug 2020 13:37:21 +0200 Subject: [PATCH 57/88] Allow all door access in Suspicion mode (#1817) * Add AccessTypes, let RuleSuspicion change it * Fix enum description Co-authored-by: DrSmugleaf * Move check to CanOpen() Co-authored-by: DrSmugleaf --- .../Components/Doors/ServerDoorComponent.cs | 50 ++++++++++++++----- .../GameObjects/EntitySystems/DoorSystem.cs | 34 +++++++++++++ .../GameTicking/GameRules/RuleSuspicion.cs | 6 ++- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs index d4579037db..1a0aeb7b21 100644 --- a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs @@ -6,6 +6,7 @@ using Content.Server.GameObjects.Components.Access; using Content.Server.GameObjects.Components.Atmos; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.EntitySystems; using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Damage; @@ -134,28 +135,53 @@ namespace Content.Server.GameObjects.Components.Doors public bool CanOpen(IEntity user) { if (!CanOpen()) return false; - if (!Owner.TryGetComponent(out AccessReader? accessReader)) + + if (!Owner.TryGetComponent(out var accessReader)) { return true; } - return accessReader.IsAllowed(user); + var doorSystem = EntitySystem.Get(); + var isAirlockExternal = HasAccessType("External"); + + return doorSystem.AccessType switch + { + DoorSystem.AccessTypes.AllowAll => true, + DoorSystem.AccessTypes.AllowAllIdExternal => isAirlockExternal ? accessReader.IsAllowed(user) : true, + DoorSystem.AccessTypes.AllowAllNoExternal => !isAirlockExternal, + _ => accessReader.IsAllowed(user) + }; + } + + /// + /// Returns whether a door has a certain access type. For example, maintenance doors will have access type + /// "Maintenance" in their AccessReader. + /// + private bool HasAccessType(string accesType) + { + if(Owner.TryGetComponent(out var accessReader)) + { + return accessReader.AccessLists.Any(list => list.Contains(accesType)); + } + + return true; } public void TryOpen(IEntity user) { - if (!CanOpen(user)) + if (CanOpen(user)) + { + Open(); + + if (user.TryGetComponent(out HandsComponent? hands) && hands.Count == 0) + { + EntitySystem.Get().PlayFromEntity("/Audio/Effects/bang.ogg", Owner, + AudioParams.Default.WithVolume(-2)); + } + } + else { Deny(); - return; - } - - Open(); - - if (user.TryGetComponent(out HandsComponent? hands) && hands.Count == 0) - { - EntitySystem.Get().PlayFromEntity("/Audio/Effects/bang.ogg", Owner, - AudioParams.Default.WithVolume(-2)); } } diff --git a/Content.Server/GameObjects/EntitySystems/DoorSystem.cs b/Content.Server/GameObjects/EntitySystems/DoorSystem.cs index fb5d0fe432..8feb00ca24 100644 --- a/Content.Server/GameObjects/EntitySystems/DoorSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/DoorSystem.cs @@ -1,10 +1,44 @@ using Content.Server.GameObjects.Components.Doors; +using JetBrains.Annotations; using Robust.Shared.GameObjects.Systems; namespace Content.Server.GameObjects.EntitySystems { + [UsedImplicitly] class DoorSystem : EntitySystem { + /// + /// Determines the base access behavior of all doors on the station. + /// + public AccessTypes AccessType { get; set; } + + /// + /// How door access should be handled. + /// + public enum AccessTypes + { + /// ID based door access. + Id, + /// + /// Allows everyone to open doors, except external which airlocks are still handled with ID's + /// + AllowAllIdExternal, + /// + /// Allows everyone to open doors, except external airlocks which are never allowed, even if the user has + /// ID access. + /// + AllowAllNoExternal, + /// Allows everyone to open all doors. + AllowAll + } + + public override void Initialize() + { + base.Initialize(); + + AccessType = AccessTypes.Id; + } + /// public override void Update(float frameTime) { diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs index f004eed7d4..7a870375bd 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -1,9 +1,9 @@ using System; using System.Threading; using Content.Server.GameObjects.Components.Suspicion; +using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; -using Content.Server.Mobs; using Content.Server.Mobs.Roles; using Content.Server.Players; using Content.Shared.GameObjects.Components.Damage; @@ -38,6 +38,8 @@ namespace Content.Server.GameTicking.GameRules EntitySystem.Get().PlayGlobal("/Audio/Misc/tatoralert.ogg", AudioParams.Default, (session) => session.ContentData().Mind?.HasRole() ?? false); + EntitySystem.Get().AccessType = DoorSystem.AccessTypes.AllowAllNoExternal; + Timer.SpawnRepeating(DeadCheckDelay, _checkWinConditions, _checkTimerCancel.Token); } @@ -45,6 +47,8 @@ namespace Content.Server.GameTicking.GameRules { base.Removed(); + EntitySystem.Get().AccessType = DoorSystem.AccessTypes.Id; + _checkTimerCancel.Cancel(); } From a854a8687217a9d21d2cb1f20f9695a9cb796326 Mon Sep 17 00:00:00 2001 From: NuclearWinter Date: Tue, 25 Aug 2020 05:37:54 -0600 Subject: [PATCH 58/88] Fixes ghost NRE in lobby (#1907) Simply tells the player "You can't ghost here!" when they try it in the lobby, works for both normal ghost and aghost --- Content.Server/Administration/AGhost.cs | 6 ++++++ Content.Server/Observer/Ghost.cs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Content.Server/Administration/AGhost.cs b/Content.Server/Administration/AGhost.cs index dfe516576b..5226c4637f 100644 --- a/Content.Server/Administration/AGhost.cs +++ b/Content.Server/Administration/AGhost.cs @@ -22,6 +22,12 @@ namespace Content.Server.Administration } var mind = player.ContentData().Mind; + if (mind == null) + { + shell.SendText(player, "You can't ghost here!"); + return; + } + if (mind.VisitingEntity != null && mind.VisitingEntity.Prototype.ID == "AdminObserver") { var visiting = mind.VisitingEntity; diff --git a/Content.Server/Observer/Ghost.cs b/Content.Server/Observer/Ghost.cs index 6c71882c9e..e1ea953bd6 100644 --- a/Content.Server/Observer/Ghost.cs +++ b/Content.Server/Observer/Ghost.cs @@ -28,6 +28,12 @@ namespace Content.Server.Observer } var mind = player.ContentData().Mind; + if (mind == null) + { + shell.SendText(player, "You can't ghost here!"); + return; + } + var canReturn = player.AttachedEntity != null && CanReturn; var name = player.AttachedEntity?.Name ?? player.Name; From 6b56297c6992cd8c763c5b7377a3b5056e3a0d64 Mon Sep 17 00:00:00 2001 From: Exp Date: Tue, 25 Aug 2020 13:45:00 +0200 Subject: [PATCH 59/88] Adds Vault Verb (#1889) * -Vault Verb -Made CanVault a method so it can be called from several places * Notifymanager bad * Unnecessary Import --- .../Components/Movement/ClimbableComponent.cs | 191 +++++++++++------- 1 file changed, 122 insertions(+), 69 deletions(-) diff --git a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs index 22d9e8a775..2fce1dc185 100644 --- a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs @@ -1,23 +1,22 @@ - +using Content.Server.GameObjects.Components.Body; +using Content.Server.GameObjects.EntitySystems.DoAfter; +using Content.Shared.GameObjects.Components.Movement; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Verbs; +using Content.Shared.Interfaces; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; +using Robust.Shared.Log; +using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; -using Robust.Server.Interfaces.Player; -using Content.Server.Interfaces; -using Content.Shared.GameObjects.EntitySystems; -using Content.Shared.Interfaces.GameObjects.Components; -using Content.Shared.GameObjects.Components.Movement; -using Content.Shared.Interfaces; -using Content.Server.GameObjects.Components.Body; -using Content.Server.GameObjects.EntitySystems.DoAfter; -using Robust.Shared.Maths; using System; -using Robust.Shared.Log; namespace Content.Server.GameObjects.Components.Movement { @@ -25,7 +24,6 @@ namespace Content.Server.GameObjects.Components.Movement [ComponentReference(typeof(IClimbable))] public class ClimbableComponent : SharedClimbableComponent, IDragDropOn { - [Dependency] private readonly IServerNotifyManager _notifyManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; /// @@ -64,68 +62,101 @@ namespace Content.Server.GameObjects.Components.Movement bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs) { - if (!ActionBlockerSystem.CanInteract(eventArgs.User)) - { - _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!")); + string reason; + bool canVault; + if (eventArgs.User == eventArgs.Dropped) + canVault = CanVault(eventArgs.User, eventArgs.Target, out reason); + else + canVault = CanVault(eventArgs.User, eventArgs.Dropped, eventArgs.Target, out reason); + + if (!canVault) + eventArgs.User.PopupMessage(eventArgs.User, reason); + + return canVault; + } + + /// + /// Checks if the user can vault the target + /// + /// The entity that wants to vault + /// The object that is being vaulted + /// The reason why it cant be dropped + /// + private bool CanVault(IEntity user, IEntity target, out string reason) + { + if (!ActionBlockerSystem.CanInteract(user)) + { + reason = Loc.GetString("You can't do that!"); return false; } - if (eventArgs.User == eventArgs.Dropped) // user is dragging themselves onto a climbable + if (!user.HasComponent()) { - if (!eventArgs.User.HasComponent()) - { - _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are incapable of climbing!")); - - return false; - } - - var bodyManager = eventArgs.User.GetComponent(); - - if (bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 || - bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0) - { - _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are unable to climb!")); - - return false; - } - - var userPosition = eventArgs.User.Transform.MapPosition; - var climbablePosition = eventArgs.Target.Transform.MapPosition; - var interaction = EntitySystem.Get(); - bool Ignored(IEntity entity) => (entity == eventArgs.Target || entity == eventArgs.User); - - if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored)) - { - _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!")); - - return false; - } - } - else // user is dragging some other entity onto a climbable - { - if (eventArgs.Target == null || !eventArgs.Dropped.HasComponent()) - { - _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!")); - - return false; - } - - var userPosition = eventArgs.User.Transform.MapPosition; - var otherUserPosition = eventArgs.Dropped.Transform.MapPosition; - var climbablePosition = eventArgs.Target.Transform.MapPosition; - var interaction = EntitySystem.Get(); - bool Ignored(IEntity entity) => (entity == eventArgs.Target || entity == eventArgs.User || entity == eventArgs.Dropped); - - if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored) || - !interaction.InRangeUnobstructed(userPosition, otherUserPosition, _range, predicate: Ignored)) - { - _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!")); - - return false; - } + reason = Loc.GetString("You are incapable of climbing!"); + return false; } + var bodyManager = user.GetComponent(); + + if (bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 || + bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0) + { + reason = Loc.GetString("You are unable to climb!"); + return false; + } + + var userPosition = user.Transform.MapPosition; + var climbablePosition = target.Transform.MapPosition; + var interaction = EntitySystem.Get(); + bool Ignored(IEntity entity) => (entity == target || entity == user); + + if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored)) + { + reason = Loc.GetString("You can't reach there!"); + return false; + } + + reason = string.Empty; + return true; + } + + /// + /// Checks if the user can vault the dragged entity onto the the target + /// + /// The user that wants to vault the entity + /// The entity that is being vaulted + /// The object that is being vaulted onto + /// The reason why it cant be dropped + /// + private bool CanVault(IEntity user, IEntity dragged, IEntity target, out string reason) + { + if (!ActionBlockerSystem.CanInteract(user)) + { + reason = Loc.GetString("You can't do that!"); + return false; + } + + if (target == null || !dragged.HasComponent()) + { + reason = Loc.GetString("You can't do that!"); + return false; + } + + var userPosition = user.Transform.MapPosition; + var otherUserPosition = dragged.Transform.MapPosition; + var climbablePosition = target.Transform.MapPosition; + var interaction = EntitySystem.Get(); + bool Ignored(IEntity entity) => (entity == target || entity == user || entity == dragged); + + if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored) || + !interaction.InRangeUnobstructed(userPosition, otherUserPosition, _range, predicate: Ignored)) + { + reason = Loc.GetString("You can't reach there!"); + return false; + } + + reason = string.Empty; return true; } @@ -177,7 +208,7 @@ namespace Content.Server.GameObjects.Components.Movement // there's also the cases where the user might collide with the person they are forcing onto the climbable that i haven't accounted for PopupMessageOtherClientsInRange(user, Loc.GetString("{0:theName} forces {1:theName} onto {2:theName}!", user, entityToMove, Owner), 15); - _notifyManager.PopupMessage(user, user, Loc.GetString("You force {0:theName} onto {1:theName}!", entityToMove, Owner)); + user.PopupMessage(user, Loc.GetString("You force {0:theName} onto {1:theName}!", entityToMove, Owner)); } } @@ -213,7 +244,7 @@ namespace Content.Server.GameObjects.Components.Movement climbMode.TryMoveTo(user.Transform.WorldPosition, endPoint); PopupMessageOtherClientsInRange(user, Loc.GetString("{0:theName} jumps onto {1:theName}!", user, Owner), 15); - _notifyManager.PopupMessage(user, user, Loc.GetString("You jump onto {0:theName}!", Owner)); + user.PopupMessage(user, Loc.GetString("You jump onto {0:theName}!", Owner)); } } @@ -233,5 +264,27 @@ namespace Content.Server.GameObjects.Components.Movement source.PopupMessage(viewer.AttachedEntity, message); } } + + /// + /// Allows you to vault an object with the ClimbableComponent through right click + /// + [Verb] + private sealed class ClimbVerb : Verb + { + protected override void GetData(IEntity user, ClimbableComponent component, VerbData data) + { + if (!component.CanVault(user, component.Owner, out var _)) + { + data.Visibility = VerbVisibility.Invisible; + } + + data.Text = Loc.GetString("Vault"); + } + + protected override void Activate(IEntity user, ClimbableComponent component) + { + component.TryClimb(user); + } + } } } From a62935dab2e17b4a00d9b4b5ec63d694075eb794 Mon Sep 17 00:00:00 2001 From: nuke <47336974+nuke-makes-games@users.noreply.github.com> Date: Tue, 25 Aug 2020 08:54:23 -0400 Subject: [PATCH 60/88] Handcuff system (#1831) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implemented most serverside logic * All serverside cuff logic complete * SFX, Clientside HUD stuff, Other logic. * fffff * Cuffs 1.0 * missing loc string * Cuffs are stored in the balls now. * Basic integrationtest * Support stripping menu. * rrr * Fixes * properties * gun emoji * fixes * get rid of unused * reeee * Update Content.Shared/GameObjects/ContentNetIDs.cs Co-authored-by: Víctor Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> --- .../ActionBlocking/CuffableComponent.cs | 58 +++ .../ActionBlocking/HandcuffComponent.cs | 27 ++ .../Inventory/StrippableBoundUserInterface.cs | 23 +- .../Components/Items/HandsComponent.cs | 3 +- .../Components/Items/ItemComponent.cs | 8 +- .../Mobs/HumanoidAppearanceComponent.cs | 12 +- .../Components/ActionBlocking/CuffUnitTest.cs | 93 +++++ Content.Server/Body/BodyCommands.cs | 4 +- .../ActionBlocking/CuffableComponent.cs | 351 ++++++++++++++++++ .../ActionBlocking/HandcuffComponent.cs | 248 +++++++++++++ .../Components/GUI/StrippableComponent.cs | 81 +++- .../Items/Clothing/ClothingComponent.cs | 1 - .../Components/Movement/ClimbableComponent.cs | 2 +- .../GameObjects/EntitySystems/CuffSystem.cs | 18 + .../Components/Items/IHandsComponent.cs | 3 - .../ActionBlocking/SharedCuffableComponent.cs | 49 +++ .../ActionBlocking/SharedHandcuffComponent.cs | 23 ++ .../GUI/SharedStrippableComponent.cs | 15 +- .../Mobs/SharedStatusEffectsComponent.cs | 3 +- Content.Shared/GameObjects/ContentNetIDs.cs | 2 + .../Appearance/HumanoidCharacterAppearance.cs | 3 +- .../Items/Handcuffs/cuff_breakout_start.ogg | Bin 0 -> 134486 bytes Resources/Audio/Items/Handcuffs/cuff_end.ogg | Bin 0 -> 23010 bytes .../Audio/Items/Handcuffs/cuff_start.ogg | Bin 0 -> 48902 bytes .../Items/Handcuffs/cuff_takeoff_end.ogg | Bin 0 -> 43735 bytes .../Items/Handcuffs/cuff_takeoff_start.ogg | Bin 0 -> 51053 bytes .../Audio/Items/Handcuffs/rope_breakout.ogg | Bin 0 -> 16522 bytes Resources/Audio/Items/Handcuffs/rope_end.ogg | Bin 0 -> 13806 bytes .../Audio/Items/Handcuffs/rope_start.ogg | Bin 0 -> 48070 bytes .../Audio/Items/Handcuffs/rope_takeoff.ogg | Bin 0 -> 75225 bytes .../Entities/Mobs/Species/human.yml | 5 + .../Entities/Objects/Misc/handcuffs.yml | 35 +- .../StatusEffects/Handcuffed/Handcuffed.png | Bin 0 -> 3286 bytes .../StatusEffects/Handcuffed/Uncuffed.png | Bin 0 -> 97 bytes .../Misc/cablecuffs.rsi/body-overlay-2.png | Bin 0 -> 3046 bytes .../Misc/cablecuffs.rsi/body-overlay-4.png | Bin 0 -> 3101 bytes .../Misc/cablecuffs.rsi/cuff-broken.png | Bin 0 -> 3104 bytes .../Objects/Misc/cablecuffs.rsi/cuff.png | Bin 333 -> 3094 bytes .../Misc/cablecuffs.rsi/inhand-left.png | Bin 204 -> 3009 bytes .../Misc/cablecuffs.rsi/inhand-right.png | Bin 209 -> 3096 bytes .../Objects/Misc/cablecuffs.rsi/meta.json | 45 +++ .../Misc/handcuffs.rsi/body-overlay-2.png | Bin 0 -> 258 bytes .../Misc/handcuffs.rsi/body-overlay-4.png | Bin 0 -> 3059 bytes .../Objects/Misc/handcuffs.rsi/meta.json | 9 +- 44 files changed, 1085 insertions(+), 36 deletions(-) create mode 100644 Content.Client/GameObjects/Components/ActionBlocking/CuffableComponent.cs create mode 100644 Content.Client/GameObjects/Components/ActionBlocking/HandcuffComponent.cs create mode 100644 Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs create mode 100644 Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs create mode 100644 Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs create mode 100644 Content.Server/GameObjects/EntitySystems/CuffSystem.cs create mode 100644 Content.Shared/GameObjects/Components/ActionBlocking/SharedCuffableComponent.cs create mode 100644 Content.Shared/GameObjects/Components/ActionBlocking/SharedHandcuffComponent.cs create mode 100644 Resources/Audio/Items/Handcuffs/cuff_breakout_start.ogg create mode 100644 Resources/Audio/Items/Handcuffs/cuff_end.ogg create mode 100644 Resources/Audio/Items/Handcuffs/cuff_start.ogg create mode 100644 Resources/Audio/Items/Handcuffs/cuff_takeoff_end.ogg create mode 100644 Resources/Audio/Items/Handcuffs/cuff_takeoff_start.ogg create mode 100644 Resources/Audio/Items/Handcuffs/rope_breakout.ogg create mode 100644 Resources/Audio/Items/Handcuffs/rope_end.ogg create mode 100644 Resources/Audio/Items/Handcuffs/rope_start.ogg create mode 100644 Resources/Audio/Items/Handcuffs/rope_takeoff.ogg create mode 100644 Resources/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png create mode 100644 Resources/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png create mode 100644 Resources/Textures/Objects/Misc/cablecuffs.rsi/body-overlay-2.png create mode 100644 Resources/Textures/Objects/Misc/cablecuffs.rsi/body-overlay-4.png create mode 100644 Resources/Textures/Objects/Misc/cablecuffs.rsi/cuff-broken.png create mode 100644 Resources/Textures/Objects/Misc/handcuffs.rsi/body-overlay-2.png create mode 100644 Resources/Textures/Objects/Misc/handcuffs.rsi/body-overlay-4.png diff --git a/Content.Client/GameObjects/Components/ActionBlocking/CuffableComponent.cs b/Content.Client/GameObjects/Components/ActionBlocking/CuffableComponent.cs new file mode 100644 index 0000000000..344e2d0c2c --- /dev/null +++ b/Content.Client/GameObjects/Components/ActionBlocking/CuffableComponent.cs @@ -0,0 +1,58 @@ +using Robust.Client.Graphics; +using Robust.Client.Interfaces.ResourceManagement; +using Robust.Shared.IoC; +using Robust.Shared.GameObjects; +using Content.Shared.GameObjects.Components.ActionBlocking; +using Content.Shared.Preferences.Appearance; +using Robust.Client.GameObjects; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; + +namespace Content.Client.GameObjects.Components.ActionBlocking +{ + [RegisterComponent] + public class CuffableComponent : SharedCuffableComponent + { + [ViewVariables] + private string _currentRSI = default; + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + if (!(curState is CuffableComponentState cuffState)) + { + return; + } + + CanStillInteract = cuffState.CanStillInteract; + + if (Owner.TryGetComponent(out var sprite)) + { + sprite.LayerSetVisible(HumanoidVisualLayers.Handcuffs, cuffState.NumHandsCuffed > 0); + sprite.LayerSetColor(HumanoidVisualLayers.Handcuffs, cuffState.Color); + + if (cuffState.NumHandsCuffed > 0) + { + if (_currentRSI != cuffState.RSI) // we don't want to keep loading the same RSI + { + _currentRSI = cuffState.RSI; + sprite.LayerSetState(HumanoidVisualLayers.Handcuffs, new RSI.StateId(cuffState.IconState), new ResourcePath(cuffState.RSI)); + } + else + { + sprite.LayerSetState(HumanoidVisualLayers.Handcuffs, new RSI.StateId(cuffState.IconState)); // TODO: safety check to see if RSI contains the state? + } + } + } + } + + public override void OnRemove() + { + base.OnRemove(); + + if (Owner.TryGetComponent(out var sprite)) + { + sprite.LayerSetVisible(HumanoidVisualLayers.Handcuffs, false); + } + } + } +} diff --git a/Content.Client/GameObjects/Components/ActionBlocking/HandcuffComponent.cs b/Content.Client/GameObjects/Components/ActionBlocking/HandcuffComponent.cs new file mode 100644 index 0000000000..62c2204cec --- /dev/null +++ b/Content.Client/GameObjects/Components/ActionBlocking/HandcuffComponent.cs @@ -0,0 +1,27 @@ +using Robust.Shared.GameObjects; +using Content.Shared.GameObjects.Components.ActionBlocking; +using Robust.Client.Graphics; +using Robust.Client.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Client.GameObjects.Components.ActionBlocking +{ + [RegisterComponent] + public class HandcuffComponent : SharedHandcuffComponent + { + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + var cuffState = curState as HandcuffedComponentState; + + if (cuffState == null || cuffState.IconState == string.Empty) + { + return; + } + + if (Owner.TryGetComponent(out var sprite)) + { + sprite.LayerSetState(0, new RSI.StateId(cuffState.IconState)); // TODO: safety check to see if RSI contains the state? + } + } + } +} diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs b/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs index 2b4f518501..2fd1c8ee66 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs @@ -4,8 +4,10 @@ using Content.Shared.GameObjects.Components.GUI; using Content.Shared.GameObjects.Components.Inventory; using JetBrains.Annotations; using Robust.Client.GameObjects.Components.UserInterface; +using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.UserInterface; using Robust.Shared.ViewVariables; +using Robust.Shared.Localization; using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; namespace Content.Client.GameObjects.Components.HUD.Inventory @@ -15,6 +17,7 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory { public Dictionary Inventory { get; private set; } public Dictionary Hands { get; private set; } + public Dictionary Handcuffs { get; private set; } [ViewVariables] private StrippingMenu _strippingMenu; @@ -49,7 +52,8 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory _strippingMenu.ClearButtons(); - if(Inventory != null) + if (Inventory != null) + { foreach (var (slot, name) in Inventory) { _strippingMenu.AddButton(EquipmentSlotDefines.SlotNames[slot], name, (ev) => @@ -57,8 +61,10 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory SendMessage(new StrippingInventoryButtonPressed(slot)); }); } + } - if(Hands != null) + if (Hands != null) + { foreach (var (hand, name) in Hands) { _strippingMenu.AddButton(hand, name, (ev) => @@ -66,6 +72,18 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory SendMessage(new StrippingHandButtonPressed(hand)); }); } + } + + if (Handcuffs != null) + { + foreach (var (id, name) in Handcuffs) + { + _strippingMenu.AddButton(Loc.GetString("Restraints"), name, (ev) => + { + SendMessage(new StrippingHandcuffButtonPressed(id)); + }); + } + } } protected override void UpdateState(BoundUserInterfaceState state) @@ -76,6 +94,7 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory Inventory = stripState.Inventory; Hands = stripState.Hands; + Handcuffs = stripState.Handcuffs; UpdateMenu(); } diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs index f023c467ee..4a11eeb584 100644 --- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs +++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs @@ -156,7 +156,8 @@ namespace Content.Client.GameObjects.Components.Items } else { - var (rsi, state) = maybeInHands.Value; + var (rsi, state, color) = maybeInHands.Value; + _sprite.LayerSetColor($"hand-{name}", color); _sprite.LayerSetVisible($"hand-{name}", true); _sprite.LayerSetState($"hand-{name}", state, rsi); } diff --git a/Content.Client/GameObjects/Components/Items/ItemComponent.cs b/Content.Client/GameObjects/Components/Items/ItemComponent.cs index 812c5223d0..b84e040fa4 100644 --- a/Content.Client/GameObjects/Components/Items/ItemComponent.cs +++ b/Content.Client/GameObjects/Components/Items/ItemComponent.cs @@ -12,6 +12,7 @@ using Robust.Shared.Interfaces.GameObjects.Components; using Robust.Shared.IoC; using Robust.Shared.Serialization; using Robust.Shared.Utility; +using Robust.Shared.Maths; using Robust.Shared.ViewVariables; namespace Content.Client.GameObjects.Components.Items @@ -25,6 +26,8 @@ namespace Content.Client.GameObjects.Components.Items [ViewVariables] protected ResourcePath RsiPath; + [ViewVariables(VVAccess.ReadWrite)] protected Color Color; + private string _equippedPrefix; [ViewVariables(VVAccess.ReadWrite)] @@ -40,7 +43,7 @@ namespace Content.Client.GameObjects.Components.Items } } - public (RSI rsi, RSI.StateId stateId)? GetInHandStateInfo(HandLocation hand) + public (RSI rsi, RSI.StateId stateId, Color color)? GetInHandStateInfo(HandLocation hand) { if (RsiPath == null) { @@ -52,7 +55,7 @@ namespace Content.Client.GameObjects.Components.Items var stateId = EquippedPrefix != null ? $"{EquippedPrefix}-inhand-{handName}" : $"inhand-{handName}"; if (rsi.TryGetState(stateId, out _)) { - return (rsi, stateId); + return (rsi, stateId, Color); } return null; @@ -62,6 +65,7 @@ namespace Content.Client.GameObjects.Components.Items { base.ExposeData(serializer); + serializer.DataFieldCached(ref Color, "color", Color.White); serializer.DataFieldCached(ref RsiPath, "sprite", null); serializer.DataFieldCached(ref _equippedPrefix, "HeldPrefix", null); } diff --git a/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs b/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs index 0ddf3f175a..dea7c697df 100644 --- a/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs +++ b/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs @@ -1,8 +1,9 @@ -using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.Preferences; using Content.Shared.Preferences.Appearance; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; +using Content.Client.GameObjects.Components.ActionBlocking; namespace Content.Client.GameObjects.Components.Mobs { @@ -49,6 +50,15 @@ namespace Content.Client.GameObjects.Components.Mobs sprite.LayerSetVisible(HumanoidVisualLayers.StencilMask, Sex == Sex.Female); + if (Owner.TryGetComponent(out var cuffed)) + { + sprite.LayerSetVisible(HumanoidVisualLayers.Handcuffs, !cuffed.CanStillInteract); + } + else + { + sprite.LayerSetVisible(HumanoidVisualLayers.Handcuffs, false); + } + var hairStyle = Appearance.HairStyleName; if (string.IsNullOrWhiteSpace(hairStyle) || !HairStyles.HairStylesMap.ContainsKey(hairStyle)) hairStyle = HairStyles.DefaultHairStyle; diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs new file mode 100644 index 0000000000..ea8d5ea324 --- /dev/null +++ b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs @@ -0,0 +1,93 @@ +#nullable enable + +using System.Threading.Tasks; +using NUnit.Framework; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Content.Server.GameObjects.Components.ActionBlocking; +using System.Linq; +using Content.Server.GameObjects.Components.Body; +using Content.Shared.Body.Part; +using Content.Shared.GameObjects.Components.Body; +using Content.Server.Interfaces.GameObjects.Components.Items; +using Robust.Shared.Prototypes; +using Content.Server.Body; +using Content.Client.GameObjects.Components.Items; + +namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking +{ + [TestFixture] + [TestOf(typeof(CuffableComponent))] + [TestOf(typeof(HandcuffComponent))] + public class CuffUnitTest : ContentIntegrationTest + { + [Test] + public async Task Test() + { + var server = StartServerDummyTicker(); + + IEntity human; + IEntity otherHuman; + IEntity cuffs; + IEntity cables; + HandcuffComponent cableHandcuff; + HandcuffComponent handcuff; + CuffableComponent cuffed; + IHandsComponent hands; + BodyManagerComponent body; + + server.Assert(() => + { + var mapManager = IoCManager.Resolve(); + mapManager.CreateNewMapEntity(MapId.Nullspace); + + var entityManager = IoCManager.Resolve(); + + // Spawn the entities + human = entityManager.SpawnEntity("BaseHumanMob_Content", MapCoordinates.Nullspace); + otherHuman = entityManager.SpawnEntity("BaseHumanMob_Content", MapCoordinates.Nullspace); + cuffs = entityManager.SpawnEntity("Handcuffs", MapCoordinates.Nullspace); + cables = entityManager.SpawnEntity("Cablecuffs", MapCoordinates.Nullspace); + + human.Transform.WorldPosition = otherHuman.Transform.WorldPosition; + + // Test for components existing + Assert.True(human.TryGetComponent(out cuffed!), $"Human has no {nameof(CuffableComponent)}"); + Assert.True(human.TryGetComponent(out hands!), $"Human has no {nameof(HandsComponent)}"); + Assert.True(human.TryGetComponent(out body!), $"Human has no {nameof(BodyManagerComponent)}"); + Assert.True(cuffs.TryGetComponent(out handcuff!), $"Handcuff has no {nameof(HandcuffComponent)}"); + Assert.True(cables.TryGetComponent(out cableHandcuff!), $"Cablecuff has no {nameof(HandcuffComponent)}"); + + // Test to ensure cuffed players register the handcuffs + cuffed.AddNewCuffs(cuffs); + Assert.True(cuffed.CuffedHandCount > 0, "Handcuffing a player did not result in their hands being cuffed"); + + // Test to ensure a player with 4 hands will still only have 2 hands cuffed + AddHand(body); + AddHand(body); + Assert.True(cuffed.CuffedHandCount == 2 && hands.Hands.Count() == 4, "Player doesn't have correct amount of hands cuffed"); + + // Test to give a player with 4 hands 2 sets of cuffs + cuffed.AddNewCuffs(cables); + Assert.True(cuffed.CuffedHandCount == 4, "Player doesn't have correct amount of hands cuffed"); + + }); + + await server.WaitIdleAsync(); + } + + private void AddHand(BodyManagerComponent body) + { + var prototypeManager = IoCManager.Resolve(); + prototypeManager.TryIndex("bodyPart.LHand.BasicHuman", out BodyPartPrototype prototype); + + var part = new BodyPart(prototype); + var slot = part.GetHashCode().ToString(); + + body.Template.Slots.Add(slot, BodyPartType.Hand); + body.InstallBodyPart(part, slot); + } + } +} diff --git a/Content.Server/Body/BodyCommands.cs b/Content.Server/Body/BodyCommands.cs index 9809a5845f..da8c9e4ca1 100644 --- a/Content.Server/Body/BodyCommands.cs +++ b/Content.Server/Body/BodyCommands.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Linq; using Content.Server.GameObjects.Components.Body; using Content.Shared.Body.Part; @@ -42,7 +42,7 @@ namespace Content.Server.Body } var prototypeManager = IoCManager.Resolve(); - prototypeManager.TryIndex("bodyPart.Hand.BasicHuman", out BodyPartPrototype prototype); + prototypeManager.TryIndex("bodyPart.LHand.BasicHuman", out BodyPartPrototype prototype); var part = new BodyPart(prototype); var slot = part.GetHashCode().ToString(); diff --git a/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs b/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs new file mode 100644 index 0000000000..4d07bc1d54 --- /dev/null +++ b/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs @@ -0,0 +1,351 @@ + +using Robust.Server.GameObjects; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Content.Server.GameObjects.EntitySystems.DoAfter; +using Robust.Shared.ViewVariables; +using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Shared.GameObjects.Components.ActionBlocking; +using Content.Shared.GameObjects.Verbs; +using Content.Server.GameObjects.Components.Items.Storage; +using Robust.Shared.Log; +using System.Linq; +using Robust.Server.GameObjects.Components.Container; +using Robust.Server.GameObjects.EntitySystems; +using Content.Server.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs; +using Robust.Shared.Maths; +using System; +using System.Collections.Generic; +using Serilog; + +namespace Content.Server.GameObjects.Components.ActionBlocking +{ + [RegisterComponent] + public class CuffableComponent : SharedCuffableComponent + { + [Dependency] + private readonly ISharedNotifyManager _notifyManager; + + /// + /// How many of this entity's hands are currently cuffed. + /// + [ViewVariables] + public int CuffedHandCount => _container.ContainedEntities.Count * 2; + + protected IEntity LastAddedCuffs => _container.ContainedEntities[_container.ContainedEntities.Count - 1]; + + public IReadOnlyList StoredEntities => _container.ContainedEntities; + + /// + /// Container of various handcuffs currently applied to the entity. + /// + [ViewVariables(VVAccess.ReadOnly)] + private Container _container = default!; + + private bool _dirtyThisFrame = false; + private float _interactRange; + private IHandsComponent _hands; + + public event Action OnCuffedStateChanged; + + public override void Initialize() + { + base.Initialize(); + + _container = ContainerManagerComponent.Ensure(Name, Owner); + _interactRange = SharedInteractionSystem.InteractionRange / 2; + + if (!Owner.TryGetComponent(out _hands)) + { + Logger.Warning("Player does not have an IHandsComponent!"); + } + } + + public override ComponentState GetComponentState() + { + // there are 2 approaches i can think of to handle the handcuff overlay on players + // 1 - make the current RSI the handcuff type that's currently active. all handcuffs on the player will appear the same. + // 2 - allow for several different player overlays for each different cuff type. + // approach #2 would be more difficult/time consuming to do and the payoff doesn't make it worth it. + // right now we're doing approach #1. + + if (CuffedHandCount > 0) + { + if (LastAddedCuffs.TryGetComponent(out var cuffs)) + { + return new CuffableComponentState(CuffedHandCount, + CanStillInteract, + cuffs.CuffedRSI, + $"{cuffs.OverlayIconState}-{CuffedHandCount}", + cuffs.Color); + // the iconstate is formatted as blah-2, blah-4, blah-6, etc. + // the number corresponds to how many hands are cuffed. + } + } + + return new CuffableComponentState(CuffedHandCount, + CanStillInteract, + "/Objects/Misc/handcuffs.rsi", + "body-overlay-2", + Color.White); + } + + /// + /// Add a set of cuffs to an existing CuffedComponent. + /// + /// + public void AddNewCuffs(IEntity handcuff) + { + if (!handcuff.HasComponent()) + { + Logger.Warning($"Handcuffs being applied to player are missing a {nameof(HandcuffComponent)}!"); + return; + } + + if (!EntitySystem.Get().InRangeUnobstructed( + handcuff.Transform.MapPosition, + Owner.Transform.MapPosition, + _interactRange, + ignoredEnt: Owner)) + { + Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!"); + return; + } + + _container.Insert(handcuff); + CanStillInteract = _hands.Hands.Count() > CuffedHandCount; + + OnCuffedStateChanged.Invoke(); + UpdateStatusEffect(); + UpdateHeldItems(); + Dirty(); + } + + public void Update(float frameTime) + { + UpdateHandCount(); + } + + /// + /// Check the current amount of hands the owner has, and if there's less hands than active cuffs we remove some cuffs. + /// + private void UpdateHandCount() + { + _dirtyThisFrame = false; + var handCount = _hands.Hands.Count(); + + while (CuffedHandCount > handCount && CuffedHandCount > 0) + { + _dirtyThisFrame = true; + + var entity = _container.ContainedEntities[_container.ContainedEntities.Count - 1]; + _container.Remove(entity); + entity.Transform.WorldPosition = Owner.Transform.GridPosition.Position; + } + + if (_dirtyThisFrame) + { + CanStillInteract = handCount > CuffedHandCount; + OnCuffedStateChanged.Invoke(); + Dirty(); + } + } + + /// + /// Check how many items the user is holding and if it's more than the number of cuffed hands, drop some items. + /// + public void UpdateHeldItems() + { + var itemCount = _hands.GetAllHeldItems().Count(); + var freeHandCount = _hands.Hands.Count() - CuffedHandCount; + + if (freeHandCount < itemCount) + { + foreach (ItemComponent item in _hands.GetAllHeldItems()) + { + if (freeHandCount < itemCount) + { + freeHandCount++; + _hands.Drop(item.Owner); + } + else + { + break; + } + } + } + } + + /// + /// Updates the status effect indicator on the HUD. + /// + private void UpdateStatusEffect() + { + if (Owner.TryGetComponent(out ServerStatusEffectsComponent status)) + { + status.ChangeStatusEffectIcon(StatusEffect.Cuffed, + CanStillInteract ? "/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png" : "/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png"); + } + } + + /// + /// Attempt to uncuff a cuffed entity. Can be called by the cuffed entity, or another entity trying to help uncuff them. + /// If the uncuffing succeeds, the cuffs will drop on the floor. + /// + /// The cuffed entity + /// Optional param for the handcuff entity to remove from the cuffed entity. If null, uses the most recently added handcuff entity. + public async void TryUncuff(IEntity user, IEntity cuffsToRemove = null) + { + var isOwner = user == Owner; + + if (cuffsToRemove == null) + { + cuffsToRemove = LastAddedCuffs; + } + else + { + if (!_container.ContainedEntities.Contains(cuffsToRemove)) + { + Logger.Warning("A user is trying to remove handcuffs that aren't in the owner's container. This should never happen!"); + } + } + + if (!cuffsToRemove.TryGetComponent(out var cuff)) + { + Logger.Warning($"A user is trying to remove handcuffs without a {nameof(HandcuffComponent)}. This should never happen!"); + return; + } + + if (!isOwner && !ActionBlockerSystem.CanInteract(user)) + { + user.PopupMessage(user, "You can't do that!"); + return; + } + + if (!isOwner && + !EntitySystem.Get().InRangeUnobstructed( + user.Transform.MapPosition, + Owner.Transform.MapPosition, + _interactRange, + ignoredEnt: Owner)) + { + user.PopupMessage(user, "You are too far away to remove the cuffs."); + return; + } + + if (!EntitySystem.Get().InRangeUnobstructed( + cuffsToRemove.Transform.MapPosition, + Owner.Transform.MapPosition, + _interactRange, + ignoredEnt: Owner)) + { + Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!"); + return; + } + + user.PopupMessage(user, "You start removing the cuffs."); + + var audio = EntitySystem.Get(); + audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner); + + var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime; + var doAfterEventArgs = new DoAfterEventArgs(user, uncuffTime) + { + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnStun = true, + NeedHand = true + }; + + var doAfterSystem = EntitySystem.Get(); + var result = await doAfterSystem.DoAfter(doAfterEventArgs); + + if (result != DoAfterStatus.Cancelled) + { + audio.PlayFromEntity(cuff.EndUncuffSound, Owner); + + _container.ForceRemove(cuffsToRemove); + cuffsToRemove.Transform.AttachToGridOrMap(); + cuffsToRemove.Transform.WorldPosition = Owner.Transform.WorldPosition; + + if (cuff.BreakOnRemove) + { + cuff.Broken = true; + + cuffsToRemove.Name = cuff.BrokenName; + cuffsToRemove.Description = cuff.BrokenDesc; + + if (cuffsToRemove.TryGetComponent(out var sprite)) + { + sprite.LayerSetState(0, cuff.BrokenState); // TODO: safety check to see if RSI contains the state? + } + } + + CanStillInteract = _hands.Hands.Count() > CuffedHandCount; + OnCuffedStateChanged.Invoke(); + UpdateStatusEffect(); + Dirty(); + + if (CuffedHandCount == 0) + { + _notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs.")); + + if (!isOwner) + { + _notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} uncuffs your hands.", user)); + } + } + else + { + if (!isOwner) + { + _notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of {0:theName}'s hands remain cuffed.", CuffedHandCount, user)); + _notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} removes your cuffs. {1} of your hands remain cuffed.", user, CuffedHandCount)); + } + else + { + _notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of your hands remain cuffed.", CuffedHandCount)); + } + } + } + else + { + _notifyManager.PopupMessage(user, user, Loc.GetString("You fail to remove the cuffs.")); + } + + return; + } + + /// + /// Allows the uncuffing of a cuffed person. Used by other people and by the component owner to break out of cuffs. + /// + [Verb] + private sealed class UncuffVerb : Verb + { + protected override void GetData(IEntity user, CuffableComponent component, VerbData data) + { + if ((user != component.Owner && !ActionBlockerSystem.CanInteract(user)) || component.CuffedHandCount == 0) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + data.Text = Loc.GetString("Uncuff"); + } + + protected override void Activate(IEntity user, CuffableComponent component) + { + if (component.CuffedHandCount > 0) + { + component.TryUncuff(user); + } + } + } + } +} diff --git a/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs b/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs new file mode 100644 index 0000000000..ea855cf6bd --- /dev/null +++ b/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs @@ -0,0 +1,248 @@ +using Content.Server.GameObjects.EntitySystems.DoAfter; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Content.Server.GameObjects.Components.GUI; +using Robust.Shared.Serialization; +using Robust.Shared.Log; +using Robust.Shared.Localization; +using Robust.Shared.ViewVariables; +using Robust.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Components.ActionBlocking; +using Content.Server.GameObjects.Components.Mobs; +using Robust.Shared.Maths; +using System; + +namespace Content.Server.GameObjects.Components.ActionBlocking +{ + [RegisterComponent] + public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract + { + [Dependency] + private readonly ISharedNotifyManager _notifyManager; + + /// + /// The time it takes to apply a to an entity. + /// + [ViewVariables] + public float CuffTime { get; set; } + + /// + /// The time it takes to remove a from an entity. + /// + [ViewVariables] + public float UncuffTime { get; set; } + + /// + /// The time it takes for a cuffed entity to remove from itself. + /// + [ViewVariables] + public float BreakoutTime { get; set; } + + /// + /// If an entity being cuffed is stunned, this amount of time is subtracted from the time it takes to add/remove their cuffs. + /// + [ViewVariables] + public float StunBonus { get; set; } + + /// + /// Will the cuffs break when removed? + /// + [ViewVariables] + public bool BreakOnRemove { get; set; } + + /// + /// The path of the RSI file used for the player cuffed overlay. + /// + [ViewVariables] + public string CuffedRSI { get; set; } + + /// + /// The iconstate used with the RSI file for the player cuffed overlay. + /// + [ViewVariables] + public string OverlayIconState { get; set; } + + /// + /// The iconstate used for broken handcuffs + /// + [ViewVariables] + public string BrokenState { get; set; } + + /// + /// The iconstate used for broken handcuffs + /// + [ViewVariables] + public string BrokenName { get; set; } + + /// + /// The iconstate used for broken handcuffs + /// + [ViewVariables] + public string BrokenDesc { get; set; } + + [ViewVariables] + public bool Broken + { + get + { + return _isBroken; + } + set + { + if (_isBroken != value) + { + _isBroken = value; + + Dirty(); + } + } + } + + public string StartCuffSound { get; set; } + public string EndCuffSound { get; set; } + public string StartBreakoutSound { get; set; } + public string StartUncuffSound { get; set; } + public string EndUncuffSound { get; set; } + public Color Color { get; set; } + + // Non-exposed data fields + private bool _isBroken = false; + private float _interactRange; + private DoAfterSystem _doAfterSystem; + private AudioSystem _audioSystem; + + public override void Initialize() + { + base.Initialize(); + + _audioSystem = EntitySystem.Get(); + _doAfterSystem = EntitySystem.Get(); + _interactRange = SharedInteractionSystem.InteractionRange / 2; + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(this, x => x.CuffTime, "cuffTime", 5.0f); + serializer.DataField(this, x => x.BreakoutTime, "breakoutTime", 30.0f); + serializer.DataField(this, x => x.UncuffTime, "uncuffTime", 5.0f); + serializer.DataField(this, x => x.StunBonus, "stunBonus", 2.0f); + serializer.DataField(this, x => x.StartCuffSound, "startCuffSound", "/Audio/Items/Handcuffs/cuff_start.ogg"); + serializer.DataField(this, x => x.EndCuffSound, "endCuffSound", "/Audio/Items/Handcuffs/cuff_end.ogg"); + serializer.DataField(this, x => x.StartUncuffSound, "startUncuffSound", "/Audio/Items/Handcuffs/cuff_takeoff_start.ogg"); + serializer.DataField(this, x => x.EndUncuffSound, "endUncuffSound", "/Audio/Items/Handcuffs/cuff_takeoff_end.ogg"); + serializer.DataField(this, x => x.StartBreakoutSound, "startBreakoutSound", "/Audio/Items/Handcuffs/cuff_breakout_start.ogg"); + serializer.DataField(this, x => x.CuffedRSI, "cuffedRSI", "Objects/Misc/handcuffs.rsi"); + serializer.DataField(this, x => x.OverlayIconState, "bodyIconState", "body-overlay"); + serializer.DataField(this, x => x.Color, "color", Color.White); + serializer.DataField(this, x => x.BreakOnRemove, "breakOnRemove", false); + serializer.DataField(this, x => x.BrokenState, "brokenIconState", string.Empty); + serializer.DataField(this, x => x.BrokenName, "brokenName", string.Empty); + serializer.DataField(this, x => x.BrokenDesc, "brokenDesc", string.Empty); + } + + public override ComponentState GetComponentState() + { + return new HandcuffedComponentState(Broken ? BrokenState : string.Empty); + } + + void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + { + if (eventArgs.Target == null || !ActionBlockerSystem.CanUse(eventArgs.User) || !eventArgs.Target.TryGetComponent(out var cuffed)) + { + return; + } + + if (eventArgs.Target == eventArgs.User) + { + _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't cuff yourself!")); + return; + } + + if (Broken) + { + _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("The cuffs are broken!")); + return; + } + + if (!eventArgs.Target.TryGetComponent(out var hands)) + { + _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("{0:theName} has no hands!", eventArgs.Target)); + return; + } + + if (cuffed.CuffedHandCount == hands.Count) + { + _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("{0:theName} has no free hands to handcuff!", eventArgs.Target)); + return; + } + + if (!EntitySystem.Get().InRangeUnobstructed( + eventArgs.User.Transform.MapPosition, + eventArgs.Target.Transform.MapPosition, + _interactRange, + ignoredEnt: Owner)) + { + _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are too far away to use the cuffs!")); + return; + } + + _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target)); + _notifyManager.PopupMessage(eventArgs.User, eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User)); + _audioSystem.PlayFromEntity(StartCuffSound, Owner); + + TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed); + } + + /// + /// Update the cuffed state of an entity + /// + private async void TryUpdateCuff(IEntity user, IEntity target, CuffableComponent cuffs) + { + var cuffTime = CuffTime; + + if (target.TryGetComponent(out var stun) && stun.Stunned) + { + cuffTime = MathF.Max(0.1f, cuffTime - StunBonus); + } + + var doAfterEventArgs = new DoAfterEventArgs(user, cuffTime, default, target) + { + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnStun = true, + NeedHand = true + }; + + var result = await _doAfterSystem.DoAfter(doAfterEventArgs); + + if (result != DoAfterStatus.Cancelled) + { + _audioSystem.PlayFromEntity(EndCuffSound, Owner); + _notifyManager.PopupMessage(user, user, Loc.GetString("You successfully cuff {0}.", target.Name)); + _notifyManager.PopupMessage(target, target, Loc.GetString("You have been cuffed by {0}!", user.Name)); + + if (user.TryGetComponent(out var hands)) + { + hands.Drop(Owner); + cuffs.AddNewCuffs(Owner); + } + else + { + Logger.Warning("Unable to remove handcuffs from player's hands! This should not happen!"); + } + } + else + { + user.PopupMessage(user, Loc.GetString("You were interrupted while cuffing {0}!", target.Name)); + target.PopupMessage(target, Loc.GetString("You interrupt {0} while they are cuffing you!", user.Name)); + } + } + } +} diff --git a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs index ec8ba05e42..4e43e0be99 100644 --- a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs @@ -1,6 +1,8 @@ #nullable enable using System.Collections.Generic; +using System.Linq; using System.Threading; +using Content.Server.GameObjects.Components.ActionBlocking; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Server.Interfaces; @@ -28,7 +30,8 @@ namespace Content.Server.GameObjects.Components.GUI public const float StripDelay = 2f; - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(StrippingUiKey.Key); + [ViewVariables] + private BoundUserInterface? UserInterface => Owner.GetUIOrNull(StrippingUiKey.Key); public override void Initialize() { @@ -39,6 +42,14 @@ namespace Content.Server.GameObjects.Components.GUI UserInterface.OnReceiveMessage += HandleUserInterfaceMessage; } + Owner.EnsureComponent(); + Owner.EnsureComponent(); + Owner.EnsureComponent(); + + if (Owner.TryGetComponent(out CuffableComponent? cuffed)) + { + cuffed.OnCuffedStateChanged += UpdateSubscribed; + } if (Owner.TryGetComponent(out InventoryComponent? inventory)) { inventory.OnItemChanged += UpdateSubscribed; @@ -62,8 +73,9 @@ namespace Content.Server.GameObjects.Components.GUI var inventory = GetInventorySlots(); var hands = GetHandSlots(); + var cuffs = GetHandcuffs(); - UserInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands)); + UserInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands, cuffs)); } public bool CanDragDrop(DragDropEventArgs eventArgs) @@ -80,6 +92,23 @@ namespace Content.Server.GameObjects.Components.GUI return true; } + private Dictionary GetHandcuffs() + { + var dictionary = new Dictionary(); + + if (!Owner.TryGetComponent(out CuffableComponent? cuffed)) + { + return dictionary; + } + + foreach (IEntity entity in cuffed.StoredEntities) + { + dictionary.Add(entity.Uid, entity.Name); + } + + return dictionary; + } + private Dictionary GetInventorySlots() { var dictionary = new Dictionary(); @@ -360,26 +389,46 @@ namespace Content.Server.GameObjects.Components.GUI switch (obj.Message) { case StrippingInventoryButtonPressed inventoryMessage: - var inventory = Owner.GetComponent(); - if (inventory.TryGetSlotItem(inventoryMessage.Slot, out ItemComponent _)) - placingItem = false; + if (Owner.TryGetComponent(out var inventory)) + { + if (inventory.TryGetSlotItem(inventoryMessage.Slot, out ItemComponent _)) + placingItem = false; - if(placingItem) - PlaceActiveHandItemInInventory(user, inventoryMessage.Slot); - else - TakeItemFromInventory(user, inventoryMessage.Slot); + if (placingItem) + PlaceActiveHandItemInInventory(user, inventoryMessage.Slot); + else + TakeItemFromInventory(user, inventoryMessage.Slot); + } break; + case StrippingHandButtonPressed handMessage: - var hands = Owner.GetComponent(); - if (hands.TryGetItem(handMessage.Hand, out _)) - placingItem = false; + if (Owner.TryGetComponent(out var hands)) + { + if (hands.TryGetItem(handMessage.Hand, out _)) + placingItem = false; - if(placingItem) - PlaceActiveHandItemInHands(user, handMessage.Hand); - else - TakeItemFromHands(user, handMessage.Hand); + if (placingItem) + PlaceActiveHandItemInHands(user, handMessage.Hand); + else + TakeItemFromHands(user, handMessage.Hand); + } + break; + + case StrippingHandcuffButtonPressed handcuffMessage: + + if (Owner.TryGetComponent(out var cuffed)) + { + foreach (var entity in cuffed.StoredEntities) + { + if (entity.Uid == handcuffMessage.Handcuff) + { + cuffed.TryUncuff(user, entity); + return; + } + } + } break; } } diff --git a/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs b/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs index 0690744f42..c8fe5b8b78 100644 --- a/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs @@ -61,7 +61,6 @@ namespace Content.Server.GameObjects.Components.Items.Clothing }); serializer.DataField(ref _quickEquipEnabled, "QuickEquip", true); - serializer.DataFieldCached(ref _heatResistance, "HeatResistance", 323); } diff --git a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs index 2fce1dc185..f04b50cff2 100644 --- a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs @@ -1,4 +1,4 @@ -using Content.Server.GameObjects.Components.Body; +using Content.Server.GameObjects.Components.Body; using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Shared.GameObjects.Components.Movement; using Content.Shared.GameObjects.EntitySystems; diff --git a/Content.Server/GameObjects/EntitySystems/CuffSystem.cs b/Content.Server/GameObjects/EntitySystems/CuffSystem.cs new file mode 100644 index 0000000000..f08ddae3ba --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/CuffSystem.cs @@ -0,0 +1,18 @@ +using Content.Server.GameObjects.Components.ActionBlocking; +using JetBrains.Annotations; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Server.GameObjects.EntitySystems +{ + [UsedImplicitly] + internal sealed class CuffSystem : EntitySystem + { + public override void Update(float frameTime) + { + foreach (var comp in ComponentManager.EntityQuery()) + { + comp.Update(frameTime); + } + } + } +} diff --git a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs index 5765098531..832fb7858f 100644 --- a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs +++ b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs @@ -2,10 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using Content.Server.GameObjects; -using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; -using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects.Components.Container; diff --git a/Content.Shared/GameObjects/Components/ActionBlocking/SharedCuffableComponent.cs b/Content.Shared/GameObjects/Components/ActionBlocking/SharedCuffableComponent.cs new file mode 100644 index 0000000000..9d82443e31 --- /dev/null +++ b/Content.Shared/GameObjects/Components/ActionBlocking/SharedCuffableComponent.cs @@ -0,0 +1,49 @@ +using Content.Shared.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; +using System; + +namespace Content.Shared.GameObjects.Components.ActionBlocking +{ + public class SharedCuffableComponent : Component, IActionBlocker + { + public override string Name => "Cuffable"; + public override uint? NetID => ContentNetIDs.CUFFED; + + [ViewVariables] + public bool CanStillInteract = true; + + #region ActionBlockers + + bool IActionBlocker.CanInteract() => CanStillInteract; + bool IActionBlocker.CanUse() => CanStillInteract; + bool IActionBlocker.CanPickup() => CanStillInteract; + bool IActionBlocker.CanDrop() => CanStillInteract; + bool IActionBlocker.CanAttack() => CanStillInteract; + bool IActionBlocker.CanEquip() => CanStillInteract; + bool IActionBlocker.CanUnequip() => CanStillInteract; + + #endregion + + [Serializable, NetSerializable] + protected sealed class CuffableComponentState : ComponentState + { + public bool CanStillInteract { get; } + public int NumHandsCuffed { get; } + public string RSI { get; } + public string IconState { get; } + public Color Color { get; } + + public CuffableComponentState(int numHandsCuffed, bool canStillInteract, string rsiPath, string iconState, Color color) : base(ContentNetIDs.CUFFED) + { + NumHandsCuffed = numHandsCuffed; + CanStillInteract = canStillInteract; + RSI = rsiPath; + IconState = iconState; + Color = color; + } + } + } +} diff --git a/Content.Shared/GameObjects/Components/ActionBlocking/SharedHandcuffComponent.cs b/Content.Shared/GameObjects/Components/ActionBlocking/SharedHandcuffComponent.cs new file mode 100644 index 0000000000..c707802918 --- /dev/null +++ b/Content.Shared/GameObjects/Components/ActionBlocking/SharedHandcuffComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using System; + +namespace Content.Shared.GameObjects.Components.ActionBlocking +{ + public class SharedHandcuffComponent : Component + { + public override string Name => "Handcuff"; + public override uint? NetID => ContentNetIDs.HANDCUFFS; + + [Serializable, NetSerializable] + protected sealed class HandcuffedComponentState : ComponentState + { + public string IconState { get; } + + public HandcuffedComponentState(string iconState) : base(ContentNetIDs.HANDCUFFS) + { + IconState = iconState; + } + } + } +} diff --git a/Content.Shared/GameObjects/Components/GUI/SharedStrippableComponent.cs b/Content.Shared/GameObjects/Components/GUI/SharedStrippableComponent.cs index bcc9b2fc7c..c498f21e8f 100644 --- a/Content.Shared/GameObjects/Components/GUI/SharedStrippableComponent.cs +++ b/Content.Shared/GameObjects/Components/GUI/SharedStrippableComponent.cs @@ -41,16 +41,29 @@ namespace Content.Shared.GameObjects.Components.GUI } } + [NetSerializable, Serializable] + public class StrippingHandcuffButtonPressed : BoundUserInterfaceMessage + { + public EntityUid Handcuff { get; } + + public StrippingHandcuffButtonPressed(EntityUid handcuff) + { + Handcuff = handcuff; + } + } + [NetSerializable, Serializable] public class StrippingBoundUserInterfaceState : BoundUserInterfaceState { public Dictionary Inventory { get; } public Dictionary Hands { get; } + public Dictionary Handcuffs { get; } - public StrippingBoundUserInterfaceState(Dictionary inventory, Dictionary hands) + public StrippingBoundUserInterfaceState(Dictionary inventory, Dictionary hands, Dictionary handcuffs) { Inventory = inventory; Hands = hands; + Handcuffs = handcuffs; } } } diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs index 189476b442..c76a275ddd 100644 --- a/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs +++ b/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; @@ -58,6 +58,7 @@ namespace Content.Shared.GameObjects.Components.Mobs Thirst, Pressure, Stun, + Cuffed, Buckled, Piloting, Pulling, diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 2eb1e59b58..365ca7aad4 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -68,6 +68,8 @@ public const uint BOLTACTION_BARREL = 1062; public const uint PUMP_BARREL = 1063; public const uint REVOLVER_BARREL = 1064; + public const uint CUFFED = 1065; + public const uint HANDCUFFS = 1066; // Net IDs for integration tests. public const uint PREDICTION_TEST = 10001; diff --git a/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs b/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs index 72b53675c1..d1d57c64df 100644 --- a/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs @@ -1,4 +1,4 @@ -using System; +using System; using Robust.Shared.Serialization; namespace Content.Shared.Preferences.Appearance @@ -19,6 +19,7 @@ namespace Content.Shared.Preferences.Appearance LLeg, RFoot, LFoot, + Handcuffs, StencilMask } } diff --git a/Resources/Audio/Items/Handcuffs/cuff_breakout_start.ogg b/Resources/Audio/Items/Handcuffs/cuff_breakout_start.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f58274219d8f318355150503a5c20782370f6985 GIT binary patch literal 134486 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

?~Bi{4gTO3li&3#c`^P4 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

!8- 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

8{QCH&97sUd zzp|Ou2tx5*k3ERnVezglzJ7B>-6|o`h))HsKOdbBV+66YTSZp z;LHr*{bIak!kebIn&)O4xaEzP0S%b7Iw{Zl)mmts?ODu}BjX0EQ&O+7jl8j78xZ?* z)LZaX%jeX+=_U8U+>G>B;&-Q<+-{Vp%(?bgw%YjLmIqx9DvF215WP4YelFzBHtf)8 zkUA17_Qe^Y0V#ty?{zkS0x}r!Wj`yTnuVymt_|scRf#EX4kqn8q$6y z-fXX{^Oe%JQ}v2@p7J_E+H+&HnxjTB~LeJr-Hf=tHrxIq)c|y9RVE+o8>=> z@uP!8eSfR#k6Dy4-;S2zw5nYnmm(WiwlMqPcp^@#lVw`(%PMO)l#S|cNuR6L|(E%#uxleOXSSL&O;Rcb9fA?if=q7|i8~-F^3;`>}l~Alr7eX{~OnyFyA-OW@V@qRpGqe|R>REC0DY zlg~suf4&O76=wP1$O;3UI^hanKDmv?CyY3*+bOQ8DW!8cDeY>bbjZWbq77)YwA73Ha&}S^1`C4+!DQ~Ro{F637#*1xw&r@TbwvF}O ztN({yZQtd=UPAOvn(%$UehWFvzEL52vu^Y+^Aw!_F9t%nVe^HWkC|HJ~{CC;J~@YPluR56#(wCkvB64gaizh{o@Yt2|NBxF-FZ9G0pVt~djGHdGv=YTc0x zyLF3?^pD~R<89BB!NVE#Q`KPcMiKt?CtZtYM`Imr0LwpKerBrPB-5AxPgE9Rwl2bm zi8HJ$?&2oDZ=lPHlJjibKVTw z`+G?YyS>Zl37T1S+JoDNBzFt=AahS-nmklvY+*0`UcJcf)vAZqG>A2mgpY?jbS`Vn z)BD~&M215Q^ypx@?A0$2_j^8fRUW$}mfmXWA;d_Qr+aofR&o(LPJO6zCSzChsBPp! zn~ro~PI)D%dY+(3?0d}i=|ehbo}cRf=T$zqHTvFO-XMSx^X*-^VU+2mfvKSpfgqrB z%c#^dFtxY3g-%El2oDGZ9UW798#6tV&Ohn-3$ge;wFlVhPRtjfHn~;*E2Bdern=@0 zSfH46mAX2JAb%;vT^%ADD1n$;Ya{@1(sA3GUl|aRGE=D-@Jw*SQ_3FoI?2EC;StA=~;hvjHR(@|jawv$LvaluE7ME$=* z$gE$Y{~8d00Uro^Q7&A!Exi|E#)IS_6(OFzw!w_5U`9$`v1aPj8sAvQdk*&}FD?Yr zOq-XhObfQwSDMV1giPs?UbPY8G21`G8Loj0a91e zlq6>tL138{gO)4pI-o9 zV(LH{<7#%$&55UK981A_4XEgSb*e`#Z$|!1gAPVJwz6i;#dI%Pyctz5JbO+|s=T;q z)+z%iXk(0b^eE_d3)q8g4m{UMVgH`2@%s(-P?V72{G<># zDwYplCFc2tu32vyxY9HaE4&MRV-ldC9TXuM-(oyf30}_A?Amc~^=|*q$90?4U4Ty~ zBny4EHMN0)d(UFz9He&xV?*pE|ApNa@Q%$sQByX}%RW^fsZrCq<7w(6RZPb=!Gn9l zmGxbcVPodcMuT#EOQuKjU!#o7@2;~gyjlGEyF&Q5Rg#~=qHGZh^M@4pROW@{SPI5) z?C)MD=5xtF&qU?>thhPe6e~b>loY+7yVUo4WL?DZjI>EaiD7TwK3WlYFZk8x)x>AZ zoe!~EIz*%+r0O2Mz)xj@*uYaVx`ah96>8YYj-kw}dM*Qqv$OrW`o@c=QEF003spbh zVz2jUo0XG#HHbN@@4ENj*v({Wj+em=J&!{_Ji>nT^_0(h&`+^C{QPXF=flp$B?NB( zmRXWr=$rU`8Jr}hAY^jV>qkv&j2=lDMD_FeL2&=-?07I+l72r`TjXW$!!gK0x{9MKI&s}2d`Ii_dyZEJ zZGhC&Y0;)GSIO9eTmFM}bm_$Cc1$RdyJOF?^@%`3A9a>859g8ahhNK$MKw{z8QfOF zfZBJy&&ONEpXIhP-xfl~L0L62C8njoF3S^Vyq9N7{)@0E#|X+Vajv{&YP)?tF!$Ogz~QkHs2) zi$X|Gv#5 z7x_J9Z{CC>Uc5G4R_>bO(+W?d-&Ef+8Ks$xxRgsphu%)dYGlBhANN%PnM$7y zk9~NR!bsHESQ$SP60!;T1sHccNDY8W)iws}L7q3S*tJ(?tx>P|B(M8O9#_B5Z}W!_ zuDl+EE%MZI%9MOqaaqwI)eF$;7K&eI#D*~B`Iue(`BTB;v`^anMX-~9Wh3wYYGsgQ zi9y!wHa_gTID;Yhgn!JDc9S47bvg5Oh8s<`tSAr<0>vlp%n7&ubxZwIDqThnC zp6AUsD(7*Z<%}1uv1i7!@1(Vq$_eBW_=T6%m-U7b<|OmMlkc=XR`GxLv8i`?6OMvn z7B#p}t~30WnSyq9&4J6+2^!>3r*Bm^leqHMT`3sgY}&6KJhL5juUq*|$WkX)|0o^m zy0__ipWH$ch@C!<5=}My8f5E`fy)XY1vO&yV0{P5M3NtokNcCt=nclM;NByeb8@-n ze*M^Ol((qK{?|ec_?~5Z5LTcJe&$prHJUpmwl0{X0z_@Jdt=^KPZsyi!o_53ov*H7 znVAZ*zh z`AJgk6y6NL4$a~06=kxMm$>c5dOimppK@G5q5d1A^=rFxyCWk9BNh(u)kUVuy6wdc z`$(>+PB9^5f1h;G=O?Z#kx`hC>~*DtjbkL{>&t_sO#0|?g})Mw#j~nZ<7@{EpzEdG zphd#Qj)e^zqsu7Z*lS!EBT}FMZkGTz1a)$Iz^`6c9+U@t(}9NxP~IP+b>|=BFzj74 z6Ii}|f$XOY#=J`2GjO1^^U`w4nS)wtbHb&oeWN+={LaWA&|Ro_%xSUsKEF-vCxA^% z()%(*n(|}_)*@6yBPozdelmdvE4m4xm$h~itbX<%4O84V;GjTR!E0EB z@8q}CCn{WwWLbo@j(g6heBD_DMK4i*r}gCy8m!A(kcR)?b}NFi2C=kVBP*1`UQEj$ z%)0ROe&OdCx3i@OpiHB+PtC2vmq&lTl;lp`OtZzj4gZ6Oo$M=QHE9Y-CwBa(-Xy4e zza{oQwM*8kobPG!D4)g5wTj4$#bpGh9=vY=!p-x479D)zP zsNqt90;q rSOWRW)0*|J3(XO82!m@;;g5&Fh%`b-o7^wxl_7_qJ%7bf~rWQ*GUt zTZzk5*|z;AQLZ+YR`?bEPCNB&KS!RhxmpwH+*2wC?K?TW%Q0Qf}v!kIC8PLDtl!PGfeo z*E^E{wHctfYW`?aYL3u=vCoc_3C(+I>@up=6GnkaQvh(?=^m!;K1U%Ok?EyBD^C2_ z&nx`iFvhG{HrOPDMqy>5upvDDZ9PY+a9Ix{o8k7c(szmi30b8R|BgtC(-&$leI=zI zT9wKfGS20kkKNxZeR7`YTi@cS&js_@S2=yPR&$e&p#(pk(cSjkTp`f6eLt2=v7J2p zcyu_>Be|YeGU&pw0UP(7a!Dyo0@B*L+M_PauI`|8Cn<&wcq3i}?0+e6(fmvD!_@C7 z=G;q28Bup|Ez}0lvy7IXEKO|amVwDt z=gHbXH^a`7zhL>MaWf%@^G4XC0&x5bXdDLY20}I?=F8HCeMJCQ_8DT-X?f>98eQOS z4+KwM&SW+YG!WRIuTx-AnZIQktk=3GGA0M@wxd0R+WlX&5tcFM3JU2iD z8s9jFvXECv264fAf&=wX!{k@FJf!Oi=uN;@E@xiK@Ar>b>2YWyb3l8qq&vX|?t>?u#VP;&Q1bIZ-Pln8>Au<7O*-y3Vg66Bfl!Qy@CykKAn zjPV=Le6e1X@xO<z^M?At^e>NJq&BSA?=j}mw%~olfS%jJXWJ{5H z!-9-)UjomkeGj3t4Ir_h`XcvH2B*7+W8BZq#fe))X|;zz?h|4iXoTJn55mhdoyTsz z0-3(sXa>-T5wENaVrO0Uch|T!M_?g=S4_l50jNCSjrTUbvC|miu?(wg>HfQZ<@?@; z3GHDBPC5r;asL_mhD2-HdqL+<%xYjH;i!H z9Pj0b#YgQq-u(?Mkb>wF4qjpVM-IKwp{O+3k)z1utb}e;z7n+j$*eoq0yDgilhs) zTaN5&QldAH2Q}^FZBu_5qIXWD)ioL0Z)oJ#bJz>(&i|;eecWulrTpA^vQt~TF%bW( z;E^LC3u-;k<3G8A6B?{pu!Q?)cdCn=-|pFX?y$Yg`4eH~0%%a}cz5Twfuw)xas@+= za*N2lCv*YE8ow0j8q4jSEn`NxWN z$+VogcrpF8J~sKPaSaPbtCBSd%MQ>FI z6XRQ7!&@Um?y_fHZgWDM;RU;=75IOru(&O2fN6q6MYZ|5KXQ`@B~E>Svcv8ri2N8| zoVdWD$f>>V{$-LH_=XTUO!8nF_2ZuvxiZyaLlrK2^T3W-&+vC}vg_7_>6KbnAI11b zcNvRGz8$+`YriXWJuwsK-0L&rGabEN66YoOV0JMl?K19C)f?Sr7s69pf@XwN<5cqv z{L{e2$3(C7SFgS0e9uqevg3wnb7$5S@N$ypP6>5_)tB-#kPyk-jSQC1t+f|9)~t8N zE31=*{$4uuzD@w^V+i>DJ~tKgR)X(5s2hb%g}nrJZ3lPU+Mfyo@zo9zB(rCVZ;^lN zkQhhayA}%2oBYS7TyZknLM^g~F-$0H3%0%^BQy6ZwdM_wZ4m82SirZS{jG|OVB-e< zo8Q&X?>de@7``JyY#`n2XNv!~v*xE@pXa>6b z)e$52is-XGo<7Bf65k>DRW5*p9WyQX-7)C7z$#E|X&FDwerR0G^2;{o4*O-+=o|Xg zO@p?M$FUMZ`%uXmdx1naKtJz5LZ+_Uz)quP%h9B3{!%64gPDUwJr04j3aD0$v={Sv z&8UmaTkzLwJhe$axUZWr8o=|y-Ha;PYDC9@ zvy?2_(;rI-v%RX#^XW5yTo!sT)#z+*Zyog9FiqgnvnPHD$T3Pu0rK>?5rxCsBeQ&6 zWRvE%tb3OKqqoy*lRCotWnr@ohcoy&F&pcs{7I_*)nQLb3FkoJ(9maJ7>_yXbm%i4*4O<) zEI>hOMYu^a#%H_7)jHU5wwo?jKo;a|O&7c4kL>QBepg!Wv*sen&WyV)l5ra-#fe>JQQv4mmi`WWjPhmo$S$2bDDwAg#zg;>W*X zM#hDBmOiaJM3$|*&R@H(lTc#XkhOSn)NK{9%Ou%w=z>hD{#y>QE%Sg_KF94w>x0*x zC(0n%Z}yG$uUPFyi71jjG}#v;U}C5I)dQovW^;paS&Q~EvvX90Dd*>E-T@7Vz;8|x z`d{-69+wa_#384x5An&ft2a;sw7ZuY#=a0vmAG?Oq#Z_<_9D}T%oa8+bf@`PkdLQj z{UvyB?)S;q*P^7E2_+Ejb&3vWzPT^HpZC)jLC^rfrd%lg-?OskTNH zD3Q5P-8II4tb0zs@pb3pB1PmQzvvex=KE|9Mc3bmrVcJtP@#F# zd>Wl`$baOyv+NFag`Hf&D*pRL23ChtbK(kl+BakPlf30J zHXP+p`?LF^wrhX3iY)_{wR6y$Ykz>N=a2JKVwv2L#V%a<`!o1$DW^G1O(~>$0DSSf^0o35t+wLsZZBz`d^X}EsSxF$G6W2 z^o5i~jDEUcb9t?#Om*@$wAWzx9Y+b&X&;e~Iiz#*_%xn%AY%?}A42m!R;MdqycY^G zxLMyyR|@JNIc5S@DJMSU~uxQtM|8wcosG>SPsdU#m4_ z)@>_h0(fw8 zTx+%@?l6nhv?vA69X}ue7FeblKn39sR_iN16|cWOjJLY^o zoY}+gaJW{YDDKCh`Kck>Om86|@Zp1Dvg|_UsMMFg0&fUx`;J_Hjl>RlCR|a%fz}gj zz}Mi*1Z|b4OJ&|(t4zH3G~s}#IDB5N(W&A|*g3h$YlhWsz51n=#~I>a*GI`i7L#Lz z%T?v#gSzx#{+pD(FWhJ_b7tkZv?u;L8~I5~s1kuYvu1d$+vP5FXo_-qDK5g()tCuP zVGN9qxJ(aKx?Bi-ElXm4UOIHJF&pKyvUzF3uB|0qwq=WU^_pUZ+PDWl`#t*c0TH!u zMUp34-a@zZk27Lh>~L{qc??wHe-Y-}hQR*|^))w#;L)D>Ezn{wb8>G8Rv0^wUfm+f zGOnGs0#9Qz9FzZn}trkE`&s4X^(OpgJ;n4vgLzjMQ4?hyR0 zdg43v!*1cvGsZlDpOt;Y9go($HvhIBy|iDErHv?wme^&RaQ$@P6Z4~dTF24C`cY&_ z9!z$vXEo2yhwjGm=0=N<*Qa|v66E@Ely=hG(S34{zx}A)136j4d~QR}`6<*76N}EK zbKraT)h1*kPchC&tPKHl-}vO@>99A&2HUF`mkq zinjG5=lle!a&ni9F%8r4`b0LxVlW{y+EvzHH^mDq{`RDCR-S6!IM~xuDeq}QPymGEy{UbBP7((YDdKc|YW6qiR?$cK5678rGi@ZASMnr`t%al`G) zU@S$Er>4a(b@cN0=a;>4C*R&urE2xjB#`}cLeDg?eDzU0h z1PZ;LZgB(e?eI(o-5|4P+;0*+-}S-iqZ3sV3qFoI??QZPKt`TKAb0f>QEyyYdzjV^ zb-z%iA*@tGOv__!&sOj*&!VXrRdLWLu%s}W^_d^t@6hkf_Z6Dp-plM3ABkdrJ6Kv0 zd5d3d1YHJCtp((oVO6|LL3`&#%a~r1=IG4|mRdu#dST#6CSbR(`2`UxoTfX4)T(XB z9bhSqd0~zmH7V3thGM?^q|F0OBSP;iH7g?SX7_9MSpNI_+lO#zxd(i)fb-~NRQ0MD z(Yt=tBOPC0y4Tk6-6o_trI(mo9yHn8Ol$zZ$Q)V2}=e(0Dyf%q$n zt@kM!Te(;-gRm$6zOOT}5cW6M!h;ihVNGug#+_XI24txbdVi^|l` zu_UmW&4PxF=F#cpQw6S-jsL_B3m2!}`}WVB(^5LD-w*#a>b;s*bk7g>D@<(BA6{^b zC0uZmjfQ=$M2j}%fAUQKVws9Y@G%2 z2eF^sFeyqUXi`v}sS$_u^xHfmDyqGC!&mt8Zl(^@tc_4JWO!cJ0m+ZuCRT~?fykCz zO;p>xRz>kERzKWU_;eS)fyLxZW?eHqO;)38|4{^5YScsjIW_MejqutFxuJDf~`e+FpHv-UeT%Ovbw8M4UQd7SG4B!E!zY0blK! zcEzjh5XZQll+5iNl_S^cc=Jle_%?C^hJP&1mPS~kXF9QsYDa6>)9T0xaNy>!{3(Pk zamc}{{l^wN!}NHfQu?ozOLN~@w)-Uq!w6`ed7TQ`-j&Gd@#m)`5{efu{h88bW)$8SLY%(>9DRShZ+V-jn`z6b}FqmvKW>s!awxa zA8vUU3*KnQLorb!#Rk=8x8n zTcnBS-^KGOhqQFg(C3b-pX>m>9J%_?%k9zIoS$FpD826;R&V9W#+d3)g_s%vjn~;< zD#C{PhPr~WTH-qVv?Epw!@0^s5D4y=>uJn`GLA4u5eJhI8Cb;g3hVZ50+etH!?j?q z2ml>}5ZS7dSkD;g*zf5x%_cYZ1d-{L!bGEovELt4Q;<%RYK(yPug7!d_=4Yet3>5GK zE5fzt@?Y}U`FT1dM(LzSO6cDBICt}s$|ZRe2IwboycY1I3XTb`iV&amR(Bj1&BEo> zEFdMd&UwoSD`rN=znA%V>)bYzj25K2h<}%zZk7WG_1#%VmZFBxZ5=e6S z^qZ&uS9rL8^!9&+2i9BRq3NUG^b=zPBQre$frfB>YeXQ`?^`)m?115igL2u7$@Nj7 z9Nw~q3+Yn%F4Y5pqh?_D^-At+PH*q=z77&}l{%`1aA~P_ZfX+~QhO3;oo^?LT^96K z=yH|%tjZb(chnYP;B@#J{o~ekLi%B%gtW!L`5dbYxjvnOduEviDBC(Z!{W;3q$_eh zvaGo7@f9*%nLUZGw;`k)U}a4d{G5A_Ga_C)QL~8<_->*$MrA>YV?Ewf|7G+{ird8A z7bHp^=l6Ki@^Nn!$By#pCd)dyNbQsuYQeN}Tjgw7LaUJOwWWmu_q>M5{zZ2&54L^j ze-Uj0-9LGsS#-4lww_b!abD=j+5cH4%aai#PVXVuv4iJoruy}PsA%p{al0XFYXdf+ z6Rn|L*Lj0MV}`sojq&uauP6)_9)_V{=yMpMTXS6~@>03`v;5#dF~lzrZ}pvQb{VyK zB-+-Vi-(b^Us13*?yl2tasK=BSH2q03Lg?tBkJt`{8m=j5h*2ZLcrnDtggxQTbD3?Bo%QaKvY$mwDw3iM=L6cQ$>BzuUlNakhMoZ7 zo9H4JuWtOr(H26n28+w`g1H2?T>TWPPJ@yE$f=n+F9*P?H>9D1!iiVQx&=DxAjtQW z)Boy3jxZ~0ri~j+izOVgEgw_>)-y-VWqH{iRGW^>a0n(|J6V5u9pout@HgBkAG8ZTB!6flm%G)0urX6Q%nLus1{EYVt9LkhpT28(V)8R+Ido> zF*L{{=nAy1j&#Gf{CB!V)*h99e7?3a(sl3~G!NNRpJd(UTe0goa`-Ma{ly@LiU9zx za{~H>@n@aJnxkNBa!iCSUOSs-2_jDz8`7}g<|qI5Lw%(EYv~r{qAN7Qw_W0&zCGO+ zulr2`Di*QBT51c~fbTuA?zLI5eS9_PvSV7r7W0(}{EIcYQY>i99{w zAigZ{!}4T4wGd_dU*kO`&~8hxH-6?!U03!3z5f-bpJ zIiM@P;({~MBFciZcsJt`Yos4NP(CXbPAZ)mJ1+CIJH7R|Oh5;jFBz#^d8{_sGg;ji zHPv4MV9OL@r}!$r>j<^49P!mIeecI5Ewf$Qy$J1j<6IW(&`cq{HY&zIJ4x*YnUaR* zVkc4S2{Bu>(kw$0RA*Uxsc${hj}{UKoETT&bA@x_FY*~F-)Zu+#Q)0bIgs(&lu{!n zxYS3y_sF?B)WdmzAI<^XY@M*hn#R%}7=wHBOu z_mr;j{{-j&7ys_@Q&|?RZiUS$zHYbTqlv5#Zu$E@d0iAK9@=vDzwL)Rz#S(n+NFzb zO()ZOq8=)#{1?*o+WQ3lsbTl)#I~5WkM_G0nOx)Bj@fQPUSYA0 zf8p5R87om;=+B#RFve}pQ-nv2$+pJx(^$CR1W9=<7E$|zrY}HmiB2|>j6Pv81|H~Q z|1B0lZ{bZOq00jSE-6EoZ`#Ka)3-0dKjJwr5dbFN5ZU literal 0 HcmV?d00001 diff --git a/Resources/Audio/Items/Handcuffs/cuff_end.ogg b/Resources/Audio/Items/Handcuffs/cuff_end.ogg new file mode 100644 index 0000000000000000000000000000000000000000..3249a5a78f8afba5b89c42573550dc91ec0dec22 GIT binary patch literal 23010 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 YL20Xa8V(8AAJZ-wvtHfj{|Ny6AMQ7_0RR91 literal 0 HcmV?d00001 diff --git a/Resources/Audio/Items/Handcuffs/cuff_takeoff_end.ogg b/Resources/Audio/Items/Handcuffs/cuff_takeoff_end.ogg new file mode 100644 index 0000000000000000000000000000000000000000..e608a2c128f8eb56d890141f5b6ed5ad6e1c6afb GIT binary patch literal 43735 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 UJvAj4iL;Oh{^hgxivPm@0@s3Bx&QzG literal 0 HcmV?d00001 diff --git a/Resources/Audio/Items/Handcuffs/rope_breakout.ogg b/Resources/Audio/Items/Handcuffs/rope_breakout.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f255d7d78bb51f76a48f7c32d80649c961578649 GIT binary patch literal 16522 zcmb`ubzGFs_cy#4gd(6IozmT10t&KHE=Y%TcQ;CRcMA*B(hVvl-Mcg*vC`eK_XU1F z-~0Rgp67Mn|2)@gxR{x9=FH4F=Y7uXuqc|EDueEU{&_kZ$(3)bMDJ~1p^&4v*g6=P zJKlDnyehr@1p;vt-M{A)>$-M;jv( zb1P$%hkr#fOG>GtfY1RRPC3dhzmb^tAP^1+^nr#EE8aww5t@+8;GUB3=C;!P))qi;~CaISBLsB2jSdMd2)v&x3RmaP5NDz_n&kN&jAC$maBvFZsa3s-h#fLB! zq&NVg{%(sBJ5cZtIjNpd4z!q-Dm>FNi2NtwD4gUlIHhl?U>2lzaapdW76ak8i)2rSDxxphj?0=1LYjzOm zB`0Z@J!vnX4G8PF1FxE+DBMvV4yfW*Bmd{;M z#Y}_#t+_u-M=L8D1Nw*%8Ccy!`T(E#SQ=W}~)1M;j&iq8_9ffLuM^!)-Pwo_r z6`#ZTlQEw>GD;d;Ip% z7z+VZ{I}uuXojDXjs8~|-Y0$cj20-PT&&96qVHAStGQTfBs7bDh!W&g1yT%6vNGyvF12jVE1b*7U{sGKi*et@G{|h;{5P2aOwfrXT z!T&~14nx#$fhcO(I2w&OI`<^g(6pk|to5SLkNyX8%)$y&!U{ve5Ft?vp-JYUX~m77 zt@_G0oBprmKgf}BpaBYioYxLC|Am}yoTO3!nyOgj5C85_bQDmi(`$nNHUJ3pEe7-L zT^xA_VV#8VPC{7UtBC%e5d%;sdF01=0L3N(c>)Cb(*aG_Hr#y~m=fo3mdK!T(!Q9=yl4HN|O1EB#w zk8?+2o};|X0D(TmIEHYdKR3h?LN9W{ffN(^#|YBXI%0ttXrVY@FhJl!&=?KLV$kN} zfn|&QV;~Grs1n$M5lW~GW_7`VJc0rQbH-XcEih|$7$ri>y!x)hx4fE5#A#1(eVcOR^iNfqQRjjAQ}NZta2tW@H4?veGXgmw)gt z====|3@Uv9lDr2Z3`j5-5JXS{HVBA;L&RS|Igj_~S@2GSGp0fR+Uc55nwZ`B3# zX<7oO7&I*43?)4xE1mdy%IrrVTF#xz;sKh%G{EEx%5cQtLXUOCltiO)030FT2qI}t z1(gH8q0Pq^1pExb#h43Kg3!_Ct7tNUKwE%%x4ua-@EFm8V$gu>m5&D;YJw5~HFT3o zfdMTWMgTqy^dS-;z?M@Sffp%|FMx9opgmH<0YF`jQGhOhzMfkGBE#@+Ho*XanCyV$ z`}LiXOAH8T7YlhP9o2H6_MfID7(kq5880FMfHq9iap)n4 zl1BjKM>h3U=!fj6`iER9W)wdW5a^FatEiuI?AEa$LVw-l@0sMUs5_5?fHD5^-5pjEp4~3;?+CyF zZ~1cn5+L9^zr7W8D-Up9s(%TX#kec8965CXQUnIr8Y-gj8OmAu@Dl)Z#W-;`0@x80wlBuAOHpYwTVI4U#R~V zn1K%obOOKuT>X3bz*uqQ?*fH`&)D~}N1mp~QYAw;<8dXTCzQeyKLo4V`4jlvB{bku z4o(Q55yvQ;1en2gqY7Biq7son^(mt!<83ddgo36etI zIz@2G7Ih_o5y1H!^a%vI|C9?C9q;AGkC-5Vmv3V3fgU}^BGX>mzaU6;J#ni!yqrv&ylG&%?*BR_q9j5bkHQxC2>BflfFd35D-`j zqF>kxAj!y4R@u0n=k{~;FF*^^pckK0QYX73QEYn1Hv)zP=d~YAgJmW zVrH@?AS5F8Qu3Ab8?c;$5=7-cR}6lBes{btfw!OEKbHu^cU_>{ON3j-@yb>aDuWM5 zBs&txsiLigM3Nwp;!4U86%`eA9bF?63u9vw6OqXu-y^A%v%g;6Ae!8NZ7bZQxbE5< zX_K{7$|!~}9#FJ-6X|$UEN-rgU7x6*|MsysRYkTlvtxit%f&w%U4EqaqW8S#XdeS? z;b|gIVYS+Ay{$(yqGc^1D}#Sa9Mo79Ydy2m(ja-k?ZX&PPi)_4*An~I;!m*R3$ zoS>GYz1|gOus?jg+UR*zbF?pestnreA9;@K*xgzn!<^k^wRUaK`V*sB7Jj~-0uz?7`$U9%WJ|BCPJ_w)P4x)234=+;j3Qk|T7E!^jD&H*LClT+l*-GLu&+0MO z6+cDH&g$kuQeNeaq(*y6YuYdDmW|uPMb`Q|M0^Kk81)8{^d!@;oH-~mxwWi%Zk$|C z*WSEC#MU{>R~Q}8*a8061%8vN*62; zMC!#Cl;a%cSqwP75A@mX=y3S6Eq}K1N+t!db~c*gws^&4lg!PwIiVcsyzC$=CQ?WM zIo8gA>H82~az11LiA;q`P6eN-sXbqEwmEn67LcdrH&;M^{yJEQRNoof(H zmXGdNZAXjQ6fSMC3Qko!X4R!=W{w|iR>+^`FDeT0sJC@WYd0wJF6PYF2y3PVXDaii zL`!#_vEmnhCZHHGd>(K~*koR;q#cb$KoKbDtz%K7+w5z<$Bnameda;MoIc|DEFE(> zd#H%lhWyX2tyq#DX47(@<qOC83P2aL^RvE|JV%mu7B{Qpu>!mHuI+u8@szVnrc(Fd=9y{(joM9d%5aBN1FbPISNS%7a4`*-@+9a8$o)LN0imMa(ql&Ct zDHSE~hU-k^uwX-K?pQep^sA0LKyb6t!DspF=kya_oii4AUD_BLt)%am)QcMm;(CSb zCqI}LuD?2zg}N=>G_Dy3D&)ViX|?4ipViWKFSyiAqdP=WXRVfQRhS(;5TT+wPT}-1 zHk@wwSYKdV?TduJ^Ke`CWE|SgRX9CqKd8_}!Jk)W^}hLtJX`v#G__*1Pr=L?By0ao zM>J4i0sV<*Dr~0l`5<_65J~dWTX=$K=C`ME*RUrBl}*2_Am)rMg)zua&PqbMU$`A+S|*KfNN`oYcac8!dkS7+xAw z=ZT#4>9uv&tAjBJADGzJY?e2vVH*;%^1^56P4h41B}ej^!44=O0egze8*!_)D@-di zL+jYFDwxBV8A#0xYu z2<9vEFiFuMR1oOuxci>=8rt@?CDz`&K+I|Tc`>UXrhXbTp zj8_K*q?9G(^qH5^Yqf^_>;4RthQ7DAo{rbN>2$xRVYRmiNSgu)TUitnlPQDiW6a2C zdC701(tY(=B;5^pIxo7N7fUb@Sm=9xjTA0ielNNs)_!StaWFmHzhkjp+c?cPe&X*g z${XJA^)+&$e7SmquV6D+^OT%fYphXwrYLlkp8Z^Dq4O}cAlSoqaX*8f0DhQ3T(MPbfryj@CGFs=J`r-N$&buyO{B?{Kf%|U(s0muFO1(8knL8PBt zeF(SDs1ltX<|4M{vK_;Yr;Hmzk-(U~UxXDQ+6 z!OJn{tD?PNaxHsTfeLqarnHCCN+m)keY+Wl=!#+-a&RQr{JJbvSbI57GR^1hu+q?h zg}YM6R68fsPrBh+{*yZ@2qv9wEVuahD3l&e7kQMPcyn-R2J0~->Kkc(Zr{IMXgVB3 z*!bbk&OrM5`Yt)yR@AP~AqD65ZlQ?n>1lIUi>bYCgv;r=DTNn=SUI;v%xS#gVu$Ry zNPKdFef#xv6)l>O=j??>eUYHaHR>^U`_eC;`l01!D5J<0{bkWY!Ohs5ymZD>R%~45 z!gu<;dLat2GNFqX((5>3AnlQU5vevVXWKSL?e$m_E%DHcPiX z>tV>Jrtw~R*MEV@8jqzzG!S2i;Id-$sC@1a#7K+<%;P_f%-Ah$vEe^;EYWB z+^>8#9Ns&P*g6X8lh&LpvJ`ovm+D_q(X>1}>BpncK6a(-4K3S{2>LdXU7J0_1v#8= zoA57vb`f&_sjJd|<#4uj&?imd0e8e961QzR$ix)%UbG8-79rX(9Tk6KrWMUj8Pnrf zSWs({O1t#p+Ix$HlwUJY@NCwT$n1r1kG$$r-52*eBbn-cUUzVOY1t>%?G>Lc7Vr2r z?BWL_=x7MBk0a}Dqh7pbna*J=JvQc*Q!m~tYWI}MCtO=_9(gPIHj3-Y45#mK4p8SZOGE| zOc@`y5r&{;2mO+^Jy$l6f#T60pN*A?cH~f>qC%(b&na{#US@XlPu2C3(ZH#9<-2r@ zLM*o7$;5T_`VZkU3)4akdSm6AUskO6I^dhLRcra2>p!s@JUwlS4?%YDxlHZI8jm{C zV4hC1(r1?4aVpT1z|xTq_A@9$tsos1Toe#y)d{KP(dB!4$45uDg=58|B*$N66I=6dcC|G@4jy>zCr>NibIUyRk@{Q&t(QxRrt%{-(}%;l}7ofH|h)JxFENk zi-v*`uj~xbr@S_;5%U%HDnE;8f6e87Y0n^?`oy+5uJJ%01WNd>UZ*47?qpfAV}1s; z7%F^H(OTC$KWy&PMO5hx9~vShCkv3LpxJ5M-R7|&nVI1O{`OSPOX@0Nqc>P;C$66X z_YF)r*-tI~MLV;LiDKjV05DEmUZIAbiAM&KOmXJ&eqQ@*c6+U31fr=dvdKr1(41Vl zVvm)cBjSuODo78M^C;IgtF`?1m43e1T>X$);znIu-n)$HW0MrGfcH9s;Y0QyQ0I3p zwC;30!TyQGWWBkejiz(=wDBqydSN~(&+Y7bBVF=Lt@M#ExLzck#BzMr8YsW^uy>E< zug?<_pEPx=*6WfPoKn#6Wf#R7_4^Jlz~`9jMUa5u^IO&LbvN0>R_J$(Ef-&oM*0_( zLXMr(5#*j2q8$R$b4hOSv3MbS^PqWK*L|c8-Lx=2Nd1G&!zV7fI$mD$8^$8RYc4NP zT=7{iqR>0faX-R~@gIuh7o_*fC^_a~Tym{_Yt#xB|VPs}D zdED0M)zokL%r7G{5d*2_GtSIWwH0l9T$k~fUpgJanI!Boe=bjl=QL~3%u4N|EU~)E zdjPSc$_KsLQR;i6Y=btt7t(h6T4*DseJezCYr_L=!3xdi=;JUGvMh7FVwiNu`}%cp zJvNU#;+vu3A2VUl)z)_8?%AO0YEXVDmI3ki-^Z1Um+Vy)Pa7CA*&n4?nVRr$7p^&+ zuv>YeqF7xx&E*qIeKogqKQ3R;&hqjVmoMyz>eClHE1x}dD}qL6S8Em8AM-Das*8!t z*!EPqUww|=j-3Pj5~sgNj94p129Q+@U&ztIcL)f}W78*w^ZT>dXQfc@&22ih_G+I8 zY?RwfB+7tTE}Uv&^WC7UYiQsyx8Lz+H|31m+rEeD$?d+b7j1|s<+furmz};Ag=2Pc zQ!!FquOHZA$_uj#n4qUp;0{9Dn~h}!U-8{a+K+ua_BWU8GU8on^$qnuoY=rCnHC!b zN)0bi2F}5ZQZpa17-4EnJxruSh3hxJnpYGHrhJBy5r|;hO{pqHiY&P%pQ-e$AD7rj z+lwAyk4^C+d{CFpJZ6p_Y~Z&7lec{U3h3`e z#7m*u$PPdqxQMv@o`ca=0?~6*Ekgq&(g=BjJV9O{FOgTsedNX5H5li9sC;~K^Y!^JUy zNVyoBj?sV<`@<&<$(hz$CFzq9Zq8S;O^#A!+=0OxH*LvpP(fS!efD%D(3E>j=kV;L zeHr^bi;VWu`V+UWHfak?{lVMkPGR2-d^diX*JZ)Q%HX?9D1IdfjgRp1v*>Ms&`Mti zQ=g+0ckA)Me)6d9KO%DxM#(zwNVYhD{59PN!ZGUQ*_~ayn)Nxe^%2h^7jK*wx4XIS zq3EG#(`{)r;882lJ#Z0Q^u1bcIGPh(KXPWe=jY7gnxQRcy^18^4z1mA#Ho0&Ex3N( zzHoupN}m+fxj?v!I={2%51i@emcq>4afxx~+RvZvo_TnAT(0)->#^;ZtX~#(u(xX` z-`srVo%gaTKD=noGT*(>ArC8qKtV4lny34|Xbxm;mx{mt^oXd5adzjXh#%t*7nRRQKS?PjjP?o~Fg0UZ(br?gJF;A$w&$ zghcLJw7r3;T$iD)!RKd}d~hc6mQjlFaa*?Wc;B0bRV{7PU5lFxX0<_&h4b;6emyoQ zZ~w!R0z#s~pJVJ#W`Dc(cp7 zNMZ0v%C?-ftzxs7-r|gBPhtB)^`^pj9kPk7MccM^kwQbBdUmH@OWdGcW8-8t*vTqe zO+fzxmW<}}!j4~62TK_bBIgFImkC{4iI89BxdbCd$6*bmg9zdttc3CHoJ>TrI)*dfmvPs8ad!gxdLc1cqsC!h5C5hQ!%xMdoYQ zuf=@TrbKP6nZ!IM=w70H$adYNXZF!oEQj%Ht9H$y>BSKjHdu_3l<1IOWQM-qdd+n{ z!o=_MVanqsH&kJmpCx%Kwau3M#)9~qfbd0>ZC7$LC$9?oPsJ@cP0C9r;D9UgqBz*JOtL=2KZv#U<<*|y?ksx&6=oyz z;KhX9IBNki4w|l9?oj>RwPM3<&$4zpypC$EdB!#UalD{$Ji6iPku{EQs_TtT=}Ner z=}1nii0Dgk`alT;^j*|Ep-BK)PJE7ZrAQrjy!#QG(y8Nv5W6?F zB*F0=srAI;V-%6u7h5}}$_rj_lg-UP1M+2?$Bg!AV)L59sh?*KO3rPvcV?a*woI+n z$Sd=sFYTKN)*=Pi3v!q27BgY*?vb^I>MwWEx-7nvXBx|$xtOw}mi zB4lyGW}Yi;%gdF$vfZnJb3E%pS6M^kA+}b{<2`)t4pY~C>{zxes)S>PQTtW|lSJ-P z$u@5y*lpw1;a3SYzgwO3lFO%7ery|HptU)h%Do&`vff(IN^@TvGHkL^UEs30c>nUo zenk_hDl2}t>1$Y2>;m_jpm)=J?VTL?;A1pQXK7#Ab$4q@A(#9m~ zd~U}-s!m>;B`vR`F=P<$G1jM5=F_^ie(0)GGdjRO(_j&w@L98tuVck@K6;RK)#RW< z*0wY$l(|aVzM=LjiBbQNtfa$l*!Adw4y%7`!DeW+Up-x#b#9D#OS5u%gW93jkLS-f zqIVS{^Qx#wYhc8#NV$GHdJ(b=b8+rUTP+IyMw}n^ynItnQgv{w(G2~2j>rrX*j*Cs7kKU!HalExZaOoS2^`GhAQjhT6Qj*=B6RkA(Lpbm`){dX?YFf!(7OkL=8FoDYui9Jh5g~Kr)B28JFl>wtr`DShkG{6>6&ka| z0jwY6#P*Q7#)8^PZM@iez-jg|Q*gZ%GYqX(9DKhx(QE06(3T~P!rNuyvAjDMJ)dGi zv4>P$@}l#r8u~U2GUL7>3&X-QU;1{;==Ww%q!aVlRAx$29a}xq)hY)!ixu_9*O?O7 zrq$cYAM3pr8{|6|;MZ^O;aTXj3tLzGEs#Gvx-b`KN_DAs5B%6{FZy&w!Nc>Sz8^x{ z(No$mxbb_uRSFGn|HVeX+yzHosV^}-FW}bu6K*qt2s%LT}AOyc7IsIKb$!=jMQ3N zF6uOt!&;3=x8-a-NQJ4bJGhj6${Bmx5(l@JyMFmBhMu!-TJdPX7rdL4qvOQdNphl# z(*1R9Pxx0wN5}j9Xpf^c+X#2^H6kM3FfIu37V!*{{(S!#@7JdUB1vX6a#w-Z4h^g? zLCXOa&78QQX*HUnG9_9R6RGLyOYdfobQN0e zJ7+Se86B2LgWvv5&MULtpQeqGOR*QJjZ08lywy`!G?d(i>1$6NwtPdreaa+@h-uzD z;9YZi-g&K8*LCh0RsT>ey@#>(OLocQrnw3M^-N;D_UgjW`N|hs!_li7r#9C+&qzA{ zsGF*twl+x9zipo|O;NGWM4`bt7`@q+U!S&qknUzaQ01_HXjng90C6YZ)ogPsc4!rP zSV*B<@>z}LP%ujC(KmUJ7@aZ(DxGH~m<$7Rk79BilFZz%G$?Yg5T8F!;Oc8mTn-)6 zgkPL(6Fo?bQAGPV@46Nvt!$_@n>Cw+YErFv=*7^ACp2#^m^MGRyfpE2jDEfXD_2#Q z^w?CBQ3bcOhyD~7J0svg^M}OgU=l_9dh!a*)yG`taPk5~(I|Xm>ZE&6>C0xis2)Y# zFn5Pu9rtv>K>vO~hY82YrR*Y-3-5vTsSX9K;0diqvt}Cb^?(o zJB#;Qgh`AJ7E>8jlOZ=@@h?YRnx2}p_uGv>2I?1rq}`Uac-asfgN?qtLxkU-P?0T< z4W(c=J)K<&VH(Gcs3pgjSr3o@lB)Dmor3>K(mh3y;%if~9FS7@KsEN9>f{48++n`e zS-bIZ=C{6?YGnngd5sdqHAK)z2h zG-Pf3ANrQ(uEs73;Rm);N5YdrmMvL!rs&>P9$#w%U46xy=+wdmZBZG$nFLvMDZWqc zrU+7)L;6{ptxg!P13#^y?ocN(w+Zu@yKYdQBPPwKx{C();}y8c+8TJX71V+%I}&|B zFR?%ibkqUgS5DrSj@?^DKA6c!9G`?6HvOs?pudKc=C^)}U-n|9_K zc;gJEu{i3p=uethi&A@E30~I_v9*Y|a4Dn@@li`=n+ZKq^Pp-KYcy* zQc0EHOQsgYdRbVUvjc+He$jRudufX0XQOIL{N59o^q+_yQN=2Ve}1Cf-Qgs_YOru( zsM}0-0rj@6t2BOAOLbBpOdfFI!6I{9UCt)_c)+8I?QL06nN?-0zU~_y^Zrc-P2KNf z7rGeBs$-kyB$wD9VRkHm=JLS`R=^*$=Mu#w4}uL2?jPUp8UFooxT6R@ocKvWH9_a{ z2Bzh`G1!2NTR1&lEOQ$cz1%tHcUN&J*pJiO*c^%EWtyl@eWzR&W~5M3LBnjS)sxLz z^7s>6uU9%zCw%#)y%;6(7n6Wz1kv)w6T;Pt`c>3nqvB4=a*Q03KVLZs{_yu5SLZe< zs4I``nmbc%!+d+BHpNR6N)0lbvDY_+mT!#OJ=uzP6pY+9KiMu=J$FA}K>KzOgUc6? zC>J2UNH9N~#bn$d40Gee-gQNdcvnci0c|32KK5oH8D#w8SyrSt8?E*Lq{~Qx~&oPs$!Xgyb5)Qj%a#El6eNQT*?6Tu^%Y6|5Q>~|@)o?CJMSWa(4i+0Ft*d$Y8 z{-j6JHB1mvXV$Ybu_F3>P^Nnm;YQXe2p%V;Ad%hsKoh>-^+oXQ4gwq3egT>#c0B5Xr=}?CVvzuHqDvU1?49BMwqEd zdWrH1;`wSF7UqS+!pfg~YB7G-ZcDa3z;o%pwH9|GSh&mW7{NRv7g@vk#ZF-HEs@xJ ze4iidSGuF(^~&on@W_KW32i3#NHE|lMnmpG@`LWb?bdOiMo%vXMULYsOQNl?ihh4s za0R|;Pq{Jh@t1-WegwSjrtLb^A=VxLXIDqc20ICHnLU{>17XE@rkkB04;^0tt!aN2 zQ`3vj?sgk3(jG0zm>+*oBNghSmNsqI`Rr$aww9HgFQVrZFreGztM|3iIp&rNF9&O zO)pBoPWH;PjAVb5L<>c7CPoWc7vybJ)vQ%f>w#XZA=0psn$g!g6ELUD!OB#B9*&hjZ=@?V$vKGykx zh7=n=^ej=lUl3VcMG+uNwt+{o5AM|w%ANPr++qa04`e6cyG4?Dg&rA?JSN2FMjy* zmN(H9mbc=Dms>4IMnM95omv~z`qDeja|Xdh3ulTudS^n3f~a)}m*G2v#{aPV7uwY2Fe6Y;vqwbAdI zscG6Ky$M^Ky?JZI(M@_EvpH}^XrVrsxJ-tAp|SRPEg|>)iq`~moF^F^n91`ITvMVJ zFVfa!a5GZ*R=w`q{r!KH2g0hrITm{}qI>QH9XTuGm)^Ay?E%}tYSj#4PAy7C|z&TM6p7-4bs*n`@ zOwRGR7x&R8WwNBoOvQyKmtCv+zK2VL9~f7b(+buaHVN5?WR2Y|1ZQ}!KY#Xevti{y zFGwS$2^uP0DytKvZrd!MK0odoI&CBK9{SDnmN5UR{nbF98XrA|^ucOJKdhqelaoNp z!bY#Lc9B)t8xKKblflZ!s)ibCzY{W_MFhc!_&JiBf-fJ=Z{E5f7xe1L4^8#)F=*Y@ zPNmh{5XMB=efC;Xx{2;VAuJ2uL6UnPc5>06{?i*(AX}#S({4xL1+H7zVilCZ$hJ}{T9(F z!teLBB5<8cNXKU*z&^h&X?25xDH^gaODa#UU_DtYnKs6|bFO9c$y~^)+!#8H%Hs1u#Fh5hEPsS={|B5`l%ogiyP>b{I>$&Vmzu>|beTbwD)WTP8)Q)cN z)0I&yx$s%S^SrDlU9GH**nFp6x{)E=Sd0-Wud5e5QnqfGrI+*JeKPB~uSdQaPgWV; z-sC{|mALK((PD?q4eD4{Wd$8JirTfd3&UOUI;}kN#k(cH zUlW%dZQ79i?z!@>9wW!>zM7OeDlA7bzg9fT^XS;R7H(}9iuv{N>mWH_>E}QhLi?UZ z+WjdD#aCdR=D8H4eZT!ATS=1#GV9OD(g~iUJ;JE1=PS#@$NV<%>G;bl1w?u)Pix5{ z0@+4D6qLaM1~ zh?gwmU77f7dZoDwdh%t8Rd%0e9!XO9M~-rxbK86V!JI2Mi9^K81*!R0GM-$NjfJdE zeO}1y)&stANzNBPL{EYV+7WCS;~wSkcI0;Mi4!pUwCqF7K_)*Y!dcLfYbsYeTW~>V zYXRoAX0EMWH0g3Nu!&j=04Js|GvT`R!r{@X&V0_Zd-{g-SR9IC)+J;G9l=YIQt=<$ zH{&fQoFtWo*P_{@vJo9JhH09z&5NCWy$DPB+-&KsyhF&gj~PR~WFqgm+~to_R3a@Q7YO-swFLzk@wRcj=qf>g4N2UcDBf z9CBWYhcp%~J+GQAXX^&@XV|ud-<3bJmO+)?>iEJ!LGt}lUjxp>P6Z{ZExdDQJD=1rMnM0DzDVYVJuCh8wjP3LGc174mg z>BX0+uVVA!RegJjn4#AZ9&*_|(;CGNow^}ocKNC)ZO5(bg&bWuPp@7YDeu@f9;HN& zulyy0Mln%#MYLn>b*hZE{lKaHH9zq$d=$1l`;+*ICU4&%_|@fJTU9P==2q5x@0x*c zTFlUmpSj)QBdp|-6Dfzs@I&uO-VMW5n`MVYo7ekIUqrpEAA%Nm@0(yHz0?qpm~%Y! zkR*|)XkB$mA=kX9gLOK2iDZFOwe)C+)46YC``Ln2 z(`mi3NbC0^IM=vwDjN{wzz!a7RnC6YHnd(g7D%c{0gF~9E?{RfWMd3U8A~ak%TTlC z64YQ>)5%oW1$)|>a4Kgy3E)Rfe0=(L_5*y{zEPN$w=zFSv9LowZku4qH1?xg6Vufp zL-gG6!{-k=0&`J|^SyUY)#(UR*Gpnl-n6#xcsDjH-iPBw_0=BRP&U+>g)ebKP;nw^ zs=f&7J&(g}e}ir*X_aw~qqfFlPyU_VUsfMDIlL4EfxPHz&oO-KPE~zpq#^Jet%Q<2 ztI!uuNcQ=A(k~m5n&#g|>S!sBRa=?wyAOZZc008=Qd&0N4>O*CPw*SW^UK?0iMJn6 znmiR!S1z~_B&6AvQhNRtlBdjqj*(GL(!ja;HO11sM-kE~RsLP1Rv8idnurQ@0lF+G@Sd!KoP~6O6xX!X*g&O}Ed1-#*IL z-O;_3IMj@FtKOm2UD;v)HXyi(Plr zGj`19uW^{@g&t;5Nu*QAACA(A*A$j$sNjp-dkep*-V+|InCIEivKTDFJH%#GkW6M? znhy;gv0m?^D~s@afSpH_B2sUYc`56SP3OJ$=Oyp8@^Qg5_dw2k za7PHeUf_7yPVRtPi=xb)EqK|9ELC;c_{| z?-o4`Bj;0OImsYaxKXb$EGzr?F2hn!&Q~uHy^L{^j8mQN4a)&%4S!$pv^}e-83ila2=KY$bjTlLaPh+y9hG4n9!>*7VZTaf zUj_HPWiQCSxYShI#hcMLb^Npn*I=6Z1g%e7O@-?1ngO7G>(LkUn0izvkEZvyRB=W@ z!8P<^P0d6^uBkiZI)FYDV{q+cPJsXL%%c-nsn_#i2dRL)v!$rGo`Qww;kEa90RDO?h@?CUZEZ z)Z$kpCn0ogx!Eu~R;=0Vb!6%6xJZzqi(bsA(&{MSH^A; z*kZErnIY8w9Qsv=7=2mq6EDqZ z7=nM9q!8_!8o(ZzuNH?FmCpA1@nk_VKY7Me(1=q{FI9~%Tl?lrH!2`cbeu%*O_RPATddu+`NfSG zjl%lM8<P;y>2GopI#6yTRzc6aEB+A<_MikG)9 z*DEe;(X%7lf23GQY90zwlJz5wCZ?Q`RZCR)4&Z{#I@3$K?NJ$N9<-&R6)!l~=h(iO zHpjzAd!I=e>4cEzh`Abt?5rniSK6?Q)}6_-AXiy}_L)RxLmT6Qbm4*Z@d-h)Q1ZaX zbuNL55$+>Z0fl0%8}>zY8x5PsHL`@`5%%hzL6l*sGq(Ifn&g zO7DmWp@$w?$l1a7{r0mrrM|SS(RCZ?g`!#1Pd#drkS;gyQPJryOouN8#hrR4fuJY{z&8T z6Q~q82JUEXX=86ea{jMK9{ESwBmf!6BdE&S^0F^l3jnAA-~~H3Wt^olH!?np%R4z< ziCAm7_cl5HZ413wxM1tQFIHjmYXEQ#c*#W`nZ2NF+aqp8&l&0ZLEJ`NERU7+-Lqff zL)D3fbDTV9&$#k-$AyuK*Z z&KjsHw3HL4F0u3}J5pkqtD;Z-5?@6>L{)G|+pMJif}tP0M;w#(&x`z24_0t3@)sD) zspZLt;@@!RCcA>7{;I_ayad$*6k}+W%V;~xn7V(mYi#j8yDIYY{u9mTYFgmqZm8p7 zG2-Dq;(^dh4%V+m=v4>nj|Lm!f=%c{{we#2862^ks?JIRe7G&1ILH0*7URd?;vdfj zg%q6u%!wvpC6B$u9hl>e|F2@HmuT^S zFS6F{cL7<@mTfLfZ7%FjpzLj~yricVZU;c0%0hWtT!j_k!fkL#uqe7c?ub|I-chCc z*9k|A_e)k=1r4ITAbex_Ay}6C|em!Kt7xP6w>@LU2v>kU&_L#8d|XSHa?XT zRrCC>ynn(X-zcr;oA@j^;_Y~t%^1lbxe~<%!pb0v~WV~s(U|U+Cx;*(`2mgy47uvQMrj8hPl`rgS{k+{< z!W!GcKd(M{Cak7;&G6?n_W?Q!%?E}9A{GN$7DFBuW0e;ARS3Pxe*p6rHZvpc|AibP zL~e;k&MC#7`yb?d;EMb$7I|AamR&FQj(4Kf+m!tG8B6&eFaB@Hv3{4A{4Vd!JItF% zuD6LcZ&M0tKHC2%S*iWMmj56}(Ul!6Aab6#vi}!yz6vru0@3t^S8e;Rj#58CgStJr z{@)G&fUnUMDyMnm8I*4bDl!D+(|RuTe?|;Q9THX>5C#pK000aCu-F1xojNu<5k}ph z^Fy3V1ZN~@N!vW_i%gb_blrGw3K8c*<@E?6-^3W`^Tgb0HldWzQs@38hZ$BT<0A#r z-Z|hI2simRg3a`%;d1S)kHZDY7=qpiUSJFClOy8@ju#?hONfRx(}T?p)}VNKWrp}@ zs1gzYUIL`x=TcT*^feOMGyr%V4Syp@cFl}hf-K*S8d^Xf7%k4p0jGp;aUiK75Rf2* zVK?tlCg;eZg(&9-Mnk!f$fponZX~@XgwKN-dJzc{Y`9Uh`VhX>cYCi@toT@{9WrMaB)Ih{FR{i$p*H#PgD@@*6iDDm7>3yi<_?=uoQf*W88o%! zt{jFHolc%ZoSaBd=7Xz4NZAs4IV{gzj2bpo z%||gpltEE2<$Tm2thg9O|B#G3n7FEnMA$n)B%K)-@^o?}I6114IC;f$s10$1ZE-6& zc|NG1onYm#ZG>PelUcVolGe}(fRm}&t(k>K=9uAHm&Vix_MYYk}fxG;RuOtBL*C3&%*6k30 z^&+@AUtnuZyHCd5LLb!1ilh&u=59%bQ{O_;!ztxk!5XB4SmS06q9OW)8p6#DZp%-9 zfqVIjz})8FKyXOWIY9mlKpzxu+9i%*1$__{4GkB^Fa<%O@2H{g1TkrWPa%*5sH)%s zvClRi3K48Q?aQ|aC-zZ;l2PE+pB6|h&&`b*2FJRAvmueA65v>HO3>agP#(yKL=Ky- zobquX2Vr4sK~RV?2Qtw#Oc1PreDSa_!NliKNIu9HR@MrIfO&5i>}yQ{=LH=WUe*fJ zgb>XI_QCAHQw%93c!pBwQ&vcLGHU%jnDoPG$kKwAqA;Zp3`v7i3z5aZDdb7nT){xd zF^9_Aupw0;N*pa-PGu0bmt0@9I~1E@5*HAet21y>e*5Lrq9L zXogm%M-b4;X5nB=gMFBID5!EW9DFf>`vrLJ0j}w+uFllCgcN1~| zxa$mVzMr3&!(YdI0O5HC$Om@`LCC3H8H4FTZZf2>lHdZkXh{CwW<2z7lbeG`WdprK zq{@S^BQ`;!LDGrMtoV3&GH{y!Ef)+L92x%{b{7dUgQn8{YggN10N6N33Vb+oLt9ad zoADCwsg5MTR}jl`7ud{*C!-+n1+J5xxXyc76ts+AXaFVfR7L^nPYR;zG`1kh+{CCL zMhY?1h|d3~X$P)_*rb1WO0^RV`P($3ATHcaa2R$SR2_dm7&={v10{6pv> zIwxFwfr&Ua4XzG`|Esfr?8}H-saM0#T_fs92D&=jKvnoWXuu{Iq(x<5=CaF2(V0asgW2pIUn?Vg4Kxj?4;Y_d5*!6t zhEpDK6Inn-4ZvX_p&NxE8U(Br*W?kU3>(0)QbZ}Jg*0E#49SvWfGo$TSQDgQZ`RuR z)DDvXpui1kK>3fS68^&UujB55h(8SpV$+<6U$?&m`S$-J1oaVteM)#L&y55}ke)i7 zE$AuaUjhw;3mN~fC?pY=|FF;y0}CqgH%$JXN&bpDjW{TT{4d|>VI`i1xX8aFzyKuj zW&I^UA*XRm6h)K=1~1#c1aP9$$xjJTc`}aw5Rjk%a8-W^;6(q{h*AKc@6?}H66j3x z7<5=(-M+|h=P$ly!R_x~UR(Bm$KM9?%s-O;UB&LnOYksV z=0C+C9f-K5WV1*)P7ua0yX4nTX z(7Rv;1@qxBr96ZWMW-KDwg9su&B_5YC^5iF>I0)8cE$7bW}n3|<8oZws8Q2!kr9|L zWqt)H0JUde*Dd}A%l55gj97FgW+ zDqORV5S49B*Y0v!dxNbdVaWRa_;x9b(h zF$Gad?6Xh+?Ges^tiiq5Xo=3x{cmn)e5HN`=9i1W8*oDl2NO`xU!R!1`wR8|0yFp{ z;f@%10N4Fi($!xO@jF+2>m${f%)TqBF>Fat!MLk(QG-vz5?+LAI|p8WdAgy2zjCPG zfEIyAQpXRQuGKt;h})K7;@MuORivqOP{^sn?D)*vxi|$YX@!DlE5>0J! zO&9gsH!ZA!S&UqFbI-nPIUnK&wD*lFp{rl=pEc-fF(j2Ilc#>I%Op!eE+#e`!a2PW zbR+G9=JRFZJjCDCzd$RD0k=NBe?Palu@8`6VPNAH6qi$e2Bu^1VFmWVB&hxJjkV>1 zn1rOXto&mIC5Wo}Q|R;mTrs?S`SO%k7JR>a`S%jxD;QhEhj@uVWLy|b54>$@Zf<67 zW@%w&VPS4zVrgM+X=XtnBG$nxxAMO??%vQ|)eeY@qH>Zl z+B-Zs$Sa+GpE)z%7ThvivwC=NH6ybP*V1EGbxb-nhdzFj03?5+asCy|`?M*^_d9Qi z*FERX`$8+>3D@+eU6+=dJxXq%mW56d;gi;YWKFyliI^(hWnjKVf-?K4_#|RyAsQbL ziBFx|ZiG!uyKNVxYxHMIX9%pJ&}G(+C*yvF=YW_NZ~GomE32#&v*&z#(grL#c8d1- zP2TpdM?H$0XFZ*$GhI7F`pXzTiex{`ff0JGWu|CZkKa?0jWPM z5amcX5MC>7_e{@glaXF&N;(5jXaplBRZMd^SrH%915=w5X2tDkQais)8?Rh}W@aNq ztVp_&k8lNSB)s2%M801kg}9@S!6g~nvr%Qsn*llpJ6p)AL*FVVnMrK%$=*kJz0fiN zv5Gvbe_LFQ-&^@+0o zU02WJcrq9Pj$pN0qIp*i`e9B7t&Nb}Aw=qa+6U`_C zM?rRnd|*rC#J5+AL8eZ!)OXt8b%)e^-hDhF|KDm z0eJJXEfk0|lg8f^YO=XjDtYi_??q@KIfPTZlG7W0%;QRUW^ie9qIEwSP(=wZ*eRZas+D9U`Re^;YgY$pE|t z6vK*lj-X0^dVNN=ESJj%7M?{I9t-k2hi|7&C3f>M!qwmxRn|TNz@JYL>)U7B@4=)G z52KpAaBEvN{&OBB*f+RCsmOYW(RQqQ!0t`M(&dqP(g_Ed748zM%A?G4z=VEbu-WK` z02|RA@5`j2RO0ep975iAC^m+ zS08^kGR+duJ3z%$J@jhpK03oGfpGF^dwXYnr7izpzGZ5ClwVr|;LRd;39LJr_)?(V zUm5bO{d-^2#R)RTx2&b>>YZkKf2pH@xz`H2ExD!cgf%@wXLLQkV7fzXD2Qnp$x zg^z&F^UR2kV8#*~X{S#{S0w2Jj`N@ge*5P0gGb-{-vF`)g*c;|%T!n2HcC;!jnP#s zLNBkp&(lz_bJe4Mw=EK$<$H%Ixc1ApGdabq12#b9Lwq^q?kxJ|PbPau1HO9kjRhYh zGNY-dF$L|3UxF!y7ude9)wek~KDxSsz9kCW`{N&j$Nzd;>G;9_+4#qEPh5U*G+8~w zlypd!njVm+S=EFY@%Vd8NbH@ZVt)n{6~AU(jl1BiO_+1fDOo?AN*14m2c8|~#H|yN1GH8H>?m5?m+J2-_@GVedvgv^S6l|t?ayUca@}b zd`fHsUNoK>Mcr7P;7Y*+m=|-!o3&pUe&F8ec^s0rhW3x+-$wZDrkCK8L|eXC<$Kyv zp&Na9r+SV5thBVDu>&siyNG}@FTdKV7CI?_mE( z=QzzfaF$O1ms-I&K?&HGalOjU5a!w@Y}zEnk{d z>h+n;!k(>)_DfQoq_`Twjsl$LD_gg~^!b=cK4%8n@u0Z^ijq*jYm2SCN_%otZ;~ z0qUL?;L2I}76`cx_{>PKU>aiVbb5z;XX~AM_+1?uC(3;=!9g}#ODZ9*#_xt`lexn|k{>44)u*^P_nFOb& zZ}0KE#~!Gp_@3WtSHAt=lLLQ;TGEsvxY=O-_8&F)ero)}-0sx{gPQnp#xQR|?De#< zn!`hh-S^&`y&I99p25AToG0G9$}{tw{Vx91GG+yaQ5owPw8qF<=uF&`F`IeqRYPe< zx0F@=C9Cu>s^0ESM4Z%~Uw&Kd=!)VAj7nH79i86O|7OgK@{bc%qBGxVoLo<5d>$=RD~cegnZS@CBoXI4jYy`^dq#HU9kcX5#MI zyMN4wcZ$}^ZM`t+nxW#Ro0(~EQXaar@jXs*&}HX{SHBW7al%o>gVn;#eJi^Rn=>$e zbIf9>dwUHUU1o8OP%ZlSYTo9u^5n!%-rO$T)S(rUd75oO_M0jpvqp66b}l?=(neX+ zB?`uyx(ANM%%l89m)fsM9md%EDs!3k8#ER_^voVK!Qy}c?HZ*Gp+ZIZOCBsMzb>@Z z4hlX9Xm;pIul{1_H{CbCH-X-%zBX-SeL=X&|3Qd*li`c$4W!^ojgjb6uS4~6UX?Mj zW2~X%`&rD2)DhX`>=?BD&w7!a1i1-bTD;cVwb0881+6z{!qaQAiqge~t;`ivmSAL^ zAJ}OT;jy)vm#i3y+r=Ix`ESpZN_Z5_3pE~`!(uTA21tR zm8k~OLv6yqVE*UaGj=>u32N|{PM#V{Ge*XA2*kRNSkef($aiA`swyx!l7YucmluzY z%EQBgAFhF?`9-)yZheCs+znI=%p<@2zrT?{{@gBQo{?|vEzDH8( z7G9ZzUc|Ybe5Mb#ycKS@xDh7P0~}Lg364B_5F<*F0I!pSg}B|3(5Y>AwS&1Cf$75} zBsYR7J#;EH^Y>3c_GkNIHK*ZRaf&zWvfx>Zu&QDMt&Yo=#oM7#V~v4z%YE~Y7o+Iv z(ZS~^3eGHFv{A{;2{tg^?Hyo|6hk+nYosbi;0LZaxB&{kklu)Q>_0i0=ndd!Ny1VI zlWqwSTr#yUy*z&=6ML`MS64m}_S(C8+WL=i)3<0di~a|yX^8E}7O|NRcxF3(8IC6- zU(Xj6b5ZpcjCJL?PJ9KB&osc?lBPZVdSS+nen$zG#rBDLNYvf2lz_YOl{P~N|vMwpps=jBu$=|kGs~e=h zX}{j7;dyS{((D1bRDd90TP6blg~rT237b2a8IE{#vArjLyxu)&Is5xqcBQA0@8!J3 z+Ma<=aE0ZS;~Y=Kk_VyIOX@_(SlZ^&z=L(lB4Z~#$%^!0rTGpz@_d%M$VN|j*OyD+l3urvi+OrhJEdTyCS*1>?k~wYzweoie zbCEKX`SEbb3ZJ&NP*_&woYcMhUSh^rfw#mr^2&-z!_>&0{{5k3+ht=3we82n`4P}` zk9^}9^|f8G5=C3wH2#bA9~48>GXY$By|vr*krqGPB;sg3*^Q;}N?WIql;QNnDBMY2 zw&9$~L$@zOB30?iWajw}u2N%`^m*Pxf*5Vd5ury=ftF*Jjsj*oT$V|bhs|eIp5|N1 zy-Tjt6^ZHrs0zC}BzXDRS%nqj3OUS8wi`k0WJgL*1a~=3HCqp>J?ONldy-nRp0Iu9pmy0vrr0&3dV8X*a$A@b zIQE5M_bTo!E#6K@JRG{Y^~wi*Q>LaUdC)tdcCKHSYS?rARimb6?S`d<_i;Dj!leJL z*H%R{4!Cp8FOo=#-poMA2JjN3KbDP>M51RD;>5+u3Vzqd5&CUwC0)x^KGU>hcb6YB zJ`gGj7TNcp)S0=P?V2VgOYf}I)w*J~$WAb$Dy9~2rT=N-Dc>5t@!y}_@jydcleg=F_(!%!YWro?4ec&! zo~_KxO|U<+MtTs1gxz6=TW=Cy4!;ppMrYrj2?g{rNXUc~TBv)Xbe7>I? z5>o7h_#)Z~o5AVKHCDz_M&bt8NX=$eCmQ-DQ6RI-F>SP z?9wfkiwn{B*TtiLKUnD(71|5aGruADL*hph4igPY>SS)?waABb3fj07s-n7$xxcqDJ=K={?QqMGf(PAoc3a?KcSoegfeRfe@;zr?0DL zVPUGLt)r{0W&Xm{+?YUkZnPN4rW6=WopooQ=YGqH^Jh0NQ%-d_T2sT) zm}KUK;r9j=N#WH;URnWjUnZn5S7{12hYt_y5Mz?wVVxy$F25D20$>cKGm#?~hSxZU zy^S=bjGX2*(W#PohW0D#Mi}qZi6``-{GG#(v(CmT6YXQ~nRuY2obV zMucAPx(d}&Uk+YAip}gf@u2mbwbVk0-$dk~zTz_b*De`>8{N)aSKZQeJl;4=CuiAK zKH_hS*K3B1wPYCPA;|V>s!J+suw? zO|#ub^!=Ic$`*NtA3B5z6)uG~yie(|sq@eL)5LMx{o(H7cN)f-lTm$T#uU!L$Ej49 zn8{$#l`}wm+Hzw}NVUzUWlgTNj_vU|#Hx0|rwRV>$rZVbcfzekSZuY)wj=Fc7pnV* zDn@x4pOSX#Loht>@>DrRC1bIG$j3VKG(lRAb8$so9+5Y?)UC*{i~33_bBOV$I_9^v zCQ3drrT6iSdvPpx4Jx&U+*t~E>g0DI@!a_0!Y9XJI5~C%LGLgWKFif!xNv~C2}Rek zE+q)Qr~`l4Og~L`i`p8$HjYv1+Q=GDzF54rYbN_VmwAyP0lhm`hHf>N>PtBoPIV1n zB8yHhG#vBmzdLb!(%a&vo}PotYiZrTT_QGO-kfbwWOrGe{^;cWoK@G;Mt&2YW|)@$ z02yGE9OzAXivWDM<4LtG2yO6_O;up*qTQ^qu@AnWSW9!U^7m4OEB%;A;iN!HKg*N}PeR_M9ea)!QDi)ysCxyzpG}y_nWud=ClM&@hQ*+S=#qv63z@w?j%p>ZL zCY5N)Q%q!1e{7U zDV9CSoPeylTgi}P%>a9zmGg8h3(VhQsf@lb^PT6^2jd%!zG_t;O>Ld3@vd!hgZjaX zjP?DLw!s8NsCU}F>!ifVj=M;5!$g1Bx__f#ZHjthehEu$Oe5lP&a8W2eL$>czs1>9A!J=qaO3^jQIjVRyo`rPek z#iN02?9mi{$P)8v&2skVkC9?4=B1w_OX3AbZSwgtk)Z*+?w`=1vEMPh>)93fki$9f zDCo2ourJ!8b)bo(8+Z2JnP=d*p0>4@yFBkCu=G2)cNbbVGcdgSDQtI78;#;^Nv)o- z7#p-*u#)zhL&Ar*ZQoV@=sg*G&@_$oxHN-ksEiV@VK;kd=>p!o?UfFczSFeVa2mv0 zqJ>wY=3?olQayDp_iP?5k+ZBZAL?`qj?p2cf@?5tQ;oYClQC=jkB-VWn@^UeFiCh; z(z!==OZmbYujAIuv_&p-H?L2o@N0P7T-ELRmHZ+vurO4;tpn+B6SaG?=Kgp90E~+6 z>+CZ4(%s*t^&Rj_gLpjq24Vb|?%I8u)DL%U5&t3~%hI7Z=DGWtb z3QPL$#^{26vkO=U9(mVOA)ehh8~W!~{S7}gwjo{*IrQwE#kji4*UQBSy-C`v2~?i9 zd53PspNUUZCg&!Nc1xIcNR8L6O7PS%4+%RuE+>cQ-E)wFjRC)2HA6ZBX?vw!o5fts z?arl2M#YKA zQ1nU5vw-&_{-X3u_A-N0inYOIY@~74xNdj&!i+(tHRbI?<{#X%X@U>R2+HeW{FqFa z`*&{G-EH^Fw6SCkiJFq@9L{cJTfF`ShDfZ{akrzXnBOxl*wUee334yIeR@13A@cJE zJMS6ZR_a%ZB-i3ka7zumgHod1-k)n8-gH1$&ODw~m8cR=E;JI=vpIaR^Nk`P$-J_v zisWVb{fM{Rgzn(zd=3Ap7I(e-w!K%ii`Qm2D^`}XV|Ai3mo{lHFtxs<3NKm=DmxNO zR`?x0q>*VPZQxGdSb)ykHjR8<^uDAaqwheIzS%DZik zocN^@d>WC(-M3NL+xoG5PW2+=G7PFoZQ#9TLeE%jX~5+R3v{)+D@NXT~gd^VC-MBBGW?H=$#=u z%o9*e#8ammB7!-9m+PkU$cbvV|Kp$`a@+SV@(jI~4gGE&KJzY39cqjfbw=MhOZ(mW zxk0~m!is-YN|VeH6S_zHC|}pUM{V2G5o_mYsDFLHFqD=f)SAI4V^jC!_U=`nl|sPO zE|_b*htItZsm{s-yLIpIZJNH$l3lmbur$T1hCg1N)If8K8+D79E63oslWfeT65Nv$ z58Rg>_tIp1)bHIL@ls=!03Q+e);dDRQDZZkx10~=)eM!f4zS8JE1+m}c>l=V6Y2dndM(9mr_U$p5}jXiL@c)o7AMW7WL!?vPpJ zk{hNVJK^xdrZ^&Kzn7<HuUNX}tKKj*sPhInpt)~vjhHAu()C_+C=Th& zuW__|JG44;wl4q7bF;X?m%~xHa^NX^tb!?D%I7$NJp@|OPLbQRaBAo zGEgl(16-h4$cRe}I8tKAVFD}OIlAp$c;+0SX}^_i&M)1lLo>-5sY3yT=ijRUxRMuu zwe_PIpE0DKYV|{#ksLuE5h>|(G%QUmS+Z_!-~SkQL6)z%;&bk0NuS1T+*ZV`qlby* z8&SAFC;UFjzf02A1^X;9X|pw>^%vTxac7&6Db_#kM1Sq4V3jOIB5OG<*vJXBe;N?p?hQr_gmKgVmX= zq4+_SmyHj?BaeI%Y56TxF&m;iUW|ym)`nDZ(AZP$lX2(?f|n)D+ihp7KTwfzw3GM5 ze&!b_Qu}RtbT(q*^dL7P*Iu?EECLR5Av5Ausa4Y7rUifZ) z-ZKEPv4J%74c|qCL}tjmV?J1?zS*xhLf6{-3lXqAd1DYWU%(|^b43(*`MHYpw(~Qo zrS(oUW=czP@wHg>BsMLFt+GZSl|31y;p%e3hLVrnWZY^5h8%(yo3S4?pR$(w8O`fnB z)EHUxgciujr~YNZnA2$!J-|z_5gCi%)Y~m~Xvp{2A2k8f$%3X+>CK*F{>)SXe`BY_ zIu5Kx=o)U21IR(`aP4(X&Wt)%L`n|V?zLTdcthuSU=}sd z=U*y(lBx8oLSzq4Uh}5-p(#3b%C^)uD#p^ihvwmKL?O=j`1mBx6b7V-q@rHy)`ee? zTWr9ziA6AVc_I$J9o$pIwPot?IMyZZ9eBp`j`%Fec&6X5n2~XrKP+3rue|ddoJ#I# zkJQMt_4J66G02ca%>?h(xsKeQ;VQM!>CFpXm%RC?!FR_4u`Ha+mmb<(d0@gavzsZs zW}Lpw-#Q=#b5Wqt)M-dtm-HS*3DGE+QPK8D6Lm1 zCv&@{9g$i2$bVco>2^0`DJQ>j$7i*B;bn^1k&BeMhP~0>#jFAoFf(t3{JrcrH_b=t zV8;(<4>1)r1k>XhV^~8)#*$B*1Vt}=T4!+7_R`v5gAOOa(}q0taL8M+Q^29UB2a(EyjNQ`<(BcuOi^Il8MNOj5Qf%jiq?&<4ryH=p;*K`q6H6qNElV$SKqRFpz zCiRizS=e)nn{lF35%px(N|vBQkGUk>LaB~V;DLn?7pc2<+X$DeVK&~VcR!Cu&bwOO zRNuw+6@O2w(q4LQO}g;jLwX=nKYyjHZJF`LAg4W}u0UG(aQPcg9l6`Z(_H z<*E_Qk%BvI?Qf1YR_gKkInG9meAV?=Zv4>>eK0NGNTs-|kaE>i$KCTGzK}`uQRUC^ z<4(pScU(-jM~saD1M=+OyMuFe!vAx30RC47IL~lb={$kJKzQCDG69JfYR`RPu=#Mw z1@Unm>&fA`m;2gqHZRbbsdC)u(b1FXnH5C1wdYi5U1gK7>8h&dI)r)}ve)8stD%}x z(?K(UU?W3w`;I5$gE_8|o!vXq>5^CH7Is{1G;7DQcOo?A29!%j_3@4yYG2#QshW(w zT%**;>qYkTN?EDSul*@*zH2tp@v}ihY9?pE$Mn-GFVwEw?SWbeUX_$IwZ-%}XV<}9n1>~v!NhmK6#bdrBnhW5QV1LX8LYUysaJSynDzg8Tu*PB29DFCeM*m}K*z@D*_&{S8?~E5ag&%kQ(bl6o`s>ZL=>3$# zhHz$P^<{Dnt+9kp*>`bsRC%EUN1AsJqIh5nriFY~k1KzR&))mAiwl?}i>Xl_ zGa9cgrTdua_~D3o=pnq?g~x~!-8-L3kO*c76>~IsR$VbS&^q5OTb~+e*h8ObT(QEq zrZ?XLj7Es>K-TVrFYh%NODi53g>{Rkb+ujlB|qj6O&I9K=W4ZL^p+`bh3H>6 zA1(M>U|Q3cdF|3gu3u@+>n1PZ39@>q=5sG6utn@xM4~>qSO{w#pc`B0V#A=GIkiUE z7Z^Sgn)X|oEB1umiYZK~mn-2#QM8G)53gY(wg%VC`W@A(Slrp^mi)G@gJ#Pceecdp zDNO9ez%m9#Smv#zw!5Pw?iCbuc}G~Pz7V9WJu~Ce(KN5%fTuBbhIY3UiZnbdW~MIHMGXB0TQCg*mwzb=~eVsEJH4)rg*ww!FkFi0Ow gSh=mPVc}b=#!gSaY&!-D;jmUmgKPW-;6JPW7ZLiYX#fBK literal 0 HcmV?d00001 diff --git a/Resources/Audio/Items/Handcuffs/rope_start.ogg b/Resources/Audio/Items/Handcuffs/rope_start.ogg new file mode 100644 index 0000000000000000000000000000000000000000..7a7720530f17bb96f7f4e6e9f5c5d46c706d92f5 GIT binary patch literal 48070 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 literal 0 HcmV?d00001 diff --git a/Resources/Audio/Items/Handcuffs/rope_takeoff.ogg b/Resources/Audio/Items/Handcuffs/rope_takeoff.ogg new file mode 100644 index 0000000000000000000000000000000000000000..47adf2fca83d8e1aa12a89a3bc3b342f48020445 GIT binary patch literal 75225 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�A9B@S&|iY$p}$-6P-fsgtp;e-0&Uvd+d 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 literal 0 HcmV?d00001 diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index aabc6022cc..241fac47ca 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -90,6 +90,10 @@ color: "#e8b59b" sprite: Mobs/Species/Human/parts.rsi state: r_foot + - map: ["enum.HumanoidVisualLayers.Handcuffs"] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 - map: ["enum.Slots.IDCARD"] - map: ["enum.Slots.GLOVES"] - map: ["enum.Slots.SHOES"] @@ -144,6 +148,7 @@ - type: BuckleVisualizer - type: CombatMode - type: Climbing + - type: Cuffable - type: Teleportable - type: CharacterInfo - type: FootstepSound diff --git a/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml b/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml index 74b2955efe..51544fabf1 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml @@ -1,9 +1,17 @@ - type: entity name: handcuffs - description: Just a prop for screenshots for now, sorry! + description: Used to detain criminals and other assholes. id: Handcuffs parent: BaseItem components: + - type: Handcuff + cuffTime: 3.0 + uncuffTime: 3.0 + stunBonus: 2.0 + breakoutTime: 20.0 + cuffedRSI: Objects/Misc/handcuffs.rsi + iconState: body-overlay + - type: Sprite sprite: Objects/Misc/handcuffs.rsi state: handcuff @@ -18,10 +26,29 @@ - type: entity - name: cable restraints + name: makeshift handcuffs + description: Homemade handcuffs crafted from spare cables. id: Cablecuffs parent: Handcuffs components: + - type: Handcuff + cuffTime: 3.5 + uncuffTime: 3.5 + stunBonus: 2.0 + breakoutTime: 15.0 + cuffedRSI: Objects/Misc/cablecuffs.rsi + bodyIconState: body-overlay + color: red + breakOnRemove: true + brokenIconState: cuff-broken + brokenName: broken cables + brokenDesc: These cables are broken in several places and don't seem very useful. + startCuffSound: /Audio/Items/Handcuffs/rope_start.ogg + endCuffSound: /Audio/Items/Handcuffs/rope_end.ogg + startUncuffSound: /Audio/Items/Handcuffs/rope_start.ogg + endUncuffSound: /Audio/Items/Handcuffs/rope_breakout.ogg + startBreakoutSound: /Audio/Items/Handcuffs/rope_takeoff.ogg + - type: Sprite sprite: Objects/Misc/cablecuffs.rsi state: cuff @@ -30,7 +57,9 @@ - type: Icon sprite: Objects/Misc/cablecuffs.rsi state: cuff + color: red - type: Clothing sprite: Objects/Misc/cablecuffs.rsi - Slots: [belt] + color: red + Slots: [belt] \ No newline at end of file diff --git a/Resources/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png b/Resources/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png new file mode 100644 index 0000000000000000000000000000000000000000..3536af53b2f274523d12771649c7ee95bae5c873 GIT binary patch literal 3286 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 UnsMRNT>t<807*qoM6N<$f?oq3mH+?% literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png b/Resources/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png new file mode 100644 index 0000000000000000000000000000000000000000..6879bd394fd6dc3f84de4bc3b3403d4a5612aa4e GIT binary patch literal 97 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 S62p)`ZDV~4y=tiAi~j-<+>$f^ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/cablecuffs.rsi/body-overlay-4.png b/Resources/Textures/Objects/Misc/cablecuffs.rsi/body-overlay-4.png new file mode 100644 index 0000000000000000000000000000000000000000..ff0d5837776a6568ac794225f9e860263180cdee GIT binary patch literal 3101 zcmZWrc{CJ!7yeCT-;y=)+OlO|vom8~!)S=I8E0&kKc-^M-fIhqT7>oeMLu-t!IGp4mtwu7r4pl+sNNH03x zkL+ykm|K+AiJ%kq2P`zz?gEcagdpG$t)X#Uu$j6EK!S$pX+X0%wpsRS>>2YKG|Lf! ziGnl-rra>4!LR|HkR)g!&@q5uaG4@DAdeC__qw?*fSXdlSu}ik7+`RRS)mZ%@JfIk zl9vMbFMGu40WWn>HgM~?KCqJoY@Vj=s$g6S$QmO&jX-5BXeP1HR{PI-xNCi+M5Yl{NBxBC&Y|1VdT(Whf zTZ6N>}_TEb+zT)t^ba z)%FvUra$u^7n&W-EnMIf~F}- zMsn8A;rzBnyxW=&FQ~>k8q}qzcLDil$1x3ta<3w)tTewd)WC?vDp#Tm-q3$_50uMJ z=E%c-efaT;W}?u8TX(nkRX*?1#@Dh9>eg;o(yJOo6PcFG@6INkht6;NGLi;Vf)E1Asxo*Q_ zgDg=mj>RL3{ovZ~(DoD{=igCqrf7)xUwM<}(6eCVHe z`b45W^MtoPK@(q+uL@HzQ9x8%@4WKVre4SlILryOpj{Cw%Gg0apJCe)pBLvTR5T;X zc{uvu(=*cTN{vdbN)6W=l`A%iaD|Qf_4Y>*OuC&WqK=}5$Yjz*+=|VF&2$l65r?^^ z+Fpe;-lu>kzg`dZ`9aZUW^U76Os<(<(M)jT%Ac!YGAHm8-#ru$ppX@+;UC>=<^o*7 zA=Fj}O_@ZmG>=lX5n-!@lgMugW0p z23Q8h6&47Wd&S$maW>dtLnJ4X_$YEGE+O9ZS+8`4mNb^9W4fzx(Ijv$;Lcpg&>jw3 zjcwg1CcocQ-t^v#oqFA;_*y*IUg6Q#wl3IrtEEA0`qyc)KC@+LOPFN@vSvj{#!V&# zQ9f2#TY$nJxNLE1?Q1=Jy`Z(Ap)w;sWAD_b(p=kB+iTh8m+KTg3kw%Ib3Y3?8w5p| zYY-$we1vX6kf#ElleE>y9{W`j8@h>kL|t~>JAK?| zoKI@qjV|ISk!F=tOV=@rv$*l{;rEr-RBM!re3|kh0E!ndPbDDN zkjtJ=ddobbt4_Z>NT`+JOQY`gseVy*(O!y<04@9hzb!Tv-SFHgSC})?j@lh#uOpNYq#83c2BiRBOsVex4?e!ddw}VGL28q2@<{| zmZckrj0$w4NQk*l#NC#jXDu}lpLKd$x~1r_^{KIqJC=4b_OmrEe`0VvTI})9E7-`K z8SLTXVpBm&Jh6T~pR=Z3eJm+$C%O-K4tcIF&q(DuFs0)&mNS?hFiuQc-@$j`f8ffSL(?@bV}}n0H+e!E_7#7QkR2Kh zPPDq4$hHt$^AXPxk0axw>I3bHc>L;N`jPLQatq&pjZTUoiW6!fCc+r;+X(NFCfquC z^!mCXQ|Dhzr?@SY6r(bu?cX^a^Fsc5!HcU0w6h0%jB1ll5<|=8sE7Z0!i(gUxd+YO z&Njf>WFI-WI@DSAX$_99&4e5qlK*1PQPi*xHrau9drz)D8%{-T4^#*m4?>}+2FLRv!Ocyn~ojPb~v3H-B8Jsdapo)$hI8E70W`r>34!Qe^HNyD3PK z&;Lz+HzR9v079+-fQba)_t`nG0`OQCfHh|T)G`6!^n2;_#Q*?^k+GgGJZSRgq$yLR zEQ9{w&nfILR!x*>f&5&?xRvOX?mN7AgMS_stqRJ|kB{+O{olC3i(eC*7aZ#A>;Ji3 zOKyn!gs;P=b8_t%)@F5wGpHyqCwy`=%Sk5NvRG`#GNTp4y^w-KgzMjrCM*qdEOEr5 zV_i(iG5J50)f}91@~z6^QKiSU7$-D`v*^T0r=_72g(qps2)*B9OyM|!!74l{hOlF_ z!k53hp@~%O60?8Z*PB>&+pCy@{5kHM77F7mmTszh9lo@~PxdA}mNWt6#eD{n|k>s#nmK%Jlc7f<7`*#H0l literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Misc/cablecuffs.rsi/cuff-broken.png b/Resources/Textures/Objects/Misc/cablecuffs.rsi/cuff-broken.png new file mode 100644 index 0000000000000000000000000000000000000000..3da396ef0fef14d3a5aeec0aaa891a1c117c1339 GIT binary patch literal 3104 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 FV1jrvk<$PG diff --git a/Resources/Textures/Objects/Misc/cablecuffs.rsi/inhand-left.png b/Resources/Textures/Objects/Misc/cablecuffs.rsi/inhand-left.png index 3ed7f9d40f7992f691d8f1f06e0ea68757187057..d311c0d128e8595313f65db82d87ee950f2a4a56 100644 GIT binary patch delta 3003 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 delta 176 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 rFV%H1|JC`sQ1XA66{}c}YZ{FX Date: Tue, 25 Aug 2020 15:09:17 +0200 Subject: [PATCH 61/88] Update submodule --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index ad2545d83d..584e540bb2 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit ad2545d83da3bad0e87b2b28c1a0d3088f3ebce2 +Subproject commit 584e540bb280ce07c4d26b1202f8b05c936663f6 From 80940a8d8a729b82045802f9dc987f978abfa375 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Tue, 25 Aug 2020 15:26:33 +0200 Subject: [PATCH 62/88] Add restart round command test (#1897) --- .../Tests/Commands/RestartRoundTest.cs | 62 +++++++++++++++++++ .../GameTicking/GameTickerCommands.cs | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs diff --git a/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs b/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs new file mode 100644 index 0000000000..83e4d111bb --- /dev/null +++ b/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs @@ -0,0 +1,62 @@ +using System.Threading.Tasks; +using Content.Server.GameTicking; +using Content.Server.Interfaces.GameTicking; +using NUnit.Framework; +using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Timing; + +namespace Content.IntegrationTests.Tests.Commands +{ + [TestFixture] + [TestOf(typeof(NewRoundCommand))] + public class RestartRoundTest : ContentIntegrationTest + { + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task RestartRoundAfterStart(bool lobbyEnabled) + { + var server = StartServer(); + + await server.WaitIdleAsync(); + + var gameTicker = server.ResolveDependency(); + var configManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + + await server.WaitRunTicks(30); + + GameTick tickBeforeRestart = default; + + server.Assert(() => + { + configManager.SetCVar("game.lobbyenabled", lobbyEnabled); + + Assert.That(gameTicker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); + + tickBeforeRestart = entityManager.CurrentTick; + + var command = new NewRoundCommand(); + command.Execute(null, null, new string[] { }); + + if (lobbyEnabled) + { + Assert.That(gameTicker.RunLevel, Is.Not.EqualTo(GameRunLevel.InRound)); + } + }); + + await server.WaitIdleAsync(); + await server.WaitRunTicks(5); + + server.Assert(() => + { + var tickAfterRestart = entityManager.CurrentTick; + + Assert.That(tickBeforeRestart < tickAfterRestart); + }); + + await server.WaitRunTicks(60); + } + } +} diff --git a/Content.Server/GameTicking/GameTickerCommands.cs b/Content.Server/GameTicking/GameTickerCommands.cs index 2e3b0d78d0..b8d073af9e 100644 --- a/Content.Server/GameTicking/GameTickerCommands.cs +++ b/Content.Server/GameTicking/GameTickerCommands.cs @@ -99,7 +99,7 @@ namespace Content.Server.GameTicking } } - class NewRoundCommand : IClientCommand + public class NewRoundCommand : IClientCommand { public string Command => "restartround"; public string Description => "Moves the server from PostRound to a new PreRoundLobby."; From f436429a8191a5b98f226bb0f201c9f6d61ac9dc Mon Sep 17 00:00:00 2001 From: Exp Date: Tue, 25 Aug 2020 16:10:54 +0200 Subject: [PATCH 63/88] Disable Win cvar (#1909) * Disable Win cvar * We positive boys --- Content.Server/GameTicking/GameRules/RuleDeathMatch.cs | 5 +++++ Content.Server/GameTicking/GameRules/RuleSuspicion.cs | 5 +++++ Content.Server/GameTicking/GameTicker.cs | 2 ++ 3 files changed, 12 insertions(+) diff --git a/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs b/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs index 7c59c9dd04..dbcd8442b3 100644 --- a/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs +++ b/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs @@ -7,6 +7,7 @@ using Robust.Server.Interfaces.Player; using Robust.Server.Player; using Robust.Shared.Enums; using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Timer = Robust.Shared.Timers.Timer; @@ -25,6 +26,7 @@ namespace Content.Server.GameTicking.GameRules [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IGameTicker _gameTicker = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; private CancellationTokenSource _checkTimerCancel; @@ -53,6 +55,9 @@ namespace Content.Server.GameTicking.GameRules { _checkTimerCancel = null; + if (!_cfg.GetCVar("game.enablewin")) + return; + IPlayerSession winner = null; foreach (var playerSession in _playerManager.GetAllPlayers()) { diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs index 7a870375bd..b67888d6f0 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -12,6 +12,7 @@ using Robust.Server.Interfaces.Player; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.Configuration; using Robust.Shared.IoC; using Timer = Robust.Shared.Timers.Timer; @@ -28,6 +29,7 @@ namespace Content.Server.GameTicking.GameRules [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IGameTicker _gameTicker = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; private readonly CancellationTokenSource _checkTimerCancel = new CancellationTokenSource(); @@ -54,6 +56,9 @@ namespace Content.Server.GameTicking.GameRules private void _checkWinConditions() { + if (!_cfg.GetCVar("game.enablewin")) + return; + var traitorsAlive = 0; var innocentsAlive = 0; diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 0f3bf337a7..19383c0f37 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -135,6 +135,8 @@ namespace Content.Server.GameTicking _configurationManager.RegisterCVar("game.defaultpreset", "Suspicion", CVar.ARCHIVE); _configurationManager.RegisterCVar("game.fallbackpreset", "Sandbox", CVar.ARCHIVE); + _configurationManager.RegisterCVar("game.enablewin", true, CVar.CHEAT); + PresetSuspicion.RegisterCVars(_configurationManager); _netManager.RegisterNetMessage(nameof(MsgTickerJoinLobby)); From 4f8fbe2749a09dd0d06edd2df4176d1bb7d2e420 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Tue, 25 Aug 2020 16:14:26 +0200 Subject: [PATCH 64/88] Fix parallel tests unreliably failing due to statics in Atmospherics (#1914) * Fix atmospherics statics unreliably failing parallel tests * Cache getting atmosphere system --- .../EntitySystems/AtmosphereSystem.cs | 10 +++++ .../EntitySystems/GasTileOverlaySystem.cs | 25 +++++------ .../Tests/Atmos/ConstantsTest.cs | 10 ++++- Content.Server/Atmos/AtmosCommands.cs | 6 ++- Content.Server/Atmos/GasMixture.cs | 15 +++++-- .../Components/Atmos/GasAnalyzerComponent.cs | 6 ++- .../Atmos/GasTileOverlaySystem.cs | 11 +++-- .../EntitySystems/AtmosphereSystem.cs | 8 +--- Content.Shared/Atmos/Atmospherics.cs | 39 +---------------- .../Atmos/SharedAtmosphereSystem.cs | 43 +++++++++++++++++++ .../Atmos/SharedGasTileOverlaySystem.cs | 7 ++- 11 files changed, 107 insertions(+), 73 deletions(-) create mode 100644 Content.Client/GameObjects/EntitySystems/AtmosphereSystem.cs create mode 100644 Content.Shared/GameObjects/EntitySystems/Atmos/SharedAtmosphereSystem.cs diff --git a/Content.Client/GameObjects/EntitySystems/AtmosphereSystem.cs b/Content.Client/GameObjects/EntitySystems/AtmosphereSystem.cs new file mode 100644 index 0000000000..9b6f57f06d --- /dev/null +++ b/Content.Client/GameObjects/EntitySystems/AtmosphereSystem.cs @@ -0,0 +1,10 @@ +using Content.Shared.GameObjects.EntitySystems.Atmos; +using JetBrains.Annotations; + +namespace Content.Client.GameObjects.EntitySystems +{ + [UsedImplicitly] + public class AtmosphereSystem : SharedAtmosphereSystem + { + } +} diff --git a/Content.Client/GameObjects/EntitySystems/GasTileOverlaySystem.cs b/Content.Client/GameObjects/EntitySystems/GasTileOverlaySystem.cs index 253563f527..a2a7bf5079 100644 --- a/Content.Client/GameObjects/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Client/GameObjects/EntitySystems/GasTileOverlaySystem.cs @@ -2,12 +2,9 @@ using System; using System.Collections.Generic; using Content.Client.Atmos; -using Content.Client.GameObjects.Components.Atmos; using Content.Shared.Atmos; using Content.Shared.GameObjects.EntitySystems.Atmos; using JetBrains.Annotations; -using Robust.Client.GameObjects; -using Robust.Client.GameObjects.EntitySystems; using Robust.Client.Graphics; using Robust.Client.Interfaces.Graphics.Overlays; using Robust.Client.Interfaces.ResourceManagement; @@ -43,19 +40,23 @@ namespace Content.Client.GameObjects.EntitySystems private readonly float[][] _fireFrameDelays = new float[FireStates][]; private readonly int[] _fireFrameCounter = new int[FireStates]; private readonly Texture[][] _fireFrames = new Texture[FireStates][]; - - private Dictionary> _tileData = + + private Dictionary> _tileData = new Dictionary>(); + private AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { base.Initialize(); SubscribeNetworkEvent(HandleGasOverlayMessage); _mapManager.OnGridRemoved += OnGridRemoved; + _atmosphereSystem = Get(); + for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { - var overlay = Atmospherics.GetOverlay(i); + var overlay = _atmosphereSystem.GetOverlay(i); switch (overlay) { case SpriteSpecifier.Rsi animated: @@ -90,7 +91,7 @@ namespace Content.Client.GameObjects.EntitySystems _fireFrameDelays[i] = state.GetDelays(); _fireFrameCounter[i] = 0; } - + var overlayManager = IoCManager.Resolve(); if(!overlayManager.HasOverlay(nameof(GasTileOverlay))) overlayManager.AddOverlay(new GasTileOverlay()); @@ -104,7 +105,7 @@ namespace Content.Client.GameObjects.EntitySystems chunk.Update(data, indices); } } - + // Slightly different to the server-side system version private GasOverlayChunk GetOrCreateChunk(GridId gridId, MapIndices indices) { @@ -113,7 +114,7 @@ namespace Content.Client.GameObjects.EntitySystems chunks = new Dictionary(); _tileData[gridId] = chunks; } - + var chunkIndices = GetGasChunkIndices(indices); if (!chunks.TryGetValue(chunkIndices, out var chunk)) @@ -151,16 +152,16 @@ namespace Content.Client.GameObjects.EntitySystems { if (!_tileData.TryGetValue(gridIndex, out var chunks)) return Array.Empty<(Texture, Color)>(); - + var chunkIndex = GetGasChunkIndices(indices); if (!chunks.TryGetValue(chunkIndex, out var chunk)) return Array.Empty<(Texture, Color)>(); - + var overlays = chunk.GetData(indices); if (overlays.Gas == null) return Array.Empty<(Texture, Color)>(); - + var fire = overlays.FireState != 0; var length = overlays.Gas.Length + (fire ? 1 : 0); diff --git a/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs b/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs index eac4460b8c..dd14e1b333 100644 --- a/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using System.Threading.Tasks; +using Content.Server.GameObjects.EntitySystems; using Content.Shared.Atmos; using NUnit.Framework; @@ -10,13 +12,17 @@ namespace Content.IntegrationTests.Tests.Atmos public class ConstantsTest : ContentIntegrationTest { [Test] - public void TotalGasesTest() + public async Task TotalGasesTest() { var server = StartServerDummyTicker(); + await server.WaitIdleAsync(); + + var atmosSystem = server.ResolveDependency(); + server.Post(() => { - Assert.That(Atmospherics.Gases.Count(), Is.EqualTo(Atmospherics.TotalNumberOfGases)); + Assert.That(atmosSystem.Gases.Count(), Is.EqualTo(Atmospherics.TotalNumberOfGases)); Assert.That(Enum.GetValues(typeof(Gas)).Length, Is.EqualTo(Atmospherics.TotalNumberOfGases)); }); diff --git a/Content.Server/Atmos/AtmosCommands.cs b/Content.Server/Atmos/AtmosCommands.cs index c53a0862d8..1db4ac0dc0 100644 --- a/Content.Server/Atmos/AtmosCommands.cs +++ b/Content.Server/Atmos/AtmosCommands.cs @@ -1,9 +1,11 @@ #nullable enable using System; using Content.Server.GameObjects.Components.Atmos; +using Content.Server.GameObjects.EntitySystems; using Content.Shared.Atmos; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; @@ -58,7 +60,9 @@ namespace Content.Server.Atmos public string Help => "listgases"; public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) { - foreach (var gasPrototype in Atmospherics.Gases) + var atmosSystem = EntitySystem.Get(); + + foreach (var gasPrototype in atmosSystem.Gases) { shell.SendText(player, $"{gasPrototype.Name} ID: {gasPrototype.ID}"); } diff --git a/Content.Server/Atmos/GasMixture.cs b/Content.Server/Atmos/GasMixture.cs index f886acad84..43665b6715 100644 --- a/Content.Server/Atmos/GasMixture.cs +++ b/Content.Server/Atmos/GasMixture.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Content.Server.Atmos.Reactions; +using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Shared.Atmos; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Serialization; using Robust.Shared.IoC; using Robust.Shared.Prototypes; @@ -20,12 +22,17 @@ namespace Content.Server.Atmos [Serializable] public class GasMixture : IExposeData, IEquatable, ICloneable { + private readonly AtmosphereSystem _atmosphereSystem; + [ViewVariables] private float[] _moles = new float[Atmospherics.TotalNumberOfGases]; [ViewVariables] private float[] _molesArchived = new float[Atmospherics.TotalNumberOfGases]; + + [ViewVariables] private float _temperature = Atmospherics.TCMB; + public IReadOnlyList Gases => _moles; [ViewVariables] @@ -51,7 +58,7 @@ namespace Content.Server.Atmos for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { - capacity += Atmospherics.GetGas(i).SpecificHeat * _moles[i]; + capacity += _atmosphereSystem.GetGas(i).SpecificHeat * _moles[i]; } return MathF.Max(capacity, Atmospherics.MinimumHeatCapacity); @@ -68,7 +75,7 @@ namespace Content.Server.Atmos for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { - capacity += Atmospherics.GetGas(i).SpecificHeat * _molesArchived[i]; + capacity += _atmosphereSystem.GetGas(i).SpecificHeat * _molesArchived[i]; } return MathF.Max(capacity, Atmospherics.MinimumHeatCapacity); @@ -124,6 +131,7 @@ namespace Content.Server.Atmos public GasMixture() { + _atmosphereSystem = EntitySystem.Get(); } public GasMixture(float volume) @@ -131,6 +139,7 @@ namespace Content.Server.Atmos if (volume < 0) volume = 0; Volume = volume; + _atmosphereSystem = EntitySystem.Get(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -274,7 +283,7 @@ namespace Content.Server.Atmos if (!(MathF.Abs(delta) >= Atmospherics.GasMinMoles)) continue; if (absTemperatureDelta > Atmospherics.MinimumTemperatureDeltaToConsider) { - var gasHeatCapacity = delta * Atmospherics.GetGas(i).SpecificHeat; + var gasHeatCapacity = delta * _atmosphereSystem.GetGas(i).SpecificHeat; if (delta > 0) { heatCapacityToSharer += gasHeatCapacity; diff --git a/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs b/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs index a8d61d1f41..9c4c0c44d9 100644 --- a/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs @@ -159,7 +159,8 @@ namespace Content.Server.GameObjects.Components.Atmos pos = _position.Value; } - var gam = EntitySystem.Get().GetGridAtmosphere(pos.GridID); + var atmosSystem = EntitySystem.Get(); + var gam = atmosSystem.GetGridAtmosphere(pos.GridID); var tile = gam?.GetTile(pos).Air; if (tile == null) { @@ -174,9 +175,10 @@ namespace Content.Server.GameObjects.Components.Atmos } var gases = new List(); + for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { - var gas = Atmospherics.GetGas(i); + var gas = atmosSystem.GetGas(i); if (tile.Gases[i] <= Atmospherics.GasMinMoles) continue; diff --git a/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs index 47f0bc50f3..41f2c4f4fc 100644 --- a/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs @@ -1,10 +1,8 @@ #nullable enable using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using Content.Server.GameObjects.Components.Atmos; -using Content.Server.Interfaces.GameTicking; using Content.Shared.Atmos; using Content.Shared.GameObjects.EntitySystems.Atmos; using JetBrains.Annotations; @@ -15,8 +13,6 @@ using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Timing; -using Robust.Shared.IoC; -using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Timing; @@ -58,10 +54,13 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos ///

private float _updateCooldown; + private AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { base.Initialize(); + _atmosphereSystem = Get(); _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; _mapManager.OnGridRemoved += OnGridRemoved; _configManager.RegisterCVar("net.gasoverlaytickrate", 3.0f); @@ -164,8 +163,8 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos for (byte i = 0; i < Atmospherics.TotalNumberOfGases; i++) { - var gas = Atmospherics.GetGas(i); - var overlay = Atmospherics.GetOverlay(i); + var gas = _atmosphereSystem.GetGas(i); + var overlay = _atmosphereSystem.GetOverlay(i); if (overlay == null || tile?.Air == null) continue; var moles = tile.Air.Gases[i]; diff --git a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs index a5ffa6e9ce..9037243434 100644 --- a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs @@ -1,13 +1,9 @@ #nullable enable -using System; -using System.Collections.Generic; using Content.Server.Atmos; +using Content.Shared.GameObjects.EntitySystems.Atmos; using JetBrains.Annotations; using Robust.Server.Interfaces.Timing; -using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.Map; -using Robust.Shared.GameObjects.Systems; -using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Map; @@ -15,7 +11,7 @@ using Robust.Shared.Map; namespace Content.Server.GameObjects.EntitySystems { [UsedImplicitly] - public class AtmosphereSystem : EntitySystem + public class AtmosphereSystem : SharedAtmosphereSystem { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPauseManager _pauseManager = default!; diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index c39b6b7d5d..dbd6bf073f 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -1,45 +1,10 @@ -using System.Collections.Generic; -using Robust.Shared.IoC; -using Robust.Shared.Prototypes; -using Robust.Shared.Utility; - -namespace Content.Shared.Atmos +namespace Content.Shared.Atmos { /// /// Class to store atmos constants. /// - public static class Atmospherics + public class Atmospherics { - static Atmospherics() - { - var protoMan = IoCManager.Resolve(); - - GasPrototypes = new GasPrototype[TotalNumberOfGases]; - GasOverlays = new SpriteSpecifier[TotalNumberOfGases]; - - for (var i = 0; i < TotalNumberOfGases; i++) - { - var gasPrototype = protoMan.Index(i.ToString()); - GasPrototypes[i] = gasPrototype; - - if(string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayTexture)) - GasOverlays[i] = new SpriteSpecifier.Texture(new ResourcePath(gasPrototype.GasOverlayTexture)); - - if(!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState)) - GasOverlays[i] = new SpriteSpecifier.Rsi(new ResourcePath(gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState); - } - } - - private static readonly GasPrototype[] GasPrototypes; - - public static GasPrototype GetGas(int gasId) => GasPrototypes[gasId]; - public static GasPrototype GetGas(Gas gasId) => GasPrototypes[(int) gasId]; - public static IEnumerable Gases => GasPrototypes; - - private static readonly SpriteSpecifier[] GasOverlays; - - public static SpriteSpecifier GetOverlay(int overlayId) => GasOverlays[overlayId]; - #region ATMOS /// /// The universal gas constant, in kPa*L/(K*mol) diff --git a/Content.Shared/GameObjects/EntitySystems/Atmos/SharedAtmosphereSystem.cs b/Content.Shared/GameObjects/EntitySystems/Atmos/SharedAtmosphereSystem.cs new file mode 100644 index 0000000000..541a30d859 --- /dev/null +++ b/Content.Shared/GameObjects/EntitySystems/Atmos/SharedAtmosphereSystem.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Content.Shared.Atmos; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.GameObjects.EntitySystems.Atmos +{ + public class SharedAtmosphereSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + private readonly GasPrototype[] GasPrototypes = new GasPrototype[Atmospherics.TotalNumberOfGases]; + + private readonly SpriteSpecifier[] GasOverlays = new SpriteSpecifier[Atmospherics.TotalNumberOfGases]; + + public override void Initialize() + { + base.Initialize(); + + for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) + { + var gasPrototype = _prototypeManager.Index(i.ToString()); + GasPrototypes[i] = gasPrototype; + + if(string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayTexture)) + GasOverlays[i] = new SpriteSpecifier.Texture(new ResourcePath(gasPrototype.GasOverlayTexture)); + + if(!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState)) + GasOverlays[i] = new SpriteSpecifier.Rsi(new ResourcePath(gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState); + } + } + + public GasPrototype GetGas(int gasId) => GasPrototypes[gasId]; + + public GasPrototype GetGas(Gas gasId) => GasPrototypes[(int) gasId]; + + public IEnumerable Gases => GasPrototypes; + + public SpriteSpecifier GetOverlay(int overlayId) => GasOverlays[overlayId]; + } +} diff --git a/Content.Shared/GameObjects/EntitySystems/Atmos/SharedGasTileOverlaySystem.cs b/Content.Shared/GameObjects/EntitySystems/Atmos/SharedGasTileOverlaySystem.cs index 6ff74584d0..3c4da94616 100644 --- a/Content.Shared/GameObjects/EntitySystems/Atmos/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/Atmos/SharedGasTileOverlaySystem.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Map; @@ -18,7 +17,7 @@ namespace Content.Shared.GameObjects.EntitySystems.Atmos { return new MapIndices((int) Math.Floor((float) indices.X / ChunkSize) * ChunkSize, (int) MathF.Floor((float) indices.Y / ChunkSize) * ChunkSize); } - + [Serializable, NetSerializable] public struct GasData { @@ -59,9 +58,9 @@ namespace Content.Shared.GameObjects.EntitySystems.Atmos { return true; } - + DebugTools.Assert(other.Gas != null); - + for (var i = 0; i < Gas.Length; i++) { var thisGas = Gas[i]; From 6240f43ea52830d1fde9c692898a52a4bb420e4b Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 26 Aug 2020 00:16:54 +1000 Subject: [PATCH 65/88] Fix AI mapping crash (#1915) Co-authored-by: Metal Gear Sloth --- .../GameObjects/EntitySystems/AI/AiSystem.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs index e8470bff1e..2901b72457 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs @@ -68,11 +68,23 @@ namespace Content.Server.GameObjects.EntitySystems.AI } _queuedSleepMessages.Clear(); + var toRemove = new List(); foreach (var processor in _awakeAi) { + if (processor.SelfEntity.Deleted) + { + toRemove.Add(processor); + continue; + } + processor.Update(frameTime); } + + foreach (var processor in toRemove) + { + _awakeAi.Remove(processor); + } } private void HandleAiSleep(SleepAiMessage message) From b0a18ebc5bdb956fe20e66cb5b40665b817bc114 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 26 Aug 2020 00:44:23 +1000 Subject: [PATCH 66/88] Fix AiReachable crash (#1913) This system haunts my nightmares and I'm gonna refactor it before release. Co-authored-by: Metal Gear Sloth --- .../AI/Pathfinding/Accessible/AiReachableSystem.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Content.Server/GameObjects/EntitySystems/AI/Pathfinding/Accessible/AiReachableSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/Pathfinding/Accessible/AiReachableSystem.cs index 3470365282..4f866d3348 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/Pathfinding/Accessible/AiReachableSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/Pathfinding/Accessible/AiReachableSystem.cs @@ -130,6 +130,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible _regions.Clear(); _cachedAccessible.Clear(); _queuedCacheDeletions.Clear(); + _mapManager.OnGridRemoved -= GridRemoved; } public void ResettingCleanup() @@ -626,6 +627,12 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible /// private void GenerateRegions(PathfindingChunk chunk) { + // Grid deleted while update queued. + if (!_mapManager.TryGetGrid(chunk.GridId, out _)) + { + return; + } + if (!_regions.ContainsKey(chunk.GridId)) { _regions.Add(chunk.GridId, new Dictionary>()); From 34b2902641fccab0c314e4cafe0b669ad6af69f2 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Tue, 25 Aug 2020 16:53:59 +0200 Subject: [PATCH 67/88] Fix errors with creating gas mixtures on class instantiation (#1916) * Fix errors when gas mixtures are created on class instantiation * Fix mistake --- Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs | 5 +++-- .../Components/Atmos/GasMixtureHolderComponent.cs | 4 +++- .../Components/Body/Circulatory/BloodstreamComponent.cs | 4 +++- .../GameObjects/Components/Body/Respiratory/LungComponent.cs | 4 +++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs b/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs index dd14e1b333..6610e1d59f 100644 --- a/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Content.Server.GameObjects.EntitySystems; using Content.Shared.Atmos; using NUnit.Framework; +using Robust.Shared.GameObjects.Systems; namespace Content.IntegrationTests.Tests.Atmos { @@ -18,10 +19,10 @@ namespace Content.IntegrationTests.Tests.Atmos await server.WaitIdleAsync(); - var atmosSystem = server.ResolveDependency(); - server.Post(() => { + var atmosSystem = EntitySystem.Get(); + Assert.That(atmosSystem.Gases.Count(), Is.EqualTo(Atmospherics.TotalNumberOfGases)); Assert.That(Enum.GetValues(typeof(Gas)).Length, Is.EqualTo(Atmospherics.TotalNumberOfGases)); diff --git a/Content.Server/GameObjects/Components/Atmos/GasMixtureHolderComponent.cs b/Content.Server/GameObjects/Components/Atmos/GasMixtureHolderComponent.cs index 5aeaff4fc9..5f2efb662a 100644 --- a/Content.Server/GameObjects/Components/Atmos/GasMixtureHolderComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GasMixtureHolderComponent.cs @@ -10,12 +10,14 @@ namespace Content.Server.GameObjects.Components.Atmos { public override string Name => "GasMixtureHolder"; - [ViewVariables] public GasMixture GasMixture { get; set; } = new GasMixture(); + [ViewVariables] public GasMixture GasMixture { get; set; } public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); + GasMixture = new GasMixture(); + serializer.DataReadWriteFunction( "volume", 0f, diff --git a/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs b/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs index 9bf5f7e906..769a5e42c5 100644 --- a/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs +++ b/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs @@ -29,7 +29,7 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory /// [ViewVariables] public ReagentUnit EmptyVolume => _internalSolution.EmptyVolume; - [ViewVariables] public GasMixture Air { get; set; } = new GasMixture(6); + [ViewVariables] public GasMixture Air { get; set; } [ViewVariables] public SolutionComponent Solution => _internalSolution; @@ -45,6 +45,8 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory { base.ExposeData(serializer); + Air = new GasMixture(6); + serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(250)); } diff --git a/Content.Server/GameObjects/Components/Body/Respiratory/LungComponent.cs b/Content.Server/GameObjects/Components/Body/Respiratory/LungComponent.cs index eb625d7a64..1596b01c64 100644 --- a/Content.Server/GameObjects/Components/Body/Respiratory/LungComponent.cs +++ b/Content.Server/GameObjects/Components/Body/Respiratory/LungComponent.cs @@ -21,7 +21,7 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory ///
[ViewVariables(VVAccess.ReadWrite)] private float Pressure { get; set; } - [ViewVariables] public GasMixture Air { get; set; } = new GasMixture(); + [ViewVariables] public GasMixture Air { get; set; } [ViewVariables] public LungStatus Status { get; set; } @@ -29,6 +29,8 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory { base.ExposeData(serializer); + Air = new GasMixture(); + serializer.DataReadWriteFunction( "volume", 6, From c665e66318ae98b126e09c3c76165ebabd97921d Mon Sep 17 00:00:00 2001 From: Exp Date: Tue, 25 Aug 2020 17:09:39 +0200 Subject: [PATCH 68/88] Accent System (#1892) * First Accent Prototype * -RegisterSystem -Fixed addaccent cmd -Spanish accent * -list is now ? -Checks if the accent is already added -Made components public * owo whats this * special word filter * Eeeeeeeeee * Better? * -Use a delegate func -Made some funcs not static -Moved SentenceRegex * InjectDependencies * Name change? Co-authored-by: DrSmugleaf --- Content.Server/Chat/ChatManager.cs | 24 ++++- Content.Server/EntryPoint.cs | 2 + .../Components/Mobs/Speech/AccentManager.cs | 41 ++++++++ .../Mobs/Speech/BackwardsAccentComponent.cs | 18 ++++ .../Mobs/Speech/OwOAccentComponent.cs | 36 +++++++ .../Mobs/Speech/SpanishAccentComponent.cs | 64 +++++++++++++ .../Components/Mobs/Speech/SpeechComponent.cs | 94 +++++++++++++++++++ .../Interfaces/Chat/IChatManager.cs | 6 +- Content.Server/ServerContentIoC.cs | 4 +- Resources/Groups/groups.yml | 2 + 10 files changed, 286 insertions(+), 5 deletions(-) create mode 100644 Content.Server/GameObjects/Components/Mobs/Speech/AccentManager.cs create mode 100644 Content.Server/GameObjects/Components/Mobs/Speech/BackwardsAccentComponent.cs create mode 100644 Content.Server/GameObjects/Components/Mobs/Speech/OwOAccentComponent.cs create mode 100644 Content.Server/GameObjects/Components/Mobs/Speech/SpanishAccentComponent.cs create mode 100644 Content.Server/GameObjects/Components/Mobs/Speech/SpeechComponent.cs diff --git a/Content.Server/Chat/ChatManager.cs b/Content.Server/Chat/ChatManager.cs index b2e13053d8..266758207a 100644 --- a/Content.Server/Chat/ChatManager.cs +++ b/Content.Server/Chat/ChatManager.cs @@ -1,11 +1,9 @@ -using System.Linq; -using Content.Server.GameObjects.Components.Observer; +using Content.Server.GameObjects.Components.Observer; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Server.Interfaces.Chat; using Content.Shared.Chat; using Content.Shared.GameObjects.EntitySystems; -using NFluidsynth; using Robust.Server.Console; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; @@ -13,6 +11,10 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; using Robust.Shared.Localization; +using System; +using System.Collections.Generic; +using System.Linq; +using static Content.Server.Interfaces.Chat.IChatManager; namespace Content.Server.Chat { @@ -33,6 +35,9 @@ namespace Content.Server.Chat ///
private const string MaxLengthExceededMessage = "Your message exceeded {0} character limit"; + //TODO: make prio based? + private List _chatTransformHandlers; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; @@ -49,6 +54,8 @@ namespace Content.Server.Chat var msg = _netManager.CreateNetMessage(); msg.MaxMessageLength = MaxMessageLength; _netManager.ServerSendToAll(msg); + + _chatTransformHandlers = new List(); } public void DispatchServerAnnouncement(string message) @@ -96,6 +103,12 @@ namespace Content.Server.Chat return; } + foreach (var handler in _chatTransformHandlers) + { + //TODO: rather return a bool and use a out var? + message = handler(source, message); + } + var pos = source.Transform.GridPosition; var clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient); @@ -215,5 +228,10 @@ namespace Content.Server.Chat response.MaxMessageLength = MaxMessageLength; _netManager.ServerSendMessage(response, msg.MsgChannel); } + + public void RegisterChatTransform(TransformChat handler) + { + _chatTransformHandlers.Add(handler); + } } } diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index 120b364352..140fcb424b 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -15,6 +15,7 @@ using Robust.Shared.Interfaces.Log; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Timing; +using Content.Server.GameObjects.Components.Mobs.Speech; namespace Content.Server { @@ -64,6 +65,7 @@ namespace Content.Server IoCManager.Resolve().StartInit(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Initialize(); } public override void PostInit() diff --git a/Content.Server/GameObjects/Components/Mobs/Speech/AccentManager.cs b/Content.Server/GameObjects/Components/Mobs/Speech/AccentManager.cs new file mode 100644 index 0000000000..c3482fe61e --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/Speech/AccentManager.cs @@ -0,0 +1,41 @@ +using Content.Server.Interfaces.Chat; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Content.Server.GameObjects.Components.Mobs.Speech +{ + public interface IAccentManager + { + public void Initialize(); + } + + public class AccentManager : IAccentManager + { + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IComponentManager _componentManager = default!; + + public static readonly Regex SentenceRegex = new Regex(@"(?<=[\.!\?])"); + + public void Initialize() + { + IoCManager.InjectDependencies(this); + + _chatManager.RegisterChatTransform(AccentHandler); + } + + public string AccentHandler(IEntity player, string message) + { + //TODO: give accents a prio? + var accents = _componentManager.GetComponents(player.Uid); + foreach (var accent in accents) + { + message = accent.Accentuate(message); + } + return message; + } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/Speech/BackwardsAccentComponent.cs b/Content.Server/GameObjects/Components/Mobs/Speech/BackwardsAccentComponent.cs new file mode 100644 index 0000000000..af97bba4f8 --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/Speech/BackwardsAccentComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameObjects; +using System; + +namespace Content.Server.GameObjects.Components.Mobs.Speech +{ + [RegisterComponent] + public class BackwardsAccentComponent : Component, IAccentComponent + { + public override string Name => "BackwardsAccent"; + + public string Accentuate(string message) + { + var arr = message.ToCharArray(); + Array.Reverse(arr); + return new string(arr); + } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/Speech/OwOAccentComponent.cs b/Content.Server/GameObjects/Components/Mobs/Speech/OwOAccentComponent.cs new file mode 100644 index 0000000000..819e7c81cd --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/Speech/OwOAccentComponent.cs @@ -0,0 +1,36 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Random; +using System.Collections.Generic; + +namespace Content.Server.GameObjects.Components.Mobs.Speech +{ + [RegisterComponent] + public class OwOAccentComponent : Component, IAccentComponent + { + public override string Name => "OwOAccent"; + + private static readonly IReadOnlyList Faces = new List{ + " (・`ω´・)", " ;;w;;", " owo", " UwU", " >w<", " ^w^" + }.AsReadOnly(); + private string RandomFace => IoCManager.Resolve().Pick(Faces); + + private static readonly Dictionary SpecialWords = new Dictionary + { + { "you", "wu" }, + }; + + public string Accentuate(string message) + { + foreach ((var word,var repl) in SpecialWords) + { + message = message.Replace(word, repl); + } + + return message.Replace("!", RandomFace) + .Replace("r", "w").Replace("R", "W") + .Replace("l", "w").Replace("L", "W"); + } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/Speech/SpanishAccentComponent.cs b/Content.Server/GameObjects/Components/Mobs/Speech/SpanishAccentComponent.cs new file mode 100644 index 0000000000..4ae5cb4019 --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/Speech/SpanishAccentComponent.cs @@ -0,0 +1,64 @@ +using Microsoft.CodeAnalysis.Operations; +using Microsoft.EntityFrameworkCore.Internal; +using Robust.Shared.GameObjects; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Content.Server.GameObjects.Components.Mobs.Speech +{ + [RegisterComponent] + public class SpanishAccentComponent : Component, IAccentComponent + { + public override string Name => "SpanishAccent"; + + public string Accentuate(string message) + { + // Insert E before every S + message = InsertS(message); + // If a sentence ends with ?, insert a reverse ? at the beginning of the sentence + message = ReplaceQuestionMark(message); + return message; + } + + private string InsertS(string message) + { + // Replace every new Word that starts with s/S + var msg = message.Replace(" s", " es").Replace(" S", "Es"); + + // Still need to check if the beginning of the message starts + if (msg.StartsWith("s")) + { + return msg.Remove(0, 1).Insert(0, "es"); + } + else if (msg.StartsWith("S")) + { + return msg.Remove(0, 1).Insert(0, "Es"); + } + + return msg; + } + + private string ReplaceQuestionMark(string message) + { + var sentences = AccentManager.SentenceRegex.Split(message); + var msg = ""; + foreach (var s in sentences) + { + if (s.EndsWith("?")) // We've got a question => add ¿ to the beginning + { + // Because we don't split by whitespace, we may have some spaces in front of the sentence. + // So we add the symbol before the first non space char + msg += s.Insert(s.Length - s.TrimStart().Length, "¿"); + } + else + { + msg += s; + } + } + return msg; + } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/Speech/SpeechComponent.cs b/Content.Server/GameObjects/Components/Mobs/Speech/SpeechComponent.cs new file mode 100644 index 0000000000..b9b8a40670 --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/Speech/SpeechComponent.cs @@ -0,0 +1,94 @@ +using Robust.Server.Interfaces.Console; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using System; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Mobs.Speech +{ + internal interface IAccentComponent + { + /// + /// Transforms a message with the given Accent + /// + /// The spoken message + /// The message after the transformation + public string Accentuate(string message); + } + + public class AddAccent : IClientCommand + { + public string Command => "addaccent"; + + public string Description => "Add a speech component to the current player"; + + public string Help => $"{Command} /?"; + + public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + { + if (args.Length == 0) + { + shell.SendText(player, Help); + return; + } + + if (player.AttachedEntity == null) + { + shell.SendText(player, "You don't have a player!"); + return; + } + + var compFactory = IoCManager.Resolve(); + + if (args[0] == "?") + { + // Get all components that implement the ISpeechComponent except + var speeches = compFactory.GetAllRefTypes() + .Where(c => typeof(IAccentComponent).IsAssignableFrom(c) && c.IsClass); + var msg = ""; + foreach(var s in speeches) + { + msg += $"{compFactory.GetRegistration(s).Name}\n"; + } + shell.SendText(player, msg); + } + else + { + var name = args[0]; + // Try to get the Component + Type type; + try + { + var comp = compFactory.GetComponent(name); + type = comp.GetType(); + } + catch (Exception) + { + shell.SendText(player, $"Accent {name} not found. Try {Command} ? to get a list of all appliable accents."); + return; + } + + // Check if that already exists + try + { + var comp = player.AttachedEntity.GetComponent(type); + shell.SendText(player, "You already have this accent!"); + return; + } + catch (Exception) + { + // Accent not found + } + + // Generic fuckery + var ensure = typeof(IEntity).GetMethod("AddComponent"); + if (ensure == null) + return; + var method = ensure.MakeGenericMethod(type); + method.Invoke(player.AttachedEntity, null); + } + } + } +} diff --git a/Content.Server/Interfaces/Chat/IChatManager.cs b/Content.Server/Interfaces/Chat/IChatManager.cs index e32c9bea8d..eb06e54d78 100644 --- a/Content.Server/Interfaces/Chat/IChatManager.cs +++ b/Content.Server/Interfaces/Chat/IChatManager.cs @@ -1,5 +1,6 @@ -using Robust.Server.Interfaces.Player; +using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.GameObjects; +using System; namespace Content.Server.Interfaces.Chat { @@ -28,5 +29,8 @@ namespace Content.Server.Interfaces.Chat void SendDeadChat(IPlayerSession player, string message); void SendHookOOC(string sender, string message); + + delegate string TransformChat(IEntity speaker, string message); + void RegisterChatTransform(TransformChat handler); } } diff --git a/Content.Server/ServerContentIoC.cs b/Content.Server/ServerContentIoC.cs index 0083b4c8bd..1e757d4232 100644 --- a/Content.Server/ServerContentIoC.cs +++ b/Content.Server/ServerContentIoC.cs @@ -1,8 +1,9 @@ -using Content.Server.AI.Utility.Considerations; +using Content.Server.AI.Utility.Considerations; using Content.Server.AI.WorldState; using Content.Server.Body.Network; using Content.Server.Cargo; using Content.Server.Chat; +using Content.Server.GameObjects.Components.Mobs.Speech; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Server.GameObjects.Components.Power.PowerNetComponents; using Content.Server.GameTicking; @@ -41,6 +42,7 @@ namespace Content.Server IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index 71787fbcaa..75fe17e4bf 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -97,6 +97,7 @@ - tilewalls - events - destroymechanism + - addaccent - readyall - factions CanViewVar: true @@ -189,6 +190,7 @@ - tilewalls - events - destroymechanism + - addaccent - readyall - factions CanViewVar: true From 292ef4ef16bb2620ffb3d25676665abc1b7519b5 Mon Sep 17 00:00:00 2001 From: Exp Date: Tue, 25 Aug 2020 17:18:32 +0200 Subject: [PATCH 69/88] Admin Menu (#1648) * First Prototype * Command Window * Dropdown * Is this better? * That's kinda better? * Added divider * Shit * Check if Admin Menu & Commands are allowed * -Funcy Shit -Now gets properly the playerlist -Fixed kick reason * Dropdown Improvement with some more func * -Added DirectCommand for commands that don't need a ui -Added RestartRound * Better way to make DirectCommandButtons * -Some new Tabs -Player list * -Split Buttons -Regions -Fixed Test Command * Some server buttons * Playerlist alignment * Fucky SpawnEntites & SpawnTiles in AdminBus * -Debug Buttons -Few more commands * -Make dem controls thicc -SpinBox * Escape Kick Reason * Only create the window when you press the button * Adds StationEvents * Nullable "fixes" * This thing wasn't made for buttons * Call other constructor for empty CommandButton * Request method in the interface * -Pushed most Controls to be fields -No more dict passing -Removed test cmd -Regions to better navigate * -Bound to key -Removed from escape menu -Remember cmd windows -Close all cmd windows on toggle * -Moved dependency * Merge fixes Co-authored-by: DrSmugleaf --- Content.Client/ClientContentIoC.cs | 2 + Content.Client/EntryPoint.cs | 2 + Content.Client/Input/ContentContexts.cs | 1 + .../StationEvents/IStationEventManager.cs | 1 + .../AdminMenu/AdminMenuManager.cs | 103 +++ .../AdminMenu/AdminMenuWindow.cs | 670 ++++++++++++++++++ .../UserInterface/TutorialWindow.cs | 6 +- Content.Shared/Input/ContentKeyFunctions.cs | 1 + Resources/Groups/groups.yml | 3 + Resources/keybinds.yml | 3 + 10 files changed, 790 insertions(+), 2 deletions(-) create mode 100644 Content.Client/UserInterface/AdminMenu/AdminMenuManager.cs create mode 100644 Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs diff --git a/Content.Client/ClientContentIoC.cs b/Content.Client/ClientContentIoC.cs index 8639ea4c24..e7d8d90841 100644 --- a/Content.Client/ClientContentIoC.cs +++ b/Content.Client/ClientContentIoC.cs @@ -7,6 +7,7 @@ using Content.Client.Parallax; using Content.Client.Sandbox; using Content.Client.StationEvents; using Content.Client.UserInterface; +using Content.Client.UserInterface.AdminMenu; using Content.Client.UserInterface.Stylesheets; using Content.Client.Utility; using Content.Shared.Interfaces; @@ -33,6 +34,7 @@ namespace Content.Client IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index 912440baac..a4edb0736c 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -9,6 +9,7 @@ using Content.Client.Sandbox; using Content.Client.State; using Content.Client.StationEvents; using Content.Client.UserInterface; +using Content.Client.UserInterface.AdminMenu; using Content.Client.UserInterface.Stylesheets; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components.Cargo; @@ -150,6 +151,7 @@ namespace Content.Client IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Initialize(); _baseClient.RunLevelChanged += (sender, args) => { diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 89ddd64268..26834dfd16 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -50,6 +50,7 @@ namespace Content.Client.Input common.AddFunction(ContentKeyFunctions.OpenEntitySpawnWindow); common.AddFunction(ContentKeyFunctions.OpenSandboxWindow); common.AddFunction(ContentKeyFunctions.OpenTileSpawnWindow); + common.AddFunction(ContentKeyFunctions.OpenAdminMenu); } } } diff --git a/Content.Client/StationEvents/IStationEventManager.cs b/Content.Client/StationEvents/IStationEventManager.cs index 828b20e80d..3381708f6a 100644 --- a/Content.Client/StationEvents/IStationEventManager.cs +++ b/Content.Client/StationEvents/IStationEventManager.cs @@ -9,5 +9,6 @@ namespace Content.Client.StationEvents public List? StationEvents { get; } public void Initialize(); public event Action OnStationEventsReceived; + public void RequestEvents(); } } diff --git a/Content.Client/UserInterface/AdminMenu/AdminMenuManager.cs b/Content.Client/UserInterface/AdminMenu/AdminMenuManager.cs new file mode 100644 index 0000000000..596d47e9ef --- /dev/null +++ b/Content.Client/UserInterface/AdminMenu/AdminMenuManager.cs @@ -0,0 +1,103 @@ +using Content.Shared.Input; +using Robust.Client.Console; +using Robust.Client.Interfaces.Input; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Input.Binding; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.IoC; +using System.Collections.Generic; + +namespace Content.Client.UserInterface.AdminMenu +{ + internal class AdminMenuManager : IAdminMenuManager + { + [Dependency] private INetManager _netManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IClientConGroupController _clientConGroupController = default!; + + private SS14Window _window; + private List _commandWindows; + + public void Initialize() + { + _commandWindows = new List(); + // Reset the AdminMenu Window on disconnect + _netManager.Disconnect += (sender, channel) => ResetWindow(); + + _inputManager.SetInputCommand(ContentKeyFunctions.OpenAdminMenu, + InputCmdHandler.FromDelegate(session => Toggle())); + } + + public void ResetWindow() + { + _window?.Close(); + _window = null; + + foreach (var window in _commandWindows) + window?.Dispose(); + _commandWindows.Clear(); + } + + public void OpenCommand(SS14Window window) + { + _commandWindows.Add(window); + window.OpenCentered(); + } + + public void Open() + { + if (_window == null) + _window = new AdminMenuWindow(); + _window.OpenCentered(); + } + + public void Close() + { + _window?.Close(); + + foreach (var window in _commandWindows) + window?.Dispose(); + _commandWindows.Clear(); + } + + /// + /// Checks if the player can open the window + /// + /// True if the player is allowed + public bool CanOpen() + { + return _clientConGroupController.CanAdminMenu(); + } + + /// + /// Checks if the player can open the window and tries to open it + /// + public void TryOpen() + { + if (CanOpen()) + Open(); + } + + public void Toggle() + { + if (_window != null && _window.IsOpen) + { + Close(); + } + else + { + TryOpen(); + } + } + } + + internal interface IAdminMenuManager + { + void Initialize(); + void Open(); + void OpenCommand(SS14Window window); + bool CanOpen(); + void TryOpen(); + void Toggle(); + } +} diff --git a/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs b/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs new file mode 100644 index 0000000000..3708adfb82 --- /dev/null +++ b/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs @@ -0,0 +1,670 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Client.GameObjects.EntitySystems; +using Content.Client.StationEvents; +using Content.Shared.Atmos; +using Robust.Client.Console; +using Robust.Client.Interfaces.Placement; +using Robust.Client.Interfaces.ResourceManagement; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; +using static Robust.Client.UserInterface.Controls.BaseButton; + +namespace Content.Client.UserInterface.AdminMenu +{ + public class AdminMenuWindow : SS14Window + { + public TabContainer MasterTabContainer; + public VBoxContainer PlayerList; + + private List _adminButtons = new List + { + new KickCommandButton(), + new DirectCommandButton("Admin Ghost", "aghost"), + //TODO: teleport + }; + private List _adminbusButtons = new List + { + new SpawnEntitiesCommandButton(), + new SpawnTilesCommandButton(), + new StationEventsCommandButton(), + }; + private List _debugButtons = new List + { + new AddAtmosCommandButton(), + new FillGasCommandButton(), + }; + private List _roundButtons = new List + { + new DirectCommandButton("Start Round", "startround"), + new DirectCommandButton("End Round", "endround"), + new DirectCommandButton("Restart Round", "restartround"), + }; + private List _serverButtons = new List + { + new DirectCommandButton("Reboot", "restart"), + new DirectCommandButton("Shutdown", "shutdown"), + }; + + private void RefreshPlayerList(ButtonEventArgs args) + { + PlayerList.RemoveAllChildren(); + var sessions = IoCManager.Resolve().Sessions; + var header = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + new Label { Text = "Name", + SizeFlagsStretchRatio = 2f, + SizeFlagsHorizontal = SizeFlags.FillExpand }, + new Label { Text = "Player", + SizeFlagsStretchRatio = 2f, + SizeFlagsHorizontal = SizeFlags.FillExpand }, + new Label { Text = "Status", + SizeFlagsStretchRatio = 1f, + SizeFlagsHorizontal = SizeFlags.FillExpand }, + new Label { Text = "Ping", + SizeFlagsStretchRatio = 1f, + SizeFlagsHorizontal = SizeFlags.FillExpand, + Align = Label.AlignMode.Right }, + } + }; + PlayerList.AddChild(header); + PlayerList.AddChild(new Controls.HighDivider()); + foreach (var player in sessions) + { + var hbox = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + new Label { + Text = player.Name, + SizeFlagsStretchRatio = 2f, + SizeFlagsHorizontal = SizeFlags.FillExpand }, + new Label { + Text = player.AttachedEntity?.Name, + SizeFlagsStretchRatio = 2f, + SizeFlagsHorizontal = SizeFlags.FillExpand }, + new Label { + Text = player.Status.ToString(), + SizeFlagsStretchRatio = 1f, + SizeFlagsHorizontal = SizeFlags.FillExpand }, + new Label { + Text = player.Ping.ToString(), + SizeFlagsStretchRatio = 1f, + SizeFlagsHorizontal = SizeFlags.FillExpand, + Align = Label.AlignMode.Right }, + } + }; + PlayerList.AddChild(hbox); + } + } + + private void AddCommandButton(List buttons, Control parent) + { + foreach (var cmd in buttons) + { + // Check if the player can do the command + if (!cmd.CanPress()) + continue; + + //TODO: make toggle? + var button = new Button + { + Text = cmd.Name + }; + button.OnPressed += cmd.ButtonPressed; + parent.AddChild(button); + } + } + + public AdminMenuWindow() //TODO: search for buttons? + { + CustomMinimumSize = (415,0); + Title = Loc.GetString("Admin Menu"); + + #region PlayerList + // Players // List of all the players, their entities and status + var playerTabContainer = new MarginContainer + { + MarginLeftOverride = 4, + MarginTopOverride = 4, + MarginRightOverride = 4, + MarginBottomOverride = 4, + CustomMinimumSize = (50, 50), + }; + PlayerList = new VBoxContainer(); + var refreshButton = new Button + { + Text = "Refresh" + }; + refreshButton.OnPressed += RefreshPlayerList; + RefreshPlayerList(null!); + var playerVBox = new VBoxContainer + { + Children = + { + refreshButton, + PlayerList + } + }; + playerTabContainer.AddChild(playerVBox); + #endregion PlayerList + + #region Admin Tab + // Admin Tab // Actual admin stuff + var adminTabContainer = new MarginContainer + { + MarginLeftOverride = 4, + MarginTopOverride = 4, + MarginRightOverride = 4, + MarginBottomOverride = 4, + CustomMinimumSize = (50, 50), + }; + var adminButtonGrid = new GridContainer + { + Columns = 4, + }; + AddCommandButton(_adminButtons, adminButtonGrid); + adminTabContainer.AddChild(adminButtonGrid); + #endregion + + #region Adminbus + // Adminbus // Fun Commands + var adminbusTabContainer = new MarginContainer + { + MarginLeftOverride = 4, + MarginTopOverride = 4, + MarginRightOverride = 4, + MarginBottomOverride = 4, + CustomMinimumSize = (50, 50), + }; + var adminbusButtonGrid = new GridContainer + { + Columns = 4, + }; + AddCommandButton(_adminbusButtons, adminbusButtonGrid); + adminbusTabContainer.AddChild(adminbusButtonGrid); + #endregion + + #region Debug + // Debug // Mostly dev tools, like addatmos + var debugTabContainer = new MarginContainer + { + MarginLeftOverride = 4, + MarginTopOverride = 4, + MarginRightOverride = 4, + MarginBottomOverride = 4, + CustomMinimumSize = (50, 50), + }; + var debugButtonGrid = new GridContainer + { + Columns = 4, + }; + AddCommandButton(_debugButtons, debugButtonGrid); + debugTabContainer.AddChild(debugButtonGrid); + #endregion + + #region Round + // Round // Commands like Check Antags, End Round, RestartRound + var roundTabContainer = new MarginContainer + { + MarginLeftOverride = 4, + MarginTopOverride = 4, + MarginRightOverride = 4, + MarginBottomOverride = 4, + CustomMinimumSize = (50, 50), + }; + var roundButtonGrid = new GridContainer + { + Columns = 4, + }; + AddCommandButton(_roundButtons, roundButtonGrid); + roundTabContainer.AddChild(roundButtonGrid); + #endregion + + #region Server + // Server // Commands like Restart, Shutdown + var serverTabContainer = new MarginContainer + { + MarginLeftOverride = 4, + MarginTopOverride = 4, + MarginRightOverride = 4, + MarginBottomOverride = 4, + CustomMinimumSize = (50, 50), + }; + var serverButtonGrid = new GridContainer + { + Columns = 4, + }; + AddCommandButton(_serverButtons, serverButtonGrid); + serverTabContainer.AddChild(serverButtonGrid); + #endregion + + + //The master menu that contains all of the tabs. + MasterTabContainer = new TabContainer(); + + //Add all the tabs to the Master container. + MasterTabContainer.AddChild(adminTabContainer); + MasterTabContainer.SetTabTitle(0, Loc.GetString("Admin")); + MasterTabContainer.AddChild(adminbusTabContainer); + MasterTabContainer.SetTabTitle(1, Loc.GetString("Adminbus")); + MasterTabContainer.AddChild(debugTabContainer); + MasterTabContainer.SetTabTitle(2, Loc.GetString("Debug")); + MasterTabContainer.AddChild(roundTabContainer); + MasterTabContainer.SetTabTitle(3, Loc.GetString("Round")); + MasterTabContainer.AddChild(serverTabContainer); + MasterTabContainer.SetTabTitle(4, Loc.GetString("Server")); + MasterTabContainer.AddChild(playerTabContainer); + MasterTabContainer.SetTabTitle(5, Loc.GetString("Players")); + Contents.AddChild(MasterTabContainer); + //Request station events, so we can use them later + IoCManager.Resolve().RequestEvents(); + } + + #region CommandButtonBaseClass + private abstract class CommandButton + { + public virtual string Name { get; } + public virtual string RequiredCommand { get; } + public abstract void ButtonPressed(ButtonEventArgs args); + public virtual bool CanPress() + { + return RequiredCommand == string.Empty || + IoCManager.Resolve().CanCommand(RequiredCommand); + } + + public CommandButton() : this(string.Empty, string.Empty) {} + public CommandButton(string name, string command) + { + Name = name; + RequiredCommand = command; + } + } + + // Button that opens a UI + private abstract class UICommandButton : CommandButton + { + // The text on the submit button + public virtual string? SubmitText { get; } + /// + /// Called when the Submit button is pressed + /// + /// Dictionary of the parameter names and values + public abstract void Submit(); + public override void ButtonPressed(ButtonEventArgs args) + { + var manager = IoCManager.Resolve(); + var window = new CommandWindow(this); + window.Submit += Submit; + manager.OpenCommand(window); + } + // List of all the UI Elements + public abstract List UI { get; } + } + + // Button that directly calls a Command + private class DirectCommandButton : CommandButton + { + public DirectCommandButton(string name, string command) : base(name, command) { } + + public override void ButtonPressed(ButtonEventArgs args) + { + IoCManager.Resolve().ProcessCommand(RequiredCommand); + } + } + #endregion + + #region CommandButtons + private class SpawnEntitiesCommandButton : CommandButton + { + public override string Name => "Spawn Entities"; + //TODO: override CanPress + public override void ButtonPressed(ButtonEventArgs args) + { + var manager = IoCManager.Resolve(); + var window = new EntitySpawnWindow(IoCManager.Resolve(), + IoCManager.Resolve(), + IoCManager.Resolve(), + IoCManager.Resolve()); + manager.OpenCommand(window); + } + } + + private class SpawnTilesCommandButton : CommandButton + { + public override string Name => "Spawn Tiles"; + //TODO: override CanPress + public override void ButtonPressed(ButtonEventArgs args) + { + var manager = IoCManager.Resolve(); + var window = new TileSpawnWindow(IoCManager.Resolve(), + IoCManager.Resolve(), + IoCManager.Resolve()); + manager.OpenCommand(window); + } + } + + private class StationEventsCommandButton : UICommandButton + { + public override string Name => "Station Events"; + public override string RequiredCommand => "events"; + public override string? SubmitText => "Run"; + + private CommandUIDropDown _eventsDropDown = new CommandUIDropDown + { + Name = "Event", + GetData = () => + { + var events = IoCManager.Resolve().StationEvents; + if (events == null) + return new List { "Not loaded" }; + events.Add("Random"); + return events.ToList(); + }, + GetDisplayName = (obj) => (string) obj, + GetValueFromData = (obj) => ((string) obj).ToLower(), + }; + + public override List UI => new List + { + _eventsDropDown, + new CommandUIButton + { + Name = "Pause", + Handler = () => + { + IoCManager.Resolve().ProcessCommand($"events pause"); + }, + }, + new CommandUIButton + { + Name = "Resume", + Handler = () => + { + IoCManager.Resolve().ProcessCommand($"events resume"); + }, + }, + }; + + public override void Submit() + { + IoCManager.Resolve().ProcessCommand($"events run \"{_eventsDropDown.GetValue()}\""); + } + } + + private class KickCommandButton : UICommandButton + { + public override string Name => "Kick"; + public override string RequiredCommand => "kick"; + + private CommandUIDropDown _playerDropDown = new CommandUIDropDown + { + Name = "Player", + GetData = () => IoCManager.Resolve().Sessions.ToList(), + GetDisplayName = (obj) => $"{((IPlayerSession) obj).Name} ({((IPlayerSession) obj).AttachedEntity?.Name})", + GetValueFromData = (obj) => ((IPlayerSession) obj).Name, + }; + private CommandUILineEdit _reason = new CommandUILineEdit + { + Name = "Reason" + }; + + public override List UI => new List + { + _playerDropDown, + _reason + }; + + public override void Submit() + { + IoCManager.Resolve().ProcessCommand($"kick \"{_playerDropDown.GetValue()}\" \"{CommandParsing.Escape(_reason.GetValue())}\""); + } + } + + private class AddAtmosCommandButton : UICommandButton + { + public override string Name => "Add Atmos"; + public override string RequiredCommand => "addatmos"; + + private CommandUIDropDown _grid = new CommandUIDropDown + { + Name = "Grid", + GetData = () => IoCManager.Resolve().GetAllGrids().Where(g => (int) g.Index != 0).ToList(), + GetDisplayName = (obj) => $"{((IMapGrid) obj).Index}{(IoCManager.Resolve().LocalPlayer?.ControlledEntity?.Transform.GridID == ((IMapGrid) obj).Index ? " (Current)" : "")}", + GetValueFromData = (obj) => ((IMapGrid) obj).Index.ToString(), + }; + + public override List UI => new List + { + _grid, + }; + + public override void Submit() + { + IoCManager.Resolve().ProcessCommand($"addatmos {_grid.GetValue()}"); + } + } + + private class FillGasCommandButton : UICommandButton + { + public override string Name => "Fill Gas"; + public override string RequiredCommand => "fillgas"; + + private CommandUIDropDown _grid = new CommandUIDropDown + { + Name = "Grid", + GetData = () => IoCManager.Resolve().GetAllGrids().Where(g => (int) g.Index != 0).ToList(), + GetDisplayName = (obj) => $"{((IMapGrid) obj).Index}{(IoCManager.Resolve().LocalPlayer?.ControlledEntity?.Transform.GridID == ((IMapGrid) obj).Index ? " (Current)" : "")}", + GetValueFromData = (obj) => ((IMapGrid) obj).Index.ToString(), + }; + + private CommandUIDropDown _gas = new CommandUIDropDown + { + Name = "Gas", + GetData = () => + { + var atmosSystem = EntitySystem.Get(); + return atmosSystem.Gases.ToList(); + }, + GetDisplayName = (obj) => $"{((GasPrototype) obj).Name} ({((GasPrototype) obj).ID})", + GetValueFromData = (obj) => ((GasPrototype) obj).ID.ToString(), + }; + + private CommandUISpinBox _amount = new CommandUISpinBox + { + Name = "Amount" + }; + + public override List UI => new List + { + _grid, + _gas, + _amount, + }; + + public override void Submit() + { + IoCManager.Resolve().ProcessCommand($"fillgas {_grid.GetValue()} {_gas.GetValue()} {_amount.GetValue()}"); + } + } + #endregion + + #region CommandUIControls + private abstract class CommandUIControl + { + public string? Name; + public Control? Control; + public abstract Control GetControl(); + public abstract string GetValue(); + } + private class CommandUIDropDown : CommandUIControl + { + public Func>? GetData; + // The string that the player sees in the list + public Func? GetDisplayName; + // The value that is given to Submit + public Func? GetValueFromData; + // Cache + protected List? Data; //TODO: make this like IEnumerable or smth, so you don't have to do this ToList shittery + + public override Control GetControl() //TODO: fix optionbutton being shitty after moving the window + { + var opt = new OptionButton { CustomMinimumSize = (100, 0), SizeFlagsHorizontal = SizeFlags.FillExpand }; + Data = GetData!(); + foreach (var item in Data) + opt.AddItem(GetDisplayName!(item)); + + opt.OnItemSelected += eventArgs => opt.SelectId(eventArgs.Id); + Control = opt; + return Control; + } + + public override string GetValue() + { + return GetValueFromData!(Data![((OptionButton)Control!).SelectedId]); + } + } + private class CommandUICheckBox : CommandUIControl + { + public override Control GetControl() + { + Control = new CheckBox { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.ShrinkCenter }; + return Control; + } + + public override string GetValue() + { + return ((CheckBox)Control!).Pressed ? "1" : "0"; + } + } + private class CommandUILineEdit : CommandUIControl + { + public override Control GetControl() + { + Control = new LineEdit { CustomMinimumSize = (100, 0), SizeFlagsHorizontal = SizeFlags.FillExpand }; + return Control; + } + + public override string GetValue() + { + return ((LineEdit)Control!).Text; + } + } + + private class CommandUISpinBox : CommandUIControl + { + public override Control GetControl() + { + Control = new SpinBox { CustomMinimumSize = (100, 0), SizeFlagsHorizontal = SizeFlags.FillExpand }; + return Control; + } + + public override string GetValue() + { + return ((SpinBox)Control!).Value.ToString(); + } + } + + private class CommandUIButton : CommandUIControl + { + public Action? Handler { get; set; } + + public override Control GetControl() + { + Control = new Button { + CustomMinimumSize = (100, 0), + SizeFlagsHorizontal = SizeFlags.FillExpand, + Text = Name }; + return Control; + } + + public override string GetValue() + { + return ""; + } + } + #endregion + + #region CommandWindow + private class CommandWindow : SS14Window + { + List _controls; + public Action? Submit { get; set; } + public CommandWindow(UICommandButton button) + { + Title = button.Name; + _controls = button.UI; + var container = new VBoxContainer //TODO: add margin between different controls + { + }; + // Init Controls in a hbox + a label + foreach (var control in _controls) + { + var c = control.GetControl(); + if (c is Button) + { + ((Button) c).OnPressed += (args) => + { + ((CommandUIButton) control).Handler?.Invoke(); + }; + container.AddChild(c); + } + else + { + var label = new Label + { + Text = control.Name, + CustomMinimumSize = (100, 0) + }; + var divider = new Control + { + CustomMinimumSize = (50, 0) + }; + var hbox = new HBoxContainer + { + Children = + { + label, + divider, + c + }, + }; + container.AddChild(hbox); + } + + + } + // Init Submit Button + var submitButton = new Button + { + Text = button.SubmitText ?? button.Name + }; + submitButton.OnPressed += SubmitPressed; + container.AddChild(submitButton); + + Contents.AddChild(container); + } + + public void SubmitPressed(ButtonEventArgs args) + { + Submit?.Invoke(); + } + } + #endregion + } +} diff --git a/Content.Client/UserInterface/TutorialWindow.cs b/Content.Client/UserInterface/TutorialWindow.cs index 6e4b90f1d6..606f3ff2b3 100644 --- a/Content.Client/UserInterface/TutorialWindow.cs +++ b/Content.Client/UserInterface/TutorialWindow.cs @@ -92,7 +92,8 @@ Toggle UI: [color=#a4885c]{17}[/color] Toggle debug overlay: [color=#a4885c]{18}[/color] Toggle entity spawner: [color=#a4885c]{19}[/color] Toggle tile spawner: [color=#a4885c]{20}[/color] -Toggle sandbox window: [color=#a4885c]{21}[/color]", +Toggle sandbox window: [color=#a4885c]{21}[/color] +Toggle admin menu [color=#a4885c]{31}[/color]", Key(MoveUp), Key(MoveLeft), Key(MoveDown), Key(MoveRight), Key(SwapHands), Key(ActivateItemInHand), @@ -120,7 +121,8 @@ Toggle sandbox window: [color=#a4885c]{21}[/color]", Key(FocusAdminChat), Key(Point), Key(TryPullObject), - Key(MovePulledObject))); + Key(MovePulledObject), + Key(OpenAdminMenu))); //Gameplay VBox.AddChild(new Label { FontOverride = headerFont, Text = "\nGameplay" }); diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 5cf834bea3..e0a1bc62c6 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -29,6 +29,7 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction OpenEntitySpawnWindow = "OpenEntitySpawnWindow"; public static readonly BoundKeyFunction OpenSandboxWindow = "OpenSandboxWindow"; public static readonly BoundKeyFunction OpenTileSpawnWindow = "OpenTileSpawnWindow"; + public static readonly BoundKeyFunction OpenAdminMenu = "OpenAdminMenu"; public static readonly BoundKeyFunction TakeScreenshot = "TakeScreenshot"; public static readonly BoundKeyFunction TakeScreenshotNoUI = "TakeScreenshotNoUI"; public static readonly BoundKeyFunction Point = "Point"; diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index 75fe17e4bf..e7120fce27 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -38,6 +38,7 @@ - hostlogin - events - factions + CanAdminMenu: true - Index: 100 Name: Administrator @@ -102,6 +103,7 @@ - factions CanViewVar: true CanAdminPlace: true + CanAdminMenu: true - Index: 200 Name: Host @@ -196,3 +198,4 @@ CanViewVar: true CanAdminPlace: true CanScript: true + CanAdminMenu: true diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 2431c3f3c9..01357c28e0 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -248,6 +248,9 @@ binds: - function: OpenTileSpawnWindow type: state key: F6 +- function: OpenAdminMenu + type: state + key: F7 - function: OpenSandboxWindow type: state key: B From 4854a54ee1e1b7dda505bfbfb629d09371c1e1da Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Tue, 25 Aug 2020 17:43:27 +0200 Subject: [PATCH 70/88] Add strip verb (#1894) * Add strip verb * Merge some of the verb and candragdrop checks --- .../Components/GUI/StrippableComponent.cs | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs index 4e43e0be99..fc54344448 100644 --- a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs @@ -9,6 +9,7 @@ using Content.Server.Interfaces; using Content.Server.Utility; using Content.Shared.GameObjects.Components.GUI; using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Verbs; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.Interfaces.GameObjects; @@ -78,10 +79,18 @@ namespace Content.Server.GameObjects.Components.GUI UserInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands, cuffs)); } + public bool CanBeStripped(IEntity by) + { + return by != Owner + && by.HasComponent() + && ActionBlockerSystem.CanInteract(by); + } + public bool CanDragDrop(DragDropEventArgs eventArgs) { - return eventArgs.User.HasComponent() - && eventArgs.Target != eventArgs.Dropped && eventArgs.Target == eventArgs.User; + return eventArgs.Target != eventArgs.Dropped + && eventArgs.Target == eventArgs.User + && CanBeStripped(eventArgs.User); } public bool DragDrop(DragDropEventArgs eventArgs) @@ -432,5 +441,31 @@ namespace Content.Server.GameObjects.Components.GUI break; } } + + [Verb] + private sealed class StripVerb : Verb + { + protected override void GetData(IEntity user, StrippableComponent component, VerbData data) + { + if (!component.CanBeStripped(user)) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + data.Visibility = VerbVisibility.Visible; + data.Text = Loc.GetString("Strip"); + } + + protected override void Activate(IEntity user, StrippableComponent component) + { + if (!user.TryGetComponent(out IActorComponent? actor)) + { + return; + } + + component.OpenUserInterface(actor.playerSession); + } + } } } From dd3a697c12d68af7f80a15969bba83e21de0b645 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Tue, 25 Aug 2020 18:13:19 +0200 Subject: [PATCH 71/88] Fix the server not checking uplink purchase prices (#1917) --- .../Components/PDA/PDABoundUserInterface.cs | 2 +- .../Components/PDA/PDAComponent.cs | 4 +- .../Interfaces/PDA/IPDAUplinkManager.cs | 6 ++- Content.Server/PDA/PDAUplinkManager.cs | 46 +++++++++++++------ .../Components/PDA/SharedPDAComponent.cs | 20 ++++---- 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/Content.Client/GameObjects/Components/PDA/PDABoundUserInterface.cs b/Content.Client/GameObjects/Components/PDA/PDABoundUserInterface.cs index e581f8a021..8f5b4cf999 100644 --- a/Content.Client/GameObjects/Components/PDA/PDABoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/PDA/PDABoundUserInterface.cs @@ -69,7 +69,7 @@ namespace Content.Client.GameObjects.Components.PDA }; } - SendMessage(new PDAUplinkBuyListingMessage(listing)); + SendMessage(new PDAUplinkBuyListingMessage(listing.ItemId)); }; _menu.OnCategoryButtonPressed += (args, category) => diff --git a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs index fc87d0ddf5..bf85b1274c 100644 --- a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs +++ b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs @@ -102,7 +102,7 @@ namespace Content.Server.GameObjects.Components.PDA case PDAUplinkBuyListingMessage buyMsg: { - if (!_uplinkManager.TryPurchaseItem(_syndicateUplinkAccount, buyMsg.ListingToBuy)) + if (!_uplinkManager.TryPurchaseItem(_syndicateUplinkAccount, buyMsg.ItemId)) { SendNetworkMessage(new PDAUplinkInsufficientFundsMessage(), message.Session.ConnectedClient); break; @@ -128,7 +128,7 @@ namespace Content.Server.GameObjects.Components.PDA { var accData = new UplinkAccountData(_syndicateUplinkAccount.AccountHolder, _syndicateUplinkAccount.Balance); - var listings = _uplinkManager.FetchListings.ToArray(); + var listings = _uplinkManager.FetchListings.Values.ToArray(); UserInterface?.SetState(new PDAUpdateState(_lightOn, ownerInfo, accData, listings)); } else diff --git a/Content.Server/Interfaces/PDA/IPDAUplinkManager.cs b/Content.Server/Interfaces/PDA/IPDAUplinkManager.cs index 3ba9bdf5c8..f58fc5d755 100644 --- a/Content.Server/Interfaces/PDA/IPDAUplinkManager.cs +++ b/Content.Server/Interfaces/PDA/IPDAUplinkManager.cs @@ -5,13 +5,15 @@ namespace Content.Server.Interfaces.PDA { public interface IPDAUplinkManager { - public IReadOnlyList FetchListings => null; + public IReadOnlyDictionary FetchListings => null; + void Initialize(); + public bool AddNewAccount(UplinkAccount acc); public bool ChangeBalance(UplinkAccount acc, int amt); - public bool TryPurchaseItem(UplinkAccount acc, UplinkListingData listing); + public bool TryPurchaseItem(UplinkAccount acc, string itemId); } } diff --git a/Content.Server/PDA/PDAUplinkManager.cs b/Content.Server/PDA/PDAUplinkManager.cs index ab2b63213a..c1baaf9533 100644 --- a/Content.Server/PDA/PDAUplinkManager.cs +++ b/Content.Server/PDA/PDAUplinkManager.cs @@ -18,13 +18,13 @@ namespace Content.Server.PDA [Dependency] private readonly IEntityManager _entityManager = default!; private List _accounts; - private List _listings; + private Dictionary _listings; - public IReadOnlyList FetchListings => _listings; + public IReadOnlyDictionary FetchListings => _listings; public void Initialize() { - _listings = new List(); + _listings = new Dictionary(); foreach (var item in _prototypeManager.EnumeratePrototypes()) { var newListing = new UplinkListingData(item.ListingName, item.ItemId, item.Price, item.Category, @@ -40,26 +40,32 @@ namespace Content.Server.PDA { if (!ContainsListing(listing)) { - _listings.Add(listing); + _listings.Add(listing.ItemId, listing); } - } private bool ContainsListing(UplinkListingData listing) { - return _listings.Any(otherListing => listing.Equals(otherListing)); + return _listings.ContainsKey(listing.ItemId); } public bool AddNewAccount(UplinkAccount acc) { var entity = _entityManager.GetEntity(acc.AccountHolder); + if (entity.TryGetComponent(out MindComponent mindComponent)) { - if (mindComponent.Mind.AllRoles.Any(role => !role.Antagonist)) + if (!mindComponent.HasMind) + { + return false; + } + + if (mindComponent.Mind!.AllRoles.Any(role => !role.Antagonist)) { return false; } } + if (_accounts.Contains(acc)) { return false; @@ -72,21 +78,35 @@ namespace Content.Server.PDA public bool ChangeBalance(UplinkAccount acc, int amt) { var account = _accounts.Find(uplinkAccount => uplinkAccount.AccountHolder == acc.AccountHolder); - if (account != null && account.Balance + amt < 0) + + if (account == null) { return false; } + + if (account.Balance + amt < 0) + { + return false; + } + account.ModifyAccountBalance(account.Balance + amt); + return true; } - public bool TryPurchaseItem(UplinkAccount acc, UplinkListingData listing) + public bool TryPurchaseItem(UplinkAccount acc, string itemId) { - if (acc == null || listing == null) + if (acc == null) { return false; } - if (!ContainsListing(listing) || acc.Balance < listing.Price) + + if (!_listings.TryGetValue(itemId, out var listing)) + { + return false; + } + + if (acc.Balance < listing.Price) { return false; } @@ -95,14 +115,12 @@ namespace Content.Server.PDA { return false; } + var player = _entityManager.GetEntity(acc.AccountHolder); var hands = player.GetComponent(); hands.PutInHandOrDrop(_entityManager.SpawnEntity(listing.ItemId, player.Transform.GridPosition).GetComponent()); return true; - } - - } } diff --git a/Content.Shared/GameObjects/Components/PDA/SharedPDAComponent.cs b/Content.Shared/GameObjects/Components/PDA/SharedPDAComponent.cs index cb2fdcd60e..5051cc553c 100644 --- a/Content.Shared/GameObjects/Components/PDA/SharedPDAComponent.cs +++ b/Content.Shared/GameObjects/Components/PDA/SharedPDAComponent.cs @@ -30,7 +30,6 @@ namespace Content.Shared.GameObjects.Components.PDA } } - [Serializable, NetSerializable] public class PDAUBoundUserInterfaceState : BoundUserInterfaceState { @@ -70,10 +69,11 @@ namespace Content.Shared.GameObjects.Components.PDA [Serializable, NetSerializable] public sealed class PDAUplinkBuyListingMessage : BoundUserInterfaceMessage { - public UplinkListingData ListingToBuy; - public PDAUplinkBuyListingMessage(UplinkListingData itemToBuy) + public string ItemId; + + public PDAUplinkBuyListingMessage(string itemId) { - ListingToBuy = itemToBuy; + ItemId = itemId; } } @@ -96,8 +96,7 @@ namespace Content.Shared.GameObjects.Components.PDA } } - - [NetSerializable, Serializable] + [Serializable, NetSerializable] public struct PDAIdInfoText { public string ActualOwnerName; @@ -105,13 +104,13 @@ namespace Content.Shared.GameObjects.Components.PDA public string JobTitle; } - [NetSerializable, Serializable] + [Serializable, NetSerializable] public enum PDAVisuals { FlashlightLit, } - [NetSerializable, Serializable] + [Serializable, NetSerializable] public enum PDAUiKey { Key @@ -142,7 +141,7 @@ namespace Content.Shared.GameObjects.Components.PDA } } - [NetSerializable, Serializable] + [Serializable, NetSerializable] public class UplinkAccountData { public EntityUid DataAccountHolder; @@ -155,7 +154,7 @@ namespace Content.Shared.GameObjects.Components.PDA } } - [NetSerializable, Serializable] + [Serializable, NetSerializable] public class UplinkListingData : ComponentState, IEquatable { public string ItemId; @@ -185,5 +184,4 @@ namespace Content.Shared.GameObjects.Components.PDA return ItemId == other.ItemId; } } - } From 47d89bc280f3ac5e277eced5d26d9e387450b5f8 Mon Sep 17 00:00:00 2001 From: nuke <47336974+nuke-makes-games@users.noreply.github.com> Date: Tue, 25 Aug 2020 21:49:29 -0400 Subject: [PATCH 72/88] Fix for missing loc strings in handcuffs. (#1919) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * loc strings, formatting fixes * Fix indentation Co-authored-by: Víctor Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> --- .../Components/ActionBlocking/CuffableComponent.cs | 8 ++++---- .../Components/ActionBlocking/HandcuffComponent.cs | 8 ++++---- Content.Shared/GameObjects/ContentNetIDs.cs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs b/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs index 4d07bc1d54..4ca5bb08b8 100644 --- a/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs +++ b/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs @@ -224,7 +224,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking if (!isOwner && !ActionBlockerSystem.CanInteract(user)) { - user.PopupMessage(user, "You can't do that!"); + user.PopupMessage(user, Loc.GetString("You can't do that!")); return; } @@ -235,7 +235,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking _interactRange, ignoredEnt: Owner)) { - user.PopupMessage(user, "You are too far away to remove the cuffs."); + user.PopupMessage(user, Loc.GetString("You are too far away to remove the cuffs.")); return; } @@ -249,7 +249,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking return; } - user.PopupMessage(user, "You start removing the cuffs."); + user.PopupMessage(user, Loc.GetString("You start removing the cuffs.")); var audio = EntitySystem.Get(); audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner); @@ -305,7 +305,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking { if (!isOwner) { - _notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of {0:theName}'s hands remain cuffed.", CuffedHandCount, user)); + _notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of {1:theName}'s hands remain cuffed.", CuffedHandCount, user)); _notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} removes your cuffs. {1} of your hands remain cuffed.", user, CuffedHandCount)); } else diff --git a/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs b/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs index ea855cf6bd..ca05cf37dc 100644 --- a/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs +++ b/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs @@ -225,8 +225,8 @@ namespace Content.Server.GameObjects.Components.ActionBlocking if (result != DoAfterStatus.Cancelled) { _audioSystem.PlayFromEntity(EndCuffSound, Owner); - _notifyManager.PopupMessage(user, user, Loc.GetString("You successfully cuff {0}.", target.Name)); - _notifyManager.PopupMessage(target, target, Loc.GetString("You have been cuffed by {0}!", user.Name)); + _notifyManager.PopupMessage(user, user, Loc.GetString("You successfully cuff {0:theName}.", target)); + _notifyManager.PopupMessage(target, target, Loc.GetString("You have been cuffed by {0:theName}!", user)); if (user.TryGetComponent(out var hands)) { @@ -240,8 +240,8 @@ namespace Content.Server.GameObjects.Components.ActionBlocking } else { - user.PopupMessage(user, Loc.GetString("You were interrupted while cuffing {0}!", target.Name)); - target.PopupMessage(target, Loc.GetString("You interrupt {0} while they are cuffing you!", user.Name)); + user.PopupMessage(user, Loc.GetString("You were interrupted while cuffing {0:theName}!", target)); + target.PopupMessage(target, Loc.GetString("You interrupt {0:theName} while they are cuffing you!", user)); } } } diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 365ca7aad4..f375525110 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -68,7 +68,7 @@ public const uint BOLTACTION_BARREL = 1062; public const uint PUMP_BARREL = 1063; public const uint REVOLVER_BARREL = 1064; - public const uint CUFFED = 1065; + public const uint CUFFED = 1065; public const uint HANDCUFFS = 1066; // Net IDs for integration tests. From 1d1733185332c944104cc8aa67845c7beb632fe7 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Wed, 26 Aug 2020 03:50:26 +0200 Subject: [PATCH 73/88] (Re)add healing items (#1918) * (Re)add healing component * Do range check only when healing someone else --- .../Components/Medical/HealingComponent.cs | 71 +++++++++++++++++++ .../Entities/Objects/Specific/medical.yml | 14 ++-- 2 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 Content.Server/GameObjects/Components/Medical/HealingComponent.cs diff --git a/Content.Server/GameObjects/Components/Medical/HealingComponent.cs b/Content.Server/GameObjects/Components/Medical/HealingComponent.cs new file mode 100644 index 0000000000..f89e4a5508 --- /dev/null +++ b/Content.Server/GameObjects/Components/Medical/HealingComponent.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using Content.Server.GameObjects.Components.Stack; +using Content.Shared.Damage; +using Content.Shared.GameObjects.Components.Body; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Server.GameObjects.Components.Medical +{ + [RegisterComponent] + public class HealingComponent : Component, IAfterInteract + { + public override string Name => "Healing"; + + public Dictionary Heal { get; private set; } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(this, h => h.Heal, "heal", new Dictionary()); + } + + public void AfterInteract(AfterInteractEventArgs eventArgs) + { + if (eventArgs.Target == null) + { + return; + } + + if (!eventArgs.Target.TryGetComponent(out IBodyManagerComponent body)) + { + return; + } + + if (!ActionBlockerSystem.CanInteract(eventArgs.User)) + { + return; + } + + if (eventArgs.User != eventArgs.Target) + { + var interactionSystem = EntitySystem.Get(); + var from = eventArgs.User.Transform.MapPosition; + var to = eventArgs.Target.Transform.MapPosition; + bool Ignored(IEntity entity) => entity == eventArgs.User || entity == eventArgs.Target; + var inRange = interactionSystem.InRangeUnobstructed(from, to, predicate: Ignored); + + if (!inRange) + { + return; + } + } + + if (Owner.TryGetComponent(out StackComponent stack) && + !stack.Use(1)) + { + return; + } + + foreach (var (type, amount) in Heal) + { + body.ChangeDamage(type, -amount, true); + } + } + } +} diff --git a/Resources/Prototypes/Entities/Objects/Specific/medical.yml b/Resources/Prototypes/Entities/Objects/Specific/medical.yml index 5e7b8af68c..cda45f9d67 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/medical.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/medical.yml @@ -27,7 +27,7 @@ components: - type: Stack - type: Item - #- type: Healing + - type: Healing - type: entity name: ointment @@ -39,9 +39,9 @@ texture: Objects/Specific/Medical/ointment.png - type: Icon texture: Objects/Specific/Medical/ointment.png - #- type: Healing - # heal: 10 - # damage: Heat + - type: Healing + heal: + Heat: 10 - type: Stack max: 5 count: 5 @@ -57,9 +57,9 @@ texture: Objects/Specific/Medical/brutepack.png - type: Icon texture: Objects/Specific/Medical/brutepack.png - #- type: Healing - # heal: 10 - # damage: Brute + - type: Healing + heal: + Blunt: 10 - type: Stack max: 5 count: 5 From ca30caec03b5f22f04d4f8fcaf8da2669a642146 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 26 Aug 2020 11:53:22 +1000 Subject: [PATCH 74/88] Recycler optimise (#1873) Recyclers only check for intersecting entities that have been triggered via collision instead of actively querying it every tick. This means they pretty much don't show up on the profiler when idle. Co-authored-by: Metal Gear Sloth --- .../Components/Recycling/RecyclerComponent.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs b/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs index 93229746f5..47285443dc 100644 --- a/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs +++ b/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Content.Server.GameObjects.Components.Conveyor; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Mobs; @@ -31,6 +32,8 @@ namespace Content.Server.GameObjects.Components.Recycling [Dependency] private readonly IEntityManager _entityManager = default!; public override string Name => "Recycler"; + + private List _intersecting = new List(); /// /// Whether or not sentient beings will be recycled @@ -87,6 +90,11 @@ namespace Content.Server.GameObjects.Components.Recycling private void Recycle(IEntity entity) { + if (!_intersecting.Contains(entity)) + { + _intersecting.Add(entity); + } + // TODO: Prevent collision with recycled items if (CanGib(entity)) { @@ -166,16 +174,19 @@ namespace Content.Server.GameObjects.Components.Recycling { if (!CanRun()) { + _intersecting.Clear(); return; } - var intersecting = _entityManager.GetEntitiesIntersecting(Owner, true); var direction = Vector2.UnitX; - foreach (var entity in intersecting) + for (var i = _intersecting.Count - 1; i >= 0; i--) { - if (!CanMove(entity)) + var entity = _intersecting[i]; + + if (!CanMove(entity) || !_entityManager.IsIntersecting(Owner, entity)) { + _intersecting.RemoveAt(i); continue; } From b1e32b08f04e37e17db80f9999e9c3ee2c0aead1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Wed, 26 Aug 2020 03:58:54 +0200 Subject: [PATCH 75/88] Update submodule. --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 584e540bb2..cd5115fa85 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 584e540bb280ce07c4d26b1202f8b05c936663f6 +Subproject commit cd5115fa8523a693f040f391b43be7e04a2b9fe4 From 01ba95396de03267ff4ce6fd3d89972be23d7b45 Mon Sep 17 00:00:00 2001 From: DTanxxx <55208219+DTanxxx@users.noreply.github.com> Date: Wed, 26 Aug 2020 23:04:21 +1200 Subject: [PATCH 76/88] Fix mismatched case in audio file (#1920) --- .../Audio/Effects/{Egloves.ogg => egloves.ogg} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename Resources/Audio/Effects/{Egloves.ogg => egloves.ogg} (100%) diff --git a/Resources/Audio/Effects/Egloves.ogg b/Resources/Audio/Effects/egloves.ogg similarity index 100% rename from Resources/Audio/Effects/Egloves.ogg rename to Resources/Audio/Effects/egloves.ogg From 6a3dbc6f8c7e6ae8ca2db214939e9d0699d8182f Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 26 Aug 2020 13:47:22 +0200 Subject: [PATCH 77/88] Update submodule --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index cd5115fa85..3f0e2fa429 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit cd5115fa8523a693f040f391b43be7e04a2b9fe4 +Subproject commit 3f0e2fa4299db2c5ddc394b526dcab82cb6d1fe6 From a17239ad2c535e690e253f293b61b0877137a318 Mon Sep 17 00:00:00 2001 From: nuke <47336974+nuke-makes-games@users.noreply.github.com> Date: Wed, 26 Aug 2020 10:59:34 -0400 Subject: [PATCH 78/88] Fix for handcuff exception (#1923) --- Resources/Prototypes/Entities/Mobs/Species/human.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 241fac47ca..0ae791261b 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -227,6 +227,10 @@ color: "#e8b59b" sprite: Mobs/Species/Human/parts.rsi state: r_hand + - map: ["enum.HumanoidVisualLayers.Handcuffs"] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 - map: ["enum.Slots.IDCARD"] - map: ["enum.Slots.GLOVES"] - map: ["enum.Slots.SHOES"] From acfb452aa7afaaa0df42d2aa79dd7dc2b8474d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= Date: Wed, 26 Aug 2020 17:57:51 +0200 Subject: [PATCH 79/88] Update submodule. --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 3f0e2fa429..56d0f04c05 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 3f0e2fa4299db2c5ddc394b526dcab82cb6d1fe6 +Subproject commit 56d0f04c0536ddde3464bc93d49c37f1ed067239 From 026b7b890cf1c6f508d60322c94bf67d6de8709c Mon Sep 17 00:00:00 2001 From: py01 <60152240+collinlunn@users.noreply.github.com> Date: Thu, 27 Aug 2020 08:27:49 -0600 Subject: [PATCH 80/88] ExAct for lockers (#1931) * ExAct for lockers * ToArray Co-authored-by: py01 --- .../Items/Storage/EntityStorageComponent.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs index 3bb2cd1161..ab4c181253 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs @@ -33,7 +33,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage [RegisterComponent] [ComponentReference(typeof(IActivate))] [ComponentReference(typeof(IStorageComponent))] - public class EntityStorageComponent : Component, IActivate, IStorageComponent, IInteractUsing, IDestroyAct, IActionBlocker + public class EntityStorageComponent : Component, IActivate, IStorageComponent, IInteractUsing, IDestroyAct, IActionBlocker, IExAct { public override string Name => "EntityStorage"; @@ -430,5 +430,22 @@ namespace Content.Server.GameObjects.Components.Items.Storage data.Text = component.Open ? "Close" : "Open"; } + + void IExAct.OnExplosion(ExplosionEventArgs eventArgs) + { + if (eventArgs.Severity < ExplosionSeverity.Heavy) + { + return; + } + + foreach (var entity in Contents.ContainedEntities) + { + var exActs = entity.GetAllComponents().ToArray(); + foreach (var exAct in exActs) + { + exAct.OnExplosion(eventArgs); + } + } + } } } From 26de86058ebb654cbae2e6dfee92b6e7587c1395 Mon Sep 17 00:00:00 2001 From: py01 <60152240+collinlunn@users.noreply.github.com> Date: Thu, 27 Aug 2020 08:28:50 -0600 Subject: [PATCH 81/88] Rpg visualization fix (#1928) Co-authored-by: py01 --- .../Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs index e976c6aeb3..4f16ffc90a 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs @@ -112,7 +112,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels _appearanceComponent = appearanceComponent; } - _appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true); + UpdateAppearance(); Dirty(); } From fc6ec5a7b9d5118d79eabe9c1c89a08840b6924f Mon Sep 17 00:00:00 2001 From: Exp Date: Thu, 27 Aug 2020 16:31:29 +0200 Subject: [PATCH 82/88] Add ItemStatus to energy weapons (#1921) * -ItemStatus for BatteryGuns -EjectCell Verb -Using the gun throws the battery also on the ground * Copy the flashlight and call it a day * Name a red fruit * Remove SoundGunshot --- .../Barrels/ClientBatteryBarrelComponent.cs | 160 ++++++++++++++++++ Content.Client/IgnoredComponents.cs | 1 - .../Barrels/ServerBatteryBarrelComponent.cs | 102 ++++++++--- .../Barrels/SharedBatteryBarrelComponent.cs | 24 +++ Content.Shared/GameObjects/ContentNetIDs.cs | 1 + .../Weapons/Guns/Battery/battery_guns.yml | 4 + 6 files changed, 263 insertions(+), 29 deletions(-) create mode 100644 Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBatteryBarrelComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBatteryBarrelComponent.cs diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBatteryBarrelComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBatteryBarrelComponent.cs new file mode 100644 index 0000000000..9de6ba9b71 --- /dev/null +++ b/Content.Client/GameObjects/Components/Weapons/Ranged/Barrels/ClientBatteryBarrelComponent.cs @@ -0,0 +1,160 @@ +using Content.Client.UserInterface.Stylesheets; +using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; +using Robust.Client.Graphics.Drawing; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; +using System; + +namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels +{ + [RegisterComponent] + public class ClientBatteryBarrelComponent : Component, IItemStatus + { + public override string Name => "BatteryBarrel"; + public override uint? NetID => ContentNetIDs.BATTERY_BARREL; + + private StatusControl _statusControl; + + /// + /// Count of bullets in the magazine. + /// + /// + /// Null if no magazine is inserted. + /// + [ViewVariables] + public (int count, int max)? MagazineCount { get; private set; } + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + if (!(curState is BatteryBarrelComponentState cast)) + return; + + MagazineCount = cast.Magazine; + _statusControl?.Update(); + } + + public Control MakeControl() + { + _statusControl = new StatusControl(this); + _statusControl.Update(); + return _statusControl; + } + + public void DestroyControl(Control control) + { + if (_statusControl == control) + { + _statusControl = null; + } + } + + private sealed class StatusControl : Control + { + private readonly ClientBatteryBarrelComponent _parent; + private readonly HBoxContainer _bulletsList; + private readonly Label _noBatteryLabel; + private readonly Label _ammoCount; + + public StatusControl(ClientBatteryBarrelComponent parent) + { + _parent = parent; + SizeFlagsHorizontal = SizeFlags.FillExpand; + SizeFlagsVertical = SizeFlags.ShrinkCenter; + + AddChild(new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + new Control + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + (_bulletsList = new HBoxContainer + { + SizeFlagsVertical = SizeFlags.ShrinkCenter, + SeparationOverride = 4 + }), + (_noBatteryLabel = new Label + { + Text = "No Battery!", + StyleClasses = {StyleNano.StyleClassItemStatus} + }) + } + }, + new Control() { CustomMinimumSize = (5,0) }, + (_ammoCount = new Label + { + StyleClasses = {StyleNano.StyleClassItemStatus}, + SizeFlagsHorizontal = SizeFlags.ShrinkEnd, + }), + } + }); + } + + public void Update() + { + _bulletsList.RemoveAllChildren(); + + if (_parent.MagazineCount == null) + { + _noBatteryLabel.Visible = true; + _ammoCount.Visible = false; + return; + } + + var (count, capacity) = _parent.MagazineCount.Value; + + _noBatteryLabel.Visible = false; + _ammoCount.Visible = true; + + _ammoCount.Text = $"x{count:00}"; + capacity = Math.Min(capacity, 8); + FillBulletRow(_bulletsList, count, capacity); + } + + private static void FillBulletRow(Control container, int count, int capacity) + { + var colorGone = Color.FromHex("#000000"); + var color = Color.FromHex("#E00000"); + + // Draw the empty ones + for (var i = count; i < capacity; i++) + { + container.AddChild(new PanelContainer + { + PanelOverride = new StyleBoxFlat() + { + BackgroundColor = colorGone, + }, + CustomMinimumSize = (10, 15), + }); + } + + // Draw the full ones, but limit the count to the capacity + count = Math.Min(count, capacity); + for (var i = 0; i < count; i++) + { + container.AddChild(new PanelContainer + { + PanelOverride = new StyleBoxFlat() + { + BackgroundColor = color, + }, + CustomMinimumSize = (10, 15), + }); + } + } + + protected override Vector2 CalculateMinimumSize() + { + return Vector2.ComponentMax((0, 15), base.CalculateMinimumSize()); + } + } + } +} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 9a72a27e22..28545a1909 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -105,7 +105,6 @@ "SecureEntityStorage", "PresetIdCard", "SolarControlConsole", - "BatteryBarrel", "FlashExplosive", "FlashProjectile", "Utensil", diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs index 486d2071b1..d81aa8115e 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs @@ -6,7 +6,10 @@ using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Power; using Content.Server.GameObjects.Components.Projectiles; using Content.Shared.Damage; +using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Verbs; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; @@ -25,6 +28,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels public sealed class ServerBatteryBarrelComponent : ServerRangedBarrelComponent { public override string Name => "BatteryBarrel"; + public override uint? NetID => ContentNetIDs.BATTERY_BARREL; // The minimum change we need before we can fire [ViewVariables] private float _lowerChargeLimit; @@ -88,6 +92,15 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels serializer.DataField(ref _soundPowerCellEject, "soundPowerCellEject", null); } + public override ComponentState GetComponentState() + { + (int, int)? count = (ShotsLeft, Capacity); + + return new BatteryBarrelComponentState( + FireRateSelector, + count); + } + public override void Initialize() { base.Initialize(); @@ -108,6 +121,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels _appearanceComponent = appearanceComponent; } + Dirty(); UpdateAppearance(); } @@ -188,8 +202,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels throw new InvalidOperationException("Ammo doesn't have hitscan or projectile?"); } + Dirty(); UpdateAppearance(); - //Dirty(); return entity; } @@ -211,30 +225,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } _powerCellContainer.Insert(entity); + + Dirty(); UpdateAppearance(); - //Dirty(); return true; } - private IEntity RemovePowerCell() - { - if (!_powerCellRemovable || _powerCellContainer.ContainedEntity == null) - { - return null; - } - - var entity = _powerCellContainer.ContainedEntity; - _powerCellContainer.Remove(entity); - if (_soundPowerCellEject != null) - { - EntitySystem.Get().PlayAtCoords(_soundPowerCellEject, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); - } - - UpdateAppearance(); - //Dirty(); - return entity; - } - public override bool UseEntity(UseEntityEventArgs eventArgs) { if (!_powerCellRemovable) @@ -242,22 +238,44 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels return false; } - if (!eventArgs.User.TryGetComponent(out HandsComponent handsComponent) || - PowerCellEntity == null) + if (PowerCellEntity == null) { return false; } - var itemComponent = PowerCellEntity.GetComponent(); - if (!handsComponent.CanPutInHand(itemComponent)) + return TryEjectCell(eventArgs.User); + } + + private bool TryEjectCell(IEntity user) + { + if (PowerCell == null || !_powerCellRemovable) { return false; } - var powerCell = RemovePowerCell(); - handsComponent.PutInHand(itemComponent); - powerCell.Transform.GridPosition = eventArgs.User.Transform.GridPosition; + if (!user.TryGetComponent(out HandsComponent hands)) + { + return false; + } + var cell = PowerCell; + if (!_powerCellContainer.Remove(cell.Owner)) + { + return false; + } + + Dirty(); + UpdateAppearance(); + + if (!hands.PutInHand(cell.Owner.GetComponent())) + { + cell.Owner.Transform.GridPosition = user.Transform.GridPosition; + } + + if (_soundPowerCellEject != null) + { + EntitySystem.Get().PlayAtCoords(_soundPowerCellEject, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2)); + } return true; } @@ -270,5 +288,33 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels return TryInsertPowerCell(eventArgs.Using); } + + [Verb] + public sealed class EjectCellVerb : Verb + { + protected override void GetData(IEntity user, ServerBatteryBarrelComponent component, VerbData data) + { + if (!ActionBlockerSystem.CanInteract(user) || !component._powerCellRemovable) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + if (component.PowerCell == null) + { + data.Text = "Eject cell (cell missing)"; + data.Visibility = VerbVisibility.Disabled; + } + else + { + data.Text = "Eject cell"; + } + } + + protected override void Activate(IEntity user, ServerBatteryBarrelComponent component) + { + component.TryEjectCell(user); + } + } } } diff --git a/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBatteryBarrelComponent.cs b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBatteryBarrelComponent.cs new file mode 100644 index 0000000000..40ef9b47e1 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Weapons/Ranged/Barrels/SharedBatteryBarrelComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels +{ + [Serializable, NetSerializable] + public class BatteryBarrelComponentState : ComponentState + { + public FireRateSelector FireRateSelector { get; } + public (int count, int max)? Magazine { get; } + + public BatteryBarrelComponentState( + FireRateSelector fireRateSelector, + (int count, int max)? magazine) : + base(ContentNetIDs.BATTERY_BARREL) + { + FireRateSelector = fireRateSelector; + Magazine = magazine; + } + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index f375525110..32ede76f3d 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -70,6 +70,7 @@ public const uint REVOLVER_BARREL = 1064; public const uint CUFFED = 1065; public const uint HANDCUFFS = 1066; + public const uint BATTERY_BARREL = 1067; // Net IDs for integration tests. public const uint PREDICTION_TEST = 10001; diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml index ef5ae605f1..f57ce3d57f 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -30,6 +30,7 @@ - Single fireRate: 2 powerCellPrototype: PowerCellSmallStandard + powerCellRemovable: true ammoPrototype: RedLaser soundGunshot: /Audio/Weapons/Guns/Gunshots/laser.ogg - type: Appearance @@ -71,6 +72,7 @@ angleIncrease: 15 angleDecay: 45 powerCellPrototype: PowerCellSmallSuper + powerCellRemovable: true ammoPrototype: RedHeavyLaser soundGunshot: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg - type: Appearance @@ -112,6 +114,7 @@ angleIncrease: 15 angleDecay: 45 powerCellPrototype: PowerCellSmallSuper + powerCellRemovable: true base_fire_cost: 600 ammoPrototype: XrayLaser soundGunshot: /Audio/Weapons/Guns/Gunshots/laser3.ogg @@ -155,6 +158,7 @@ angleIncrease: 20 angleDecay: 15 powerCellPrototype: PowerCellSmallStandard + powerCellRemovable: false ammoPrototype: BulletTaser soundGunshot: /Audio/Weapons/Guns/Gunshots/taser.ogg - type: Appearance From 388e717a5384d42c017b55e4960f08c0e48251e4 Mon Sep 17 00:00:00 2001 From: nuke <47336974+nuke-makes-games@users.noreply.github.com> Date: Thu, 27 Aug 2020 10:33:10 -0400 Subject: [PATCH 83/88] Get rid of CuffSystem, make CuffableComponent update its hand count properly (#1927) * Merge branch 'master' of https://github.com/space-wizards/space-station-14 into cuff-fix * yml * event bus --- .../ActionBlocking/CuffableComponent.cs | 24 +++++++++++-------- .../Components/GUI/HandsComponent.cs | 13 ++++++++++ .../GameObjects/EntitySystems/CuffSystem.cs | 18 -------------- .../Entities/Mobs/Species/human.yml | 2 ++ 4 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 Content.Server/GameObjects/EntitySystems/CuffSystem.cs diff --git a/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs b/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs index 4ca5bb08b8..3325fc2caa 100644 --- a/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs +++ b/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs @@ -22,7 +22,7 @@ using Content.Shared.GameObjects.Components.Mobs; using Robust.Shared.Maths; using System; using System.Collections.Generic; -using Serilog; +using Content.Server.GameObjects.Components.GUI; namespace Content.Server.GameObjects.Components.ActionBlocking { @@ -48,7 +48,6 @@ namespace Content.Server.GameObjects.Components.ActionBlocking [ViewVariables(VVAccess.ReadOnly)] private Container _container = default!; - private bool _dirtyThisFrame = false; private float _interactRange; private IHandsComponent _hands; @@ -61,6 +60,8 @@ namespace Content.Server.GameObjects.Components.ActionBlocking _container = ContainerManagerComponent.Ensure(Name, Owner); _interactRange = SharedInteractionSystem.InteractionRange / 2; + Owner.EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, HandleHandCountChange); + if (!Owner.TryGetComponent(out _hands)) { Logger.Warning("Player does not have an IHandsComponent!"); @@ -127,29 +128,24 @@ namespace Content.Server.GameObjects.Components.ActionBlocking Dirty(); } - public void Update(float frameTime) - { - UpdateHandCount(); - } - /// /// Check the current amount of hands the owner has, and if there's less hands than active cuffs we remove some cuffs. /// private void UpdateHandCount() { - _dirtyThisFrame = false; + var dirty = false; var handCount = _hands.Hands.Count(); while (CuffedHandCount > handCount && CuffedHandCount > 0) { - _dirtyThisFrame = true; + dirty = true; var entity = _container.ContainedEntities[_container.ContainedEntities.Count - 1]; _container.Remove(entity); entity.Transform.WorldPosition = Owner.Transform.GridPosition.Position; } - if (_dirtyThisFrame) + if (dirty) { CanStillInteract = handCount > CuffedHandCount; OnCuffedStateChanged.Invoke(); @@ -157,6 +153,14 @@ namespace Content.Server.GameObjects.Components.ActionBlocking } } + private void HandleHandCountChange(HandCountChangedEvent message) + { + if (message.Sender == Owner) + { + UpdateHandCount(); + } + } + /// /// Check how many items the user is holding and if it's more than the number of cuffed hands, drop some items. /// diff --git a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs index ade7beee0d..bda951b392 100644 --- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -29,6 +29,7 @@ using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Players; using Robust.Shared.ViewVariables; +using Content.Server.GameObjects.Components.ActionBlocking; namespace Content.Server.GameObjects.Components.GUI { @@ -444,6 +445,7 @@ namespace Content.Server.GameObjects.Components.GUI ActiveHand ??= name; OnItemChanged?.Invoke(); + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner)); Dirty(); } @@ -466,6 +468,7 @@ namespace Content.Server.GameObjects.Components.GUI } OnItemChanged?.Invoke(); + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner)); Dirty(); } @@ -791,4 +794,14 @@ namespace Content.Server.GameObjects.Components.GUI return new SharedHand(index, Name, Entity?.Uid, location); } } + + public class HandCountChangedEvent : EntitySystemMessage + { + public HandCountChangedEvent(IEntity sender) + { + Sender = sender; + } + + public IEntity Sender { get; } + } } diff --git a/Content.Server/GameObjects/EntitySystems/CuffSystem.cs b/Content.Server/GameObjects/EntitySystems/CuffSystem.cs deleted file mode 100644 index f08ddae3ba..0000000000 --- a/Content.Server/GameObjects/EntitySystems/CuffSystem.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Server.GameObjects.Components.ActionBlocking; -using JetBrains.Annotations; -using Robust.Shared.GameObjects.Systems; - -namespace Content.Server.GameObjects.EntitySystems -{ - [UsedImplicitly] - internal sealed class CuffSystem : EntitySystem - { - public override void Update(float frameTime) - { - foreach (var comp in ComponentManager.EntityQuery()) - { - comp.Update(frameTime); - } - } - } -} diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 0ae791261b..43a4507138 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -94,6 +94,7 @@ color: "#ffffff" sprite: Objects/Misc/handcuffs.rsi state: body-overlay-2 + visible: false - map: ["enum.Slots.IDCARD"] - map: ["enum.Slots.GLOVES"] - map: ["enum.Slots.SHOES"] @@ -231,6 +232,7 @@ color: "#ffffff" sprite: Objects/Misc/handcuffs.rsi state: body-overlay-2 + visible: false - map: ["enum.Slots.IDCARD"] - map: ["enum.Slots.GLOVES"] - map: ["enum.Slots.SHOES"] From 548ef3dedb182108404f25df262378002084b1c2 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Thu, 27 Aug 2020 16:39:29 +0200 Subject: [PATCH 84/88] Add HUD button that displays your SSS role and allies (#1895) * Add button that displays your SSS role and allies * Capitalize button name * Add cases for 0, 1 and invalid number of allies * Make the ally syncing system saner --- .../Components/Items/HandsComponent.cs | 4 +- .../Suspicion/SuspicionRoleComponent.cs | 109 ++++++++++ Content.Client/UserInterface/GameHud.cs | 13 ++ .../UserInterface/Suspicion/SuspicionGui.cs | 116 ++++++++++ .../Suspicion/SuspicionRoleComponent.cs | 202 +++++++++++++++++- .../EntitySystems/SuspicionRoleSystem.cs | 51 +++++ .../GamePresets/PresetSuspicion.cs | 3 +- .../GameTicking/GameRules/RuleSuspicion.cs | 1 + Content.Server/Mobs/Mind.cs | 14 +- Content.Server/Mobs/Roles/RoleAddedMessage.cs | 7 + Content.Server/Mobs/Roles/RoleMessage.cs | 14 ++ .../Mobs/Roles/RoleRemovedMessage.cs | 7 + .../{ => Suspicion}/SuspicionInnocentRole.cs | 4 +- .../Mobs/Roles/Suspicion/SuspicionRole.cs | 7 + .../{ => Suspicion}/SuspicionTraitorRole.cs | 3 +- .../Suspicion/SharedSuspicionRoleComponent.cs | 75 +++++++ Content.Shared/GameObjects/ContentNetIDs.cs | 1 + 17 files changed, 616 insertions(+), 15 deletions(-) create mode 100644 Content.Client/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs create mode 100644 Content.Client/UserInterface/Suspicion/SuspicionGui.cs create mode 100644 Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs create mode 100644 Content.Server/Mobs/Roles/RoleAddedMessage.cs create mode 100644 Content.Server/Mobs/Roles/RoleMessage.cs create mode 100644 Content.Server/Mobs/Roles/RoleRemovedMessage.cs rename Content.Server/Mobs/Roles/{ => Suspicion}/SuspicionInnocentRole.cs (89%) create mode 100644 Content.Server/Mobs/Roles/Suspicion/SuspicionRole.cs rename Content.Server/Mobs/Roles/{ => Suspicion}/SuspicionTraitorRole.cs (94%) create mode 100644 Content.Shared/GameObjects/Components/Suspicion/SharedSuspicionRoleComponent.cs diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs index 4a11eeb584..2750547ad3 100644 --- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs +++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs @@ -17,10 +17,10 @@ namespace Content.Client.GameObjects.Components.Items [ComponentReference(typeof(ISharedHandsComponent))] public class HandsComponent : SharedHandsComponent { - private HandsGui? _gui; - [Dependency] private readonly IGameHud _gameHud = default!; + private HandsGui? _gui; + /// private readonly List _hands = new List(); diff --git a/Content.Client/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs b/Content.Client/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs new file mode 100644 index 0000000000..82cf727f90 --- /dev/null +++ b/Content.Client/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs @@ -0,0 +1,109 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.Client.UserInterface; +using Content.Client.UserInterface.Suspicion; +using Content.Shared.GameObjects.Components.Suspicion; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.IoC; +using Robust.Shared.Players; + +namespace Content.Client.GameObjects.Components.Suspicion +{ + [RegisterComponent] + public class SuspicionRoleComponent : SharedSuspicionRoleComponent + { + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + private SuspicionGui? _gui; + private string? _role; + private bool? _antagonist; + + public string? Role + { + get => _role; + set + { + _role = value; + _gui?.UpdateLabel(); + Dirty(); + } + } + + public bool? Antagonist + { + get => _antagonist; + set + { + _antagonist = value; + _gui?.UpdateLabel(); + Dirty(); + } + } + + public HashSet Allies { get; } = new HashSet(); + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + base.HandleComponentState(curState, nextState); + + if (!(curState is SuspicionRoleComponentState state)) + { + return; + } + + _role = state.Role; + _antagonist = state.Antagonist; + } + + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + + switch (message) + { + case PlayerAttachedMsg _: + if (_gui == null) + { + _gui = new SuspicionGui(); + } + else + { + _gui.Parent?.RemoveChild(_gui); + } + + _gameHud.SuspicionContainer.AddChild(_gui); + _gui.UpdateLabel(); + + break; + case PlayerDetachedMsg _: + _gui?.Parent?.RemoveChild(_gui); + break; + } + } + + public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) + { + base.HandleNetworkMessage(message, netChannel, session); + + switch (message) + { + case SuspicionAlliesMessage msg: + Allies.Clear(); + Allies.UnionWith(msg.Allies.Select(_entityManager.GetEntity)); + break; + } + } + + public override void OnRemove() + { + base.OnRemove(); + + _gui?.Dispose(); + } + } +} diff --git a/Content.Client/UserInterface/GameHud.cs b/Content.Client/UserInterface/GameHud.cs index ced6e8a15b..51ed48c2ad 100644 --- a/Content.Client/UserInterface/GameHud.cs +++ b/Content.Client/UserInterface/GameHud.cs @@ -47,6 +47,7 @@ namespace Content.Client.UserInterface Action SandboxButtonToggled { get; set; } Control HandsContainer { get; } + Control SuspicionContainer { get; } Control InventoryQuickButtonContainer { get; } bool CombatPanelVisible { get; set; } @@ -79,6 +80,7 @@ namespace Content.Client.UserInterface [Dependency] private readonly IInputManager _inputManager = default!; public Control HandsContainer { get; private set; } + public Control SuspicionContainer { get; private set; } public Control InventoryQuickButtonContainer { get; private set; } public bool CombatPanelVisible @@ -242,6 +244,17 @@ namespace Content.Client.UserInterface LayoutContainer.SetAnchorAndMarginPreset(HandsContainer, LayoutContainer.LayoutPreset.CenterBottom); LayoutContainer.SetGrowHorizontal(HandsContainer, LayoutContainer.GrowDirection.Both); LayoutContainer.SetGrowVertical(HandsContainer, LayoutContainer.GrowDirection.Begin); + + SuspicionContainer = new MarginContainer + { + SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter + }; + + RootControl.AddChild(SuspicionContainer); + + LayoutContainer.SetAnchorAndMarginPreset(SuspicionContainer, LayoutContainer.LayoutPreset.BottomLeft, margin: 10); + LayoutContainer.SetGrowHorizontal(SuspicionContainer, LayoutContainer.GrowDirection.End); + LayoutContainer.SetGrowVertical(SuspicionContainer, LayoutContainer.GrowDirection.Begin); } private void ButtonTutorialOnOnToggled() diff --git a/Content.Client/UserInterface/Suspicion/SuspicionGui.cs b/Content.Client/UserInterface/Suspicion/SuspicionGui.cs new file mode 100644 index 0000000000..e466eb8074 --- /dev/null +++ b/Content.Client/UserInterface/Suspicion/SuspicionGui.cs @@ -0,0 +1,116 @@ +using System; +using System.Globalization; +using Content.Client.GameObjects.Components.Suspicion; +using Content.Shared.Interfaces; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.Timing; +using static Robust.Client.UserInterface.Controls.BaseButton; + +namespace Content.Client.UserInterface.Suspicion +{ + public class SuspicionGui : Control + { +#pragma warning disable 0649 + [Dependency] private readonly IPlayerManager _playerManager; +#pragma warning restore 0649 + + private readonly VBoxContainer _container; + private readonly Button _roleButton; + + private string _previousRoleName; + private bool _previousAntagonist; + + public SuspicionGui() + { + IoCManager.InjectDependencies(this); + + AddChild(_container = new VBoxContainer + { + SeparationOverride = 0, + Children = + { + (_roleButton = new Button + { + Name = "Suspicion Role Button" + }) + } + }); + + _roleButton.CustomMinimumSize = (200, 60); + _roleButton.OnPressed += RoleButtonPressed; + } + + private void RoleButtonPressed(ButtonEventArgs obj) + { + if (!TryGetComponent(out var role)) + { + return; + } + + if (!role.Antagonist ?? false) + { + return; + } + + var allies = string.Join(", ", role.Allies); + var message = role.Allies.Count switch + { + 0 => Loc.GetString("You have no allies"), + 1 => Loc.GetString("Your ally is {0}", allies), + var n when n > 2 => Loc.GetString("Your allies are {0}", allies), + _ => throw new ArgumentException($"Invalid number of allies: {role.Allies.Count}") + }; + + role.Owner.PopupMessage(role.Owner, message); + } + + private bool TryGetComponent(out SuspicionRoleComponent suspicion) + { + suspicion = default; + + return _playerManager?.LocalPlayer?.ControlledEntity?.TryGetComponent(out suspicion) == true; + } + + public void UpdateLabel() + { + if (!TryGetComponent(out var suspicion)) + { + Visible = false; + return; + } + + if (suspicion.Role == null || suspicion.Antagonist == null) + { + Visible = false; + return; + } + + if (_previousRoleName == suspicion.Role && _previousAntagonist == suspicion.Antagonist) + { + return; + } + + _previousRoleName = suspicion.Role; + _previousAntagonist = suspicion.Antagonist.Value; + + var buttonText = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(_previousRoleName); + buttonText = Loc.GetString(buttonText); + + _roleButton.Text = buttonText; + _roleButton.ModulateSelfOverride = _previousAntagonist ? Color.Red : Color.Green; + + Visible = true; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + UpdateLabel(); + } + } +} diff --git a/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs index d982130afa..70071bc28b 100644 --- a/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs +++ b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs @@ -1,31 +1,185 @@ -using Content.Server.GameObjects.Components.Mobs; +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.EntitySystems; +using Content.Server.Mobs; using Content.Server.Mobs.Roles; +using Content.Server.Mobs.Roles.Suspicion; using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Suspicion; using Content.Shared.GameObjects.EntitySystems; +using Robust.Server.GameObjects; +using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Suspicion { [RegisterComponent] - public class SuspicionRoleComponent : Component, IExamine + public class SuspicionRoleComponent : SharedSuspicionRoleComponent, IExamine { - public override string Name => "SuspicionRole"; + private Role? _role; + private readonly HashSet _allies = new HashSet(); + + [ViewVariables] + public Role? Role + { + get => _role; + set + { + if (_role == value) + { + return; + } + + _role = value; + + Dirty(); + + var suspicionRoleSystem = EntitySystem.Get(); + + if (value == null || !value.Antagonist) + { + ClearAllies(); + suspicionRoleSystem.RemoveTraitor(this); + } + else if (value.Antagonist) + { + SetAllies(suspicionRoleSystem.Traitors); + suspicionRoleSystem.AddTraitor(this); + } + } + } + + [ViewVariables] public bool KnowsAllies => IsTraitor(); public bool IsDead() { - return Owner.TryGetComponent(out IDamageableComponent damageable) && + return Owner.TryGetComponent(out IDamageableComponent? damageable) && damageable.CurrentDamageState == DamageState.Dead; } + public bool IsInnocent() + { + return Owner.TryGetComponent(out MindComponent? mind) && + mind.HasMind && + mind.Mind!.HasRole(); + } + public bool IsTraitor() { - return Owner.TryGetComponent(out MindComponent mind) && + return Owner.TryGetComponent(out MindComponent? mind) && mind.HasMind && mind.Mind!.HasRole(); } + public void SyncRoles() + { + if (!Owner.TryGetComponent(out MindComponent? mind) || + !mind.HasMind) + { + return; + } + + Role = mind.Mind!.AllRoles.First(role => role is SuspicionRole); + } + + public void AddAlly(SuspicionRoleComponent ally) + { + if (ally == this) + { + return; + } + + _allies.Add(ally); + + if (KnowsAllies && Owner.TryGetComponent(out IActorComponent? actor)) + { + var channel = actor.playerSession.ConnectedClient; + DebugTools.AssertNotNull(channel); + + var message = new SuspicionAllyAddedMessage(ally.Owner.Uid); + + SendNetworkMessage(message, channel); + } + } + + public bool RemoveAlly(SuspicionRoleComponent ally) + { + if (ally == this) + { + return false; + } + + if (_allies.Remove(ally)) + { + if (KnowsAllies && Owner.TryGetComponent(out IActorComponent? actor)) + { + var channel = actor.playerSession.ConnectedClient; + DebugTools.AssertNotNull(channel); + + var message = new SuspicionAllyRemovedMessage(ally.Owner.Uid); + + SendNetworkMessage(message, channel); + } + + return true; + } + + return false; + } + + public void SetAllies(IEnumerable allies) + { + _allies.Clear(); + + foreach (var ally in allies) + { + if (ally == this) + { + continue; + } + + _allies.Add(ally); + } + + if (!KnowsAllies || + !Owner.TryGetComponent(out IActorComponent? actor)) + { + return; + } + + var channel = actor.playerSession.ConnectedClient; + DebugTools.AssertNotNull(channel); + + var message = new SuspicionAlliesMessage(_allies.Select(role => role.Owner.Uid)); + + SendNetworkMessage(message, channel); + } + + public void ClearAllies() + { + _allies.Clear(); + + if (!KnowsAllies || + !Owner.TryGetComponent(out IActorComponent? actor)) + { + return; + } + + var channel = actor.playerSession.ConnectedClient; + DebugTools.AssertNotNull(channel); + + var message = new SuspicionAlliesClearedMessage(); + + SendNetworkMessage(message, channel); + } + void IExamine.Examine(FormattedMessage message, bool inDetailsRange) { if (!IsDead()) @@ -39,5 +193,43 @@ namespace Content.Server.GameObjects.Components.Suspicion message.AddMarkup(tooltip); } + + public override void OnRemove() + { + Role = null; + base.OnRemove(); + } + + public override ComponentState GetComponentState() + { + return Role == null + ? new SuspicionRoleComponentState(null, null) + : new SuspicionRoleComponentState(Role?.Name, Role?.Antagonist); + } + + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + + if (!(message is RoleMessage msg) || + !(msg.Role is SuspicionRole role)) + { + return; + } + + switch (message) + { + case PlayerAttachedMsg _: + case PlayerDetachedMsg _: + SyncRoles(); + break; + case RoleAddedMessage _: + Role = role; + break; + case RoleRemovedMessage _: + Role = null; + break; + } + } } } diff --git a/Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs b/Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs new file mode 100644 index 0000000000..936fe24185 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Content.Server.GameObjects.Components.Suspicion; +using JetBrains.Annotations; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Server.GameObjects.EntitySystems +{ + [UsedImplicitly] + public class SuspicionRoleSystem : EntitySystem + { + private readonly HashSet _traitors = new HashSet(); + + public IReadOnlyCollection Traitors => _traitors; + + public void AddTraitor(SuspicionRoleComponent role) + { + if (!_traitors.Add(role)) + { + return; + } + + foreach (var traitor in _traitors) + { + traitor.AddAlly(role); + } + + role.SetAllies(_traitors); + } + + public void RemoveTraitor(SuspicionRoleComponent role) + { + if (!_traitors.Remove(role)) + { + return; + } + + foreach (var traitor in _traitors) + { + traitor.RemoveAlly(role); + } + + role.ClearAllies(); + } + + public override void Shutdown() + { + _traitors.Clear(); + base.Shutdown(); + } + } +} diff --git a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs index 81eeb43d22..eb192dbea6 100644 --- a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs +++ b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs @@ -1,7 +1,6 @@ using Content.Server.GameTicking.GameRules; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; -using Content.Server.Mobs.Roles; using Content.Server.Players; using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.Random; @@ -11,6 +10,8 @@ using Robust.Shared.Random; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Suspicion; +using Content.Server.Mobs.Roles; +using Content.Server.Mobs.Roles.Suspicion; using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.Configuration; diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs index b67888d6f0..0161ab409a 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -5,6 +5,7 @@ using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; using Content.Server.Mobs.Roles; +using Content.Server.Mobs.Roles.Suspicion; using Content.Server.Players; using Content.Shared.GameObjects.Components.Damage; using Robust.Server.GameObjects.EntitySystems; diff --git a/Content.Server/Mobs/Mind.cs b/Content.Server/Mobs/Mind.cs index b0247fc202..1fc6e5f71c 100644 --- a/Content.Server/Mobs/Mind.cs +++ b/Content.Server/Mobs/Mind.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Mobs; +using Content.Server.Mobs.Roles; using Content.Server.Players; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; @@ -95,7 +96,7 @@ namespace Content.Server.Mobs /// /// Gives this mind a new role. /// - /// The type of the role to give. + /// The type of the role to give. /// The instance of the role. /// /// Thrown if we already have a role with this type. @@ -109,13 +110,17 @@ namespace Content.Server.Mobs _roles.Add(role); role.Greet(); + + var message = new RoleAddedMessage(role); + OwnedEntity?.SendMessage(OwnedMob, message); + return role; } /// /// Removes a role from this mind. /// - /// The type of the role to remove. + /// The type of the role to remove. /// /// Thrown if we do not have this role. /// @@ -126,9 +131,10 @@ namespace Content.Server.Mobs throw new ArgumentException($"We do not have this role: {role}"); } - // This can definitely get more complex removal hooks later, - // when we need it. _roles.Remove(role); + + var message = new RoleRemovedMessage(role); + OwnedEntity?.SendMessage(OwnedMob, message); } public bool HasRole() where T : Role diff --git a/Content.Server/Mobs/Roles/RoleAddedMessage.cs b/Content.Server/Mobs/Roles/RoleAddedMessage.cs new file mode 100644 index 0000000000..c4de66e333 --- /dev/null +++ b/Content.Server/Mobs/Roles/RoleAddedMessage.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Mobs.Roles +{ + public class RoleAddedMessage : RoleMessage + { + public RoleAddedMessage(Role role) : base(role) { } + } +} diff --git a/Content.Server/Mobs/Roles/RoleMessage.cs b/Content.Server/Mobs/Roles/RoleMessage.cs new file mode 100644 index 0000000000..1a4489ddc7 --- /dev/null +++ b/Content.Server/Mobs/Roles/RoleMessage.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameObjects; + +namespace Content.Server.Mobs.Roles +{ + public class RoleMessage : ComponentMessage + { + public readonly Role Role; + + public RoleMessage(Role role) + { + Role = role; + } + } +} diff --git a/Content.Server/Mobs/Roles/RoleRemovedMessage.cs b/Content.Server/Mobs/Roles/RoleRemovedMessage.cs new file mode 100644 index 0000000000..0e8cd0ef85 --- /dev/null +++ b/Content.Server/Mobs/Roles/RoleRemovedMessage.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Mobs.Roles +{ + public class RoleRemovedMessage : RoleMessage + { + public RoleRemovedMessage(Role role) : base(role) { } + } +} diff --git a/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs b/Content.Server/Mobs/Roles/Suspicion/SuspicionInnocentRole.cs similarity index 89% rename from Content.Server/Mobs/Roles/SuspicionInnocentRole.cs rename to Content.Server/Mobs/Roles/Suspicion/SuspicionInnocentRole.cs index 570e307c79..0fab5b7059 100644 --- a/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs +++ b/Content.Server/Mobs/Roles/Suspicion/SuspicionInnocentRole.cs @@ -2,9 +2,9 @@ using Content.Server.Interfaces.Chat; using Content.Shared.Roles; using Robust.Shared.IoC; -namespace Content.Server.Mobs.Roles +namespace Content.Server.Mobs.Roles.Suspicion { - public class SuspicionInnocentRole : Role + public class SuspicionInnocentRole : SuspicionRole { public AntagPrototype Prototype { get; } diff --git a/Content.Server/Mobs/Roles/Suspicion/SuspicionRole.cs b/Content.Server/Mobs/Roles/Suspicion/SuspicionRole.cs new file mode 100644 index 0000000000..f9b46aecd3 --- /dev/null +++ b/Content.Server/Mobs/Roles/Suspicion/SuspicionRole.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Mobs.Roles.Suspicion +{ + public abstract class SuspicionRole : Role + { + protected SuspicionRole(Mind mind) : base(mind) { } + } +} diff --git a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs b/Content.Server/Mobs/Roles/Suspicion/SuspicionTraitorRole.cs similarity index 94% rename from Content.Server/Mobs/Roles/SuspicionTraitorRole.cs rename to Content.Server/Mobs/Roles/Suspicion/SuspicionTraitorRole.cs index f10bf273e8..d265bb23bf 100644 --- a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs +++ b/Content.Server/Mobs/Roles/Suspicion/SuspicionTraitorRole.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Content.Server.Interfaces.Chat; +using Content.Server.Mobs.Roles.Suspicion; using Content.Shared.Roles; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.Audio; @@ -9,7 +10,7 @@ using Robust.Shared.Localization; namespace Content.Server.Mobs.Roles { - public sealed class SuspicionTraitorRole : Role + public sealed class SuspicionTraitorRole : SuspicionRole { public AntagPrototype Prototype { get; } diff --git a/Content.Shared/GameObjects/Components/Suspicion/SharedSuspicionRoleComponent.cs b/Content.Shared/GameObjects/Components/Suspicion/SharedSuspicionRoleComponent.cs new file mode 100644 index 0000000000..c31e2e7cce --- /dev/null +++ b/Content.Shared/GameObjects/Components/Suspicion/SharedSuspicionRoleComponent.cs @@ -0,0 +1,75 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Suspicion +{ + public abstract class SharedSuspicionRoleComponent : Component + { + public sealed override string Name => "SuspicionRole"; + public sealed override uint? NetID => ContentNetIDs.SUSPICION_ROLE; + } + + [Serializable, NetSerializable] + public class SuspicionRoleComponentState : ComponentState + { + public readonly string? Role; + public readonly bool? Antagonist; + + public SuspicionRoleComponentState(string? role, bool? antagonist) : base(ContentNetIDs.SUSPICION_ROLE) + { + Role = role; + Antagonist = antagonist; + } + } + + [Serializable, NetSerializable] + public class SuspicionAlliesMessage : ComponentMessage + { + public readonly HashSet Allies; + + public SuspicionAlliesMessage(HashSet allies) + { + Directed = true; + Allies = allies; + } + + public SuspicionAlliesMessage(IEnumerable allies) : this(allies.ToHashSet()) { } + } + + [Serializable, NetSerializable] + public class SuspicionAllyAddedMessage : ComponentMessage + { + public readonly EntityUid Ally; + + public SuspicionAllyAddedMessage(EntityUid ally) + { + Directed = true; + Ally = ally; + } + } + + [Serializable, NetSerializable] + public class SuspicionAllyRemovedMessage : ComponentMessage + { + public readonly EntityUid Ally; + + public SuspicionAllyRemovedMessage(EntityUid ally) + { + Directed = true; + Ally = ally; + } + } + + [Serializable, NetSerializable] + public class SuspicionAlliesClearedMessage : ComponentMessage + { + public SuspicionAlliesClearedMessage() + { + Directed = true; + } + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 32ede76f3d..3f127c68c4 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -71,6 +71,7 @@ public const uint CUFFED = 1065; public const uint HANDCUFFS = 1066; public const uint BATTERY_BARREL = 1067; + public const uint SUSPICION_ROLE = 1068; // Net IDs for integration tests. public const uint PREDICTION_TEST = 10001; From 7b12d4e08c0ce3f0e5c5e8022ce9eb6fc22a4e78 Mon Sep 17 00:00:00 2001 From: py01 <60152240+collinlunn@users.noreply.github.com> Date: Thu, 27 Aug 2020 09:45:27 -0600 Subject: [PATCH 85/88] PipeNet (#1626) * PipeNode * Pipe prototypes * Fixes Default NodeGroup not being registered by NodeGroupFactory * GasNet * PumpComponent * IPipeNet * PipeComponent * misc naming, yaml * PipeComponent rework * PipeNet gas transfer from pipes * PipeNet correctly combines gas on combining with other group * Client ignores piping components * AfterRemake * PipeNet remake simplification * IGasMixtureHolder on PipeComponent, IPipeNet * PipeContainerComponent * BasePump * DebugPump * IgnoredComponent fix * Pipe LocalAir and Air * comments * Pump fix * PipeNet fix * name simplification * PipeDirection name changes * BaseVentComponent and DebugVentComponent * Moves Pipe to own file * DebugVentComponent moved to own file * BaseScrubberComponent * DebugScrubberComponent * IgnoredComponents update * scrubber prototype * vent prototype fix * comments * Removes vent and scrubber PipeDirection check * PipeContainer, Pipe, and PipeNode refactor * Yaml cleanup * pump prototype fix * Removes AssumeAir usage from old IGasMixtureHolders * Simplfies Vent & Scrubber to use AtmosHelper methods * Vents and scrubbers invalidate the coordinate they changed the gas of * UpdatedPipingComponent * ScrubberComponent renamed to SiphonComponent * Removes PumpSystem * Removes framTime from UpdatedPiping * PipeNetDevices * PipeNetDevice updated by GridAtmosphereComponent * PipeNets react from update in GridAtmosphereComponent * GridAtmosphereComponent stores PipeNets/PipeNetDevices to be updated in queue * diff fix * Removes debug gas starting in pipes * type safety in IPipeNet when combining groups * null checks * GridAtmos stores PipeNets and PipeNetDevices in List * comments * rogue curly bracket * ProcessPipeNets update fix * RemovePipeNet fix * PipeNet update() unique index * fix diff * Integration test fixes * Error Logging * error fix Co-authored-by: py01 --- Content.Client/IgnoredComponents.cs | 3 + .../Atmos/IGridAtmosphereComponent.cs | 10 + .../Atmos/GridAtmosphereComponent.cs | 89 +++++++++ .../Atmos/Piping/PipeNetDeviceComponent.cs | 50 +++++ .../Atmos/Piping/Pumps/BasePumpComponent.cs | 68 +++++++ .../Atmos/Piping/Pumps/DebugPumpComponent.cs | 21 +++ .../Piping/Scrubbers/BaseSiphonComponent.cs | 52 ++++++ .../Piping/Scrubbers/DebugSiphonComponent.cs | 21 +++ .../Atmos/Piping/Vents/BaseVentComponent.cs | 54 ++++++ .../Atmos/Piping/Vents/DebugVentComponent.cs | 21 +++ .../NodeContainer/NodeGroups/INodeGroup.cs | 19 ++ .../NodeContainer/NodeGroups/IPipeNet.cs | 100 ++++++++++ .../NodeGroups/NodeGroupFactory.cs | 22 ++- .../Components/NodeContainer/Nodes/Node.cs | 4 +- .../NodeContainer/Nodes/PipeNode.cs | 171 ++++++++++++++++++ .../Entities/Constructible/Ground/pipes.yml | 43 +++++ .../Entities/Constructible/Ground/pumps.yml | 36 ++++ .../Constructible/Ground/scrubbers.yml | 30 +++ .../Entities/Constructible/Ground/vents.yml | 30 +++ 19 files changed, 833 insertions(+), 11 deletions(-) create mode 100644 Content.Server/GameObjects/Components/Atmos/Piping/PipeNetDeviceComponent.cs create mode 100644 Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs create mode 100644 Content.Server/GameObjects/Components/Atmos/Piping/Pumps/DebugPumpComponent.cs create mode 100644 Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs create mode 100644 Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/DebugSiphonComponent.cs create mode 100644 Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs create mode 100644 Content.Server/GameObjects/Components/Atmos/Piping/Vents/DebugVentComponent.cs create mode 100644 Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs create mode 100644 Content.Server/GameObjects/Components/NodeContainer/Nodes/PipeNode.cs create mode 100644 Resources/Prototypes/Entities/Constructible/Ground/pipes.yml create mode 100644 Resources/Prototypes/Entities/Constructible/Ground/pumps.yml create mode 100644 Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml create mode 100644 Resources/Prototypes/Entities/Constructible/Ground/vents.yml diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 28545a1909..5d3cca4938 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -160,6 +160,9 @@ "Metabolism", "AiFactionTag", "PressureProtection", + "DebugPump", + "DebugVent", + "DebugSiphon", }; } } diff --git a/Content.Server/Atmos/IGridAtmosphereComponent.cs b/Content.Server/Atmos/IGridAtmosphereComponent.cs index 5518881767..416445b1a9 100644 --- a/Content.Server/Atmos/IGridAtmosphereComponent.cs +++ b/Content.Server/Atmos/IGridAtmosphereComponent.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using Content.Server.GameObjects.Components.Atmos.Piping; +using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Map; @@ -146,5 +148,13 @@ namespace Content.Server.Atmos float GetVolumeForCells(int cellCount); void Update(float frameTime); + + void AddPipeNet(IPipeNet pipeNet); + + void RemovePipeNet(IPipeNet pipeNet); + + void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice); + + void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice); } } diff --git a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs index 6af07c6eba..87346d632b 100644 --- a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Content.Server.Atmos; +using Content.Server.GameObjects.Components.Atmos.Piping; +using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Shared.Atmos; using Content.Shared.Maps; using Robust.Server.Interfaces.GameObjects; @@ -73,6 +75,22 @@ namespace Content.Server.GameObjects.Components.Atmos [ViewVariables] private HashSet _highPressureDelta = new HashSet(1000); + [ViewVariables] + private readonly List _pipeNets = new List(); + + /// + /// Index of most recently updated . + /// + private int _pipeNetIndex = 0; + + [ViewVariables] + private readonly List _pipeNetDevices = new List(); + + /// + /// Index of most recently updated . + /// + private int _deviceIndex = 0; + [ViewVariables] private ProcessState _state = ProcessState.TileEqualize; @@ -84,6 +102,8 @@ namespace Content.Server.GameObjects.Components.Atmos HighPressureDelta, Hotspots, Superconductivity, + PipeNet, + PipeNetDevices, } /// @@ -296,6 +316,28 @@ namespace Content.Server.GameObjects.Components.Atmos _excitedGroups.Remove(excitedGroup); } + public void AddPipeNet(IPipeNet pipeNet) + { + _pipeNets.Add(pipeNet); + } + + public void RemovePipeNet(IPipeNet pipeNet) + { + _pipeNets.Remove(pipeNet); + _deviceIndex = 0; + } + + public void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice) + { + _pipeNetDevices.Add(pipeNetDevice); + } + + public void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice) + { + _pipeNetDevices.Remove(pipeNetDevice); + _deviceIndex = 0; + } + /// public TileAtmosphere? GetTile(GridCoordinates coordinates) { @@ -401,6 +443,14 @@ namespace Content.Server.GameObjects.Components.Atmos break; case ProcessState.Superconductivity: ProcessSuperconductivity(); + _state = ProcessState.PipeNet; + break; + case ProcessState.PipeNet: + ProcessPipeNets(); + _state = ProcessState.PipeNetDevices; + break; + case ProcessState.PipeNetDevices: + ProcessPipeNetDevices(); _state = ProcessState.TileEqualize; break; } @@ -520,6 +570,45 @@ namespace Content.Server.GameObjects.Components.Atmos } } + private void ProcessPipeNets() + { + _stopwatch.Restart(); + + var number = 0; + var pipeNets = _pipeNets.ToArray(); + var netCount = pipeNets.Count(); + for ( ; _pipeNetIndex < netCount; _pipeNetIndex++) + { + pipeNets[_pipeNetIndex].Update(); + + if (number++ < LagCheckIterations) continue; + number = 0; + // Process the rest next time. + if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) + return; + } + _pipeNetIndex = 0; + } + + private void ProcessPipeNetDevices() + { + _stopwatch.Restart(); + + var number = 0; + var pipeNetDevices = _pipeNetDevices.ToArray(); + var deviceCount = pipeNetDevices.Count(); + for ( ; _deviceIndex < deviceCount; _deviceIndex++) + { + pipeNetDevices[_deviceIndex].Update(); + + if (number++ < LagCheckIterations) continue; + number = 0; + // Process the rest next time. + if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) + return; + } + _deviceIndex = 0; + } private AirtightComponent? GetObstructingComponent(MapIndices indices) { if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default; diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/PipeNetDeviceComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/PipeNetDeviceComponent.cs new file mode 100644 index 0000000000..e9494d059d --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/PipeNetDeviceComponent.cs @@ -0,0 +1,50 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Log; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Adds itself to a to be updated by. + /// TODO: Make compatible with unanchoring/anchoring. Currently assumes that the Owner does not move. + /// + public abstract class PipeNetDeviceComponent : Component + { + public abstract void Update(); + + protected IGridAtmosphereComponent JoinedGridAtmos { get; private set; } + + public override void Initialize() + { + base.Initialize(); + JoinGridAtmos(); + } + + public override void OnRemove() + { + base.OnRemove(); + LeaveGridAtmos(); + } + + private void JoinGridAtmos() + { + var gridAtmos = EntitySystem.Get() + .GetGridAtmosphere(Owner.Transform.GridID); + if (gridAtmos == null) + { + Logger.Error($"{nameof(PipeNetDeviceComponent)} on entity {Owner.Uid} could not find an {nameof(IGridAtmosphereComponent)}."); + return; + } + JoinedGridAtmos = gridAtmos; + JoinedGridAtmos.AddPipeNetDevice(this); + } + + private void LeaveGridAtmos() + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + JoinedGridAtmos = null; + } + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs new file mode 100644 index 0000000000..0099549e84 --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs @@ -0,0 +1,68 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; +using Robust.Shared.Log; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Transfer gas from one to another. + /// + public abstract class BasePumpComponent : PipeNetDeviceComponent + { + /// + /// Needs to be same as that of a on this entity. + /// + [ViewVariables] + private PipeDirection _inletDirection; + + /// + /// Needs to be same as that of a on this entity. + /// + [ViewVariables] + private PipeDirection _outletDirection; + + [ViewVariables] + private PipeNode _inletPipe; + + [ViewVariables] + private PipeNode _outletPipe; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _inletDirection, "inletDirection", PipeDirection.None); + serializer.DataField(ref _outletDirection, "outletDirection", PipeDirection.None); + } + + public override void Initialize() + { + base.Initialize(); + if (!Owner.TryGetComponent(out var container)) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BasePumpComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}."); + return; + } + var pipeNodes = container.Nodes.OfType(); + _inletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _inletDirection).FirstOrDefault(); + _outletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _outletDirection).FirstOrDefault(); + if (_inletPipe == null | _outletPipe == null) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BasePumpComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + return; + } + } + + public override void Update() + { + PumpGas(_inletPipe.Air, _outletPipe.Air); + } + + protected abstract void PumpGas(GasMixture inletGas, GasMixture outletGas); + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/DebugPumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/DebugPumpComponent.cs new file mode 100644 index 0000000000..dfddf21cab --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/DebugPumpComponent.cs @@ -0,0 +1,21 @@ +using Content.Server.Atmos; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Placeholder example of pump functionality. + /// + [RegisterComponent] + [ComponentReference(typeof(BasePumpComponent))] + public class DebugPumpComponent : BasePumpComponent + { + public override string Name => "DebugPump"; + + protected override void PumpGas(GasMixture inletGas, GasMixture outletGas) + { + outletGas.Merge(inletGas); + inletGas.Clear(); + } + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs new file mode 100644 index 0000000000..16d04a20fb --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs @@ -0,0 +1,52 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; +using Content.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Log; +using Robust.Shared.ViewVariables; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Transfers gas from the tile it is on to a . + /// + public abstract class BaseSiphonComponent : PipeNetDeviceComponent + { + [ViewVariables] + private PipeNode _scrubberOutlet; + + private AtmosphereSystem _atmosSystem; + + public override void Initialize() + { + base.Initialize(); + _atmosSystem = EntitySystem.Get(); + if (!Owner.TryGetComponent(out var container)) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BaseSiphonComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}."); + return; + } + _scrubberOutlet = container.Nodes.OfType().FirstOrDefault(); + if (_scrubberOutlet == null) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BaseSiphonComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + return; + } + } + + public override void Update() + { + var tileAtmos = AtmosHelpers.GetTileAtmosphere(Owner.Transform.GridPosition); + if (tileAtmos == null) + return; + ScrubGas(tileAtmos.Air, _scrubberOutlet.Air); + _atmosSystem.GetGridAtmosphere(Owner.Transform.GridID).Invalidate(tileAtmos.GridIndices); + } + + protected abstract void ScrubGas(GasMixture inletGas, GasMixture outletGas); + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/DebugSiphonComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/DebugSiphonComponent.cs new file mode 100644 index 0000000000..6aee5812b6 --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/DebugSiphonComponent.cs @@ -0,0 +1,21 @@ +using Content.Server.Atmos; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Placeholder example of scrubber functionality. + /// + [RegisterComponent] + [ComponentReference(typeof(BaseSiphonComponent))] + public class DebugSiphonComponent : BaseSiphonComponent + { + public override string Name => "DebugSiphon"; + + protected override void ScrubGas(GasMixture inletGas, GasMixture outletGas) + { + outletGas.Merge(inletGas); + inletGas.Clear(); + } + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs new file mode 100644 index 0000000000..334d647712 --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs @@ -0,0 +1,54 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; +using Content.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Log; +using Robust.Shared.ViewVariables; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Transfers gas from a to the tile it is on. + /// + public abstract class BaseVentComponent : PipeNetDeviceComponent + { + [ViewVariables] + private PipeNode _ventInlet; + + private AtmosphereSystem _atmosSystem; + + + + public override void Initialize() + { + base.Initialize(); + _atmosSystem = EntitySystem.Get(); + if (!Owner.TryGetComponent(out var container)) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BaseVentComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}."); + return; + } + _ventInlet = container.Nodes.OfType().FirstOrDefault(); + if (_ventInlet == null) + { + JoinedGridAtmos?.RemovePipeNetDevice(this); + Logger.Error($"{typeof(BaseVentComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + return; + } + } + + public override void Update() + { + var tileAtmos = AtmosHelpers.GetTileAtmosphere(Owner.Transform.GridPosition); + if (tileAtmos == null) + return; + VentGas(_ventInlet.Air, tileAtmos.Air); + _atmosSystem.GetGridAtmosphere(Owner.Transform.GridID).Invalidate(tileAtmos.GridIndices); + } + + protected abstract void VentGas(GasMixture inletGas, GasMixture outletGas); + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/DebugVentComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/DebugVentComponent.cs new file mode 100644 index 0000000000..211cbd440c --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/DebugVentComponent.cs @@ -0,0 +1,21 @@ +using Content.Server.Atmos; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Placeholder example of vent functionality. + /// + [RegisterComponent] + [ComponentReference(typeof(BaseVentComponent))] + public class DebugVentComponent : BaseVentComponent + { + public override string Name => "DebugVent"; + + protected override void VentGas(GasMixture inletGas, GasMixture outletGas) + { + outletGas.Merge(inletGas); + inletGas.Clear(); + } + } +} diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs index b019cbf4ea..e57ce1db5d 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Robust.Shared.IoC; +using Robust.Shared.Map; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups @@ -13,6 +14,8 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups { IReadOnlyList Nodes { get; } + void Initialize(Node sourceNode); + void AddNode(Node node); void RemoveNode(Node node); @@ -34,6 +37,13 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups public static readonly INodeGroup NullGroup = new NullNodeGroup(); + protected GridId GridId { get; private set;} + + public virtual void Initialize(Node sourceNode) + { + GridId = sourceNode.Owner.Transform.GridID; + } + public void AddNode(Node node) { _nodes.Add(node); @@ -54,6 +64,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups newGroup.CombineGroup(this); return; } + OnGivingNodesForCombine(newGroup); foreach (var node in Nodes) { node.NodeGroup = newGroup; @@ -70,23 +81,31 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups { node.ClearNodeGroup(); } + var newGroups = new List(); foreach (var node in Nodes) { if (node.TryAssignGroupIfNeeded()) { node.SpreadGroup(); + newGroups.Add(node.NodeGroup); } } + AfterRemake(newGroups); } protected virtual void OnAddNode(Node node) { } protected virtual void OnRemoveNode(Node node) { } + protected virtual void OnGivingNodesForCombine(INodeGroup newGroup) { } + + protected virtual void AfterRemake(IEnumerable newGroups) { } + private class NullNodeGroup : INodeGroup { public IReadOnlyList Nodes => _nodes; private readonly List _nodes = new List(); + public void Initialize(Node sourceNode) { } public void AddNode(Node node) { } public void CombineGroup(INodeGroup newGroup) { } public void RemoveNode(Node node) { } diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs new file mode 100644 index 0000000000..75f5f147b1 --- /dev/null +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs @@ -0,0 +1,100 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; +using Content.Server.GameObjects.EntitySystems; +using Content.Server.Interfaces; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.ViewVariables; +using System.Collections.Generic; + +namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups +{ + public interface IPipeNet : IGasMixtureHolder + { + /// + /// Causes gas in the PipeNet to react. + /// + void Update(); + } + + [NodeGroup(NodeGroupID.Pipe)] + public class PipeNet : BaseNodeGroup, IPipeNet + { + [ViewVariables] + public GasMixture Air { get; set; } = new GasMixture(); + + public static readonly IPipeNet NullNet = new NullPipeNet(); + + [ViewVariables] + private readonly List _pipes = new List(); + + [ViewVariables] + private IGridAtmosphereComponent _gridAtmos; + + public override void Initialize(Node sourceNode) + { + base.Initialize(sourceNode); + _gridAtmos = EntitySystem.Get() + .GetGridAtmosphere(GridId); + _gridAtmos?.AddPipeNet(this); + } + + public void Update() + { + Air.React(this); + } + + protected override void OnAddNode(Node node) + { + if (!(node is PipeNode pipeNode)) + return; + _pipes.Add(pipeNode); + pipeNode.JoinPipeNet(this); + Air.Volume += pipeNode.Volume; + Air.Merge(pipeNode.LocalAir); + pipeNode.LocalAir.Clear(); + } + + protected override void OnRemoveNode(Node node) + { + RemoveFromGridAtmos(); + if (!(node is PipeNode pipeNode)) + return; + var pipeAir = pipeNode.LocalAir; + pipeAir.Merge(Air); + pipeAir.Multiply(pipeNode.Volume / Air.Volume); + _pipes.Remove(pipeNode); + } + + protected override void OnGivingNodesForCombine(INodeGroup newGroup) + { + if (!(newGroup is IPipeNet newPipeNet)) + return; + newPipeNet.Air.Merge(Air); + Air.Clear(); + } + + protected override void AfterRemake(IEnumerable newGroups) + { + foreach (var newGroup in newGroups) + { + if (!(newGroup is IPipeNet newPipeNet)) + continue; + newPipeNet.Air.Merge(Air); + var newPipeNetGas = newPipeNet.Air; + newPipeNetGas.Multiply(newPipeNetGas.Volume / Air.Volume); + } + RemoveFromGridAtmos(); + } + + private void RemoveFromGridAtmos() + { + _gridAtmos.RemovePipeNet(this); + } + + private class NullPipeNet : IPipeNet + { + GasMixture IGasMixtureHolder.Air { get; set; } = new GasMixture(); + public void Update() { } + } + } +} diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs index 630437f04e..2133f67134 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs @@ -1,8 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Reflection; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.IoC; +using System; +using System.Collections.Generic; +using System.Reflection; namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups { @@ -17,7 +18,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups /// /// Returns a new instance. /// - INodeGroup MakeNodeGroup(NodeGroupID nodeGroupType); + INodeGroup MakeNodeGroup(Node sourceNode); } public class NodeGroupFactory : INodeGroupFactory @@ -29,7 +30,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups public void Initialize() { - var nodeGroupTypes = _reflectionManager.GetAllChildren(); + var nodeGroupTypes = _reflectionManager.GetAllChildren(); foreach (var nodeGroupType in nodeGroupTypes) { var att = nodeGroupType.GetCustomAttribute(); @@ -43,13 +44,15 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups } } - public INodeGroup MakeNodeGroup(NodeGroupID nodeGroupType) + public INodeGroup MakeNodeGroup(Node sourceNode) { - if (_groupTypes.TryGetValue(nodeGroupType, out var type)) + if (_groupTypes.TryGetValue(sourceNode.NodeGroupID, out var type)) { - return _typeFactory.CreateInstance(type); + var nodeGroup = _typeFactory.CreateInstance(type); + nodeGroup.Initialize(sourceNode); + return nodeGroup; } - throw new ArgumentException($"{nodeGroupType} did not have an associated {nameof(INodeGroup)}."); + throw new ArgumentException($"{sourceNode.NodeGroupID} did not have an associated {nameof(INodeGroup)}."); } } @@ -59,5 +62,6 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups HVPower, MVPower, Apc, + Pipe, } } diff --git a/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs b/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs index 291d681df4..02c6ef3cf7 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs @@ -53,7 +53,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes serializer.DataField(this, x => NodeGroupID, "nodeGroupID", NodeGroupID.Default); } - public void Initialize(IEntity owner) + public virtual void Initialize(IEntity owner) { Owner = owner; _nodeGroupFactory = IoCManager.Resolve(); @@ -143,7 +143,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes private INodeGroup MakeNewGroup() { - return _nodeGroupFactory.MakeNodeGroup(NodeGroupID); + return _nodeGroupFactory.MakeNodeGroup(this); } private void AnchorUpdate() diff --git a/Content.Server/GameObjects/Components/NodeContainer/Nodes/PipeNode.cs b/Content.Server/GameObjects/Components/NodeContainer/Nodes/PipeNode.cs new file mode 100644 index 0000000000..b35d16280f --- /dev/null +++ b/Content.Server/GameObjects/Components/NodeContainer/Nodes/PipeNode.cs @@ -0,0 +1,171 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; +using Content.Server.Interfaces; +using Content.Shared.Atmos; +using Robust.Shared.GameObjects.Components.Transform; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Content.Server.GameObjects.Components.NodeContainer.Nodes +{ + /// + /// Connects with other s whose + /// correctly correspond. + /// + public class PipeNode : Node, IGasMixtureHolder + { + [ViewVariables] + public PipeDirection PipeDirection => _pipeDirection; + private PipeDirection _pipeDirection; + + [ViewVariables] + private IPipeNet _pipeNet = PipeNet.NullNet; + + [ViewVariables] + private bool _needsPipeNet = true; + + /// + /// The gases in this pipe. + /// + [ViewVariables] + public GasMixture Air + { + get => _needsPipeNet ? LocalAir : _pipeNet.Air; + set + { + if (_needsPipeNet) + LocalAir = value; + else + _pipeNet.Air = value; + } + } + + /// + /// Stores gas in this pipe when disconnected from a . + /// Only for usage by s. + /// + [ViewVariables] + public GasMixture LocalAir { get; set; } + + [ViewVariables] + public float Volume { get; private set; } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _pipeDirection, "pipeDirection", PipeDirection.None); + serializer.DataField(this, x => Volume, "volume", 10); + } + + public override void Initialize(IEntity owner) + { + base.Initialize(owner); + LocalAir = new GasMixture(Volume); + } + + public void JoinPipeNet(IPipeNet pipeNet) + { + _pipeNet = pipeNet; + _needsPipeNet = false; + } + + public void ClearPipeNet() + { + _pipeNet = PipeNet.NullNet; + _needsPipeNet = true; + } + + protected override IEnumerable GetReachableNodes() + { + foreach (CardinalDirection direction in Enum.GetValues(typeof(CardinalDirection))) + { + PipeDirectionFromCardinal(direction, out var ownNeededConnection, out var theirNeededConnection); + if ((_pipeDirection & ownNeededConnection) == PipeDirection.None) + { + continue; + } + var pipeNodesInDirection = Owner.GetComponent() + .GetInDir((Direction) direction) + .Select(entity => entity.TryGetComponent(out var container) ? container : null) + .Where(container => container != null) + .SelectMany(container => container.Nodes) + .OfType() + .Where(pipeNode => (pipeNode._pipeDirection & theirNeededConnection) != PipeDirection.None); + foreach (var pipeNode in pipeNodesInDirection) + { + yield return pipeNode; + } + } + } + + private void PipeDirectionFromCardinal(CardinalDirection direction, out PipeDirection sameDir, out PipeDirection oppDir) + { + switch (direction) + { + case CardinalDirection.North: + sameDir = PipeDirection.North; + oppDir = PipeDirection.South; + break; + case CardinalDirection.South: + sameDir = PipeDirection.South; + oppDir = PipeDirection.North; + break; + case CardinalDirection.East: + sameDir = PipeDirection.East; + oppDir = PipeDirection.West; + break; + case CardinalDirection.West: + sameDir = PipeDirection.West; + oppDir = PipeDirection.East; + break; + default: + throw new ArgumentException("Invalid Direction."); + } + } + + private enum CardinalDirection + { + North = Direction.North, + South = Direction.South, + East = Direction.East, + West = Direction.West, + } + } + + public enum PipeDirection + { + None = 0, + + //Half of a pipe in a direction + North = 1 << 0, + South = 1 << 1, + West = 1 << 2, + East = 1 << 3, + + //Straight pipes + Longitudinal = North | South, + Lateral = West | East, + + //Bends + NWBend = North | West, + NEBend = North | East, + SWBend = South | West, + SEBend = South | East, + + //T-Junctions + TNorth = North | Lateral, + TSouth = South | Lateral, + TWest = West | Longitudinal, + TEast = East | Longitudinal, + + //Four way + FourWay = North | South | East | West, + + All = -1, + } +} diff --git a/Resources/Prototypes/Entities/Constructible/Ground/pipes.yml b/Resources/Prototypes/Entities/Constructible/Ground/pipes.yml new file mode 100644 index 0000000000..5dcbab768c --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Ground/pipes.yml @@ -0,0 +1,43 @@ +- type: entity + abstract: true + id: PipeBase + placement: + mode: SnapgridCenter + components: + - type: Clickable + - type: InteractionOutline + - type: Collidable + - type: SnapGrid + offset: Center + - type: Icon + texture: Constructible/Power/eightdirwire.png + - type: Sprite + sprite: Constructible/Power/hv_cable.rsi + - type: Destructible + thresholdvalue: 100 + +- type: entity + parent: PipeBase + id: FourwayPipe + name: Fourway Pipe + components: + - type: Sprite + state: hvcable_15 + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: FourWay + +- type: entity + parent: PipeBase + id: LongitudinalPipe + name: Longitudinal Pipe + components: + - type: Sprite + state: hvcable_3 + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: Longitudinal diff --git a/Resources/Prototypes/Entities/Constructible/Ground/pumps.yml b/Resources/Prototypes/Entities/Constructible/Ground/pumps.yml new file mode 100644 index 0000000000..e3741e762e --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Ground/pumps.yml @@ -0,0 +1,36 @@ +- type: entity + abstract: true + id: PumpBase + placement: + mode: SnapgridCenter + components: + - type: Clickable + - type: InteractionOutline + - type: Collidable + - type: SnapGrid + offset: Center + - type: Icon + texture: Constructible/Power/eightdirwire.png + - type: Sprite + sprite: Constructible/Power/mv_cable.rsi + - type: Destructible + thresholdvalue: 100 + +- type: entity + parent: PumpBase + id: NorthFromSouthPipePump + name: North from south pipe pump + components: + - type: Sprite + state: mvcable_3 + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: North + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: South + - type: DebugPump + outletDirection: North + inletDirection: South diff --git a/Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml b/Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml new file mode 100644 index 0000000000..2372c3e65d --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml @@ -0,0 +1,30 @@ +- type: entity + abstract: true + id: ScrubberBase + placement: + mode: SnapgridCenter + components: + - type: Clickable + - type: InteractionOutline + - type: Collidable + - type: SnapGrid + offset: Center + - type: Icon + texture: Constructible/Power/eightdirwire.png + - type: Sprite + texture: Constructible/Power/eightdirwire.png + - type: Destructible + thresholdvalue: 100 + +- type: entity + parent: ScrubberBase + id: FromSouthScrubber + name: From South Scrubber + components: + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroID: Pipe + pipeDirection: South + - type: DebugSiphon + scrubberOutletDirection: South diff --git a/Resources/Prototypes/Entities/Constructible/Ground/vents.yml b/Resources/Prototypes/Entities/Constructible/Ground/vents.yml new file mode 100644 index 0000000000..2940ec1b08 --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Ground/vents.yml @@ -0,0 +1,30 @@ +- type: entity + abstract: true + id: VentBase + placement: + mode: SnapgridCenter + components: + - type: Clickable + - type: InteractionOutline + - type: Collidable + - type: SnapGrid + offset: Center + - type: Icon + texture: Constructible/Power/eightdirwire.png + - type: Sprite + texture: Constructible/Power/eightdirwire.png + - type: Destructible + thresholdvalue: 100 + +- type: entity + parent: VentBase + id: FromSouthVent + name: From South Vent + components: + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: South + - type: DebugVent + ventInletDirection: South From c09c9176ef0f5294398ded497662f0e690f940ea Mon Sep 17 00:00:00 2001 From: Julian Giebel Date: Fri, 28 Aug 2020 09:31:17 +0200 Subject: [PATCH 86/88] Add do_after to TryInsert (#1938) Add delay to flushing Co-authored-by: Julian Giebel --- .../Tests/Disposal/DisposalUnitTest.cs | 15 +++-- .../Disposal/DisposalUnitComponent.cs | 57 +++++++++++++++---- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs index 3eb3821634..b4296daded 100644 --- a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs +++ b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs @@ -22,14 +22,17 @@ namespace Content.IntegrationTests.Tests.Disposal { foreach (var entity in entities) { + var insertTask = unit.TryInsert(entity); Assert.That(unit.CanInsert(entity), Is.EqualTo(result)); - Assert.That(unit.TryInsert(entity), Is.EqualTo(result)); - - if (result) + insertTask.ContinueWith(task => { - // Not in a tube yet - Assert.That(entity.Transform.Parent == unit.Owner.Transform); - } + Assert.That(task.Result, Is.EqualTo(result)); + if (result) + { + // Not in a tube yet + Assert.That(entity.Transform.Parent, Is.EqualTo(unit.Owner.Transform)); + } + }); } } diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs index 521615f4b9..d67721987a 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Power.ApcNetComponents; +using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Utility; @@ -68,6 +69,12 @@ namespace Content.Server.GameObjects.Components.Disposal [ViewVariables] private TimeSpan _automaticEngageTime; + [ViewVariables] + private TimeSpan _flushDelay; + + [ViewVariables] + private float _entryDelay; + /// /// Token used to cancel the automatic engage of a disposal unit /// after an entity enters it. @@ -174,13 +181,34 @@ namespace Content.Server.GameObjects.Components.Disposal UpdateVisualState(); } - public bool TryInsert(IEntity entity) + public async Task TryInsert(IEntity entity, IEntity? user = default) { - if (!CanInsert(entity) || !_container.Insert(entity)) - { + if (!CanInsert(entity)) return false; + + if (user != null && _entryDelay > 0f) + { + var doAfterSystem = EntitySystem.Get(); + + var doAfterArgs = new DoAfterEventArgs(user, _entryDelay, default, Owner) + { + BreakOnDamage = true, + BreakOnStun = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + NeedHand = false, + }; + + var result = await doAfterSystem.DoAfter(doAfterArgs); + + if (result == DoAfterStatus.Cancelled) + return false; + } + if (!_container.Insert(entity)) + return false; + AfterInsert(entity); return true; @@ -225,9 +253,9 @@ namespace Content.Server.GameObjects.Components.Disposal { Engaged ^= true; - if (Engaged) + if (Engaged && CanFlush()) { - TryFlush(); + Timer.Spawn(_flushDelay, () => TryFlush()); } } @@ -487,10 +515,16 @@ namespace Content.Server.GameObjects.Components.Disposal () => (int) _automaticEngageTime.TotalSeconds); serializer.DataReadWriteFunction( - "automaticEngageTime", - 30, - seconds => _automaticEngageTime = TimeSpan.FromSeconds(seconds), - () => (int) _automaticEngageTime.TotalSeconds); + "flushDelay", + 3, + seconds => _flushDelay = TimeSpan.FromSeconds(seconds), + () => (int) _flushDelay.TotalSeconds); + + serializer.DataReadWriteFunction( + "entryDelay", + 0.5f, + seconds => _entryDelay = seconds, + () => (int) _entryDelay); } public override void Initialize() @@ -620,7 +654,8 @@ namespace Content.Server.GameObjects.Components.Disposal bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs) { - return TryInsert(eventArgs.Dropped); + _ = TryInsert(eventArgs.Dropped, eventArgs.User); + return true; } [Verb] @@ -642,7 +677,7 @@ namespace Content.Server.GameObjects.Components.Disposal protected override void Activate(IEntity user, DisposalUnitComponent component) { - component.TryInsert(user); + _ = component.TryInsert(user, user); } } From 3758eb1b602aa6c19e05abb19fb267e28560f2cd Mon Sep 17 00:00:00 2001 From: Julian Giebel Date: Fri, 28 Aug 2020 10:11:08 +0200 Subject: [PATCH 87/88] Disposal trunks now eject incoming entities (#1942) * Implement disposal trunk eject on incomming entities * Move variable declaration into if block Co-authored-by: Julian Giebel --- .../Disposal/DisposalEntryComponent.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalEntryComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalEntryComponent.cs index 80765abd10..aaadca6f5e 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalEntryComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalEntryComponent.cs @@ -1,7 +1,11 @@ using System.Collections.Generic; +using System.Linq; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; using Robust.Shared.Maths; +using Robust.Shared.Random; namespace Content.Server.GameObjects.Components.Disposal { @@ -9,6 +13,8 @@ namespace Content.Server.GameObjects.Components.Disposal [ComponentReference(typeof(IDisposalTubeComponent))] public class DisposalEntryComponent : DisposalTubeComponent { + [Dependency] private readonly IRobustRandom _random = default!; + private const string HolderPrototypeId = "DisposalHolder"; public override string Name => "DisposalEntry"; @@ -43,8 +49,19 @@ namespace Content.Server.GameObjects.Components.Disposal return new[] {Owner.Transform.LocalRotation.GetDir()}; } + /// + /// Ejects contents when they come from the same direction the entry is facing. + /// public override Direction NextDirection(DisposalHolderComponent holder) { + if (holder.PreviousTube != null && DirectionTo(holder.PreviousTube) == ConnectableDirections()[0]) + { + var invalidDirections = new Direction[] { ConnectableDirections()[0], Direction.Invalid }; + var directions = System.Enum.GetValues(typeof(Direction)) + .Cast().Except(invalidDirections).ToList(); + return _random.Pick(directions); + } + return ConnectableDirections()[0]; } } From fb0ac3d70ebc2c52fa12c6d19dc48d23589394c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= <6766154+Zumorica@users.noreply.github.com> Date: Fri, 28 Aug 2020 14:32:56 +0200 Subject: [PATCH 88/88] Atmos optimizations (#1944) * Adds IFireAct, ITemperatureExpose * Use AtmosDirection instead of Direction for Atmos * Refactor atmos to heavily rely on arrays and bitflags. Adds F A S T M O S and reduces atmos tech debt heavily. * Optimize and fix more stuff * Kinda improve superconduction * Pipenet is a word * T U R B O M O S * Address reviews * Small optimization * Superconduct is also a word * Remove check * Cleanup tile atmosphere * Correct a comment --- Content.Server/Atmos/FireEvent.cs | 16 + Content.Server/Atmos/GasMixture.cs | 25 +- .../Atmos/HighPressureMovementController.cs | 7 +- .../Atmos/IGridAtmosphereComponent.cs | 9 +- .../Atmos/Reactions/GasReactionPrototype.cs | 5 +- .../Atmos/Reactions/PhoronFireReaction.cs | 10 +- .../Atmos/Reactions/TritiumFireReaction.cs | 9 +- .../Atmos/TemperatureExposeEvent.cs | 23 ++ Content.Server/Atmos/TileAtmosInfo.cs | 27 +- Content.Server/Atmos/TileAtmosphere.cs | 325 +++++++++--------- .../Atmos/GridAtmosphereComponent.cs | 313 ++++++++++++----- .../EntitySystems/AtmosphereSystem.cs | 25 ++ .../Interfaces/IGasReactionEffect.cs | 3 +- Content.Shared/Atmos/AtmosDirection.cs | 89 +++++ Content.Shared/Atmos/Atmospherics.cs | 6 + SpaceStation14.sln.DotSettings | 2 + 16 files changed, 619 insertions(+), 275 deletions(-) create mode 100644 Content.Server/Atmos/FireEvent.cs create mode 100644 Content.Server/Atmos/TemperatureExposeEvent.cs create mode 100644 Content.Shared/Atmos/AtmosDirection.cs diff --git a/Content.Server/Atmos/FireEvent.cs b/Content.Server/Atmos/FireEvent.cs new file mode 100644 index 0000000000..8c1a48abb2 --- /dev/null +++ b/Content.Server/Atmos/FireEvent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameObjects; + +namespace Content.Server.Atmos +{ + public class FireActEvent : EntitySystemMessage + { + public float Temperature { get; } + public float Volume { get; } + + public FireActEvent(float temperature, float volume) + { + Temperature = temperature; + Volume = volume; + } + } +} diff --git a/Content.Server/Atmos/GasMixture.cs b/Content.Server/Atmos/GasMixture.cs index 43665b6715..ddd834eceb 100644 --- a/Content.Server/Atmos/GasMixture.cs +++ b/Content.Server/Atmos/GasMixture.cs @@ -129,17 +129,21 @@ namespace Content.Server.Atmos [ViewVariables] public float Volume { get; set; } - public GasMixture() + public GasMixture() : this(null) { - _atmosphereSystem = EntitySystem.Get(); } - public GasMixture(float volume) + public GasMixture(AtmosphereSystem? atmosphereSystem) + { + _atmosphereSystem = atmosphereSystem ?? EntitySystem.Get(); + } + + public GasMixture(float volume, AtmosphereSystem? atmosphereSystem = null) { if (volume < 0) volume = 0; Volume = volume; - _atmosphereSystem = EntitySystem.Get(); + _atmosphereSystem = atmosphereSystem ?? EntitySystem.Get(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -224,12 +228,12 @@ namespace Content.Server.Atmos public GasMixture RemoveRatio(float ratio) { if(ratio <= 0) - return new GasMixture(Volume); + return new GasMixture(Volume, _atmosphereSystem); if (ratio > 1) ratio = 1; - var removed = new GasMixture {Volume = Volume, Temperature = Temperature}; + var removed = new GasMixture(_atmosphereSystem) {Volume = Volume, Temperature = Temperature}; for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { @@ -252,7 +256,7 @@ namespace Content.Server.Atmos public void CopyFromMutable(GasMixture sample) { if (Immutable) return; - sample._moles.AsSpan().CopyTo(_moles.AsSpan()); + sample._moles.CopyTo(_moles, 0); Temperature = sample.Temperature; } @@ -485,8 +489,7 @@ namespace Content.Server.Atmos var temperature = Temperature; var energy = ThermalEnergy; - // TODO ATMOS Take reaction priority into account! - foreach (var prototype in IoCManager.Resolve().EnumeratePrototypes()) + foreach (var prototype in _atmosphereSystem.GasReactions) { if (energy < prototype.MinimumEnergyRequirement || temperature < prototype.MinimumTemperatureRequirement) @@ -508,7 +511,7 @@ namespace Content.Server.Atmos if (!doReaction) continue; - reaction = prototype.React(this, holder); + reaction = prototype.React(this, holder, _atmosphereSystem.EventBus); if(reaction.HasFlag(ReactionResult.StopReactions)) break; } @@ -588,7 +591,7 @@ namespace Content.Server.Atmos public object Clone() { - var newMixture = new GasMixture() + var newMixture = new GasMixture(_atmosphereSystem) { _moles = (float[])_moles.Clone(), _molesArchived = (float[])_molesArchived.Clone(), diff --git a/Content.Server/Atmos/HighPressureMovementController.cs b/Content.Server/Atmos/HighPressureMovementController.cs index bd9f6b3e38..133d141fb0 100644 --- a/Content.Server/Atmos/HighPressureMovementController.cs +++ b/Content.Server/Atmos/HighPressureMovementController.cs @@ -1,6 +1,7 @@ #nullable enable using System; using Content.Server.GameObjects.Components.Atmos; +using Content.Shared.Atmos; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.Physics; using Robust.Shared.Interfaces.Random; @@ -24,7 +25,7 @@ namespace Content.Server.Atmos private const float ProbabilityBasePercent = 10f; private const float ThrowForce = 100f; - public void ExperiencePressureDifference(int cycle, float pressureDifference, Direction direction, + public void ExperiencePressureDifference(int cycle, float pressureDifference, AtmosDirection direction, float pressureResistanceProbDelta, GridCoordinates throwTarget) { if (ControlledComponent == null) @@ -54,14 +55,14 @@ namespace Content.Server.Atmos if (throwTarget != GridCoordinates.InvalidGrid) { var moveForce = maxForce * MathHelper.Clamp(moveProb, 0, 100) / 150f; - var pos = ((throwTarget.Position - transform.GridPosition.Position).Normalized + direction.ToVec()).Normalized; + var pos = ((throwTarget.Position - transform.GridPosition.Position).Normalized + direction.ToDirection().ToVec()).Normalized; LinearVelocity = pos * moveForce; } else { var moveForce = MathF.Min(maxForce * MathHelper.Clamp(moveProb, 0, 100) / 2500f, 20f); - LinearVelocity = direction.ToVec() * moveForce; + LinearVelocity = direction.ToDirection().ToVec() * moveForce; } pressureComponent.LastHighPressureMovementAirCycle = cycle; diff --git a/Content.Server/Atmos/IGridAtmosphereComponent.cs b/Content.Server/Atmos/IGridAtmosphereComponent.cs index 416445b1a9..1b53d33610 100644 --- a/Content.Server/Atmos/IGridAtmosphereComponent.cs +++ b/Content.Server/Atmos/IGridAtmosphereComponent.cs @@ -13,11 +13,6 @@ namespace Content.Server.Atmos /// int UpdateCounter { get; } - /// - /// How many tiles have high pressure delta. - /// - int HighPressureDeltaCount { get; } - /// /// Control variable for equalization. /// @@ -115,14 +110,14 @@ namespace Content.Server.Atmos /// /// /// - TileAtmosphere GetTile(MapIndices indices); + TileAtmosphere GetTile(MapIndices indices, bool createSpace = true); /// /// Returns a tile. /// /// /// - TileAtmosphere GetTile(GridCoordinates coordinates); + TileAtmosphere GetTile(GridCoordinates coordinates, bool createSpace = true); /// /// Returns if the tile in question is air-blocked. diff --git a/Content.Server/Atmos/Reactions/GasReactionPrototype.cs b/Content.Server/Atmos/Reactions/GasReactionPrototype.cs index e0dffe25c6..732a39fce4 100644 --- a/Content.Server/Atmos/Reactions/GasReactionPrototype.cs +++ b/Content.Server/Atmos/Reactions/GasReactionPrototype.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Content.Server.Interfaces; using Content.Shared.Atmos; +using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using YamlDotNet.RepresentationModel; @@ -64,13 +65,13 @@ namespace Content.Server.Atmos.Reactions serializer.DataField(ref _effects, "effects", new List()); } - public ReactionResult React(GasMixture mixture, IGasMixtureHolder holder) + public ReactionResult React(GasMixture mixture, IGasMixtureHolder holder, IEventBus eventBus) { var result = ReactionResult.NoReaction; foreach (var effect in _effects) { - result |= effect.React(mixture, holder); + result |= effect.React(mixture, holder, eventBus); } return result; diff --git a/Content.Server/Atmos/Reactions/PhoronFireReaction.cs b/Content.Server/Atmos/Reactions/PhoronFireReaction.cs index 46e8ceec2b..1aaf80a0d6 100644 --- a/Content.Server/Atmos/Reactions/PhoronFireReaction.cs +++ b/Content.Server/Atmos/Reactions/PhoronFireReaction.cs @@ -1,8 +1,12 @@ #nullable enable using System; using Content.Server.Interfaces; +using Content.Server.Interfaces.GameObjects.Components.Interaction; using Content.Shared.Atmos; +using Content.Shared.Maps; using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; using Robust.Shared.Serialization; namespace Content.Server.Atmos.Reactions @@ -10,7 +14,7 @@ namespace Content.Server.Atmos.Reactions [UsedImplicitly] public class PhoronFireReaction : IGasReactionEffect { - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder) + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, IEventBus eventBus) { var energyReleased = 0f; var oldHeatCapacity = mixture.HeatCapacity; @@ -71,9 +75,7 @@ namespace Content.Server.Atmos.Reactions { location.HotspotExpose(temperature, mixture.Volume); - // TODO ATMOS Expose temperature all items on cell - - location.TemperatureExpose(mixture, temperature, mixture.Volume); + eventBus.QueueEvent(EventSource.Local, new TemperatureExposeEvent(location.GridIndices, location.GridIndex, mixture, temperature, mixture.Volume)); } } diff --git a/Content.Server/Atmos/Reactions/TritiumFireReaction.cs b/Content.Server/Atmos/Reactions/TritiumFireReaction.cs index 8b90431253..da77173710 100644 --- a/Content.Server/Atmos/Reactions/TritiumFireReaction.cs +++ b/Content.Server/Atmos/Reactions/TritiumFireReaction.cs @@ -1,7 +1,10 @@ #nullable enable using Content.Server.Interfaces; +using Content.Server.Interfaces.GameObjects.Components.Interaction; using Content.Shared.Atmos; +using Content.Shared.Maps; using JetBrains.Annotations; +using Robust.Shared.GameObjects; using Robust.Shared.Serialization; namespace Content.Server.Atmos.Reactions @@ -13,7 +16,7 @@ namespace Content.Server.Atmos.Reactions { } - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder) + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, IEventBus eventBus) { var energyReleased = 0f; var oldHeatCapacity = mixture.HeatCapacity; @@ -66,9 +69,7 @@ namespace Content.Server.Atmos.Reactions { location.HotspotExpose(temperature, mixture.Volume); - // TODO ATMOS Expose temperature all items on cell - - location.TemperatureExpose(mixture, temperature, mixture.Volume); + eventBus.QueueEvent(EventSource.Local, new TemperatureExposeEvent(location.GridIndices, location.GridIndex, mixture, temperature, mixture.Volume)); } } diff --git a/Content.Server/Atmos/TemperatureExposeEvent.cs b/Content.Server/Atmos/TemperatureExposeEvent.cs new file mode 100644 index 0000000000..5401fe0279 --- /dev/null +++ b/Content.Server/Atmos/TemperatureExposeEvent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Content.Server.Atmos +{ + public class TemperatureExposeEvent : EntitySystemMessage + { + public MapIndices Indices { get; } + public GridId Grid { get; } + public GasMixture Air { get; } + public float Temperature { get; } + public float Volume { get; } + + public TemperatureExposeEvent(MapIndices indices, GridId gridId, GasMixture air, float temperature, float volume) + { + Indices = indices; + Grid = gridId; + Air = air; + Temperature = temperature; + Volume = volume; + } + } +} diff --git a/Content.Server/Atmos/TileAtmosInfo.cs b/Content.Server/Atmos/TileAtmosInfo.cs index 43ed5f129d..23cc37e550 100644 --- a/Content.Server/Atmos/TileAtmosInfo.cs +++ b/Content.Server/Atmos/TileAtmosInfo.cs @@ -1,4 +1,5 @@ using System; +using Content.Shared.Atmos; using Robust.Shared.Maths; using Robust.Shared.ViewVariables; @@ -30,15 +31,15 @@ namespace Content.Server.Atmos [ViewVariables] public float TransferDirectionSouth; - public float this[Direction direction] + public float this[AtmosDirection direction] { get => direction switch { - Direction.East => TransferDirectionEast, - Direction.West => TransferDirectionWest, - Direction.North => TransferDirectionNorth, - Direction.South => TransferDirectionSouth, + AtmosDirection.East => TransferDirectionEast, + AtmosDirection.West => TransferDirectionWest, + AtmosDirection.North => TransferDirectionNorth, + AtmosDirection.South => TransferDirectionSouth, _ => throw new ArgumentOutOfRangeException(nameof(direction)) }; @@ -46,16 +47,16 @@ namespace Content.Server.Atmos { switch (direction) { - case Direction.East: + case AtmosDirection.East: TransferDirectionEast = value; break; - case Direction.West: + case AtmosDirection.West: TransferDirectionWest = value; break; - case Direction.North: + case AtmosDirection.North: TransferDirectionNorth = value; break; - case Direction.South: + case AtmosDirection.South: TransferDirectionSouth = value; break; default: @@ -64,10 +65,16 @@ namespace Content.Server.Atmos } } + public float this[int index] + { + get => this[(AtmosDirection) (1 << index)]; + set => this[(AtmosDirection) (1 << index)] = value; + } + [ViewVariables] public float CurrentTransferAmount; - public Direction CurrentTransferDirection; + public AtmosDirection CurrentTransferDirection; [ViewVariables] public bool FastDone; diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs index 6f93d0c9a0..a8afc84cb5 100644 --- a/Content.Server/Atmos/TileAtmosphere.cs +++ b/Content.Server/Atmos/TileAtmosphere.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using Content.Server.Atmos.Reactions; using Content.Server.GameObjects.Components.Atmos; -using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems.Atmos; using Content.Server.Interfaces; using Content.Shared.Atmos; @@ -12,13 +11,13 @@ using Content.Shared.Audio; using Content.Shared.Maps; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.Containers; +using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; -using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Random; @@ -28,18 +27,15 @@ namespace Content.Server.Atmos { public class TileAtmosphere : IGasMixtureHolder { - [Robust.Shared.IoC.Dependency] private IRobustRandom _robustRandom = default!; - [Robust.Shared.IoC.Dependency] private IEntityManager _entityManager = default!; - [Robust.Shared.IoC.Dependency] private IMapManager _mapManager = default!; + [Robust.Shared.IoC.Dependency] private readonly IRobustRandom _robustRandom = default!; + [Robust.Shared.IoC.Dependency] private readonly IEntityManager _entityManager = default!; + [Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!; - private static readonly TileAtmosphereComparer _comparer = new TileAtmosphereComparer(); + private static readonly TileAtmosphereComparer Comparer = new TileAtmosphereComparer(); - [ViewVariables] - private int _archivedCycle = 0; - - [ViewVariables] - private int _currentCycle = 0; + [ViewVariables] private int _archivedCycle; + [ViewVariables] private int _currentCycle; [ViewVariables] private static GasTileOverlaySystem _gasTileOverlaySystem; @@ -51,13 +47,13 @@ namespace Content.Server.Atmos private float _temperatureArchived = Atmospherics.T20C; // I know this being static is evil, but I seriously can't come up with a better solution to sound spam. - private static int _soundCooldown = 0; + private static int _soundCooldown; [ViewVariables] - public TileAtmosphere PressureSpecificTarget { get; set; } = null; + public TileAtmosphere PressureSpecificTarget { get; set; } [ViewVariables] - public float PressureDifference { get; set; } = 0; + public float PressureDifference { get; set; } [ViewVariables(VVAccess.ReadWrite)] public float HeatCapacity { get; set; } = 1f; @@ -66,13 +62,19 @@ namespace Content.Server.Atmos public float ThermalConductivity => Tile?.Tile.GetContentTileDefinition().ThermalConductivity ?? 0.05f; [ViewVariables] - public bool Excited { get; set; } = false; + public bool Excited { get; set; } [ViewVariables] - private GridAtmosphereComponent _gridAtmosphereComponent; + private readonly GridAtmosphereComponent _gridAtmosphereComponent; + + /// + /// Adjacent tiles in the same order as . (NSEW) + /// + [ViewVariables] + private readonly TileAtmosphere[] _adjacentTiles = new TileAtmosphere[Atmospherics.Directions]; [ViewVariables] - private readonly Dictionary _adjacentTiles = new Dictionary(); + private AtmosDirection _adjacentBits = AtmosDirection.Invalid; [ViewVariables] private TileAtmosInfo _tileAtmosInfo; @@ -80,7 +82,7 @@ namespace Content.Server.Atmos [ViewVariables] public Hotspot Hotspot; - private Direction _pressureDirection; + private AtmosDirection _pressureDirection; [ViewVariables] public GridId GridIndex { get; } @@ -100,14 +102,16 @@ namespace Content.Server.Atmos [ViewVariables] public bool BlocksAir => _gridAtmosphereComponent.IsAirBlocked(GridIndices); - public TileAtmosphere(GridAtmosphereComponent atmosphereComponent, GridId gridIndex, MapIndices gridIndices, GasMixture mixture = null) + public TileAtmosphere(GridAtmosphereComponent atmosphereComponent, GridId gridIndex, MapIndices gridIndices, GasMixture mixture = null, bool immutable = false) { IoCManager.InjectDependencies(this); _gridAtmosphereComponent = atmosphereComponent; GridIndex = gridIndex; GridIndices = gridIndices; Air = mixture; - ResetTileAtmosInfo(); + + if(immutable) + Air?.MarkImmutable(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -163,7 +167,7 @@ namespace Content.Server.Atmos } } - //[MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void HighPressureMovements() { // TODO ATMOS finish this @@ -175,7 +179,6 @@ namespace Content.Server.Atmos GridIndices.ToGridCoordinates(_mapManager, GridIndex), AudioHelpers.WithVariation(0.125f).WithVolume(MathHelper.Clamp(PressureDifference / 10, 10, 100))); } - foreach (var entity in _entityManager.GetEntitiesIntersecting(_mapManager.GetGrid(GridIndex).ParentMapId, Box2.UnitCentered.Translated(GridIndices))) { if (!entity.TryGetComponent(out ICollidableComponent physics) @@ -195,7 +198,7 @@ namespace Content.Server.Atmos if (PressureDifference > 100) { - // Do space wind graphics here! + // TODO ATMOS Do space wind graphics here! } _soundCooldown++; @@ -220,20 +223,22 @@ namespace Content.Server.Atmos } } - //[MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EqualizePressureInZone(int cycleNum) { if (Air == null || (_tileAtmosInfo.LastCycle >= cycleNum)) return; // Already done. - ResetTileAtmosInfo(); - + _tileAtmosInfo = new TileAtmosInfo(); var startingMoles = Air.TotalMoles; var runAtmos = false; // We need to figure if this is necessary - foreach (var (direction, other) in _adjacentTiles) + for (var i = 0; i < Atmospherics.Directions; i++) { + var direction = (AtmosDirection) (1 << i); + if (!_adjacentBits.HasFlag(direction)) continue; + var other = _adjacentTiles[i]; if (other?.Air == null) continue; var comparisonMoles = other.Air.TotalMoles; if (!(MathF.Abs(comparisonMoles - startingMoles) > Atmospherics.MinimumMolesDeltaToMove)) continue; @@ -265,13 +270,15 @@ namespace Content.Server.Atmos totalMoles += tileMoles; } - foreach (var (_, adj) in exploring._adjacentTiles) + for (var j = 0; j < Atmospherics.Directions; j++) { + var direction = (AtmosDirection) (1 << j); + if (!exploring._adjacentBits.HasFlag(direction)) continue; + var adj = exploring._adjacentTiles[j]; if (adj?.Air == null) continue; if(adj._tileAtmosInfo.LastQueueCycle == queueCycle) continue; - adj.ResetTileAtmosInfo(); + adj._tileAtmosInfo = new TileAtmosInfo {LastQueueCycle = queueCycle}; - adj._tileAtmosInfo.LastQueueCycle = queueCycle; if(tileCount < Atmospherics.ZumosHardTileLimit) tiles[tileCount++] = adj; if (adj.Air.Immutable) @@ -326,47 +333,42 @@ namespace Content.Server.Atmos if (giverTilesLength > logN && takerTilesLength > logN) { // Even if it fails, it will speed up the next part. - Array.Sort(tiles, 0, tileCount, _comparer); + Array.Sort(tiles, 0, tileCount, Comparer); for (var i = 0; i < tileCount; i++) { var tile = tiles[i]; tile._tileAtmosInfo.FastDone = true; if (!(tile._tileAtmosInfo.MoleDelta > 0)) continue; - var eligibleDirections = ArrayPool.Shared.Rent(4); + var eligibleDirections = AtmosDirection.Invalid; var eligibleDirectionCount = 0; - foreach (var direction in Cardinal) + for (var j = 0; j < Atmospherics.Directions; j++) { - if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; + var direction = (AtmosDirection) (1 << j); + if (!tile._adjacentBits.HasFlag(direction)) continue; + var tile2 = tile._adjacentTiles[j]; // skip anything that isn't part of our current processing block. if (tile2._tileAtmosInfo.FastDone || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue; - eligibleDirections[eligibleDirectionCount++] = direction; + eligibleDirections |= direction; + eligibleDirectionCount++; } if (eligibleDirectionCount <= 0) continue; // Oof we've painted ourselves into a corner. Bad luck. Next part will handle this. var molesToMove = tile._tileAtmosInfo.MoleDelta / eligibleDirectionCount; - foreach (var direction in Cardinal) + for (var j = 0; j < Atmospherics.Directions; j++) { - var hasDirection = false; - for (var j = 0; j < eligibleDirectionCount; j++) - { - if (eligibleDirections[j] != direction) continue; - hasDirection = true; - break; - } + var direction = (AtmosDirection) (1 << j); + if (!eligibleDirections.HasFlag(direction)) continue; - if (hasDirection || !tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; tile.AdjustEqMovement(direction, molesToMove); tile._tileAtmosInfo.MoleDelta -= molesToMove; - tile2._tileAtmosInfo.MoleDelta += molesToMove; + tile._adjacentTiles[j]._tileAtmosInfo.MoleDelta += molesToMove; } - - ArrayPool.Shared.Return(eligibleDirections); } giverTilesLength = 0; @@ -393,7 +395,7 @@ namespace Content.Server.Atmos for (var j = 0; j < giverTilesLength; j++) { var giver = giverTiles[j]; - giver._tileAtmosInfo.CurrentTransferDirection = (Direction) (-1); + giver._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid; giver._tileAtmosInfo.CurrentTransferAmount = 0; var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl; var queueLength = 0; @@ -405,9 +407,11 @@ namespace Content.Server.Atmos break; // We're done here now. Let's not do more work than needed. var tile = queue[i]; - foreach (var direction in Cardinal) + for (var k = 0; k < Atmospherics.Directions; k++) { - if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; + var direction = (AtmosDirection) (1 << k); + if (!tile._adjacentBits.HasFlag(direction)) continue; + var tile2 = tile._adjacentTiles[k]; if (giver._tileAtmosInfo.MoleDelta <= 0) break; // We're done here now. Let's not do more work than needed. if (tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue; if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) continue; @@ -441,15 +445,11 @@ namespace Content.Server.Atmos for (var i = queueLength - 1; i >= 0; i--) { var tile = queue[i]; - if (tile._tileAtmosInfo.CurrentTransferAmount != 0 && - tile._tileAtmosInfo.CurrentTransferDirection != (Direction) (-1)) + if (tile._tileAtmosInfo.CurrentTransferAmount != 0 && tile._tileAtmosInfo.CurrentTransferDirection != AtmosDirection.Invalid) { - tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, - tile._tileAtmosInfo.CurrentTransferAmount); - if (tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, - out var adjacent)) - adjacent._tileAtmosInfo.CurrentTransferAmount += - tile._tileAtmosInfo.CurrentTransferAmount; + tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount); + tile._adjacentTiles[tile._tileAtmosInfo.CurrentTransferDirection.ToIndex()] + ._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount; tile._tileAtmosInfo.CurrentTransferAmount = 0; } } @@ -463,7 +463,7 @@ namespace Content.Server.Atmos for (var j = 0; j < takerTilesLength; j++) { var taker = takerTiles[j]; - taker._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid; + taker._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid; taker._tileAtmosInfo.CurrentTransferAmount = 0; var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl; var queueLength = 0; @@ -475,10 +475,11 @@ namespace Content.Server.Atmos break; // We're done here now. Let's not do more work than needed. var tile = queue[i]; - foreach (var direction in Cardinal) + for (var k = 0; k < Atmospherics.Directions; k++) { - if (!tile._adjacentTiles.ContainsKey(direction)) continue; - var tile2 = tile._adjacentTiles[direction]; + var direction = (AtmosDirection) (1 << k); + if (!tile._adjacentBits.HasFlag(direction)) continue; + var tile2 = tile._adjacentTiles[k]; if (taker._tileAtmosInfo.MoleDelta >= 0) break; // We're done here now. Let's not do more work than needed. if (tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue; @@ -512,16 +513,14 @@ namespace Content.Server.Atmos for (var i = queueLength - 1; i >= 0; i--) { var tile = queue[i]; - if (tile._tileAtmosInfo.CurrentTransferAmount == 0 || tile._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid) + if (tile._tileAtmosInfo.CurrentTransferAmount == 0 || tile._tileAtmosInfo.CurrentTransferDirection == AtmosDirection.Invalid) continue; tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount); - if (tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, out var adjacent)) - { - adjacent._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount; - tile._tileAtmosInfo.CurrentTransferAmount = 0; - } + tile._adjacentTiles[tile._tileAtmosInfo.CurrentTransferDirection.ToIndex()] + ._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount; + tile._tileAtmosInfo.CurrentTransferAmount = 0; } } @@ -537,9 +536,11 @@ namespace Content.Server.Atmos for (var i = 0; i < tileCount; i++) { var tile = tiles[i]; - foreach (var direction in Cardinal) + for (var j = 0; j < Atmospherics.Directions; j++) { - if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; + var direction = (AtmosDirection) (1 << j); + if (!tile._adjacentBits.HasFlag(direction)) continue; + var tile2 = tile._adjacentTiles[j]; if (tile2?.Air?.Compare(Air) == GasMixture.GasCompareResult.NoExchange) continue; _gridAtmosphereComponent.AddActiveTile(tile2); break; @@ -555,69 +556,68 @@ namespace Content.Server.Atmos [MethodImpl(MethodImplOptions.AggressiveInlining)] private void FinalizeEq() { - var transferDirections = new Dictionary(); + var transferDirections = new float[Atmospherics.Directions]; var hasTransferDirs = false; - foreach (var direction in Cardinal) + for (var i = 0; i < Atmospherics.Directions; i++) { - var amount = _tileAtmosInfo[direction]; + var amount = _tileAtmosInfo[i]; if (amount == 0) continue; - transferDirections[direction] = amount; - _tileAtmosInfo[direction] = 0; + transferDirections[i] = amount; + _tileAtmosInfo[i] = 0; // Set them to 0 to prevent infinite recursion. hasTransferDirs = true; } if (!hasTransferDirs) return; - foreach (var (direction, amount) in transferDirections) + for(var i = 0; i < Atmospherics.Directions; i++) { - if (!_adjacentTiles.TryGetValue(direction, out var tile) || tile.Air == null) continue; + var direction = (AtmosDirection) (1 << i); + if (!_adjacentBits.HasFlag(direction)) continue; + var amount = transferDirections[i]; + var tile = _adjacentTiles[i]; + if (tile?.Air == null) continue; if (amount > 0) { if (Air.TotalMoles < amount) - FinalizeEqNeighbors(transferDirections.Keys); + FinalizeEqNeighbors(transferDirections); + tile._tileAtmosInfo[direction.GetOpposite()] = 0; tile.Air.Merge(Air.Remove(amount)); UpdateVisuals(); tile.UpdateVisuals(); - ConsiderPressureDifference(direction, amount); + ConsiderPressureDifference(tile, amount); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void FinalizeEqNeighbors(IEnumerable directions) + private void FinalizeEqNeighbors(in float[] transferDirs) { - foreach (var direction in directions) + for (var i = 0; i < Atmospherics.Directions; i++) { - var amount = _tileAtmosInfo[direction]; - if(amount < 0 && _adjacentTiles.TryGetValue(direction, out var adjacent)) - adjacent.FinalizeEq(); + var direction = (AtmosDirection) (1 << i); + var amount = transferDirs[i]; + if(amount < 0 && _adjacentBits.HasFlag(direction)) + _adjacentTiles[i].FinalizeEq(); // A bit of recursion if needed. } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConsiderPressureDifference(Direction direction, float difference) + private void ConsiderPressureDifference(TileAtmosphere other, float difference) { _gridAtmosphereComponent.AddHighPressureDelta(this); if (difference > PressureDifference) { PressureDifference = difference; - _pressureDirection = difference < 0 ? direction.GetOpposite() : direction; + _pressureDirection = ((Vector2i)(GridIndices - other.GridIndices)).GetDir().ToAtmosDirection(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AdjustEqMovement(Direction direction, float molesToMove) + private void AdjustEqMovement(AtmosDirection direction, float amount) { - _tileAtmosInfo[direction] += molesToMove; - if(direction != Direction.Invalid && _adjacentTiles.TryGetValue(direction, out var adj)) - adj._tileAtmosInfo[direction.GetOpposite()] -= molesToMove; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ResetTileAtmosInfo() - { - _tileAtmosInfo = new TileAtmosInfo {CurrentTransferDirection = Direction.Invalid}; + _tileAtmosInfo[direction] += amount; + _adjacentTiles[direction.ToIndex()]._tileAtmosInfo[direction.GetOpposite()] -= amount; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -635,9 +635,13 @@ namespace Content.Server.Atmos _currentCycle = fireCount; var adjacentTileLength = 0; - foreach (var (direction, enemyTile) in _adjacentTiles) + for(var i = 0; i < Atmospherics.Directions; i++) { - // If the tile is null or has no air, we don't do anything + var direction = (AtmosDirection) (1 << i); + if (!_adjacentBits.HasFlag(direction)) continue; + var enemyTile = _adjacentTiles[i]; + + // If the tile is null or has no air, we don't do anything for it. if(enemyTile?.Air == null) continue; adjacentTileLength++; if (fireCount <= enemyTile._currentCycle) continue; @@ -685,11 +689,11 @@ namespace Content.Server.Atmos // Space wind! if (difference > 0) { - ConsiderPressureDifference(direction, difference); + ConsiderPressureDifference(enemyTile, difference); } else { - enemyTile.ConsiderPressureDifference(direction.GetOpposite(), -difference); + enemyTile.ConsiderPressureDifference(this, -difference); } LastShareCheck(); @@ -737,7 +741,7 @@ namespace Content.Server.Atmos if (Air.Temperature > Atmospherics.FireMinimumTemperatureToSpread) { var radiatedTemperature = Air.Temperature * Atmospherics.FireSpreadRadiosityScale; - foreach (var (_, tile) in _adjacentTiles) + foreach (var tile in _adjacentTiles) { if(!tile.Hotspot.Valid) tile.HotspotExpose(radiatedTemperature, Atmospherics.CellVolume/4); @@ -771,17 +775,19 @@ namespace Content.Server.Atmos else { var affected = Air.RemoveRatio(Hotspot.Volume / Air.Volume); - if (affected != null) - { - affected.Temperature = Hotspot.Temperature; - affected.React(this); - Hotspot.Temperature = affected.Temperature; - Hotspot.Volume = affected.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate; - AssumeAir(affected); - } + affected.Temperature = Hotspot.Temperature; + affected.React(this); + Hotspot.Temperature = affected.Temperature; + Hotspot.Volume = affected.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate; + AssumeAir(affected); } - // TODO ATMOS Let all entities in this tile know about the fire? + var tileRef = GridIndices.GetTileRef(GridIndex); + + if (tileRef == null) return; + + _gridAtmosphereComponent.Owner.EntityManager. + EventBus.QueueEvent(EventSource.Local, new FireActEvent(Hotspot.Temperature, Hotspot.Volume)); } private bool ConsiderSuperconductivity() @@ -806,24 +812,24 @@ namespace Content.Server.Atmos public void Superconduct() { var directions = ConductivityDirections(); - var adjacentTiles = _gridAtmosphereComponent.GetAdjacentTiles(GridIndices, true); - if (directions.Length > 0) + for(var i = 0; i < Atmospherics.Directions; i++) { - foreach (var direction in directions) - { - if (!adjacentTiles.TryGetValue(direction, out var adjacent)) continue; + var direction = (AtmosDirection) (1 << i); + if (!directions.HasFlag(direction)) continue; - if (adjacent.ThermalConductivity == 0f) - continue; + var adjacent = _adjacentTiles[direction.ToIndex()]; - if(adjacent._archivedCycle < _gridAtmosphereComponent.UpdateCounter) - adjacent.Archive(_gridAtmosphereComponent.UpdateCounter); + // TODO ATMOS handle adjacent being null. + if (adjacent == null || adjacent.ThermalConductivity == 0f) + continue; - adjacent.NeighborConductWithSource(this); + if(adjacent._archivedCycle < _gridAtmosphereComponent.UpdateCounter) + adjacent.Archive(_gridAtmosphereComponent.UpdateCounter); - adjacent.ConsiderSuperconductivity(); - } + adjacent.NeighborConductWithSource(this); + + adjacent.ConsiderSuperconductivity(); } RadiateToSpace(); @@ -917,20 +923,20 @@ namespace Content.Server.Atmos } } - public Direction[] ConductivityDirections() + public AtmosDirection ConductivityDirections() { if(BlocksAir) { if(_archivedCycle < _gridAtmosphereComponent.UpdateCounter) Archive(_gridAtmosphereComponent.UpdateCounter); - return Cardinal; + return AtmosDirection.All; } // TODO ATMOS check if this is correct - return Cardinal; + return AtmosDirection.All; } - //[MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ExplosivelyDepressurize(int cycleNum) { if (Air == null) return; @@ -947,14 +953,13 @@ namespace Content.Server.Atmos tiles[tileCount++] = this; - ResetTileAtmosInfo(); - _tileAtmosInfo.LastQueueCycle = queueCycle; + _tileAtmosInfo = new TileAtmosInfo {LastQueueCycle = queueCycle}; for (var i = 0; i < tileCount; i++) { var tile = tiles[i]; tile._tileAtmosInfo.LastCycle = cycleNum; - tile._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid; + tile._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid; if (tile.Air.Immutable) { spaceTiles[spaceTileCount++] = tile; @@ -962,18 +967,19 @@ namespace Content.Server.Atmos } else { - foreach (var direction in Cardinal) + for (var j = 0; j < Atmospherics.Directions; j++) { - if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; + var direction = (AtmosDirection) (1 << j); + if (!tile._adjacentBits.HasFlag(direction)) continue; + var tile2 = tile._adjacentTiles[j]; if (tile2.Air == null) continue; if (tile2._tileAtmosInfo.LastQueueCycle == queueCycle) continue; tile.ConsiderFirelocks(tile2); // The firelocks might have closed on us. - if (tile._adjacentTiles[direction]?.Air == null) continue; - tile2.ResetTileAtmosInfo(); - tile2._tileAtmosInfo.LastQueueCycle = queueCycle; + if (!tile._adjacentBits.HasFlag(direction)) continue; + tile2._tileAtmosInfo = new TileAtmosInfo {LastQueueCycle = queueCycle}; tiles[tileCount++] = tile2; } } @@ -991,18 +997,21 @@ namespace Content.Server.Atmos var tile = spaceTiles[i]; progressionOrder[progressionCount++] = tile; tile._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; - tile._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid; + tile._tileAtmosInfo.CurrentTransferDirection = AtmosDirection.Invalid; } for (var i = 0; i < progressionCount; i++) { var tile = progressionOrder[i]; - foreach (var direction in Cardinal) + for (var j = 0; j < Atmospherics.Directions; j++) { - if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; + var direction = (AtmosDirection) (1 << j); + // TODO ATMOS This is a terrible hack that accounts for the mess that are space TileAtmospheres. + if (!tile._adjacentBits.HasFlag(direction) && !tile.Air.Immutable) continue; + var tile2 = tile._adjacentTiles[j]; if (tile2?._tileAtmosInfo.LastQueueCycle != queueCycle) continue; if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) continue; - if(tile2.Air.Immutable) continue; + if(tile2.Air?.Immutable ?? false) continue; tile2._tileAtmosInfo.CurrentTransferDirection = direction.GetOpposite(); tile2._tileAtmosInfo.CurrentTransferAmount = 0; tile2.PressureSpecificTarget = tile.PressureSpecificTarget; @@ -1014,10 +1023,11 @@ namespace Content.Server.Atmos for (var i = progressionCount - 1; i >= 0; i--) { var tile = progressionOrder[i]; - if (tile._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid) continue; + if (tile._tileAtmosInfo.CurrentTransferDirection == AtmosDirection.Invalid) continue; _gridAtmosphereComponent.AddHighPressureDelta(tile); _gridAtmosphereComponent.AddActiveTile(tile); - if (!tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, out var tile2) || tile2.Air == null) continue; + var tile2 = tile._adjacentTiles[tile._tileAtmosInfo.CurrentTransferDirection.ToIndex()]; + if (tile2?.Air == null) continue; var sum = tile2.Air.TotalMoles; totalGasesRemoved += sum; tile._tileAtmosInfo.CurrentTransferAmount += sum; @@ -1025,7 +1035,7 @@ namespace Content.Server.Atmos tile.PressureDifference = tile._tileAtmosInfo.CurrentTransferAmount; tile._pressureDirection = tile._tileAtmosInfo.CurrentTransferDirection; - if (tile2._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid) + if (tile2._tileAtmosInfo.CurrentTransferDirection == AtmosDirection.Invalid) { tile2.PressureDifference = tile2._tileAtmosInfo.CurrentTransferAmount; tile2._pressureDirection = tile._tileAtmosInfo.CurrentTransferDirection; @@ -1082,21 +1092,30 @@ namespace Content.Server.Atmos [MethodImpl(MethodImplOptions.AggressiveInlining)] public void UpdateAdjacent() { - foreach (var direction in Cardinal) + for (var i = 0; i < Atmospherics.Directions; i++) { - if (!_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction))) + var direction = (AtmosDirection) (1 << i); + + var otherIndices = GridIndices.Offset(direction.ToDirection()); + + var isSpace = _gridAtmosphereComponent.IsSpace(GridIndices); + var adjacent = _gridAtmosphereComponent.GetTile(otherIndices, !isSpace); + _adjacentTiles[direction.ToIndex()] = adjacent; + adjacent?.UpdateAdjacent(direction.GetOpposite()); + + if (adjacent != null && !_gridAtmosphereComponent.IsAirBlocked(adjacent.GridIndices)) { - var adjacent = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction)); - _adjacentTiles[direction] = adjacent; - adjacent.UpdateAdjacent(direction.GetOpposite()); + _adjacentBits |= direction; } } } - public void UpdateAdjacent(Direction direction) + public void UpdateAdjacent(AtmosDirection direction) { - if (!_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction))) - _adjacentTiles[direction] = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction)); + if (!_gridAtmosphereComponent.IsAirBlocked(GridIndices.Offset(direction.ToDirection()))) + { + _adjacentTiles[direction.ToIndex()] = _gridAtmosphereComponent.GetTile(GridIndices.Offset(direction.ToDirection())); + } } private void LastShareCheck() @@ -1111,13 +1130,7 @@ namespace Content.Server.Atmos } } - private static readonly Direction[] Cardinal = - new Direction[] - { - Direction.North, Direction.East, Direction.South, Direction.West - }; - - public void TemperatureExpose(GasMixture mixture, float temperature, float cellVolume) + public void TemperatureExpose(GasMixture air, float temperature, float volume) { // TODO ATMOS do this } diff --git a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs index 87346d632b..fd56e9d34d 100644 --- a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs @@ -36,7 +36,7 @@ namespace Content.Server.GameObjects.Components.Atmos /// /// Check current execution time every n instances processed. /// - private const int LagCheckIterations = 15; + private const int LagCheckIterations = 30; /// /// Max milliseconds allowed for atmos updates. @@ -50,46 +50,93 @@ namespace Content.Server.GameObjects.Components.Atmos public override string Name => "GridAtmosphere"; + private bool _paused = false; private float _timer = 0f; private Stopwatch _stopwatch = new Stopwatch(); + + [ViewVariables] public int UpdateCounter { get; private set; } = 0; + [ViewVariables] + private double _tileEqualizeLastProcess; + [ViewVariables] private readonly HashSet _excitedGroups = new HashSet(1000); + [ViewVariables] + private int ExcitedGroupCount => _excitedGroups.Count; + + [ViewVariables] + private double _excitedGroupLastProcess; + [ViewVariables] private readonly Dictionary _tiles = new Dictionary(1000); [ViewVariables] private readonly HashSet _activeTiles = new HashSet(1000); + [ViewVariables] + private int ActiveTilesCount => _activeTiles.Count; + + [ViewVariables] + private double _activeTilesLastProcess; + [ViewVariables] private readonly HashSet _hotspotTiles = new HashSet(1000); + [ViewVariables] + private int HotspotTilesCount => _hotspotTiles.Count; + + [ViewVariables] + private double _hotspotsLastProcess; + [ViewVariables] private readonly HashSet _superconductivityTiles = new HashSet(1000); + [ViewVariables] + private int SuperconductivityTilesCount => _superconductivityTiles.Count; + + [ViewVariables] + private double _superconductivityLastProcess; + [ViewVariables] private readonly HashSet _invalidatedCoords = new HashSet(1000); + [ViewVariables] + private int InvalidatedCoordsCount => _invalidatedCoords.Count; + [ViewVariables] private HashSet _highPressureDelta = new HashSet(1000); [ViewVariables] - private readonly List _pipeNets = new List(); - - /// - /// Index of most recently updated . - /// - private int _pipeNetIndex = 0; + private int HighPressureDeltaCount => _highPressureDelta.Count; [ViewVariables] - private readonly List _pipeNetDevices = new List(); + private double _highPressureDeltaLastProcess; - /// - /// Index of most recently updated . - /// - private int _deviceIndex = 0; + [ViewVariables] + private readonly HashSet _pipeNets = new HashSet(); + + [ViewVariables] + private double _pipeNetLastProcess; + + [ViewVariables] + private readonly HashSet _pipeNetDevices = new HashSet(); + + [ViewVariables] + private double _pipeNetDevicesLastProcess; + + [ViewVariables] + private Queue _currentRunTiles = new Queue(); + + [ViewVariables] + private Queue _currentRunExcitedGroups = new Queue(); + + [ViewVariables] + private Queue _currentRunPipeNet = new Queue(); + + [ViewVariables] + private Queue _currentRunPipeNetDevice = new Queue(); [ViewVariables] private ProcessState _state = ProcessState.TileEqualize; @@ -204,9 +251,10 @@ namespace Content.Server.GameObjects.Components.Atmos tile.UpdateAdjacent(); tile.UpdateVisuals(); - foreach (var direction in Cardinal) + for (var i = 0; i < Atmospherics.Directions; i++) { - var otherIndices = indices.Offset(direction); + var direction = (AtmosDirection) (1 << i); + var otherIndices = indices.Offset(direction.ToDirection()); var otherTile = GetTile(otherIndices); AddActiveTile(otherTile); otherTile?.UpdateAdjacent(direction.GetOpposite()); @@ -324,7 +372,6 @@ namespace Content.Server.GameObjects.Components.Atmos public void RemovePipeNet(IPipeNet pipeNet) { _pipeNets.Remove(pipeNet); - _deviceIndex = 0; } public void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice) @@ -335,28 +382,25 @@ namespace Content.Server.GameObjects.Components.Atmos public void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice) { _pipeNetDevices.Remove(pipeNetDevice); - _deviceIndex = 0; } /// - public TileAtmosphere? GetTile(GridCoordinates coordinates) + public TileAtmosphere? GetTile(GridCoordinates coordinates, bool createSpace = true) { - return GetTile(coordinates.ToMapIndices(_mapManager)); + return GetTile(coordinates.ToMapIndices(_mapManager), createSpace); } /// - public TileAtmosphere? GetTile(MapIndices indices) + public TileAtmosphere? GetTile(MapIndices indices, bool createSpace = true) { if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return null; if (_tiles.TryGetValue(indices, out var tile)) return tile; // We don't have that tile! - if (IsSpace(indices)) + if (IsSpace(indices) && createSpace) { - var space = new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(int.MaxValue){Temperature = Atmospherics.TCMB}); - space.Air.MarkImmutable(); - return space; + return new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.TCMB}, true); } return null; @@ -378,23 +422,21 @@ namespace Content.Server.GameObjects.Components.Atmos return mapGrid.Grid.GetTileRef(indices).Tile.IsEmpty; } - public Dictionary GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false) + public Dictionary GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false) { - var sides = new Dictionary(); - foreach (var dir in Cardinal) + var sides = new Dictionary(); + for (var i = 0; i < Atmospherics.Directions; i++) { - var side = indices.Offset(dir); + var direction = (AtmosDirection) (1 << i); + var side = indices.Offset(direction.ToDirection()); var tile = GetTile(side); if (tile != null && (tile.Air != null || includeAirBlocked)) - sides[dir] = tile; + sides[direction] = tile; } return sides; } - /// - public int HighPressureDeltaCount => _highPressureDelta.Count; - public long EqualizationQueueCycleControl { get; set; } /// @@ -422,35 +464,83 @@ namespace Content.Server.GameObjects.Components.Atmos switch (_state) { case ProcessState.TileEqualize: - ProcessTileEqualize(); + if (!ProcessTileEqualize(_paused)) + { + _paused = true; + return; + } + + _paused = false; _state = ProcessState.ActiveTiles; return; case ProcessState.ActiveTiles: - ProcessActiveTiles(); + if (!ProcessActiveTiles(_paused)) + { + _paused = true; + return; + } + + _paused = false; _state = ProcessState.ExcitedGroups; return; case ProcessState.ExcitedGroups: - ProcessExcitedGroups(); + if (!ProcessExcitedGroups(_paused)) + { + _paused = true; + return; + } + + _paused = false; _state = ProcessState.HighPressureDelta; return; case ProcessState.HighPressureDelta: - ProcessHighPressureDelta(); + if (!ProcessHighPressureDelta(_paused)) + { + _paused = true; + return; + } + + _paused = false; _state = ProcessState.Hotspots; break; case ProcessState.Hotspots: - ProcessHotspots(); + if (!ProcessHotspots(_paused)) + { + _paused = true; + return; + } + + _paused = false; _state = ProcessState.Superconductivity; break; case ProcessState.Superconductivity: - ProcessSuperconductivity(); + if (!ProcessSuperconductivity(_paused)) + { + _paused = true; + return; + } + + _paused = false; _state = ProcessState.PipeNet; break; case ProcessState.PipeNet: - ProcessPipeNets(); + if (!ProcessPipeNets(_paused)) + { + _paused = true; + return; + } + + _paused = false; _state = ProcessState.PipeNetDevices; break; case ProcessState.PipeNetDevices: - ProcessPipeNetDevices(); + if (!ProcessPipeNetDevices(_paused)) + { + _paused = true; + return; + } + + _paused = false; _state = ProcessState.TileEqualize; break; } @@ -458,47 +548,71 @@ namespace Content.Server.GameObjects.Components.Atmos UpdateCounter++; } - public void ProcessTileEqualize() + public bool ProcessTileEqualize(bool resumed = false) { _stopwatch.Restart(); + if(!resumed) + _currentRunTiles = new Queue(_activeTiles); + var number = 0; - foreach (var tile in _activeTiles.ToArray()) + while (_currentRunTiles.Count > 0) { + var tile = _currentRunTiles.Dequeue(); tile.EqualizePressureInZone(UpdateCounter); if (number++ < LagCheckIterations) continue; number = 0; // Process the rest next time. if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) - return; + { + _tileEqualizeLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return false; + } } + + _tileEqualizeLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return true; } - public void ProcessActiveTiles() + public bool ProcessActiveTiles(bool resumed = false) { _stopwatch.Restart(); + if(!resumed) + _currentRunTiles = new Queue(_activeTiles); + var number = 0; - foreach (var tile in _activeTiles.ToArray()) + while (_currentRunTiles.Count > 0) { + var tile = _currentRunTiles.Dequeue(); tile.ProcessCell(UpdateCounter); if (number++ < LagCheckIterations) continue; number = 0; // Process the rest next time. if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) - return; + { + _activeTilesLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return false; + } } + + _activeTilesLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return true; } - public void ProcessExcitedGroups() + public bool ProcessExcitedGroups(bool resumed = false) { _stopwatch.Restart(); + if(!resumed) + _currentRunExcitedGroups = new Queue(_excitedGroups); + var number = 0; - foreach (var excitedGroup in _excitedGroups.ToArray()) + while (_currentRunExcitedGroups.Count > 0) { + var excitedGroup = _currentRunExcitedGroups.Dequeue(); excitedGroup.BreakdownCooldown++; excitedGroup.DismantleCooldown++; @@ -512,17 +626,27 @@ namespace Content.Server.GameObjects.Components.Atmos number = 0; // Process the rest next time. if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) - return; + { + _excitedGroupLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return false; + } } + + _excitedGroupLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return true; } - public void ProcessHighPressureDelta() + public bool ProcessHighPressureDelta(bool resumed = false) { _stopwatch.Restart(); + if(!resumed) + _currentRunTiles = new Queue(_highPressureDelta); + var number = 0; - foreach (var tile in _highPressureDelta.ToArray()) + while (_currentRunTiles.Count > 0) { + var tile = _currentRunTiles.Dequeue(); tile.HighPressureMovements(); tile.PressureDifference = 0f; tile.PressureSpecificTarget = null; @@ -532,83 +656,124 @@ namespace Content.Server.GameObjects.Components.Atmos number = 0; // Process the rest next time. if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) - return; + { + _highPressureDeltaLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return false; + } } + + _highPressureDeltaLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return true; } - private void ProcessHotspots() + private bool ProcessHotspots(bool resumed = false) { _stopwatch.Restart(); + if(!resumed) + _currentRunTiles = new Queue(_hotspotTiles); + var number = 0; - foreach (var hotspot in _hotspotTiles.ToArray()) + while (_currentRunTiles.Count > 0) { + var hotspot = _currentRunTiles.Dequeue(); hotspot.ProcessHotspot(); if (number++ < LagCheckIterations) continue; number = 0; // Process the rest next time. if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) - return; + { + _hotspotsLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return false; + } } + + _hotspotsLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return true; } - private void ProcessSuperconductivity() + private bool ProcessSuperconductivity(bool resumed = false) { _stopwatch.Restart(); + if(!resumed) + _currentRunTiles = new Queue(_superconductivityTiles); + var number = 0; - foreach (var superconductivity in _superconductivityTiles.ToArray()) + while (_currentRunTiles.Count > 0) { + var superconductivity = _currentRunTiles.Dequeue(); superconductivity.Superconduct(); if (number++ < LagCheckIterations) continue; number = 0; // Process the rest next time. if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) - return; + { + _superconductivityLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return false; + } } + + _superconductivityLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return true; } - private void ProcessPipeNets() + private bool ProcessPipeNets(bool resumed = false) { _stopwatch.Restart(); + if(!resumed) + _currentRunPipeNet = new Queue(_pipeNets); + var number = 0; - var pipeNets = _pipeNets.ToArray(); - var netCount = pipeNets.Count(); - for ( ; _pipeNetIndex < netCount; _pipeNetIndex++) + while (_currentRunPipeNet.Count > 0) { - pipeNets[_pipeNetIndex].Update(); + var pipenet = _currentRunPipeNet.Dequeue(); + pipenet.Update(); if (number++ < LagCheckIterations) continue; number = 0; // Process the rest next time. if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) - return; + { + _pipeNetLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return false; + } } - _pipeNetIndex = 0; + + _pipeNetLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return true; } - private void ProcessPipeNetDevices() + private bool ProcessPipeNetDevices(bool resumed = false) { _stopwatch.Restart(); + if(!resumed) + _currentRunPipeNetDevice = new Queue(_pipeNetDevices); + var number = 0; - var pipeNetDevices = _pipeNetDevices.ToArray(); - var deviceCount = pipeNetDevices.Count(); - for ( ; _deviceIndex < deviceCount; _deviceIndex++) + while (_currentRunPipeNet.Count > 0) { - pipeNetDevices[_deviceIndex].Update(); + var device = _currentRunPipeNetDevice.Dequeue(); + device.Update(); if (number++ < LagCheckIterations) continue; number = 0; // Process the rest next time. if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) - return; + { + _pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return false; + } } - _deviceIndex = 0; + + _pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return true; } + private AirtightComponent? GetObstructingComponent(MapIndices indices) { if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default; @@ -622,12 +787,6 @@ namespace Content.Server.GameObjects.Components.Atmos return null; } - private static readonly Direction[] Cardinal = - new [] - { - Direction.North, Direction.East, Direction.South, Direction.West - }; - public void Dispose() { diff --git a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs index 9037243434..84bce7a646 100644 --- a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs @@ -1,25 +1,50 @@ #nullable enable +using System; +using System.Collections.Generic; +using System.Linq; using Content.Server.Atmos; +using Content.Server.Atmos.Reactions; +using Content.Server.Interfaces; using Content.Shared.GameObjects.EntitySystems.Atmos; using JetBrains.Annotations; using Robust.Server.Interfaces.Timing; +using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.Map; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.Shared.Prototypes; namespace Content.Server.GameObjects.EntitySystems { [UsedImplicitly] public class AtmosphereSystem : SharedAtmosphereSystem { + [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPauseManager _pauseManager = default!; + [Dependency] private IEntityManager _entityManager = default!; + + private GasReactionPrototype[] _gasReactions = Array.Empty(); + + /// + /// List of gas reactions ordered by priority. + /// + public IEnumerable GasReactions => _gasReactions!; + + /// + /// EventBus reference for gas reactions. + /// + public IEventBus EventBus => _entityManager.EventBus; public override void Initialize() { base.Initialize(); + _gasReactions = _protoMan.EnumeratePrototypes().ToArray(); + Array.Sort(_gasReactions, (a, b) => b.Priority.CompareTo(a.Priority)); + _mapManager.TileChanged += OnTileChanged; } diff --git a/Content.Server/Interfaces/IGasReactionEffect.cs b/Content.Server/Interfaces/IGasReactionEffect.cs index 95148d570c..7d2b991236 100644 --- a/Content.Server/Interfaces/IGasReactionEffect.cs +++ b/Content.Server/Interfaces/IGasReactionEffect.cs @@ -1,12 +1,13 @@ #nullable enable using Content.Server.Atmos; using Content.Server.Atmos.Reactions; +using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.Serialization; namespace Content.Server.Interfaces { public interface IGasReactionEffect : IExposeData { - ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder); + ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, IEventBus eventBus); } } diff --git a/Content.Shared/Atmos/AtmosDirection.cs b/Content.Shared/Atmos/AtmosDirection.cs new file mode 100644 index 0000000000..450a92e4df --- /dev/null +++ b/Content.Shared/Atmos/AtmosDirection.cs @@ -0,0 +1,89 @@ +using System; +using Robust.Shared.Maths; + +namespace Content.Shared.Atmos +{ + /// + /// The reason we use this over is that we are going to do some heavy bitflag usage. + /// + [Flags] + public enum AtmosDirection : byte + { + Invalid = 0, + North = 1 << 0, + South = 1 << 1, + East = 1 << 2, + West = 1 << 3, + + NorthEast = North | East, + NorthWest = North | West, + SouthEast = South | East, + SouthWest = South | West, + + All = North | South | East | West, + } + + public static class AtmosDirectionHelpers + { + public static AtmosDirection GetOpposite(this AtmosDirection direction) + { + return direction switch + { + AtmosDirection.North => AtmosDirection.South, + AtmosDirection.South => AtmosDirection.North, + AtmosDirection.East => AtmosDirection.West, + AtmosDirection.West => AtmosDirection.East, + AtmosDirection.NorthEast => AtmosDirection.SouthWest, + AtmosDirection.NorthWest => AtmosDirection.SouthEast, + AtmosDirection.SouthEast => AtmosDirection.NorthWest, + AtmosDirection.SouthWest => AtmosDirection.NorthEast, + _ => throw new ArgumentOutOfRangeException(nameof(direction)) + }; + } + + public static Direction ToDirection(this AtmosDirection direction) + { + return direction switch + { + AtmosDirection.North => Direction.North, + AtmosDirection.South => Direction.South, + AtmosDirection.East => Direction.East, + AtmosDirection.West => Direction.West, + AtmosDirection.NorthEast => Direction.NorthEast, + AtmosDirection.NorthWest => Direction.NorthWest, + AtmosDirection.SouthEast => Direction.SouthEast, + AtmosDirection.SouthWest => Direction.SouthWest, + AtmosDirection.Invalid => Direction.Invalid, + _ => throw new ArgumentOutOfRangeException(nameof(direction)) + }; + } + + public static AtmosDirection ToAtmosDirection(this Direction direction) + { + return direction switch + { + Direction.North => AtmosDirection.North, + Direction.South => AtmosDirection.South, + Direction.East => AtmosDirection.East, + Direction.West => AtmosDirection.West, + Direction.NorthEast => AtmosDirection.NorthEast, + Direction.NorthWest => AtmosDirection.NorthWest, + Direction.SouthEast => AtmosDirection.SouthEast, + Direction.SouthWest => AtmosDirection.SouthWest, + Direction.Invalid => AtmosDirection.Invalid, + _ => throw new ArgumentOutOfRangeException(nameof(direction)) + }; + } + + public static int ToIndex(this AtmosDirection direction) + { + // This will throw if you pass an invalid direction. Not this method's fault, but yours! + return (int) Math.Log2((int) direction); + } + + public static AtmosDirection WithFlag(this AtmosDirection direction, AtmosDirection other) + { + return direction | other; + } + } +} diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index dbd6bf073f..f9391a040c 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -211,6 +211,12 @@ public const int LowPressureDamage = 4; public const float WindowHeatTransferCoefficient = 0.1f; + + /// + /// Directions that atmos currently supports. Modify in case of multi-z. + /// See on the server. + /// + public const int Directions = 4; } /// diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 9a4d47bb02..5bf9f9cdd9 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -84,6 +84,7 @@ True True True + True True True True @@ -96,6 +97,7 @@ True True True + True True True True