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/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/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..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; @@ -39,14 +40,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() { @@ -152,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/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/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/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/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/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 08ec838c88..0524a87ff7 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ChemMaster/ChemMasterWindow.cs @@ -21,6 +21,8 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster /// public class ChemMasterWindow : SS14Window { + [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; @@ -45,11 +47,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 +66,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 +91,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { new Label { - Text = _localizationManager.GetString("No container loaded.") + Text = Loc.GetString("No container loaded.") } } }), @@ -109,10 +106,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 +133,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { new Label { - Text = _localizationManager.GetString("Buffer empty.") + Text = Loc.GetString("Buffer empty.") } } }), @@ -151,7 +148,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { Children = { - new Label {Text = _localizationManager.GetString("Packaging ")}, + new Label {Text = Loc.GetString("Packaging ")}, } }, @@ -185,7 +182,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { new Label { - Text = _localizationManager.GetString("Pills:") + Text = Loc.GetString("Pills:") }, }, @@ -212,7 +209,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 +219,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ChemMaster { new Label { - Text = _localizationManager.GetString("Bottles:") + Text = Loc.GetString("Bottles:") }, }, @@ -249,7 +246,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 +311,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 +330,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 +367,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 +385,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..d32d10f776 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs @@ -20,6 +20,8 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser /// public class ReagentDispenserWindow : SS14Window { + [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; @@ -50,11 +52,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 +73,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 +97,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 +122,7 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser { new Label { - Text = _localizationManager.GetString("No container loaded.") + Text = Loc.GetString("No container loaded.") } } }), @@ -155,7 +152,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 +240,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 +264,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/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/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/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.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.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/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/HUD/Inventory/StrippableBoundUserInterface.cs b/Content.Client/GameObjects/Components/HUD/Inventory/StrippableBoundUserInterface.cs index afd87e0e06..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; @@ -28,6 +31,8 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory base.Open(); _strippingMenu = new StrippingMenu($"{Owner.Owner.Name}'s inventory"); + + _strippingMenu.OnClose += Close; _strippingMenu.OpenCentered(); UpdateMenu(); } @@ -47,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) => @@ -55,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) => @@ -64,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) @@ -74,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/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.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/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/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs index 30e32afad3..2750547ad3 100644 --- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs +++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs @@ -17,11 +17,9 @@ namespace Content.Client.GameObjects.Components.Items [ComponentReference(typeof(ISharedHandsComponent))] public class HandsComponent : SharedHandsComponent { - private HandsGui? _gui; - -#pragma warning disable 649 [Dependency] private readonly IGameHud _gameHud = default!; -#pragma warning restore 649 + + private HandsGui? _gui; /// private readonly List _hands = new List(); @@ -158,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/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/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/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/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.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.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..8f5b4cf999 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; @@ -70,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.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.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/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.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/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/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/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/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)) { 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/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/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/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.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/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 6e67d79995..5d3cca4938 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -86,9 +86,6 @@ "CanSpill", "SpeedLoader", "Hitscan", - "BoltActionBarrel", - "PumpBarrel", - "RevolverBarrel", "ExplosiveProjectile", "StunnableProjectile", "RandomPottedPlant", @@ -108,7 +105,6 @@ "SecureEntityStorage", "PresetIdCard", "SolarControlConsole", - "BatteryBarrel", "FlashExplosive", "FlashProjectile", "Utensil", @@ -162,6 +158,11 @@ "GasVapor", "MobStateManager", "Metabolism", + "AiFactionTag", + "PressureProtection", + "DebugPump", + "DebugVent", + "DebugSiphon", }; } } 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/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 d3f756a0c8..3059eb9529 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,25 +16,25 @@ 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 { 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; @@ -211,12 +209,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 +233,7 @@ namespace Content.Client.State _ => "", }; } - _lobby.PlayerReadyList.AddItem(readyState, null, false); + _lobby.OnlinePlayerList.AddItem(session.Name, readyState); } } 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/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/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..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; } @@ -74,13 +75,12 @@ 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 SuspicionContainer { get; private set; } public Control InventoryQuickButtonContainer { get; private set; } public bool CombatPanelVisible @@ -244,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/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/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(); + } + } } 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.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/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.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.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.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/DummyGameTicker.cs b/Content.IntegrationTests/DummyGameTicker.cs index afe44ed8ff..e6b7e2a3ef 100644 --- a/Content.IntegrationTests/DummyGameTicker.cs +++ b/Content.IntegrationTests/DummyGameTicker.cs @@ -2,14 +2,15 @@ using System; using System.Collections.Generic; using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; -using Content.Shared; +using Content.Shared.Roles; using Robust.Server.Interfaces.Player; +using Robust.Shared.Interfaces.GameObjects; 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 +26,6 @@ namespace Content.IntegrationTests remove { } } - public void Initialize() - { - } - public void Update(FrameEventArgs frameEventArgs) { } @@ -64,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.IntegrationTests/Tests/Atmos/ConstantsTest.cs b/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs index eac4460b8c..6610e1d59f 100644 --- a/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs @@ -1,7 +1,10 @@ using System; using System.Linq; +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 { @@ -10,13 +13,17 @@ namespace Content.IntegrationTests.Tests.Atmos public class ConstantsTest : ContentIntegrationTest { [Test] - public void TotalGasesTest() + public async Task TotalGasesTest() { var server = StartServerDummyTicker(); + await server.WaitIdleAsync(); + server.Post(() => { - Assert.That(Atmospherics.Gases.Count(), Is.EqualTo(Atmospherics.TotalNumberOfGases)); + 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.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.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.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.IntegrationTests/Tests/Doors/AirlockTest.cs b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs new file mode 100644 index 0000000000..939ebce941 --- /dev/null +++ b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs @@ -0,0 +1,138 @@ +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Doors; +using NUnit.Framework; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.Map; +using Robust.Shared.Physics; +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.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 639a2f78b6..ae458d078b 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading.Tasks; using NUnit.Framework; using Robust.Server.Interfaces.Maps; @@ -11,6 +13,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 +33,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(() => @@ -57,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); } }); @@ -101,5 +97,212 @@ 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() + { + 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(() => + { + 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}"); + + 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.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.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.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(); + } + } +} 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/AI/Operators/Inventory/OpenStorageOperator.cs b/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs index 07eeb3e911..f909842264 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; @@ -34,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 6b5b438663..d6631a41d7 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)) @@ -32,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/UseItemInHandsOperator.cs b/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs similarity index 75% rename from Content.Server/AI/Operators/Inventory/UseItemInHandsOperator.cs rename to Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs index 130b139ad0..1be054733d 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,18 +21,13 @@ 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)) + 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/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..7388ee13b4 --- /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; + } + } +} diff --git a/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs b/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs new file mode 100644 index 0000000000..27f74d92c4 --- /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; + } + } +} 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/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/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/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/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/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/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 f886acad84..ddd834eceb 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); @@ -122,15 +129,21 @@ namespace Content.Server.Atmos [ViewVariables] public float Volume { get; set; } - public GasMixture() + public GasMixture() : this(null) { } - 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 = atmosphereSystem ?? EntitySystem.Get(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -215,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++) { @@ -243,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; } @@ -274,7 +287,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; @@ -476,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) @@ -499,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; } @@ -579,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/GasSprayerComponent.cs b/Content.Server/Atmos/GasSprayerComponent.cs index 4b78d413a9..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; @@ -20,10 +19,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/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 e16d630708..56678a75a1 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; using Robust.Shared.Maths; @@ -12,11 +14,6 @@ namespace Content.Server.Atmos /// int UpdateCounter { get; } - /// - /// How many tiles have high pressure delta. - /// - int HighPressureDeltaCount { get; } - /// /// Control variable for equalization. /// @@ -120,14 +117,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. @@ -158,5 +155,13 @@ namespace Content.Server.Atmos Dictionary GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false); 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/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 b69feedf4f..8091830976 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; @@ -1101,21 +1111,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() @@ -1130,13 +1149,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/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/Chat/ChatManager.cs b/Content.Server/Chat/ChatManager.cs index a6e0296b04..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,14 +35,15 @@ 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 + //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!; + [Dependency] private readonly ILocalizationManager _localizationManager = default!; + [Dependency] private readonly IMoMMILink _mommiLink = default!; + [Dependency] private readonly IConGroupController _conGroupController = default!; public void Initialize() { @@ -51,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) @@ -98,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); @@ -217,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/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/Components/Access/IdCardConsoleComponent.cs b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs index 841b4dd176..1183e461b2 100644 --- a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs @@ -1,8 +1,10 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; 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; @@ -15,6 +17,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 +25,13 @@ 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 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.GetUIOrNull(IdCardConsoleUiKey.Key); public override void Initialize() { @@ -40,16 +40,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 +86,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,9 +130,9 @@ 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.")); + _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("You have no hands.")); return; } @@ -133,9 +153,15 @@ namespace Content.Server.GameObjects.Components.Access { return; } - if(!hands.Drop(hands.ActiveHand, container)) + + if (hands.ActiveHand == null) { - _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("You can't let go of the ID card!")); + return; + } + + if (!hands.Drop(hands.ActiveHand, container)) + { + _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("You can't let go of the ID card!")); return; } UpdateUserInterface(); @@ -185,17 +211,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/ActionBlocking/CuffableComponent.cs b/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs new file mode 100644 index 0000000000..3325fc2caa --- /dev/null +++ b/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs @@ -0,0 +1,355 @@ + +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 Content.Server.GameObjects.Components.GUI; + +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 float _interactRange; + private IHandsComponent _hands; + + public event Action OnCuffedStateChanged; + + public override void Initialize() + { + base.Initialize(); + + _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!"); + } + } + + 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(); + } + + /// + /// 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() + { + var dirty = false; + var handCount = _hands.Hands.Count(); + + while (CuffedHandCount > handCount && CuffedHandCount > 0) + { + dirty = true; + + var entity = _container.ContainedEntities[_container.ContainedEntities.Count - 1]; + _container.Remove(entity); + entity.Transform.WorldPosition = Owner.Transform.GridPosition.Position; + } + + if (dirty) + { + CanStillInteract = handCount > CuffedHandCount; + OnCuffedStateChanged.Invoke(); + Dirty(); + } + } + + 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. + /// + 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, Loc.GetString("You can't do that!")); + return; + } + + if (!isOwner && + !EntitySystem.Get().InRangeUnobstructed( + user.Transform.MapPosition, + Owner.Transform.MapPosition, + _interactRange, + ignoredEnt: Owner)) + { + user.PopupMessage(user, Loc.GetString("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, Loc.GetString("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 {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 + { + _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..ca05cf37dc --- /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:theName}.", target)); + _notifyManager.PopupMessage(target, target, Loc.GetString("You have been cuffed by {0:theName}!", user)); + + 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:theName}!", target)); + target.PopupMessage(target, Loc.GetString("You interrupt {0:theName} while they are cuffing you!", user)); + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs b/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs index 0d71d0717d..3703eff58d 100644 --- a/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs @@ -1,9 +1,12 @@ -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.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -13,7 +16,8 @@ namespace Content.Server.GameObjects.Components.Atmos [RegisterComponent] public class AirtightComponent : Component, IMapInit { - private SnapGridComponent _snapGrid; + [Dependency] private readonly IMapManager _mapManager = default!; + private (GridId, MapIndices) _lastPosition; public override string Name => "Airtight"; @@ -28,7 +32,11 @@ namespace Content.Server.GameObjects.Components.Atmos set { _airBlocked = value; - EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)?.Revalidate(_snapGrid.Position); + + if (Owner.TryGetComponent(out SnapGridComponent? snapGrid)) + { + EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)?.Invalidate(snapGrid.Position); + } } } @@ -48,19 +56,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()} didn'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 +82,16 @@ 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) + { + var mapIndices = Owner.Transform.GridPosition.ToMapIndices(_mapManager); + EntitySystem.Get().GetGridAtmosphere(Owner.Transform.GridID)?.FixVacuum(mapIndices); + } UpdatePosition(); } @@ -83,15 +100,22 @@ 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() + { + var mapIndices = Owner.Transform.GridPosition.ToMapIndices(_mapManager); + UpdatePosition(Owner.Transform.GridID, mapIndices); + } 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..9c4c0c44d9 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; @@ -16,30 +17,32 @@ 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.GetUIOrNull(GasAnalyzerUiKey.Key); + 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 +59,7 @@ namespace Content.Server.GameObjects.Components.Atmos { _checkPlayer = true; _position = null; - _userInterface.Open(session); + UserInterface?.Open(session); UpdateUserInterface(); Resync(); } @@ -71,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Atmos { _checkPlayer = false; _position = pos; - _userInterface.Open(session); + UserInterface?.Open(session); UpdateUserInterface(); Resync(); } @@ -79,7 +82,7 @@ namespace Content.Server.GameObjects.Components.Atmos public void CloseInterface(IPlayerSession session) { _position = null; - _userInterface.Close(session); + UserInterface?.Close(session); Resync(); } @@ -123,10 +126,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; @@ -151,12 +159,13 @@ 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) { error = "No Atmosphere!"; - _userInterface.SetState( + UserInterface.SetState( new GasAnalyzerBoundUserInterfaceState( 0, 0, @@ -166,16 +175,17 @@ 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); + var gas = atmosSystem.GetGas(i); if (tile.Gases[i] <= Atmospherics.GasMinMoles) continue; 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 55% rename from Content.Server/GameObjects/Components/Atmos/GasMixtureComponent.cs rename to Content.Server/GameObjects/Components/Atmos/GasMixtureHolderComponent.cs index c60ad08efa..5f2efb662a 100644 --- a/Content.Server/GameObjects/Components/Atmos/GasMixtureComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GasMixtureHolderComponent.cs @@ -6,16 +6,23 @@ 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(); + [ViewVariables] public GasMixture GasMixture { get; set; } public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - serializer.DataField(this, x => GasMixture.Volume, "volume", 0f); + + GasMixture = new GasMixture(); + + 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 9948bdba90..fd56e9d34d 100644 --- a/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs @@ -1,9 +1,12 @@ -using System; +#nullable enable +using System; using System.Collections; 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; @@ -33,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. @@ -47,32 +50,94 @@ 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; - private IMapGrid _grid; + + [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 int HighPressureDeltaCount => _highPressureDelta.Count; + + [ViewVariables] + private double _highPressureDeltaLastProcess; + + [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; @@ -84,47 +149,47 @@ namespace Content.Server.GameObjects.Components.Atmos HighPressureDelta, Hotspots, Superconductivity, + PipeNet, + PipeNetDevices, } /// 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,69 +210,66 @@ namespace Content.Server.GameObjects.Components.Atmos private void Revalidate() { + if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; + foreach (var indices in _invalidatedCoords.ToArray()) { - Revalidate(indices); + var tile = GetTile(indices); + AddActiveTile(tile); + + if (tile == null) + { + tile = new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}); + _tiles[indices] = tile; + } + + if (IsSpace(indices)) + { + tile.Air = new GasMixture(GetVolumeForCells(1)); + tile.Air.MarkImmutable(); + _tiles[indices] = tile; + + } else if (IsAirBlocked(indices)) + { + tile.Air = null; + } + else + { + var obs = GetObstructingComponent(indices); + + if (obs != null) + { + if (tile.Air == null && obs.FixVacuum) + { + FixVacuum(tile.GridIndices); + } + } + + tile.Air ??= new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}; + } + + tile.UpdateAdjacent(); + tile.UpdateVisuals(); + + for (var i = 0; i < Atmospherics.Directions; i++) + { + var direction = (AtmosDirection) (1 << i); + var otherIndices = indices.Offset(direction.ToDirection()); + var otherTile = GetTile(otherIndices); + AddActiveTile(otherTile); + otherTile?.UpdateAdjacent(direction.GetOpposite()); + } } _invalidatedCoords.Clear(); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Revalidate(MapIndices indices) - { - var tile = GetTile(indices); - AddActiveTile(tile); - - if (tile == null) - { - tile = new TileAtmosphere(this, _grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}); - _tiles[indices] = tile; - } - - if (IsSpace(indices)) - { - tile.Air = new GasMixture(GetVolumeForCells(1)); - tile.Air.MarkImmutable(); - _tiles[indices] = tile; - - } else if (IsAirBlocked(indices)) - { - tile.Air = null; - } - else - { - var obs = GetObstructingComponent(indices); - - if (obs != null) - { - if (tile.Air == null && obs.FixVacuum) - { - FixVacuum(tile.GridIndices); - } - } - - tile.Air ??= new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}; - } - - tile.UpdateAdjacent(); - tile.UpdateVisuals(); - - foreach (var direction in Cardinal) - { - var otherIndices = indices.Offset(direction); - var otherTile = GetTile(otherIndices); - AddActiveTile(otherTile); - otherTile?.UpdateAdjacent(direction.GetOpposite()); - } - } - /// 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; @@ -224,16 +286,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); @@ -243,27 +306,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); @@ -271,9 +336,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); } @@ -298,23 +364,43 @@ namespace Content.Server.GameObjects.Components.Atmos _excitedGroups.Remove(excitedGroup); } - /// - public TileAtmosphere GetTile(GridCoordinates coordinates) + public void AddPipeNet(IPipeNet pipeNet) { - return GetTile(coordinates.ToMapIndices(_mapManager)); + _pipeNets.Add(pipeNet); + } + + public void RemovePipeNet(IPipeNet pipeNet) + { + _pipeNets.Remove(pipeNet); + } + + public void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice) + { + _pipeNetDevices.Add(pipeNetDevice); + } + + public void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice) + { + _pipeNetDevices.Remove(pipeNetDevice); } /// - public TileAtmosphere GetTile(MapIndices indices) + public TileAtmosphere? GetTile(GridCoordinates coordinates, bool createSpace = true) { + return GetTile(coordinates.ToMapIndices(_mapManager), createSpace); + } + + /// + 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, _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; @@ -331,32 +417,34 @@ 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) + 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?.Air != null || includeAirBlocked) - sides[dir] = tile; + if (tile != null && (tile.Air != null || includeAirBlocked)) + sides[direction] = tile; } return sides; } - /// - public int HighPressureDeltaCount => _highPressureDelta.Count; - public long EqualizationQueueCycleControl { get; set; } /// 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; } /// @@ -376,27 +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: + if (!ProcessPipeNets(_paused)) + { + _paused = true; + return; + } + + _paused = false; + _state = ProcessState.PipeNetDevices; + break; + case ProcessState.PipeNetDevices: + if (!ProcessPipeNetDevices(_paused)) + { + _paused = true; + return; + } + + _paused = false; _state = ProcessState.TileEqualize; break; } @@ -404,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++; @@ -458,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; @@ -478,47 +656,129 @@ 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 AirtightComponent GetObstructingComponent(MapIndices indices) + private bool ProcessPipeNets(bool resumed = false) { - foreach (var v in _grid.GetSnapGridCell(indices, SnapGridOffset.Center)) + _stopwatch.Restart(); + + if(!resumed) + _currentRunPipeNet = new Queue(_pipeNets); + + var number = 0; + while (_currentRunPipeNet.Count > 0) + { + var pipenet = _currentRunPipeNet.Dequeue(); + pipenet.Update(); + + if (number++ < LagCheckIterations) continue; + number = 0; + // Process the rest next time. + if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) + { + _pipeNetLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return false; + } + } + + _pipeNetLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return true; + } + + private bool ProcessPipeNetDevices(bool resumed = false) + { + _stopwatch.Restart(); + + if(!resumed) + _currentRunPipeNetDevice = new Queue(_pipeNetDevices); + + var number = 0; + while (_currentRunPipeNet.Count > 0) + { + var device = _currentRunPipeNetDevice.Dequeue(); + device.Update(); + + if (number++ < LagCheckIterations) continue; + number = 0; + // Process the rest next time. + if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds) + { + _pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return false; + } + } + + _pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds; + return true; + } + + private AirtightComponent? GetObstructingComponent(MapIndices indices) + { + 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; @@ -527,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() { @@ -541,22 +795,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/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/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/BodyManagerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs index 5c14e4da0c..3c65da2fd1 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; @@ -43,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!; @@ -138,11 +134,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/Body/BodyScannerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs index 2c49665757..dfc1cb5198 100644 --- a/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs +++ b/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs @@ -1,10 +1,14 @@ -using System.Collections.Generic; +#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; 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 +16,39 @@ 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.GetUIOrNull(BodyScannerUiKey.Key); + 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/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/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..f7fb29c6e7 100644 --- a/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs +++ b/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs @@ -1,11 +1,14 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; 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; +using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; @@ -21,20 +24,18 @@ 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.GetUIOrNull(GenericSurgeryUiKey.Key); void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { @@ -48,7 +49,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 +59,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 +70,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 +93,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 +131,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 +151,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..939222ed2e 100644 --- a/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs +++ b/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs @@ -1,21 +1,23 @@ -using System; +#nullable enable 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; 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 +28,22 @@ 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.GetUIOrNull(GenericSurgeryUiKey.Key); void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { @@ -63,12 +63,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 +77,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 +88,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 +107,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 +151,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 +172,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/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, diff --git a/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs b/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs index 7f4ece43a0..0c937b5a12 100644 --- a/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs +++ b/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs @@ -1,8 +1,10 @@ -using System; +#nullable enable +using System; 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; @@ -18,6 +20,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 +34,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 +43,17 @@ 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.GetUIOrNull(GenericSurgeryUiKey.Key); void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { @@ -60,7 +62,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 +75,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 +107,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 +140,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 +158,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 +208,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 +238,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 +267,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..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; @@ -10,6 +11,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 +21,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 +57,44 @@ 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.GetUIOrNull(CargoConsoleUiKey.Key); + 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 +107,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,45 +126,45 @@ 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) + PrototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product); + 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 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++) { @@ -166,12 +185,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 +199,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/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/ChemMasterComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs index 156b9efefb..c3a7dc4764 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; @@ -7,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; @@ -40,24 +43,20 @@ 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!; - [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.GetUIOrNull(ChemMasterUiKey.Key); /// /// Shows the serializer how to save/load this components yaml prototype. @@ -77,14 +76,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 +97,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 +109,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 +133,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 +156,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 +181,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 +216,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 +236,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } BufferSolution.Solution.RemoveReagent(id, actualAmount); - if (BufferModeTransfer) + if (_bufferModeTransfer) { beakerSolution.Solution.AddReagent(id, actualAmount); } @@ -351,22 +360,22 @@ 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.")); + Loc.GetString("You have no hands.")); return; } var activeHandEntity = hands.GetActiveHand?.Owner; if (activeHandEntity == null) { - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } } @@ -379,26 +388,33 @@ 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.")); + Loc.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)) { 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 { @@ -409,7 +425,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/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/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 e11d7d7438..1a507f2bcb 100644 --- a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs @@ -19,10 +19,7 @@ namespace Content.Server.GameObjects.Components.Chemistry [RegisterComponent] class PourableComponent : Component, IInteractUsing { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; public override string Name => "Pourable"; @@ -91,7 +88,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 +98,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 d32ae288ad..3ec235caf7 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; @@ -7,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; @@ -38,14 +40,10 @@ 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!; - [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,9 @@ 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.GetUIOrNull(ReagentDispenserUiKey.Key); /// /// Shows the serializer how to save/load this components yaml prototype. @@ -75,14 +73,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 +111,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } } - private void OnPowerChanged(object sender, PowerStateEventArgs e) + private void OnPowerChanged(object? sender, PowerStateEventArgs e) { UpdateUserInterface(); } @@ -120,6 +123,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 +183,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 +219,7 @@ namespace Content.Server.GameObjects.Components.Chemistry private void UpdateUserInterface() { var state = GetUserInterfaceState(); - _userInterface.SetState(state); + UserInterface?.SetState(state); } /// @@ -265,22 +273,22 @@ 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.")); + Loc.GetString("You have no hands.")); return; } var activeHandEntity = hands.GetActiveHand?.Owner; if (activeHandEntity == null) { - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } } @@ -293,26 +301,33 @@ 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.")); + Loc.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)) { 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 { @@ -323,7 +338,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..241d103dc9 100644 --- a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs @@ -29,11 +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 ILocalizationManager _loc; - [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; @@ -276,7 +273,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 +300,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/Chemistry/TransformableContainerComponent.cs b/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs index 94d18c734e..24f4879d55 100644 --- a/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs @@ -1,8 +1,10 @@ -using Content.Server.GameObjects.EntitySystems; +#nullable enable +using Content.Server.GameObjects.EntitySystems; 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; @@ -11,28 +13,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 +41,27 @@ 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() { _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 +90,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/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/Command/CommunicationsConsoleComponent.cs b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs index d982b1fb06..09cd7c3137 100644 --- a/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs @@ -1,5 +1,7 @@ -using Content.Server.GameObjects.Components.Power.ApcNetComponents; +#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; @@ -8,6 +10,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 +18,22 @@ 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.GetUIOrNull(CommunicationsConsoleUiKey.Key); + 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 +42,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 +61,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/BreakableComponent.cs b/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs index 15ad3e8a35..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"; @@ -41,8 +39,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/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/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/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/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]; } } 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/Disposal/DisposalRouterComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs index 1d3662a85c..6cf6689e75 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; @@ -16,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 @@ -25,22 +27,19 @@ 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.GetUIOrNull(DisposalRouterUiKey.Key); + public override Direction NextDirection(DisposalHolderComponent holder) { var directions = ConnectableDirections(); @@ -53,15 +52,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 +71,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 +115,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 +139,7 @@ namespace Content.Server.GameObjects.Components.Disposal private void UpdateUserInterface() { var state = GetUserInterfaceState(); - _userInterface.SetState(state); + UserInterface?.SetState(state); } private void ClickSound() @@ -150,12 +153,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 +169,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..76d913e2cf 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs @@ -1,5 +1,7 @@ -using Content.Server.Interfaces; +#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; @@ -23,35 +25,33 @@ 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.GetUIOrNull(DisposalTaggerUiKey.Key); + 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 +70,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 +81,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 +98,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 +107,7 @@ namespace Content.Server.GameObjects.Components.Disposal private void UpdateUserInterface() { var state = GetUserInterfaceState(); - _userInterface.SetState(state); + UserInterface?.SetState(state); } private void ClickSound() @@ -121,12 +121,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 +137,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/DisposalTubeComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs index 94dccce0a7..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!; @@ -69,21 +68,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; } @@ -192,6 +181,8 @@ namespace Content.Server.GameObjects.Components.Disposal return; } + collidable.CanCollide = !collidable.Anchored; + if (collidable.Anchored) { OnAnchor(); @@ -230,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 2e420ef91e..d67721987a 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -7,8 +7,10 @@ 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; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Disposal; using Content.Shared.GameObjects.EntitySystems; @@ -29,6 +31,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,13 +39,12 @@ 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 { -#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"; @@ -67,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. @@ -81,9 +89,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 +120,15 @@ namespace Content.Server.GameObjects.Components.Disposal } } + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalUnitUiKey.Key); + + private DisposalUnitBoundUserInterfaceState? _lastUiState; + + /// + /// Store the translated state. + /// + private (PressureState State, string Localized) _locState; + public bool CanInsert(IEntity entity) { if (!Anchored) @@ -161,19 +175,40 @@ namespace Content.Server.GameObjects.Components.Disposal if (entity.TryGetComponent(out IActorComponent? actor)) { - _userInterface.Close(actor.playerSession); + UserInterface?.Close(actor.playerSession); } 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; @@ -218,9 +253,9 @@ namespace Content.Server.GameObjects.Components.Disposal { Engaged ^= true; - if (Engaged) + if (Engaged && CanFlush()) { - TryFlush(); + Timer.Spawn(_flushDelay, () => TryFlush()); } } @@ -284,17 +319,35 @@ 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(); - _userInterface.SetState(state); + + if (_lastUiState != null && _lastUiState.Equals(state)) + { + return; + } + + _lastUiState = state; + UserInterface?.SetState(state); } - private bool PlayerCanUse(IEntity player) + private bool PlayerCanUse(IEntity? player) { if (player == null) { @@ -402,8 +455,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; @@ -461,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() @@ -472,9 +532,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(); } @@ -483,10 +545,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)) { @@ -513,7 +580,7 @@ namespace Content.Server.GameObjects.Components.Disposal _container.ForceRemove(entity); } - _userInterface.CloseAll(); + UserInterface?.CloseAll(); _automaticEngageToken?.Cancel(); _automaticEngageToken = null; @@ -571,7 +638,7 @@ namespace Content.Server.GameObjects.Components.Disposal return false; } - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); return true; } @@ -587,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] @@ -609,7 +677,7 @@ namespace Content.Server.GameObjects.Components.Disposal protected override void Activate(IEntity user, DisposalUnitComponent component) { - component.TryInsert(user); + _ = component.TryInsert(user, user); } } diff --git a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs index 398e9230c9..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,26 +136,45 @@ 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); } } - protected override DoorState State + public override DoorState State { - set + protected set { 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,25 +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() { - _powerReceiver.OnPowerStateChanged -= PowerDeviceOnOnPowerStateChanged; + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) + { + 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); } @@ -188,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 @@ -272,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); @@ -377,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) @@ -388,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 d2f8f7f65b..78200dc785 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.Atmos; @@ -35,10 +36,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; @@ -46,10 +47,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(); protected virtual TimeSpan CloseTimeOne => TimeSpan.FromSeconds(0.3f); protected virtual TimeSpan CloseTimeTwo => TimeSpan.FromSeconds(0.9f); @@ -72,21 +70,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; + _cancellationTokenSource?.Cancel(); base.OnRemove(); } @@ -108,7 +94,6 @@ namespace Content.Server.GameObjects.Components.Doors ActivateImpl(eventArgs); } - void ICollideBehavior.CollideWith(IEntity entity) { if (State != DoorState.Closed) @@ -139,8 +124,10 @@ namespace Content.Server.GameObjects.Components.Doors protected 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() @@ -151,28 +138,53 @@ namespace Content.Server.GameObjects.Components.Doors public virtual 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)); } } @@ -185,15 +197,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); @@ -212,7 +231,7 @@ namespace Content.Server.GameObjects.Components.Doors public virtual bool CanClose(IEntity user) { if (!CanClose()) return false; - if (!Owner.TryGetComponent(out AccessReader accessReader)) + if (!Owner.TryGetComponent(out AccessReader? accessReader)) { return true; } @@ -233,18 +252,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); @@ -319,7 +342,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; @@ -331,7 +355,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; } @@ -343,8 +367,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); @@ -391,7 +422,7 @@ namespace Content.Server.GameObjects.Components.Doors } } - protected enum DoorState + public enum DoorState { Closed, Open, diff --git a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs index 11c3281a94..ee190c6461 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,25 @@ 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 - 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,16 +50,28 @@ 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; + } + + 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) + var solution = contents.SplitSolution(transferAmount); + if (!mopContents.TryAddSolution(solution) || mopComponent.CurrentVolume == 0) { return false; } @@ -73,7 +88,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; } @@ -86,7 +106,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; } @@ -96,15 +116,22 @@ namespace Content.Server.GameObjects.Components.Fluids return false; } - var solution = mopComponent.Contents.SplitSolution(transferAmount); - if (!_contents.TryAddSolution(solution)) + var mopContents = mopComponent.Contents; + + if (mopContents == null) + { + return false; + } + + var solution = mopContents.SplitSolution(transferAmount); + if (!contents.TryAddSolution(solution)) { //This really shouldn't happen throw new InvalidOperationException(); } // 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) { @@ -114,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 b24b529b43..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; @@ -6,8 +7,8 @@ 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.Log; using Robust.Shared.Serialization; namespace Content.Server.GameObjects.Components.Fluids @@ -18,21 +19,24 @@ 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; + + 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 @@ -41,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) @@ -54,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) @@ -71,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; } @@ -107,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, _localizationManager.GetString("Swish")); + 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 598ee20754..8d28737c05 100644 --- a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs @@ -46,10 +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; - [Dependency] private readonly ILocalizationManager _loc; -#pragma warning restore 649 + [Dependency] private readonly IMapManager _mapManager = default!; public override string Name => "Puddle"; @@ -116,6 +113,7 @@ namespace Content.Server.GameObjects.Components.Fluids public override void Initialize() { base.Initialize(); + if (Owner.TryGetComponent(out SolutionComponent solutionComponent)) { _contents = solutionComponent; @@ -123,21 +121,27 @@ namespace Content.Server.GameObjects.Components.Fluids else { _contents = Owner.AddComponent(); - _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(); @@ -153,7 +157,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/Fluids/SprayComponent.cs b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs index ffb9cbdaa0..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; @@ -16,10 +17,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; @@ -46,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) @@ -75,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/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs index 69fe0be734..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 { @@ -37,9 +38,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; @@ -446,6 +445,7 @@ namespace Content.Server.GameObjects.Components.GUI ActiveHand ??= name; OnItemChanged?.Invoke(); + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner)); Dirty(); } @@ -468,6 +468,7 @@ namespace Content.Server.GameObjects.Components.GUI } OnItemChanged?.Invoke(); + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner)); Dirty(); } @@ -793,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/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/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/GUI/StrippableComponent.cs b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs index 0363fee424..fc54344448 100644 --- a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs @@ -1,12 +1,15 @@ -using System; +#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; +using Content.Server.Utility; using Content.Shared.GameObjects.Components.GUI; -using Content.Shared.GameObjects.Components.Inventory; 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; @@ -16,7 +19,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 +27,39 @@ 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; + [ViewVariables] + private BoundUserInterface? UserInterface => Owner.GetUIOrNull(StrippingUiKey.Key); 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(); + Owner.EnsureComponent(); + + if (Owner.TryGetComponent(out CuffableComponent? cuffed)) + { + cuffed.OnCuffedStateChanged += UpdateSubscribed; + } + if (Owner.TryGetComponent(out InventoryComponent? inventory)) + { + inventory.OnItemChanged += UpdateSubscribed; + } - _inventoryComponent.OnItemChanged += UpdateSubscribed; + if (Owner.TryGetComponent(out HandsComponent? hands)) + { + hands.OnItemChanged += UpdateSubscribed; + } // Initial update. UpdateSubscribed(); @@ -53,33 +67,69 @@ namespace Content.Server.GameObjects.Components.GUI private void UpdateSubscribed() { + if (UserInterface == null) + { + return; + } + var inventory = GetInventorySlots(); var hands = GetHandSlots(); + var cuffs = GetHandcuffs(); - _userInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands)); + 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) { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) return false; + if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false; OpenUserInterface(actor.playerSession); 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(); - 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 +139,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 +154,7 @@ namespace Content.Server.GameObjects.Components.GUI private void OpenUserInterface(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } /// @@ -336,34 +391,80 @@ 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; 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; + } + } + + [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); } } } diff --git a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs index 55e6acfe92..3a766f985a 100644 --- a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs +++ b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs @@ -1,9 +1,10 @@ -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.Server.Utility; using Content.Shared.GameObjects.Components.Gravity; using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.GameObjects.EntitySystems; @@ -16,17 +17,13 @@ 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 { [RegisterComponent] public class GravityGeneratorComponent : SharedGravityGeneratorComponent, IInteractUsing, IBreakAct, IInteractHand { - private BoundUserInterface _userInterface; - - private PowerReceiverComponent _powerReceiver; - - private SpriteComponent _sprite; private bool _switchedOn; @@ -34,7 +31,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 +61,17 @@ namespace Content.Server.GameObjects.Components.Gravity public override string Name => "GravityGenerator"; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GravityGeneratorUiKey.Key); + 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 +100,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 +149,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 +162,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..931b95288a 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -1,8 +1,10 @@ -using System; +#nullable enable +using System; 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; @@ -34,12 +36,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 +45,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 +70,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 +90,7 @@ namespace Content.Server.GameObjects.Components.Instruments } } - public IPlayerSession InstrumentPlayer + public IPlayerSession? InstrumentPlayer { get => _instrumentPlayer; private set @@ -108,11 +103,13 @@ 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.GetUIOrNull(InstrumentUiKey.Key); + + private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) { if (e.Session != _instrumentPlayer || e.NewStatus != SessionStatus.Disconnected) return; InstrumentPlayer = null; @@ -122,8 +119,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 +137,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 +231,7 @@ namespace Content.Server.GameObjects.Components.Instruments Clean(); SendNetworkMessage(new InstrumentStopMidiMessage()); InstrumentPlayer = null; - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } public void Thrown(ThrownEventArgs eventArgs) @@ -239,7 +239,7 @@ namespace Content.Server.GameObjects.Components.Instruments Clean(); SendNetworkMessage(new InstrumentStopMidiMessage()); InstrumentPlayer = null; - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } public void HandSelected(HandSelectedEventArgs eventArgs) @@ -255,12 +255,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 +270,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 +291,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 +302,7 @@ namespace Content.Server.GameObjects.Components.Instruments { InstrumentPlayer = null; Clean(); - _userInterface.CloseAll(); + UserInterface?.CloseAll(); } if ((_batchesDropped >= MaxMidiBatchDropped @@ -314,9 +314,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..b2daa8e37c 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; @@ -27,28 +28,26 @@ 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 { -#pragma warning disable 649 - [Dependency] private readonly ISharedNotifyManager _notifyManager; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly ISharedNotifyManager _notifyManager = 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; + if (_cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent? cell)) + { + return cell; + } - _cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent cell); - return cell; + return null; } } @@ -58,6 +57,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; @@ -81,11 +82,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].")); } } @@ -98,11 +97,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(); } @@ -140,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) @@ -153,10 +150,9 @@ namespace Content.Server.GameObjects.Components.Interactable var cell = Cell; if (cell == null) { - 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; } @@ -166,7 +162,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; } @@ -174,25 +170,46 @@ namespace Content.Server.GameObjects.Components.Interactable SetState(true); EntitySystem.Get().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner); - } 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"; } } 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(); } @@ -211,7 +228,9 @@ namespace Content.Server.GameObjects.Components.Interactable return; } - if (!user.TryGetComponent(out HandsComponent hands)) + Dirty(); + + if (!user.TryGetComponent(out HandsComponent? hands)) { return; } @@ -222,24 +241,23 @@ namespace Content.Server.GameObjects.Components.Interactable } EntitySystem.Get().PlayFromEntity("/Audio/Items/pistol_magout.ogg", Owner); - } public override ComponentState GetComponentState() { 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.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/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/Clothing/ClothingComponent.cs b/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs index 7de3236ec9..c8fe5b8b78 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; @@ -63,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/Items/DiceComponent.cs b/Content.Server/GameObjects/Components/Items/DiceComponent.cs index 227c0e3c4e..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"; @@ -85,9 +83,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/Items/FloorTileItemComponent.cs b/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs index f15948fc22..8321016565 100644 --- a/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs +++ b/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs @@ -15,16 +15,12 @@ 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 StackComponent _stack; private string _outputTile; - public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); @@ -34,18 +30,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/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/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); + } + } + } } } 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/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/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/Kitchen/MicrowaveComponent.cs b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs index 470fb19ec6..21ef4a521a 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; @@ -14,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; @@ -39,25 +41,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 +63,18 @@ 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.GetUIOrNull(MicrowaveUiKey.Key); public override void ExposeData(ObjectSerializer serializer) { @@ -95,22 +89,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 +151,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 +167,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 +183,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 +225,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 +241,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 +278,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 +306,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 +333,7 @@ namespace Content.Server.GameObjects.Components.Kitchen { if (goodMeal) { - SubtractContents(recipeToCook); + SubtractContents(recipeToCook!); } else { @@ -357,12 +360,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 +404,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 +420,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 +439,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..48aaadd613 100644 --- a/Content.Server/GameObjects/Components/MagicMirrorComponent.cs +++ b/Content.Server/GameObjects/Components/MagicMirrorComponent.cs @@ -1,4 +1,6 @@ -using Content.Server.GameObjects.Components.Mobs; +#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; @@ -8,6 +10,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 +18,36 @@ namespace Content.Server.GameObjects.Components [ComponentReference(typeof(IActivate))] public class MagicMirrorComponent : SharedMagicMirrorComponent, IActivate { - private BoundUserInterface _userInterface; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(MagicMirrorUiKey.Key); 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 +90,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/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/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/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs index d50513e374..cb2a11fb40 100644 --- a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs +++ b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs @@ -1,7 +1,9 @@ -using System; +#nullable enable +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; @@ -16,6 +18,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 +26,29 @@ 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.GetUIOrNull(MedicalScannerUiKey.Key); 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 +65,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 +93,7 @@ namespace Content.Server.GameObjects.Components.Medical } var newState = GetUserInterfaceState(); - _userInterface.SetState(newState); + UserInterface?.SetState(newState); } private MedicalScannerStatus GetStatusFromDamageState(DamageState damageState) @@ -115,12 +122,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 +138,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/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 08a4bffd48..eb289fdc40 100644 --- a/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs +++ b/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs @@ -17,17 +17,16 @@ 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(); - var spriteComponent = Owner.GetComponent(); + + var spriteComponent = Owner.EnsureComponent(); spriteComponent.LayerSetState(0, _random.Pick(SpriteStates)); } diff --git a/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs b/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs index acb501a1ea..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); } @@ -187,7 +193,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 +207,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"); @@ -281,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, @@ -382,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.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/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/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/GameObjects/Components/Movement/AiControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs index ac635153e2..a1d595062d 100644 --- a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs @@ -1,11 +1,16 @@ -using Content.Server.GameObjects.EntitySystems.AI; +#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; @@ -14,23 +19,26 @@ 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 string? StartingGearPrototype { get; set; } [ViewVariables(VVAccess.ReadWrite)] public float VisionRadius @@ -45,18 +53,34 @@ 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); } + 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); } @@ -74,7 +98,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 +115,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..f04b50cff2 100644 --- a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs @@ -1,21 +1,21 @@ - +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; namespace Content.Server.GameObjects.Components.Movement @@ -24,10 +24,7 @@ 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. @@ -41,14 +38,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(); } @@ -62,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; } @@ -175,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)); } } @@ -211,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)); } } @@ -231,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); + } + } } } 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/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 7c8963544c..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,21 +18,19 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups /// /// Returns a new instance. /// - INodeGroup MakeNodeGroup(NodeGroupID nodeGroupType); + INodeGroup MakeNodeGroup(Node sourceNode); } 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() { - var nodeGroupTypes = _reflectionManager.GetAllChildren(); + var nodeGroupTypes = _reflectionManager.GetAllChildren(); foreach (var nodeGroupType in nodeGroupTypes) { var att = nodeGroupType.GetCustomAttribute(); @@ -45,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)}."); } } @@ -61,5 +62,6 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups HVPower, MVPower, Apc, + Pipe, } } 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/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/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs index a52ebc1d48..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] @@ -44,7 +43,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/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/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/PDA/PDAComponent.cs b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs index 1aa9f2f776..bf85b1274c 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; @@ -37,21 +38,19 @@ 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.GetUIOrNull(PDAUiKey.Key); + public PDAComponent() { _accessSet = new PdaAccessSet(this); @@ -67,11 +66,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); @@ -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,12 +128,12 @@ 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)); + var listings = _uplinkManager.FetchListings.Values.ToArray(); + UserInterface?.SetState(new PDAUpdateState(_lightOn, ownerInfo, accData, listings)); } else { - _interface.SetState(new PDAUpdateState(_lightOn, ownerInfo)); + UserInterface?.SetState(new PDAUpdateState(_lightOn, ownerInfo)); } UpdatePDAAppearance(); @@ -141,7 +141,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 +172,7 @@ namespace Content.Server.GameObjects.Components.PDA return; } - _interface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); UpdatePDAAppearance(); } @@ -180,7 +183,7 @@ namespace Content.Server.GameObjects.Components.PDA return false; } - _interface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); UpdatePDAAppearance(); return true; } @@ -217,8 +220,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..ecae29f0a7 100644 --- a/Content.Server/GameObjects/Components/Paper/PaperComponent.cs +++ b/Content.Server/GameObjects/Components/Paper/PaperComponent.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +#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; @@ -7,30 +9,33 @@ 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 { [RegisterComponent] public class PaperComponent : SharedPaperComponent, IExamine, IInteractUsing, IUse { - - private BoundUserInterface _userInterface; - private string _content; + private string _content = ""; private PaperAction _mode; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(PaperUiKey.Key); + 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 +48,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 +65,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 +77,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..fdb9d14133 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs @@ -1,6 +1,8 @@ -using System; +#nullable enable +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; @@ -20,17 +22,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 +36,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents private TimeSpan _lastExternalPowerStateChange; - private float _lastCharge = 0f; + private float _lastCharge; private TimeSpan _lastChargeChange; @@ -49,17 +46,22 @@ 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.GetUIOrNull(ApcUiKey.Key); + + 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 +92,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 +116,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 +155,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 +176,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..e0f0f436ed 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; @@ -12,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; @@ -24,19 +24,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 +48,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(); } @@ -71,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; @@ -97,12 +100,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 +113,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 +128,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 +146,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 +189,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 +231,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 +277,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/EmergencyLightComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs new file mode 100644 index 0000000000..58a53cf253 --- /dev/null +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +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.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(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 (!Owner.TryGetComponent(out PowerReceiverComponent receiver)) + { + return; + } + + if (receiver.Powered) + { + receiver.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 (!Owner.TryGetComponent(out BatteryComponent battery)) + { + 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) + { + if (Owner.TryGetComponent(out PowerReceiverComponent receiver)) + { + receiver.Load = 1; + } + + _lightState = EmergencyLightState.Full; + } + } + } + + private void TurnOff() + { + if (Owner.TryGetComponent(out SpriteComponent sprite)) + { + sprite.LayerSetState(0, "emergency_light_off"); + } + + if (Owner.TryGetComponent(out PointLightComponent light)) + { + light.Enabled = false; + } + } + + private void TurnOn() + { + 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(); + + 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() + { + if (Owner.TryGetComponent(out PowerReceiverComponent receiver)) + { + receiver.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/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs index b33be166af..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. @@ -108,7 +105,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 5f6edb59e6..71befe71bc 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); @@ -221,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/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.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..6eefdc32af 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarControlConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarControlConsoleComponent.cs @@ -1,5 +1,7 @@ -using Content.Server.GameObjects.Components.Power.ApcNetComponents; +#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; @@ -7,6 +9,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 +17,29 @@ 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.GetUIOrNull(SolarControlConsoleUiKey.Key); 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 +61,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 +73,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/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/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/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/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/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..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; @@ -28,11 +29,11 @@ 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"; + + private List _intersecting = new List(); /// /// Whether or not sentient beings will be recycled @@ -89,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)) { @@ -168,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; } diff --git a/Content.Server/GameObjects/Components/Research/LatheComponent.cs b/Content.Server/GameObjects/Components/Research/LatheComponent.cs index 55b2ddbbcb..be55677be9 100644 --- a/Content.Server/GameObjects/Components/Research/LatheComponent.cs +++ b/Content.Server/GameObjects/Components/Research/LatheComponent.cs @@ -1,9 +1,11 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; 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; @@ -25,15 +27,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 +41,21 @@ 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.GetUIOrNull(LatheUiKey.Key); + 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) @@ -65,29 +66,29 @@ namespace Content.Server.GameObjects.Components.Research switch (message.Message) { case LatheQueueRecipeMessage msg: - _prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe); - if (recipe != null) + PrototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe); + 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 +104,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 +117,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 +127,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 +137,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 +154,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 +206,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..44a5069dcf 100644 --- a/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs +++ b/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs @@ -1,4 +1,6 @@ -using Content.Server.GameObjects.EntitySystems; +#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; @@ -14,20 +16,17 @@ 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 + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ResearchClientUiKey.Key); 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 +40,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 +69,7 @@ namespace Content.Server.GameObjects.Components.Research public void UpdateUserInterface() { - _userInterface?.SetState(GetNewUiState()); + UserInterface?.SetState(GetNewUiState()); } private ResearchClientBoundInterfaceState GetNewUiState() @@ -73,7 +77,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..4a1adc57c1 100644 --- a/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs @@ -1,4 +1,6 @@ -using Content.Server.GameObjects.Components.Power.ApcNetComponents; +#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; @@ -14,6 +16,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 +24,33 @@ 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.GetUIOrNull(ResearchConsoleUiKey.Key); 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 +59,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 +70,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 +86,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 +107,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 +121,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/Rotatable/RotatableComponent.cs b/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs index ffbcfbe4cc..564decbb9c 100644 --- a/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs +++ b/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs @@ -13,10 +13,8 @@ namespace Content.Server.GameObjects.Components.Rotatable [RegisterComponent] public class RotatableComponent : Component { -#pragma warning disable 649 - [Dependency] private readonly IServerNotifyManager _notifyManager; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; + public override string Name => "Rotatable"; private void TryRotate(IEntity user, Angle angle) @@ -25,7 +23,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/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 e38af7a857..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; @@ -107,8 +105,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/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/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/VendingMachines/VendingMachineComponent.cs b/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs index 6929d9f981..3fd2c7177a 100644 --- a/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs +++ b/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs @@ -1,8 +1,9 @@ -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.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 @@ -29,27 +31,24 @@ 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 = ""; + + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(VendingMachineUiKey.Key); public void Activate(ActivateEventArgs eventArgs) { - if(!eventArgs.User.TryGetComponent(out IActorComponent actor)) + if(!eventArgs.User.TryGetComponent(out IActorComponent? actor)) { return; } @@ -62,7 +61,7 @@ namespace Content.Server.GameObjects.Components.VendingMachines wires.OpenInterface(actor.playerSession); } else { - _userInterface.Open(actor.playerSession); + UserInterface?.Open(actor.playerSession); } } @@ -106,25 +105,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 +147,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 +166,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 +181,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 +210,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/Melee/FlashComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs index 9d12014cda..104e20924d 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs @@ -22,11 +22,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee [RegisterComponent] 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 + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly ISharedNotifyManager _notifyManager = default!; public override string Name => "Flash"; @@ -173,10 +170,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/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 2a3fc9df37..6ede88e76a 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs @@ -30,11 +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; - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly ISharedNotifyManager _notifyManager = default!; public override string Name => "Stunbaton"; @@ -168,14 +165,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 +239,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/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..3755370ea7 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; @@ -18,6 +19,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,12 +27,13 @@ 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 public override string Name => "BoltActionBarrel"; + public override uint? NetID => ContentNetIDs.BOLTACTION_BARREL; public override int ShotsLeft { @@ -66,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)); @@ -73,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)); @@ -81,6 +86,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels _boltOpen = value; UpdateAppearance(); + Dirty(); } } private bool _boltOpen; @@ -122,6 +128,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 @@ -169,6 +193,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { Cycle(); } + else + { + Dirty(); + } + return chamberEntity?.GetComponent().TakeBullet(spawnAtGrid, spawnAtMap); } @@ -184,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) { @@ -215,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) { @@ -255,7 +263,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; } @@ -268,7 +276,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; } @@ -284,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; } @@ -297,6 +305,53 @@ 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); + + message.AddMarkup(Loc.GetString("\nIt uses [color=white]{0}[/color] ammo.", _caliber)); + } + [Verb] private sealed class OpenBoltVerb : Verb { @@ -309,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) @@ -330,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/PumpBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs index 2139767dc0..9dd3d9c7b5 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs @@ -1,7 +1,9 @@ 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; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; @@ -15,6 +17,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,9 +25,10 @@ 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"; + public override uint? NetID => ContentNetIDs.PUMP_BARREL; public override int ShotsLeft { @@ -79,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(); @@ -108,6 +129,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } _appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true); + Dirty(); UpdateAppearance(); } @@ -129,6 +151,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { Cycle(); } + else + { + Dirty(); + } + return chamberEntity?.GetComponent().TakeBullet(spawnAtGrid, spawnAtMap); } @@ -166,7 +193,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } } - // Dirty(); + Dirty(); UpdateAppearance(); } @@ -187,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) { @@ -211,5 +238,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/RevolverBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs index 004e742b71..4f16ffc90a 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(); @@ -89,7 +112,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels _appearanceComponent = appearanceComponent; } - _appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true); + UpdateAppearance(); + 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.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.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs index ef8b35ca90..cdda71816d 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; @@ -104,21 +139,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 +152,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; @@ -169,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; @@ -217,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) { @@ -263,8 +247,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels Owner.PopupMessage(container.Owner, Loc.GetString("Bolt open")); } BoltOpen = true; - Dirty(); - UpdateAppearance(); return; } @@ -304,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; } @@ -315,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; @@ -420,8 +451,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] @@ -465,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; } } @@ -486,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; } } } @@ -511,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/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs index aca55f1921..454f165ee0 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 { @@ -39,10 +40,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; @@ -157,8 +156,14 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels public override void OnAdd() { base.OnAdd(); - var rangedWeaponComponent = Owner.GetComponent(); - rangedWeaponComponent.Barrel = this; + + 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; rangedWeaponComponent.WeaponCanFireHandler += WeaponCanFire; } diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs index 6867052599..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; @@ -33,13 +34,10 @@ 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; @@ -107,7 +105,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); + } } /// @@ -140,15 +141,22 @@ namespace Content.Server.GameObjects.Components [ViewVariables] private string? _layoutId; + [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(WiresUiKey.Key); + 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 (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(WiresVisuals.MaintenancePanelState, IsPanelOpen); + } + + if (UserInterface != null) + { + UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + } } private void GenerateSerialNumber() @@ -357,7 +365,7 @@ namespace Content.Server.GameObjects.Components /// public void OpenInterface(IPlayerSession session) { - _userInterface.Open(session); + UserInterface?.Open(session); } private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) @@ -450,7 +458,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(), @@ -485,9 +493,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.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/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs index b04aca780e..2901b72457 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,13 +66,25 @@ namespace Content.Server.GameObjects.EntitySystems.AI break; } } - + _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) @@ -87,7 +99,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/AI/Pathfinding/Accessible/AiReachableSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/Pathfinding/Accessible/AiReachableSystem.cs index 3531dcdde1..4f866d3348 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) @@ -132,6 +130,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible _regions.Clear(); _cachedAccessible.Clear(); _queuedCacheDeletions.Clear(); + _mapManager.OnGridRemoved -= GridRemoved; } public void ResettingCleanup() @@ -161,7 +160,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 +198,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 +408,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); } @@ -628,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>()); @@ -686,7 +691,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 +714,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 +734,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 +743,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 2d955dbe82..fbb36045ab 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; @@ -24,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; /// @@ -42,6 +41,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 +212,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,16 +241,25 @@ 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)) + 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; @@ -262,13 +276,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 @@ -298,9 +326,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 @@ -320,22 +348,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); 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 diff --git a/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs index d70331f71e..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); @@ -153,13 +152,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; + var gas = _atmosphereSystem.GetGas(i); + var overlay = _atmosphereSystem.GetOverlay(i); + if (overlay == null || tile?.Air == null) continue; var moles = tile.Air.Gases[i]; @@ -169,7 +175,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)) { @@ -261,7 +267,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.Server/GameObjects/EntitySystems/AtmosphereSystem.cs b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs index a5ffa6e9ce..84bce7a646 100644 --- a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs @@ -1,29 +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.GameObjects.Systems; 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 : EntitySystem + 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/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/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.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/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/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/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.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/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/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/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/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..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; @@ -21,13 +22,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..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; @@ -21,12 +22,11 @@ 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!; + [Dependency] private readonly IConfigurationManager _cfg = default!; private CancellationTokenSource _checkTimerCancel; @@ -55,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 026c11516a..0161ab409a 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -1,13 +1,19 @@ 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.Roles; +using Content.Server.Mobs.Roles.Suspicion; 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.Interfaces.Configuration; using Robust.Shared.IoC; using Timer = Robust.Shared.Timers.Timer; @@ -21,11 +27,10 @@ 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!; + [Dependency] private readonly IConfigurationManager _cfg = default!; private readonly CancellationTokenSource _checkTimerCancel = new CancellationTokenSource(); @@ -33,6 +38,11 @@ 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); + + EntitySystem.Get().AccessType = DoorSystem.AccessTypes.AllowAllNoExternal; + Timer.SpawnRepeating(DeadCheckDelay, _checkWinConditions, _checkTimerCancel.Token); } @@ -40,11 +50,16 @@ namespace Content.Server.GameTicking.GameRules { base.Removed(); + EntitySystem.Get().AccessType = DoorSystem.AccessTypes.Id; + _checkTimerCancel.Cancel(); } 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 785ec372a0..19383c0f37 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); @@ -133,9 +135,9 @@ namespace Content.Server.GameTicking _configurationManager.RegisterCVar("game.defaultpreset", "Suspicion", CVar.ARCHIVE); _configurationManager.RegisterCVar("game.fallbackpreset", "Sandbox", CVar.ARCHIVE); - PresetSuspicion.RegisterCVars(_configurationManager); + _configurationManager.RegisterCVar("game.enablewin", true, CVar.CHEAT); - _playerManager.PlayerStatusChanged += _handlePlayerStatusChanged; + PresetSuspicion.RegisterCVars(_configurationManager); _netManager.RegisterNetMessage(nameof(MsgTickerJoinLobby)); _netManager.RegisterNetMessage(nameof(MsgTickerJoinGame)); @@ -209,7 +211,7 @@ namespace Content.Server.GameTicking } else { - if (_playerManager.PlayerCount == 0) + if (PlayerManager.PlayerCount == 0) _roundStartCountdownHasNotStartedYetDueToNoPlayers = true; else _roundStartTimeUtc = DateTime.UtcNow + LobbyDuration; @@ -222,7 +224,7 @@ namespace Content.Server.GameTicking private void ReqWindowAttentionAll() { - foreach (var player in _playerManager.GetAllPlayers()) + foreach (var player in PlayerManager.GetAllPlayers()) { player.RequestWindowAttention(); } @@ -347,11 +349,12 @@ 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) { + _playersInLobby.TryGetValue(ply, out var status); var antag = mind.AllRoles.Any(role => role.Antagonist); var playerEndRoundInfo = new RoundEndPlayerInfo() { @@ -360,7 +363,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); } @@ -539,6 +543,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; @@ -559,8 +570,6 @@ namespace Content.Server.GameTicking handsComponent.PutInHand(inhandEntity.GetComponent(), hand); } } - - return entity; } private void ApplyCharacterProfile(IEntity entity, ICharacterProfile profile) @@ -643,7 +652,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 +660,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 +690,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 +705,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 +765,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. @@ -983,23 +987,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 IPlayerManager _playerManager; - [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/GameTickerBase.cs b/Content.Server/GameTicking/GameTickerBase.cs new file mode 100644 index 0000000000..917be07cd8 --- /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); + } + } + } +} diff --git a/Content.Server/GameTicking/GameTickerCommands.cs b/Content.Server/GameTicking/GameTickerCommands.cs index a43f2d9498..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."; @@ -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/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/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/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.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/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.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/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/Mobs/Mind.cs b/Content.Server/Mobs/Mind.cs index 2c5a275bec..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 @@ -231,6 +237,9 @@ namespace Content.Server.Mobs { Session?.AttachToEntity(entity); VisitingEntity = entity; + + var comp = entity.AddComponent(); + comp.Mind = this; } public void UnVisit() @@ -241,7 +250,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(); + } } } } 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 87% rename from Content.Server/Mobs/Roles/SuspicionTraitorRole.cs rename to Content.Server/Mobs/Roles/Suspicion/SuspicionTraitorRole.cs index 8a834564fd..d265bb23bf 100644 --- a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs +++ b/Content.Server/Mobs/Roles/Suspicion/SuspicionTraitorRole.cs @@ -1,12 +1,16 @@ 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; +using Robust.Shared.GameObjects.Systems; 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.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/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; diff --git a/Content.Server/PDA/PDAUplinkManager.cs b/Content.Server/PDA/PDAUplinkManager.cs index 0d93c95dc5..c1baaf9533 100644 --- a/Content.Server/PDA/PDAUplinkManager.cs +++ b/Content.Server/PDA/PDAUplinkManager.cs @@ -14,19 +14,17 @@ 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; + 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, @@ -42,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; @@ -74,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; } @@ -97,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.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(); } 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/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/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.Server/Utility/InventoryHelpers.cs b/Content.Server/Utility/InventoryHelpers.cs new file mode 100644 index 0000000000..ec998908ac --- /dev/null +++ b/Content.Server/Utility/InventoryHelpers.cs @@ -0,0 +1,50 @@ +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(); + var user = inventory.Owner; + + // Let's do nothing if the owner of the inventory has been deleted. + if (user.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 first... + var item = entityManager.SpawnEntity(prototype, user.Transform.MapPosition); + + // 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(); + } + } +} 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); + } + } +} 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 c39b6b7d5d..f9391a040c 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) @@ -246,6 +211,12 @@ namespace Content.Shared.Atmos 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/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/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/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/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/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/Damage/DamageableComponent.cs b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs index 2d31eeb42b..8557e738df 100644 --- a/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs +++ b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs @@ -21,21 +21,66 @@ 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"; - 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 +92,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 +132,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 +285,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/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalUnitComponent.cs index 9bcd7edbf0..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,8 +68,48 @@ 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 + public class DisposalUnitBoundUserInterfaceState : BoundUserInterfaceState, IEquatable { public readonly string UnitName; public readonly string UnitState; @@ -75,6 +126,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); + } } /// 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/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/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/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 4d05195dba..013b40e59e 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; @@ -14,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"; @@ -116,12 +116,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/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; } } - } 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/Components/SharedHandheldLightComponent.cs b/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs index ecc9229693..f6dd37fcef 100644 --- a/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs +++ b/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs @@ -9,15 +9,36 @@ 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; } } } + + [Serializable, NetSerializable] + public enum HandheldLightVisuals + { + Power + } + + [Serializable, NetSerializable] + public enum HandheldLightPowerStates + { + FullPower, + LowPower, + Dying, + } + + } 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/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/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..3f127c68c4 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -65,6 +65,13 @@ 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; + 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; 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]; 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)); } } } 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/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/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; - } -} 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/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/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; } 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/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/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) 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); } } 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 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) 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 0000000000..f58274219d Binary files /dev/null and b/Resources/Audio/Items/Handcuffs/cuff_breakout_start.ogg differ diff --git a/Resources/Audio/Items/Handcuffs/cuff_end.ogg b/Resources/Audio/Items/Handcuffs/cuff_end.ogg new file mode 100644 index 0000000000..3249a5a78f Binary files /dev/null and b/Resources/Audio/Items/Handcuffs/cuff_end.ogg differ diff --git a/Resources/Audio/Items/Handcuffs/cuff_start.ogg b/Resources/Audio/Items/Handcuffs/cuff_start.ogg new file mode 100644 index 0000000000..5dfae57c20 Binary files /dev/null and b/Resources/Audio/Items/Handcuffs/cuff_start.ogg differ 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 0000000000..e608a2c128 Binary files /dev/null and b/Resources/Audio/Items/Handcuffs/cuff_takeoff_end.ogg differ diff --git a/Resources/Audio/Items/Handcuffs/cuff_takeoff_start.ogg b/Resources/Audio/Items/Handcuffs/cuff_takeoff_start.ogg new file mode 100644 index 0000000000..70b6d5f0fc Binary files /dev/null and b/Resources/Audio/Items/Handcuffs/cuff_takeoff_start.ogg differ diff --git a/Resources/Audio/Items/Handcuffs/rope_breakout.ogg b/Resources/Audio/Items/Handcuffs/rope_breakout.ogg new file mode 100644 index 0000000000..f255d7d78b Binary files /dev/null and b/Resources/Audio/Items/Handcuffs/rope_breakout.ogg differ diff --git a/Resources/Audio/Items/Handcuffs/rope_end.ogg b/Resources/Audio/Items/Handcuffs/rope_end.ogg new file mode 100644 index 0000000000..427f94c6aa Binary files /dev/null and b/Resources/Audio/Items/Handcuffs/rope_end.ogg differ diff --git a/Resources/Audio/Items/Handcuffs/rope_start.ogg b/Resources/Audio/Items/Handcuffs/rope_start.ogg new file mode 100644 index 0000000000..7a7720530f Binary files /dev/null and b/Resources/Audio/Items/Handcuffs/rope_start.ogg differ diff --git a/Resources/Audio/Items/Handcuffs/rope_takeoff.ogg b/Resources/Audio/Items/Handcuffs/rope_takeoff.ogg new file mode 100644 index 0000000000..47adf2fca8 Binary files /dev/null and b/Resources/Audio/Items/Handcuffs/rope_takeoff.ogg differ diff --git a/Resources/Audio/Misc/tatoralert.ogg b/Resources/Audio/Misc/tatoralert.ogg new file mode 100644 index 0000000000..ca0efa0ea0 Binary files /dev/null and b/Resources/Audio/Misc/tatoralert.ogg differ diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index e18adbfd95..e7120fce27 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -37,6 +37,8 @@ - loc - hostlogin - events + - factions + CanAdminMenu: true - Index: 100 Name: Administrator @@ -96,9 +98,12 @@ - tilewalls - events - destroymechanism + - addaccent - readyall + - factions CanViewVar: true CanAdminPlace: true + CanAdminMenu: true - Index: 200 Name: Host @@ -187,7 +192,10 @@ - tilewalls - events - destroymechanism + - addaccent - readyall + - factions CanViewVar: true CanAdminPlace: true CanScript: true + CanAdminMenu: true 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/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 diff --git a/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml b/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml index 8383ee0575..bc92e47c07 100644 --- a/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml +++ b/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml @@ -57,6 +57,8 @@ - type: Occluder - type: SnapGrid offset: Center + - type: Destructible + deadThreshold: 500 placement: mode: SnapgridCenter diff --git a/Resources/Prototypes/Entities/Constructible/Ground/furniture.yml b/Resources/Prototypes/Entities/Constructible/Ground/furniture.yml index 1ad0119924..a46c09b640 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 + deadThreshold: 50 - type: entity name: bar stool @@ -48,6 +50,8 @@ position: Stand - type: Anchorable - type: Pullable + - type: Destructible + deadThreshold: 50 - type: entity name: dark office chair @@ -81,6 +85,8 @@ position: Stand - type: Anchorable - type: Pullable + - type: Destructible + deadThreshold: 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 + 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/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/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/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 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/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/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 6067f47b90..06ee76322b 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 + deadThreshold: 50 - type: entity name: small light @@ -66,3 +68,5 @@ - type: PoweredLight bulb: Bulb - type: PowerReceiver + - type: Destructible + deadThreshold: 25 diff --git a/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml b/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml index 7c1bb1e5a4..cde4c568aa 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml @@ -25,9 +25,10 @@ - VaultImpassable - SmallImpassable - type: Destructible - maxHP: 100 + deadThreshold: 100 - type: SnapGrid offset: Center + - type: Climbable - type: LowWall key: walls base: metal_ 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/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 diff --git a/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml b/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml index 1fe1cb94e2..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 @@ -264,6 +265,7 @@ - GrenadeBlast - GrenadeFrag - GrenadeBaton + - SoapSyndie # shhh! chance: 0.75 gameRules: - RuleSuspicion @@ -285,7 +287,10 @@ prototypes: - MagazineSRifle - MagazineClRifle + - MagazineClRifle10x24 + - MagazineClRiflePistol - MagazineLRifle + - MagazinePistolCalicoTopMounted chance: 0.95 gameRules: - RuleSuspicion @@ -327,8 +332,6 @@ prototypes: - MagazinePistol - MagazineHCPistol - - MagazinePistolSmg - - MagazineClRiflePistol chance: 0.95 gameRules: - RuleSuspicion diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 9ab31e8bcb..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 @@ -39,9 +42,10 @@ layer: - Opaque - MobImpassable - - type: BodyManager - BaseTemplate: bodyTemplate.Humanoid - BasePreset: bodyPreset.BasicHuman + - 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 00d7371892..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 @@ -34,9 +37,10 @@ layer: - Opaque - MobImpassable - - type: BodyManager - BaseTemplate: bodyTemplate.Humanoid - BasePreset: bodyPreset.BasicHuman + - type: Damageable + criticalThreshold: 50 + deadThreshold: 100 + - type: MobStateManager - type: HeatResistance - type: CombatMode - type: Teleportable 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 cd5cdbc22e..6df6db7fab 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml @@ -5,9 +5,14 @@ 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 @@ -15,6 +20,7 @@ parent: BaseHumanMob_Content id: HumanMob_Spirate description: Yarr + suffix: AI components: - type: AiController logic: Spirate diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml index 9f34c6b0a8..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 @@ -34,9 +37,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..b24b2d0d92 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 @@ -31,9 +34,10 @@ layer: - Opaque - MobImpassable - - type: BodyManager - BaseTemplate: bodyTemplate.Humanoid - BasePreset: bodyPreset.BasicHuman + - type: Damageable + criticalThreshold: 50 + deadThreshold: 100 + - type: MobStateManager - type: HeatResistance - type: CombatMode - type: Teleportable @@ -165,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/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index ada753f860..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 @@ -36,11 +39,11 @@ layer: - Opaque - MobImpassable - - type: BodyManager - baseTemplate: bodyTemplate.Humanoid - basePreset: bodyPreset.BasicHuman - - type: Metabolism + - type: Damageable + criticalThreshold: 150 + deadThreshold: 200 - type: MobStateManager + - type: Metabolism - type: HeatResistance - type: CombatMode - type: Teleportable 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 diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index e104ad5ccb..43a4507138 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -90,6 +90,11 @@ 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 + visible: false - map: ["enum.Slots.IDCARD"] - map: ["enum.Slots.GLOVES"] - map: ["enum.Slots.SHOES"] @@ -117,7 +122,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 @@ -126,6 +131,8 @@ - Opaque - MobImpassable - type: BodyManager + criticalThreshold: 100 + deadThreshold: 200 baseTemplate: bodyTemplate.Humanoid basePreset: bodyPreset.BasicHuman - type: Metabolism @@ -142,6 +149,7 @@ - type: BuckleVisualizer - type: CombatMode - type: Climbing + - type: Cuffable - type: Teleportable - type: CharacterInfo - type: FootstepSound @@ -220,6 +228,11 @@ 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 + visible: false - map: ["enum.Slots.IDCARD"] - map: ["enum.Slots.GLOVES"] - map: ["enum.Slots.SHOES"] @@ -256,6 +269,8 @@ layer: - MobImpassable - type: BodyManager + criticalThreshold: 100 + deadThreshold: 200 baseTemplate: bodyTemplate.Humanoid basePreset: bodyPreset.BasicHuman - type: MobStateManager 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/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 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 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/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 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/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml index 4107dd634c..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,20 +146,23 @@ 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: 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 @@ -187,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 @@ -231,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 @@ -263,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 @@ -295,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 @@ -322,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 @@ -357,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 @@ -385,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 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml index eb298ef72b..1d144ed9e7 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 @@ -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/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 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 0000000000..6427384c2f Binary files /dev/null and b/Resources/Textures/Constructible/Lighting/emergency_light.rsi/emergency_light_off.png differ 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 0000000000..364d44b505 Binary files /dev/null and b/Resources/Textures/Constructible/Lighting/emergency_light.rsi/emergency_light_on.png differ 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 + ] + ] + } + ] +} 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 0000000000..44d7290469 Binary files /dev/null and b/Resources/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png differ diff --git a/Resources/Textures/Interface/ItemStatus/Bullets/empty.png b/Resources/Textures/Interface/ItemStatus/Bullets/empty.png new file mode 100644 index 0000000000..564f5bf851 Binary files /dev/null and b/Resources/Textures/Interface/ItemStatus/Bullets/empty.png differ diff --git a/Resources/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png b/Resources/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png new file mode 100644 index 0000000000..3536af53b2 Binary files /dev/null and b/Resources/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png differ diff --git a/Resources/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png b/Resources/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png new file mode 100644 index 0000000000..6879bd394f Binary files /dev/null and b/Resources/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png differ diff --git a/Resources/Textures/Objects/Misc/cablecuffs.rsi/body-overlay-2.png b/Resources/Textures/Objects/Misc/cablecuffs.rsi/body-overlay-2.png new file mode 100644 index 0000000000..8c3c8a53fd Binary files /dev/null and b/Resources/Textures/Objects/Misc/cablecuffs.rsi/body-overlay-2.png differ 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 0000000000..ff0d583777 Binary files /dev/null and b/Resources/Textures/Objects/Misc/cablecuffs.rsi/body-overlay-4.png differ 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 0000000000..3da396ef0f Binary files /dev/null and b/Resources/Textures/Objects/Misc/cablecuffs.rsi/cuff-broken.png differ diff --git a/Resources/Textures/Objects/Misc/cablecuffs.rsi/cuff.png b/Resources/Textures/Objects/Misc/cablecuffs.rsi/cuff.png index 4679847291..6e4b284336 100644 Binary files a/Resources/Textures/Objects/Misc/cablecuffs.rsi/cuff.png and b/Resources/Textures/Objects/Misc/cablecuffs.rsi/cuff.png differ diff --git a/Resources/Textures/Objects/Misc/cablecuffs.rsi/inhand-left.png b/Resources/Textures/Objects/Misc/cablecuffs.rsi/inhand-left.png index 3ed7f9d40f..d311c0d128 100644 Binary files a/Resources/Textures/Objects/Misc/cablecuffs.rsi/inhand-left.png and b/Resources/Textures/Objects/Misc/cablecuffs.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Misc/cablecuffs.rsi/inhand-right.png b/Resources/Textures/Objects/Misc/cablecuffs.rsi/inhand-right.png index 9fdcd9f62c..f02cd96249 100644 Binary files a/Resources/Textures/Objects/Misc/cablecuffs.rsi/inhand-right.png and b/Resources/Textures/Objects/Misc/cablecuffs.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Misc/cablecuffs.rsi/meta.json b/Resources/Textures/Objects/Misc/cablecuffs.rsi/meta.json index 2129ef9e34..071dcf8cba 100644 --- a/Resources/Textures/Objects/Misc/cablecuffs.rsi/meta.json +++ b/Resources/Textures/Objects/Misc/cablecuffs.rsi/meta.json @@ -13,6 +13,51 @@ 1 ] ] + }, + { + "name": "cuff-broken", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "body-overlay-2", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "body-overlay-4", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] }, { "name": "inhand-left", diff --git a/Resources/Textures/Objects/Misc/handcuffs.rsi/body-overlay-2.png b/Resources/Textures/Objects/Misc/handcuffs.rsi/body-overlay-2.png new file mode 100644 index 0000000000..7a270dc250 Binary files /dev/null and b/Resources/Textures/Objects/Misc/handcuffs.rsi/body-overlay-2.png differ diff --git a/Resources/Textures/Objects/Misc/handcuffs.rsi/body-overlay-4.png b/Resources/Textures/Objects/Misc/handcuffs.rsi/body-overlay-4.png new file mode 100644 index 0000000000..ae5e8e2b42 Binary files /dev/null and b/Resources/Textures/Objects/Misc/handcuffs.rsi/body-overlay-4.png differ diff --git a/Resources/Textures/Objects/Misc/handcuffs.rsi/meta.json b/Resources/Textures/Objects/Misc/handcuffs.rsi/meta.json index 4fa026ebe6..c6533225fe 100644 --- a/Resources/Textures/Objects/Misc/handcuffs.rsi/meta.json +++ b/Resources/Textures/Objects/Misc/handcuffs.rsi/meta.json @@ -1 +1,8 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "states": [{"name": "handcuff", "directions": 1, "delays": [[1.0]]}, {"name": "inhand-left", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "inhand-right", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "equipped-BELT", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]} \ No newline at end of file +{"version": 1, "size": {"x": 32, "y": 32}, "states": [ +{"name": "body-overlay-2", "directions": 4, "delays": [[1.0],[1.0],[1.0],[1.0]]}, +{"name": "body-overlay-4", "directions": 4, "delays": [[1.0],[1.0],[1.0],[1.0]]}, +{"name": "handcuff", "directions": 1, "delays": [[1.0]]}, +{"name": "inhand-left", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, +{"name": "inhand-right", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, +{"name": "equipped-BELT", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]} +]} \ No newline at end of file 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 0000000000..c52c684562 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/base.png differ 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 0000000000..c52c684562 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/icon.png differ 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 0000000000..c52c684562 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Magazine/Pistol/calico_mag.rsi/mag-1.png differ 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 0000000000..6b5c09158c Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/base.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/icon.png b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/icon.png new file mode 100644 index 0000000000..f1027fbd2c Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/icon.png differ 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 0000000000..157e2a6d64 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/inhand-left.png differ 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 0000000000..1b179d55a0 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/inhand-right.png differ 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 0000000000..e37aa9a0e8 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/mag-0.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/meta.json new file mode 100644 index 0000000000..f061c8a636 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Rifles/calico.rsi/meta.json @@ -0,0 +1,31 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Yeeye", + "states": [ + { + "name": "icon", + "directions": 1 + }, + { + "name": "base", + "directions": 1 + }, + { + "name": "mag-0", + "directions": 1 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} 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 b40953313c..eea5242eee 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn.rsi/inhand-right.png and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn.rsi/inhand-right.png differ 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 diff --git a/RobustToolbox b/RobustToolbox index f0824212da..56d0f04c05 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit f0824212da5d913a51c8e40dee9a6622b9d5d1b7 +Subproject commit 56d0f04c0536ddde3464bc93d49c37f1ed067239 diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index c9b97a94bc..939d5d6e4c 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 @@ -43,6 +57,7 @@ True <data /> <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="*.UnitTesting" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> + True True True True @@ -52,6 +67,8 @@ True True True + True + True True True True @@ -62,11 +79,13 @@ True True True + True True True True True True + True True True True @@ -79,6 +98,7 @@ True True True + True True True True