From fd7fcbadda3d3e95073196a6b92d06a1fc9dc003 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 22 Jan 2021 23:04:20 +0100 Subject: [PATCH 01/61] Transfter --- .../GameObjects/Components/Chemistry/InjectorComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs index 76fc2f8b29..9d7878f79d 100644 --- a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs @@ -251,7 +251,7 @@ namespace Content.Server.GameObjects.Components.Chemistry targetSolution.TryAddSolution(removedSolution); - Owner.PopupMessage(user, Loc.GetString("You transfter {0}u to {1:theName}", removedSolution.TotalVolume, targetSolution.Owner)); + Owner.PopupMessage(user, Loc.GetString("You transfer {0}u to {1:theName}", removedSolution.TotalVolume, targetSolution.Owner)); Dirty(); } From ae91059c0bea7c47a0798eb330e4a10a265cc9d1 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Jan 2021 16:45:20 +0100 Subject: [PATCH 02/61] Adds "Add Reagent" admin verb. --- .../UserInterface/AdminAddReagentUI.cs | 172 ++++++++++++++++++ .../Chemistry/SolutionContainerComponent.cs | 107 ++++++++++- .../Administration/AdminAddReagentEuiState.cs | 31 ++++ 3 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 Content.Client/UserInterface/AdminAddReagentUI.cs create mode 100644 Content.Shared/Administration/AdminAddReagentEuiState.cs diff --git a/Content.Client/UserInterface/AdminAddReagentUI.cs b/Content.Client/UserInterface/AdminAddReagentUI.cs new file mode 100644 index 0000000000..adb9986a71 --- /dev/null +++ b/Content.Client/UserInterface/AdminAddReagentUI.cs @@ -0,0 +1,172 @@ +using Content.Client.Eui; +using Content.Shared.Administration; +using Content.Shared.Chemistry; +using Content.Shared.Eui; +using JetBrains.Annotations; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Prototypes; + +namespace Content.Client.UserInterface +{ + [UsedImplicitly] + public sealed class AdminAddReagentEui : BaseEui + { + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + private readonly Menu _window; + private bool _closed; + + public AdminAddReagentEui() + { + _window = new Menu(this); + _window.OnClose += () => SendMessage(new AdminAddReagentEuiMsg.Close()); + } + + public override void Opened() + { + _window.OpenCentered(); + } + + public override void Closed() + { + _closed = true; + + _window.Close(); + } + + public override void HandleState(EuiStateBase state) + { + _window.HandleState((AdminAddReagentEuiState) state); + } + + private void DoAdd(bool close, string reagentId, ReagentUnit amount) + { + SendMessage(new AdminAddReagentEuiMsg.DoAdd + { + Amount = amount, + ReagentId = reagentId, + CloseAfter = close + }); + } + + private sealed class Menu : SS14Window + { + private readonly AdminAddReagentEui _eui; + private readonly Label _volumeLabel; + private readonly LineEdit _reagentIdEdit; + private readonly LineEdit _amountEdit; + private readonly Label _errorLabel; + private readonly Button _addButton; + private readonly Button _addCloseButton; + + public Menu(AdminAddReagentEui eui) + { + _eui = eui; + + Title = Loc.GetString("Add reagent..."); + + Contents.AddChild(new VBoxContainer + { + Children = + { + new GridContainer + { + Columns = 2, + Children = + { + new Label {Text = Loc.GetString("Cur volume: ")}, + (_volumeLabel = new Label()), + new Label {Text = Loc.GetString("Reagent: ")}, + (_reagentIdEdit = new LineEdit {PlaceHolder = Loc.GetString("Reagent ID...")}), + new Label {Text = Loc.GetString("Amount: ")}, + (_amountEdit = new LineEdit + { + PlaceHolder = Loc.GetString("A number..."), + SizeFlagsHorizontal = SizeFlags.FillExpand + }), + }, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand + }, + new HBoxContainer + { + Children = + { + (_errorLabel = new Label + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + ClipText = true + }), + + (_addButton = new Button {Text = Loc.GetString("Add")}), + (_addCloseButton = new Button {Text = Loc.GetString("Add & Close")}) + } + } + } + }); + + _reagentIdEdit.OnTextChanged += _ => CheckErrors(); + _amountEdit.OnTextChanged += _ => CheckErrors(); + _addButton.OnPressed += _ => DoAdd(false); + _addCloseButton.OnPressed += _ => DoAdd(true); + + CheckErrors(); + } + + private void DoAdd(bool close) + { + _eui.DoAdd( + close, + _reagentIdEdit.Text, + ReagentUnit.New(float.Parse(_amountEdit.Text))); + } + + private void CheckErrors() + { + if (string.IsNullOrWhiteSpace(_reagentIdEdit.Text)) + { + DoError(Loc.GetString("Must specify reagent ID")); + return; + } + + if (!_eui._prototypes.HasIndex(_reagentIdEdit.Text)) + { + DoError(Loc.GetString("'{0}' does not exist.", _reagentIdEdit.Text)); + return; + } + + if (string.IsNullOrWhiteSpace(_amountEdit.Text)) + { + DoError(Loc.GetString("Must specify reagent amount")); + return; + } + + if (!float.TryParse(_amountEdit.Text, out _)) + { + DoError(Loc.GetString("Invalid amount")); + return; + } + + _addButton.Disabled = false; + _addCloseButton.Disabled = false; + _errorLabel.Text = ""; + + void DoError(string text) + { + _errorLabel.Text = text; + + _addButton.Disabled = true; + _addCloseButton.Disabled = true; + } + } + + public void HandleState(AdminAddReagentEuiState state) + { + _volumeLabel.Text = Loc.GetString("{0}/{1}u", state.CurVolume, state.MaxVolume); + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs index 79a003913e..d60b1dc0fb 100644 --- a/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs @@ -1,11 +1,18 @@ #nullable enable +using Content.Server.Administration; +using Content.Server.Eui; using Content.Server.GameObjects.Components.GUI; +using Content.Shared.Administration; using Content.Shared.Chemistry; +using Content.Shared.Eui; using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.GameObjects.Verbs; +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; namespace Content.Server.GameObjects.Components.Chemistry @@ -58,7 +65,8 @@ namespace Content.Server.GameObjects.Components.Chemistry return; } - var transferQuantity = ReagentUnit.Min(component.MaxVolume - component.CurrentVolume, handSolutionComp.CurrentVolume, ReagentUnit.New(10)); + var transferQuantity = ReagentUnit.Min(component.MaxVolume - component.CurrentVolume, + handSolutionComp.CurrentVolume, ReagentUnit.New(10)); if (transferQuantity <= 0) { @@ -108,14 +116,15 @@ namespace Content.Server.GameObjects.Components.Chemistry return; } - if(!hands.GetActiveHand.Owner.TryGetComponent(out var handSolutionComp) || + if (!hands.GetActiveHand.Owner.TryGetComponent(out var handSolutionComp) || !handSolutionComp.CanAddSolutions || !component.CanRemoveSolutions) { return; } - var transferQuantity = ReagentUnit.Min(handSolutionComp.MaxVolume - handSolutionComp.CurrentVolume, component.CurrentVolume, ReagentUnit.New(10)); + var transferQuantity = ReagentUnit.Min(handSolutionComp.MaxVolume - handSolutionComp.CurrentVolume, + component.CurrentVolume, ReagentUnit.New(10)); if (transferQuantity <= 0) { @@ -126,5 +135,97 @@ namespace Content.Server.GameObjects.Components.Chemistry handSolutionComp.TryAddSolution(transferSolution); } } + + [Verb] + private sealed class AdminAddReagentVerb : Verb + { + private const AdminFlags ReqFlags = AdminFlags.Fun; + + protected override void GetData(IEntity user, SolutionContainerComponent component, VerbData data) + { + data.Text = Loc.GetString("Add Reagent..."); + data.CategoryData = VerbCategories.Debug; + data.Visibility = VerbVisibility.Invisible; + + var adminManager = IoCManager.Resolve(); + + if (user.TryGetComponent(out var player)) + { + if (adminManager.HasAdminFlag(player.playerSession, ReqFlags)) + { + data.Visibility = VerbVisibility.Visible; + } + } + } + + protected override void Activate(IEntity user, SolutionContainerComponent component) + { + var groupController = IoCManager.Resolve(); + if (user.TryGetComponent(out var player)) + { + if (groupController.HasAdminFlag(player.playerSession, ReqFlags)) + OpenAddReagentMenu(player.playerSession, component); + } + } + + private static void OpenAddReagentMenu(IPlayerSession player, SolutionContainerComponent comp) + { + var euiMgr = IoCManager.Resolve(); + euiMgr.OpenEui(new AdminAddReagentEui(comp), player); + } + + private sealed class AdminAddReagentEui : BaseEui + { + private readonly SolutionContainerComponent _target; + [Dependency] private readonly IAdminManager _adminManager = default!; + + public AdminAddReagentEui(SolutionContainerComponent target) + { + _target = target; + + IoCManager.InjectDependencies(this); + } + + public override void Opened() + { + StateDirty(); + } + + public override EuiStateBase GetNewState() + { + return new AdminAddReagentEuiState + { + CurVolume = _target.CurrentVolume, + MaxVolume = _target.MaxVolume + }; + } + + public override void HandleMessage(EuiMessageBase msg) + { + switch (msg) + { + case AdminAddReagentEuiMsg.Close: + Close(); + break; + case AdminAddReagentEuiMsg.DoAdd doAdd: + // Double check that user wasn't de-adminned in the mean time... + // Or the target was deleted. + if (!_adminManager.HasAdminFlag(Player, ReqFlags) || _target.Deleted) + { + Close(); + return; + } + + _target.TryAddReagent(doAdd.ReagentId, doAdd.Amount, out _); + StateDirty(); + + if (doAdd.CloseAfter) + Close(); + + break; + } + } + } + } } } diff --git a/Content.Shared/Administration/AdminAddReagentEuiState.cs b/Content.Shared/Administration/AdminAddReagentEuiState.cs new file mode 100644 index 0000000000..25bf03e17c --- /dev/null +++ b/Content.Shared/Administration/AdminAddReagentEuiState.cs @@ -0,0 +1,31 @@ +using System; +using Content.Shared.Chemistry; +using Content.Shared.Eui; +using Robust.Shared.Serialization; + +namespace Content.Shared.Administration +{ + [Serializable, NetSerializable] + public sealed class AdminAddReagentEuiState : EuiStateBase + { + public ReagentUnit MaxVolume; + public ReagentUnit CurVolume; + } + + public static class AdminAddReagentEuiMsg + { + [Serializable, NetSerializable] + public sealed class Close : EuiMessageBase + { + + } + + [Serializable, NetSerializable] + public sealed class DoAdd : EuiMessageBase + { + public bool CloseAfter; + public ReagentUnit Amount; + public string ReagentId; + } + } +} From 85fcf7290caec160d61b23306b88f8053317daf8 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Jan 2021 16:49:22 +0100 Subject: [PATCH 03/61] Clean up terrible solution entity reaction copy pasta. --- .../Components/Chemistry/InjectorComponent.cs | 18 +++--------------- .../Components/Chemistry/PillComponent.cs | 6 +----- .../Components/Chemistry/VaporComponent.cs | 7 +------ .../Components/Nutrition/DrinkComponent.cs | 6 +----- .../Components/Nutrition/FoodComponent.cs | 6 +----- Content.Shared/Chemistry/Solution.cs | 16 ++++++++++++++++ 6 files changed, 23 insertions(+), 36 deletions(-) diff --git a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs index 9d7878f79d..d4bcfdffc6 100644 --- a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs @@ -201,19 +201,11 @@ namespace Content.Server.GameObjects.Components.Chemistry // TODO: Account for partial transfer. - foreach (var (reagentId, quantity) in removedSolution.Contents) - { - if(!_prototypeManager.TryIndex(reagentId, out ReagentPrototype reagent)) continue; - removedSolution.RemoveReagent(reagentId, reagent.ReactionEntity(solution.Owner, ReactionMethod.Injection, quantity)); - } + removedSolution.DoEntityReaction(solution.Owner, ReactionMethod.Injection); solution.TryAddSolution(removedSolution); - foreach (var (reagentId, quantity) in removedSolution.Contents) - { - if(!_prototypeManager.TryIndex(reagentId, out ReagentPrototype reagent)) continue; - reagent.ReactionEntity(targetBloodstream.Owner, ReactionMethod.Injection, quantity); - } + removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection); Owner.PopupMessage(user, Loc.GetString("You inject {0}u into {1:theName}!", removedSolution.TotalVolume, targetBloodstream.Owner)); Dirty(); @@ -243,11 +235,7 @@ namespace Content.Server.GameObjects.Components.Chemistry return; } - foreach (var (reagentId, quantity) in removedSolution.Contents) - { - if(!_prototypeManager.TryIndex(reagentId, out ReagentPrototype reagent)) continue; - removedSolution.RemoveReagent(reagentId, reagent.ReactionEntity(targetSolution.Owner, ReactionMethod.Injection, quantity)); - } + removedSolution.DoEntityReaction(targetSolution.Owner, ReactionMethod.Injection); targetSolution.TryAddSolution(removedSolution); diff --git a/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs b/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs index 9c1bd88881..d96e27cc60 100644 --- a/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs @@ -103,11 +103,7 @@ namespace Content.Server.GameObjects.Components.Chemistry // TODO: Account for partial transfer. - foreach (var (reagentId, quantity) in split.Contents) - { - if (!_prototypeManager.TryIndex(reagentId, out ReagentPrototype reagent)) continue; - split.RemoveReagent(reagentId, reagent.ReactionEntity(trueTarget, ReactionMethod.Ingestion, quantity)); - } + split.DoEntityReaction(trueTarget, ReactionMethod.Ingestion); firstStomach.TryTransferSolution(split); diff --git a/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs b/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs index 44461c3b94..208cfd658f 100644 --- a/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs @@ -135,12 +135,7 @@ namespace Content.Server.GameObjects.Components.Chemistry if (!Owner.TryGetComponent(out SolutionContainerComponent contents)) return; - foreach (var reagentQuantity in contents.ReagentList.ToArray()) - { - if (reagentQuantity.Quantity == ReagentUnit.Zero) continue; - var reagent = _prototypeManager.Index(reagentQuantity.ReagentId); - contents.TryRemoveReagent(reagentQuantity.ReagentId, reagent.ReactionEntity(collidedWith, ReactionMethod.Touch, reagentQuantity.Quantity * 0.125f)); - } + contents.Solution.DoEntityReaction(collidedWith, ReactionMethod.Touch); // Check for collision with a impassable object (e.g. wall) and stop if (collidedWith.TryGetComponent(out IPhysicsComponent physics)) diff --git a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs index 6ea6074a46..4990ac6075 100644 --- a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs @@ -181,11 +181,7 @@ namespace Content.Server.GameObjects.Components.Nutrition // TODO: Account for partial transfer. - foreach (var (reagentId, quantity) in split.Contents) - { - if (!_prototypeManager.TryIndex(reagentId, out ReagentPrototype reagent)) continue; - split.RemoveReagent(reagentId, reagent.ReactionEntity(target, ReactionMethod.Ingestion, quantity)); - } + split.DoEntityReaction(target, ReactionMethod.Ingestion); firstStomach.TryTransferSolution(split); diff --git a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs index 94fef359b0..b7318fdce1 100644 --- a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs @@ -183,11 +183,7 @@ namespace Content.Server.GameObjects.Components.Nutrition // TODO: Account for partial transfer. - foreach (var (reagentId, quantity) in split.Contents) - { - if (!_prototypeManager.TryIndex(reagentId, out ReagentPrototype reagent)) continue; - split.RemoveReagent(reagentId, reagent.ReactionEntity(trueTarget, ReactionMethod.Ingestion, quantity)); - } + split.DoEntityReaction(trueTarget, ReactionMethod.Ingestion); firstStomach.TryTransferSolution(split); diff --git a/Content.Shared/Chemistry/Solution.cs b/Content.Shared/Chemistry/Solution.cs index ea334527b3..96b802696b 100644 --- a/Content.Shared/Chemistry/Solution.cs +++ b/Content.Shared/Chemistry/Solution.cs @@ -3,6 +3,8 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Serialization; using Robust.Shared.IoC; using Robust.Shared.Maths; @@ -315,6 +317,20 @@ namespace Content.Shared.Chemistry return newSolution; } + public void DoEntityReaction(IEntity entity, ReactionMethod method) + { + var proto = IoCManager.Resolve(); + + foreach (var (reagentId, quantity) in _contents) + { + if (!proto.TryIndex(reagentId, out ReagentPrototype reagent)) + continue; + + var removedAmount = reagent.ReactionEntity(entity, method, quantity); + RemoveReagent(reagentId, removedAmount); + } + } + [Serializable, NetSerializable] public readonly struct ReagentQuantity: IComparable { From 6635054cacdf3399c1198161c21debeb80e314f9 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Jan 2021 17:50:48 +0100 Subject: [PATCH 04/61] Attacks now re-orients players like interactions so. --- .../EntitySystems/Click/InteractionSystem.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index dde99dbddd..552d3c43c5 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -315,14 +315,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click var item = hands.GetActiveHand?.Owner; - if (ActionBlockerSystem.CanChangeDirection(player)) - { - var diff = coordinates.ToMapPos(EntityManager) - playerTransform.MapPosition.Position; - if (diff.LengthSquared > 0.01f) - { - playerTransform.LocalRotation = new Angle(diff); - } - } + ClickFace(player, coordinates); if (!ActionBlockerSystem.CanInteract(player)) { @@ -399,6 +392,18 @@ namespace Content.Server.GameObjects.EntitySystems.Click } } + private void ClickFace(IEntity player, EntityCoordinates coordinates) + { + if (ActionBlockerSystem.CanChangeDirection(player)) + { + var diff = coordinates.ToMapPos(EntityManager) - player.Transform.MapPosition.Position; + if (diff.LengthSquared > 0.01f) + { + player.Transform.LocalRotation = new Angle(diff); + } + } + } + /// /// We didn't click on any entity, try doing an AfterInteract on the click location /// @@ -850,6 +855,8 @@ namespace Content.Server.GameObjects.EntitySystems.Click return; } + ClickFace(player, coordinates); + if (!ActionBlockerSystem.CanAttack(player) || (!wideAttack && !player.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true))) { From 1eb5af64490fc3db0a75f65f56489bb1bc6d8bfd Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Jan 2021 17:54:58 +0100 Subject: [PATCH 05/61] Adds ClumsyComponent.TryRollClumsy helper method. --- .../Components/Mobs/ClumsyComponent.cs | 27 +++++++++++++++++++ .../Ranged/ServerRangedWeaponComponent.cs | 4 +-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Content.Server/GameObjects/Components/Mobs/ClumsyComponent.cs b/Content.Server/GameObjects/Components/Mobs/ClumsyComponent.cs index 5e55a21a3e..02a278a29b 100644 --- a/Content.Server/GameObjects/Components/Mobs/ClumsyComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/ClumsyComponent.cs @@ -1,4 +1,10 @@ using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Random; + +#nullable enable namespace Content.Server.GameObjects.Components.Mobs { @@ -8,6 +14,27 @@ namespace Content.Server.GameObjects.Components.Mobs [RegisterComponent] public class ClumsyComponent : Component { + [Dependency] private readonly IRobustRandom _random = default!; + public override string Name => "Clumsy"; + + public bool RollClumsy(float chance) + { + return Running && _random.Prob(chance); + } + + /// + /// Rolls a probability chance for a "bad action" if the target entity is clumsy. + /// + /// The entity that the clumsy check is happening for. + /// + /// The chance that a "bad action" happens if the user is clumsy, between 0 and 1 inclusive. + /// + /// True if a "bad action" happened, false if the normal action should happen. + public static bool TryRollClumsy(IEntity entity, float chance) + { + return entity.TryGetComponent(out ClumsyComponent? clumsy) + && clumsy.RollClumsy(chance); + } } } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs index f1331d4e7e..7f554a5eed 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs @@ -161,9 +161,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged _lastFireTime = curTime; - if (ClumsyCheck && - user.HasComponent() && - _random.Prob(ClumsyExplodeChance)) + if (ClumsyCheck && ClumsyComponent.TryRollClumsy(user, ClumsyExplodeChance)) { var soundSystem = EntitySystem.Get(); soundSystem.PlayAtCoords("/Audio/Items/bikehorn.ogg", From 8b2f28f155b19dfd19c1a42dee5f2bd24cbb5737 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Jan 2021 19:36:48 +0100 Subject: [PATCH 06/61] ReactionPrototype now uses arrays instead of lists internally. Just a tiny optimization. --- Content.Shared/Chemistry/ReactionPrototype.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Content.Shared/Chemistry/ReactionPrototype.cs b/Content.Shared/Chemistry/ReactionPrototype.cs index 7f2f9eb006..57ae9eaf7a 100644 --- a/Content.Shared/Chemistry/ReactionPrototype.cs +++ b/Content.Shared/Chemistry/ReactionPrototype.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.Collections.Generic; using Content.Server.Interfaces.Chemistry; using Content.Shared.Interfaces; @@ -20,7 +21,7 @@ namespace Content.Shared.Chemistry private string _name = default!; private Dictionary _reactants = default!; private Dictionary _products = default!; - private List _effects = default!; + private IReactionEffect[] _effects = default!; public string ID => _id; public string Name => _name; @@ -55,11 +56,11 @@ namespace Content.Shared.Chemistry { //TODO: Don't have a check for if this is the server //Some implementations of IReactionEffect can't currently be moved to shared, so this is here to prevent the client from breaking when reading server-only IReactionEffects. - serializer.DataField(ref _effects, "effects", new List()); + serializer.DataField(ref _effects, "effects", Array.Empty()); } else { - _effects = new(); //To ensure _effects isn't null since it is only serializable on the server right snow + _effects = Array.Empty(); //To ensure _effects isn't null since it is only serializable on the server right snow } } } From 2af616284e4a421b171c0fc8b839210f6f04ad8d Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Jan 2021 19:37:28 +0100 Subject: [PATCH 07/61] Log all broken reactions. --- Content.Shared/EntryPoint.cs | 52 ++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/Content.Shared/EntryPoint.cs b/Content.Shared/EntryPoint.cs index 0f163e43c4..bbee9f5b2a 100644 --- a/Content.Shared/EntryPoint.cs +++ b/Content.Shared/EntryPoint.cs @@ -1,15 +1,17 @@ -using System; - using System.Collections.Generic; - using System.Globalization; - using Content.Shared.Maps; - using Robust.Shared.ContentPack; - using Robust.Shared.Interfaces.Map; - using Robust.Shared.IoC; - using Robust.Shared.Localization; - using Robust.Shared.Localization.Macros; - using Robust.Shared.Prototypes; +using System; +using System.Collections.Generic; +using System.Globalization; +using Content.Shared.Chemistry; +using Content.Shared.Maps; +using Robust.Shared.ContentPack; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Localization.Macros; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; - namespace Content.Shared +namespace Content.Shared { public class EntryPoint : GameShared { @@ -39,6 +41,33 @@ base.PostInit(); _initTileDefinitions(); + CheckReactions(); + } + + private void CheckReactions() + { + foreach (var reaction in _prototypeManager.EnumeratePrototypes()) + { + foreach (var reactant in reaction.Reactants.Keys) + { + if (!_prototypeManager.HasIndex(reactant)) + { + Logger.ErrorS( + "chem", "Reaction {reaction} has unknown reactant {reagent}.", + reaction.ID, reactant); + } + } + + foreach (var product in reaction.Products.Keys) + { + if (!_prototypeManager.HasIndex(product)) + { + Logger.ErrorS( + "chem", "Reaction {reaction} has unknown product {product}.", + reaction.ID, product); + } + } + } } private void _initTileDefinitions() @@ -55,6 +84,7 @@ { continue; } + prototypeList.Add(tileDef); } From d4f4a1a44bd7e1091a40a6e7a4ec086a5c644562 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Jan 2021 20:03:35 +0100 Subject: [PATCH 08/61] Make pills not FoodBase items to fix some exceptions. --- Resources/Prototypes/Entities/Objects/Specific/chemistry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index 28bfe4a37c..071f68b2ec 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -91,7 +91,7 @@ - type: entity name: pill - parent: FoodBase + parent: BaseItem id: pill description: It's not a suppository. components: From ea33397c89c8ca41cfaab6653dc1821ff4945509 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Jan 2021 20:09:02 +0100 Subject: [PATCH 09/61] Mark ReagentUnit instance methods are readonly. --- Content.Shared/Chemistry/ReagentUnit.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Content.Shared/Chemistry/ReagentUnit.cs b/Content.Shared/Chemistry/ReagentUnit.cs index 67a900ec5f..f19553cd6b 100644 --- a/Content.Shared/Chemistry/ReagentUnit.cs +++ b/Content.Shared/Chemistry/ReagentUnit.cs @@ -19,7 +19,7 @@ namespace Content.Shared.Chemistry public static ReagentUnit Epsilon { get; } = new(1); public static ReagentUnit Zero { get; } = new(0); - private double ShiftDown() + private readonly double ShiftDown() { return _value / Math.Pow(10, Shift); } @@ -164,17 +164,17 @@ namespace Content.Shared.Chemistry return a._value > b._value; } - public float Float() + public readonly float Float() { return (float) ShiftDown(); } - public double Double() + public readonly double Double() { return ShiftDown(); } - public int Int() + public readonly int Int() { return (int) ShiftDown(); } @@ -204,14 +204,15 @@ namespace Content.Shared.Chemistry return reagent < min ? min : reagent > max ? max : reagent; } - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is ReagentUnit unit && _value == unit._value; } - public override int GetHashCode() + public override readonly int GetHashCode() { + // ReSharper disable once NonReadonlyMemberInGetHashCode return HashCode.Combine(_value); } @@ -220,19 +221,19 @@ namespace Content.Shared.Chemistry _value = FromFloat(FloatFromString(value)); } - public override string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}"; + public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}"; - public string Serialize() + public readonly string Serialize() { return ToString(); } - public bool Equals(ReagentUnit other) + public readonly bool Equals(ReagentUnit other) { return _value == other._value; } - public int CompareTo(ReagentUnit other) + public readonly int CompareTo(ReagentUnit other) { if(other._value > _value) { From 148c8daeb633bf46cfed8c8cc0aa716dc1fe830f Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Jan 2021 20:27:45 +0100 Subject: [PATCH 10/61] Adds 15/20/30 buttons to chem dispenser. --- .../ReagentDispenserBoundUserInterface.cs | 3 +++ .../ReagentDispenserWindow.cs | 21 +++++++++++++++++++ .../Chemistry/ReagentDispenserComponent.cs | 9 ++++++++ .../SharedReagentDispenserComponent.cs | 3 +++ 4 files changed, 36 insertions(+) diff --git a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserBoundUserInterface.cs b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserBoundUserInterface.cs index 6ab682f76b..e6675df4c4 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserBoundUserInterface.cs @@ -48,7 +48,10 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser _window.DispenseButton1.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount1); _window.DispenseButton5.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount5); _window.DispenseButton10.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount10); + _window.DispenseButton15.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount15); + _window.DispenseButton20.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount20); _window.DispenseButton25.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount25); + _window.DispenseButton30.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount30); _window.DispenseButton50.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount50); _window.DispenseButton100.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount100); } diff --git a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs index d32d10f776..699df5e956 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenser/ReagentDispenserWindow.cs @@ -34,9 +34,18 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser /// Sets the dispense amount to 10 when pressed. public Button DispenseButton10 { get; } + /// Sets the dispense amount to 15 when pressed. + public Button DispenseButton15 { get; } + + /// Sets the dispense amount to 20 when pressed. + public Button DispenseButton20 { get; } + /// Sets the dispense amount to 25 when pressed. public Button DispenseButton25 { get; } + /// Sets the dispense amount to 30 when pressed. + public Button DispenseButton30 { get; } + /// Sets the dispense amount to 50 when pressed. public Button DispenseButton50 { get; } @@ -79,7 +88,10 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser (DispenseButton1 = new Button {Text = "1", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenRight }}), (DispenseButton5 = new Button {Text = "5", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}), (DispenseButton10 = new Button {Text = "10", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}), + (DispenseButton15 = new Button {Text = "15", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}), + (DispenseButton20 = new Button {Text = "20", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}), (DispenseButton25 = new Button {Text = "25", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}), + (DispenseButton30 = new Button {Text = "30", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}), (DispenseButton50 = new Button {Text = "50", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenBoth }}), (DispenseButton100 = new Button {Text = "100", Group = dispenseAmountGroup, StyleClasses = { StyleBase.ButtonOpenLeft }}), } @@ -215,9 +227,18 @@ namespace Content.Client.GameObjects.Components.Chemistry.ReagentDispenser case 10: DispenseButton10.Pressed = true; break; + case 15: + DispenseButton15.Pressed = true; + break; + case 20: + DispenseButton20.Pressed = true; + break; case 25: DispenseButton25.Pressed = true; break; + case 30: + DispenseButton30.Pressed = true; + break; case 50: DispenseButton50.Pressed = true; break; diff --git a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs index 51f2bdb46d..eb62004809 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs @@ -161,9 +161,18 @@ namespace Content.Server.GameObjects.Components.Chemistry case UiButton.SetDispenseAmount10: _dispenseAmount = ReagentUnit.New(10); break; + case UiButton.SetDispenseAmount15: + _dispenseAmount = ReagentUnit.New(15); + break; + case UiButton.SetDispenseAmount20: + _dispenseAmount = ReagentUnit.New(20); + break; case UiButton.SetDispenseAmount25: _dispenseAmount = ReagentUnit.New(25); break; + case UiButton.SetDispenseAmount30: + _dispenseAmount = ReagentUnit.New(30); + break; case UiButton.SetDispenseAmount50: _dispenseAmount = ReagentUnit.New(50); break; diff --git a/Content.Shared/GameObjects/Components/Chemistry/ReagentDispenser/SharedReagentDispenserComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/ReagentDispenser/SharedReagentDispenserComponent.cs index b0d17284ec..c36e8498ff 100644 --- a/Content.Shared/GameObjects/Components/Chemistry/ReagentDispenser/SharedReagentDispenserComponent.cs +++ b/Content.Shared/GameObjects/Components/Chemistry/ReagentDispenser/SharedReagentDispenserComponent.cs @@ -88,7 +88,10 @@ namespace Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser SetDispenseAmount1, SetDispenseAmount5, SetDispenseAmount10, + SetDispenseAmount15, + SetDispenseAmount20, SetDispenseAmount25, + SetDispenseAmount30, SetDispenseAmount50, SetDispenseAmount100, /// From 12b442d2ee8237f8a4024f79c9b69a8b817d8648 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 23 Jan 2021 21:51:18 +0100 Subject: [PATCH 11/61] EntityButton no longer inherits PanelContainer. It didn't use the functionality so. --- .../GameObjects/Components/Storage/ClientStorageComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs index 02d30dce09..1d6043ff18 100644 --- a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs +++ b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs @@ -284,7 +284,7 @@ namespace Content.Client.GameObjects.Components.Storage /// /// Button created for each entity that represents that item in the storage UI, with a texture, and name and size label /// - private class EntityButton : PanelContainer + private class EntityButton : Control { public EntityUid EntityUid { get; set; } public Button ActualButton { get; } From 3874f1f77a15cca06259d9a70f3f5e12bf8bd34c Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 02:31:38 +0100 Subject: [PATCH 12/61] HighDPI actions UI icons. --- Content.Client/UserInterface/ActionsUI.cs | 22 +++--- Resources/Textures/Interface/Nano/gear.svg | 59 ++++++++++++++- .../Interface/Nano/gear.svg.192dpi.png | Bin 0 -> 1460 bytes .../Interface/Nano/gear.svg.192dpi.png.yml | 2 + .../Textures/Interface/Nano/gear.svg.png | Bin 612 -> 0 bytes .../Textures/Interface/Nano/left_arrow.svg | 67 ++++++++++++++++- .../Interface/Nano/left_arrow.svg.192dpi.png | Bin 0 -> 736 bytes .../Nano/left_arrow.svg.192dpi.png.yml | 2 + .../Interface/Nano/left_arrow.svg.png | Bin 395 -> 0 bytes .../Interface/Nano/lock.svg.192dpi.png | Bin 0 -> 869 bytes .../Interface/Nano/lock.svg.192dpi.png.yml | 2 + .../Textures/Interface/Nano/lock.svg.png | Bin 446 -> 0 bytes .../Interface/Nano/lock_open.svg.192dpi.png | Bin 0 -> 915 bytes .../Nano/lock_open.svg.192dpi.png.yml | 2 + .../Textures/Interface/Nano/lock_open.svg.png | Bin 557 -> 0 bytes .../Textures/Interface/Nano/right_arrow.svg | 70 +++++++++++++++++- .../Interface/Nano/right_arrow.svg.192dpi.png | Bin 0 -> 850 bytes .../Nano/right_arrow.svg.192dpi.png.yml | 2 + .../Interface/Nano/right_arrow.svg.png | Bin 449 -> 0 bytes 19 files changed, 213 insertions(+), 15 deletions(-) create mode 100644 Resources/Textures/Interface/Nano/gear.svg.192dpi.png create mode 100644 Resources/Textures/Interface/Nano/gear.svg.192dpi.png.yml delete mode 100644 Resources/Textures/Interface/Nano/gear.svg.png create mode 100644 Resources/Textures/Interface/Nano/left_arrow.svg.192dpi.png create mode 100644 Resources/Textures/Interface/Nano/left_arrow.svg.192dpi.png.yml delete mode 100644 Resources/Textures/Interface/Nano/left_arrow.svg.png create mode 100644 Resources/Textures/Interface/Nano/lock.svg.192dpi.png create mode 100644 Resources/Textures/Interface/Nano/lock.svg.192dpi.png.yml delete mode 100644 Resources/Textures/Interface/Nano/lock.svg.png create mode 100644 Resources/Textures/Interface/Nano/lock_open.svg.192dpi.png create mode 100644 Resources/Textures/Interface/Nano/lock_open.svg.192dpi.png.yml delete mode 100644 Resources/Textures/Interface/Nano/lock_open.svg.png create mode 100644 Resources/Textures/Interface/Nano/right_arrow.svg.192dpi.png create mode 100644 Resources/Textures/Interface/Nano/right_arrow.svg.192dpi.png.yml delete mode 100644 Resources/Textures/Interface/Nano/right_arrow.svg.png diff --git a/Content.Client/UserInterface/ActionsUI.cs b/Content.Client/UserInterface/ActionsUI.cs index c0c6085279..2c275398e7 100644 --- a/Content.Client/UserInterface/ActionsUI.cs +++ b/Content.Client/UserInterface/ActionsUI.cs @@ -122,23 +122,25 @@ namespace Content.Client.UserInterface hotbarContainer.AddChild(settingsContainer); settingsContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 1 }); - _lockTexture = resourceCache.GetTexture("/Textures/Interface/Nano/lock.svg.png"); - _unlockTexture = resourceCache.GetTexture("/Textures/Interface/Nano/lock_open.svg.png"); + _lockTexture = resourceCache.GetTexture("/Textures/Interface/Nano/lock.svg.192dpi.png"); + _unlockTexture = resourceCache.GetTexture("/Textures/Interface/Nano/lock_open.svg.192dpi.png"); _lockButton = new TextureButton { TextureNormal = _unlockTexture, SizeFlagsHorizontal = SizeFlags.ShrinkCenter, SizeFlagsVertical = SizeFlags.ShrinkCenter, - SizeFlagsStretchRatio = 1 + SizeFlagsStretchRatio = 1, + Scale = (0.5f, 0.5f) }; settingsContainer.AddChild(_lockButton); settingsContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 2 }); _settingsButton = new TextureButton { - TextureNormal = resourceCache.GetTexture("/Textures/Interface/Nano/gear.svg.png"), + TextureNormal = resourceCache.GetTexture("/Textures/Interface/Nano/gear.svg.192dpi.png"), SizeFlagsHorizontal = SizeFlags.ShrinkCenter, SizeFlagsVertical = SizeFlags.ShrinkCenter, - SizeFlagsStretchRatio = 1 + SizeFlagsStretchRatio = 1, + Scale = (0.5f, 0.5f) }; settingsContainer.AddChild(_settingsButton); settingsContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 1 }); @@ -160,10 +162,11 @@ namespace Content.Client.UserInterface _loadoutContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 1 }); var previousHotbarIcon = new TextureRect() { - Texture = resourceCache.GetTexture("/Textures/Interface/Nano/left_arrow.svg.png"), + Texture = resourceCache.GetTexture("/Textures/Interface/Nano/left_arrow.svg.192dpi.png"), SizeFlagsHorizontal = SizeFlags.ShrinkCenter, SizeFlagsVertical = SizeFlags.ShrinkCenter, - SizeFlagsStretchRatio = 1 + SizeFlagsStretchRatio = 1, + TextureScale = (0.5f, 0.5f) }; _loadoutContainer.AddChild(previousHotbarIcon); _loadoutContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 2 }); @@ -176,10 +179,11 @@ namespace Content.Client.UserInterface _loadoutContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 2 }); var nextHotbarIcon = new TextureRect { - Texture = resourceCache.GetTexture("/Textures/Interface/Nano/right_arrow.svg.png"), + Texture = resourceCache.GetTexture("/Textures/Interface/Nano/right_arrow.svg.192dpi.png"), SizeFlagsHorizontal = SizeFlags.ShrinkCenter, SizeFlagsVertical = SizeFlags.ShrinkCenter, - SizeFlagsStretchRatio = 1 + SizeFlagsStretchRatio = 1, + TextureScale = (0.5f, 0.5f) }; _loadoutContainer.AddChild(nextHotbarIcon); _loadoutContainer.AddChild(new Control { SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 1 }); diff --git a/Resources/Textures/Interface/Nano/gear.svg b/Resources/Textures/Interface/Nano/gear.svg index ccb451d706..a4b4256d6e 100644 --- a/Resources/Textures/Interface/Nano/gear.svg +++ b/Resources/Textures/Interface/Nano/gear.svg @@ -1,3 +1,58 @@ - - + + + + + + image/svg+xml + + + + + + + diff --git a/Resources/Textures/Interface/Nano/gear.svg.192dpi.png b/Resources/Textures/Interface/Nano/gear.svg.192dpi.png new file mode 100644 index 0000000000000000000000000000000000000000..90d434da82eea1e82292531ba2969e72a3904e5b GIT binary patch literal 1460 zcmV;l1xxygP)XVAKw3den*FAZF*W%HgZwNZKf4iC2+u@>y5}sRdxWQF9oPiKYGXl zA2%IjIZy>GOxfCwqCkmcW}zzYm(ZYFVUH2^f;p~2w>ljoXcg#OkXgXcL28`EP@PKD z{?Kpat)W$|+eYf&Y#O9UFX)DBWp-Wb>Ln)&WH!c9?Kj}N7J-FDYE|Oc3QX@e2Fy_0 zrdvQ>DNx`KMIUXpb)ADiUr-{MsaNGKqG!vA=;KX|bvp|JflA!AZ2`i!?Ou>=@Wjm!@pp=AaXCmx*R8dshItyrIbAOMwAsYOLES@M_3D9EK?X1jBlV zJxu=k+TUzzKD-o|ew`U^T7csLYl^k)Q^UE)jZsw|3~q8Fvc6CwYtxf_q#l@{qFvca zw|U*m^{2wQ*H$h%9ZP05iL$oe%ZWb+=nCfKd{Jw1Ue#pd{AyLH1{MP&`ZA}xukIQB z=CWnI;U*0>Gtr6CTWK6`27Ow~39KHb%;TTW|YFw1r6011| zd|P15r%|T0)&OG)j5#MBe;9zK#@g*36{8fL1*`=A3|Gt%ksGRk;PLi*6`sTBUENprjBJiCI(%XJtbMWv=ma|Bw(WM%C;C1bR(aOk59}l) zYiqu8DCZQAyae6(#b~BPvaCn|?6_dQsMPjPN;g7`U!j7hs2nYp{0lFNH$!ru4nyWSzo2 zgHZ2F+1lBWKmdtkdbX;3PX6p=8F;{Qf*eqEp9n`mdmJ)WRI9)|06ICMdALruhq=w# zMMnz)0mPDRFNo>}kdHBLz305YecUeK#zC&Om^wYo0w@06s4b1fayT4AW;P(D~NJVBJRe?WPA!$0jUb24lGv?bzr&zYsS9FHVHp0 zcV&)E`((_J1hD`f@PJk7nS_Rnd0xgXQ3*AuM|iY@zA>lNH%2l=|E#wGN5?`a}Z(b;F#A9-v}h-9FJ=SEoRXdoeW=6|ci^ zB|p2SjTN^k)U=5R@K1Bye)se|;qR8`uDrU}i?GOF*a5e%j=8ws2n*k;V`-~K)-}3TA zS_`0zVLSyNUu@4r>wW*7Z44rls9Ogs20*Y$#c_bC%4!5rXBZSx)IWRF&sK@-8!X*B z-#5xm)E#dNd@4LEv^Mc}w46x0GL%+jJBt(*%ol6^Fery-G6@JT_(>{mWr z*aG}vdq?)DA^Imo4<^Y=t=ujFaOVB?dqhN^*y|t{XvcH2)J@x-!KuFL2)E?>7+K(* z)AxyX;NS%X(`)S0OQM_wmAS%Hq}u+alq)g`gYYdDGda>}?Z6+3kO{v2JJgMg+E$6S yVJblG*ZVPn8WtJ#%3l=(9sskDe*kQT0&W3)neCmX&TGd20000 - + + + + + + image/svg+xml + + + + + + + + diff --git a/Resources/Textures/Interface/Nano/left_arrow.svg.192dpi.png b/Resources/Textures/Interface/Nano/left_arrow.svg.192dpi.png new file mode 100644 index 0000000000000000000000000000000000000000..6539a6cb7d0e7c60a8f2219f998a128717a8ff66 GIT binary patch literal 736 zcmV<60w4W}P)@A0yQkHpW<)#isKjav#`LRfC>b`jkKl8ME`#dn8* zW16Ef2qW1Abp{6`a}WXCVtmC0#-#UpD!uqzqENZ=d@(@BT@(JYT3%TLW1iM#{rRh& zuW?{|o}Lm*y84a%s$Bk>J&4BKR?nZGQZ$tppB1DtXiXoeM10i>9Oq%PT7d_kXY!N} zI_`UNV_?9ZsYJM>fiaJEcxv^kat#OopiF3Bu_I|Ne^`(3GK2Q^(O~25pzZl2fSoyA zzM&dT#=}oQbR*&bmSe_FH|_~eB8=w6e+%5JwHBI_@yO%Nh(qZnS9L#G{U-c+=7)C} zCvH)XgixzJ>mG+E${sCLBTk@uB~I>v2LUtj)XPz)%JDJ8}< zTy{*$8Wa$Xxvk_b+5S8=E6AN1>+7&FCR>TYnm0MoXFm&C_058G0&_rvqAAbc_t7zK zOSwwQRJP00009a7bBm000#! z000#!0ilZzLjV8(8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10T@X{ zK~yM_m6E+m15pr#&)nV3N-PCQVWX&xmE;v{^cloRlnB-llDIcqSH(_=R>Az*iLa11 zu(pa=SP6Dk!n*fZh>}3EF;jns;X5;ee@+mzS17!cNno*fH#?J#jtD6M@YQ&fQqj#F zKmGM=AzJ|V0AS<>tvgvO_8O2ykj8Uf8=qKMa;)mRgP<|(Ohleo+8hLRPrOZIwm$%% zWEubf7XS){x;LLm9Y&TRz(++Vdm8ROjoR)8rnNzf8bKeB{qr#YkW|92G?SpAz?sSS z-ru5if!qMRJ+3x;T6fmQ1%_HGm%DQ+6<0wV{79$tfb_CsRe$PQ6E01u=vGcdE&#lc ptcl3|)UPoy@aJc`t16DKz5&E~av}B!%RK-9002ovPDHLkV1jUCp-2D# diff --git a/Resources/Textures/Interface/Nano/lock.svg.192dpi.png b/Resources/Textures/Interface/Nano/lock.svg.192dpi.png new file mode 100644 index 0000000000000000000000000000000000000000..6e30dbb3949823a10afd98c08f6392ca4543d166 GIT binary patch literal 869 zcmV-r1DgDaP)F@$3F(gYR|4RB8P#MXHQj*V+?B18#Ce(E~v7a68-$o1!(=ZuyZBZ0)6R z!hL%VI(K9et&7)}HH^1Vfc>^@PRQvEV8O^@sc3459#8ExsRe+9_ciKA5l*E;0a|RN z-7{_@(Cs_7x}xIP#Ef7O+3Fj1PY@SNhmPRouM`gXN+nZ^r>sp2S5}^!s<6tp;)~Xn zcV!9#Xo6FKOOBm$>(j!aN#VKpZyn`UHYh-IbMzDNG&vS!URu~d$>@UX$QB;T`{5&z znQ38Q@~8$c)R&tpz2nUZDYuigXQm}Qvm0S#2dl;q^Ez8r(Y*pI29N2)cZ7&6&#wNW2I1=L`NUMmEIjqqCdpqstQgIzxo> zd4MI_Xd~B?qk{*_z81*ah@iIvgB3cP>Lb@u>(*MQ$mzgfVBA-v7~Ta23oNwu(wHw1 z^<;5iFi@1xp6b+MEOxFK(S6xWl+C|`!6>y=b*G_QC)( v+ifq^P%3Z-@B%Ws5NYY|>M0Ls{><_d+0*{dYYl{A00000NkvXXu0mjfB-f>( literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/lock.svg.192dpi.png.yml b/Resources/Textures/Interface/Nano/lock.svg.192dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/Interface/Nano/lock.svg.192dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Interface/Nano/lock.svg.png b/Resources/Textures/Interface/Nano/lock.svg.png deleted file mode 100644 index 148cc20f5eb2004a815e20c143646362e6804f7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 446 zcmV;v0YUzWP)ISl0BtEtYX2A3jlHhJpotH6F5i6#wwL0ZBI>&=XqGy`5SlGkrg!8(|PIpr3F zx0?mNg&~6%3^ESLOTaea?3}qTVgA3$(rPbaeifmK>BC=@+MMg{0gM40jF)=4x17p< z#tOqM+hSEQUj&+DALE?+5px3D5T_6I?gr`pTZp9`>G|DO?j!X8+KC(BG%=~jpaYob oF+B{KZ73JhZz?3PS>8|gU#tm?A;vW8DgXcg07*qoM6N<$f|l*G%m4rY diff --git a/Resources/Textures/Interface/Nano/lock_open.svg.192dpi.png b/Resources/Textures/Interface/Nano/lock_open.svg.192dpi.png new file mode 100644 index 0000000000000000000000000000000000000000..729c820aa07e293fe294ab06a468f1b04cd50d80 GIT binary patch literal 915 zcmV;E18n?>P)PcF=CKV2XiJmYq7!DjXY7J^a44Bw1sU8SiG}y2U z40LJH7!PnVz(N{(G$A1*kjS5de-0i5V+aj;*<#|&h-ACn?VFzmw(M@~>u$IE4)q3$0r$OIn zV!ap``OwZaHyc_g_)D{iVQOvOJD9K(7<>!SUn6Gxfet?PMx&^OFkOs-X>D zvXle%M&*v&aVC@R$K!V(sD{1^O!szcca|6P@7s4Wxvr=C8t`&8R0&KtVC~4g#r!4; zjN$D%Xy}ZLL_q;go!UR=3?qQ6hJs4y?TAhvA%#~K+5$f`J1hVbHho*rA0N##wHz&j zjnKEycIeG!(3;oOysCXszR1Jno5X9Sl6U}fb3v(9utD$Z0vB1gY_PTO-)|Mxj-n4& z%xWm7Z1&tnHST#ZOV;;NQgypTWJfi5L6J|u&5JX?=5oV#8^8w+oL%cD<-7>*v94QMn|W5 zVgr{$c?)>zzazTdOO)S?4J?@6s4+#Kjt#8hevV9JByw!U;PGSW%P7|tS<=;<9l8=5 z7(@fFGKlDx3ip5+gfEK!tQ`c=rqk=R>^ipgqayHe^=n(J@5)?#V84smV&%~j2^4l^ zj^tnLBt(@>_5Chpivo~2oG;Mg!+WI?DCCZ%pD_pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10lG;< zK~y-6jg-4<6j2n$f9K9P!Zt#Pl_3a0K`et!1e*m5A%(SRCQ)LMPDs$~hRg2kN_s8A zMpnolU=cydqw&%B?gX1OTKNDQ!OiYD7MsBAI+MxQ-S2$A`*H7uD`aSCY3)=HotQ@C zjG$k;f4fgtSLc5Z=VaKZ`1UnZ+WrbP?mEIHnsu-Z`+h@2#}o3$|rJxCq3Dc{czXQ@~#0XLr(mvlefI z>|u5}?GH3JYP08t?;y4HH(+2c0N$^aIz;D#o&-QHL<|B{Jc9{pLqQ z9s)lQ9yERYDJz$I=4LHk02cD*xy{E_=s*onw7k61K3fE+=>!N4G{ng()b1d1AFQ}e v`d@&-OM(9Hf!Y*D+(AlR#a=gSbI<+(e+in41SQmE00000NkvXXu0mjfhuriS diff --git a/Resources/Textures/Interface/Nano/right_arrow.svg b/Resources/Textures/Interface/Nano/right_arrow.svg index eff5fdff21..b01f7192fb 100644 --- a/Resources/Textures/Interface/Nano/right_arrow.svg +++ b/Resources/Textures/Interface/Nano/right_arrow.svg @@ -1,3 +1,69 @@ - - + + + + + + image/svg+xml + + + + + + + + diff --git a/Resources/Textures/Interface/Nano/right_arrow.svg.192dpi.png b/Resources/Textures/Interface/Nano/right_arrow.svg.192dpi.png new file mode 100644 index 0000000000000000000000000000000000000000..00badb55509603d86755b2c5b1c10d071139f999 GIT binary patch literal 850 zcmV-Y1FigtP)5$i{MI8S9W3DbyZuPiM61#X%@B9d6Sou zNoG0=!8FB1shMX}vMKH&s4M@V8=+LJlu(M&S|}nIVQS)ZGWWQcc}kOtN#FEa9`5~p z_q=m?mtdrJBA=h>=N{oO4yEpETq%appsJ?3FKF|SVG~WiPPn^62_5&cmZc^@2F`Uw&p>yjK31Y@I`(?)cvct??2Yv&34H;Sg zz|G5r;i-LKl1R|*Mtx;$V7hgy=Lv*KU`b$;h@2^g(H8}co=zl)saE&Qmq&Ah-pO|0 za{z-E#M}8GoSH5aPCuMT5OZ9!bLCQQXZu>q&+RPPE9ul+A&ib5JUH2tNDxylWoH-a z_djJw9;gEw1gk#S-*+(xquq%G%eZaW;%qVT-!llKUGV1#u*W*3KMbkX=j#(EPabOA zwcwaW+nl!vu$Ma}4>n^<{z#>=|JQikng#j%On<7Up?G^c-Y@W#cXGI*x$l>+U$sD0 z-L2N<*F+lcVQd62xQ~*yv)tZ);_2VEVtp z|F;9x)0>*!Rm|Jo&IK{xV_N>mc=^q%t2x{q1*)fdmwfu&rkA6@x=#7LH#c~E?2T_% z6CAVR69r+ktLZbB3>j&M%MFYJ8F%g3wT9o;f%mo}2x|dY{HXjT@P67YR8-%&xz^wn z0)fguRpXOZiovSpe!AP>6@m_P;$&!Cv!5jmj)`aRyC8=%?%MWlh6AJ#CR>djtycHU ccQZKVZ}71gCxf)9oB#j-07*qoM6N<$g6D&lL;wH) literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Nano/right_arrow.svg.192dpi.png.yml b/Resources/Textures/Interface/Nano/right_arrow.svg.192dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/Interface/Nano/right_arrow.svg.192dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Interface/Nano/right_arrow.svg.png b/Resources/Textures/Interface/Nano/right_arrow.svg.png deleted file mode 100644 index 4a75a88e761fc9690062eb2543e59c777801e74a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 449 zcmV;y0Y3hTP)JP00009a7bBm000#6 z000#60kN{bl>h($8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10ZvIo zK~yM_jgK))n^6>opL3I_q(gr}9Yhfn95Omc!Tf}7f@0$0U`r(u>izP0iA{8fh;ef8 zOCnCXic9I*p%gL}itgef4pxii9tROb{j|?|-simMU`x@rQmHx2e_{}_UVPgwMH|K9 z*4x(w0AvCslk83%2VvurbI!adbWEpRDNm1q%!1Be``(|{MWZh?0Ekw~)B9I{lS6_Y z;Q4ODXwTZ{U5s!?tu}e_wVZe2WdtNmg5N>7?dIm{-2h_4NIS+4th$+_=S~P*lvPh1 zNP#TIKKBR#RE%CG;}Sv87*NBydVMcw?fmRwWpnf6zb8VWu=A!TeZ8P)5p+LgOuuuv zll%DtLB)p64{)=l$SH>Lip&3ayf6u1I+C$rqXqP->X+(rt_7mcooWC?WEbIA%5)7@ rm-EfI@Lhv)=o5+0D{ksfT<9UU5JYw#j{~@+00000NkvXXu0mjf@59FN From c3004b737f1333a5cacbea46943c118c5b284034 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 04:22:22 +0100 Subject: [PATCH 13/61] Update submodule --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index b205a14f69..cd01ca924b 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit b205a14f69d585f04ff2b2eba27a76926b7ba51b +Subproject commit cd01ca924b63a4068bae416f8011b0f865cc9b0a From 329d59910719886c9c18d2488ddfb5027d5a1176 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 24 Jan 2021 19:00:58 +1100 Subject: [PATCH 14/61] Fix drag-drop stripping (#3001) * Fix drag-drop stripping * More robust Co-authored-by: Metal Gear Sloth --- .../EntitySystems/DragDropSystem.cs | 9 ++++--- .../GUI/SharedStrippingComponent.cs | 27 +++++++++++++++++++ .../Entities/Mobs/Species/human.yml | 1 + 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 Content.Shared/GameObjects/Components/GUI/SharedStrippingComponent.cs diff --git a/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs b/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs index ab4e1d601c..f4ef1b72a3 100644 --- a/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/DragDropSystem.cs @@ -302,6 +302,8 @@ namespace Content.Client.GameObjects.EntitySystems foreach (var entity in entities) { + if (entity == _dragDropHelper.Dragged) continue; + // check if it's able to be dropped on by current dragged entity var dropArgs = new DragDropEventArgs(_dragger, args.Coordinates, _dragDropHelper.Dragged, entity); var valid = true; @@ -381,10 +383,9 @@ namespace Content.Client.GameObjects.EntitySystems var pvsEntities = EntityManager.GetEntitiesIntersecting(_eyeManager.CurrentMap, bounds, true); foreach (var pvsEntity in pvsEntities) { - if (!pvsEntity.TryGetComponent(out ISpriteComponent? inRangeSprite)) continue; - - // can't highlight if there's no sprite or it's not visible - if (inRangeSprite.Visible == false) continue; + if (!pvsEntity.TryGetComponent(out ISpriteComponent? inRangeSprite) || + !inRangeSprite.Visible || + pvsEntity == _dragDropHelper.Dragged) continue; var valid = (bool?) null; // check if it's able to be dropped on by current dragged entity diff --git a/Content.Shared/GameObjects/Components/GUI/SharedStrippingComponent.cs b/Content.Shared/GameObjects/Components/GUI/SharedStrippingComponent.cs new file mode 100644 index 0000000000..558a55b741 --- /dev/null +++ b/Content.Shared/GameObjects/Components/GUI/SharedStrippingComponent.cs @@ -0,0 +1,27 @@ +#nullable enable +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; + +namespace Content.Shared.GameObjects.Components.GUI +{ + /// + /// Give to an entity to say they can strip another entity. + /// + [RegisterComponent] + public class SharedStrippingComponent : Component, IDragDropOn + { + public override string Name => "Stripping"; + + public bool CanDragDropOn(DragDropEventArgs eventArgs) + { + if (!eventArgs.Dragged.TryGetComponent(out SharedStrippableComponent? strippable)) return false; + return strippable.CanBeStripped(Owner); + } + + public bool DragDropOn(DragDropEventArgs eventArgs) + { + // Handled by StrippableComponent + return true; + } + } +} diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index e85e369fe1..22dc2ab80c 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -196,6 +196,7 @@ - type: Pullable - type: DoAfter - type: CreamPied + - type: Stripping - type: Strippable - type: UserInterface interfaces: From e53ae365a36cedb1dfef2faa823c6da65d19e53e Mon Sep 17 00:00:00 2001 From: 20kdc Date: Sun, 24 Jan 2021 08:17:45 +0000 Subject: [PATCH 15/61] Get rid of the OverlayEffectsComponent stuff (#3010) * Get rid of the OverlayEffectsComponent stuff because it just ended up creating workarounds for it's bugs, without removing any functionality * Flashes and Flashbangs use the same code now (the Flashable path because it's better) --- Content.Client/EntryPoint.cs | 8 +- .../Mobs/ClientOverlayEffectsComponent.cs | 169 ------------------ .../Components/Weapons/FlashableComponent.cs | 86 +-------- .../Graphics/Overlays/CircleMaskOverlay.cs | 6 +- .../Graphics/Overlays/FlashOverlay.cs | 27 ++- .../Graphics/Overlays/GradientCircleMask.cs | 29 ++- .../StationEvents/RadiationPulseOverlay.cs | 2 +- .../Commands/Mobs/AddOverlayCommand.cs | 33 ---- .../Commands/Mobs/RemoveOverlayCommand.cs | 33 ---- .../Mobs/ServerOverlayEffectsComponent.cs | 95 ---------- .../Components/Mobs/State/CriticalMobState.cs | 10 -- .../Components/Mobs/State/DeadMobState.cs | 10 -- .../Components/Mobs/State/MobStateManager.cs | 10 -- .../Components/Weapon/Melee/FlashComponent.cs | 20 +-- .../TimedOverlayRemovalSystem.cs | 35 ---- Content.Server/GameTicking/GamePreset.cs | 6 - Content.Server/ServerNotifyManager.cs | 1 + .../StationEvents/RadiationStorm.cs | 14 -- .../Mobs/SharedOverlayEffectsComponent.cs | 120 ------------- .../Interfaces/IConfigurableOverlay.cs | 9 - .../Prototypes/Entities/Mobs/Player/human.yml | 1 - 21 files changed, 65 insertions(+), 659 deletions(-) delete mode 100644 Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs delete mode 100644 Content.Server/Commands/Mobs/AddOverlayCommand.cs delete mode 100644 Content.Server/Commands/Mobs/RemoveOverlayCommand.cs delete mode 100644 Content.Server/GameObjects/Components/Mobs/ServerOverlayEffectsComponent.cs delete mode 100644 Content.Server/GameObjects/EntitySystems/TimedOverlayRemovalSystem.cs delete mode 100644 Content.Shared/GameObjects/Components/Mobs/SharedOverlayEffectsComponent.cs delete mode 100644 Content.Shared/Interfaces/IConfigurableOverlay.cs diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index 23c3612739..a9b1309d5c 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -13,6 +13,7 @@ using Content.Client.StationEvents; using Content.Client.UserInterface; using Content.Client.UserInterface.AdminMenu; using Content.Client.UserInterface.Stylesheets; +using Content.Client.Graphics.Overlays; using Content.Shared.Actions; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components.Cargo; @@ -149,7 +150,12 @@ namespace Content.Client IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); - IoCManager.Resolve().AddOverlay(new ParallaxOverlay()); + var overlayMgr = IoCManager.Resolve(); + overlayMgr.AddOverlay(new ParallaxOverlay()); + overlayMgr.AddOverlay(new GradientCircleMaskOverlay()); + overlayMgr.AddOverlay(new CircleMaskOverlay()); + overlayMgr.AddOverlay(new FlashOverlay()); + overlayMgr.AddOverlay(new RadiationPulseOverlay()); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); diff --git a/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs deleted file mode 100644 index 2a6c1e7358..0000000000 --- a/Content.Client/GameObjects/Components/Mobs/ClientOverlayEffectsComponent.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Content.Shared.GameObjects.Components.Mobs; -using Content.Shared.Interfaces; -using Robust.Client.GameObjects; -using Robust.Client.Graphics.Overlays; -using Robust.Client.Interfaces.Graphics.Overlays; -using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Network; -using Robust.Shared.Interfaces.Reflection; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Players; -using Robust.Shared.ViewVariables; - -namespace Content.Client.GameObjects.Components.Mobs -{ - /// - /// A character UI component which shows the current damage state of the mob (living/dead) - /// - [RegisterComponent] - [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 - /// - private List _currentEffects = new(); - - [ViewVariables(VVAccess.ReadOnly)] - public List ActiveOverlays - { - get => _currentEffects; - set => SetEffects(value); - } - - public override void Initialize() - { - base.Initialize(); - - UpdateOverlays(); - } - - public override void HandleMessage(ComponentMessage message, IComponent component) - { - switch (message) - { - case PlayerAttachedMsg _: - UpdateOverlays(); - break; - case PlayerDetachedMsg _: - ActiveOverlays.ForEach(o => _overlayManager.RemoveOverlay(o.ID)); - break; - } - } - - public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession session = null) - { - base.HandleNetworkMessage(message, netChannel, session); - if (message is OverlayEffectComponentMessage overlayMessage) - { - SetEffects(overlayMessage.Overlays); - } - } - - private void UpdateOverlays() - { - _currentEffects = _overlayManager.AllOverlays - .Where(overlay => Enum.IsDefined(typeof(SharedOverlayID), overlay.ID)) - .Select(overlay => new OverlayContainer(overlay.ID)) - .ToList(); - - foreach (var overlayContainer in ActiveOverlays) - { - if (!_overlayManager.HasOverlay(overlayContainer.ID)) - { - if (TryCreateOverlay(overlayContainer, out var overlay)) - { - _overlayManager.AddOverlay(overlay); - } - } - } - - SendNetworkMessage(new ResendOverlaysMessage(), _netManager.ServerChannel); - } - - private void SetEffects(List newOverlays) - { - foreach (var container in ActiveOverlays.ToArray()) - { - if (!newOverlays.Contains(container)) - { - RemoveOverlay(container); - } - } - - foreach (var container in newOverlays) - { - if (!ActiveOverlays.Contains(container)) - { - AddOverlay(container); - } - else - { - UpdateOverlayConfiguration(container, _overlayManager.GetOverlay(container.ID)); - } - } - - _currentEffects = newOverlays; - } - - private void RemoveOverlay(OverlayContainer container) - { - ActiveOverlays.Remove(container); - _overlayManager.RemoveOverlay(container.ID); - } - - private void AddOverlay(OverlayContainer container) - { - if (_overlayManager.HasOverlay(container.ID)) - { - return; - } - - ActiveOverlays.Add(container); - if (TryCreateOverlay(container, out var overlay)) - { - _overlayManager.AddOverlay(overlay); - } - else - { - Logger.ErrorS("overlay", $"Could not add overlay {container.ID}"); - } - } - - private void UpdateOverlayConfiguration(OverlayContainer container, Overlay overlay) - { - if (overlay is IConfigurableOverlay configurable) - { - foreach (var param in container.Parameters) - { - configurable.Configure(param); - } - } - } - - private bool TryCreateOverlay(OverlayContainer container, out Overlay overlay) - { - var overlayTypes = _reflectionManager.GetAllChildren(); - var overlayType = overlayTypes.FirstOrDefault(t => t.Name == container.ID); - - if (overlayType != null) - { - overlay = IoCManager.Resolve().CreateInstance(overlayType); - UpdateOverlayConfiguration(container, overlay); - return true; - } - - overlay = default; - return false; - } - } -} diff --git a/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs b/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs index f990eb2b27..f4cc9eddf8 100644 --- a/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs +++ b/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using Content.Client.Graphics.Overlays; using Content.Shared.GameObjects.Components.Weapons; using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Overlays; @@ -19,10 +20,8 @@ namespace Content.Client.GameObjects.Components.Weapons [RegisterComponent] public sealed class FlashableComponent : SharedFlashableComponent { - private CancellationTokenSource _cancelToken; private TimeSpan _startTime; private double _duration; - private FlashOverlay _overlay; public override void HandleComponentState(ComponentState curState, ComponentState nextState) { @@ -57,94 +56,15 @@ namespace Content.Client.GameObjects.Components.Weapons if (currentTime > newEndTime) { - DisableOverlay(); return; } _startTime = newState.Time; _duration = newState.Duration; - EnableOverlay(newEndTime - currentTime); - } - - private void EnableOverlay(double duration) - { - // If the timer gets reset - if (_overlay != null) - { - _overlay.Duration = _duration; - _overlay.StartTime = _startTime; - _cancelToken.Cancel(); - } - else - { - var overlayManager = IoCManager.Resolve(); - _overlay = new FlashOverlay(_duration); - overlayManager.AddOverlay(_overlay); - } - - _cancelToken = new CancellationTokenSource(); - Owner.SpawnTimer((int) duration * 1000, DisableOverlay, _cancelToken.Token); - } - - private void DisableOverlay() - { - if (_overlay == null) - { - return; - } - var overlayManager = IoCManager.Resolve(); - overlayManager.RemoveOverlay(_overlay.ID); - _overlay = null; - _cancelToken.Cancel(); - _cancelToken = null; - } - } - - public sealed class FlashOverlay : Overlay - { - public override OverlaySpace Space => OverlaySpace.ScreenSpace; - private readonly IGameTiming _timer; - private readonly IClyde _displayManager; - public TimeSpan StartTime { get; set; } - public double Duration { get; set; } - public FlashOverlay(double duration) : base(nameof(FlashOverlay)) - { - _timer = IoCManager.Resolve(); - _displayManager = IoCManager.Resolve(); - StartTime = _timer.CurTime; - Duration = duration; - } - - protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) - { - var elapsedTime = (_timer.CurTime - StartTime).TotalSeconds; - if (elapsedTime > Duration) - { - return; - } - var screenHandle = (DrawingHandleScreen) handle; - - screenHandle.DrawRect( - new UIBox2(0.0f, 0.0f, _displayManager.ScreenSize.X, _displayManager.ScreenSize.Y), - Color.White.WithAlpha(GetAlpha(elapsedTime / Duration)) - ); - } - - private float GetAlpha(double ratio) - { - // Ideally you just want a smooth slope to finish it so it's not jarring at the end - // By all means put in a better curve - const float slope = -9.0f; - const float exponent = 0.1f; - const float yOffset = 9.0f; - const float xOffset = 0.0f; - - // Overkill but easy to adjust if you want to mess around with the design - var result = (float) MathHelper.Clamp(slope * (float) Math.Pow(ratio - xOffset, exponent) + yOffset, 0.0, 1.0); - DebugTools.Assert(!float.IsNaN(result)); - return result; + var overlay = overlayManager.GetOverlay(nameof(FlashOverlay)); + overlay.ReceiveFlash(_duration); } } } diff --git a/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs b/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs index 9e300ce897..50d7bddf28 100644 --- a/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs +++ b/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs @@ -3,6 +3,7 @@ using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Overlays; using Robust.Client.Graphics.Shaders; using Robust.Client.Interfaces.Graphics.ClientEye; +using Robust.Client.Player; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Prototypes; @@ -13,11 +14,12 @@ namespace Content.Client.Graphics.Overlays { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; public override OverlaySpace Space => OverlaySpace.WorldSpace; private readonly ShaderInstance _shader; - public CircleMaskOverlay() : base(nameof(SharedOverlayID.CircleMaskOverlay)) + public CircleMaskOverlay() : base(nameof(CircleMaskOverlay)) { IoCManager.InjectDependencies(this); _shader = _prototypeManager.Index("CircleMask").Instance(); @@ -25,6 +27,8 @@ namespace Content.Client.Graphics.Overlays protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) { + if (!GradientCircleMaskOverlay.LocalPlayerHasState(_playerManager, false, true)) + return; handle.UseShader(_shader); var worldHandle = (DrawingHandleWorld)handle; var viewport = _eyeManager.GetWorldViewport(); diff --git a/Content.Client/Graphics/Overlays/FlashOverlay.cs b/Content.Client/Graphics/Overlays/FlashOverlay.cs index 547e4dcb17..6acab964bf 100644 --- a/Content.Client/Graphics/Overlays/FlashOverlay.cs +++ b/Content.Client/Graphics/Overlays/FlashOverlay.cs @@ -1,5 +1,6 @@ using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.Interfaces; +using Content.Shared.Network.NetMessages; using Robust.Client.Graphics; using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Overlays; @@ -14,7 +15,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace Content.Client.Graphics.Overlays { - public class FlashOverlay : Overlay, IConfigurableOverlay + public class FlashOverlay : Overlay { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IClyde _displayManager = default!; @@ -22,27 +23,33 @@ namespace Content.Client.Graphics.Overlays public override OverlaySpace Space => OverlaySpace.ScreenSpace; private readonly ShaderInstance _shader; - private readonly double _startTime; - private int _lastsFor = 5000; + private double _startTime = -1; + private double _lastsFor = 1; private Texture _screenshotTexture; - public FlashOverlay() : base(nameof(SharedOverlayID.FlashOverlay)) + public FlashOverlay() : base(nameof(FlashOverlay)) { IoCManager.InjectDependencies(this); _shader = _prototypeManager.Index("FlashedEffect").Instance().Duplicate(); + } - _startTime = _gameTiming.CurTime.TotalMilliseconds; + public void ReceiveFlash(double duration) + { _displayManager.Screenshot(ScreenshotType.BeforeUI, image => { var rgba32Image = image.CloneAs(Configuration.Default); _screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image); }); + _startTime = _gameTiming.CurTime.TotalSeconds; + _lastsFor = duration; } protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) { + var percentComplete = (float) ((_gameTiming.CurTime.TotalSeconds - _startTime) / _lastsFor); + if (percentComplete >= 1.0f) + return; handle.UseShader(_shader); - var percentComplete = (float) ((_gameTiming.CurTime.TotalMilliseconds - _startTime) / _lastsFor); _shader?.SetParameter("percentComplete", percentComplete); var screenSpaceHandle = handle as DrawingHandleScreen; @@ -60,13 +67,5 @@ namespace Content.Client.Graphics.Overlays _screenshotTexture = null; } - - public void Configure(OverlayParameter parameters) - { - if (parameters is TimedOverlayParameter timedParams) - { - _lastsFor = timedParams.Length; - } - } } } diff --git a/Content.Client/Graphics/Overlays/GradientCircleMask.cs b/Content.Client/Graphics/Overlays/GradientCircleMask.cs index 5dec976b53..b60fd7d16b 100644 --- a/Content.Client/Graphics/Overlays/GradientCircleMask.cs +++ b/Content.Client/Graphics/Overlays/GradientCircleMask.cs @@ -1,8 +1,10 @@ using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs.State; using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Overlays; using Robust.Client.Graphics.Shaders; using Robust.Client.Interfaces.Graphics.ClientEye; +using Robust.Client.Player; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Prototypes; @@ -13,18 +15,43 @@ namespace Content.Client.Graphics.Overlays { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; public override OverlaySpace Space => OverlaySpace.WorldSpace; private readonly ShaderInstance _shader; - public GradientCircleMaskOverlay() : base(nameof(SharedOverlayID.GradientCircleMaskOverlay)) + public GradientCircleMaskOverlay() : base(nameof(GradientCircleMaskOverlay)) { IoCManager.InjectDependencies(this); _shader = _prototypeManager.Index("GradientCircleMask").Instance(); } + public static bool LocalPlayerHasState(IPlayerManager pm, bool critical, bool dead) { + var playerEntity = pm.LocalPlayer?.ControlledEntity; + + if (playerEntity == null) + { + return false; + } + + if (playerEntity.TryGetComponent(out var mobState)) + { + if (critical) + if (mobState.IsCritical()) + return true; + if (dead) + if (mobState.IsDead()) + return true; + } + + return false; + } + protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) { + if (!LocalPlayerHasState(_playerManager, true, false)) + return; + handle.UseShader(_shader); var worldHandle = (DrawingHandleWorld)handle; var viewport = _eyeManager.GetWorldViewport(); diff --git a/Content.Client/StationEvents/RadiationPulseOverlay.cs b/Content.Client/StationEvents/RadiationPulseOverlay.cs index d8094e3273..b08d38358a 100644 --- a/Content.Client/StationEvents/RadiationPulseOverlay.cs +++ b/Content.Client/StationEvents/RadiationPulseOverlay.cs @@ -47,7 +47,7 @@ namespace Content.Client.StationEvents // TODO: When worldHandle can do DrawCircle change this. public override OverlaySpace Space => OverlaySpace.ScreenSpace; - public RadiationPulseOverlay() : base(nameof(SharedOverlayID.RadiationPulseOverlay)) + public RadiationPulseOverlay() : base(nameof(RadiationPulseOverlay)) { IoCManager.InjectDependencies(this); _lastTick = _gameTiming.CurTime; diff --git a/Content.Server/Commands/Mobs/AddOverlayCommand.cs b/Content.Server/Commands/Mobs/AddOverlayCommand.cs deleted file mode 100644 index 4bbf196e48..0000000000 --- a/Content.Server/Commands/Mobs/AddOverlayCommand.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Server.Administration; -using Content.Server.GameObjects.Components.Mobs; -using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; -using Robust.Server.Interfaces.Player; - -namespace Content.Server.Commands.Mobs -{ - [AdminCommand(AdminFlags.Debug)] - public class AddOverlayCommand : IClientCommand - { - public string Command => "addoverlay"; - public string Description => "Adds an overlay by its ID"; - public string Help => "addoverlay "; - - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) - { - if (args.Length != 1) - { - shell.SendText(player, "Expected 1 argument."); - return; - } - - if (player?.AttachedEntity != null) - { - if (player.AttachedEntity.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent)) - { - overlayEffectsComponent.AddOverlay(args[0]); - } - } - } - } -} \ No newline at end of file diff --git a/Content.Server/Commands/Mobs/RemoveOverlayCommand.cs b/Content.Server/Commands/Mobs/RemoveOverlayCommand.cs deleted file mode 100644 index c08e8f4ff6..0000000000 --- a/Content.Server/Commands/Mobs/RemoveOverlayCommand.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Server.Administration; -using Content.Server.GameObjects.Components.Mobs; -using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; -using Robust.Server.Interfaces.Player; - -namespace Content.Server.Commands.Mobs -{ - [AdminCommand(AdminFlags.Debug)] - public class RemoveOverlayCommand : IClientCommand - { - public string Command => "rmoverlay"; - public string Description => "Removes an overlay by its ID"; - public string Help => "rmoverlay "; - - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) - { - if (args.Length != 1) - { - shell.SendText(player, "Expected 1 argument."); - return; - } - - if (player?.AttachedEntity != null) - { - if (player.AttachedEntity.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent)) - { - overlayEffectsComponent.RemoveOverlay(args[0]); - } - } - } - } -} diff --git a/Content.Server/GameObjects/Components/Mobs/ServerOverlayEffectsComponent.cs b/Content.Server/GameObjects/Components/Mobs/ServerOverlayEffectsComponent.cs deleted file mode 100644 index 6b0c7214f4..0000000000 --- a/Content.Server/GameObjects/Components/Mobs/ServerOverlayEffectsComponent.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections.Generic; -using Content.Shared.GameObjects.Components.Mobs; -using Robust.Server.Interfaces.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.Network; -using Robust.Shared.Players; -using Robust.Shared.ViewVariables; - -namespace Content.Server.GameObjects.Components.Mobs -{ - [RegisterComponent] - [ComponentReference(typeof(SharedOverlayEffectsComponent))] - public sealed class ServerOverlayEffectsComponent : SharedOverlayEffectsComponent - { - public ServerOverlayEffectsComponent() - { - NetSyncEnabled = false; - } - - [ViewVariables(VVAccess.ReadWrite)] - public List ActiveOverlays { get; } = new(); - - public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession session = null) - { - if (Owner.TryGetComponent(out IActorComponent actor) && message is ResendOverlaysMessage) - { - if (actor.playerSession.ConnectedClient == netChannel) - { - SyncClient(); - } - } - } - - public void AddOverlay(string id) => AddOverlay(new OverlayContainer(id)); - - public void AddOverlay(SharedOverlayID id) => AddOverlay(new OverlayContainer(id)); - - public void AddOverlay(OverlayContainer container) - { - if (!ActiveOverlays.Contains(container)) - { - ActiveOverlays.Add(container); - SyncClient(); - } - } - - public void RemoveOverlay(SharedOverlayID id) => RemoveOverlay(id.ToString()); - - public void RemoveOverlay(string id) => RemoveOverlay(new OverlayContainer(id)); - - public void RemoveOverlay(OverlayContainer container) - { - if (ActiveOverlays.Remove(container)) - { - SyncClient(); - } - } - - public bool TryModifyOverlay(string id, Action modifications) - { - var overlay = ActiveOverlays.Find(c => c.ID == id); - if (overlay == null) - { - return false; - } - - modifications(overlay); - SyncClient(); - return true; - } - - public void ClearOverlays() - { - if (ActiveOverlays.Count == 0) - { - return; - } - - ActiveOverlays.Clear(); - SyncClient(); - } - - private void SyncClient() - { - if (Owner.TryGetComponent(out IActorComponent actor)) - { - if (actor.playerSession.ConnectedClient.IsConnected) - { - SendNetworkMessage(new OverlayEffectComponentMessage(ActiveOverlays), actor.playerSession.ConnectedClient); - } - } - } - } -} diff --git a/Content.Server/GameObjects/Components/Mobs/State/CriticalMobState.cs b/Content.Server/GameObjects/Components/Mobs/State/CriticalMobState.cs index a7d5c7c8e0..cae7cd53e7 100644 --- a/Content.Server/GameObjects/Components/Mobs/State/CriticalMobState.cs +++ b/Content.Server/GameObjects/Components/Mobs/State/CriticalMobState.cs @@ -20,11 +20,6 @@ namespace Content.Server.GameObjects.Components.Mobs.State appearance.SetData(DamageStateVisuals.State, DamageState.Critical); } - if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) - { - overlay.AddOverlay(SharedOverlayID.GradientCircleMaskOverlay); - } - if (entity.TryGetComponent(out StunnableComponent stun)) { stun.CancelAll(); @@ -36,11 +31,6 @@ namespace Content.Server.GameObjects.Components.Mobs.State public override void ExitState(IEntity entity) { base.ExitState(entity); - - if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) - { - overlay.ClearOverlays(); - } } } } diff --git a/Content.Server/GameObjects/Components/Mobs/State/DeadMobState.cs b/Content.Server/GameObjects/Components/Mobs/State/DeadMobState.cs index 819ade09ac..26ca90cd14 100644 --- a/Content.Server/GameObjects/Components/Mobs/State/DeadMobState.cs +++ b/Content.Server/GameObjects/Components/Mobs/State/DeadMobState.cs @@ -26,11 +26,6 @@ namespace Content.Server.GameObjects.Components.Mobs.State status.ShowAlert(AlertType.HumanDead); } - if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent)) - { - overlayComponent.AddOverlay(SharedOverlayID.CircleMaskOverlay); - } - if (entity.TryGetComponent(out StunnableComponent stun)) { stun.CancelAll(); @@ -52,11 +47,6 @@ namespace Content.Server.GameObjects.Components.Mobs.State { physics.CanCollide = true; } - - if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) - { - overlay.ClearOverlays(); - } } } } diff --git a/Content.Server/GameObjects/Components/Mobs/State/MobStateManager.cs b/Content.Server/GameObjects/Components/Mobs/State/MobStateManager.cs index 9e45c1395b..298d43bae1 100644 --- a/Content.Server/GameObjects/Components/Mobs/State/MobStateManager.cs +++ b/Content.Server/GameObjects/Components/Mobs/State/MobStateManager.cs @@ -8,15 +8,5 @@ namespace Content.Server.GameObjects.Components.Mobs.State [ComponentReference(typeof(IMobStateComponent))] public class MobStateComponent : SharedMobStateComponent { - public override void OnRemove() - { - // TODO: Might want to add an OnRemove() to IMobState since those are where these components are being used - if (Owner.TryGetComponent(out ServerOverlayEffectsComponent overlay)) - { - overlay.ClearOverlays(); - } - - base.OnRemove(); - } } } diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs index 5130373ee1..06202f2166 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/FlashComponent.cs @@ -4,13 +4,17 @@ using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; +using Content.Shared.Network.NetMessages; using Robust.Server.GameObjects; using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.Player; +using Robust.Server.Interfaces.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.Timers; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Serialization; @@ -128,22 +132,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee } // TODO: Check if target can be flashed (e.g. things like sunglasses would block a flash) + // TODO: Merge with the code in FlashableComponent private void Flash(IEntity entity, IEntity user, int flashDuration) { - if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent)) + if (entity.TryGetComponent(out FlashableComponent flashable)) { - if (!overlayEffectsComponent.TryModifyOverlay(nameof(SharedOverlayID.FlashOverlay), - overlay => - { - if (overlay.TryGetOverlayParameter(out var timed)) - { - timed.Length += flashDuration; - } - })) - { - var container = new OverlayContainer(SharedOverlayID.FlashOverlay, new TimedOverlayParameter(flashDuration)); - overlayEffectsComponent.AddOverlay(container); - } + flashable.Flash(flashDuration / 1000d); } if (entity.TryGetComponent(out StunnableComponent stunnableComponent)) diff --git a/Content.Server/GameObjects/EntitySystems/TimedOverlayRemovalSystem.cs b/Content.Server/GameObjects/EntitySystems/TimedOverlayRemovalSystem.cs deleted file mode 100644 index 4994837d09..0000000000 --- a/Content.Server/GameObjects/EntitySystems/TimedOverlayRemovalSystem.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Content.Server.GameObjects.Components.Mobs; -using Content.Shared.GameObjects.Components.Mobs; -using JetBrains.Annotations; -using Robust.Shared.GameObjects.Systems; -using Robust.Shared.Interfaces.Timing; -using Robust.Shared.IoC; - -namespace Content.Server.GameObjects.EntitySystems -{ - [UsedImplicitly] - internal sealed class TimedOverlayRemovalSystem : EntitySystem - { - [Dependency] private readonly IGameTiming _gameTiming = default!; - - public override void Update(float frameTime) - { - base.Update(frameTime); - - foreach (var component in ComponentManager.EntityQuery()) - { - - foreach (var overlay in component.ActiveOverlays.ToArray()) - { - if (overlay.TryGetOverlayParameter(out var parameter)) - { - if (parameter.StartedAt + parameter.Length <= _gameTiming.CurTime.TotalMilliseconds) - { - component.RemoveOverlay(overlay); - } - } - } - } - } - } -} diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs index 1d0d14cc42..ff61818fd9 100644 --- a/Content.Server/GameTicking/GamePreset.cs +++ b/Content.Server/GameTicking/GamePreset.cs @@ -85,12 +85,6 @@ namespace Content.Server.GameTicking var ghostComponent = ghost.GetComponent(); ghostComponent.CanReturnToBody = canReturn; - if (playerEntity != null && - playerEntity.TryGetComponent(out ServerOverlayEffectsComponent? overlayComponent)) - { - overlayComponent.RemoveOverlay(SharedOverlayID.CircleMaskOverlay); - } - if (canReturn) mind.Visit(ghost); else diff --git a/Content.Server/ServerNotifyManager.cs b/Content.Server/ServerNotifyManager.cs index 6206e9838e..8dd2d978f3 100644 --- a/Content.Server/ServerNotifyManager.cs +++ b/Content.Server/ServerNotifyManager.cs @@ -1,6 +1,7 @@ using Content.Server.Administration; using Content.Server.Interfaces; using Content.Shared; +using Content.Shared.Network.NetMessages; using Content.Shared.Administration; using Content.Shared.Interfaces; using Robust.Server.Interfaces.Console; diff --git a/Content.Server/StationEvents/RadiationStorm.cs b/Content.Server/StationEvents/RadiationStorm.cs index ee88eb47cb..46c66e2423 100644 --- a/Content.Server/StationEvents/RadiationStorm.cs +++ b/Content.Server/StationEvents/RadiationStorm.cs @@ -51,25 +51,11 @@ namespace Content.Server.StationEvents public override void Startup() { ResetTimeUntilPulse(); - - var componentManager = IoCManager.Resolve(); - - foreach (var overlay in componentManager.EntityQuery()) - { - overlay.AddOverlay(SharedOverlayID.RadiationPulseOverlay); - } - base.Startup(); } public override void Shutdown() { - var componentManager = IoCManager.Resolve(); - - foreach (var overlay in componentManager.EntityQuery()) - { - overlay.RemoveOverlay(SharedOverlayID.RadiationPulseOverlay); - } base.Shutdown(); } diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedOverlayEffectsComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedOverlayEffectsComponent.cs deleted file mode 100644 index 77c65c53c8..0000000000 --- a/Content.Shared/GameObjects/Components/Mobs/SharedOverlayEffectsComponent.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.Timing; -using Robust.Shared.IoC; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Shared.GameObjects.Components.Mobs -{ - /// - /// Full screen overlays; Blindness, death, flash, alcohol etc. - /// - public abstract class SharedOverlayEffectsComponent : Component - { - public override string Name => "OverlayEffectsUI"; - - public sealed override uint? NetID => ContentNetIDs.OVERLAYEFFECTS; - } - - [Serializable, NetSerializable] - public class OverlayContainer : IEquatable, IEquatable - { - [ViewVariables(VVAccess.ReadOnly)] - public string ID { get; } - - [ViewVariables(VVAccess.ReadWrite)] - public List Parameters { get; } = new(); - - public OverlayContainer([NotNull] string id) - { - ID = id; - } - - public OverlayContainer(SharedOverlayID id) : this(id.ToString()) - { - } - - public OverlayContainer(SharedOverlayID id, params OverlayParameter[] parameters) : this(id) - { - Parameters.AddRange(parameters); - } - - public bool TryGetOverlayParameter(out T parameter) where T : OverlayParameter - { - var found = Parameters.FirstOrDefault(p => p is T); - if (found != null) - { - parameter = found as T; - return true; - } - - parameter = default; - return false; - } - - public bool Equals(string other) - { - return ID == other; - } - - public bool Equals(OverlayContainer other) - { - return ID == other?.ID; - } - - public override int GetHashCode() - { - return ID != null ? ID.GetHashCode() : 0; - } - - } - - [Serializable, NetSerializable] - public class OverlayEffectComponentMessage : ComponentMessage - { - public List Overlays; - - public OverlayEffectComponentMessage(List overlays) - { - Directed = true; - Overlays = overlays; - } - } - - [Serializable, NetSerializable] - public class ResendOverlaysMessage : ComponentMessage - { - } - - [Serializable, NetSerializable] - public abstract class OverlayParameter - { - } - - [Serializable, NetSerializable] - public class TimedOverlayParameter : OverlayParameter - { - [ViewVariables(VVAccess.ReadOnly)] - public int Length { get; set; } - - public double StartedAt { get; private set; } - - public TimedOverlayParameter(int length) - { - Length = length; - StartedAt = IoCManager.Resolve().CurTime.TotalMilliseconds; - } - } - - public enum SharedOverlayID - { - GradientCircleMaskOverlay, - CircleMaskOverlay, - FlashOverlay, - RadiationPulseOverlay, - } -} diff --git a/Content.Shared/Interfaces/IConfigurableOverlay.cs b/Content.Shared/Interfaces/IConfigurableOverlay.cs deleted file mode 100644 index e1f0a6b974..0000000000 --- a/Content.Shared/Interfaces/IConfigurableOverlay.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Shared.GameObjects.Components.Mobs; - -namespace Content.Shared.Interfaces -{ - public interface IConfigurableOverlay - { - void Configure(OverlayParameter parameter); - } -} diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index afe8be1e5b..fda0135061 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -15,7 +15,6 @@ innateActions: - HumanScream - Disarm - - type: OverlayEffectsUI - type: Eye zoom: 0.5, 0.5 - type: CameraRecoil From 72b5ae94681e436c198ca894af8d489bd38e3083 Mon Sep 17 00:00:00 2001 From: py01 <60152240+collinlunn@users.noreply.github.com> Date: Sun, 24 Jan 2021 02:42:33 -0600 Subject: [PATCH 16/61] GasGeneratorComponent (#3029) * GasGeneratorComponent * gas generator sprite * component comment * replace the other typeof with nameof * Update Resources/Textures/Constructible/Atmos/gasgenerator.rsi/meta.json Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Update Resources/Prototypes/Entities/Constructible/Ground/gasgenerator.yml Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Update Content.Server/GameObjects/Components/Atmos/Piping/GasGeneratorComponent.cs Co-authored-by: Paul Ritter * Update Content.Server/GameObjects/Components/Atmos/Piping/GasGeneratorComponent.cs Co-authored-by: Paul Ritter * specifies physics component * comments Co-authored-by: py01 Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: Paul Ritter --- Content.Client/IgnoredComponents.cs | 3 +- .../Atmos/Piping/GasCanisterPortComponent.cs | 4 +- .../Atmos/Piping/GasFilterComponent.cs | 2 +- .../Atmos/Piping/GasGeneratorComponent.cs | 105 ++++++++++++++++++ .../Atmos/Piping/Pumps/BasePumpComponent.cs | 4 +- .../Piping/Scrubbers/BaseSiphonComponent.cs | 4 +- .../Atmos/Piping/Vents/BaseVentComponent.cs | 4 +- .../Constructible/Ground/gasgenerator.yml | 45 ++++++++ .../Atmos/gasgenerator.rsi/gasGenerator.png | Bin 0 -> 1239 bytes .../Atmos/gasgenerator.rsi/meta.json | 14 +++ 10 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 Content.Server/GameObjects/Components/Atmos/Piping/GasGeneratorComponent.cs create mode 100644 Resources/Prototypes/Entities/Constructible/Ground/gasgenerator.yml create mode 100644 Resources/Textures/Constructible/Atmos/gasgenerator.rsi/gasGenerator.png create mode 100644 Resources/Textures/Constructible/Atmos/gasgenerator.rsi/meta.json diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 6823e56a43..101ce6c852 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -239,7 +239,8 @@ namespace Content.Client "Recyclable", "SecretStash", "Toilet", - "ClusterFlash" + "ClusterFlash", + "GasGenerator" }; } } diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/GasCanisterPortComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/GasCanisterPortComponent.cs index cdc701d36d..a5fbc16b0c 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/GasCanisterPortComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/GasCanisterPortComponent.cs @@ -83,13 +83,13 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping { if (!Owner.TryGetComponent(out var container)) { - Logger.Error($"{typeof(GasCanisterPortComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}."); + Logger.Error($"{nameof(GasCanisterPortComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}."); return; } _gasPort = container.Nodes.OfType().FirstOrDefault(); if (_gasPort == null) { - Logger.Error($"{typeof(GasCanisterPortComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + Logger.Error($"{nameof(GasCanisterPortComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); return; } } diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/GasFilterComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/GasFilterComponent.cs index 8a6cd18f1d..461647fd43 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/GasFilterComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/GasFilterComponent.cs @@ -171,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Filters if (_inletPipe == null || _filterOutletPipe == null || _outletPipe == null) { - Logger.Error($"{typeof(GasFilterComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + Logger.Error($"{nameof(GasFilterComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); return; } } diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/GasGeneratorComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/GasGeneratorComponent.cs new file mode 100644 index 0000000000..5a37d7a669 --- /dev/null +++ b/Content.Server/GameObjects/Components/Atmos/Piping/GasGeneratorComponent.cs @@ -0,0 +1,105 @@ +#nullable enable +using Content.Server.GameObjects.Components.NodeContainer; +using Content.Server.GameObjects.Components.NodeContainer.Nodes; +using Content.Shared.Atmos; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Log; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using System.Linq; + +namespace Content.Server.GameObjects.Components.Atmos.Piping +{ + /// + /// Generates gas in the attached pipe. + /// + [RegisterComponent] + public class GasGeneratorComponent : Component + { + public override string Name => "GasGenerator"; + + /// + /// If the generator is producing gas. + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool GeneratorEnabled { get; set; } + + /// + /// What gas is being generated. + /// + [ViewVariables(VVAccess.ReadWrite)] + public Gas GeneratedGas { get; set; } + + /// + /// Molar rate of gas generation. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float GasGenerationRate { get; set; } + + /// + /// The pipe pressure above which the generator stops producing gas. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float GeneratorPressureCap { get; set; } + + /// + /// The pipe to which generated gas is added. + /// + [ViewVariables] + private PipeNode? Pipe { get; set; } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(this, x => x.GeneratorEnabled, "generatorEnabled", true); + serializer.DataField(this, x => x.GeneratedGas, "generatedGas", Gas.Oxygen); + serializer.DataField(this, x => x.GasGenerationRate, "gasGenerationRate", 10); + serializer.DataField(this, x => x.GeneratorPressureCap, "generatorPressureCap", 10); + } + + public override void Initialize() + { + base.Initialize(); + Owner.EnsureComponentWarn(); + SetPipes(); + } + + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + switch (message) + { + case PipeNetUpdateMessage: + Update(); + break; + } + } + + private void Update() + { + if (!GeneratorEnabled) + return; + + if (Pipe == null || Pipe.Air.Pressure > GeneratorPressureCap) + return; + + Pipe.Air.AdjustMoles(GeneratedGas, GasGenerationRate); + } + + private void SetPipes() + { + if (!Owner.TryGetComponent(out var container)) + { + Logger.Error($"{nameof(GasGeneratorComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}."); + return; + } + Pipe = container.Nodes.OfType().FirstOrDefault(); + if (Pipe == null) + { + Logger.Error($"{nameof(GasGeneratorComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + return; + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs index a4c3d6a8e9..4b7eb31895 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs @@ -107,7 +107,7 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps if (!Owner.TryGetComponent(out var container)) { - Logger.Error($"{typeof(BasePumpComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}."); + Logger.Error($"{nameof(BasePumpComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}."); return; } var pipeNodes = container.Nodes.OfType(); @@ -115,7 +115,7 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps _outletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _initialOutletDirection).FirstOrDefault(); if (_inletPipe == null || _outletPipe == null) { - Logger.Error($"{typeof(BasePumpComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + Logger.Error($"{nameof(BasePumpComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); return; } } diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs index fe30881d58..a67e354a4d 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs @@ -80,13 +80,13 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Scrubbers { if (!Owner.TryGetComponent(out var container)) { - Logger.Error($"{typeof(BaseSiphonComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}."); + Logger.Error($"{nameof(BaseSiphonComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}."); return; } _scrubberOutlet = container.Nodes.OfType().FirstOrDefault(); if (_scrubberOutlet == null) { - Logger.Error($"{typeof(BaseSiphonComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + Logger.Error($"{nameof(BaseSiphonComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); return; } } diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs index db4508959b..c9b9e48f86 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs @@ -80,13 +80,13 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Vents { if (!Owner.TryGetComponent(out var container)) { - Logger.Error($"{typeof(BaseVentComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}."); + Logger.Error($"{nameof(BaseVentComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}."); return; } _ventInlet = container.Nodes.OfType().FirstOrDefault(); if (_ventInlet == null) { - Logger.Error($"{typeof(BaseVentComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); + Logger.Error($"{nameof(BaseVentComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); return; } } diff --git a/Resources/Prototypes/Entities/Constructible/Ground/gasgenerator.yml b/Resources/Prototypes/Entities/Constructible/Ground/gasgenerator.yml new file mode 100644 index 0000000000..028b736c17 --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Ground/gasgenerator.yml @@ -0,0 +1,45 @@ +- type: entity + abstract: true + id: GasGeneratorBase + placement: + mode: SnapgridCenter + components: + - type: Clickable + - type: InteractionOutline + - type: Physics + anchored: true + shapes: + - !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.5" + layer: + - Impassable + - MobImpassable + - VaultImpassable + - Opaque + mask: + - Impassable + - MobImpassable + - VaultImpassable + - type: SnapGrid + offset: Center + - type: GasGenerator + +- type: entity + parent: GasGeneratorBase + id: GasGenerator + name: gas generator + description: Fabricates gas. + components: + - type: Sprite + netsync: false + sprite: Constructible/Atmos/gasgenerator.rsi + layers: + - sprite: Constructible/Atmos/pipe.rsi + state: pipeFourway + - state: gasGenerator + - type: NodeContainer + nodes: + - !type:PipeNode + nodeGroupID: Pipe + pipeDirection: Fourway + diff --git a/Resources/Textures/Constructible/Atmos/gasgenerator.rsi/gasGenerator.png b/Resources/Textures/Constructible/Atmos/gasgenerator.rsi/gasGenerator.png new file mode 100644 index 0000000000000000000000000000000000000000..038cde0c9ba42965e97848f54d6c7ac550be6718 GIT binary patch literal 1239 zcmV;|1StE7P)Am)6vcn@@iO*|v5{lq2rGO{BnsH9v@DB|)(VY<=#dc8($P@n&!M8DNe2~B!HN~d zW+|4WFkrjEK|pMc9M5>{nep2qcoYK}Phhv$Tg{ty=iD>*-Sh5+Z?syiChf&VX)i9y zTCFC(6-Q0uRIOIy0BdV&vR13f=H_O+-`w1!R;$UiwKe&*Z<0(&6MT7jnIH&QSXkiv z`SS!pz~je{`Sa;h0DifAnJZVWAcWw>ix+(O@BtwNjYflK&z>d5NZgzR?{qp`zkZz{ z22wIg5ZkuNX0vFTM!Vfc2od|t&CTIB4wXuUd_Iq17}V=^T-QZalj}Sc z0bSSWbUGA^Mb4f*%cV<~==b|H8Vx>v{K&Cm$N25mEjsNsZ{ED2*Xv;z2E*YnKBj|g zW)ZHetdP&=5kg>^CYelzLZN`~`%|ee6bk6N&hG9mzVDOCWaxIgJbLsf_W9aT%*?>V zOw&Y4iK?pX?(QPxp3H+FKnOt)1o*y><2dNLj^j9~a8q4G6H}DEACOWK1Oc#D@q@tt z&+`}#hbW3d6h$bCf~IMxs>;oqHxExB?M*hZ&!srA@p#Ph=g$vM0AP7}nS4GEz+f<# z+S7GC&O$buMNyQPgr;d&mPHgr+`fIAMx$|Hj00=nQ^CliM~`yi#0gy2Wi%S0>pDUR z!Z1YFbreNmJRTDS0jjEESr)pkv#_v0uh-+wojZv);mjg*9ET%Ej!-U_QB@To1m$uW z+qO{@h3)NaN~IDzJ3BazgYWx{$75c1WeN;2!gosEz6?SYEiG(d!kwW28T5oI!&nb@gRFah6hrfJ5+ zJ}=P8+V}l0uu>C%WEJesLN=SlFpL9LkYxW)6`VSCiosyO($W$;J3CXp|A`7L%c9@! zqiGsK2%60%uIo~-*MYs`C=?2Ko)^ypQcC9M=V!L#)V)B{G@>X1_Ud6j@T4D1RN{Fa zVHoZO_%{f+u1gd}XqrYgn+1sb{G?3-@t$Q_aY4?S2M*N@g%I?5z4-2*1enZ<6QvA? z!_=6m2(;U6T-S|tJs1p-Qj*W-V{AMFikG5uHySX#bS|S zu^9UZA?S9ybh}-`Fy!}7V_3dYfmSOKKUFXIX>}DKZY)bnOGqi{cDv+qISj)f3`5?% zd&fA6Sh;u+AaM>j6afI+w%Od+;IFMM1>om%=VbTQD*%dT&P=_xZJTDZnF=>Gfd}jB zaS&kYxwW;$)cf^y`NM?^sc=)NU~^+5Ezd)L^AD?#U Date: Sun, 24 Jan 2021 14:18:12 +0100 Subject: [PATCH 17/61] You feel a tiny prick (Hypospray) (#3034) --- .../Chemistry/HyposprayComponent.cs | 68 +++++++++ .../Chemistry/HyposprayComponent.cs | 141 ++++++++++++++++++ .../Chemistry/SharedHyposprayComponent.cs | 26 ++++ Content.Shared/GameObjects/ContentNetIDs.cs | 3 +- Resources/Audio/Items/hypospray.ogg | Bin 0 -> 9677 bytes .../Prototypes/Entities/Objects/hypospray.yml | 15 ++ .../Specific/Medical/hypospray.rsi/hypo.png | Bin 0 -> 429 bytes .../Medical/hypospray.rsi/inhand-left.png | Bin 0 -> 319 bytes .../Medical/hypospray.rsi/inhand-right.png | Bin 0 -> 319 bytes .../Specific/Medical/hypospray.rsi/meta.json | 1 + 10 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 Content.Client/GameObjects/Components/Chemistry/HyposprayComponent.cs create mode 100644 Content.Server/GameObjects/Components/Chemistry/HyposprayComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Chemistry/SharedHyposprayComponent.cs create mode 100644 Resources/Audio/Items/hypospray.ogg create mode 100644 Resources/Prototypes/Entities/Objects/hypospray.yml create mode 100644 Resources/Textures/Objects/Specific/Medical/hypospray.rsi/hypo.png create mode 100644 Resources/Textures/Objects/Specific/Medical/hypospray.rsi/inhand-left.png create mode 100644 Resources/Textures/Objects/Specific/Medical/hypospray.rsi/inhand-right.png create mode 100644 Resources/Textures/Objects/Specific/Medical/hypospray.rsi/meta.json diff --git a/Content.Client/GameObjects/Components/Chemistry/HyposprayComponent.cs b/Content.Client/GameObjects/Components/Chemistry/HyposprayComponent.cs new file mode 100644 index 0000000000..979d371e62 --- /dev/null +++ b/Content.Client/GameObjects/Components/Chemistry/HyposprayComponent.cs @@ -0,0 +1,68 @@ +using Content.Client.UserInterface.Stylesheets; +using Content.Client.Utility; +using Content.Shared.Chemistry; +using Content.Shared.GameObjects.Components.Chemistry; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Timing; +using Robust.Shared.ViewVariables; + +#nullable enable + +namespace Content.Client.GameObjects.Components.Chemistry +{ + [RegisterComponent] + public sealed class HyposprayComponent : SharedHyposprayComponent, IItemStatus + { + [ViewVariables] private ReagentUnit CurrentVolume { get; set; } + [ViewVariables] private ReagentUnit TotalVolume { get; set; } + [ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded; + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + if (curState is not HyposprayComponentState cState) + return; + + CurrentVolume = cState.CurVolume; + TotalVolume = cState.MaxVolume; + _uiUpdateNeeded = true; + } + + Control IItemStatus.MakeControl() + { + return new StatusControl(this); + } + + private sealed class StatusControl : Control + { + private readonly HyposprayComponent _parent; + private readonly RichTextLabel _label; + + public StatusControl(HyposprayComponent parent) + { + _parent = parent; + _label = new RichTextLabel {StyleClasses = {StyleNano.StyleClassItemStatus}}; + AddChild(_label); + + parent._uiUpdateNeeded = true; + } + + protected override void Update(FrameEventArgs args) + { + base.Update(args); + if (!_parent._uiUpdateNeeded) + { + return; + } + + _parent._uiUpdateNeeded = false; + + _label.SetMarkup(Loc.GetString( + "Volume: [color=white]{0}/{1}[/color]", + _parent.CurrentVolume, _parent.TotalVolume)); + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Chemistry/HyposprayComponent.cs b/Content.Server/GameObjects/Components/Chemistry/HyposprayComponent.cs new file mode 100644 index 0000000000..8b88d21d4b --- /dev/null +++ b/Content.Server/GameObjects/Components/Chemistry/HyposprayComponent.cs @@ -0,0 +1,141 @@ +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.Components.Mobs.State; +using Content.Server.GameObjects.EntitySystems; +using Content.Shared.Chemistry; +using Content.Shared.GameObjects.Components.Chemistry; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.ComponentDependencies; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +#nullable enable + +namespace Content.Server.GameObjects.Components.Chemistry +{ + [RegisterComponent] + public sealed class HyposprayComponent : SharedHyposprayComponent, IAttack, ISolutionChange, IAfterInteract + { + [ViewVariables(VVAccess.ReadWrite)] public float ClumsyFailChance { get; set; } + [ViewVariables(VVAccess.ReadWrite)] public ReagentUnit TransferAmount { get; set; } + + [ComponentDependency] private readonly SolutionContainerComponent? _solution = default!; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(this, x => x.ClumsyFailChance, "ClumsyFailChance", 0.5f); + serializer.DataField(this, x => x.TransferAmount, "TransferAmount", ReagentUnit.New(5)); + } + + public override void Initialize() + { + base.Initialize(); + + Dirty(); + } + + bool IAttack.ClickAttack(AttackEventArgs eventArgs) + { + var target = eventArgs.TargetEntity; + var user = eventArgs.User; + + return TryDoInject(target, user); + } + + Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + { + TryDoInject(eventArgs.Target, eventArgs.User); + return Task.CompletedTask; + } + + private bool TryDoInject(IEntity? target, IEntity user) + { + if (target == null || !EligibleEntity(target)) + return false; + + var msgFormat = "You inject {0:TheName}."; + + if (target == user) + { + msgFormat = "You inject yourself."; + } + else if (EligibleEntity(user) && ClumsyComponent.TryRollClumsy(user, ClumsyFailChance)) + { + msgFormat = "Oops! You injected yourself!"; + target = user; + } + + if (_solution == null || _solution.CurrentVolume == 0) + { + user.PopupMessageCursor(Loc.GetString("It's empty!")); + return true; + } + + user.PopupMessage(Loc.GetString(msgFormat, target)); + if (target != user) + { + target.PopupMessage(Loc.GetString("You feel a tiny prick!")); + var meleeSys = EntitySystem.Get(); + var angle = new Angle(target.Transform.WorldPosition - user.Transform.WorldPosition); + meleeSys.SendLunge(angle, user); + } + + EntitySystem.Get().PlayFromEntity("/Audio/Items/hypospray.ogg", user); + + var targetSolution = target.GetComponent(); + + // Get transfer amount. May be smaller than _transferAmount if not enough room + var realTransferAmount = ReagentUnit.Min(TransferAmount, targetSolution.EmptyVolume); + + if (realTransferAmount <= 0) + { + user.PopupMessage(user, Loc.GetString("{0:TheName} is already full!", targetSolution.Owner)); + return true; + } + + // Move units from attackSolution to targetSolution + var removedSolution = _solution.SplitSolution(realTransferAmount); + + if (!targetSolution.CanAddSolution(removedSolution)) + { + return true; + } + + removedSolution.DoEntityReaction(target, ReactionMethod.Injection); + + targetSolution.TryAddSolution(removedSolution); + + static bool EligibleEntity(IEntity entity) + { + // TODO: Does checking for BodyComponent make sense as a "can be hypospray'd" tag? + // In SS13 the hypospray ONLY works on mobs, NOT beakers or anything else. + return entity.HasComponent() && entity.HasComponent(); + } + + return true; + } + + void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) + { + Dirty(); + } + + public override ComponentState GetComponentState() + { + if (_solution == null) + return new HyposprayComponentState(ReagentUnit.Zero, ReagentUnit.Zero); + + return new HyposprayComponentState(_solution.CurrentVolume, _solution.MaxVolume); + } + } +} diff --git a/Content.Shared/GameObjects/Components/Chemistry/SharedHyposprayComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/SharedHyposprayComponent.cs new file mode 100644 index 0000000000..292e7ff36d --- /dev/null +++ b/Content.Shared/GameObjects/Components/Chemistry/SharedHyposprayComponent.cs @@ -0,0 +1,26 @@ +using System; +using Content.Shared.Chemistry; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Chemistry +{ + public abstract class SharedHyposprayComponent : Component + { + public sealed override string Name => "Hypospray"; + public sealed override uint? NetID => ContentNetIDs.HYPOSPRAY; + + [Serializable, NetSerializable] + protected sealed class HyposprayComponentState : ComponentState + { + public ReagentUnit CurVolume { get; } + public ReagentUnit MaxVolume { get; } + + public HyposprayComponentState(ReagentUnit curVolume, ReagentUnit maxVolume) : base(ContentNetIDs.HYPOSPRAY) + { + CurVolume = curVolume; + MaxVolume = maxVolume; + } + } + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 0fe7b87c6c..4ce0dfa358 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -3,7 +3,8 @@ // Starting from 1000 to avoid crossover with engine. public static class ContentNetIDs { - // 1000 + // As a CMO main I hereby declare the hypospray worthy of ID #1000. + public const uint HYPOSPRAY = 1000; public const uint DESTRUCTIBLE = 1001; public const uint MAGAZINE_BARREL = 1002; public const uint HANDS = 1003; diff --git a/Resources/Audio/Items/hypospray.ogg b/Resources/Audio/Items/hypospray.ogg new file mode 100644 index 0000000000000000000000000000000000000000..92d73147a5376ea15a493152132a932a6c8e79dc GIT binary patch literal 9677 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 D3|1gw literal 0 HcmV?d00001 diff --git a/Resources/Prototypes/Entities/Objects/hypospray.yml b/Resources/Prototypes/Entities/Objects/hypospray.yml new file mode 100644 index 0000000000..a2d11fbc7e --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/hypospray.yml @@ -0,0 +1,15 @@ +- type: entity + name: hypospray + parent: BaseItem + description: A sterile injector for rapid administration of drugs to patients. + id: Hypospray + components: + - type: Sprite + sprite: Objects/Specific/Medical/hypospray.rsi + state: hypo + - type: Item + sprite: Objects/Specific/Medical/hypospray.rsi + - type: SolutionContainer + maxVol: 30 + caps: AddTo, CanExamine + - type: Hypospray diff --git a/Resources/Textures/Objects/Specific/Medical/hypospray.rsi/hypo.png b/Resources/Textures/Objects/Specific/Medical/hypospray.rsi/hypo.png new file mode 100644 index 0000000000000000000000000000000000000000..cab29e5b0afb18444eea7a58194527592dc92392 GIT binary patch literal 429 zcmV;e0aE^nP)1wNA3KCfK@OyN14*acLhe8pS2u6K!AlTtz)^8?@(wyGbjj2ls!*s^kPHfK zl-450rHRslm~*5K=3P!Q9v!LZNcCNbC6)7-1FY9- z#BrRNkCKG@;Shk~<7+N+Fz5jx1a;dck`jP!6lvAU6+x;n46XOsjPc8hkvZguU{T%q zpPNdH+;iQfPK%^mO$46ns;1*`E+tLJq2+m4mUaSqJ+Rwu18_87mC84I#sC*VKnNTL zfwr*`Knl@lH0Var>LVysD%rDTSw>^1y#wWH6)?-+2;jG*4WN|DPQsM}qzz}%ct!y5 zc@^Ic2F4&-0GmS4@Aug`yEZb{2Dt6@D3?myJdD{sy`trL+M}R-87*$T$yrdS(4Xo9 XF~@m1n0(H*00000NkvXXu0mjf=b6JS literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Specific/Medical/hypospray.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Medical/hypospray.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..e2007ee06ab73b7b219f29cb59acdb1f27ab88ce GIT binary patch literal 319 zcmV-F0l@x=P) Date: Sun, 24 Jan 2021 14:32:14 +0100 Subject: [PATCH 18/61] Fix incorrect reagents. --- Resources/Prototypes/Reagents/drinks.yml | 9 +- .../Prototypes/Recipes/Reactions/drinks.yml | 4 +- .../Prototypes/Recipes/Reactions/medicine.yml | 116 +++++++++--------- 3 files changed, 68 insertions(+), 61 deletions(-) diff --git a/Resources/Prototypes/Reagents/drinks.yml b/Resources/Prototypes/Reagents/drinks.yml index 1173cb83d6..7b7fd1d7f7 100644 --- a/Resources/Prototypes/Reagents/drinks.yml +++ b/Resources/Prototypes/Reagents/drinks.yml @@ -38,10 +38,17 @@ - type: reagent id: chem.Vodka name: vodka - desc: The glass contain wodka. Xynta. + desc: The glass contain wodka. Хуита. physicalDesc: strong-smelling color: "#d1d1d155" +- type: reagent + id: chem.Rum + name: rum + desc: Popular with the sailors. Not very popular with anyone else. + physicalDesc: strong-smelling + color: "#664300" + - type: reagent id: chem.Moonshine name: moonshine diff --git a/Resources/Prototypes/Recipes/Reactions/drinks.yml b/Resources/Prototypes/Recipes/Reactions/drinks.yml index efff2ca418..eed03ad522 100644 --- a/Resources/Prototypes/Recipes/Reactions/drinks.yml +++ b/Resources/Prototypes/Recipes/Reactions/drinks.yml @@ -65,7 +65,7 @@ reactants: chem.B52: amount: 10 - chem.U: + chem.Uranium: amount: 1 products: chem.AtomicBomb: 11 @@ -134,4 +134,4 @@ chem.Moonshine: amount: 1 products: - chem.LeanShine: 2 \ No newline at end of file + chem.LeanShine: 2 diff --git a/Resources/Prototypes/Recipes/Reactions/medicine.yml b/Resources/Prototypes/Recipes/Reactions/medicine.yml index 7675eae989..d82593e00e 100644 --- a/Resources/Prototypes/Recipes/Reactions/medicine.yml +++ b/Resources/Prototypes/Recipes/Reactions/medicine.yml @@ -13,9 +13,9 @@ - type: reaction id: react.Alkysine reactants: - chem.Cl: + chem.Chlorine: amount: 1 - chem.N: + chem.Nitrogen: amount: 1 chem.Dylovene: amount: 1 @@ -25,11 +25,11 @@ - type: reaction id: react.Dylovene reactants: - chem.Si: + chem.Silicon: amount: 1 - chem.N: + chem.Nitrogen: amount: 1 - chem.K: + chem.Potassium: amount: 1 products: chem.Dylovene: 3 @@ -39,7 +39,7 @@ reactants: chem.Hyronalin: amount: 1 - chem.H: + chem.Hydrogen: amount: 1 products: chem.Arithrazine: 2 @@ -49,7 +49,7 @@ reactants: chem.Inaprovaline: amount: 1 - chem.C: + chem.Carbon: amount: 1 products: chem.Bicaridine: 2 @@ -59,9 +59,9 @@ reactants: chem.Dexalin: amount: 1 - chem.H2O: + chem.Water: amount: 1 - chem.O: + chem.Oxygen: amount: 1 products: chem.Cryoxadone: 3 @@ -71,9 +71,9 @@ reactants: chem.Cryoxadone: amount: 1 - chem.Na: + chem.Sodium: amount: 1 - chem.Plasma: + chem.Phoron: amount: 5 catalyst: true products: @@ -84,7 +84,7 @@ reactants: chem.MindbreakerToxin: amount: 5 - chem.C: + chem.Carbon: amount: 5 products: chem.Citalopram: 10 @@ -94,9 +94,9 @@ reactants: chem.Kelotane: amount: 1 - chem.O: + chem.Oxygen: amount: 1 - chem.P: + chem.Phosphorus: amount: 1 products: chem.Dermaline: 3 @@ -104,9 +104,9 @@ - type: reaction id: react.Dexalin reactants: - chem.O: + chem.Oxygen: amount: 2 - chem.Plasma: + chem.Phoron: amount: 5 catalyst: true products: @@ -117,9 +117,9 @@ reactants: chem.Dexalin: amount: 1 - chem.C: + chem.Carbon: amount: 1 - chem.Fe: + chem.Iron: amount: 1 products: chem.DexalinPlus: 3 @@ -127,11 +127,11 @@ - type: reaction id: react.Ethylredoxrazine reactants: - chem.O: + chem.Oxygen: amount: 1 chem.Dylovene: amount: 1 - chem.C: + chem.Carbon: amount: 1 products: chem.Ethylredoxrazine: 3 @@ -141,9 +141,9 @@ reactants: chem.Glucose: amount: 1 - chem.P: + chem.Phosphorus: amount: 1 - chem.S: + chem.Sulfur: amount: 1 products: chem.Hyperzine: 3 @@ -151,7 +151,7 @@ - type: reaction id: react.Hyronalin reactants: - chem.Ra: + chem.Radium: amount: 1 chem.Dylovene: amount: 1 @@ -161,9 +161,9 @@ - type: reaction id: react.Imidazoline reactants: - chem.H: + chem.Hydrogen: amount: 1 - chem.C: + chem.Carbon: amount: 1 chem.Dylovene: amount: 1 @@ -175,9 +175,9 @@ reactants: chem.Dylovene: amount: 1 - chem.C: + chem.Carbon: amount: 1 - chem.H2O: + chem.Water: amount: 1 products: chem.Inacusiate: 3 @@ -185,9 +185,9 @@ - type: reaction id: react.Inaprovaline reactants: - chem.O: + chem.Oxygen: amount: 1 - chem.C: + chem.Carbon: amount: 1 chem.Glucose: amount: 1 @@ -197,9 +197,9 @@ - type: reaction id: react.Kelotane reactants: - chem.Si: + chem.Silicon: amount: 1 - chem.C: + chem.Carbon: amount: 1 products: chem.Kelotane: 2 @@ -207,11 +207,11 @@ - type: reaction id: react.Leporazine reactants: - chem.Si: + chem.Silicon: amount: 1 - chem.Cu: + chem.Copper: amount: 1 - chem.Plasma: + chem.Phoron: amount: 5 catalyst: true products: @@ -220,13 +220,13 @@ - type: reaction id: react.Methylin reactants: - chem.H: + chem.Hydrogen: amount: 1 - chem.Cl: + chem.Chlorine: amount: 1 chem.Ethanol: amount: 1 - chem.F: + chem.Fluorine: amount: 5 catalyst: true products: @@ -239,7 +239,7 @@ amount: 1 chem.Tramadol: amount: 1 - chem.Plasma: + chem.Phoron: amount: 1 products: chem.Oxycodone: 3 @@ -261,7 +261,7 @@ reactants: chem.MindbreakerToxin: amount: 5 - chem.O: + chem.Oxygen: amount: 5 chem.Inaprovaline: amount: 5 @@ -273,7 +273,7 @@ reactants: chem.Arithrazine: amount: 1 - chem.C: + chem.Carbon: amount: 1 products: chem.Ryetalyn: 2 @@ -291,11 +291,11 @@ - type: reaction id: react.Synaptizine reactants: - chem.Li: + chem.Lithium: amount: 1 chem.Glucose: amount: 1 - chem.H2O: + chem.Water: amount: 1 products: chem.Synaptizine: 3 @@ -307,7 +307,7 @@ amount: 1 chem.Ethanol: amount: 1 - chem.O: + chem.Oxygen: amount: 1 products: chem.Tramadol: 3 @@ -325,11 +325,11 @@ - type: reaction id: react.Vaccine reactants: - chem.Al: + chem.Aluminium: amount: 1 chem.Glucose: amount: 1 - chem.H2O: + chem.Water: amount: 1 products: chem.Vaccine: 3 @@ -349,9 +349,9 @@ reactants: chem.Ethanol: amount: 1 - chem.Cl: + chem.Chlorine: amount: 3 - chem.H2O: + chem.Water: amount: 1 products: chem.ChloralHydrate: 5 @@ -371,9 +371,9 @@ - type: reaction id: react.Cryptobiolin reactants: - chem.K: + chem.Potassium: amount: 1 - chem.O: + chem.Oxygen: amount: 1 chem.Glucose: amount: 1 @@ -393,9 +393,9 @@ - type: reaction id: react.Impedrezene reactants: - chem.Hg: + chem.Mercury: amount: 1 - chem.O: + chem.Oxygen: amount: 1 chem.Glucose: amount: 1 @@ -407,7 +407,7 @@ reactants: chem.Ammonia: amount: 1 - chem.Plasma: + chem.Phoron: amount: 1 products: chem.Lexorin: 2 @@ -419,7 +419,7 @@ amount: 1 chem.Ethanol: amount: 1 - chem.Ra: + chem.Radium: amount: 1 products: chem.Lipozine: 3 @@ -427,9 +427,9 @@ - type: reaction id: react.MindbreakerToxin reactants: - chem.Si: + chem.Silicon: amount: 1 - chem.H: + chem.Hydrogen: amount: 1 chem.Dylovene: amount: 1 @@ -453,7 +453,7 @@ amount: 1 chem.Dylovene: amount: 1 - chem.Cl: + chem.Chlorine: amount: 1 products: chem.Sterilizine: 3 @@ -461,11 +461,11 @@ - type: reaction id: react.SpaceDrugs reactants: - chem.Hg: + chem.Mercury: amount: 1 chem.Glucose: amount: 1 - chem.Li: + chem.Lithium: amount: 1 products: chem.SpaceDrugs: 3 From e34a78a8af041978670309ccfeef11ab02ef60eb Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 14:33:16 +0100 Subject: [PATCH 19/61] Fix chloral hydrate recipe to be more expensive. --- Resources/Prototypes/Recipes/Reactions/medicine.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Recipes/Reactions/medicine.yml b/Resources/Prototypes/Recipes/Reactions/medicine.yml index d82593e00e..6d50158ee7 100644 --- a/Resources/Prototypes/Recipes/Reactions/medicine.yml +++ b/Resources/Prototypes/Recipes/Reactions/medicine.yml @@ -354,7 +354,7 @@ chem.Water: amount: 1 products: - chem.ChloralHydrate: 5 + chem.ChloralHydrate: 1 - type: reaction id: react.Creatine #Add nutriment as ingredient (amount = 1) once that's a thing From 935cfe4be5706a0ae47d2fd33f74803861ef1655 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 15:03:23 +0100 Subject: [PATCH 20/61] A little bit of work on bar drinks until I gave up. --- .../Specific/Dispensers/booze_dispenser.yml | 1 + .../Objects/Consumable/drinks_bottles.yml | 26 ++++++++++++------- Resources/Prototypes/Reagents/drinks.yml | 20 +++++++++++++- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/Resources/Prototypes/Entities/Constructible/Specific/Dispensers/booze_dispenser.yml b/Resources/Prototypes/Entities/Constructible/Specific/Dispensers/booze_dispenser.yml index 66b2844c9e..24b5de3306 100644 --- a/Resources/Prototypes/Entities/Constructible/Specific/Dispensers/booze_dispenser.yml +++ b/Resources/Prototypes/Entities/Constructible/Specific/Dispensers/booze_dispenser.yml @@ -21,3 +21,4 @@ - chem.Vodka - chem.Cognac - chem.Kahlua + - chem.Rum diff --git a/Resources/Prototypes/Entities/Objects/Consumable/drinks_bottles.yml b/Resources/Prototypes/Entities/Objects/Consumable/drinks_bottles.yml index 82da85a1da..be7543e45c 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/drinks_bottles.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/drinks_bottles.yml @@ -6,6 +6,7 @@ - type: Drink openSounds: bottleOpenSounds - type: SolutionContainer + maxVol: 100 - type: Pourable transferAmount: 5 - type: Sprite @@ -34,13 +35,17 @@ - type: entity parent: DrinkBottleBaseFull id: DrinkAbsintheBottleFull - name: jailbreaker verte + name: Jailbreaker Verte description: One sip of this and you just know you're gonna have a good time. components: - type: Drink - type: Sprite sprite: Objects/Consumable/Drinks/absinthebottle.rsi - + - type: SolutionContainer + contents: + reagents: + - ReagentId: chem.Absinthe + Quantity: 100 - type: entity parent: DrinkBottleBaseFull @@ -56,15 +61,15 @@ - type: entity parent: DrinkBottleBaseFull id: DrinkAleBottleFull - name: magm-ale + name: Magm-Ale description: A true dorf's drink of choice. components: - type: SolutionContainer - maxVol: 80 + maxVol: 100 contents: reagents: - ReagentId: chem.Ale - Quantity: 80 + Quantity: 100 - type: Sprite sprite: Objects/Consumable/Drinks/alebottle.rsi @@ -87,11 +92,10 @@ description: A sweet and strongly alchoholic drink, made after numerous distillations and years of maturing. You might as well not scream 'SHITCURITY' this time. components: - type: SolutionContainer - maxVol: 80 contents: reagents: - ReagentId: chem.Cognac - Quantity: 80 + Quantity: 100 - type: Sprite sprite: Objects/Consumable/Drinks/cognacbottle.rsi @@ -99,13 +103,17 @@ - type: entity parent: DrinkBottleBaseFull id: DrinkGinBottleFull - name: griffeater gin bottle + name: Griffeater Gin description: A bottle of high quality gin, produced in the New London Space Station. components: - type: Drink - type: Sprite sprite: Objects/Consumable/Drinks/ginbottle.rsi - + - type: SolutionContainer + contents: + reagents: + - ReagentId: chem.Gin + Quantity: 100 - type: entity parent: DrinkBottleBaseFull diff --git a/Resources/Prototypes/Reagents/drinks.yml b/Resources/Prototypes/Reagents/drinks.yml index 7b7fd1d7f7..f11b3ec508 100644 --- a/Resources/Prototypes/Reagents/drinks.yml +++ b/Resources/Prototypes/Reagents/drinks.yml @@ -1,3 +1,11 @@ +- type: reagent + id: chem.Absinthe + name: absinthe + desc: One sip of this and you just know you're gonna have a good time. + spritePath: absintheglass.rsi + color: "#33EE00" + physicalDesc: strong-smelling + - type: reagent id: chem.Whiskey name: whiskey @@ -38,9 +46,18 @@ - type: reagent id: chem.Vodka name: vodka - desc: The glass contain wodka. Хуита. + desc: The glass contain wodka. Хуета. physicalDesc: strong-smelling color: "#d1d1d155" + spritePath: ginvodkaglass.rsi + +- type: reagent + id: chem.Gin + name: gin + desc: A crystal clear glass of Griffeater gin. + physicalDesc: strong-smelling + color: "#664300" + spritePath: ginvodkaglass.rsi - type: reagent id: chem.Rum @@ -48,6 +65,7 @@ desc: Popular with the sailors. Not very popular with anyone else. physicalDesc: strong-smelling color: "#664300" + spritePath: rumglass.rsi - type: reagent id: chem.Moonshine From 436694e3767cef03d0ade324d5fa73779c87f650 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 16:06:03 +0100 Subject: [PATCH 21/61] Explosions can be spawned at any coordinate, play sound again. --- .../Commands/ExplosionCommand.cs | 41 +++++++++++++ Content.Server/Explosions/ExplosionHelper.cs | 59 +++++++++---------- 2 files changed, 70 insertions(+), 30 deletions(-) create mode 100644 Content.Server/Administration/Commands/ExplosionCommand.cs diff --git a/Content.Server/Administration/Commands/ExplosionCommand.cs b/Content.Server/Administration/Commands/ExplosionCommand.cs new file mode 100644 index 0000000000..768f01b8d5 --- /dev/null +++ b/Content.Server/Administration/Commands/ExplosionCommand.cs @@ -0,0 +1,41 @@ +using Content.Server.Explosions; +using Content.Shared.Administration; +using Robust.Server.Interfaces.Console; +using Robust.Server.Interfaces.Player; +using Robust.Shared.Map; + +#nullable enable + +namespace Content.Server.Administration.Commands +{ + [AdminCommand(AdminFlags.Fun)] + public sealed class ExplosionCommand : IClientCommand + { + public string Command => "explode"; + public string Description => "Train go boom"; + public string Help => "Usage: explode \n" + + "The explosion happens on the same map as the user."; + + public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + { + if (player?.AttachedEntity == null) + { + shell.SendText(player, "You must have an attached entity."); + return; + } + + var x = float.Parse(args[0]); + var y = float.Parse(args[1]); + + var dev = int.Parse(args[2]); + var hvy = int.Parse(args[3]); + var lgh = int.Parse(args[4]); + var fla = int.Parse(args[5]); + + var mapTransform = player.AttachedEntity.Transform.GetMapTransform(); + var coords = new EntityCoordinates(mapTransform.Owner.Uid, x, y); + + ExplosionHelper.SpawnExplosion(coords, dev, hvy, lgh, fla); + } + } +} diff --git a/Content.Server/Explosions/ExplosionHelper.cs b/Content.Server/Explosions/ExplosionHelper.cs index 5479db7b3a..60962b67ec 100644 --- a/Content.Server/Explosions/ExplosionHelper.cs +++ b/Content.Server/Explosions/ExplosionHelper.cs @@ -1,33 +1,27 @@ using System; using System.Collections.Generic; using System.Linq; -using Content.Server.GameObjects.Components.Atmos; using Content.Server.GameObjects.Components.Explosion; -using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Mobs; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Maps; using Content.Shared.Physics; using Content.Shared.Utility; -using Microsoft.Extensions.Logging; using Robust.Server.GameObjects.EntitySystems; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.EntitySystemMessages; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Random; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; -using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; -using Robust.Shared.Physics; using Robust.Shared.Random; -using Robust.Shared.Timing; namespace Content.Server.Explosions { @@ -151,7 +145,7 @@ namespace Content.Server.Explosions /// damage bracket [light, heavy, devastation], the distance from the epicenter and /// a probabilty bracket [, , 1.0]. /// - /// + /// private static void DamageTilesInRange(EntityCoordinates epicenter, GridId gridId, Box2 boundingBox, @@ -281,9 +275,31 @@ namespace Content.Server.Explosions } } - private static void Detonate(IEntity source, int devastationRange, int heavyImpactRange, int lightImpactRange, int flashRange) + public static void SpawnExplosion(this IEntity entity, int devastationRange = 0, int heavyImpactRange = 0, + int lightImpactRange = 0, int flashRange = 0) { - var mapId = source.Transform.MapID; + // If you want to directly set off the explosive + if (!entity.Deleted && entity.TryGetComponent(out ExplosiveComponent explosive) && !explosive.Exploding) + { + explosive.Explosion(); + } + else + { + while (entity.TryGetContainer(out var cont)) + { + entity = cont.Owner; + } + + var epicenter = entity.Transform.Coordinates; + + SpawnExplosion(epicenter, devastationRange, heavyImpactRange, lightImpactRange, flashRange); + } + } + + public static void SpawnExplosion(EntityCoordinates epicenter, int devastationRange = 0, + int heavyImpactRange = 0, int lightImpactRange = 0, int flashRange = 0) + { + var mapId = epicenter.GetMapId(IoCManager.Resolve()); if (mapId == MapId.Nullspace) { return; @@ -291,18 +307,14 @@ namespace Content.Server.Explosions var maxRange = MathHelper.Max(devastationRange, heavyImpactRange, lightImpactRange, 0); - while(source.TryGetContainer(out var cont)) - { - source = cont.Owner; - } - var epicenter = source.Transform.Coordinates; - var entityManager = IoCManager.Resolve(); var mapManager = IoCManager.Resolve(); var epicenterMapPos = epicenter.ToMapPos(entityManager); - var boundingBox = new Box2(epicenterMapPos - new Vector2(maxRange, maxRange), epicenterMapPos + new Vector2(maxRange, maxRange)); + var boundingBox = new Box2(epicenterMapPos - new Vector2(maxRange, maxRange), + epicenterMapPos + new Vector2(maxRange, maxRange)); + EntitySystem.Get().PlayAtCoords("/Audio/Effects/explosion.ogg", epicenter); DamageEntitiesInRange(epicenter, boundingBox, devastationRange, heavyImpactRange, maxRange, mapId); var mapGridsNear = mapManager.FindGridsIntersecting(mapId, boundingBox); @@ -315,18 +327,5 @@ namespace Content.Server.Explosions CameraShakeInRange(epicenter, maxRange); FlashInRange(epicenter, flashRange); } - - public static void SpawnExplosion(this IEntity entity, int devastationRange = 0, int heavyImpactRange = 0, int lightImpactRange = 0, int flashRange = 0) - { - // If you want to directly set off the explosive - if (!entity.Deleted && entity.TryGetComponent(out ExplosiveComponent explosive) && !explosive.Exploding) - { - explosive.Explosion(); - } - else - { - Detonate(entity, devastationRange, heavyImpactRange, lightImpactRange, flashRange); - } - } } } From 526c3d1ebfea3337f44b23ce50a6121f984c5aa3 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 16:21:18 +0100 Subject: [PATCH 22/61] You can now rig power cells to explode. VERY funny. --- .../Components/Power/BatteryComponent.cs | 4 +- .../Components/Power/PowerCellComponent.cs | 54 +++++++++++++++++-- .../Barrels/ServerBatteryBarrelComponent.cs | 6 ++- .../Entities/Objects/Power/powercells.yml | 4 ++ 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/Content.Server/GameObjects/Components/Power/BatteryComponent.cs b/Content.Server/GameObjects/Components/Power/BatteryComponent.cs index 887f9420f0..5235cf69fe 100644 --- a/Content.Server/GameObjects/Components/Power/BatteryComponent.cs +++ b/Content.Server/GameObjects/Components/Power/BatteryComponent.cs @@ -53,7 +53,7 @@ namespace Content.Server.GameObjects.Components.Power /// /// If sufficient charge is avaiable on the battery, use it. Otherwise, don't. /// - public bool TryUseCharge(float chargeToUse) + public virtual bool TryUseCharge(float chargeToUse) { if (chargeToUse >= CurrentCharge) { @@ -66,7 +66,7 @@ namespace Content.Server.GameObjects.Components.Power } } - public float UseCharge(float toDeduct) + public virtual float UseCharge(float toDeduct) { var chargeChangedBy = Math.Min(CurrentCharge, toDeduct); CurrentCharge -= chargeChangedBy; diff --git a/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs b/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs index 8a16a7c7da..61c321c497 100644 --- a/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs @@ -1,4 +1,7 @@ -using Content.Shared.GameObjects.Components.Power; +using System; +using Content.Server.Explosions; +using Content.Server.GameObjects.Components.Chemistry; +using Content.Shared.GameObjects.Components.Power; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Utility; using Robust.Server.GameObjects; @@ -8,6 +11,8 @@ using Robust.Shared.Serialization; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; +#nullable enable + namespace Content.Server.GameObjects.Components.Power { /// @@ -16,13 +21,15 @@ namespace Content.Server.GameObjects.Components.Power /// [RegisterComponent] [ComponentReference(typeof(BatteryComponent))] - public class PowerCellComponent : BatteryComponent, IExamine + public class PowerCellComponent : BatteryComponent, IExamine, ISolutionChange { public override string Name => "PowerCell"; [ViewVariables] public PowerCellSize CellSize => _cellSize; private PowerCellSize _cellSize = PowerCellSize.Small; + [ViewVariables] public bool IsRigged { get; private set; } + public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); @@ -42,9 +49,41 @@ namespace Content.Server.GameObjects.Components.Power UpdateVisuals(); } + public override bool TryUseCharge(float chargeToUse) + { + if (IsRigged) + { + Explode(); + return false; + } + + return base.TryUseCharge(chargeToUse); + } + + public override float UseCharge(float toDeduct) + { + if (IsRigged) + { + Explode(); + return 0; + } + + return base.UseCharge(toDeduct); + } + + private void Explode() + { + var heavy = (int) Math.Ceiling(Math.Sqrt(CurrentCharge) / 60); + var light = (int) Math.Ceiling(Math.Sqrt(CurrentCharge) / 30); + + CurrentCharge = 0; + Owner.SpawnExplosion(0, heavy, light, light*2); + Owner.Delete(); + } + private void UpdateVisuals() { - if (Owner.TryGetComponent(out AppearanceComponent appearance)) + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) { appearance.SetData(PowerCellVisuals.ChargeLevel, GetLevel(CurrentCharge / MaxCharge)); } @@ -57,11 +96,18 @@ namespace Content.Server.GameObjects.Components.Power void IExamine.Examine(FormattedMessage message, bool inDetailsRange) { - if(inDetailsRange) + if (inDetailsRange) { message.AddMarkup(Loc.GetString($"The charge indicator reads {CurrentCharge / MaxCharge * 100:F0} %.")); } } + + void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) + { + IsRigged = Owner.TryGetComponent(out SolutionContainerComponent? solution) + && solution.Solution.ContainsReagent("chem.Phoron", out var phoron) + && phoron >= 5; + } } public enum PowerCellSize diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs index c6a7d7a178..eecee54097 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs @@ -169,7 +169,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels // Multiply the entity's damage / whatever by the percentage of charge the shot has. IEntity entity; var chargeChange = Math.Min(capacitor.CurrentCharge, _baseFireCost); - capacitor.UseCharge(chargeChange); + if (capacitor.UseCharge(chargeChange) < _lowerChargeLimit) + { + // Handling of funny exploding cells. + return null; + } var energyRatio = chargeChange / _baseFireCost; if (_ammoContainer.ContainedEntity != null) diff --git a/Resources/Prototypes/Entities/Objects/Power/powercells.yml b/Resources/Prototypes/Entities/Objects/Power/powercells.yml index d94314de03..02af2ec187 100644 --- a/Resources/Prototypes/Entities/Objects/Power/powercells.yml +++ b/Resources/Prototypes/Entities/Objects/Power/powercells.yml @@ -15,6 +15,10 @@ - type: PowerCell - type: Sprite netsync: false + - type: SolutionContainer + maxVol: 5 + caps: AddTo, RemoveFrom + - type: entity id: PowerCellSmallBase From 961f80254db044bb80a7c3395f844e9c3d1d7b14 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 16:38:01 +0100 Subject: [PATCH 23/61] You now stop breathing while in crit. --- .../GameObjects/Components/Body/Behavior/LungBehavior.cs | 6 ++++++ .../Components/Metabolism/MetabolismComponent.cs | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Content.Server/GameObjects/Components/Body/Behavior/LungBehavior.cs b/Content.Server/GameObjects/Components/Body/Behavior/LungBehavior.cs index 4c60bdecfb..cf402231ec 100644 --- a/Content.Server/GameObjects/Components/Body/Behavior/LungBehavior.cs +++ b/Content.Server/GameObjects/Components/Body/Behavior/LungBehavior.cs @@ -8,6 +8,7 @@ using Content.Server.GameObjects.Components.Body.Respiratory; using Content.Server.Utility; using Content.Shared.Atmos; using Content.Shared.GameObjects.Components.Body.Behavior; +using Content.Shared.GameObjects.Components.Mobs.State; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Localization; @@ -99,6 +100,11 @@ namespace Content.Server.GameObjects.Components.Body.Behavior public override void Update(float frameTime) { + if (Body != null && Body.Owner.TryGetComponent(out IMobStateComponent? mobState) && mobState.IsCritical()) + { + return; + } + if (Status == LungStatus.None) { Status = LungStatus.Inhaling; diff --git a/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs b/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs index 180e73897b..b9f98a6ec4 100644 --- a/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs +++ b/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs @@ -206,10 +206,13 @@ namespace Content.Server.GameObjects.Components.Metabolism if (bloodstreamAmount < amountNeeded) { - // Panic inhale - foreach (var lung in lungs) + if (!Owner.GetComponent().IsCritical()) { - lung.Gasp(); + // Panic inhale + foreach (var lung in lungs) + { + lung.Gasp(); + } } bloodstreamAmount = bloodstream.Air.GetMoles(gas); From 690a862e57c28a550436d6a71fcbf08be101bf35 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 22:58:22 +0100 Subject: [PATCH 24/61] Move various alerts to animated RSIs. --- Resources/Prototypes/Alerts/alerts.yml | 30 +++++++++++++----- .../Alerts/Pressure/highpressure1.png | Bin 591 -> 0 bytes .../Alerts/Pressure/highpressure2.png | Bin 578 -> 0 bytes .../Alerts/Pressure/lowpressure1.png | Bin 601 -> 0 bytes .../Alerts/Pressure/lowpressure2.png | Bin 506 -> 0 bytes .../Interface/Alerts/Pressure/meta.json | 27 ---------------- .../Interface/Alerts/Temperature/cold1.png | Bin 396 -> 0 bytes .../Interface/Alerts/Temperature/cold2.png | Bin 388 -> 0 bytes .../Interface/Alerts/Temperature/cold3.png | Bin 3295 -> 0 bytes .../Interface/Alerts/Temperature/hot1.png | Bin 365 -> 0 bytes .../Interface/Alerts/Temperature/hot2.png | Bin 333 -> 0 bytes .../Interface/Alerts/Temperature/hot3.png | Bin 3121 -> 0 bytes .../Alerts/human_health.rsi/health0.png | Bin 0 -> 232 bytes .../Alerts/human_health.rsi/health1.png | Bin 0 -> 212 bytes .../Alerts/human_health.rsi/health2.png | Bin 0 -> 212 bytes .../Alerts/human_health.rsi/health3.png | Bin 0 -> 212 bytes .../Alerts/human_health.rsi/health4.png | Bin 0 -> 212 bytes .../Alerts/human_health.rsi/health5.png | Bin 0 -> 361 bytes .../Alerts/human_health.rsi/health6.png | Bin 0 -> 323 bytes .../Alerts/human_health.rsi/health7.png | Bin 0 -> 207 bytes .../Alerts/human_health.rsi/health_numb.png | Bin 0 -> 256 bytes .../Alerts/human_health.rsi/meta.json | 1 + .../Alerts/pressure.rsi/highpressure1.png | Bin 0 -> 354 bytes .../Alerts/pressure.rsi/highpressure2.png | Bin 0 -> 405 bytes .../Alerts/pressure.rsi/lowpressure1.png | Bin 0 -> 353 bytes .../Alerts/pressure.rsi/lowpressure2.png | Bin 0 -> 354 bytes .../Interface/Alerts/pressure.rsi/meta.json | 1 + .../Alerts/temperature.rsi/cold1.png | Bin 0 -> 242 bytes .../Alerts/temperature.rsi/cold2.png | Bin 0 -> 240 bytes .../Alerts/temperature.rsi/cold3.png | Bin 0 -> 339 bytes .../Interface/Alerts/temperature.rsi/hot1.png | Bin 0 -> 242 bytes .../Interface/Alerts/temperature.rsi/hot2.png | Bin 0 -> 232 bytes .../Interface/Alerts/temperature.rsi/hot3.png | Bin 0 -> 319 bytes .../Alerts/temperature.rsi/meta.json | 1 + 34 files changed, 25 insertions(+), 35 deletions(-) delete mode 100644 Resources/Textures/Interface/Alerts/Pressure/highpressure1.png delete mode 100644 Resources/Textures/Interface/Alerts/Pressure/highpressure2.png delete mode 100644 Resources/Textures/Interface/Alerts/Pressure/lowpressure1.png delete mode 100644 Resources/Textures/Interface/Alerts/Pressure/lowpressure2.png delete mode 100644 Resources/Textures/Interface/Alerts/Pressure/meta.json delete mode 100644 Resources/Textures/Interface/Alerts/Temperature/cold1.png delete mode 100644 Resources/Textures/Interface/Alerts/Temperature/cold2.png delete mode 100644 Resources/Textures/Interface/Alerts/Temperature/cold3.png delete mode 100644 Resources/Textures/Interface/Alerts/Temperature/hot1.png delete mode 100644 Resources/Textures/Interface/Alerts/Temperature/hot2.png delete mode 100644 Resources/Textures/Interface/Alerts/Temperature/hot3.png create mode 100644 Resources/Textures/Interface/Alerts/human_health.rsi/health0.png create mode 100644 Resources/Textures/Interface/Alerts/human_health.rsi/health1.png create mode 100644 Resources/Textures/Interface/Alerts/human_health.rsi/health2.png create mode 100644 Resources/Textures/Interface/Alerts/human_health.rsi/health3.png create mode 100644 Resources/Textures/Interface/Alerts/human_health.rsi/health4.png create mode 100644 Resources/Textures/Interface/Alerts/human_health.rsi/health5.png create mode 100644 Resources/Textures/Interface/Alerts/human_health.rsi/health6.png create mode 100644 Resources/Textures/Interface/Alerts/human_health.rsi/health7.png create mode 100644 Resources/Textures/Interface/Alerts/human_health.rsi/health_numb.png create mode 100644 Resources/Textures/Interface/Alerts/human_health.rsi/meta.json create mode 100644 Resources/Textures/Interface/Alerts/pressure.rsi/highpressure1.png create mode 100644 Resources/Textures/Interface/Alerts/pressure.rsi/highpressure2.png create mode 100644 Resources/Textures/Interface/Alerts/pressure.rsi/lowpressure1.png create mode 100644 Resources/Textures/Interface/Alerts/pressure.rsi/lowpressure2.png create mode 100644 Resources/Textures/Interface/Alerts/pressure.rsi/meta.json create mode 100644 Resources/Textures/Interface/Alerts/temperature.rsi/cold1.png create mode 100644 Resources/Textures/Interface/Alerts/temperature.rsi/cold2.png create mode 100644 Resources/Textures/Interface/Alerts/temperature.rsi/cold3.png create mode 100644 Resources/Textures/Interface/Alerts/temperature.rsi/hot1.png create mode 100644 Resources/Textures/Interface/Alerts/temperature.rsi/hot2.png create mode 100644 Resources/Textures/Interface/Alerts/temperature.rsi/hot3.png create mode 100644 Resources/Textures/Interface/Alerts/temperature.rsi/meta.json diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index ba9ee9ec27..5be4593ab4 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -29,7 +29,9 @@ - type: alert alertType: LowPressure category: Pressure - icon: /Textures/Interface/Alerts/Pressure/lowpressure.png + icon: + sprite: /Textures/Interface/Alerts/pressure.rsi + state: lowpressure maxSeverity: 2 name: "[color=red]Low Pressure[/color]" description: "The air around you is [color=red]hazardously thin[/color]. A [color=green]space suit[/color] would protect you." @@ -37,7 +39,9 @@ - type: alert alertType: HighPressure category: Pressure - icon: /Textures/Interface/Alerts/Pressure/highpressure.png + icon: + sprite: /Textures/Interface/Alerts/pressure.rsi + state: highpressure maxSeverity: 2 name: "[color=red]High Pressure[/color]" description: "The air around you is [color=red]hazardously thick[/color]. A [color=green]pressurized suit[/color] would be enough protect you" @@ -52,7 +56,9 @@ - type: alert alertType: Cold category: Temperature - icon: /Textures/Interface/Alerts/Temperature/cold.png + icon: + sprite: /Textures/Interface/Alerts/temperature.rsi + state: cold maxSeverity: 3 name: "[color=cyan]Too Cold[/color]" description: "You're [color=cyan]freezing cold![/color] Get somewhere warmer and take off any insulating clothing like a space suit." @@ -60,7 +66,9 @@ - type: alert alertType: Hot category: Temperature - icon: /Textures/Interface/Alerts/Temperature/hot.png + icon: + sprite: /Textures/Interface/Alerts/temperature.rsi + state: hot maxSeverity: 3 name: "[color=red]Too Hot[/color]" description: "It's [color=red]too hot![/color] Flee to space or at least away from the flames. Standing on weeds will heal you." @@ -70,7 +78,7 @@ icon: /Textures/Interface/Alerts/Weightless/weightless.png name: Weightless description: > - Gravity has ceased affecting you, and you're floating around aimlessly. Find something sturdy to hold onto, or throw or shoot something in a direction opposite of you. + Gravity has ceased affecting you, and you're floating around aimlessly. Find something sturdy to hold onto, or throw or shoot something in a direction opposite of you. Mag-boots or jetpacks would help you move with more control - type: alert @@ -96,21 +104,27 @@ - type: alert alertType: HumanCrit category: Health - icon: /Textures/Interface/Alerts/Human/humancrit-0.png + icon: + sprite: /Textures/Interface/Alerts/human_health.rsi + state: health6 name: "[color=red]Critical Condition[/color]" description: "You're severely injured and unconscious." - type: alert alertType: HumanDead category: Health - icon: /Textures/Interface/Alerts/Human/humandead.png + icon: + sprite: /Textures/Interface/Alerts/human_health.rsi + state: health7 name: Dead description: You're dead, note that you can still be revived! - type: alert alertType: HumanHealth category: Health - icon: /Textures/Interface/Alerts/Human/human.png + icon: + sprite: /Textures/Interface/Alerts/human_health.rsi + state: health name: Health description: "[color=green]Green[/color] good. [color=red]Red[/color] bad." minSeverity: 0 diff --git a/Resources/Textures/Interface/Alerts/Pressure/highpressure1.png b/Resources/Textures/Interface/Alerts/Pressure/highpressure1.png deleted file mode 100644 index daebd742be0abda30eb116df931d2857f89b9cea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 591 zcmV-V03SN=sAP_r6?EDLoLIlf5z$TTI&4G4T2of7j zX`|`(KuL}}yebySoIBsMxeIw=Np>?c?=v6oOcu7R$oTY-#YWuT>3%xEA&cVPM(jzy z=@2(v6&msX?1J#qHBEaw!Ib>~Hm8W&tNYy8=p!`=aK z0K5bsd6Y1J7Q=B99)AvP);|J5=U_B0&8x)0b=;L~(EwMMb>J|NVHfusFp#5^*YyA(VH0wSnf1qB1B zjhk{_)!Ydn3`~uloI*MnYR`*aw=#q^hUs8PIvQ)nQqSux=}JfA;2qEl5UQe+UtXMj zuM81%7Kl&NsK9!29|Z7V=ecA~z2$^lN6r)+pT7cf$-L;~QW?`?RH0lW3DS+h=OK|m;lnj^r0ILSp$#{so07b96QHY=^ zP$5E;4u;O?Rsv|mGDJ|h)%I*#d_vwA6VnM;j-u_bD%ThHS9&o~?_HbAwf!O4#Hv@d d^_SDF>mOg4)}B91JL>=d002ovPDHLkV1n~65+48n diff --git a/Resources/Textures/Interface/Alerts/Pressure/highpressure2.png b/Resources/Textures/Interface/Alerts/Pressure/highpressure2.png deleted file mode 100644 index 85f74289aaa222102c91954f6cc10faf76aa18ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 578 zcmV-I0=@l-P)1u5NR_CrClCu#bGKGWJ2iiSAHZ;Hqz-Jb^A{MASW%^0_yLTt-GGFe>Efyb z3zr?#N_XNG%+L$Cj-A+PtG-gy#QyB}>F3Xh3~)= zAGqwp%df7}HfNyJ?YH&%|9rJF_6N_e0XY7B<81B)phDQ>YYrYu5PW}BY3rSwZ0srd z^uhY(^$r0dU_Kmjbzg|8R?8(z^%*FN!nze(0jLng7Vm+r08|Kv{o18iZ3^ zcl56o{FEXE|1YUrYMZHFkJDpKCoboMiT+&oa@pC(XqN>Sr{?y9A5IUhzuV^2I9u(q Q?f?J)07*qoM6N<$f*P?BdjJ3c diff --git a/Resources/Textures/Interface/Alerts/Pressure/lowpressure1.png b/Resources/Textures/Interface/Alerts/Pressure/lowpressure1.png deleted file mode 100644 index 27153cc54276cf663a52c5c07dc5926eb1aa1766..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 601 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 diff --git a/Resources/Textures/Interface/Alerts/Temperature/hot1.png b/Resources/Textures/Interface/Alerts/Temperature/hot1.png deleted file mode 100644 index 2d3896f7b0562801aa20e39e11c8340a2cee48f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 365 zcmV-z0h0cSP)b;@5Jf*x3Q(l%2`C4pNE4A0@g{&=2yx(LaUwq3 zch0CFL_x9QWm$sx2=Vj%1)4mlAZ{f9p@uDFD23o-J!N zjj1o;EfO0Rml{*GD~?Iyiq&&LN}bFFmk@0LolHg{MAN=zK)tFC^6HRmMA`O200000 LNkvXXu0mjf&1ah` diff --git a/Resources/Textures/Interface/Alerts/Temperature/hot2.png b/Resources/Textures/Interface/Alerts/Temperature/hot2.png deleted file mode 100644 index 7408c204c81f5617bfe2b12d6e1a19a13f6ef47a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 333 zcmV-T0kZyyP)dwOGxPnxQ5Z#*in0c*~Dmvg@LSOmz~{6DBNEG zJHvmOgzyI#Sreb8G(oLA1HFSnV#)zf73YMV5bb>dAOcVV*b}u7TsZ>(z!|{Rr}0++TjjbK z10YNwOdw1kOyIvJpvwTZ2{g57g4*^+j9kp)oG=qYYrXpos0AK@DnSdn1x~Jv__gH2PjixW*sHs)Wxxv&s73<`aioN#&lLpxfx)3&iK{g{5 fqVL>iK^;|ZUHyh+Pv_fY00000NkvXXu0mjf{&9`u diff --git a/Resources/Textures/Interface/Alerts/Temperature/hot3.png b/Resources/Textures/Interface/Alerts/Temperature/hot3.png deleted file mode 100644 index d74178208fc62ef2a087c0988b00026df1152c4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3121 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 LNkvXXu0mjf+9m-y diff --git a/Resources/Textures/Interface/Alerts/human_health.rsi/health0.png b/Resources/Textures/Interface/Alerts/human_health.rsi/health0.png new file mode 100644 index 0000000000000000000000000000000000000000..9330c4928296b4d5cbd219e5367c014b935da77f GIT binary patch literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvi2$DvS0K$0$G}j^z_5aWVK2iH zZideS4FCWC|8n5s44@QaNswPKgTu2MX+Tb$r;B5V#`&caFY-1h@VLBXi@Cy>!W=Zm zLCoNopoe=x?Lq#9|1!S(`&IN-^j7J7*2URL%N^fGM*rGi!?cKp^Fe$8!_--hv%MP_ cKYTsGu0KU~&12S*=^!V2y85}Sb4q9e0EXOFrT_o{ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/human_health.rsi/health1.png b/Resources/Textures/Interface/Alerts/human_health.rsi/health1.png new file mode 100644 index 0000000000000000000000000000000000000000..c6232e16c005241da13d7853e7dcc8c5ce595d15 GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv{s5m4S0Jqw$6#8@;Jtz&bT0!Z zlNKjXh_NKdFPOpM*^M+Hr`XfQF+}71(uo&&8x(k4cJl6cz_iJ+I49xze9s?&t2)(A z#mC<(f4N z=qh>5a!|@zt9?kRi@z!DEz_q@;sp&CZXLYCZ8Z7MuanldI7Qh6=bB6cTF>C=>gTe~ HDWM4f@}^4X literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/human_health.rsi/health2.png b/Resources/Textures/Interface/Alerts/human_health.rsi/health2.png new file mode 100644 index 0000000000000000000000000000000000000000..51cf805d9aaf3e2cae76cabb104c8770cec6b14c GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv{s5m4S0Eh}$Bb5ZllS6ex0S3j3^ HP6C6uMTCA;yv*zhDN3XE)M-oMKNG#}JM4ODA6BZBXEG*~z=(0n;YO;+%x<^F4nAuIf}f z6(4`Ekax+Kp9aSzH*t6chjiuVJ51+zCG|YdVA_uJk|*w(&t5NcY^B4S1E&@qFkp~7 zpsVCH%Rwn?t@a_MF8-#pw@jZti5E0nxOMOjx6$N3zfM};;uK{QoNF=(Xg!0ctDnm{ Hr-UW|3NK8y literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/human_health.rsi/health5.png b/Resources/Textures/Interface/Alerts/human_health.rsi/health5.png new file mode 100644 index 0000000000000000000000000000000000000000..fc96021bfe34c234e71aab93e98d57d327eb94e5 GIT binary patch literal 361 zcmV-v0ha!WP)+f8^$DLq#el8d1!CMgCPn*cKq`cMUw zfpw}12v7j1!0HR&KLGx1!N|-85D^79gkc!2P*ad!mgV7VisE4NA$&2Y+QA%V18nQ| z26SH*7{AX3umI}0sQ@Nr23S9L26Rq9>=4_R@HYaZfQgQ)4A+A80f#E+x!~X^bh64-? zw?G^QAm{&oAo?G@zc~gd&sY-V7tG-B>_!@pbJ^3yF~q`u?<7OMLkc2o@hJwflDk)` zD^#xZd|uVu7Pnxc!LFB_QpYCw-3i;^%Gf;j&Hh?_d3ynd8#*Tz7}(GFAf_g>nJ4u4 z>y_q_ZC)|zZ|uE;>UdjT9G7uET6t%J!36K%mg>5Vv5f~;25)N@Yr3v>{P*ukt>w#A z<0^Q(G&Gm&I$NW&a^bCvst-*il2@WH=&ACuTo>S4rzkdwZI;=ZT*EWGm$;waS+I9Q zmGAdIcj_8!m|xz~6B63ST-Mh*W4piw-iUde+nXQ6X)-&ebj)~a|D5yggi7ACi!KHL Py~p6`>gTe~DWM4fnM{AV literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/human_health.rsi/health7.png b/Resources/Textures/Interface/Alerts/human_health.rsi/health7.png new file mode 100644 index 0000000000000000000000000000000000000000..9fb45eca0b3d0e9842c342a0fcc6b8d52b122199 GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvp#Yx{S0F7TBV#Kh6dW8hlY!y? z|Nncq_O* zVAyvlahr%y_xAq`*FqJJ?JAZ?e4Fmk;+pd9qnD4qfEUnj`Mq2B3=6Kl+{KGzo}{v`E*HX#zv-p4eN!~(##lI?)?^x z_CA}zbS9y4D))}#`yE8v!dP#2yYzKDcvi#UKV5D8Dv78RpbHs1UHx3vIVCg!04z6N A1poj5 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/human_health.rsi/meta.json b/Resources/Textures/Interface/Alerts/human_health.rsi/meta.json new file mode 100644 index 0000000000..e34c0728f1 --- /dev/null +++ b/Resources/Textures/Interface/Alerts/human_health.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA 3.0", "copyright": "Taken from https://github.com/vgstation-coders/vgstation13 at 1d52615ca647d96583f5bc7a1017f3910194bed3", "states": [{"name": "health0", "directions": 1, "delays": [[1.0]]}, {"name": "health1", "directions": 1, "delays": [[1.0]]}, {"name": "health2", "directions": 1, "delays": [[1.0]]}, {"name": "health3", "directions": 1, "delays": [[1.0]]}, {"name": "health4", "directions": 1, "delays": [[1.0]]}, {"name": "health5", "directions": 1, "delays": [[0.5, 0.5]]}, {"name": "health6", "directions": 1, "delays": [[0.5, 0.5]]}, {"name": "health7", "directions": 1, "delays": [[1.0]]}, {"name": "health_numb", "directions": 1, "delays": [[1.0]]}]} \ No newline at end of file diff --git a/Resources/Textures/Interface/Alerts/pressure.rsi/highpressure1.png b/Resources/Textures/Interface/Alerts/pressure.rsi/highpressure1.png new file mode 100644 index 0000000000000000000000000000000000000000..92c8aa0eb255587253f69c8b359b8426621188bf GIT binary patch literal 354 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyt^qzFu0R?DH0CoHpJ9k&VC28X zbm>2H>wm7yAN=#@2+YgSna^))Ygi1V?oo7(`)!(u21J?%mU;qA9_yynS)E4-gn2<0}IFRA71?CHSvx&Tfy7R?ia>s4A##M+&ILt#6x<5ndAbk wGtEg%HdCg%?v)qsfBGQhRKu?qe`W5?+9#)}Qgzopr00W1OUjP6A literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/pressure.rsi/highpressure2.png b/Resources/Textures/Interface/Alerts/pressure.rsi/highpressure2.png new file mode 100644 index 0000000000000000000000000000000000000000..34cc305c84663aee6e86e59c5265556109d69ff5 GIT binary patch literal 405 zcmV;G0c!qmz)YR1g0Pw62@$vEUZw>zd z0RI#N|7QYGJ=om<0004WQchC$+_#0(d`H zIf?56=ZC=M96-e!;6F)F1n_3`j+k15z73fT|!Kq%hG| zw|`93`~2ZTTN?`rdp=lrpE2H>wm7yAN=#@2+YgSna`h;l$4vBTT@fB_XGcxD_1_O;s5yYyt#nk(*A1!OGJ587(`Y#Z0KvO z5Nud_`}B5}jGwnfSIFsIQT?DJx}Yj^Qu<1U?{e~jug-`}n8El|Q0s*6D~0}(4Z8x& xwchP${g!(1&C{dF)dj0U9{hXp*Je-oPbR6&(zRkgb@V_1<>~6@vd$@?2>`YnlxzS1 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/pressure.rsi/lowpressure2.png b/Resources/Textures/Interface/Alerts/pressure.rsi/lowpressure2.png new file mode 100644 index 0000000000000000000000000000000000000000..3c266180bea6e578adc2cc0bfde7d807c894e88e GIT binary patch literal 354 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QceLrA+A6g1T^L|7@uK?V_@XJ z#&qdFbL)S0c6P4JAN=0``RC6On3th5pFb%nY3~RAD_5?3Si}GEKCoDV;kEOD<$Um0>chobLL7`jk0a%o zPA4UY-7ph6@LND)TEtdam4@$z1`ZDr7}oGNW*QxPqq*U|%7P#Ar~a2p9`$Yf9R0Sj zPB@z3mk#HGa)yFPEe0`81yNCjz5BQ(oL@d^w+7Rc&o_=(b!Ru+`u*#}z5na*S0K%Jt%LdhB7u1s#taOL z7#Pl+Jb8|R;mVaOj~_q&`0?X^28Jmg&;J3bD+%%oW^j0RBMr!@@pN$v(U_Q=aDZ<{ zZ!d2E3lA%6YoG{+rlzKnwqZ!rRxeM9$i{0u+gv@oX0%S*QIfKdW6c`};RVyXK52+F zUVWOuq_Wx|glVP25BV3r-#&f1l;eMMuY*~O4x@>)@dLqTl$Xv$2u;;BK%R}*ydZ0TPJYD@<);T3K0RW6pR0#k8 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/temperature.rsi/cold2.png b/Resources/Textures/Interface/Alerts/temperature.rsi/cold2.png new file mode 100644 index 0000000000000000000000000000000000000000..3ba601625aac6207faa52d14d1ac4aa8c36c8647 GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvl>na*S0K&Uc#V11e}VLQ#taOL z7#Pl+K6#FT;mVaOj~_q&`0?X^28NiA)Axhal?3?(GdMiEkp|>cdAc};XiQ8_IKVff zx0g47g@={3HBf{@Q&Uq(+b|?*tCy!lWaG7-ZLS_(Gg_zZC`nn!vF43~u!NV^sl^jm zc&3I;pYE>shyTmpZ=XH|wfvvi+Q4;c;+F}k4QU!DZe0ja$#(P#TkFJExHYaZfQn~>?A+A80k^dTF{xxQi|IDrb z1*GQ*M9(v3U|7V!aOU*Ma|{euu3UNi`0>Y&AOABj{J+3>8>FEm$S;_|;n|HeAm@sw zi(`nz>Er|jz71_{Yzm)mUb-X{Ai~1M)ur@#@{}o80(b<>%*Jb zm?4nCbE&c8y^6Siyx{!yUJBj!<;OA#Qli*>JNKE9WHbSFg&~*-uQNP=;{C& z&PU3sryL$vuePe1xEI99ULaWhe$ld5yUQmAn_l2ha5yRM&@g9f1ZyLMWx6IKqt`PY iR;H9XDFK0*?-_-s9d^HcxLy_LKL$@%KbLh*2~7a!NQF%R literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/temperature.rsi/hot1.png b/Resources/Textures/Interface/Alerts/temperature.rsi/hot1.png new file mode 100644 index 0000000000000000000000000000000000000000..ee137750543123c93fffc52f438c29257b311442 GIT binary patch literal 242 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvl>na*S0HW7z>t>4FmoouA_j&t zr%#?a!*Gs);mVaOj~_q&`0?X^28Q(ATh;^BF_r}R1v5B2yO9Ru)Ofl$hG?8mPB_3f zW6Bhf0G27ey}bbY$Pn|mG1T67pqoE@+2+My819keM+v}gtMR0hVP mD~6KHj9yQ)7#aIY7#QmJcxHwln13JW4hBzGKbLh*2~7Y1#8!g< literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/temperature.rsi/hot2.png b/Resources/Textures/Interface/Alerts/temperature.rsi/hot2.png new file mode 100644 index 0000000000000000000000000000000000000000..c23d5859b41529da0ed50bd042ab9bab1007b8db GIT binary patch literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvl>na*S0HW7z>sFfFf)*05d*`S z9ELNePo867xN_ynOgc+Kz;=bBc%kYkOWyRgJ2ttrlq zS_1#rf7Ml2R&pNtpP)Q}SJ9i{Y`}!AdU_t$nx?FYYL$8Apz6>dQ|fcfp+RV;mqUZH aEYM0>x5>r_)=vf6&fw|l=d#Wzp$P!t0#2C# literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/temperature.rsi/hot3.png b/Resources/Textures/Interface/Alerts/temperature.rsi/hot3.png new file mode 100644 index 0000000000000000000000000000000000000000..df70e373e879502ac8abd5170aabd3fdc8b70fe9 GIT binary patch literal 319 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!VDw>HYaZfQYryHA+A8$n1LaUjbSDi!#oLr zMGOpQcp1(yFkItf{P^+Xe+GvCBFs084m$(YF_r}R1v5B2yO9Ruob+^Y46!(!oS?vG zpsA^;@HsLjMkYW+Lt0u|@w2U!Ra5|vfw#A}n^Du!>hJGlPq=9;KRah;TT09EcQcL4 zw<}0b*tx1i>JZ1YcXL;Jr7KGwOfd`GU?UXC*wMczALnZc!FZNfg51Dk=K OV(@hJb6Mw<&;$U;0Bkb= literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/temperature.rsi/meta.json b/Resources/Textures/Interface/Alerts/temperature.rsi/meta.json new file mode 100644 index 0000000000..e1cc97be28 --- /dev/null +++ b/Resources/Textures/Interface/Alerts/temperature.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA 3.0", "copyright": "Taken from https://github.com/tgstation/tgstation at 1c7401722f397d8ac8afdd10e550c5962e0f38d4", "states": [{"name": "cold1", "directions": 1, "delays": [[1.0]]}, {"name": "cold2", "directions": 1, "delays": [[1.0]]}, {"name": "cold3", "directions": 1, "delays": [[0.3, 0.3]]}, {"name": "hot1", "directions": 1, "delays": [[1.0]]}, {"name": "hot2", "directions": 1, "delays": [[1.0]]}, {"name": "hot3", "directions": 1, "delays": [[0.3, 0.3]]}]} \ No newline at end of file From 9f7f55d2648f9d8eaac6a1ffb372bc11fd9d741e Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 23:02:16 +0100 Subject: [PATCH 25/61] Update submodule. --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index cd01ca924b..3e5efd5ed0 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit cd01ca924b63a4068bae416f8011b0f865cc9b0a +Subproject commit 3e5efd5ed06d85d4c874d6369c26569a67e3242a From 080846d3969b4dfb48264e7b45a2b5933f0221de Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 23:02:29 +0100 Subject: [PATCH 26/61] Make AlertControl use AnimatedTextureRect for animated alert icons. --- .../UserInterface/Controls/AlertControl.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Content.Client/UserInterface/Controls/AlertControl.cs b/Content.Client/UserInterface/Controls/AlertControl.cs index 019a899417..383a7db448 100644 --- a/Content.Client/UserInterface/Controls/AlertControl.cs +++ b/Content.Client/UserInterface/Controls/AlertControl.cs @@ -3,7 +3,6 @@ using System; using Content.Shared.Alert; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; -using Robust.Client.Utility; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Maths; @@ -34,11 +33,12 @@ namespace Content.Client.UserInterface.Controls } } } + private (TimeSpan Start, TimeSpan End)? _cooldown; private short? _severity; private readonly IGameTiming _gameTiming; - private readonly TextureRect _icon; + private readonly AnimatedTextureRect _icon; private readonly CooldownGraphic _cooldownGraphic; /// @@ -53,17 +53,17 @@ namespace Content.Client.UserInterface.Controls TooltipSupplier = SupplyTooltip; Alert = alert; _severity = severity; - var texture = alert.GetIcon(_severity).Frame0(); - _icon = new TextureRect + var specifier = alert.GetIcon(_severity); + _icon = new AnimatedTextureRect { - TextureScale = (2, 2), - Texture = texture + DisplayRect = {TextureScale = (2, 2)} }; + _icon.SetFromSpriteSpecifier(specifier); + Children.Add(_icon); _cooldownGraphic = new CooldownGraphic(); Children.Add(_cooldownGraphic); - } private Control SupplyTooltip(Control? sender) @@ -79,7 +79,7 @@ namespace Content.Client.UserInterface.Controls if (_severity != severity) { _severity = severity; - _icon.Texture = Alert.GetIcon(_severity).Frame0(); + _icon.SetFromSpriteSpecifier(Alert.GetIcon(_severity)); } } @@ -99,7 +99,7 @@ namespace Content.Client.UserInterface.Controls var progress = (curTime - Cooldown.Value.Start).TotalSeconds / length; var ratio = (progress <= 1 ? (1 - progress) : (curTime - Cooldown.Value.End).TotalSeconds * -5); - _cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1); + _cooldownGraphic.Progress = MathHelper.Clamp((float) ratio, -1, 1); _cooldownGraphic.Visible = ratio > -1f; } } From c465253a2aef4aaa918a86a4871bfde63d0cee79 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 24 Jan 2021 23:03:52 +0100 Subject: [PATCH 27/61] Delete unused human alerts folder. --- .../Textures/Interface/Alerts/Human/human0.png | Bin 233 -> 0 bytes .../Textures/Interface/Alerts/Human/human1.png | Bin 214 -> 0 bytes .../Textures/Interface/Alerts/Human/human2.png | Bin 214 -> 0 bytes .../Textures/Interface/Alerts/Human/human3.png | Bin 214 -> 0 bytes .../Textures/Interface/Alerts/Human/human4.png | Bin 214 -> 0 bytes .../Textures/Interface/Alerts/Human/human5.png | Bin 214 -> 0 bytes .../Textures/Interface/Alerts/Human/human6.png | Bin 214 -> 0 bytes .../Textures/Interface/Alerts/Human/human7.png | Bin 240 -> 0 bytes .../Interface/Alerts/Human/humancrit-0.png | Bin 252 -> 0 bytes .../Interface/Alerts/Human/humancrit-1.png | Bin 252 -> 0 bytes .../Textures/Interface/Alerts/Human/humandead.png | Bin 207 -> 0 bytes 11 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Resources/Textures/Interface/Alerts/Human/human0.png delete mode 100644 Resources/Textures/Interface/Alerts/Human/human1.png delete mode 100644 Resources/Textures/Interface/Alerts/Human/human2.png delete mode 100644 Resources/Textures/Interface/Alerts/Human/human3.png delete mode 100644 Resources/Textures/Interface/Alerts/Human/human4.png delete mode 100644 Resources/Textures/Interface/Alerts/Human/human5.png delete mode 100644 Resources/Textures/Interface/Alerts/Human/human6.png delete mode 100644 Resources/Textures/Interface/Alerts/Human/human7.png delete mode 100644 Resources/Textures/Interface/Alerts/Human/humancrit-0.png delete mode 100644 Resources/Textures/Interface/Alerts/Human/humancrit-1.png delete mode 100644 Resources/Textures/Interface/Alerts/Human/humandead.png diff --git a/Resources/Textures/Interface/Alerts/Human/human0.png b/Resources/Textures/Interface/Alerts/Human/human0.png deleted file mode 100644 index ca94b41e6eab6eb22c7cc7e2b53c3d4cf445f065..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvi2$Dv*8>L*FvKx1tYBc+%fL{| zu!Ni8vjD^Y|Nr^f=0pJ1FqQ=Q1v5B2yO9Ru)O)%(hG(35+AOYr zI}o=e%)n6H@8hNeb1bxHas6P_FxFaNoVuD_xkbQcOSizu_6E~!%vpc9H!w_`gTe~DWM4f_gGV{ diff --git a/Resources/Textures/Interface/Alerts/Human/human1.png b/Resources/Textures/Interface/Alerts/Human/human1.png deleted file mode 100644 index 68b189bc6aab8a202cd1e343456c09f870831e5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv{s5m4*8>L*FvKx1tYBc+%fL{| z@K>SxB~Xg7B*-tA!Qt7BG$5zc)5S4FW8%^Y7kQZ#d7MA8&G2iu`M~I)=NtRrhtK(f zSBBpH^Ymq8-=s_D55KVvk)5Iz?j}k z_yKQ4XU8P52_5%-b6UIn6WDUgY=ZoMCclKW%xm^47-#DLPMfFxSfF*|wYi&s4q)(f L^>bP0l+XkK*E~-B diff --git a/Resources/Textures/Interface/Alerts/Human/human2.png b/Resources/Textures/Interface/Alerts/Human/human2.png deleted file mode 100644 index ad2acb4ad030cbfd656f6724b9a3c3512e9eed92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv{s5m4*8>L*XvHyjuV4t>%V1i{ zpvyFSDo~2CB*-tA!Qt7BG$5zc)5S4FW8%^Y7kQZ#d7MA8&G2iu`M~I)=NtRrhtK(f zSBBpH^Ymq8-=s_D55KVvk)5Iz?j}k z_yKQ4XU8P52_5%-b6UIn6WDUgY=ZoMCclKW%xm^47-#DLPMfFxSfF*|wYi&s4q)(f L^>bP0l+XkKzw}PZ diff --git a/Resources/Textures/Interface/Alerts/Human/human3.png b/Resources/Textures/Interface/Alerts/Human/human3.png deleted file mode 100644 index 0888dd79a781750f47ea693ffd152f5e59d3f932..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv{s5m4*8>L*1jR8-Ucsp&^Sk|4ie28U-i(tw;&PZ!4!jfqPqT;yd|co*42@`b4iBOcBaCHC_PqD z;Rn1GogI_JCUo5U&1voOPhiU}vkCJ5nfwygGOyXIV4SJ{J8ho&V}aI<*XC{lI)K5` L)z4*}Q$iB}xuj53 diff --git a/Resources/Textures/Interface/Alerts/Human/human4.png b/Resources/Textures/Interface/Alerts/Human/human4.png deleted file mode 100644 index 8eed659e972d0a660bc330c22c8bef3976f7c93b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv{s5m4*8>L*#5pjmC}h}M$586W z@MUfJHJ}t@NswPKgTu2MX+Tb?r;B5V#>Ax)F7h%f@;HBFo8i}R^MTPp&o}nL51;b| zuMEBY=jqGHzDbwPAAVyUB0EJb+)HMD!#0jzFMk`nP!-H(>*`3}xggTe~DWM4f2TV}c diff --git a/Resources/Textures/Interface/Alerts/Human/human5.png b/Resources/Textures/Interface/Alerts/Human/human5.png deleted file mode 100644 index 8b4e8a4b9ef741da6ac8b33ade2ceb6a5ddefb1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv{s5m4*8>L*#3?bXuwmHi#89fq z!0}k z_yKQ4XU8P52_5%-b6UIn6WDUgY=ZoMCclKW%xm^47-#DLPMfFxSfF*|wYi&s4q)(f L^>bP0l+XkK{l`x* diff --git a/Resources/Textures/Interface/Alerts/Human/human6.png b/Resources/Textures/Interface/Alerts/Human/human6.png deleted file mode 100644 index 05f0233a7328a5719b3f0462c44e817be2cb726d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv{s5m4*8>L*#IZ1}U}xCN#!$+_ zU~S&13zT9k3GxeOaCmkj4ah0=ba4#Pn7DMpMP6n_9_Np2GyEEEJ}^4y`Nlr@;d8#= zm7%x)JbfA2H|f&(!*8rZWT&Wwd&$gi*v9eeL*G%zsy|Ns9MkYr#u zz`!7;!YKumWh@Eu3ubV5b|VeQ>G5=N4AGdlbizU2!wNjEZ+#B%%PuHke!ZEYOZ-IR z-*C+sq2*JnJZe8*KJ4%G-LRrFv}xNz_HI`>qo!yNaOqt@0neEA`s l(Bxg(a3fTKal-7+Y@0qw`EVRKZvu1|gQu&X%Q~loCIIX;UM&Cs diff --git a/Resources/Textures/Interface/Alerts/Human/humancrit-0.png b/Resources/Textures/Interface/Alerts/Human/humancrit-0.png deleted file mode 100644 index 252d22c75013f8f0c500a941b07c8f9e7352c5a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvi2$Dv*8>L*G%zsy|Ns9MkYr#u z03;iLT!z43*Ij{X7)yfuf*Bm1-ADs+CV9FzhGC^SNm0PHU3bmQqs$%VH+aMq z{G4(V^OuS%JpAP}<*R<#>-QCvhyCp?zT2?$B~QLGccoozBe&6Sj;_ny&pEu9R|z(p zxMr~ZbxZw>EQ4)37HECel98KV^5KlD_oa|`vP-7LFqkBByCvJ4%vUb`5PbXD1S^hr u!qW^Gs{e==FpEWNABa1gGjnFyGgfg{?M2R86z>3C$KdJe=d#Wzp$P!|Z(kAs diff --git a/Resources/Textures/Interface/Alerts/Human/humancrit-1.png b/Resources/Textures/Interface/Alerts/Human/humancrit-1.png deleted file mode 100644 index c3102a638cbe201a47989f77fa98aab2c65d0fcc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvi2$Dv*8>L*G%zsy|Ns9MkYr#u z02FTk(*LttW3qs3#*!evU*OxbkD0=YMs7ySKfy1{g2N^|2xBOA+yDR^{d&I#^aa8cer$)_bROUHe;$%gQd2^ z7V|S-jb7UO7zb?Mam44e`;7PROFn3rUXe)JTPmQsR7>HQT+gwJpg6ZT50-~LTVU34 vPdzu2q3o~t0*2Mm(iir!T~tz%IU~IGphn*Pj{CoWu4C|Y^>bP0l+XkKK{R6P diff --git a/Resources/Textures/Interface/Alerts/Human/humandead.png b/Resources/Textures/Interface/Alerts/Human/humandead.png deleted file mode 100644 index 75e6fb07af08de8d9b0ec4d95861b0ce00a2e0a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvp#Yx{*8>L*$jHdp3JC=V2hC() z`2YXEv+Gtnpe$oakY6x^!?PP{Ku)%&i(`n!#N-48X%4OhpCqS>1hTeFnZlwW>R>)a zaD|hAFMF3Xi@^%Lyh$38sY^Oc_y54p9PaNfF#dJB3!= z-pIkh5vZlWAtc}s#vtObtt~Jhz#%|D^@Il_L-=E Date: Mon, 25 Jan 2021 02:10:23 +0100 Subject: [PATCH 28/61] Syringes now automatically switch between draw/inject when emptied/filled up fully. --- .../Components/Chemistry/InjectorComponent.cs | 57 ++++++++++++++----- .../Chemistry/SharedInjectorComponent.cs | 2 +- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs index d4bcfdffc6..275fdc9342 100644 --- a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs @@ -10,9 +10,7 @@ using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Utility; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -26,8 +24,6 @@ namespace Content.Server.GameObjects.Components.Chemistry [RegisterComponent] public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse, ISolutionChange { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - /// /// Whether or not the injector is able to draw from containers or if it's a single use /// device that can only inject. @@ -48,13 +44,23 @@ namespace Content.Server.GameObjects.Components.Chemistry [ViewVariables] private ReagentUnit _initialMaxVolume; + private InjectorToggleMode _toggleState; + /// /// The state of the injector. Determines it's attack behavior. Containers must have the /// right SolutionCaps to support injection/drawing. For InjectOnly injectors this should /// only ever be set to Inject /// [ViewVariables(VVAccess.ReadWrite)] - private InjectorToggleMode _toggleState; + public InjectorToggleMode ToggleState + { + get => _toggleState; + set + { + _toggleState = value; + Dirty(); + } + } public override void ExposeData(ObjectSerializer serializer) { @@ -62,7 +68,9 @@ namespace Content.Server.GameObjects.Components.Chemistry serializer.DataField(ref _injectOnly, "injectOnly", false); serializer.DataField(ref _initialMaxVolume, "initialMaxVolume", ReagentUnit.New(15)); serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5)); + serializer.DataField(ref _toggleState, "toggleState", _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw); } + protected override void Startup() { base.Startup(); @@ -87,14 +95,14 @@ namespace Content.Server.GameObjects.Components.Chemistry } string msg; - switch (_toggleState) + switch (ToggleState) { case InjectorToggleMode.Inject: - _toggleState = InjectorToggleMode.Draw; + ToggleState = InjectorToggleMode.Draw; msg = "Now drawing"; break; case InjectorToggleMode.Draw: - _toggleState = InjectorToggleMode.Inject; + ToggleState = InjectorToggleMode.Inject; msg = "Now injecting"; break; default: @@ -102,8 +110,6 @@ namespace Content.Server.GameObjects.Components.Chemistry } Owner.PopupMessage(user, Loc.GetString(msg)); - - Dirty(); } /// @@ -125,7 +131,7 @@ namespace Content.Server.GameObjects.Components.Chemistry // Handle injecting/drawing for solutions if (targetEntity.TryGetComponent(out var targetSolution)) { - if (_toggleState == InjectorToggleMode.Inject) + if (ToggleState == InjectorToggleMode.Inject) { if (solution.CanRemoveSolutions && targetSolution.CanAddSolutions) { @@ -136,7 +142,7 @@ namespace Content.Server.GameObjects.Components.Chemistry eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You aren't able to transfer to {0:theName}!", targetSolution.Owner)); } } - else if (_toggleState == InjectorToggleMode.Draw) + else if (ToggleState == InjectorToggleMode.Draw) { if (targetSolution.CanRemoveSolutions && solution.CanAddSolutions) { @@ -150,7 +156,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } else // Handle injecting into bloodstream { - if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) && _toggleState == InjectorToggleMode.Inject) + if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) && ToggleState == InjectorToggleMode.Inject) { if (solution.CanRemoveSolutions) { @@ -209,6 +215,7 @@ namespace Content.Server.GameObjects.Components.Chemistry Owner.PopupMessage(user, Loc.GetString("You inject {0}u into {1:theName}!", removedSolution.TotalVolume, targetBloodstream.Owner)); Dirty(); + AfterInject(); } private void TryInject(SolutionContainerComponent targetSolution, IEntity user) @@ -241,6 +248,16 @@ namespace Content.Server.GameObjects.Components.Chemistry Owner.PopupMessage(user, Loc.GetString("You transfer {0}u to {1:theName}", removedSolution.TotalVolume, targetSolution.Owner)); Dirty(); + AfterInject(); + } + + private void AfterInject() + { + // Automatically set syringe to draw after completely draining it. + if (Owner.GetComponent().CurrentVolume == 0) + { + ToggleState = InjectorToggleMode.Draw; + } } private void TryDraw(SolutionContainerComponent targetSolution, IEntity user) @@ -269,9 +286,19 @@ namespace Content.Server.GameObjects.Components.Chemistry Owner.PopupMessage(user, Loc.GetString("Drew {0}u from {1:theName}", removedSolution.TotalVolume, targetSolution.Owner)); Dirty(); + AfterDraw(); } - public void SolutionChanged(SolutionChangeEventArgs eventArgs) + private void AfterDraw() + { + // Automatically set syringe to inject after completely filling it. + if (Owner.GetComponent().EmptyVolume == 0) + { + ToggleState = InjectorToggleMode.Inject; + } + } + + void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) { Dirty(); } @@ -283,7 +310,7 @@ namespace Content.Server.GameObjects.Components.Chemistry var currentVolume = solution?.CurrentVolume ?? ReagentUnit.Zero; var maxVolume = solution?.MaxVolume ?? ReagentUnit.Zero; - return new InjectorComponentState(currentVolume, maxVolume, _toggleState); + return new InjectorComponentState(currentVolume, maxVolume, ToggleState); } } } diff --git a/Content.Shared/GameObjects/Components/Chemistry/SharedInjectorComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/SharedInjectorComponent.cs index 4fbef4ff26..d63ea77f6c 100644 --- a/Content.Shared/GameObjects/Components/Chemistry/SharedInjectorComponent.cs +++ b/Content.Shared/GameObjects/Components/Chemistry/SharedInjectorComponent.cs @@ -31,7 +31,7 @@ namespace Content.Shared.GameObjects.Components.Chemistry } } - protected enum InjectorToggleMode + public enum InjectorToggleMode { Inject, Draw From 4bf61770e174b6a4b0c627baf6e5e903b28d0b22 Mon Sep 17 00:00:00 2001 From: py01 <60152240+collinlunn@users.noreply.github.com> Date: Thu, 28 Jan 2021 20:20:13 -0600 Subject: [PATCH 29/61] Piping nullability (#3048) Co-authored-by: py01 --- .../Components/Atmos/Piping/Pumps/PressurePumpComponent.cs | 3 ++- .../Components/Atmos/Piping/Pumps/VolumePumpComponent.cs | 3 ++- .../Components/Atmos/Piping/Scrubbers/PressureSiphon.cs | 3 ++- .../Components/Atmos/Piping/Vents/PressureVentComponent.cs | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/PressurePumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/PressurePumpComponent.cs index 33c1ceaa2d..3ccff576bb 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/PressurePumpComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/PressurePumpComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using Content.Server.Atmos; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/VolumePumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/VolumePumpComponent.cs index 614a7487af..d9c8c27a28 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/VolumePumpComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/VolumePumpComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using Content.Server.Atmos; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/PressureSiphon.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/PressureSiphon.cs index a48537d6f5..dd66336038 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/PressureSiphon.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/PressureSiphon.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using Content.Server.Atmos; using Content.Shared.Atmos; using Robust.Shared.GameObjects; diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/PressureVentComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/PressureVentComponent.cs index 7f853a7761..e84c67ec9f 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/PressureVentComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/PressureVentComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.Atmos; +#nullable enable +using Content.Server.Atmos; using Content.Shared.Atmos; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; From c80efdaf44bc63124a2ca556f8c280d19a2b374a Mon Sep 17 00:00:00 2001 From: py01 <60152240+collinlunn@users.noreply.github.com> Date: Thu, 28 Jan 2021 20:21:08 -0600 Subject: [PATCH 30/61] Node and NodeGroup nullability (#3047) Co-authored-by: py01 --- .../NodeContainer/NodeGroups/ApcNetNodeGroup.cs | 5 ++--- .../NodeContainer/NodeGroups/BaseNetConnectorNodeGroup.cs | 3 ++- .../Components/NodeContainer/NodeGroups/INodeGroup.cs | 3 ++- .../Components/NodeContainer/NodeGroups/IPipeNet.cs | 7 ++++--- .../NodeContainer/NodeGroups/PowerNetNodeGroup.cs | 3 ++- .../Components/NodeContainer/Nodes/AdjacentNode.cs | 1 + .../GameObjects/Components/NodeContainer/Nodes/Node.cs | 8 +++----- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/ApcNetNodeGroup.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/ApcNetNodeGroup.cs index 703f159162..d8ef14d799 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/ApcNetNodeGroup.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/ApcNetNodeGroup.cs @@ -1,6 +1,5 @@ -using System; +#nullable enable using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Content.Server.GameObjects.Components.Power; using Content.Server.GameObjects.Components.Power.ApcNetComponents; @@ -58,7 +57,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups public void AddApc(ApcComponent apc) { - if (!apc.Owner.TryGetComponent(out BatteryComponent battery)) + if (!apc.Owner.TryGetComponent(out var battery)) { return; } diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/BaseNetConnectorNodeGroup.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/BaseNetConnectorNodeGroup.cs index c4b2bea708..5c0dacbb04 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/BaseNetConnectorNodeGroup.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/BaseNetConnectorNodeGroup.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Content.Server.GameObjects.Components.Power; diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs index 6f6f18b381..a7b4e87099 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroup.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Robust.Shared.IoC; using Robust.Shared.Map; diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs index e5e4a97066..a44f1ae476 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/IPipeNet.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using Content.Server.Atmos; using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Content.Server.GameObjects.EntitySystems; @@ -27,9 +28,9 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups [ViewVariables] private readonly List _pipes = new(); - [ViewVariables] private AtmosphereSystem _atmosphereSystem; + [ViewVariables] private AtmosphereSystem? _atmosphereSystem; - [ViewVariables] private IGridAtmosphereComponent GridAtmos => _atmosphereSystem.GetGridAtmosphere(GridId); + [ViewVariables] private IGridAtmosphereComponent? GridAtmos => _atmosphereSystem?.GetGridAtmosphere(GridId); public override void Initialize(Node sourceNode) { diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/PowerNetNodeGroup.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/PowerNetNodeGroup.cs index 59ff48ef1b..933eeb34af 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/PowerNetNodeGroup.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/PowerNetNodeGroup.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Diagnostics; using Content.Server.GameObjects.Components.Power.PowerNetComponents; diff --git a/Content.Server/GameObjects/Components/NodeContainer/Nodes/AdjacentNode.cs b/Content.Server/GameObjects/Components/NodeContainer/Nodes/AdjacentNode.cs index 3badc4fe7f..4e1735ac2e 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/Nodes/AdjacentNode.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/Nodes/AdjacentNode.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Collections.Generic; using Robust.Shared.GameObjects.Components.Transform; diff --git a/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs b/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs index 09c45000df..8e784963cb 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/Nodes/Node.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -29,7 +30,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes private INodeGroup _nodeGroup = BaseNodeGroup.NullGroup; [ViewVariables] - public IEntity Owner { get; private set; } + public IEntity Owner { get; private set; } = default!; [ViewVariables] private bool _needsGroup = true; @@ -46,8 +47,6 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes /// private bool _deleting = false; - private INodeGroupFactory _nodeGroupFactory; - public virtual void ExposeData(ObjectSerializer serializer) { serializer.DataField(this, x => x.NodeGroupID, "nodeGroupID", NodeGroupID.Default); @@ -56,7 +55,6 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes public virtual void Initialize(IEntity owner) { Owner = owner; - _nodeGroupFactory = IoCManager.Resolve(); } public void OnContainerStartup() @@ -163,7 +161,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes private INodeGroup MakeNewGroup() { - return _nodeGroupFactory.MakeNodeGroup(this); + return IoCManager.Resolve().MakeNodeGroup(this); } } } From 59d508c5dffa8999474b6b1db94f2f5c878b8509 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Sat, 30 Jan 2021 15:55:10 +0100 Subject: [PATCH 31/61] Fixes exception when solution reaction completely removes reagent. Fixed #3053 --- Content.Shared/Chemistry/Solution.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Chemistry/Solution.cs b/Content.Shared/Chemistry/Solution.cs index 96b802696b..7fb2315c1e 100644 --- a/Content.Shared/Chemistry/Solution.cs +++ b/Content.Shared/Chemistry/Solution.cs @@ -321,7 +321,7 @@ namespace Content.Shared.Chemistry { var proto = IoCManager.Resolve(); - foreach (var (reagentId, quantity) in _contents) + foreach (var (reagentId, quantity) in _contents.ToArray()) { if (!proto.TryIndex(reagentId, out ReagentPrototype reagent)) continue; From 86841385c6c74d82cd204428951f739e452fba86 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Sat, 30 Jan 2021 15:57:31 +0100 Subject: [PATCH 32/61] Remove invalid CyberSylph bar sign. Fixed #3039 --- .../Entities/Constructible/Walls/bar_sign.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Resources/Prototypes/Entities/Constructible/Walls/bar_sign.yml b/Resources/Prototypes/Entities/Constructible/Walls/bar_sign.yml index 9f396756c2..99f1d83ffe 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/bar_sign.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/bar_sign.yml @@ -38,18 +38,3 @@ components: - type: BarSign current: EngineChange - -- type: entity - id: BarSignCyberSylph - name: Cyber Sylph - parent: LargeBarSign - components: - - type: BarSign - current: CyberSylph - - type: Physics - shapes: - - !type:PhysShapeAabb - bounds: "-0.45, -0.95, 0.95, 1.5" - layer: [ Passable ] - - type: Sprite - drawdepth: Ghosts From e0bf335030c61f06c7439fd82fd0e039fabab47a Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 31 Jan 2021 00:08:34 +0100 Subject: [PATCH 33/61] Fix parallax constantly regenerating on Windows. Because Windows does CRLF (yuck) the parallax file mismatches with the one shipped with the actual launcher client. We now normalize the EOLs to fix this. --- Content.Client/Parallax/ParallaxManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Client/Parallax/ParallaxManager.cs b/Content.Client/Parallax/ParallaxManager.cs index 9ff1bc08ed..3a17cc2ea6 100644 --- a/Content.Client/Parallax/ParallaxManager.cs +++ b/Content.Client/Parallax/ParallaxManager.cs @@ -54,7 +54,7 @@ namespace Content.Client.Parallax { using (var reader = new StreamReader(configStream, EncodingHelpers.UTF8)) { - contents = reader.ReadToEnd(); + contents = reader.ReadToEnd().Replace(Environment.NewLine, "\n"); } if (!debugParallax && _resourceCache.UserData.Exists(ParallaxConfigOld)) From afb8e5a7821e767ce7f83fc7c94c12c630f308cb Mon Sep 17 00:00:00 2001 From: Acruid Date: Sun, 31 Jan 2021 15:54:05 -0800 Subject: [PATCH 34/61] Stops the exception spam in console about PlayWeaponArc when the client is outside the PVS of a weapon swing. This does not fix the bug, just catches it. --- .../GameObjects/EntitySystems/MeleeWeaponSystem.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs b/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs index e35c150cd2..1133c1a8d2 100644 --- a/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs @@ -1,4 +1,4 @@ -using System; +using System; using Content.Client.GameObjects.Components.Mobs; using Content.Client.GameObjects.Components.Weapons.Melee; using Content.Shared.GameObjects.Components.Weapons.Melee; @@ -14,7 +14,6 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Maths; using Robust.Shared.Prototypes; -using Robust.Shared.Timers; using static Content.Shared.GameObjects.EntitySystemMessages.MeleeWeaponSystemMessages; namespace Content.Client.GameObjects.EntitySystems @@ -49,7 +48,12 @@ namespace Content.Client.GameObjects.EntitySystems return; } - var attacker = EntityManager.GetEntity(msg.Attacker); + if (!EntityManager.TryGetEntity(msg.Attacker, out var attacker)) + { + //FIXME: This should never happen. + Logger.Error($"Tried to play a weapon arc {msg.ArcPrototype}, but the attacker does not exist. attacker={msg.Attacker}, source={msg.Source}"); + return; + } if (!attacker.Deleted) { From 044040effeeff787fd2ae65d3c5612de58af2ea9 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> Date: Mon, 1 Feb 2021 16:49:32 +0100 Subject: [PATCH 35/61] Port instrument menu UI to use XAML (#3066) --- .../Instruments/InstrumentMenu.xaml | 18 +++ ...strumentMenu.cs => InstrumentMenu.xaml.cs} | 128 +++--------------- 2 files changed, 38 insertions(+), 108 deletions(-) create mode 100644 Content.Client/Instruments/InstrumentMenu.xaml rename Content.Client/Instruments/{InstrumentMenu.cs => InstrumentMenu.xaml.cs} (57%) diff --git a/Content.Client/Instruments/InstrumentMenu.xaml b/Content.Client/Instruments/InstrumentMenu.xaml new file mode 100644 index 0000000000..7f1588021c --- /dev/null +++ b/Content.Client/Instruments/InstrumentMenu.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/Content.Client/Instruments/InstrumentMenu.cs b/Content.Client/Instruments/InstrumentMenu.xaml.cs similarity index 57% rename from Content.Client/Instruments/InstrumentMenu.cs rename to Content.Client/Instruments/InstrumentMenu.xaml.cs index 5f30d71494..e9debae7e1 100644 --- a/Content.Client/Instruments/InstrumentMenu.cs +++ b/Content.Client/Instruments/InstrumentMenu.xaml.cs @@ -5,12 +5,14 @@ using Content.Client.GameObjects.Components.Instruments; using Content.Client.UserInterface.Stylesheets; using Content.Client.Utility; using Robust.Client.Audio.Midi; +using Robust.Client.AutoGenerated; using Robust.Client.Graphics.Drawing; using Robust.Client.Interfaces.UserInterface; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; using Robust.Shared.Containers; using Robust.Shared.IoC; using Robust.Shared.Localization; @@ -20,20 +22,19 @@ using Robust.Shared.Timers; namespace Content.Client.Instruments { - public class InstrumentMenu : SS14Window + [GenerateTypedNameReferences] + public partial class InstrumentMenu : SS14Window { [Dependency] private readonly IMidiManager _midiManager = default!; [Dependency] private readonly IFileDialogManager _fileDialogManager = default!; private readonly InstrumentBoundUserInterface _owner; - private readonly Button _midiLoopButton; - private readonly Button _midiStopButton; - private readonly Button _midiInputButton; protected override Vector2? CustomSize => (400, 150); public InstrumentMenu(InstrumentBoundUserInterface owner) { + RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); _owner = owner; @@ -42,108 +43,13 @@ namespace Content.Client.Instruments Title = _owner.Instrument.Owner.Name; - var margin = new MarginContainer() - { - SizeFlagsVertical = SizeFlags.FillExpand, - SizeFlagsHorizontal = SizeFlags.FillExpand, - }; - - var vBox = new VBoxContainer() - { - SizeFlagsVertical = SizeFlags.FillExpand, - SeparationOverride = 5, - }; - - var hBoxTopButtons = new HBoxContainer() - { - SizeFlagsHorizontal = SizeFlags.FillExpand, - SizeFlagsVertical = SizeFlags.FillExpand, - SizeFlagsStretchRatio = 1, - Align = BoxContainer.AlignMode.Center - }; - - _midiInputButton = new Button() - { - Text = Loc.GetString("MIDI Input"), - TextAlign = Label.AlignMode.Center, - SizeFlagsHorizontal = SizeFlags.FillExpand, - SizeFlagsStretchRatio = 1, - ToggleMode = true, - Pressed = _owner.Instrument.IsInputOpen, - }; - - _midiInputButton.OnToggled += MidiInputButtonOnOnToggled; - - var topSpacer = new Control() - { - SizeFlagsHorizontal = SizeFlags.FillExpand, - SizeFlagsStretchRatio = 2, - }; - - var midiFileButton = new Button() - { - Text = Loc.GetString("Play MIDI File"), - TextAlign = Label.AlignMode.Center, - SizeFlagsHorizontal = SizeFlags.FillExpand, - SizeFlagsStretchRatio = 1, - }; - - midiFileButton.OnPressed += MidiFileButtonOnOnPressed; - - var hBoxBottomButtons = new HBoxContainer() - { - SizeFlagsHorizontal = SizeFlags.FillExpand, - SizeFlagsVertical = SizeFlags.FillExpand, - SizeFlagsStretchRatio = 1, - Align = BoxContainer.AlignMode.Center - }; - - _midiLoopButton = new Button() - { - Text = Loc.GetString("Loop"), - TextAlign = Label.AlignMode.Center, - SizeFlagsHorizontal = SizeFlags.FillExpand, - SizeFlagsStretchRatio = 1, - ToggleMode = true, - Disabled = !_owner.Instrument.IsMidiOpen, - Pressed = _owner.Instrument.LoopMidi, - }; - - _midiLoopButton.OnToggled += MidiLoopButtonOnOnToggled; - - var bottomSpacer = new Control() - { - SizeFlagsHorizontal = SizeFlags.FillExpand, - SizeFlagsStretchRatio = 2, - }; - - _midiStopButton = new Button() - { - Text = Loc.GetString("Stop"), - TextAlign = Label.AlignMode.Center, - SizeFlagsHorizontal = SizeFlags.FillExpand, - SizeFlagsStretchRatio = 1, - Disabled = !_owner.Instrument.IsMidiOpen, - }; - - _midiStopButton.OnPressed += MidiStopButtonOnPressed; - - hBoxBottomButtons.AddChild(_midiLoopButton); - hBoxBottomButtons.AddChild(bottomSpacer); - hBoxBottomButtons.AddChild(_midiStopButton); - - hBoxTopButtons.AddChild(_midiInputButton); - hBoxTopButtons.AddChild(topSpacer); - hBoxTopButtons.AddChild(midiFileButton); - - vBox.AddChild(hBoxTopButtons); - vBox.AddChild(hBoxBottomButtons); - - margin.AddChild(vBox); + LoopButton.Disabled = !_owner.Instrument.IsMidiOpen; + LoopButton.Pressed = _owner.Instrument.LoopMidi; + StopButton.Disabled = !_owner.Instrument.IsMidiOpen; if (!_midiManager.IsAvailable) { - margin.AddChild(new PanelContainer + Margin.AddChild(new PanelContainer { MouseFilter = MouseFilterMode.Stop, PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Black.WithAlpha(0.90f)}, @@ -159,9 +65,15 @@ namespace Content.Client.Instruments } } }); + + // We return early as to not give the buttons behavior. + return; } - Contents.AddChild(margin); + InputButton.OnToggled += MidiInputButtonOnOnToggled; + FileButton.OnPressed += MidiFileButtonOnOnPressed; + LoopButton.OnToggled += MidiLoopButtonOnOnToggled; + StopButton.OnPressed += MidiStopButtonOnPressed; } private void InstrumentOnMidiPlaybackEnded() @@ -171,8 +83,8 @@ namespace Content.Client.Instruments public void MidiPlaybackSetButtonsDisabled(bool disabled) { - _midiLoopButton.Disabled = disabled; - _midiStopButton.Disabled = disabled; + LoopButton.Disabled = disabled; + StopButton.Disabled = disabled; } private async void MidiFileButtonOnOnPressed(BaseButton.ButtonEventArgs obj) @@ -204,8 +116,8 @@ namespace Content.Client.Instruments return; MidiPlaybackSetButtonsDisabled(false); - if (_midiInputButton.Pressed) - _midiInputButton.Pressed = false; + if (InputButton.Pressed) + InputButton.Pressed = false; } private void MidiInputButtonOnOnToggled(BaseButton.ButtonToggledEventArgs obj) From b51b8cb182c3cea04224caff9702ca6f9631cd14 Mon Sep 17 00:00:00 2001 From: collinlunn <60152240+collinlunn@users.noreply.github.com> Date: Mon, 1 Feb 2021 10:19:43 -0600 Subject: [PATCH 36/61] Some Power nullable & ComponentDependency (#3050) * Power nullability * AME nullability * re-adds EnsureComponents * conflict fix * Update Content.Server/GameObjects/Components/Power/PowerNetComponents/RadiationCollectorComponent.cs Co-authored-by: Paul Ritter * Update Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryStorageComponent.cs Co-authored-by: Paul Ritter * Update Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryStorageComponent.cs Co-authored-by: Paul Ritter * Update Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryDischargerComponent.cs Co-authored-by: Paul Ritter * Update Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryDischargerComponent.cs Co-authored-by: Paul Ritter * removes duplicate component assignment Co-authored-by: py01 Co-authored-by: Paul Ritter --- .../Power/AME/AMEFuelContainerComponent.cs | 3 +- .../Components/Power/AME/AMEPartComponent.cs | 6 ++-- .../Power/ApcNetComponents/ApcComponent.cs | 4 +++ .../ApcNetComponents/BaseApcNetComponent.cs | 3 +- .../PowerProviderComponent.cs | 6 ++-- .../Power/BaseNetConnectorComponent.cs | 2 +- .../Components/Power/BatteryComponent.cs | 3 +- .../Components/Power/PowerCellComponent.cs | 5 ++-- .../BasePowerNetComponent.cs | 3 +- .../BatteryDischargerComponent.cs | 23 ++++++++++---- .../BatteryStorageComponent.cs | 30 +++++++++++++------ .../PowerNetComponents/IPowerNetManager.cs | 3 +- .../PowerConsumerComponent.cs | 5 ++-- .../PowerSupplierComponent.cs | 3 +- .../RadiationCollectorComponent.cs | 25 +++++----------- .../PowerNetComponents/SolarPanelComponent.cs | 11 ++++--- .../Components/Power/WireComponent.cs | 10 +++++-- .../Components/Power/WirePlacerComponent.cs | 11 ++++--- 18 files changed, 94 insertions(+), 62 deletions(-) diff --git a/Content.Server/GameObjects/Components/Power/AME/AMEFuelContainerComponent.cs b/Content.Server/GameObjects/Components/Power/AME/AMEFuelContainerComponent.cs index 98e336608e..6367ac9002 100644 --- a/Content.Server/GameObjects/Components/Power/AME/AMEFuelContainerComponent.cs +++ b/Content.Server/GameObjects/Components/Power/AME/AMEFuelContainerComponent.cs @@ -1,4 +1,5 @@ -using Robust.Shared.GameObjects; +#nullable enable +using Robust.Shared.GameObjects; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Power.AME diff --git a/Content.Server/GameObjects/Components/Power/AME/AMEPartComponent.cs b/Content.Server/GameObjects/Components/Power/AME/AMEPartComponent.cs index 776dfb6309..5817fe5882 100644 --- a/Content.Server/GameObjects/Components/Power/AME/AMEPartComponent.cs +++ b/Content.Server/GameObjects/Components/Power/AME/AMEPartComponent.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Threading.Tasks; using System.Linq; using Content.Server.GameObjects.Components.Interactable; @@ -28,14 +29,13 @@ namespace Content.Server.GameObjects.Components.Power.AME async Task IInteractUsing.InteractUsing(InteractUsingEventArgs args) { - if (!args.User.TryGetComponent(out IHandsComponent hands)) + if (!args.User.TryGetComponent(out var hands)) { Owner.PopupMessage(args.User, Loc.GetString("You have no hands.")); return true; } - var activeHandEntity = hands.GetActiveHand.Owner; - if (activeHandEntity.TryGetComponent(out var multitool) && multitool.Qualities == ToolQuality.Multitool) + if (args.Using.TryGetComponent(out var multitool) && multitool.Qualities == ToolQuality.Multitool) { var mapGrid = _mapManager.GetGrid(args.ClickLocation.GetGridId(_serverEntityManager)); diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs index 4edd7f3df8..c2a2b6b431 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/ApcComponent.cs @@ -179,6 +179,10 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents return ApcExternalPowerState.None; } var consumer = batteryStorage.Consumer; + + if (consumer == null) + return ApcExternalPowerState.None; + if (consumer.ReceivedPower == 0 && consumer.DrawRate != 0) { return ApcExternalPowerState.None; diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/BaseApcNetComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/BaseApcNetComponent.cs index b8f6c13d15..d13f3fecfe 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/BaseApcNetComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/BaseApcNetComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; +#nullable enable +using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; namespace Content.Server.GameObjects.Components.Power.ApcNetComponents { diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerProviderComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerProviderComponent.cs index f1c14043dd..96c2b5e9e9 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerProviderComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerProviderComponent.cs @@ -1,6 +1,6 @@ +#nullable enable using System; using System.Collections.Generic; -using System.Diagnostics; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; @@ -20,7 +20,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents void UpdateReceiverLoad(int oldLoad, int newLoad); - public IEntity ProviderOwner { get; } + public IEntity? ProviderOwner { get; } public bool HasApcPower { get; } } @@ -172,7 +172,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents public void AddReceiver(PowerReceiverComponent receiver) { } public void RemoveReceiver(PowerReceiverComponent receiver) { } public void UpdateReceiverLoad(int oldLoad, int newLoad) { } - public IEntity ProviderOwner => default; + public IEntity? ProviderOwner => default; } } } diff --git a/Content.Server/GameObjects/Components/Power/BaseNetConnectorComponent.cs b/Content.Server/GameObjects/Components/Power/BaseNetConnectorComponent.cs index fc2148f85f..66ac85b62d 100644 --- a/Content.Server/GameObjects/Components/Power/BaseNetConnectorComponent.cs +++ b/Content.Server/GameObjects/Components/Power/BaseNetConnectorComponent.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Content.Server.GameObjects.Components.NodeContainer; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Robust.Shared.GameObjects; diff --git a/Content.Server/GameObjects/Components/Power/BatteryComponent.cs b/Content.Server/GameObjects/Components/Power/BatteryComponent.cs index 5235cf69fe..c7dce26485 100644 --- a/Content.Server/GameObjects/Components/Power/BatteryComponent.cs +++ b/Content.Server/GameObjects/Components/Power/BatteryComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using Robust.Shared.GameObjects; using Robust.Shared.Maths; using Robust.Shared.Serialization; diff --git a/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs b/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs index 61c321c497..8806e69272 100644 --- a/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerCellComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using Content.Server.Explosions; using Content.Server.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.Components.Power; @@ -11,8 +12,6 @@ using Robust.Shared.Serialization; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; -#nullable enable - namespace Content.Server.GameObjects.Components.Power { /// diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/BasePowerNetComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/BasePowerNetComponent.cs index 3482d225a0..a80fc4ed9d 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/BasePowerNetComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/BasePowerNetComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; +#nullable enable +using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; namespace Content.Server.GameObjects.Components.Power.PowerNetComponents { diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryDischargerComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryDischargerComponent.cs index 65eaafb285..332b2abcbb 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryDischargerComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryDischargerComponent.cs @@ -1,6 +1,9 @@ -using Robust.Shared.GameObjects; +#nullable enable +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.ComponentDependencies; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; +using System; namespace Content.Server.GameObjects.Components.Power.PowerNetComponents { @@ -13,10 +16,10 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents public override string Name => "BatteryDischarger"; [ViewVariables] - private BatteryComponent _battery; + [ComponentDependency] private BatteryComponent? _battery = default!; [ViewVariables] - private PowerSupplierComponent _supplier; + [ComponentDependency] private PowerSupplierComponent? _supplier = default!; [ViewVariables(VVAccess.ReadWrite)] public int ActiveSupplyRate { get => _activeSupplyRate; set => SetActiveSupplyRate(value); } @@ -31,14 +34,16 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents public override void Initialize() { base.Initialize(); - - _battery = Owner.EnsureComponent(); - _supplier = Owner.EnsureComponent(); + Owner.EnsureComponentWarn(); + Owner.EnsureComponentWarn(); UpdateSupplyRate(); } public void Update(float frameTime) { + if (_battery == null) + return; + //Simplified implementation - if the battery is empty, and charge is being added to the battery //at a lower rate that this is using it, the charge is used without creating power supply. _battery.CurrentCharge -= ActiveSupplyRate * frameTime; @@ -47,6 +52,9 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents private void UpdateSupplyRate() { + if (_battery == null) + return; + if (_battery.BatteryState == BatteryState.Empty) { SetSupplierSupplyRate(0); @@ -59,6 +67,9 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents private void SetSupplierSupplyRate(int newSupplierSupplyRate) { + if (_supplier == null) + return; + if (_supplier.SupplyRate != newSupplierSupplyRate) { _supplier.SupplyRate = newSupplierSupplyRate; diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryStorageComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryStorageComponent.cs index 416ac17a5c..e87ccb96f8 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/BatteryStorageComponent.cs @@ -1,4 +1,6 @@ -using Robust.Shared.GameObjects; +#nullable enable +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.ComponentDependencies; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -17,10 +19,12 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents private int _activeDrawRate; [ViewVariables] - private BatteryComponent _battery; + [ComponentDependency] private BatteryComponent? _battery = default!; [ViewVariables] - public PowerConsumerComponent Consumer { get; private set; } + public PowerConsumerComponent? Consumer => _consumer; + + [ComponentDependency] private PowerConsumerComponent? _consumer = default!; public override void ExposeData(ObjectSerializer serializer) { @@ -31,21 +35,26 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents public override void Initialize() { base.Initialize(); - - _battery = Owner.EnsureComponent(); - Consumer = Owner.EnsureComponent(); + Owner.EnsureComponentWarn(); + Owner.EnsureComponentWarn(); UpdateDrawRate(); } public void Update(float frameTime) { + if (_consumer == null || _battery == null) + return; + //Simplified implementation - If a frame adds more power to a partially full battery than it can hold, the power is lost. - _battery.CurrentCharge += Consumer.ReceivedPower * frameTime; + _battery.CurrentCharge += _consumer.ReceivedPower * frameTime; UpdateDrawRate(); } private void UpdateDrawRate() { + if (_battery == null) + return; + if (_battery.BatteryState == BatteryState.Full) { SetConsumerDraw(0); @@ -58,9 +67,12 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents private void SetConsumerDraw(int newConsumerDrawRate) { - if (Consumer.DrawRate != newConsumerDrawRate) + if (_consumer == null) + return; + + if (_consumer.DrawRate != newConsumerDrawRate) { - Consumer.DrawRate = newConsumerDrawRate; + _consumer.DrawRate = newConsumerDrawRate; } } diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/IPowerNetManager.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/IPowerNetManager.cs index 808f3f564b..2f8b8230b2 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/IPowerNetManager.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/IPowerNetManager.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; namespace Content.Server.GameObjects.Components.Power.PowerNetComponents diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/PowerConsumerComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/PowerConsumerComponent.cs index 54dd3437d2..c6118c87f3 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/PowerConsumerComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/PowerConsumerComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Diagnostics; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Robust.Shared.GameObjects; @@ -34,7 +35,7 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents public int ReceivedPower { get => _receivedPower; set => SetReceivedPower(value); } private int _receivedPower; - public event EventHandler OnReceivedPowerChanged; + public event EventHandler? OnReceivedPowerChanged; public override void ExposeData(ObjectSerializer serializer) { diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/PowerSupplierComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/PowerSupplierComponent.cs index b843c1bee7..02047fe80c 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/PowerSupplierComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/PowerSupplierComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; +#nullable enable +using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/RadiationCollectorComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/RadiationCollectorComponent.cs index b6b63ffc2c..90e5ea562f 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/RadiationCollectorComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/RadiationCollectorComponent.cs @@ -1,20 +1,18 @@ +#nullable enable using System; -using System.Threading; using Content.Server.Utility; using Content.Shared.GameObjects.Components; -using Content.Shared.GameObjects.Components.Doors; using Content.Shared.GameObjects.Components.Singularity; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.ComponentDependencies; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Log; -using Timer = Robust.Shared.Timers.Timer; namespace Content.Server.GameObjects.Components.Power.PowerNetComponents { @@ -27,19 +25,9 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents private bool _enabled; private TimeSpan _coolDownEnd; - private PhysicsComponent _collidableComponent; + [ComponentDependency] private readonly PhysicsComponent? _collidableComponent = default!; - public override void Initialize() - { - base.Initialize(); - if (!Owner.TryGetComponent(out _collidableComponent)) - { - Logger.Error("RadiationCollectorComponent created with no CollidableComponent"); - return; - } - } - - public override void HandleMessage(ComponentMessage message, IComponent component) + public override void HandleMessage(ComponentMessage message, IComponent? component) { base.HandleMessage(message, component); switch (message) @@ -52,7 +40,8 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents private void OnAnchoredChanged() { - if(_collidableComponent.Anchored) Owner.SnapToGrid(); + if(_collidableComponent != null && _collidableComponent.Anchored) + Owner.SnapToGrid(); } bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) @@ -99,7 +88,7 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents protected void SetAppearance(RadiationCollectorVisualState state) { - if (Owner.TryGetComponent(out AppearanceComponent appearance)) + if (Owner.TryGetComponent(out var appearance)) { appearance.SetData(RadiationCollectorVisuals.VisualState, state); } diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs index 6a3f46efcc..89fca08196 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects; @@ -62,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents private void UpdateSupply() { - if (Owner.TryGetComponent(out PowerSupplierComponent supplier)) + if (Owner.TryGetComponent(out var supplier)) { supplier.SupplyRate = (int) (_maxSupply * _coverage); } @@ -72,7 +73,7 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents { base.Initialize(); - Owner.EnsureComponentWarn(out PowerSupplierComponent _); + Owner.EnsureComponentWarn(); UpdateSupply(); } @@ -86,7 +87,9 @@ namespace Content.Server.GameObjects.Components.Power.PowerNetComponents public void OnBreak(BreakageEventArgs args) { - var sprite = Owner.GetComponent(); + if (!Owner.TryGetComponent(out var sprite)) + return; + sprite.LayerSetState(0, "broken"); MaxSupply = 0; } diff --git a/Content.Server/GameObjects/Components/Power/WireComponent.cs b/Content.Server/GameObjects/Components/Power/WireComponent.cs index ad9cf19602..8fd1efad13 100644 --- a/Content.Server/GameObjects/Components/Power/WireComponent.cs +++ b/Content.Server/GameObjects/Components/Power/WireComponent.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +#nullable enable +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Stack; using Content.Shared.GameObjects.Components.Interactable; @@ -18,7 +19,7 @@ namespace Content.Server.GameObjects.Components.Power public override string Name => "Wire"; [ViewVariables] - private string _wireDroppedOnCutPrototype; + private string? _wireDroppedOnCutPrototype; /// /// Checked by to determine if there is @@ -37,7 +38,10 @@ namespace Content.Server.GameObjects.Components.Power public async Task InteractUsing(InteractUsingEventArgs eventArgs) { - if (!eventArgs.Using.TryGetComponent(out ToolComponent tool)) return false; + if (_wireDroppedOnCutPrototype == null) + return false; + + if (!eventArgs.Using.TryGetComponent(out var tool)) return false; if (!await tool.UseTool(eventArgs.User, Owner, 0.25f, ToolQuality.Cutting)) return false; Owner.Delete(); diff --git a/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs b/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs index 0330bd1c4a..81d8654360 100644 --- a/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs +++ b/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs @@ -1,7 +1,7 @@ -using Content.Server.GameObjects.Components.Stack; +#nullable enable +using Content.Server.GameObjects.Components.Stack; using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Utility; -using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.Interfaces.Map; @@ -21,7 +21,7 @@ namespace Content.Server.GameObjects.Components.Power public override string Name => "WirePlacer"; [ViewVariables] - private string _wirePrototypeID; + private string? _wirePrototypeID; [ViewVariables] private WireType _blockingWireType; @@ -36,6 +36,9 @@ namespace Content.Server.GameObjects.Components.Power /// public async Task AfterInteract(AfterInteractEventArgs eventArgs) { + if (_wirePrototypeID == null) + return; + if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return; if(!_mapManager.TryGetGrid(eventArgs.ClickLocation.GetGridId(Owner.EntityManager), out var grid)) return; @@ -50,7 +53,7 @@ namespace Content.Server.GameObjects.Components.Power return; } } - if (Owner.TryGetComponent(out StackComponent stack) && !stack.Use(1)) + if (Owner.TryGetComponent(out var stack) && !stack.Use(1)) return; Owner.EntityManager.SpawnEntity(_wirePrototypeID, grid.GridTileToLocal(snapPos)); } From 6b6ddd0a73af0896e12c0ea93ae36b0221585a34 Mon Sep 17 00:00:00 2001 From: bhespiritu Date: Mon, 1 Feb 2021 11:22:50 -0500 Subject: [PATCH 37/61] update UpdateVisuals to use Destructible thresholds (#3051) * update UpdateVisuals to use Destructible thresholds * refactor windows to use Destructible threshold instead of maxDamage --- .../GameObjects/Components/WindowComponent.cs | 28 +++++++++---------- .../Entities/Constructible/Walls/windows.yml | 3 -- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/Content.Server/GameObjects/Components/WindowComponent.cs b/Content.Server/GameObjects/Components/WindowComponent.cs index 266f25e85d..669971fb6d 100644 --- a/Content.Server/GameObjects/Components/WindowComponent.cs +++ b/Content.Server/GameObjects/Components/WindowComponent.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using Content.Server.Utility; using Content.Shared.Audio; @@ -6,6 +6,7 @@ using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; +using Content.Server.GameObjects.Components.Destructible; using Content.Shared.Utility; using Robust.Server.GameObjects; using Robust.Server.GameObjects.EntitySystems; @@ -13,7 +14,6 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Localization; -using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Content.Server.GameObjects.Components @@ -22,14 +22,6 @@ namespace Content.Server.GameObjects.Components [ComponentReference(typeof(SharedWindowComponent))] public class WindowComponent : SharedWindowComponent, IExamine, IInteractHand { - private int _maxDamage; - - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - - serializer.DataField(ref _maxDamage, "maxDamage", 100); - } public override void HandleMessage(ComponentMessage message, IComponent? component) { @@ -50,18 +42,24 @@ namespace Content.Server.GameObjects.Components { if (Owner.TryGetComponent(out AppearanceComponent? appearance)) { - appearance.SetData(WindowVisuals.Damage, (float) currentDamage / _maxDamage); + if (Owner.TryGetComponent(out DestructibleComponent? destructible)) + { + var damageThreshold = destructible.LowestToHighestThresholds.FirstOrNull()?.Key; + if (damageThreshold == null) return; + appearance.SetData(WindowVisuals.Damage, (float) currentDamage / damageThreshold); + } } } void IExamine.Examine(FormattedMessage message, bool inDetailsRange) { var damage = Owner.GetComponentOrNull()?.TotalDamage; - if (damage == null) return; - var fraction = ((damage == 0 || _maxDamage == 0) + var damageThreshold = Owner.GetComponentOrNull()?.LowestToHighestThresholds.FirstOrNull()?.Key; + if (damage == null || damageThreshold == null) return; + var fraction = ((damage == 0 || damageThreshold == 0) ? 0f - : (float) damage / _maxDamage); - var level = Math.Min(ContentHelpers.RoundToLevels(fraction, 1, 7), 5); + : (float) damage / damageThreshold); + var level = Math.Min(ContentHelpers.RoundToLevels((double) fraction, 1, 7), 5); switch (level) { case 0: diff --git a/Resources/Prototypes/Entities/Constructible/Walls/windows.yml b/Resources/Prototypes/Entities/Constructible/Walls/windows.yml index c63b189159..9f9778ae6e 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/windows.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/windows.yml @@ -46,7 +46,6 @@ - type: Airtight - type: Window base: window - maxDamage: 15 - type: Construction graph: window node: window @@ -81,7 +80,6 @@ acts: [ "Destruction" ] - type: Window base: rwindow - maxDamage: 75 - type: Construction graph: window node: reinforcedWindow @@ -114,7 +112,6 @@ resistances: metallicResistances - type: Window base: pwindow - maxDamage: 100 - type: Construction graph: window node: phoronWindow From 633a6b3ab9a99c59c969e8b629bc10caf93f5816 Mon Sep 17 00:00:00 2001 From: 20kdc Date: Mon, 1 Feb 2021 20:58:47 +0000 Subject: [PATCH 38/61] Walls, catwalks, table frames can no longer be constructed rotated. (#3057) * Walls, catwalks, table frames can no longer be constructed rotated. This should help prevent bugs from unanticipated rotations. * SnapToGrid ZeroRotation (now SouthRotation) - Stuff must face south apparently --- .../Construction/Completions/SnapToGrid.cs | 7 +++++++ .../Recipes/Construction/Graphs/catwalk.yml | 3 ++- .../Recipes/Construction/Graphs/girder.yml | 15 ++++++++++----- .../Recipes/Construction/Graphs/tables.yml | 3 ++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Content.Server/Construction/Completions/SnapToGrid.cs b/Content.Server/Construction/Completions/SnapToGrid.cs index d44e7a1392..ab35e14dd4 100644 --- a/Content.Server/Construction/Completions/SnapToGrid.cs +++ b/Content.Server/Construction/Completions/SnapToGrid.cs @@ -6,6 +6,7 @@ using JetBrains.Annotations; using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Serialization; +using Robust.Shared.Maths; namespace Content.Server.Construction.Completions { @@ -13,10 +14,12 @@ namespace Content.Server.Construction.Completions public class SnapToGrid : IGraphAction { public SnapGridOffset Offset { get; private set; } = SnapGridOffset.Center; + public bool SouthRotation { get; private set; } = false; public void ExposeData(ObjectSerializer serializer) { serializer.DataField(this, x => x.Offset, "offset", SnapGridOffset.Center); + serializer.DataField(this, x => x.SouthRotation, "southRotation", false); } public async Task PerformAction(IEntity entity, IEntity? user) @@ -24,6 +27,10 @@ namespace Content.Server.Construction.Completions if (entity.Deleted) return; entity.SnapToGrid(Offset); + if (SouthRotation) + { + entity.Transform.LocalRotation = Angle.South; + } } } } diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/catwalk.yml b/Resources/Prototypes/Recipes/Construction/Graphs/catwalk.yml index a218cf8e4c..a43bb1d729 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/catwalk.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/catwalk.yml @@ -6,7 +6,8 @@ edges: - to: Catwalk completed: - - !type:SnapToGrid { } + - !type:SnapToGrid + southRotation: true steps: - material: MetalRod amount: 2 diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/girder.yml b/Resources/Prototypes/Recipes/Construction/Graphs/girder.yml index b1fff3816a..ff840def92 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/girder.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/girder.yml @@ -6,7 +6,8 @@ edges: - to: girder completed: - - !type:SnapToGrid { } + - !type:SnapToGrid + southRotation: true steps: - material: Metal amount: 2 @@ -35,7 +36,8 @@ - to: wall completed: - - !type:SnapToGrid {} + - !type:SnapToGrid + southRotation: true conditions: - !type:EntityAnchored {} steps: @@ -44,7 +46,8 @@ - to: reinforcedGirder completed: - - !type:SnapToGrid { } + - !type:SnapToGrid + southRotation: true conditions: - !type:EntityAnchored {} steps: @@ -72,7 +75,8 @@ edges: - to: reinforcedWall completed: - - !type:SnapToGrid { } + - !type:SnapToGrid + southRotation: true conditions: - !type:EntityAnchored { } steps: @@ -82,7 +86,8 @@ - to: girder completed: - - !type:SnapToGrid { } + - !type:SnapToGrid + southRotation: true conditions: - !type:EntityAnchored { } steps: diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/tables.yml b/Resources/Prototypes/Recipes/Construction/Graphs/tables.yml index afe3f1753c..f94b12ace6 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/tables.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/tables.yml @@ -6,7 +6,8 @@ edges: - to: TableFrame completed: - - !type:SnapToGrid { } + - !type:SnapToGrid + southRotation: true steps: - material: MetalRod amount: 2 From 80ad2ef5b776154c900c2d4d63f8eee052ff0a32 Mon Sep 17 00:00:00 2001 From: 20kdc Date: Mon, 1 Feb 2021 22:46:20 +0000 Subject: [PATCH 39/61] Botany composting & shovel/spade (or, make botany sustainable) (#3064) * Botany: Introduce shovel and spade into hydro\_tools * Botany: Add shovel/spade (just removes plants) * Botany: Add shovel/spade lathe recipes and make all botany lathe recipes accessible * Botany: Add spade into Saltern * Botany: Give Saltern a bucket for the botanist * Botany: Plant produce can be composted for nutriment/etc. * Botany: Bye-bye duplicated force update code, hello ForceUpdateByExternalCause * Botany: Shovel identity crisis resolved (thanks Paul) --- .../Components/Botany/PlantHolderComponent.cs | 49 +++++++++++++++--- .../Components/Botany/ShovelComponent.cs | 11 ++++ Resources/Maps/saltern.yml | 14 +++++ .../Catalog/LatheRecipes/botany.yml | 23 ++++++++ .../Entities/Constructible/Power/lathe.yml | 5 ++ .../Entities/Objects/Tools/botany_tools.yml | 29 +++++++++++ .../Hydroponics/hydro_tools.rsi/meta.json | 2 +- .../Hydroponics/hydro_tools.rsi/shovel.png | Bin 0 -> 563 bytes .../Hydroponics/hydro_tools.rsi/spade.png | Bin 0 -> 633 bytes 9 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 Content.Server/GameObjects/Components/Botany/ShovelComponent.cs create mode 100644 Resources/Textures/Constructible/Hydroponics/hydro_tools.rsi/shovel.png create mode 100644 Resources/Textures/Constructible/Hydroponics/hydro_tools.rsi/spade.png diff --git a/Content.Server/GameObjects/Components/Botany/PlantHolderComponent.cs b/Content.Server/GameObjects/Components/Botany/PlantHolderComponent.cs index 6d481b80ed..ec9fa8b951 100644 --- a/Content.Server/GameObjects/Components/Botany/PlantHolderComponent.cs +++ b/Content.Server/GameObjects/Components/Botany/PlantHolderComponent.cs @@ -639,6 +639,13 @@ namespace Content.Server.GameObjects.Components.Botany Seed = Seed.Diverge(modified); } + private void ForceUpdateByExternalCause() + { + SkipAging++; // We're forcing an update cycle, so one age hasn't passed. + ForceUpdate = true; + Update(); + } + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { var user = eventArgs.User; @@ -695,6 +702,22 @@ namespace Content.Server.GameObjects.Components.Botany return true; } + if (usingItem.HasComponent()) + { + if (Seed != null) + { + user.PopupMessageCursor(Loc.GetString("You remove the plant from the {0}.", Owner.Name)); + user.PopupMessageOtherClients(Loc.GetString("{0} removes the plant.", user.Name)); + RemovePlant(); + } + else + { + user.PopupMessageCursor(Loc.GetString("There is no plant to remove.")); + } + + return true; + } + if (usingItem.TryGetComponent(out SolutionContainerComponent? solution) && solution.CanRemoveSolutions) { var amount = 5f; @@ -715,9 +738,7 @@ namespace Content.Server.GameObjects.Components.Botany _solutionContainer?.TryAddSolution(split); - SkipAging++; // We're forcing an update cycle, so one age hasn't passed. - ForceUpdate = true; - Update(); + ForceUpdateByExternalCause(); return true; } @@ -752,9 +773,7 @@ namespace Content.Server.GameObjects.Components.Botany // Just in case. CheckLevelSanity(); - SkipAging++; // We're forcing an update cycle, so one age hasn't passed. - ForceUpdate = true; - Update(); + ForceUpdateByExternalCause(); return true; } @@ -764,6 +783,24 @@ namespace Content.Server.GameObjects.Components.Botany return DoHarvest(user); } + if (usingItem.HasComponent()) + { + user.PopupMessageCursor(Loc.GetString("You compost {1:theName} into {0:theName}.", Owner, usingItem)); + user.PopupMessageOtherClients(Loc.GetString("{0:TheName} composts {1:theName} into {2:theName}.", user, usingItem, Owner)); + + if (usingItem.TryGetComponent(out SolutionContainerComponent? solution2)) + { + // This deliberately discards overfill. + _solutionContainer?.TryAddSolution(solution2.SplitSolution(solution2.Solution.TotalVolume)); + + ForceUpdateByExternalCause(); + } + + usingItem.Delete(); + + return true; + } + return false; } diff --git a/Content.Server/GameObjects/Components/Botany/ShovelComponent.cs b/Content.Server/GameObjects/Components/Botany/ShovelComponent.cs new file mode 100644 index 0000000000..be6afbb8a1 --- /dev/null +++ b/Content.Server/GameObjects/Components/Botany/ShovelComponent.cs @@ -0,0 +1,11 @@ +#nullable enable +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Botany +{ + [RegisterComponent] + public class ShovelComponent : Component + { + public override string Name => "Shovel"; + } +} diff --git a/Resources/Maps/saltern.yml b/Resources/Maps/saltern.yml index 6ec3a2cf13..a97f8ac388 100644 --- a/Resources/Maps/saltern.yml +++ b/Resources/Maps/saltern.yml @@ -48835,4 +48835,18 @@ entities: type: PowerConsumer - supplyRate: 6000 type: PowerSupplier +- uid: 4717 + type: Spade + components: + - parent: 853 + pos: -20.5,-1.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 4718 + type: Bucket + components: + - parent: 853 + pos: -20.5,-1.5 + rot: -1.5707963267948966 rad + type: Transform ... diff --git a/Resources/Prototypes/Catalog/LatheRecipes/botany.yml b/Resources/Prototypes/Catalog/LatheRecipes/botany.yml index 57d7115d1c..d011c676da 100644 --- a/Resources/Prototypes/Catalog/LatheRecipes/botany.yml +++ b/Resources/Prototypes/Catalog/LatheRecipes/botany.yml @@ -30,3 +30,26 @@ materials: steel: 60 glass: 10 + +- type: latheRecipe + id: Shovel + icon: + sprite: Constructible/Hydroponics/hydro_tools.rsi + state: shovel + result: Shovel + completetime: 500 + materials: + steel: 60 + glass: 10 + +- type: latheRecipe + id: Spade + icon: + sprite: Constructible/Hydroponics/hydro_tools.rsi + state: spade + result: Spade + completetime: 500 + materials: + steel: 30 + glass: 10 + diff --git a/Resources/Prototypes/Entities/Constructible/Power/lathe.yml b/Resources/Prototypes/Entities/Constructible/Power/lathe.yml index 16c48e451f..9bfa935619 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/lathe.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/lathe.yml @@ -77,6 +77,11 @@ - CableStack - Crowbar - Multitool + - MiniHoe + - Scythe + - Hatchet + - Shovel + - Spade - type: Appearance visuals: - type: AutolatheVisualizer diff --git a/Resources/Prototypes/Entities/Objects/Tools/botany_tools.yml b/Resources/Prototypes/Entities/Objects/Tools/botany_tools.yml index b4dceedbf2..1d1025be6e 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/botany_tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/botany_tools.yml @@ -98,3 +98,32 @@ - type: MeleeWeapon - type: BotanySharp - type: Item + +- type: entity + name: spade + parent: BaseItem + id: Spade + description: A small tool for digging and moving dirt. + components: + - type: Sprite + sprite: Constructible/Hydroponics/hydro_tools.rsi + state: spade + - type: ItemCooldown + - type: MeleeWeapon + - type: Shovel + - type: Item + +- type: entity + name: shovel + parent: BaseItem + id: Shovel + description: A large tool for digging and moving dirt. + components: + - type: Sprite + sprite: Constructible/Hydroponics/hydro_tools.rsi + state: shovel + - type: ItemCooldown + - type: MeleeWeapon + - type: Shovel + - type: Item + diff --git a/Resources/Textures/Constructible/Hydroponics/hydro_tools.rsi/meta.json b/Resources/Textures/Constructible/Hydroponics/hydro_tools.rsi/meta.json index 7c0496334f..8fd6d66a8e 100644 --- a/Resources/Textures/Constructible/Hydroponics/hydro_tools.rsi/meta.json +++ b/Resources/Textures/Constructible/Hydroponics/hydro_tools.rsi/meta.json @@ -1 +1 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA 3.0", "copyright": "Taken from https://github.com/vgstation-coders/vgstation13 at 1dbcf389b0ec6b2c51b002df5fef8dd1519f8068", "states": [{"name": "claypot", "delays": [[1.0]]}, {"name": "claypot-item", "delays": [[1.0]]}, {"name": "claypot-large", "delays": [[1.0]]}, {"name": "cyan", "delays": [[1.0]]}, {"name": "cyan black stripe", "delays": [[1.0]]}, {"name": "cyan blue stripe", "delays": [[1.0]]}, {"name": "cyan lime stripe", "delays": [[1.0]]}, {"name": "cyan purple stripe", "delays": [[1.0]]}, {"name": "cyan red stripe", "delays": [[1.0]]}, {"name": "cyan white stripe", "delays": [[1.0]]}, {"name": "cyan yellow stripe", "delays": [[1.0]]}, {"name": "deathspray", "delays": [[1.0]]}, {"name": "disk", "delays": [[0.1, 0.1, 0.1]]}, {"name": "green black stripe", "delays": [[1.0]]}, {"name": "green blue stripe", "delays": [[1.0]]}, {"name": "green lime stripe", "delays": [[1.0]]}, {"name": "green purple stripe", "delays": [[1.0]]}, {"name": "green red stripe", "delays": [[1.0]]}, {"name": "green white stripe", "delays": [[1.0]]}, {"name": "green yellow stripe", "delays": [[1.0]]}, {"name": "hydrocover", "delays": [[1.0]]}, {"name": "hydrotray", "delays": [[1.0]]}, {"name": "hydrotray2", "delays": [[1.0]]}, {"name": "hydrotray3", "delays": [[1.0]]}, {"name": "moldcreep0", "delays": [[1.0]]}, {"name": "moldcreep1", "delays": [[1.0]]}, {"name": "moldcreep2", "delays": [[1.0]]}, {"name": "nolabelspray", "delays": [[1.0]]}, {"name": "over_alert3", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "over_harvest3", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "over_lowhealth3", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "over_lownutri", "delays": [[1.0]]}, {"name": "over_lownutri3", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "over_lowwater3", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "pestspray", "delays": [[1.0]]}, {"name": "plantbag", "delays": [[1.0]]}, {"name": "plantbgone", "delays": [[1.0]]}, {"name": "portaseeder", "delays": [[1.0]]}, {"name": "seedbag", "delays": [[1.0]]}, {"name": "sextractor", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "soil", "delays": [[1.0]]}, {"name": "spaceworms", "delays": [[0.4, 0.4, 0.4, 0.4]]}, {"name": "spawner", "delays": [[1.0]]}, {"name": "sprayparts", "delays": [[1.0]]}, {"name": "traitcopier", "delays": [[1.0]]}, {"name": "traitgun", "delays": [[1.0]]}, {"name": "traitscanner", "delays": [[1.0]]}, {"name": "vine_flowers", "delays": [[1.0]]}, {"name": "vine_fruit", "delays": [[1.0]]}, {"name": "weedspray", "delays": [[1.0]]}, {"name": "scythe", "delays": [[1.0]]}, {"name": "hoe", "delays": [[1.0]]}, {"name": "hatchet", "delays": [[1.0]]}]} +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA 3.0", "copyright": "Taken from https://github.com/vgstation-coders/vgstation13 at 1dbcf389b0ec6b2c51b002df5fef8dd1519f8068", "states": [{"name": "claypot", "delays": [[1.0]]}, {"name": "claypot-item", "delays": [[1.0]]}, {"name": "claypot-large", "delays": [[1.0]]}, {"name": "cyan", "delays": [[1.0]]}, {"name": "cyan black stripe", "delays": [[1.0]]}, {"name": "cyan blue stripe", "delays": [[1.0]]}, {"name": "cyan lime stripe", "delays": [[1.0]]}, {"name": "cyan purple stripe", "delays": [[1.0]]}, {"name": "cyan red stripe", "delays": [[1.0]]}, {"name": "cyan white stripe", "delays": [[1.0]]}, {"name": "cyan yellow stripe", "delays": [[1.0]]}, {"name": "deathspray", "delays": [[1.0]]}, {"name": "disk", "delays": [[0.1, 0.1, 0.1]]}, {"name": "green black stripe", "delays": [[1.0]]}, {"name": "green blue stripe", "delays": [[1.0]]}, {"name": "green lime stripe", "delays": [[1.0]]}, {"name": "green purple stripe", "delays": [[1.0]]}, {"name": "green red stripe", "delays": [[1.0]]}, {"name": "green white stripe", "delays": [[1.0]]}, {"name": "green yellow stripe", "delays": [[1.0]]}, {"name": "hydrocover", "delays": [[1.0]]}, {"name": "hydrotray", "delays": [[1.0]]}, {"name": "hydrotray2", "delays": [[1.0]]}, {"name": "hydrotray3", "delays": [[1.0]]}, {"name": "moldcreep0", "delays": [[1.0]]}, {"name": "moldcreep1", "delays": [[1.0]]}, {"name": "moldcreep2", "delays": [[1.0]]}, {"name": "nolabelspray", "delays": [[1.0]]}, {"name": "over_alert3", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "over_harvest3", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "over_lowhealth3", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "over_lownutri", "delays": [[1.0]]}, {"name": "over_lownutri3", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "over_lowwater3", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "pestspray", "delays": [[1.0]]}, {"name": "plantbag", "delays": [[1.0]]}, {"name": "plantbgone", "delays": [[1.0]]}, {"name": "portaseeder", "delays": [[1.0]]}, {"name": "seedbag", "delays": [[1.0]]}, {"name": "sextractor", "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "soil", "delays": [[1.0]]}, {"name": "spaceworms", "delays": [[0.4, 0.4, 0.4, 0.4]]}, {"name": "spawner", "delays": [[1.0]]}, {"name": "sprayparts", "delays": [[1.0]]}, {"name": "traitcopier", "delays": [[1.0]]}, {"name": "traitgun", "delays": [[1.0]]}, {"name": "traitscanner", "delays": [[1.0]]}, {"name": "vine_flowers", "delays": [[1.0]]}, {"name": "vine_fruit", "delays": [[1.0]]}, {"name": "weedspray", "delays": [[1.0]]}, {"name": "scythe", "delays": [[1.0]]}, {"name": "hoe", "delays": [[1.0]]}, {"name": "hatchet", "delays": [[1.0]]}, {"name": "shovel", "delays": [[1.0]]}, {"name": "spade", "delays": [[1.0]]}]} diff --git a/Resources/Textures/Constructible/Hydroponics/hydro_tools.rsi/shovel.png b/Resources/Textures/Constructible/Hydroponics/hydro_tools.rsi/shovel.png new file mode 100644 index 0000000000000000000000000000000000000000..c7eed381719d6e17bf9cb639bcf9471b49aed50c GIT binary patch literal 563 zcmV-30?hr1P)vIpb-S&ZYWa9Eh0Kup+f%xp_?uaj*f2P=H}M9qjUcVHwQmx zM~8qN1n;txn!wR54w8^GY3avZ@=hUv{GR81?|nEDK7=T`T74%3fVUf;kfH^&`cBAp zchElI@Fr7OWx)ZU*BO#ZS zyTfO)?msLYtG0>rtps!j^-xTHpIUP!8s z)zk;ZSo};Wg_JTDwM|tP!IMA$5JG@421=>c+t{p literal 0 HcmV?d00001 diff --git a/Resources/Textures/Constructible/Hydroponics/hydro_tools.rsi/spade.png b/Resources/Textures/Constructible/Hydroponics/hydro_tools.rsi/spade.png new file mode 100644 index 0000000000000000000000000000000000000000..d76bc3de57394eb85fb388586d92de648eb3114f GIT binary patch literal 633 zcmV-<0*3vGP)=%tOaIVsj~;A zO6fvL+znSG4p6F;^rN*095kBoSr~@#KPe@h&5lUaf`djA(=_AehGAeB1^~8glX;!d ziQj=B2vABgkgx|IMnNP9LGT^MHq%L#Gtz}Hb5kM)I9q7zCYbD zv@Gi!L}W)|0Pcn>eBX~7<#M@yAONJ4*tR{fu{@`Mlo9}?6oz5MN?4XfKA$I@PA6+Y zXR{;vAtE)Z)hex4i$bBm&YPX&Hem#DcGPU!{%e!#vi)is{aowy{q?bJ5Q840{!#1q zTxh^9)%g2Z0pMkLk-$sNO`8E9UnUiHLDO|aB7>3#yg0(XXlm{=rhmpyUvMX=m4`RzgQ0d770KSXMhYqeeW zg!TEV?q#9(9hO>ISebGtRrfs6t-@j}3q!1i6bV%LaQLM(^H@Fym;=lKW)AQNGbiMt Ta!>Ub00000NkvXXu0mjf@Q)DO literal 0 HcmV?d00001 From 8b5d66050aa35b89e43b8331bb54118a19a6794c Mon Sep 17 00:00:00 2001 From: Acruid Date: Mon, 1 Feb 2021 16:49:43 -0800 Subject: [PATCH 40/61] Console Unify API Changes (#3059) * Remove unused IChatCommand. * Lots of refactoring into a shared context. * Removed ICommonSession from server concmd Execute. * Added argStr parameter to concmd execute. * The execute function of client concmds now returns void, use the new shell.RemoteExecuteCommand function to forward commands. * Finally move shells and commands into shared. * Console commands can now be registered directly without a class in a shared context. * Engine API Changes. * Repair rebase damage. * Update Submodule. --- Content.Client/Chat/ChatManager.cs | 14 ++--- Content.Client/ClientNotifyManager.cs | 5 +- Content.Client/Commands/AtmosDebugCommands.cs | 55 +++++++++---------- Content.Client/Commands/CreditsCommand.cs | 5 +- Content.Client/Commands/DebugAiCommand.cs | 12 ++-- Content.Client/Commands/DebugCommands.cs | 31 ++++------- .../Commands/DebugPathfindingCommand.cs | 12 ++-- .../Commands/HideMechanismsCommand.cs | 8 +-- .../Commands/ShowMechanismsCommand.cs | 8 +-- .../Commands/ToggleOutlineCommand.cs | 8 +-- Content.Client/EscapeMenuOwner.cs | 6 +- Content.Client/Sandbox/SandboxManager.cs | 16 +++--- Content.Client/State/LobbyState.cs | 8 +-- .../AdminMenu/AdminMenuWindow.cs | 16 +++--- .../AdminMenu/SetOutfit/SetOutfitMenu.xaml.cs | 4 +- Content.Client/UserInterface/EscapeMenu.cs | 10 ++-- Content.Client/UserInterface/LateJoinGui.cs | 4 +- Content.IntegrationTests/DummyGameTicker.cs | 1 - .../Tests/Commands/RestartRoundTest.cs | 5 +- .../Components/ActionBlocking/CuffUnitTest.cs | 8 +-- .../Tests/Networking/ReconnectTest.cs | 2 +- .../Administration/AdminCommandAttribute.cs | 4 +- Content.Server/Administration/AdminManager.cs | 10 ++-- .../Administration/AnyCommandAttribute.cs | 4 +- .../Administration/Commands/AGhost.cs | 11 ++-- .../Administration/Commands/BanCommand.cs | 11 ++-- .../Administration/Commands/ControlMob.cs | 17 +++--- .../Administration/Commands/DSay.cs | 9 +-- .../Administration/Commands/DeAdminCommand.cs | 11 ++-- .../Commands/DeleteComponent.cs | 12 ++-- .../Commands/DeleteEntitiesWithComponent.cs | 10 ++-- .../Commands/DeleteEntitiesWithId.cs | 10 ++-- .../Commands/ExplosionCommand.cs | 11 ++-- .../Commands/OpenPermissionsCommand.cs | 11 ++-- .../Commands/PromoteHostCommand.cs | 10 ++-- .../Administration/Commands/ReAdminCommand.cs | 11 ++-- .../Administration/Commands/ReadyAll.cs | 8 +-- .../Administration/Commands/Rejuvenate.cs | 15 ++--- .../Commands/SetOutfitCommand.cs | 17 +++--- .../Administration/Commands/WarpCommand.cs | 19 ++++--- Content.Server/Commands/AI/AddAiCommand.cs | 14 ++--- Content.Server/Commands/AI/FactionCommand.cs | 28 +++++----- .../Commands/Actions/CooldownAction.cs | 19 ++++--- .../Commands/Actions/GrantAction.cs | 17 +++--- .../Commands/Actions/RevokeAction.cs | 17 +++--- Content.Server/Commands/Alerts/ClearAlert.cs | 15 ++--- Content.Server/Commands/Alerts/ShowAlert.cs | 19 ++++--- .../Commands/Atmos/AddAtmosCommand.cs | 18 +++--- .../Commands/Atmos/AddGasCommand.cs | 16 +++--- .../Atmos/AddUnsimulatedAtmosCommand.cs | 18 +++--- .../Commands/Atmos/DeleteGasCommand.cs | 43 ++++++++------- .../Commands/Atmos/FillGasCommand.cs | 12 ++-- .../Commands/Atmos/ListGasesCommand.cs | 8 +-- .../Commands/Atmos/RemoveGasCommand.cs | 16 +++--- .../Atmos/SetAtmosTemperatureCommand.cs | 16 +++--- .../Commands/Atmos/SetTemperatureCommand.cs | 18 +++--- .../Commands/Atmos/ShowAtmosCommand.cs | 13 +++-- .../Commands/AttachBodyPartCommand.cs | 31 ++++++----- .../Commands/Body/AddHandCommand.cs | 36 ++++++------ .../Commands/Body/DestroyMechanismCommand.cs | 21 +++---- .../Commands/Body/RemoveHandCommand.cs | 17 +++--- .../Commands/Chat/AdminChatCommand.cs | 9 +-- Content.Server/Commands/Chat/MeCommand.cs | 13 +++-- Content.Server/Commands/Chat/OOCCommand.cs | 9 +-- Content.Server/Commands/Chat/SayCommand.cs | 15 ++--- .../Commands/Chat/SuicideCommand.cs | 15 ++--- Content.Server/Commands/CommandUtils.cs | 9 +-- .../Commands/Damage/AddDamageFlagCommand.cs | 10 ++-- .../Commands/Damage/DamageFlagCommand.cs | 26 ++++----- .../Commands/Damage/GodModeCommand.cs | 21 +++---- .../Damage/RemoveDamageFlagCommand.cs | 10 ++-- .../Disposal/TubeConnectionsCommand.cs | 19 ++++--- .../Commands/FindEntitiesWithComponents.cs | 14 ++--- .../Commands/GameTicking/DelayStartCommand.cs | 16 +++--- .../Commands/GameTicking/EndRoundCommand.cs | 8 +-- .../GameTicking/ForcePresetCommand.cs | 14 ++--- .../Commands/GameTicking/GoLobbyCommand.cs | 10 ++-- .../Commands/GameTicking/JoinGameCommand.cs | 15 ++--- .../Commands/GameTicking/MappingCommand.cs | 27 ++++----- .../Commands/GameTicking/NewRoundCommand.cs | 6 +- .../Commands/GameTicking/ObserveCommand.cs | 11 ++-- .../Commands/GameTicking/RespawnCommand.cs | 22 ++++---- .../GameTicking/SetGamePresetCommand.cs | 8 +-- .../Commands/GameTicking/StartRoundCommand.cs | 8 +-- .../Commands/GameTicking/TileWallsCommand.cs | 21 +++---- .../ToggleDisallowLateJoinCommand.cs | 12 ++-- .../GameTicking/ToggleReadyCommand.cs | 11 ++-- .../Commands/HideContainedContextCommand.cs | 11 ++-- Content.Server/Commands/Hungry.cs | 15 ++--- Content.Server/Commands/HurtCommand.cs | 44 +++++++-------- .../Commands/Interactable/AnchorCommand.cs | 15 ++--- .../Commands/Interactable/TilePryCommand.cs | 15 ++--- .../Commands/Interactable/UnAnchorCommand.cs | 15 ++--- .../MachineLinking/SignalLinkerCommand.cs | 13 +++-- .../Commands/MakeSentientCommand.cs | 12 ++-- .../Commands/Mobs/AddRoleCommand.cs | 10 ++-- .../Commands/Mobs/MindInfoCommand.cs | 12 ++-- .../Commands/Mobs/RemoveRoleCommand.cs | 10 ++-- .../Commands/Notify/PopupMsgCommand.cs | 6 +- .../Objectives/AddObjectiveCommand.cs | 16 +++--- .../Objectives/ListObjectivesCommand.cs | 19 ++++--- .../Objectives/RemoveObjectiveCommand.cs | 21 ++++--- Content.Server/Commands/Observer/Ghost.cs | 15 ++--- .../Commands/RemoveExtraComponents.cs | 10 ++-- Content.Server/Commands/SetAnchorCommand.cs | 14 ++--- .../Commands/ShowContainedContextCommand.cs | 11 ++-- Content.Server/Commands/Speech/AddAccent.cs | 19 ++++--- .../Commands/StartSingularityEngineCommand.cs | 10 ++-- .../StationEvents/StationEventCommand.cs | 33 +++++------ .../Components/Body/BodyComponent.cs | 9 +-- .../Components/GUI/InventoryComponent.cs | 9 +-- .../GameObjects/EntitySystems/AI/AiSystem.cs | 1 - Content.Server/GameTicking/GamePreset.cs | 1 - .../GlobalVerbs/MakeSentientVerb.cs | 9 ++- .../Interfaces/Chat/IChatCommand.cs | 10 ---- .../Interfaces/GameTicking/IGameTicker.cs | 1 - Content.Server/Sandbox/SandboxManager.cs | 7 +-- Content.Server/ServerNotifyManager.cs | 1 - RobustToolbox | 2 +- 119 files changed, 820 insertions(+), 796 deletions(-) delete mode 100644 Content.Server/Interfaces/Chat/IChatCommand.cs diff --git a/Content.Client/Chat/ChatManager.cs b/Content.Client/Chat/ChatManager.cs index 96910110f8..490bdd7a0c 100644 --- a/Content.Client/Chat/ChatManager.cs +++ b/Content.Client/Chat/ChatManager.cs @@ -78,7 +78,7 @@ namespace Content.Client.Chat private ChatChannel _filteredChannels; [Dependency] private readonly IClientNetManager _netManager = default!; - [Dependency] private readonly IClientConsole _console = default!; + [Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; @@ -255,7 +255,7 @@ namespace Content.Client.Chat { // run locally var conInput = text.Substring(1); - _console.ProcessCommand(conInput); + _consoleHost.ExecuteCommand(conInput); break; } case OOCAlias: @@ -263,7 +263,7 @@ namespace Content.Client.Chat var conInput = text.Substring(1); if (string.IsNullOrWhiteSpace(conInput)) return; - _console.ProcessCommand($"ooc \"{CommandParsing.Escape(conInput)}\""); + _consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(conInput)}\""); break; } case AdminChatAlias: @@ -273,11 +273,11 @@ namespace Content.Client.Chat return; if (_groupController.CanCommand("asay")) { - _console.ProcessCommand($"asay \"{CommandParsing.Escape(conInput)}\""); + _consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(conInput)}\""); } else { - _console.ProcessCommand($"ooc \"{CommandParsing.Escape(conInput)}\""); + _consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(conInput)}\""); } break; @@ -287,7 +287,7 @@ namespace Content.Client.Chat var conInput = text.Substring(1); if (string.IsNullOrWhiteSpace(conInput)) return; - _console.ProcessCommand($"me \"{CommandParsing.Escape(conInput)}\""); + _consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(conInput)}\""); break; } default: @@ -295,7 +295,7 @@ namespace Content.Client.Chat var conInput = _currentChatBox?.DefaultChatFormat != null ? string.Format(_currentChatBox.DefaultChatFormat, CommandParsing.Escape(text)) : text; - _console.ProcessCommand(conInput); + _consoleHost.ExecuteCommand(conInput); break; } } diff --git a/Content.Client/ClientNotifyManager.cs b/Content.Client/ClientNotifyManager.cs index 847417aac5..818c0829b1 100644 --- a/Content.Client/ClientNotifyManager.cs +++ b/Content.Client/ClientNotifyManager.cs @@ -4,12 +4,12 @@ using System.Collections.Generic; using Content.Client.Interfaces; using Content.Client.UserInterface.Stylesheets; using Content.Shared; -using Robust.Client.Interfaces.Console; using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Input; using Robust.Client.Interfaces.UserInterface; using Robust.Client.Player; using Robust.Client.UserInterface.Controls; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; @@ -168,12 +168,11 @@ namespace Content.Client public string Description => ""; public string Help => ""; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var arg = args[0]; var mgr = IoCManager.Resolve(); mgr.PopupMessage(arg); - return false; } } } diff --git a/Content.Client/Commands/AtmosDebugCommands.cs b/Content.Client/Commands/AtmosDebugCommands.cs index 823d1183e7..10f6856f01 100644 --- a/Content.Client/Commands/AtmosDebugCommands.cs +++ b/Content.Client/Commands/AtmosDebugCommands.cs @@ -1,9 +1,9 @@ using JetBrains.Annotations; -using Robust.Client.Interfaces.Console; using Content.Client.GameObjects.EntitySystems; using Robust.Shared.GameObjects.Systems; using Content.Shared.Atmos; using System; +using Robust.Shared.Console; namespace Content.Client.Commands { @@ -13,32 +13,31 @@ namespace Content.Client.Commands public string Command => "atvrange"; public string Description => "Sets the atmos debug range (as two floats, start [red] and end [blue])"; public string Help => "atvrange "; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 2) { - console.AddLine(Help); - return false; + shell.WriteLine(Help); + return; } if (!float.TryParse(args[0], out var xStart)) { - console.AddLine("Bad float START"); - return false; + shell.WriteLine("Bad float START"); + return; } if (!float.TryParse(args[1], out var xEnd)) { - console.AddLine("Bad float END"); - return false; + shell.WriteLine("Bad float END"); + return; } if (xStart == xEnd) { - console.AddLine("Scale cannot be zero, as this would cause a division by zero in AtmosDebugOverlay."); - return false; + shell.WriteLine("Scale cannot be zero, as this would cause a division by zero in AtmosDebugOverlay."); + return; } var sys = EntitySystem.Get(); sys.CfgBase = xStart; sys.CfgScale = xEnd - xStart; - return false; } } @@ -48,17 +47,17 @@ namespace Content.Client.Commands public string Command => "atvmode"; public string Description => "Sets the atmos debug mode. This will automatically reset the scale."; public string Help => "atvmode []"; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length < 1) { - console.AddLine(Help); - return false; + shell.WriteLine(Help); + return; } if (!Enum.TryParse(args[0], out var xMode)) { - console.AddLine("Invalid mode"); - return false; + shell.WriteLine("Invalid mode"); + return; } int xSpecificGas = 0; float xBase = 0; @@ -67,21 +66,21 @@ namespace Content.Client.Commands { if (args.Length != 2) { - console.AddLine("A target gas must be provided for this mode."); - return false; + shell.WriteLine("A target gas must be provided for this mode."); + return; } if (!AtmosCommandUtils.TryParseGasID(args[1], out xSpecificGas)) { - console.AddLine("Gas ID not parsable or out of range."); - return false; + shell.WriteLine("Gas ID not parsable or out of range."); + return; } } else { if (args.Length != 1) { - console.AddLine("No further information is required for this mode."); - return false; + shell.WriteLine("No further information is required for this mode."); + return; } if (xMode == AtmosDebugOverlayMode.Temperature) { @@ -95,7 +94,6 @@ namespace Content.Client.Commands sys.CfgSpecificGas = xSpecificGas; sys.CfgBase = xBase; sys.CfgScale = xScale; - return false; } } @@ -105,21 +103,20 @@ namespace Content.Client.Commands public string Command => "atvcbm"; public string Description => "Changes from red/green/blue to greyscale"; public string Help => "atvcbm "; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 1) { - console.AddLine(Help); - return false; + shell.WriteLine(Help); + return; } if (!bool.TryParse(args[0], out var xFlag)) { - console.AddLine("Invalid flag"); - return false; + shell.WriteLine("Invalid flag"); + return; } var sys = EntitySystem.Get(); sys.CfgCBM = xFlag; - return false; } } } diff --git a/Content.Client/Commands/CreditsCommand.cs b/Content.Client/Commands/CreditsCommand.cs index 47643b11ac..f3fa57e85e 100644 --- a/Content.Client/Commands/CreditsCommand.cs +++ b/Content.Client/Commands/CreditsCommand.cs @@ -1,6 +1,6 @@ using Content.Client.UserInterface; using JetBrains.Annotations; -using Robust.Client.Interfaces.Console; +using Robust.Shared.Console; namespace Content.Client.Commands { @@ -11,10 +11,9 @@ namespace Content.Client.Commands public string Description => "Opens the credits window"; public string Help => "credits"; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { new CreditsWindow().Open(); - return false; } } } diff --git a/Content.Client/Commands/DebugAiCommand.cs b/Content.Client/Commands/DebugAiCommand.cs index 065da01bab..ddaadc153d 100644 --- a/Content.Client/Commands/DebugAiCommand.cs +++ b/Content.Client/Commands/DebugAiCommand.cs @@ -1,6 +1,6 @@ using Content.Client.GameObjects.EntitySystems.AI; using JetBrains.Annotations; -using Robust.Client.Interfaces.Console; +using Robust.Shared.Console; using Robust.Shared.GameObjects.Systems; namespace Content.Client.Commands @@ -16,12 +16,13 @@ namespace Content.Client.Commands public string Description => "Handles all tooltip debugging above AI mobs"; public string Help => "debugai [hide/paths/thonk]"; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { #if DEBUG if (args.Length < 1) { - return true; + shell.RemoteExecuteCommand(argStr); + return; } var anyAction = false; @@ -50,9 +51,10 @@ namespace Content.Client.Commands } } - return !anyAction; + if(!anyAction) + shell.RemoteExecuteCommand(argStr); #else - return true; + shell.RemoteExecuteCommand(argStr); #endif } } diff --git a/Content.Client/Commands/DebugCommands.cs b/Content.Client/Commands/DebugCommands.cs index aa9eef2a94..0ea5efc6c7 100644 --- a/Content.Client/Commands/DebugCommands.cs +++ b/Content.Client/Commands/DebugCommands.cs @@ -1,9 +1,10 @@ +using System; using Content.Client.GameObjects.Components; using Content.Client.GameObjects.EntitySystems; using Content.Client.Interfaces; using Content.Shared.GameObjects; -using Robust.Client.Interfaces.Console; using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Shared.Console; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -17,12 +18,10 @@ namespace Content.Client.Commands public string Description => "Toggles visibility of markers such as spawn points."; public string Help => ""; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { EntitySystem.Get() .MarkersVisible ^= true; - - return false; } } @@ -33,12 +32,10 @@ namespace Content.Client.Commands public string Description => "Makes entities below the floor always visible."; public string Help => $"Usage: {Command}"; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { EntitySystem.Get() .EnableAll ^= true; - - return false; } } @@ -49,7 +46,7 @@ namespace Content.Client.Commands public string Description => "Makes entities below the floor always visible until the client is restarted."; public string Help => $"Usage: {Command}"; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { EntitySystem.Get() .EnableAll = true; @@ -64,8 +61,6 @@ namespace Content.Client.Commands sprite.DrawDepth = (int) DrawDepth.Overlays; } } - - return false; } } @@ -75,14 +70,12 @@ namespace Content.Client.Commands public string Description => "Send a notify client side."; public string Help => "notify "; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var message = args[0]; var notifyManager = IoCManager.Resolve(); notifyManager.PopupMessage(message); - - return false; } } @@ -92,18 +85,18 @@ namespace Content.Client.Commands public string Description => "Creates and teleports you to a new uninitialized map for mapping."; public string Help => $"Usage: {Command} / {Command} "; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length == 0) { - console.AddLine(Help); - return false; + shell.WriteLine(Help); + return; } - console.Commands["togglelight"].Execute(console); - console.Commands["showsubfloorforever"].Execute(console); + shell.ConsoleHost.RegisteredCommands["togglelight"].Execute(shell, string.Empty, Array.Empty()); + shell.ConsoleHost.RegisteredCommands["showsubfloorforever"].Execute(shell, string.Empty, Array.Empty()); - return true; + shell.RemoteExecuteCommand(argStr); } } } diff --git a/Content.Client/Commands/DebugPathfindingCommand.cs b/Content.Client/Commands/DebugPathfindingCommand.cs index 9c79bec8e7..0d6135abbb 100644 --- a/Content.Client/Commands/DebugPathfindingCommand.cs +++ b/Content.Client/Commands/DebugPathfindingCommand.cs @@ -1,6 +1,6 @@ using Content.Client.GameObjects.EntitySystems.AI; using JetBrains.Annotations; -using Robust.Client.Interfaces.Console; +using Robust.Shared.Console; using Robust.Shared.GameObjects.Systems; namespace Content.Client.Commands @@ -13,12 +13,13 @@ namespace Content.Client.Commands public string Description => "Toggles visibility of pathfinding debuggers."; public string Help => "pathfinder [hide/nodes/routes/graph/regioncache/regions]"; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { #if DEBUG if (args.Length < 1) { - return true; + shell.RemoteExecuteCommand(argStr); + return; } var anyAction = false; @@ -63,9 +64,10 @@ namespace Content.Client.Commands } } - return !anyAction; + if(!anyAction) + shell.RemoteExecuteCommand(argStr); #else - return true; + shell.RemoteExecuteCommand(argStr); #endif } } diff --git a/Content.Client/Commands/HideMechanismsCommand.cs b/Content.Client/Commands/HideMechanismsCommand.cs index 7570055038..9e7bde255c 100644 --- a/Content.Client/Commands/HideMechanismsCommand.cs +++ b/Content.Client/Commands/HideMechanismsCommand.cs @@ -1,7 +1,7 @@ using Content.Shared.GameObjects.Components.Body.Mechanism; using Robust.Client.Console; using Robust.Client.GameObjects; -using Robust.Client.Interfaces.Console; +using Robust.Shared.Console; using Robust.Shared.Containers; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -14,7 +14,7 @@ namespace Content.Client.Commands public string Description => $"Reverts the effects of {ShowMechanismsCommand.CommandName}"; public string Help => $"{Command}"; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var componentManager = IoCManager.Resolve(); var mechanisms = componentManager.EntityQuery(); @@ -41,9 +41,7 @@ namespace Content.Client.Commands } } - IoCManager.Resolve().ProcessCommand("hidecontainedcontext"); - - return false; + IoCManager.Resolve().ExecuteCommand("hidecontainedcontext"); } } } diff --git a/Content.Client/Commands/ShowMechanismsCommand.cs b/Content.Client/Commands/ShowMechanismsCommand.cs index 86e18c33a9..fe0381a3c6 100644 --- a/Content.Client/Commands/ShowMechanismsCommand.cs +++ b/Content.Client/Commands/ShowMechanismsCommand.cs @@ -1,7 +1,7 @@ using Content.Shared.GameObjects.Components.Body.Mechanism; using Robust.Client.Console; using Robust.Client.GameObjects; -using Robust.Client.Interfaces.Console; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -16,7 +16,7 @@ namespace Content.Client.Commands public string Description => "Makes mechanisms visible, even when they shouldn't be."; public string Help => $"{Command}"; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var componentManager = IoCManager.Resolve(); var mechanisms = componentManager.EntityQuery(); @@ -29,9 +29,7 @@ namespace Content.Client.Commands } } - IoCManager.Resolve().ProcessCommand("showcontainedcontext"); - - return false; + IoCManager.Resolve().ExecuteCommand("showcontainedcontext"); } } } diff --git a/Content.Client/Commands/ToggleOutlineCommand.cs b/Content.Client/Commands/ToggleOutlineCommand.cs index ca96762eac..af7aa9e80e 100644 --- a/Content.Client/Commands/ToggleOutlineCommand.cs +++ b/Content.Client/Commands/ToggleOutlineCommand.cs @@ -1,5 +1,5 @@ using Content.Shared; -using Robust.Client.Interfaces.Console; +using Robust.Shared.Console; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.IoC; @@ -13,16 +13,14 @@ namespace Content.Client.Commands public string Help => ""; - public bool Execute(IDebugConsole console, params string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var configurationManager = IoCManager.Resolve(); var cvar = CCVars.OutlineEnabled; var old = configurationManager.GetCVar(cvar); configurationManager.SetCVar(cvar, !old); - console.AddLine($"Draw outlines set to: {configurationManager.GetCVar(cvar)}"); - - return false; + shell.WriteLine($"Draw outlines set to: {configurationManager.GetCVar(cvar)}"); } } } diff --git a/Content.Client/EscapeMenuOwner.cs b/Content.Client/EscapeMenuOwner.cs index b57a67653d..1f8de2f5b6 100644 --- a/Content.Client/EscapeMenuOwner.cs +++ b/Content.Client/EscapeMenuOwner.cs @@ -1,4 +1,4 @@ -using Content.Client.State; +using Content.Client.State; using Content.Client.UserInterface; using Robust.Client.Console; using Robust.Client.Interfaces.Input; @@ -12,7 +12,7 @@ namespace Content.Client { internal sealed class EscapeMenuOwner : IEscapeMenuOwner { - [Dependency] private readonly IClientConsole _clientConsole = default!; + [Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IGameHud _gameHud = default!; @@ -31,7 +31,7 @@ namespace Content.Client if (obj.NewState is GameScreenBase) { // Switched TO GameScreen. - _escapeMenu = new EscapeMenu(_clientConsole); + _escapeMenu = new EscapeMenu(_consoleHost); _escapeMenu.OnClose += () => _gameHud.EscapeButtonDown = false; diff --git a/Content.Client/Sandbox/SandboxManager.cs b/Content.Client/Sandbox/SandboxManager.cs index a7a79fe884..aa91bb7264 100644 --- a/Content.Client/Sandbox/SandboxManager.cs +++ b/Content.Client/Sandbox/SandboxManager.cs @@ -102,7 +102,7 @@ namespace Content.Client.Sandbox internal class SandboxManager : SharedSandboxManager, ISandboxManager { - [Dependency] private readonly IClientConsole _console = default!; + [Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly IClientNetManager _netManager = default!; [Dependency] private readonly IPlacementManager _placementManager = default!; @@ -314,37 +314,37 @@ namespace Content.Client.Sandbox private void ToggleLight() { - _console.ProcessCommand("togglelight"); + _consoleHost.ExecuteCommand("togglelight"); } private void ToggleFov() { - _console.ProcessCommand("togglefov"); + _consoleHost.ExecuteCommand("togglefov"); } private void ToggleShadows() { - _console.ProcessCommand("toggleshadows"); + _consoleHost.ExecuteCommand("toggleshadows"); } private void ToggleSubFloor() { - _console.ProcessCommand("showsubfloor"); + _consoleHost.ExecuteCommand("showsubfloor"); } private void ShowMarkers() { - _console.ProcessCommand("showmarkers"); + _consoleHost.ExecuteCommand("showmarkers"); } private void ShowBb() { - _console.ProcessCommand("showbb"); + _consoleHost.ExecuteCommand("showbb"); } private void LinkMachines() { - _console.ProcessCommand("signallink"); + _consoleHost.ExecuteCommand("signallink"); } } } diff --git a/Content.Client/State/LobbyState.cs b/Content.Client/State/LobbyState.cs index 866c65c463..6b6ae5d6bf 100644 --- a/Content.Client/State/LobbyState.cs +++ b/Content.Client/State/LobbyState.cs @@ -26,7 +26,7 @@ namespace Content.Client.State public class LobbyState : Robust.Client.State.State { [Dependency] private readonly IBaseClient _baseClient = default!; - [Dependency] private readonly IClientConsole _console = default!; + [Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; @@ -87,7 +87,7 @@ namespace Content.Client.State _userInterfaceManager.StateRoot.AddChild(_characterSetup); }; - _lobby.ObserveButton.OnPressed += args => _console.ProcessCommand("observe"); + _lobby.ObserveButton.OnPressed += args => _consoleHost.ExecuteCommand("observe"); _lobby.ReadyButton.OnPressed += args => { if (!_clientGameTicker.IsGameStarted) @@ -104,7 +104,7 @@ namespace Content.Client.State SetReady(args.Pressed); }; - _lobby.LeaveButton.OnPressed += args => _console.ProcessCommand("disconnect"); + _lobby.LeaveButton.OnPressed += args => _consoleHost.ExecuteCommand("disconnect"); _lobby.OptionsButton.OnPressed += args => new OptionsMenu().Open(); UpdatePlayerList(); @@ -259,7 +259,7 @@ namespace Content.Client.State return; } - _console.ProcessCommand($"toggleready {newReady}"); + _consoleHost.ExecuteCommand($"toggleready {newReady}"); UpdatePlayerList(); } } diff --git a/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs b/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs index c166d2e0d0..56331156ef 100644 --- a/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs +++ b/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs @@ -440,7 +440,7 @@ namespace Content.Client.UserInterface.AdminMenu public override void ButtonPressed(ButtonEventArgs args) { - IoCManager.Resolve().ProcessCommand(RequiredCommand); + IoCManager.Resolve().ExecuteCommand(RequiredCommand); } } #endregion @@ -504,7 +504,7 @@ namespace Content.Client.UserInterface.AdminMenu Name = "Pause", Handler = () => { - IoCManager.Resolve().ProcessCommand("events pause"); + IoCManager.Resolve().ExecuteCommand("events pause"); }, }, new CommandUIButton @@ -512,14 +512,14 @@ namespace Content.Client.UserInterface.AdminMenu Name = "Resume", Handler = () => { - IoCManager.Resolve().ProcessCommand("events resume"); + IoCManager.Resolve().ExecuteCommand("events resume"); }, }, }; public override void Submit() { - IoCManager.Resolve().ProcessCommand($"events run \"{_eventsDropDown.GetValue()}\""); + IoCManager.Resolve().ExecuteCommand($"events run \"{_eventsDropDown.GetValue()}\""); } } @@ -548,7 +548,7 @@ namespace Content.Client.UserInterface.AdminMenu public override void Submit() { - IoCManager.Resolve().ProcessCommand($"kick \"{_playerDropDown.GetValue()}\" \"{CommandParsing.Escape(_reason.GetValue())}\""); + IoCManager.Resolve().ExecuteCommand($"kick \"{_playerDropDown.GetValue()}\" \"{CommandParsing.Escape(_reason.GetValue())}\""); } } @@ -572,7 +572,7 @@ namespace Content.Client.UserInterface.AdminMenu public override void Submit() { - IoCManager.Resolve().ProcessCommand($"tpto \"{_playerDropDown.GetValue()}\""); + IoCManager.Resolve().ExecuteCommand($"tpto \"{_playerDropDown.GetValue()}\""); } } @@ -596,7 +596,7 @@ namespace Content.Client.UserInterface.AdminMenu public override void Submit() { - IoCManager.Resolve().ProcessCommand($"addatmos {_grid.GetValue()}"); + IoCManager.Resolve().ExecuteCommand($"addatmos {_grid.GetValue()}"); } } @@ -639,7 +639,7 @@ namespace Content.Client.UserInterface.AdminMenu public override void Submit() { - IoCManager.Resolve().ProcessCommand($"fillgas {_grid.GetValue()} {_gas.GetValue()} {_amount.GetValue()}"); + IoCManager.Resolve().ExecuteCommand($"fillgas {_grid.GetValue()} {_gas.GetValue()} {_amount.GetValue()}"); } } #endregion diff --git a/Content.Client/UserInterface/AdminMenu/SetOutfit/SetOutfitMenu.xaml.cs b/Content.Client/UserInterface/AdminMenu/SetOutfit/SetOutfitMenu.xaml.cs index 0f1fba1fbf..9590db8768 100644 --- a/Content.Client/UserInterface/AdminMenu/SetOutfit/SetOutfitMenu.xaml.cs +++ b/Content.Client/UserInterface/AdminMenu/SetOutfit/SetOutfitMenu.xaml.cs @@ -22,7 +22,7 @@ namespace Content.Client.UserInterface.AdminMenu.SetOutfit public partial class SetOutfitMenu : SS14Window { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IClientConsole _console = default!; + [Dependency] private readonly IClientConsoleHost _consoleHost = default!; public EntityUid? TargetEntityId { get; set; } protected override Vector2? CustomSize => (250, 320); @@ -49,7 +49,7 @@ namespace Content.Client.UserInterface.AdminMenu.SetOutfit if (TargetEntityId == null || _selectedOutfit == null) return; var command = $"setoutfit {TargetEntityId} {_selectedOutfit.ID}"; - _console.ProcessCommand(command); + _consoleHost.ExecuteCommand(command); Close(); } diff --git a/Content.Client/UserInterface/EscapeMenu.cs b/Content.Client/UserInterface/EscapeMenu.cs index 25c8dc85c6..ec346d7522 100644 --- a/Content.Client/UserInterface/EscapeMenu.cs +++ b/Content.Client/UserInterface/EscapeMenu.cs @@ -8,16 +8,16 @@ namespace Content.Client.UserInterface { internal sealed class EscapeMenu : SS14Window { - private readonly IClientConsole _console; + private readonly IClientConsoleHost _consoleHost; private BaseButton DisconnectButton; private BaseButton QuitButton; private BaseButton OptionsButton; private OptionsMenu optionsMenu; - public EscapeMenu(IClientConsole console) + public EscapeMenu(IClientConsoleHost consoleHost) { - _console = console; + _consoleHost = consoleHost; IoCManager.InjectDependencies(this); @@ -50,13 +50,13 @@ namespace Content.Client.UserInterface private void OnQuitButtonClicked(BaseButton.ButtonEventArgs args) { - _console.ProcessCommand("quit"); + _consoleHost.ExecuteCommand("quit"); Dispose(); } private void OnDisconnectButtonClicked(BaseButton.ButtonEventArgs args) { - _console.ProcessCommand("disconnect"); + _consoleHost.ExecuteCommand("disconnect"); Dispose(); } diff --git a/Content.Client/UserInterface/LateJoinGui.cs b/Content.Client/UserInterface/LateJoinGui.cs index c1449ceb11..f4bbc7ecef 100644 --- a/Content.Client/UserInterface/LateJoinGui.cs +++ b/Content.Client/UserInterface/LateJoinGui.cs @@ -21,7 +21,7 @@ namespace Content.Client.UserInterface public sealed class LateJoinGui : SS14Window { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IClientConsole _console = default!; + [Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IClientGameTicker _gameTicker = default!; protected override Vector2? CustomSize => (360, 560); @@ -147,7 +147,7 @@ namespace Content.Client.UserInterface SelectedId += jobId => { Logger.InfoS("latejoin", $"Late joining as ID: {jobId}"); - _console.ProcessCommand($"joingame {CommandParsing.Escape(jobId)}"); + _consoleHost.ExecuteCommand($"joingame {CommandParsing.Escape(jobId)}"); Close(); }; diff --git a/Content.IntegrationTests/DummyGameTicker.cs b/Content.IntegrationTests/DummyGameTicker.cs index 9a98794513..8b8ea88887 100644 --- a/Content.IntegrationTests/DummyGameTicker.cs +++ b/Content.IntegrationTests/DummyGameTicker.cs @@ -6,7 +6,6 @@ using Content.Shared.Roles; using Content.Shared.Preferences; using Content.Server.Mobs; using Robust.Server.Interfaces.Player; -using Robust.Server.Interfaces.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Map; using Robust.Shared.Timing; diff --git a/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs b/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs index bd1484508e..e978f10629 100644 --- a/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs +++ b/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Content.Server.Commands.GameTicking; using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; @@ -40,7 +41,7 @@ namespace Content.IntegrationTests.Tests.Commands tickBeforeRestart = entityManager.CurrentTick; var command = new NewRoundCommand(); - command.Execute(null, null, new string[] { }); + command.Execute(null, string.Empty, Array.Empty()); if (lobbyEnabled) { diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs index 96105a3c24..2259df9c43 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Linq; using System.Threading.Tasks; using Content.Client.GameObjects.Components.Items; @@ -7,7 +7,7 @@ using Content.Server.GameObjects.Components.Body; using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Body; using NUnit.Framework; -using Robust.Server.Interfaces.Console; +using Robust.Server.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; @@ -96,8 +96,8 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking private void AddHand(IEntity to) { - var shell = IoCManager.Resolve(); - shell.ExecuteCommand($"addhand {to.Uid}"); + var host = IoCManager.Resolve(); + host.ExecuteCommand(null, $"addhand {to.Uid}"); } } } diff --git a/Content.IntegrationTests/Tests/Networking/ReconnectTest.cs b/Content.IntegrationTests/Tests/Networking/ReconnectTest.cs index aea01d41c5..780dd49f02 100644 --- a/Content.IntegrationTests/Tests/Networking/ReconnectTest.cs +++ b/Content.IntegrationTests/Tests/Networking/ReconnectTest.cs @@ -27,7 +27,7 @@ namespace Content.IntegrationTests.Tests.Networking await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()); - await client.WaitPost(() => IoCManager.Resolve().ProcessCommand("disconnect")); + await client.WaitPost(() => IoCManager.Resolve().ExecuteCommand("disconnect")); // Run some ticks for the disconnect to complete and such. await RunTicksSync(client, server, 5); diff --git a/Content.Server/Administration/AdminCommandAttribute.cs b/Content.Server/Administration/AdminCommandAttribute.cs index 79ae7fc769..0c9730df82 100644 --- a/Content.Server/Administration/AdminCommandAttribute.cs +++ b/Content.Server/Administration/AdminCommandAttribute.cs @@ -1,7 +1,7 @@ using System; using Content.Shared.Administration; using JetBrains.Annotations; -using Robust.Server.Interfaces.Console; +using Robust.Shared.Console; namespace Content.Server.Administration { @@ -13,7 +13,7 @@ namespace Content.Server.Administration /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - [BaseTypeRequired(typeof(IClientCommand))] + [BaseTypeRequired(typeof(IConsoleCommand))] [MeansImplicitUse] public sealed class AdminCommandAttribute : Attribute { diff --git a/Content.Server/Administration/AdminManager.cs b/Content.Server/Administration/AdminManager.cs index 4ca81c52fc..c66d4163d1 100644 --- a/Content.Server/Administration/AdminManager.cs +++ b/Content.Server/Administration/AdminManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -12,9 +12,9 @@ using Content.Shared; using Content.Shared.Administration; using Content.Shared.Network.NetMessages; using Robust.Server.Console; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Server.Player; +using Robust.Shared.Console; using Robust.Shared.Enums; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Network; @@ -37,7 +37,7 @@ namespace Content.Server.Administration [Dependency] private readonly IServerNetManager _netMgr = default!; [Dependency] private readonly IConGroupController _conGroup = default!; [Dependency] private readonly IResourceManager _res = default!; - [Dependency] private readonly IConsoleShell _consoleShell = default!; + [Dependency] private readonly IServerConsoleHost _consoleHost = default!; [Dependency] private readonly IChatManager _chat = default!; private readonly Dictionary _admins = new(); @@ -171,7 +171,7 @@ namespace Content.Server.Administration _netMgr.RegisterNetMessage(MsgUpdateAdminStatus.NAME); // Cache permissions for loaded console commands with the requisite attributes. - foreach (var (cmdName, cmd) in _consoleShell.AvailableCommands) + foreach (var (cmdName, cmd) in _consoleHost.RegisteredCommands) { var (isAvail, flagsReq) = GetRequiredFlag(cmd); @@ -420,7 +420,7 @@ namespace Content.Server.Administration return false; } - private static (bool isAvail, AdminFlags[] flagsReq) GetRequiredFlag(IClientCommand cmd) + private static (bool isAvail, AdminFlags[] flagsReq) GetRequiredFlag(IConsoleCommand cmd) { var type = cmd.GetType(); if (Attribute.IsDefined(type, typeof(AnyCommandAttribute))) diff --git a/Content.Server/Administration/AnyCommandAttribute.cs b/Content.Server/Administration/AnyCommandAttribute.cs index ab9895f728..0185cff723 100644 --- a/Content.Server/Administration/AnyCommandAttribute.cs +++ b/Content.Server/Administration/AnyCommandAttribute.cs @@ -1,6 +1,6 @@ using System; using JetBrains.Annotations; -using Robust.Server.Interfaces.Console; +using Robust.Shared.Console; namespace Content.Server.Administration { @@ -9,7 +9,7 @@ namespace Content.Server.Administration /// /// [AttributeUsage(AttributeTargets.Class)] - [BaseTypeRequired(typeof(IClientCommand))] + [BaseTypeRequired(typeof(IConsoleCommand))] [MeansImplicitUse] public sealed class AnyCommandAttribute : Attribute { diff --git a/Content.Server/Administration/Commands/AGhost.cs b/Content.Server/Administration/Commands/AGhost.cs index eddc879dde..7d7bd0720f 100644 --- a/Content.Server/Administration/Commands/AGhost.cs +++ b/Content.Server/Administration/Commands/AGhost.cs @@ -3,25 +3,26 @@ using Content.Server.GameObjects.Components.Observer; using Content.Server.Interfaces.GameTicking; using Content.Server.Players; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - public class AGhost : IClientCommand + public class AGhost : IConsoleCommand { public string Command => "aghost"; public string Description => "Makes you an admin ghost."; public string Help => "aghost"; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText((IPlayerSession) null, "Nah"); + shell.WriteLine("Nah"); return; } @@ -29,7 +30,7 @@ namespace Content.Server.Administration.Commands if (mind == null) { - shell.SendText(player, "You can't ghost here!"); + shell.WriteLine("You can't ghost here!"); return; } diff --git a/Content.Server/Administration/Commands/BanCommand.cs b/Content.Server/Administration/Commands/BanCommand.cs index f542d1c0c2..3d906be036 100644 --- a/Content.Server/Administration/Commands/BanCommand.cs +++ b/Content.Server/Administration/Commands/BanCommand.cs @@ -1,8 +1,8 @@ -using System; +using System; using Content.Server.Database; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; using Robust.Shared.Network; @@ -11,14 +11,15 @@ using Robust.Shared.Network; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Ban)] - public sealed class BanCommand : IClientCommand + public sealed class BanCommand : IConsoleCommand { public string Command => "ban"; public string Description => "Bans somebody"; public string Help => "Usage: "; - public async void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public async void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; var plyMgr = IoCManager.Resolve(); var dbMan = IoCManager.Resolve(); @@ -37,7 +38,7 @@ namespace Content.Server.Administration.Commands } else { - shell.SendText(player, "Unable to find user with that name."); + shell.WriteLine("Unable to find user with that name."); return; } diff --git a/Content.Server/Administration/Commands/ControlMob.cs b/Content.Server/Administration/Commands/ControlMob.cs index 1e979e633a..2e830269df 100644 --- a/Content.Server/Administration/Commands/ControlMob.cs +++ b/Content.Server/Administration/Commands/ControlMob.cs @@ -2,8 +2,8 @@ using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Observer; using Content.Server.Players; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -12,23 +12,24 @@ using Robust.Shared.Localization; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - class ControlMob : IClientCommand + class ControlMob : IConsoleCommand { public string Command => "controlmob"; public string Description => Loc.GetString("Transfers user mind to the specified entity."); public string Help => Loc.GetString("Usage: controlmob ."); - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText((IPlayerSession) null, "Server cannot do this."); + shell.WriteLine("Server cannot do this."); return; } if (args.Length != 1) { - shell.SendText(player, Loc.GetString("Wrong number of arguments.")); + shell.WriteLine(Loc.GetString("Wrong number of arguments.")); return; } @@ -38,7 +39,7 @@ namespace Content.Server.Administration.Commands if (!int.TryParse(args[0], out var targetId)) { - shell.SendText(player, Loc.GetString("Argument must be a number.")); + shell.WriteLine(Loc.GetString("Argument must be a number.")); return; } @@ -46,14 +47,14 @@ namespace Content.Server.Administration.Commands if (!eUid.IsValid() || !entityManager.EntityExists(eUid)) { - shell.SendText(player, Loc.GetString("Invalid entity ID.")); + shell.WriteLine(Loc.GetString("Invalid entity ID.")); return; } var target = entityManager.GetEntity(eUid); if (!target.TryGetComponent(out MindComponent mindComponent)) { - shell.SendText(player, Loc.GetString("Target entity is not a mob!")); + shell.WriteLine(Loc.GetString("Target entity is not a mob!")); return; } diff --git a/Content.Server/Administration/Commands/DSay.cs b/Content.Server/Administration/Commands/DSay.cs index 40bf93b5b5..b2a1d021af 100644 --- a/Content.Server/Administration/Commands/DSay.cs +++ b/Content.Server/Administration/Commands/DSay.cs @@ -1,14 +1,14 @@ using Content.Server.Interfaces.Chat; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; using Robust.Shared.Localization; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - class DSay : IClientCommand + class DSay : IConsoleCommand { public string Command => "dsay"; @@ -16,11 +16,12 @@ namespace Content.Server.Administration.Commands public string Help => Loc.GetString($"Usage: {Command} "); - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText((IPlayerSession) null, "Only players can use this command"); + shell.WriteLine("Only players can use this command"); return; } diff --git a/Content.Server/Administration/Commands/DeAdminCommand.cs b/Content.Server/Administration/Commands/DeAdminCommand.cs index 81a61194cf..057d942218 100644 --- a/Content.Server/Administration/Commands/DeAdminCommand.cs +++ b/Content.Server/Administration/Commands/DeAdminCommand.cs @@ -1,7 +1,7 @@ -using Content.Shared.Administration; +using Content.Shared.Administration; using JetBrains.Annotations; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; #nullable enable @@ -10,17 +10,18 @@ namespace Content.Server.Administration.Commands { [UsedImplicitly] [AdminCommand(AdminFlags.None)] - public class DeAdminCommand : IClientCommand + public class DeAdminCommand : IConsoleCommand { public string Command => "deadmin"; public string Description => "Temporarily de-admins you so you can experience the round as a normal player."; public string Help => "Usage: deadmin\nUse readmin to re-admin after using this."; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "You cannot use this command from the server console."); + shell.WriteLine("You cannot use this command from the server console."); return; } diff --git a/Content.Server/Administration/Commands/DeleteComponent.cs b/Content.Server/Administration/Commands/DeleteComponent.cs index 2fbeb0ce91..8a6777d7fc 100644 --- a/Content.Server/Administration/Commands/DeleteComponent.cs +++ b/Content.Server/Administration/Commands/DeleteComponent.cs @@ -1,25 +1,25 @@ #nullable enable using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - public class DeleteComponent : IClientCommand + public class DeleteComponent : IConsoleCommand { public string Command => "deletecomponent"; public string Description => "Deletes all instances of the specified component."; public string Help => $"Usage: {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { switch (args.Length) { case 0: - shell.SendText(player, $"Not enough arguments.\n{Help}"); + shell.WriteLine($"Not enough arguments.\n{Help}"); break; default: var name = string.Join(" ", args); @@ -28,7 +28,7 @@ namespace Content.Server.Administration.Commands if (!componentFactory.TryGetRegistration(name, out var registration)) { - shell.SendText(player, $"No component exists with name {name}."); + shell.WriteLine($"No component exists with name {name}."); break; } @@ -44,7 +44,7 @@ namespace Content.Server.Administration.Commands i++; } - shell.SendText(player, $"Removed {i} components with name {name}."); + shell.WriteLine($"Removed {i} components with name {name}."); break; } diff --git a/Content.Server/Administration/Commands/DeleteEntitiesWithComponent.cs b/Content.Server/Administration/Commands/DeleteEntitiesWithComponent.cs index f87bd7a99b..c8849b1e2b 100644 --- a/Content.Server/Administration/Commands/DeleteEntitiesWithComponent.cs +++ b/Content.Server/Administration/Commands/DeleteEntitiesWithComponent.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -11,7 +11,7 @@ using Robust.Shared.Localization; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - class DeleteEntitiesWithComponent : IClientCommand + class DeleteEntitiesWithComponent : IConsoleCommand { public string Command => "deleteewc"; public string Description @@ -29,11 +29,11 @@ namespace Content.Server.Administration.Commands } } - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length < 1) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } @@ -54,7 +54,7 @@ namespace Content.Server.Administration.Commands count += 1; } - shell.SendText(player, Loc.GetString("Deleted {0} entities", count)); + shell.WriteLine(Loc.GetString("Deleted {0} entities", count)); } } } diff --git a/Content.Server/Administration/Commands/DeleteEntitiesWithId.cs b/Content.Server/Administration/Commands/DeleteEntitiesWithId.cs index d7ff635c10..8ff64ce231 100644 --- a/Content.Server/Administration/Commands/DeleteEntitiesWithId.cs +++ b/Content.Server/Administration/Commands/DeleteEntitiesWithId.cs @@ -1,7 +1,7 @@ #nullable enable using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -9,17 +9,17 @@ using Robust.Shared.IoC; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - public class DeleteEntitiesWithId : IClientCommand + public class DeleteEntitiesWithId : IConsoleCommand { public string Command => "deleteewi"; public string Description => "Deletes entities with the specified prototype ID."; public string Help => $"Usage: {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 1) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } @@ -35,7 +35,7 @@ namespace Content.Server.Administration.Commands i++; } - shell.SendText(player, $"Deleted all entities with id {id}. Occurrences: {i}"); + shell.WriteLine($"Deleted all entities with id {id}. Occurrences: {i}"); } } } diff --git a/Content.Server/Administration/Commands/ExplosionCommand.cs b/Content.Server/Administration/Commands/ExplosionCommand.cs index 768f01b8d5..eef777b895 100644 --- a/Content.Server/Administration/Commands/ExplosionCommand.cs +++ b/Content.Server/Administration/Commands/ExplosionCommand.cs @@ -1,7 +1,7 @@ -using Content.Server.Explosions; +using Content.Server.Explosions; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Map; #nullable enable @@ -9,18 +9,19 @@ using Robust.Shared.Map; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Fun)] - public sealed class ExplosionCommand : IClientCommand + public sealed class ExplosionCommand : IConsoleCommand { public string Command => "explode"; public string Description => "Train go boom"; public string Help => "Usage: explode \n" + "The explosion happens on the same map as the user."; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player?.AttachedEntity == null) { - shell.SendText(player, "You must have an attached entity."); + shell.WriteLine("You must have an attached entity."); return; } diff --git a/Content.Server/Administration/Commands/OpenPermissionsCommand.cs b/Content.Server/Administration/Commands/OpenPermissionsCommand.cs index 093a10733a..eeb8551f0a 100644 --- a/Content.Server/Administration/Commands/OpenPermissionsCommand.cs +++ b/Content.Server/Administration/Commands/OpenPermissionsCommand.cs @@ -1,7 +1,7 @@ -using Content.Server.Eui; +using Content.Server.Eui; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; #nullable enable @@ -9,17 +9,18 @@ using Robust.Shared.IoC; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Permissions)] - public sealed class OpenPermissionsCommand : IClientCommand + public sealed class OpenPermissionsCommand : IConsoleCommand { public string Command => "permissions"; public string Description => "Opens the admin permissions panel."; public string Help => "Usage: permissions"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "This does not work from the server console."); + shell.WriteLine("This does not work from the server console."); return; } diff --git a/Content.Server/Administration/Commands/PromoteHostCommand.cs b/Content.Server/Administration/Commands/PromoteHostCommand.cs index 058557fa14..b81efefb04 100644 --- a/Content.Server/Administration/Commands/PromoteHostCommand.cs +++ b/Content.Server/Administration/Commands/PromoteHostCommand.cs @@ -1,30 +1,30 @@ #nullable enable using JetBrains.Annotations; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Administration.Commands { [UsedImplicitly] - public sealed class PromoteHostCommand : IClientCommand + public sealed class PromoteHostCommand : IConsoleCommand { public string Command => "promotehost"; public string Description => "Grants client temporary full host admin privileges. Use this to bootstrap admins."; public string Help => "Usage promotehost "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 1) { - shell.SendText(player, "Expected exactly one argument."); + shell.WriteLine("Expected exactly one argument."); return; } var plyMgr = IoCManager.Resolve(); if (!plyMgr.TryGetSessionByUsername(args[0], out var targetPlayer)) { - shell.SendText(player, "Unable to find a player by that name."); + shell.WriteLine("Unable to find a player by that name."); return; } diff --git a/Content.Server/Administration/Commands/ReAdminCommand.cs b/Content.Server/Administration/Commands/ReAdminCommand.cs index 8f78442689..100bdf92ef 100644 --- a/Content.Server/Administration/Commands/ReAdminCommand.cs +++ b/Content.Server/Administration/Commands/ReAdminCommand.cs @@ -1,5 +1,5 @@ -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; #nullable enable @@ -7,17 +7,18 @@ using Robust.Shared.IoC; namespace Content.Server.Administration.Commands { [AnyCommand] - public class ReAdminCommand : IClientCommand + public class ReAdminCommand : IConsoleCommand { public string Command => "readmin"; public string Description => "Re-admins you if you previously de-adminned."; public string Help => "Usage: readmin"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "You cannot use this command from the server console."); + shell.WriteLine("You cannot use this command from the server console."); return; } @@ -25,7 +26,7 @@ namespace Content.Server.Administration.Commands if (mgr.GetAdminData(player, includeDeAdmin: true) == null) { - shell.SendText(player, "You're not an admin."); + shell.WriteLine("You're not an admin."); return; } diff --git a/Content.Server/Administration/Commands/ReadyAll.cs b/Content.Server/Administration/Commands/ReadyAll.cs index ec703bb5b7..f37ef7c7dd 100644 --- a/Content.Server/Administration/Commands/ReadyAll.cs +++ b/Content.Server/Administration/Commands/ReadyAll.cs @@ -2,19 +2,19 @@ using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Server)] - public class ReadyAll : IClientCommand + public class ReadyAll : IConsoleCommand { public string Command => "readyall"; public string Description => "Readies up all players in the lobby."; public string Help => $"{Command} | ̣{Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var ready = true; @@ -29,7 +29,7 @@ namespace Content.Server.Administration.Commands if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby) { - shell.SendText(player, "This command can only be ran while in the lobby!"); + shell.WriteLine("This command can only be ran while in the lobby!"); return; } diff --git a/Content.Server/Administration/Commands/Rejuvenate.cs b/Content.Server/Administration/Commands/Rejuvenate.cs index 69c79a36b8..7134e5d590 100644 --- a/Content.Server/Administration/Commands/Rejuvenate.cs +++ b/Content.Server/Administration/Commands/Rejuvenate.cs @@ -1,7 +1,7 @@ -using Content.Server.GlobalVerbs; +using Content.Server.GlobalVerbs; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -10,7 +10,7 @@ using Robust.Shared.Localization; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - class Rejuvenate : IClientCommand + class Rejuvenate : IConsoleCommand { public string Command => "rejuvenate"; public string Description @@ -28,14 +28,15 @@ namespace Content.Server.Administration.Commands } } - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (args.Length < 1 && player != null) //Try to heal the users mob if applicable { - shell.SendText(player, Loc.GetString("Healing the user's mob since no arguments were provided.")); + shell.WriteLine(Loc.GetString("Healing the user's mob since no arguments were provided.")); if (player.AttachedEntity == null) { - shell.SendText(player, Loc.GetString("There's no entity attached to the user.")); + shell.WriteLine(Loc.GetString("There's no entity attached to the user.")); return; } RejuvenateVerb.PerformRejuvenate(player.AttachedEntity); @@ -46,7 +47,7 @@ namespace Content.Server.Administration.Commands { if(!EntityUid.TryParse(arg, out var uid) || !entityManager.TryGetEntity(uid, out var entity)) { - shell.SendText(player, Loc.GetString("Could not find entity {0}", arg)); + shell.WriteLine(Loc.GetString("Could not find entity {0}", arg)); continue; } RejuvenateVerb.PerformRejuvenate(entity); diff --git a/Content.Server/Administration/Commands/SetOutfitCommand.cs b/Content.Server/Administration/Commands/SetOutfitCommand.cs index f6e0b2b064..ea6b84bc8a 100644 --- a/Content.Server/Administration/Commands/SetOutfitCommand.cs +++ b/Content.Server/Administration/Commands/SetOutfitCommand.cs @@ -3,8 +3,8 @@ using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Shared.Administration; using Content.Shared.Roles; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -14,7 +14,7 @@ using Robust.Shared.Prototypes; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - class SetOutfitCommand : IClientCommand + class SetOutfitCommand : IConsoleCommand { public string Command => "setoutfit"; @@ -22,17 +22,17 @@ namespace Content.Server.Administration.Commands public string Help => Loc.GetString("Usage: {0} | {0} ", Command); - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length < 1) { - shell.SendText(player, Loc.GetString("Wrong number of arguments.")); + shell.WriteLine(Loc.GetString("Wrong number of arguments.")); return; } if (!int.TryParse(args[0], out var entityUid)) { - shell.SendText(player, Loc.GetString("EntityUid must be a number.")); + shell.WriteLine(Loc.GetString("EntityUid must be a number.")); return; } @@ -42,7 +42,7 @@ namespace Content.Server.Administration.Commands if (!eUid.IsValid() || !entityManager.EntityExists(eUid)) { - shell.SendText(player, Loc.GetString("Invalid entity ID.")); + shell.WriteLine(Loc.GetString("Invalid entity ID.")); return; } @@ -50,7 +50,7 @@ namespace Content.Server.Administration.Commands if (!target.TryGetComponent(out var inventoryComponent)) { - shell.SendText(player, Loc.GetString("Target entity does not have an inventory!")); + shell.WriteLine(Loc.GetString("Target entity does not have an inventory!")); return; } @@ -58,6 +58,7 @@ namespace Content.Server.Administration.Commands { var eui = IoCManager.Resolve(); var ui = new SetOutfitEui(target); + var player = shell.Player as IPlayerSession; eui.OpenEui(ui, player); return; } @@ -65,7 +66,7 @@ namespace Content.Server.Administration.Commands var prototypeManager = IoCManager.Resolve(); if (!prototypeManager.TryIndex(args[1], out var startingGear)) { - shell.SendText(player, Loc.GetString("Invalid outfit id")); + shell.WriteLine(Loc.GetString("Invalid outfit id")); return; } diff --git a/Content.Server/Administration/Commands/WarpCommand.cs b/Content.Server/Administration/Commands/WarpCommand.cs index 6e48b9637d..a8972487b7 100644 --- a/Content.Server/Administration/Commands/WarpCommand.cs +++ b/Content.Server/Administration/Commands/WarpCommand.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Markers; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Enums; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; @@ -14,7 +14,7 @@ using Robust.Shared.Map; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - public class WarpCommand : IClientCommand + public class WarpCommand : IConsoleCommand { public string Command => "warp"; public string Description => "Teleports you to predefined areas on the map."; @@ -23,17 +23,18 @@ namespace Content.Server.Administration.Commands "warp \nLocations you can teleport to are predefined by the map. " + "You can specify '?' as location to get a list of valid locations."; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText((IPlayerSession) null, "Only players can use this command"); + shell.WriteLine("Only players can use this command"); return; } if (args.Length != 1) { - shell.SendText(player, "Expected a single argument."); + shell.WriteLine("Expected a single argument."); return; } @@ -48,13 +49,13 @@ namespace Content.Server.Administration.Commands .OrderBy(p => p) .Distinct()); - shell.SendText(player, locations); + shell.WriteLine(locations); } else { if (player.Status != SessionStatus.InGame || player.AttachedEntity == null) { - shell.SendText(player, "You are not in-game!"); + shell.WriteLine("You are not in-game!"); return; } @@ -121,7 +122,7 @@ namespace Content.Server.Administration.Commands } else { - shell.SendText(player, "That location does not exist!"); + shell.WriteLine("That location does not exist!"); } } } diff --git a/Content.Server/Commands/AI/AddAiCommand.cs b/Content.Server/Commands/AI/AddAiCommand.cs index 395bae79bf..184341fc1f 100644 --- a/Content.Server/Commands/AI/AddAiCommand.cs +++ b/Content.Server/Commands/AI/AddAiCommand.cs @@ -4,8 +4,8 @@ using Content.Server.GameObjects.Components.Movement; using Content.Server.GameObjects.EntitySystems.AI; using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Movement; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; @@ -14,7 +14,7 @@ using Robust.Shared.IoC; namespace Content.Server.Commands.AI { [AdminCommand(AdminFlags.Fun)] - public class AddAiCommand : IClientCommand + public class AddAiCommand : IConsoleCommand { public string Command => "addai"; public string Description => "Add an ai component with a given processor to an entity."; @@ -22,11 +22,11 @@ namespace Content.Server.Commands.AI + "\n processorId: Class that inherits AiLogicProcessor and has an AiLogicProcessor attribute." + "\n entityID: Uid of entity to add the AiControllerComponent to. Open its VV menu to find this."; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if(args.Length != 2) { - shell.SendText(player, "Wrong number of args."); + shell.WriteLine("Wrong number of args."); return; } @@ -37,12 +37,12 @@ namespace Content.Server.Commands.AI if (!aiSystem.ProcessorTypeExists(processorId)) { - shell.SendText(player, "Invalid processor type. Processor must inherit AiLogicProcessor and have an AiLogicProcessor attribute."); + shell.WriteLine("Invalid processor type. Processor must inherit AiLogicProcessor and have an AiLogicProcessor attribute."); return; } if (ent.HasComponent()) { - shell.SendText(player, "Entity already has an AI component."); + shell.WriteLine("Entity already has an AI component."); return; } @@ -53,7 +53,7 @@ namespace Content.Server.Commands.AI var comp = ent.AddComponent(); comp.LogicName = processorId; - shell.SendText(player, "AI component added."); + shell.WriteLine("AI component added."); } } } diff --git a/Content.Server/Commands/AI/FactionCommand.cs b/Content.Server/Commands/AI/FactionCommand.cs index ae0107ff62..a040c082a3 100644 --- a/Content.Server/Commands/AI/FactionCommand.cs +++ b/Content.Server/Commands/AI/FactionCommand.cs @@ -4,22 +4,22 @@ using Content.Server.Administration; using Content.Server.GameObjects.Components.AI; using Content.Server.GameObjects.EntitySystems.AI; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Localization; namespace Content.Server.Commands.AI { [AdminCommand(AdminFlags.Fun)] - public sealed class FactionCommand : IClientCommand + public sealed class FactionCommand : IConsoleCommand { 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) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length == 0) { @@ -31,19 +31,19 @@ namespace Content.Server.Commands.AI result.Append(value + "\n"); } - shell.SendText(player, result.ToString()); + shell.WriteLine(result.ToString()); return; } if (args.Length < 2) { - shell.SendText(player, Loc.GetString("Need more args")); + shell.WriteLine(Loc.GetString("Need more args")); return; } if (!Enum.TryParse(args[0], true, out Faction faction)) { - shell.SendText(player, Loc.GetString("Invalid faction")); + shell.WriteLine(Loc.GetString("Invalid faction")); return; } @@ -54,40 +54,40 @@ namespace Content.Server.Commands.AI case "friendly": if (args.Length < 3) { - shell.SendText(player, Loc.GetString("Need to supply a target faction")); + shell.WriteLine(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")); + shell.WriteLine(Loc.GetString("Invalid target faction")); return; } EntitySystem.Get().MakeFriendly(faction, targetFaction); - shell.SendText(player, Loc.GetString("Command successful")); + shell.WriteLine(Loc.GetString("Command successful")); break; case "hostile": if (args.Length < 3) { - shell.SendText(player, Loc.GetString("Need to supply a target faction")); + shell.WriteLine(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")); + shell.WriteLine(Loc.GetString("Invalid target faction")); return; } EntitySystem.Get().MakeHostile(faction, targetFaction); - shell.SendText(player, Loc.GetString("Command successful")); + shell.WriteLine(Loc.GetString("Command successful")); break; case "list": - shell.SendText(player, EntitySystem.Get().GetHostileFactions(faction).ToString()); + shell.WriteLine(EntitySystem.Get().GetHostileFactions(faction).ToString()); break; default: - shell.SendText(player, Loc.GetString("Unknown faction arg")); + shell.WriteLine(Loc.GetString("Unknown faction arg")); break; } diff --git a/Content.Server/Commands/Actions/CooldownAction.cs b/Content.Server/Commands/Actions/CooldownAction.cs index 15c9b9a903..7c8367af09 100644 --- a/Content.Server/Commands/Actions/CooldownAction.cs +++ b/Content.Server/Commands/Actions/CooldownAction.cs @@ -1,25 +1,26 @@ -#nullable enable +#nullable enable using System; using Content.Server.Administration; using Content.Server.GameObjects.Components.Mobs; using Content.Shared.Actions; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; namespace Content.Server.Commands.Actions { [AdminCommand(AdminFlags.Debug)] - public sealed class CooldownAction : IClientCommand + public sealed class CooldownAction : IConsoleCommand { public string Command => "coolaction"; public string Description => "Sets a cooldown on an action for a player, defaulting to current player"; public string Help => "coolaction "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) return; var attachedEntity = player.AttachedEntity; if (args.Length > 2) @@ -31,29 +32,29 @@ namespace Content.Server.Commands.Actions if (attachedEntity == null) return; if (!attachedEntity.TryGetComponent(out ServerActionsComponent? actionsComponent)) { - shell.SendText(player, "user has no actions component"); + shell.WriteLine("user has no actions component"); return; } var actionTypeRaw = args[0]; if (!Enum.TryParse(actionTypeRaw, out var actionType)) { - shell.SendText(player, "unrecognized ActionType enum value, please" + - " ensure you used correct casing: " + actionTypeRaw); + shell.WriteLine("unrecognized ActionType enum value, please" + + " ensure you used correct casing: " + actionTypeRaw); return; } var actionMgr = IoCManager.Resolve(); if (!actionMgr.TryGet(actionType, out var action)) { - shell.SendText(player, "unrecognized actionType " + actionType); + shell.WriteLine("unrecognized actionType " + actionType); return; } var cooldownStart = IoCManager.Resolve().CurTime; if (!uint.TryParse(args[1], out var seconds)) { - shell.SendText(player, "cannot parse seconds: " + args[1]); + shell.WriteLine("cannot parse seconds: " + args[1]); return; } diff --git a/Content.Server/Commands/Actions/GrantAction.cs b/Content.Server/Commands/Actions/GrantAction.cs index fd2cd3d6b8..a6bb3f410b 100644 --- a/Content.Server/Commands/Actions/GrantAction.cs +++ b/Content.Server/Commands/Actions/GrantAction.cs @@ -1,23 +1,24 @@ -#nullable enable +#nullable enable using System; using Content.Server.Administration; using Content.Server.GameObjects.Components.Mobs; using Content.Shared.Actions; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Actions { [AdminCommand(AdminFlags.Debug)] - public sealed class GrantAction : IClientCommand + public sealed class GrantAction : IConsoleCommand { public string Command => "grantaction"; public string Description => "Grants an action to a player, defaulting to current player"; public string Help => "grantaction "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) return; var attachedEntity = player.AttachedEntity; if (args.Length > 1) @@ -29,21 +30,21 @@ namespace Content.Server.Commands.Actions if (attachedEntity == null) return; if (!attachedEntity.TryGetComponent(out ServerActionsComponent? actionsComponent)) { - shell.SendText(player, "user has no actions component"); + shell.WriteLine("user has no actions component"); return; } var actionTypeRaw = args[0]; if (!Enum.TryParse(actionTypeRaw, out var actionType)) { - shell.SendText(player, "unrecognized ActionType enum value, please" + - " ensure you used correct casing: " + actionTypeRaw); + shell.WriteLine("unrecognized ActionType enum value, please" + + " ensure you used correct casing: " + actionTypeRaw); return; } var actionMgr = IoCManager.Resolve(); if (!actionMgr.TryGet(actionType, out var action)) { - shell.SendText(player, "unrecognized actionType " + actionType); + shell.WriteLine("unrecognized actionType " + actionType); return; } actionsComponent.Grant(action.ActionType); diff --git a/Content.Server/Commands/Actions/RevokeAction.cs b/Content.Server/Commands/Actions/RevokeAction.cs index be7ca6082d..78a0d0cae3 100644 --- a/Content.Server/Commands/Actions/RevokeAction.cs +++ b/Content.Server/Commands/Actions/RevokeAction.cs @@ -1,24 +1,25 @@ -#nullable enable +#nullable enable using System; using Content.Server.Administration; using Content.Server.GameObjects.Components.Mobs; using Content.Shared.Actions; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Actions { [AdminCommand(AdminFlags.Debug)] - public sealed class RevokeAction : IClientCommand + public sealed class RevokeAction : IConsoleCommand { public string Command => "revokeaction"; public string Description => "Revokes an action from a player, defaulting to current player"; public string Help => "revokeaction "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) return; var attachedEntity = player.AttachedEntity; if (args.Length > 1) @@ -29,21 +30,21 @@ namespace Content.Server.Commands.Actions if (attachedEntity == null) return; if (!attachedEntity.TryGetComponent(out ServerActionsComponent? actionsComponent)) { - shell.SendText(player, "user has no actions component"); + shell.WriteLine("user has no actions component"); return; } var actionTypeRaw = args[0]; if (!Enum.TryParse(actionTypeRaw, out var actionType)) { - shell.SendText(player, "unrecognized ActionType enum value, please" + - " ensure you used correct casing: " + actionTypeRaw); + shell.WriteLine("unrecognized ActionType enum value, please" + + " ensure you used correct casing: " + actionTypeRaw); return; } var actionMgr = IoCManager.Resolve(); if (!actionMgr.TryGet(actionType, out var action)) { - shell.SendText(player, "unrecognized actionType " + actionType); + shell.WriteLine("unrecognized actionType " + actionType); return; } diff --git a/Content.Server/Commands/Alerts/ClearAlert.cs b/Content.Server/Commands/Alerts/ClearAlert.cs index c8fabad81c..761f08b7c6 100644 --- a/Content.Server/Commands/Alerts/ClearAlert.cs +++ b/Content.Server/Commands/Alerts/ClearAlert.cs @@ -1,27 +1,28 @@ -#nullable enable +#nullable enable using System; using Content.Server.Administration; using Content.Server.GameObjects.Components.Mobs; using Content.Shared.Administration; using Content.Shared.Alert; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Alerts { [AdminCommand(AdminFlags.Debug)] - public sealed class ClearAlert : IClientCommand + public sealed class ClearAlert : IConsoleCommand { public string Command => "clearalert"; public string Description => "Clears an alert for a player, defaulting to current player"; public string Help => "clearalert "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player?.AttachedEntity == null) { - shell.SendText(player, "You don't have an entity."); + shell.WriteLine("You don't have an entity."); return; } @@ -35,7 +36,7 @@ namespace Content.Server.Commands.Alerts if (!attachedEntity.TryGetComponent(out ServerAlertsComponent? alertsComponent)) { - shell.SendText(player, "user has no alerts component"); + shell.WriteLine("user has no alerts component"); return; } @@ -43,7 +44,7 @@ namespace Content.Server.Commands.Alerts var alertMgr = IoCManager.Resolve(); if (!alertMgr.TryGet(Enum.Parse(alertType), out var alert)) { - shell.SendText(player, "unrecognized alertType " + alertType); + shell.WriteLine("unrecognized alertType " + alertType); return; } diff --git a/Content.Server/Commands/Alerts/ShowAlert.cs b/Content.Server/Commands/Alerts/ShowAlert.cs index 20a08cc809..2381c14fa3 100644 --- a/Content.Server/Commands/Alerts/ShowAlert.cs +++ b/Content.Server/Commands/Alerts/ShowAlert.cs @@ -1,27 +1,28 @@ -#nullable enable +#nullable enable using System; using Content.Server.Administration; using Content.Server.GameObjects.Components.Mobs; using Content.Shared.Administration; using Content.Shared.Alert; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Alerts { [AdminCommand(AdminFlags.Debug)] - public sealed class ShowAlert : IClientCommand + public sealed class ShowAlert : IConsoleCommand { public string Command => "showalert"; public string Description => "Shows an alert for a player, defaulting to current player"; public string Help => "showalert "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "You cannot run this command from the server."); + shell.WriteLine("You cannot run this command from the server."); return; } @@ -29,7 +30,7 @@ namespace Content.Server.Commands.Alerts if (attachedEntity == null) { - shell.SendText(player, "You don't have an entity."); + shell.WriteLine("You don't have an entity."); return; } @@ -41,7 +42,7 @@ namespace Content.Server.Commands.Alerts if (!attachedEntity.TryGetComponent(out ServerAlertsComponent? alertsComponent)) { - shell.SendText(player, "user has no alerts component"); + shell.WriteLine("user has no alerts component"); return; } @@ -50,12 +51,12 @@ namespace Content.Server.Commands.Alerts var alertMgr = IoCManager.Resolve(); if (!alertMgr.TryGet(Enum.Parse(alertType), out var alert)) { - shell.SendText(player, "unrecognized alertType " + alertType); + shell.WriteLine("unrecognized alertType " + alertType); return; } if (!short.TryParse(severity, out var sevint)) { - shell.SendText(player, "invalid severity " + sevint); + shell.WriteLine("invalid severity " + sevint); return; } alertsComponent.ShowAlert(alert.AlertType, sevint == -1 ? (short?) null : sevint); diff --git a/Content.Server/Commands/Atmos/AddAtmosCommand.cs b/Content.Server/Commands/Atmos/AddAtmosCommand.cs index 48298d0641..e2e1c2322c 100644 --- a/Content.Server/Commands/Atmos/AddAtmosCommand.cs +++ b/Content.Server/Commands/Atmos/AddAtmosCommand.cs @@ -3,8 +3,8 @@ using Content.Server.Administration; using Content.Server.Atmos; using Content.Server.GameObjects.Components.Atmos; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; @@ -13,23 +13,23 @@ using Robust.Shared.Map; namespace Content.Server.Commands.Atmos { [AdminCommand(AdminFlags.Debug)] - public class AddAtmosCommand : IClientCommand + public class AddAtmosCommand : IConsoleCommand { public string Command => "addatmos"; public string Description => "Adds atmos support to a grid."; public string Help => $"{Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length < 1) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } if (!int.TryParse(args[0], out var id)) { - shell.SendText(player, $"{args[0]} is not a valid integer."); + shell.WriteLine($"{args[0]} is not a valid integer."); return; } @@ -39,7 +39,7 @@ namespace Content.Server.Commands.Atmos if (!gridId.IsValid() || !mapMan.TryGetGrid(gridId, out var gridComp)) { - shell.SendText(player, $"{gridId} is not a valid grid id."); + shell.WriteLine($"{gridId} is not a valid grid id."); return; } @@ -47,19 +47,19 @@ namespace Content.Server.Commands.Atmos if (!entMan.TryGetEntity(gridComp.GridEntityId, out var grid)) { - shell.SendText(player, "Failed to get grid entity."); + shell.WriteLine("Failed to get grid entity."); return; } if (grid.HasComponent()) { - shell.SendText(player, "Grid already has an atmosphere."); + shell.WriteLine("Grid already has an atmosphere."); return; } grid.AddComponent(); - shell.SendText(player, $"Added atmosphere to grid {id}."); + shell.WriteLine($"Added atmosphere to grid {id}."); } } } diff --git a/Content.Server/Commands/Atmos/AddGasCommand.cs b/Content.Server/Commands/Atmos/AddGasCommand.cs index 73c3ab28a1..a73d005137 100644 --- a/Content.Server/Commands/Atmos/AddGasCommand.cs +++ b/Content.Server/Commands/Atmos/AddGasCommand.cs @@ -3,8 +3,8 @@ using Content.Server.Administration; using Content.Server.GameObjects.Components.Atmos; using Content.Shared.Administration; using Content.Shared.Atmos; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; @@ -14,13 +14,13 @@ using Robust.Shared.Maths; namespace Content.Server.Commands.Atmos { [AdminCommand(AdminFlags.Debug)] - public class AddGasCommand : IClientCommand + public class AddGasCommand : IConsoleCommand { public string Command => "addgas"; public string Description => "Adds gas at a certain position."; public string Help => "addgas "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length < 5) return; if(!int.TryParse(args[0], out var x) @@ -35,7 +35,7 @@ namespace Content.Server.Commands.Atmos if (!gridId.IsValid() || !mapMan.TryGetGrid(gridId, out var gridComp)) { - shell.SendText(player, "Invalid grid ID."); + shell.WriteLine("Invalid grid ID."); return; } @@ -43,13 +43,13 @@ namespace Content.Server.Commands.Atmos if (!entMan.TryGetEntity(gridComp.GridEntityId, out var grid)) { - shell.SendText(player, "Failed to get grid entity."); + shell.WriteLine("Failed to get grid entity."); return; } if (!grid.HasComponent()) { - shell.SendText(player, "Grid doesn't have an atmosphere."); + shell.WriteLine("Grid doesn't have an atmosphere."); return; } @@ -59,13 +59,13 @@ namespace Content.Server.Commands.Atmos if (tile == null) { - shell.SendText(player, "Invalid coordinates."); + shell.WriteLine("Invalid coordinates."); return; } if (tile.Air == null) { - shell.SendText(player, "Can't add gas to that tile."); + shell.WriteLine("Can't add gas to that tile."); return; } diff --git a/Content.Server/Commands/Atmos/AddUnsimulatedAtmosCommand.cs b/Content.Server/Commands/Atmos/AddUnsimulatedAtmosCommand.cs index 9b39a92653..e6ec43ec27 100644 --- a/Content.Server/Commands/Atmos/AddUnsimulatedAtmosCommand.cs +++ b/Content.Server/Commands/Atmos/AddUnsimulatedAtmosCommand.cs @@ -3,8 +3,8 @@ using Content.Server.Administration; using Content.Server.Atmos; using Content.Server.GameObjects.Components.Atmos; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; @@ -13,23 +13,23 @@ using Robust.Shared.Map; namespace Content.Server.Commands.Atmos { [AdminCommand(AdminFlags.Debug)] - public class AddUnsimulatedAtmosCommand : IClientCommand + public class AddUnsimulatedAtmosCommand : IConsoleCommand { public string Command => "addunsimulatedatmos"; public string Description => "Adds unimulated atmos support to a grid."; public string Help => $"{Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length < 1) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } if (!int.TryParse(args[0], out var id)) { - shell.SendText(player, $"{args[0]} is not a valid integer."); + shell.WriteLine($"{args[0]} is not a valid integer."); return; } @@ -39,7 +39,7 @@ namespace Content.Server.Commands.Atmos if (!gridId.IsValid() || !mapMan.TryGetGrid(gridId, out var gridComp)) { - shell.SendText(player, $"{gridId} is not a valid grid id."); + shell.WriteLine($"{gridId} is not a valid grid id."); return; } @@ -47,19 +47,19 @@ namespace Content.Server.Commands.Atmos if (!entMan.TryGetEntity(gridComp.GridEntityId, out var grid)) { - shell.SendText(player, "Failed to get grid entity."); + shell.WriteLine("Failed to get grid entity."); return; } if (grid.HasComponent()) { - shell.SendText(player, "Grid already has an atmosphere."); + shell.WriteLine("Grid already has an atmosphere."); return; } grid.AddComponent(); - shell.SendText(player, $"Added unsimulated atmosphere to grid {id}."); + shell.WriteLine($"Added unsimulated atmosphere to grid {id}."); } } diff --git a/Content.Server/Commands/Atmos/DeleteGasCommand.cs b/Content.Server/Commands/Atmos/DeleteGasCommand.cs index dd92aeb957..ac11c85360 100644 --- a/Content.Server/Commands/Atmos/DeleteGasCommand.cs +++ b/Content.Server/Commands/Atmos/DeleteGasCommand.cs @@ -1,11 +1,11 @@ -#nullable enable +#nullable enable using System; using Content.Server.Administration; using Content.Server.GameObjects.Components.Atmos; using Content.Shared.Administration; using Content.Shared.Atmos; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; @@ -14,14 +14,15 @@ using Robust.Shared.Map; namespace Content.Server.Commands.Atmos { [AdminCommand(AdminFlags.Debug)] - public class DeleteGasCommand : IClientCommand + public class DeleteGasCommand : IConsoleCommand { public string Command => "deletegas"; public string Description => "Removes all gases from a grid, or just of one type if specified."; public string Help => $"Usage: {Command} / {Command} / {Command} / {Command}"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; GridId gridId; Gas? gas = null; @@ -30,13 +31,13 @@ namespace Content.Server.Commands.Atmos case 0: if (player == null) { - shell.SendText(player, "A grid must be specified when the command isn't used by a player."); + shell.WriteLine("A grid must be specified when the command isn't used by a player."); return; } if (player.AttachedEntity == null) { - shell.SendText(player, "You have no entity to get a grid from."); + shell.WriteLine("You have no entity to get a grid from."); return; } @@ -44,7 +45,7 @@ namespace Content.Server.Commands.Atmos if (gridId == GridId.Invalid) { - shell.SendText(player, "You aren't on a grid to delete gas from."); + shell.WriteLine("You aren't on a grid to delete gas from."); return; } @@ -56,13 +57,13 @@ namespace Content.Server.Commands.Atmos // Argument is a gas if (player == null) { - shell.SendText(player, "A grid id must be specified if not using this command as a player."); + shell.WriteLine("A grid id must be specified if not using this command as a player."); return; } if (player.AttachedEntity == null) { - shell.SendText(player, "You have no entity from which to get a grid id."); + shell.WriteLine("You have no entity from which to get a grid id."); return; } @@ -70,13 +71,13 @@ namespace Content.Server.Commands.Atmos if (gridId == GridId.Invalid) { - shell.SendText(player, "You aren't on a grid to delete gas from."); + shell.WriteLine("You aren't on a grid to delete gas from."); return; } if (!Enum.TryParse(args[0], true, out var parsedGas)) { - shell.SendText(player, $"{args[0]} is not a valid gas name."); + shell.WriteLine($"{args[0]} is not a valid gas name."); return; } @@ -89,7 +90,7 @@ namespace Content.Server.Commands.Atmos if (gridId == GridId.Invalid) { - shell.SendText(player, $"{gridId} is not a valid grid id."); + shell.WriteLine($"{gridId} is not a valid grid id."); return; } @@ -99,7 +100,7 @@ namespace Content.Server.Commands.Atmos { if (!int.TryParse(args[0], out var first)) { - shell.SendText(player, $"{args[0]} is not a valid integer for a grid id."); + shell.WriteLine($"{args[0]} is not a valid integer for a grid id."); return; } @@ -107,13 +108,13 @@ namespace Content.Server.Commands.Atmos if (gridId == GridId.Invalid) { - shell.SendText(player, $"{gridId} is not a valid grid id."); + shell.WriteLine($"{gridId} is not a valid grid id."); return; } if (!Enum.TryParse(args[1], true, out var parsedGas)) { - shell.SendText(player, $"{args[1]} is not a valid gas."); + shell.WriteLine($"{args[1]} is not a valid gas."); return; } @@ -122,7 +123,7 @@ namespace Content.Server.Commands.Atmos break; } default: - shell.SendText(player, Help); + shell.WriteLine(Help); return; } @@ -130,7 +131,7 @@ namespace Content.Server.Commands.Atmos if (!mapManager.TryGetGrid(gridId, out var grid)) { - shell.SendText(player, $"No grid exists with id {gridId}"); + shell.WriteLine($"No grid exists with id {gridId}"); return; } @@ -138,13 +139,13 @@ namespace Content.Server.Commands.Atmos if (!entityManager.TryGetEntity(grid.GridEntityId, out var gridEntity)) { - shell.SendText(player, $"Grid {gridId} has no entity."); + shell.WriteLine($"Grid {gridId} has no entity."); return; } if (!gridEntity.TryGetComponent(out GridAtmosphereComponent? atmosphere)) { - shell.SendText(player, $"Grid {gridId} has no {nameof(GridAtmosphereComponent)}"); + shell.WriteLine($"Grid {gridId} has no {nameof(GridAtmosphereComponent)}"); return; } @@ -182,11 +183,11 @@ namespace Content.Server.Commands.Atmos if (gas == null) { - shell.SendText(player, $"Removed {moles} moles from {tiles} tiles."); + shell.WriteLine($"Removed {moles} moles from {tiles} tiles."); return; } - shell.SendText(player, $"Removed {moles} moles of gas {gas} from {tiles} tiles."); + shell.WriteLine($"Removed {moles} moles of gas {gas} from {tiles} tiles."); } } diff --git a/Content.Server/Commands/Atmos/FillGasCommand.cs b/Content.Server/Commands/Atmos/FillGasCommand.cs index 63efd89b63..1bac6ace5d 100644 --- a/Content.Server/Commands/Atmos/FillGasCommand.cs +++ b/Content.Server/Commands/Atmos/FillGasCommand.cs @@ -3,8 +3,8 @@ using Content.Server.Administration; using Content.Server.GameObjects.Components.Atmos; using Content.Shared.Administration; using Content.Shared.Atmos; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; @@ -13,13 +13,13 @@ using Robust.Shared.Map; namespace Content.Server.Commands.Atmos { [AdminCommand(AdminFlags.Debug)] - public class FillGas : IClientCommand + public class FillGas : IConsoleCommand { public string Command => "fillgas"; public string Description => "Adds gas to all tiles in a grid."; public string Help => "fillgas "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length < 3) return; if(!int.TryParse(args[0], out var id) @@ -32,7 +32,7 @@ namespace Content.Server.Commands.Atmos if (!gridId.IsValid() || !mapMan.TryGetGrid(gridId, out var gridComp)) { - shell.SendText(player, "Invalid grid ID."); + shell.WriteLine("Invalid grid ID."); return; } @@ -40,13 +40,13 @@ namespace Content.Server.Commands.Atmos if (!entMan.TryGetEntity(gridComp.GridEntityId, out var grid)) { - shell.SendText(player, "Failed to get grid entity."); + shell.WriteLine("Failed to get grid entity."); return; } if (!grid.HasComponent()) { - shell.SendText(player, "Grid doesn't have an atmosphere."); + shell.WriteLine("Grid doesn't have an atmosphere."); return; } diff --git a/Content.Server/Commands/Atmos/ListGasesCommand.cs b/Content.Server/Commands/Atmos/ListGasesCommand.cs index 57c56c5420..129719e866 100644 --- a/Content.Server/Commands/Atmos/ListGasesCommand.cs +++ b/Content.Server/Commands/Atmos/ListGasesCommand.cs @@ -2,26 +2,26 @@ using Content.Server.Administration; using Content.Server.GameObjects.EntitySystems; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects.Systems; namespace Content.Server.Commands.Atmos { [AdminCommand(AdminFlags.Debug)] - public class ListGasesCommand : IClientCommand + public class ListGasesCommand : IConsoleCommand { public string Command => "listgases"; public string Description => "Prints a list of gases and their indices."; public string Help => "listgases"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var atmosSystem = EntitySystem.Get(); foreach (var gasPrototype in atmosSystem.Gases) { - shell.SendText(player, $"{gasPrototype.Name} ID: {gasPrototype.ID}"); + shell.WriteLine($"{gasPrototype.Name} ID: {gasPrototype.ID}"); } } } diff --git a/Content.Server/Commands/Atmos/RemoveGasCommand.cs b/Content.Server/Commands/Atmos/RemoveGasCommand.cs index 7bf83f33dd..84f0cadcee 100644 --- a/Content.Server/Commands/Atmos/RemoveGasCommand.cs +++ b/Content.Server/Commands/Atmos/RemoveGasCommand.cs @@ -2,8 +2,8 @@ using Content.Server.Administration; using Content.Server.GameObjects.Components.Atmos; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; @@ -13,13 +13,13 @@ using Robust.Shared.Maths; namespace Content.Server.Commands.Atmos { [AdminCommand(AdminFlags.Debug)] - public class RemoveGasCommand : IClientCommand + public class RemoveGasCommand : IConsoleCommand { public string Command => "removegas"; public string Description => "Removes an amount of gases."; public string Help => "removegas \nIf is true, amount will be treated as the ratio of gas to be removed."; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length < 5) return; if(!int.TryParse(args[0], out var x) @@ -34,7 +34,7 @@ namespace Content.Server.Commands.Atmos if (!gridId.IsValid() || !mapMan.TryGetGrid(gridId, out var gridComp)) { - shell.SendText(player, "Invalid grid ID."); + shell.WriteLine("Invalid grid ID."); return; } @@ -42,13 +42,13 @@ namespace Content.Server.Commands.Atmos if (!entMan.TryGetEntity(gridComp.GridEntityId, out var grid)) { - shell.SendText(player, "Failed to get grid entity."); + shell.WriteLine("Failed to get grid entity."); return; } if (!grid.HasComponent()) { - shell.SendText(player, "Grid doesn't have an atmosphere."); + shell.WriteLine("Grid doesn't have an atmosphere."); return; } @@ -58,13 +58,13 @@ namespace Content.Server.Commands.Atmos if (tile == null) { - shell.SendText(player, "Invalid coordinates."); + shell.WriteLine("Invalid coordinates."); return; } if (tile.Air == null) { - shell.SendText(player, "Can't remove gas from that tile."); + shell.WriteLine("Can't remove gas from that tile."); return; } diff --git a/Content.Server/Commands/Atmos/SetAtmosTemperatureCommand.cs b/Content.Server/Commands/Atmos/SetAtmosTemperatureCommand.cs index 6d935e004a..28b2452397 100644 --- a/Content.Server/Commands/Atmos/SetAtmosTemperatureCommand.cs +++ b/Content.Server/Commands/Atmos/SetAtmosTemperatureCommand.cs @@ -3,8 +3,8 @@ using Content.Server.Administration; using Content.Server.GameObjects.Components.Atmos; using Content.Shared.Administration; using Content.Shared.Atmos; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; @@ -13,13 +13,13 @@ using Robust.Shared.Map; namespace Content.Server.Commands.Atmos { [AdminCommand(AdminFlags.Debug)] - public class SetAtmosTemperatureCommand : IClientCommand + public class SetAtmosTemperatureCommand : IConsoleCommand { public string Command => "setatmostemp"; public string Description => "Sets a grid's temperature (in kelvin)."; public string Help => "Usage: setatmostemp "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length < 2) return; if(!int.TryParse(args[0], out var id) @@ -31,13 +31,13 @@ namespace Content.Server.Commands.Atmos if (temperature < Atmospherics.TCMB) { - shell.SendText(player, "Invalid temperature."); + shell.WriteLine("Invalid temperature."); return; } if (!gridId.IsValid() || !mapMan.TryGetGrid(gridId, out var gridComp)) { - shell.SendText(player, "Invalid grid ID."); + shell.WriteLine("Invalid grid ID."); return; } @@ -45,13 +45,13 @@ namespace Content.Server.Commands.Atmos if (!entMan.TryGetEntity(gridComp.GridEntityId, out var grid)) { - shell.SendText(player, "Failed to get grid entity."); + shell.WriteLine("Failed to get grid entity."); return; } if (!grid.HasComponent()) { - shell.SendText(player, "Grid doesn't have an atmosphere."); + shell.WriteLine("Grid doesn't have an atmosphere."); return; } @@ -69,7 +69,7 @@ namespace Content.Server.Commands.Atmos tile.Invalidate(); } - shell.SendText(player, $"Changed the temperature of {tiles} tiles."); + shell.WriteLine($"Changed the temperature of {tiles} tiles."); } } } diff --git a/Content.Server/Commands/Atmos/SetTemperatureCommand.cs b/Content.Server/Commands/Atmos/SetTemperatureCommand.cs index c761c9cac4..33be481d12 100644 --- a/Content.Server/Commands/Atmos/SetTemperatureCommand.cs +++ b/Content.Server/Commands/Atmos/SetTemperatureCommand.cs @@ -3,8 +3,8 @@ using Content.Server.Administration; using Content.Server.GameObjects.Components.Atmos; using Content.Shared.Administration; using Content.Shared.Atmos; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; @@ -14,13 +14,13 @@ using Robust.Shared.Maths; namespace Content.Server.Commands.Atmos { [AdminCommand(AdminFlags.Debug)] - public class SetTemperatureCommand : IClientCommand + public class SetTemperatureCommand : IConsoleCommand { public string Command => "settemp"; public string Description => "Sets a tile's temperature (in kelvin)."; public string Help => "Usage: settemp "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length < 4) return; if(!int.TryParse(args[0], out var x) @@ -34,13 +34,13 @@ namespace Content.Server.Commands.Atmos if (temperature < Atmospherics.TCMB) { - shell.SendText(player, "Invalid temperature."); + shell.WriteLine("Invalid temperature."); return; } if (!gridId.IsValid() || !mapMan.TryGetGrid(gridId, out var gridComp)) { - shell.SendText(player, "Invalid grid ID."); + shell.WriteLine("Invalid grid ID."); return; } @@ -48,13 +48,13 @@ namespace Content.Server.Commands.Atmos if (!entMan.TryGetEntity(gridComp.GridEntityId, out var grid)) { - shell.SendText(player, "Failed to get grid entity."); + shell.WriteLine("Failed to get grid entity."); return; } if (!grid.HasComponent()) { - shell.SendText(player, "Grid doesn't have an atmosphere."); + shell.WriteLine("Grid doesn't have an atmosphere."); return; } @@ -64,13 +64,13 @@ namespace Content.Server.Commands.Atmos if (tile == null) { - shell.SendText(player, "Invalid coordinates."); + shell.WriteLine("Invalid coordinates."); return; } if (tile.Air == null) { - shell.SendText(player, "Can't change that tile's temperature."); + shell.WriteLine("Can't change that tile's temperature."); return; } diff --git a/Content.Server/Commands/Atmos/ShowAtmosCommand.cs b/Content.Server/Commands/Atmos/ShowAtmosCommand.cs index 01e1ccc61d..bd3f8cf6ca 100644 --- a/Content.Server/Commands/Atmos/ShowAtmosCommand.cs +++ b/Content.Server/Commands/Atmos/ShowAtmosCommand.cs @@ -1,32 +1,33 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.EntitySystems.Atmos; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects.Systems; namespace Content.Server.Commands.Atmos { [AdminCommand(AdminFlags.Debug)] - public class ShowAtmos : IClientCommand + public class ShowAtmos : IConsoleCommand { public string Command => "showatmos"; public string Description => "Toggles seeing atmos debug overlay."; public string Help => $"Usage: {Command}"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "You must be a player to use this command."); + shell.WriteLine("You must be a player to use this command."); return; } var atmosDebug = EntitySystem.Get(); var enabled = atmosDebug.ToggleObserver(player); - shell.SendText(player, enabled + shell.WriteLine(enabled ? "Enabled the atmospherics debug overlay." : "Disabled the atmospherics debug overlay."); } diff --git a/Content.Server/Commands/AttachBodyPartCommand.cs b/Content.Server/Commands/AttachBodyPartCommand.cs index 72128390b5..07b9e92fdd 100644 --- a/Content.Server/Commands/AttachBodyPartCommand.cs +++ b/Content.Server/Commands/AttachBodyPartCommand.cs @@ -1,11 +1,11 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.Components.Body.Part; using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body.Part; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -13,14 +13,15 @@ using Robust.Shared.IoC; namespace Content.Server.Commands { [AdminCommand(AdminFlags.Fun)] - public class AttachBodyPartCommand : IClientCommand + public class AttachBodyPartCommand : IConsoleCommand { public string Command => "attachbodypart"; public string Description => "Attaches a body part to you or someone else."; public string Help => $"{Command} / {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; var entityManager = IoCManager.Resolve(); IEntity entity; @@ -31,19 +32,19 @@ namespace Content.Server.Commands case 1: if (player == null) { - shell.SendText(player, $"You need to specify an entity to attach the part to if you aren't a player.\n{Help}"); + shell.WriteLine($"You need to specify an entity to attach the part to if you aren't a player.\n{Help}"); return; } if (player.AttachedEntity == null) { - shell.SendText(player, $"You need to specify an entity to attach the part to if you aren't attached to an entity.\n{Help}"); + shell.WriteLine($"You need to specify an entity to attach the part to if you aren't attached to an entity.\n{Help}"); return; } if (!EntityUid.TryParse(args[0], out partUid)) { - shell.SendText(player, $"{args[0]} is not a valid entity uid."); + shell.WriteLine($"{args[0]} is not a valid entity uid."); return; } @@ -53,50 +54,50 @@ namespace Content.Server.Commands case 2: if (!EntityUid.TryParse(args[0], out var entityUid)) { - shell.SendText(player, $"{args[0]} is not a valid entity uid."); + shell.WriteLine($"{args[0]} is not a valid entity uid."); return; } if (!EntityUid.TryParse(args[1], out partUid)) { - shell.SendText(player, $"{args[1]} is not a valid entity uid."); + shell.WriteLine($"{args[1]} is not a valid entity uid."); return; } if (!entityManager.TryGetEntity(entityUid, out var tempEntity)) { - shell.SendText(player, $"{entityUid} is not a valid entity."); + shell.WriteLine($"{entityUid} is not a valid entity."); return; } entity = tempEntity; break; default: - shell.SendText(player, Help); + shell.WriteLine(Help); return; } if (!entity.TryGetComponent(out IBody? body)) { - shell.SendText(player, $"Entity {entity.Name} with uid {entity.Uid} does not have a {nameof(IBody)} component."); + shell.WriteLine($"Entity {entity.Name} with uid {entity.Uid} does not have a {nameof(IBody)} component."); return; } if (!entityManager.TryGetEntity(partUid, out var partEntity)) { - shell.SendText(player, $"{partUid} is not a valid entity."); + shell.WriteLine($"{partUid} is not a valid entity."); return; } if (!partEntity.TryGetComponent(out IBodyPart? part)) { - shell.SendText(player, $"Entity {partEntity.Name} with uid {args[0]} does not have a {nameof(IBodyPart)} component."); + shell.WriteLine($"Entity {partEntity.Name} with uid {args[0]} does not have a {nameof(IBodyPart)} component."); return; } if (body.HasPart(part)) { - shell.SendText(player, $"Body part {partEntity.Name} with uid {partEntity.Uid} is already attached to entity {entity.Name} with uid {entity.Uid}"); + shell.WriteLine($"Body part {partEntity.Name} with uid {partEntity.Uid} is already attached to entity {entity.Name} with uid {entity.Uid}"); return; } diff --git a/Content.Server/Commands/Body/AddHandCommand.cs b/Content.Server/Commands/Body/AddHandCommand.cs index a448df85f7..e0dedf53af 100644 --- a/Content.Server/Commands/Body/AddHandCommand.cs +++ b/Content.Server/Commands/Body/AddHandCommand.cs @@ -1,10 +1,10 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body.Part; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Random; @@ -15,7 +15,7 @@ using Robust.Shared.Random; namespace Content.Server.Commands.Body { [AdminCommand(AdminFlags.Fun)] - class AddHandCommand : IClientCommand + class AddHandCommand : IConsoleCommand { public const string DefaultHandPrototype = "LeftHandHuman"; @@ -23,11 +23,12 @@ namespace Content.Server.Commands.Body public string Description => "Adds a hand to your entity."; public string Help => $"Usage: {Command} / {Command} / {Command} / {Command}"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (args.Length > 1) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } @@ -43,13 +44,13 @@ namespace Content.Server.Commands.Body { if (player == null) { - shell.SendText(player, "Only a player can run this command without arguments."); + shell.WriteLine("Only a player can run this command without arguments."); return; } if (player.AttachedEntity == null) { - shell.SendText(player, "You don't have an entity to add a hand to."); + shell.WriteLine("You don't have an entity to add a hand to."); return; } @@ -63,7 +64,7 @@ namespace Content.Server.Commands.Body { if (!entityManager.TryGetEntity(uid, out var parsedEntity)) { - shell.SendText(player, $"No entity found with uid {uid}"); + shell.WriteLine($"No entity found with uid {uid}"); return; } @@ -74,14 +75,13 @@ namespace Content.Server.Commands.Body { if (player == null) { - shell.SendText(player, - "You must specify an entity to add a hand to when using this command from the server terminal."); + shell.WriteLine("You must specify an entity to add a hand to when using this command from the server terminal."); return; } if (player.AttachedEntity == null) { - shell.SendText(player, "You don't have an entity to add a hand to."); + shell.WriteLine("You don't have an entity to add a hand to."); return; } @@ -95,13 +95,13 @@ namespace Content.Server.Commands.Body { if (!EntityUid.TryParse(args[0], out var uid)) { - shell.SendText(player, $"{args[0]} is not a valid entity uid."); + shell.WriteLine($"{args[0]} is not a valid entity uid."); return; } if (!entityManager.TryGetEntity(uid, out var parsedEntity)) { - shell.SendText(player, $"No entity exists with uid {uid}."); + shell.WriteLine($"No entity exists with uid {uid}."); return; } @@ -109,7 +109,7 @@ namespace Content.Server.Commands.Body if (!prototypeManager.HasIndex(args[1])) { - shell.SendText(player, $"No hand entity exists with id {args[1]}."); + shell.WriteLine($"No hand entity exists with id {args[1]}."); return; } @@ -119,7 +119,7 @@ namespace Content.Server.Commands.Body } default: { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } } @@ -129,13 +129,13 @@ namespace Content.Server.Commands.Body var random = IoCManager.Resolve(); var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}"; - shell.SendText(player, text); + shell.WriteLine(text); return; } if (!hand.TryGetComponent(out IBodyPart? part)) { - shell.SendText(player, $"Hand entity {hand} does not have a {nameof(IBodyPart)} component."); + shell.WriteLine($"Hand entity {hand} does not have a {nameof(IBodyPart)} component."); return; } @@ -144,7 +144,7 @@ namespace Content.Server.Commands.Body ? $"Added hand to entity {entity.Name}" : $"Error occurred trying to add a hand to entity {entity.Name}"; - shell.SendText(player, response); + shell.WriteLine(response); } } } diff --git a/Content.Server/Commands/Body/DestroyMechanismCommand.cs b/Content.Server/Commands/Body/DestroyMechanismCommand.cs index 76fe801165..59aa109a10 100644 --- a/Content.Server/Commands/Body/DestroyMechanismCommand.cs +++ b/Content.Server/Commands/Body/DestroyMechanismCommand.cs @@ -1,9 +1,9 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Body; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; using Robust.Shared.Random; @@ -11,29 +11,30 @@ using Robust.Shared.Random; namespace Content.Server.Commands.Body { [AdminCommand(AdminFlags.Fun)] - class DestroyMechanismCommand : IClientCommand + class DestroyMechanismCommand : IConsoleCommand { public string Command => "destroymechanism"; public string Description => "Destroys a mechanism from your entity"; public string Help => $"Usage: {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "Only a player can run this command."); + shell.WriteLine("Only a player can run this command."); return; } if (args.Length == 0) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } if (player.AttachedEntity == null) { - shell.SendText(player, "You have no entity."); + shell.WriteLine("You have no entity."); return; } @@ -42,7 +43,7 @@ namespace Content.Server.Commands.Body var random = IoCManager.Resolve(); var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}"; - shell.SendText(player, text); + shell.WriteLine(text); return; } @@ -54,12 +55,12 @@ namespace Content.Server.Commands.Body if (mechanism.Name.ToLowerInvariant() == mechanismName) { part.DeleteMechanism(mechanism); - shell.SendText(player, $"Mechanism with name {mechanismName} has been destroyed."); + shell.WriteLine($"Mechanism with name {mechanismName} has been destroyed."); return; } } - shell.SendText(player, $"No mechanism was found with name {mechanismName}."); + shell.WriteLine($"No mechanism was found with name {mechanismName}."); } } } diff --git a/Content.Server/Commands/Body/RemoveHandCommand.cs b/Content.Server/Commands/Body/RemoveHandCommand.cs index 6491c5525e..9a3bcd87fa 100644 --- a/Content.Server/Commands/Body/RemoveHandCommand.cs +++ b/Content.Server/Commands/Body/RemoveHandCommand.cs @@ -1,11 +1,11 @@ -#nullable enable +#nullable enable using System.Linq; using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body.Part; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; using Robust.Shared.Random; @@ -13,23 +13,24 @@ using Robust.Shared.Random; namespace Content.Server.Commands.Body { [AdminCommand(AdminFlags.Fun)] - class RemoveHandCommand : IClientCommand + class RemoveHandCommand : IConsoleCommand { public string Command => "removehand"; public string Description => "Removes a hand from your entity."; public string Help => $"Usage: {Command}"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "Only a player can run this command."); + shell.WriteLine("Only a player can run this command."); return; } if (player.AttachedEntity == null) { - shell.SendText(player, "You have no entity."); + shell.WriteLine("You have no entity."); return; } @@ -38,14 +39,14 @@ namespace Content.Server.Commands.Body var random = IoCManager.Resolve(); var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}"; - shell.SendText(player, text); + shell.WriteLine(text); return; } var (_, hand) = body.Parts.FirstOrDefault(x => x.Value.PartType == BodyPartType.Hand); if (hand == null) { - shell.SendText(player, "You have no hands."); + shell.WriteLine("You have no hands."); } else { diff --git a/Content.Server/Commands/Chat/AdminChatCommand.cs b/Content.Server/Commands/Chat/AdminChatCommand.cs index 9c793b2125..a138ac68ae 100644 --- a/Content.Server/Commands/Chat/AdminChatCommand.cs +++ b/Content.Server/Commands/Chat/AdminChatCommand.cs @@ -1,22 +1,23 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.Interfaces.Chat; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Chat { [AdminCommand(AdminFlags.Admin)] - internal class AdminChatCommand : IClientCommand + internal class AdminChatCommand : IConsoleCommand { public string Command => "asay"; public string Description => "Send chat messages to the private admin chat channel."; public string Help => "asay "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (args.Length < 1) return; diff --git a/Content.Server/Commands/Chat/MeCommand.cs b/Content.Server/Commands/Chat/MeCommand.cs index bb371a6c7c..f22d82f054 100644 --- a/Content.Server/Commands/Chat/MeCommand.cs +++ b/Content.Server/Commands/Chat/MeCommand.cs @@ -1,26 +1,27 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.Interfaces.Chat; using Content.Server.Players; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Enums; using Robust.Shared.IoC; namespace Content.Server.Commands.Chat { [AnyCommand] - internal class MeCommand : IClientCommand + internal class MeCommand : IConsoleCommand { public string Command => "me"; public string Description => "Perform an action."; public string Help => "me "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "This command cannot be run from the server."); + shell.WriteLine("This command cannot be run from the server."); return; } @@ -39,7 +40,7 @@ namespace Content.Server.Commands.Chat if (mindComponent == null) { - shell.SendText(player, "You don't have a mind!"); + shell.WriteLine("You don't have a mind!"); return; } diff --git a/Content.Server/Commands/Chat/OOCCommand.cs b/Content.Server/Commands/Chat/OOCCommand.cs index 1c7d85ce53..7233d2f96a 100644 --- a/Content.Server/Commands/Chat/OOCCommand.cs +++ b/Content.Server/Commands/Chat/OOCCommand.cs @@ -1,21 +1,22 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.Interfaces.Chat; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Chat { [AnyCommand] - internal class OOCCommand : IClientCommand + internal class OOCCommand : IConsoleCommand { public string Command => "ooc"; public string Description => "Send Out Of Character chat messages."; public string Help => "ooc "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (args.Length < 1) return; diff --git a/Content.Server/Commands/Chat/SayCommand.cs b/Content.Server/Commands/Chat/SayCommand.cs index ed01c97006..530e029636 100644 --- a/Content.Server/Commands/Chat/SayCommand.cs +++ b/Content.Server/Commands/Chat/SayCommand.cs @@ -1,27 +1,28 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.Components.Observer; using Content.Server.Interfaces.Chat; using Content.Server.Players; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Enums; using Robust.Shared.IoC; namespace Content.Server.Commands.Chat { [AnyCommand] - internal class SayCommand : IClientCommand + internal class SayCommand : IConsoleCommand { public string Command => "say"; public string Description => "Send chat messages to the local channel or a specified radio channel."; public string Help => "say "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "This command cannot be run from the server."); + shell.WriteLine("This command cannot be run from the server."); return; } @@ -40,7 +41,7 @@ namespace Content.Server.Commands.Chat if (playerEntity == null) { - shell.SendText(player, "You don't have an entity!"); + shell.WriteLine("You don't have an entity!"); return; } @@ -52,7 +53,7 @@ namespace Content.Server.Commands.Chat if (mindComponent == null) { - shell.SendText(player, "You don't have a mind!"); + shell.WriteLine("You don't have a mind!"); return; } diff --git a/Content.Server/Commands/Chat/SuicideCommand.cs b/Content.Server/Commands/Chat/SuicideCommand.cs index aa73ff4913..c20e82f8a7 100644 --- a/Content.Server/Commands/Chat/SuicideCommand.cs +++ b/Content.Server/Commands/Chat/SuicideCommand.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Linq; using Content.Server.Administration; @@ -12,8 +12,8 @@ using Content.Server.Utility; using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.Interfaces; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Enums; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -22,7 +22,7 @@ using Robust.Shared.Localization; namespace Content.Server.Commands.Chat { [AnyCommand] - internal class SuicideCommand : IClientCommand + internal class SuicideCommand : IConsoleCommand { public string Command => "suicide"; @@ -56,11 +56,12 @@ namespace Content.Server.Commands.Chat } } - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "You cannot run this command from the server."); + shell.WriteLine("You cannot run this command from the server."); return; } @@ -72,7 +73,7 @@ namespace Content.Server.Commands.Chat if (owner == null) { - shell.SendText(player, "You don't have a mind!"); + shell.WriteLine("You don't have a mind!"); return; } @@ -122,7 +123,7 @@ namespace Content.Server.Commands.Chat // Prevent the player from returning to the body. Yes, this is an ugly hack. var ghost = new Ghost(){CanReturn = false}; - ghost.Execute(shell, player, Array.Empty()); + ghost.Execute(shell, argStr, Array.Empty()); } } } diff --git a/Content.Server/Commands/CommandUtils.cs b/Content.Server/Commands/CommandUtils.cs index 66e36773fb..c291c0b36c 100644 --- a/Content.Server/Commands/CommandUtils.cs +++ b/Content.Server/Commands/CommandUtils.cs @@ -1,8 +1,9 @@ #nullable enable using System; using System.Diagnostics.CodeAnalysis; -using Robust.Server.Interfaces.Console; +using Robust.Server.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Network; @@ -26,11 +27,11 @@ namespace Content.Server.Commands if (Guid.TryParse(usernameOrId, out var targetGuid)) { if (plyMgr.TryGetSessionById(new NetUserId(targetGuid), out session)) return true; - shell.SendText(performer, "Unable to find user with that name/id."); + shell.WriteLine("Unable to find user with that name/id."); return false; } - shell.SendText(performer, "Unable to find user with that name/id."); + shell.WriteLine("Unable to find user with that name/id."); return false; } @@ -45,7 +46,7 @@ namespace Content.Server.Commands if (!TryGetSessionByUsernameOrId(shell, usernameOrId, performer, out var session)) return false; if (session.AttachedEntity == null) { - shell.SendText(performer, "User has no attached entity."); + shell.WriteLine("User has no attached entity."); return false; } diff --git a/Content.Server/Commands/Damage/AddDamageFlagCommand.cs b/Content.Server/Commands/Damage/AddDamageFlagCommand.cs index 21d353b078..698a9a4b07 100644 --- a/Content.Server/Commands/Damage/AddDamageFlagCommand.cs +++ b/Content.Server/Commands/Damage/AddDamageFlagCommand.cs @@ -1,8 +1,9 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; +using Robust.Server.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; namespace Content.Server.Commands.Damage { @@ -13,15 +14,16 @@ namespace Content.Server.Commands.Damage public override string Description => "Adds a damage flag to your entity or another."; public override string Help => $"Usage: {Command} / {Command} "; - public override void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public override void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (!TryGetEntity(shell, player, args, true, out var entity, out var flag, out var damageable)) { return; } damageable.AddFlag(flag); - shell.SendText(player, $"Added damage flag {flag} to entity {entity.Name}"); + shell.WriteLine($"Added damage flag {flag} to entity {entity.Name}"); } } } diff --git a/Content.Server/Commands/Damage/DamageFlagCommand.cs b/Content.Server/Commands/Damage/DamageFlagCommand.cs index 1edf0e973b..b1eee70281 100644 --- a/Content.Server/Commands/Damage/DamageFlagCommand.cs +++ b/Content.Server/Commands/Damage/DamageFlagCommand.cs @@ -2,21 +2,21 @@ using System; using System.Diagnostics.CodeAnalysis; using Content.Shared.GameObjects.Components.Damage; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; namespace Content.Server.Commands.Damage { - public abstract class DamageFlagCommand : IClientCommand + public abstract class DamageFlagCommand : IConsoleCommand { public abstract string Command { get; } public abstract string Description { get; } public abstract string Help { get; } - public abstract void Execute(IConsoleShell shell, IPlayerSession? player, string[] args); + public abstract void Execute(IConsoleShell shell, string argStr, string[] args); public bool TryGetEntity( IConsoleShell shell, @@ -41,19 +41,19 @@ namespace Content.Server.Commands.Damage { if (player == null) { - shell.SendText(player, "An entity needs to be specified when the command isn't used by a player."); + shell.WriteLine("An entity needs to be specified when the command isn't used by a player."); return false; } if (player.AttachedEntity == null) { - shell.SendText(player, "An entity needs to be specified when you aren't attached to an entity."); + shell.WriteLine("An entity needs to be specified when you aren't attached to an entity."); return false; } if (!Enum.TryParse(args[0], true, out parsedFlag)) { - shell.SendText(player, $"{args[0]} is not a valid damage flag."); + shell.WriteLine($"{args[0]} is not a valid damage flag."); return false; } @@ -65,44 +65,44 @@ namespace Content.Server.Commands.Damage { if (!EntityUid.TryParse(args[0], out var id)) { - shell.SendText(player, $"{args[0]} isn't a valid entity id."); + shell.WriteLine($"{args[0]} isn't a valid entity id."); return false; } var entityManager = IoCManager.Resolve(); if (!entityManager.TryGetEntity(id, out parsedEntity)) { - shell.SendText(player, $"No entity found with id {id}."); + shell.WriteLine($"No entity found with id {id}."); return false; } if (!Enum.TryParse(args[1], true, out parsedFlag)) { - shell.SendText(player, $"{args[1]} is not a valid damage flag."); + shell.WriteLine($"{args[1]} is not a valid damage flag."); return false; } break; } default: - shell.SendText(player, Help); + shell.WriteLine(Help); return false; } if (!parsedEntity.TryGetComponent(out parsedDamageable)) { - shell.SendText(player, $"Entity {parsedEntity.Name} doesn't have a {nameof(IDamageableComponent)}"); + shell.WriteLine($"Entity {parsedEntity.Name} doesn't have a {nameof(IDamageableComponent)}"); return false; } if (parsedDamageable.HasFlag(parsedFlag) && adding) { - shell.SendText(player, $"Entity {parsedEntity.Name} already has damage flag {parsedFlag}."); + shell.WriteLine($"Entity {parsedEntity.Name} already has damage flag {parsedFlag}."); return false; } else if (!parsedDamageable.HasFlag(parsedFlag) && !adding) { - shell.SendText(player, $"Entity {parsedEntity.Name} doesn't have damage flag {parsedFlag}."); + shell.WriteLine($"Entity {parsedEntity.Name} doesn't have damage flag {parsedFlag}."); return false; } diff --git a/Content.Server/Commands/Damage/GodModeCommand.cs b/Content.Server/Commands/Damage/GodModeCommand.cs index 77c05fc24c..cedd3b1fb1 100644 --- a/Content.Server/Commands/Damage/GodModeCommand.cs +++ b/Content.Server/Commands/Damage/GodModeCommand.cs @@ -1,9 +1,9 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.EntitySystems; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; @@ -12,14 +12,15 @@ using Robust.Shared.IoC; namespace Content.Server.Commands.Damage { [AdminCommand(AdminFlags.Admin)] - public class GodModeCommand : IClientCommand + public class GodModeCommand : IConsoleCommand { public string Command => "godmode"; public string Description => "Makes your entity or another invulnerable to almost anything. May have irreversible changes."; public string Help => $"Usage: {Command} / {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; IEntity entity; switch (args.Length) @@ -27,13 +28,13 @@ namespace Content.Server.Commands.Damage case 0: if (player == null) { - shell.SendText(player, "An entity needs to be specified when the command isn't used by a player."); + shell.WriteLine("An entity needs to be specified when the command isn't used by a player."); return; } if (player.AttachedEntity == null) { - shell.SendText(player, "An entity needs to be specified when you aren't attached to an entity."); + shell.WriteLine("An entity needs to be specified when you aren't attached to an entity."); return; } @@ -42,28 +43,28 @@ namespace Content.Server.Commands.Damage case 1: if (!EntityUid.TryParse(args[0], out var id)) { - shell.SendText(player, $"{args[0]} isn't a valid entity id."); + shell.WriteLine($"{args[0]} isn't a valid entity id."); return; } var entityManager = IoCManager.Resolve(); if (!entityManager.TryGetEntity(id, out var parsedEntity)) { - shell.SendText(player, $"No entity found with id {id}."); + shell.WriteLine($"No entity found with id {id}."); return; } entity = parsedEntity; break; default: - shell.SendText(player, Help); + shell.WriteLine(Help); return; } var godmodeSystem = EntitySystem.Get(); var enabled = godmodeSystem.ToggleGodmode(entity); - shell.SendText(player, enabled + shell.WriteLine(enabled ? $"Enabled godmode for entity {entity.Name} with id {entity.Uid}" : $"Disabled godmode for entity {entity.Name} with id {entity.Uid}"); } diff --git a/Content.Server/Commands/Damage/RemoveDamageFlagCommand.cs b/Content.Server/Commands/Damage/RemoveDamageFlagCommand.cs index 631eaae370..c315723273 100644 --- a/Content.Server/Commands/Damage/RemoveDamageFlagCommand.cs +++ b/Content.Server/Commands/Damage/RemoveDamageFlagCommand.cs @@ -1,8 +1,9 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; +using Robust.Server.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; namespace Content.Server.Commands.Damage { @@ -13,15 +14,16 @@ namespace Content.Server.Commands.Damage public override string Description => "Removes a damage flag from your entity or another."; public override string Help => $"Usage: {Command} / {Command} "; - public override void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public override void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (!TryGetEntity(shell, player, args, false, out var entity, out var flag, out var damageable)) { return; } damageable.RemoveFlag(flag); - shell.SendText(player, $"Removed damage flag {flag} from entity {entity.Name}"); + shell.WriteLine($"Removed damage flag {flag} from entity {entity.Name}"); } } } diff --git a/Content.Server/Commands/Disposal/TubeConnectionsCommand.cs b/Content.Server/Commands/Disposal/TubeConnectionsCommand.cs index eb13975d74..5188893165 100644 --- a/Content.Server/Commands/Disposal/TubeConnectionsCommand.cs +++ b/Content.Server/Commands/Disposal/TubeConnectionsCommand.cs @@ -1,9 +1,9 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.Components.Disposal; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -12,42 +12,43 @@ using Robust.Shared.Localization; namespace Content.Server.Commands.Disposal { [AdminCommand(AdminFlags.Debug)] - public class TubeConnectionsCommand : IClientCommand + public class TubeConnectionsCommand : IConsoleCommand { public string Command => "tubeconnections"; public string Description => Loc.GetString("Shows all the directions that a tube can connect in."); public string Help => $"Usage: {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player?.AttachedEntity == null) { - shell.SendText(player, Loc.GetString("Only players can use this command")); + shell.WriteLine(Loc.GetString("Only players can use this command")); return; } if (args.Length < 1) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } if (!EntityUid.TryParse(args[0], out var id)) { - shell.SendText(player, Loc.GetString("{0} isn't a valid entity uid", args[0])); + shell.WriteLine(Loc.GetString("{0} isn't a valid entity uid", args[0])); return; } var entityManager = IoCManager.Resolve(); if (!entityManager.TryGetEntity(id, out var entity)) { - shell.SendText(player, Loc.GetString("No entity exists with uid {0}", id)); + shell.WriteLine(Loc.GetString("No entity exists with uid {0}", id)); return; } if (!entity.TryGetComponent(out IDisposalTubeComponent? tube)) { - shell.SendText(player, Loc.GetString("Entity with uid {0} doesn't have a {1} component", id, nameof(IDisposalTubeComponent))); + shell.WriteLine(Loc.GetString("Entity with uid {0} doesn't have a {1} component", id, nameof(IDisposalTubeComponent))); return; } diff --git a/Content.Server/Commands/FindEntitiesWithComponents.cs b/Content.Server/Commands/FindEntitiesWithComponents.cs index 947ab82def..85daab24c6 100644 --- a/Content.Server/Commands/FindEntitiesWithComponents.cs +++ b/Content.Server/Commands/FindEntitiesWithComponents.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; using Content.Server.Administration; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -12,17 +12,17 @@ using Robust.Shared.IoC; namespace Content.Server.Commands { [AdminCommand(AdminFlags.Mapping)] - public class FindEntitiesWithComponents : IClientCommand + public class FindEntitiesWithComponents : IConsoleCommand { public string Command => "findentitieswithcomponents"; public string Description => "Finds entities with all of the specified components."; public string Help => $"{Command} ..."; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length == 0) { - shell.SendText(player, $"Invalid amount of arguments: {args.Length}.\n{Help}"); + shell.WriteLine($"Invalid amount of arguments: {args.Length}.\n{Help}"); return; } @@ -43,7 +43,7 @@ namespace Content.Server.Commands if (invalidArgs.Count > 0) { - shell.SendText(player, $"No component found for component names: {string.Join(", ", invalidArgs)}"); + shell.WriteLine($"No component found for component names: {string.Join(", ", invalidArgs)}"); return; } @@ -63,11 +63,11 @@ namespace Content.Server.Commands if (entityIds.Count == 0) { - shell.SendText(player, $"No entities found with components {string.Join(", ", args)}."); + shell.WriteLine($"No entities found with components {string.Join(", ", args)}."); return; } - shell.SendText(player, $"{entityIds.Count} entities found:\n{string.Join("\n", entityIds)}"); + shell.WriteLine($"{entityIds.Count} entities found:\n{string.Join("\n", entityIds)}"); } } } diff --git a/Content.Server/Commands/GameTicking/DelayStartCommand.cs b/Content.Server/Commands/GameTicking/DelayStartCommand.cs index ff9a219d58..a74293779f 100644 --- a/Content.Server/Commands/GameTicking/DelayStartCommand.cs +++ b/Content.Server/Commands/GameTicking/DelayStartCommand.cs @@ -3,51 +3,51 @@ using Content.Server.Administration; using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.GameTicking { [AdminCommand(AdminFlags.Server)] - class DelayStartCommand : IClientCommand + class DelayStartCommand : IConsoleCommand { public string Command => "delaystart"; public string Description => "Delays the round start."; public string Help => $"Usage: {Command} \nPauses/Resumes the countdown if no argument is provided."; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var ticker = IoCManager.Resolve(); if (ticker.RunLevel != GameRunLevel.PreRoundLobby) { - shell.SendText(player, "This can only be executed while the game is in the pre-round lobby."); + shell.WriteLine("This can only be executed while the game is in the pre-round lobby."); return; } if (args.Length == 0) { var paused = ticker.TogglePause(); - shell.SendText(player, paused ? "Paused the countdown." : "Resumed the countdown."); + shell.WriteLine(paused ? "Paused the countdown." : "Resumed the countdown."); return; } if (args.Length != 1) { - shell.SendText(player, "Need zero or one arguments."); + shell.WriteLine("Need zero or one arguments."); return; } if (!uint.TryParse(args[0], out var seconds) || seconds == 0) { - shell.SendText(player, $"{args[0]} isn't a valid amount of seconds."); + shell.WriteLine($"{args[0]} isn't a valid amount of seconds."); return; } var time = TimeSpan.FromSeconds(seconds); if (!ticker.DelayStart(time)) { - shell.SendText(player, "An unknown error has occurred."); + shell.WriteLine("An unknown error has occurred."); } } } diff --git a/Content.Server/Commands/GameTicking/EndRoundCommand.cs b/Content.Server/Commands/GameTicking/EndRoundCommand.cs index b274ed61cf..12afa279cb 100644 --- a/Content.Server/Commands/GameTicking/EndRoundCommand.cs +++ b/Content.Server/Commands/GameTicking/EndRoundCommand.cs @@ -3,26 +3,26 @@ using Content.Server.Administration; using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.GameTicking { [AdminCommand(AdminFlags.Server)] - class EndRoundCommand : IClientCommand + class EndRoundCommand : IConsoleCommand { public string Command => "endround"; public string Description => "Ends the round and moves the server to PostRound."; public string Help => String.Empty; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var ticker = IoCManager.Resolve(); if (ticker.RunLevel != GameRunLevel.InRound) { - shell.SendText(player, "This can only be executed while the game is in a round."); + shell.WriteLine("This can only be executed while the game is in a round."); return; } diff --git a/Content.Server/Commands/GameTicking/ForcePresetCommand.cs b/Content.Server/Commands/GameTicking/ForcePresetCommand.cs index 06c7cd67e4..bdd92c6500 100644 --- a/Content.Server/Commands/GameTicking/ForcePresetCommand.cs +++ b/Content.Server/Commands/GameTicking/ForcePresetCommand.cs @@ -2,43 +2,43 @@ using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.GameTicking { [AdminCommand(AdminFlags.Server)] - class ForcePresetCommand : IClientCommand + class ForcePresetCommand : IConsoleCommand { public string Command => "forcepreset"; public string Description => "Forces a specific game preset to start for the current lobby."; public string Help => $"Usage: {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var ticker = IoCManager.Resolve(); if (ticker.RunLevel != GameRunLevel.PreRoundLobby) { - shell.SendText(player, "This can only be executed while the game is in the pre-round lobby."); + shell.WriteLine("This can only be executed while the game is in the pre-round lobby."); return; } if (args.Length != 1) { - shell.SendText(player, "Need exactly one argument."); + shell.WriteLine("Need exactly one argument."); return; } var name = args[0]; if (!ticker.TryGetPreset(name, out var type)) { - shell.SendText(player, $"No preset exists with name {name}."); + shell.WriteLine($"No preset exists with name {name}."); return; } ticker.SetStartPreset(type, true); - shell.SendText(player, $"Forced the game to start with preset {name}."); + shell.WriteLine($"Forced the game to start with preset {name}."); } } } \ No newline at end of file diff --git a/Content.Server/Commands/GameTicking/GoLobbyCommand.cs b/Content.Server/Commands/GameTicking/GoLobbyCommand.cs index 35d2691f00..7ca9c0c76b 100644 --- a/Content.Server/Commands/GameTicking/GoLobbyCommand.cs +++ b/Content.Server/Commands/GameTicking/GoLobbyCommand.cs @@ -4,8 +4,8 @@ using Content.Server.Administration; using Content.Server.Interfaces.GameTicking; using Content.Shared; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.IoC; using Robust.Shared.Localization; @@ -13,12 +13,12 @@ using Robust.Shared.Localization; namespace Content.Server.Commands.GameTicking { [AdminCommand(AdminFlags.Server)] - public class GoLobbyCommand : IClientCommand + public class GoLobbyCommand : IConsoleCommand { public string Command => "golobby"; public string Description => "Enables the lobby and restarts the round."; public string Help => $"Usage: {Command} / {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { Type? preset = null; var presetName = string.Join(" ", args); @@ -29,7 +29,7 @@ namespace Content.Server.Commands.GameTicking { if (!ticker.TryGetPreset(presetName, out preset)) { - shell.SendText(player, $"No preset found with name {presetName}"); + shell.WriteLine($"No preset found with name {presetName}"); return; } } @@ -44,7 +44,7 @@ namespace Content.Server.Commands.GameTicking ticker.SetStartPreset(preset); } - shell.SendText(player, $"Enabling the lobby and restarting the round.{(preset == null ? "" : $"\nPreset set to {presetName}")}"); + shell.WriteLine($"Enabling the lobby and restarting the round.{(preset == null ? "" : $"\nPreset set to {presetName}")}"); } } } diff --git a/Content.Server/Commands/GameTicking/JoinGameCommand.cs b/Content.Server/Commands/GameTicking/JoinGameCommand.cs index 95006d5781..b160a1fe08 100644 --- a/Content.Server/Commands/GameTicking/JoinGameCommand.cs +++ b/Content.Server/Commands/GameTicking/JoinGameCommand.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Content.Server.Administration; using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; using Content.Shared.Roles; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; using Robust.Shared.Prototypes; namespace Content.Server.Commands.GameTicking { [AnyCommand] - class JoinGameCommand : IClientCommand + class JoinGameCommand : IConsoleCommand { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -23,8 +23,9 @@ namespace Content.Server.Commands.GameTicking { IoCManager.InjectDependencies(this); } - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; var output = string.Join(".", args); if (player == null) { @@ -34,7 +35,7 @@ namespace Content.Server.Commands.GameTicking var ticker = IoCManager.Resolve(); if (ticker.RunLevel == GameRunLevel.PreRoundLobby) { - shell.SendText(player, "Round has not started."); + shell.WriteLine("Round has not started."); return; } else if(ticker.RunLevel == GameRunLevel.InRound) @@ -45,7 +46,7 @@ namespace Content.Server.Commands.GameTicking if(positions.GetValueOrDefault(ID, 0) == 0) //n < 0 is treated as infinite { var jobPrototype = _prototypeManager.Index(ID); - shell.SendText(player, $"{jobPrototype.Name} has no available slots."); + shell.WriteLine($"{jobPrototype.Name} has no available slots."); return; } ticker.MakeJoinGame(player, args[0].ToString()); @@ -55,4 +56,4 @@ namespace Content.Server.Commands.GameTicking ticker.MakeJoinGame(player, null); } } -} \ No newline at end of file +} diff --git a/Content.Server/Commands/GameTicking/MappingCommand.cs b/Content.Server/Commands/GameTicking/MappingCommand.cs index abf94258b5..332e5d57c5 100644 --- a/Content.Server/Commands/GameTicking/MappingCommand.cs +++ b/Content.Server/Commands/GameTicking/MappingCommand.cs @@ -1,9 +1,9 @@ -using System.Linq; +using System.Linq; using Content.Server.Administration; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Server.Interfaces.Timing; +using Robust.Shared.Console; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Utility; @@ -11,17 +11,18 @@ using Robust.Shared.Utility; namespace Content.Server.Commands.GameTicking { [AdminCommand(AdminFlags.Server | AdminFlags.Mapping)] - class MappingCommand : IClientCommand + class MappingCommand : IConsoleCommand { public string Command => "mapping"; public string Description => "Creates and teleports you to a new uninitialized map for mapping."; public string Help => $"Usage: {Command} / {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "Only players can use this command"); + shell.WriteLine("Only players can use this command"); return; } @@ -34,7 +35,7 @@ namespace Content.Server.Commands.GameTicking case 1: if (player.AttachedEntity == null) { - shell.SendText(player, "The map name argument cannot be omitted if you have no entity."); + shell.WriteLine("The map name argument cannot be omitted if you have no entity."); return; } @@ -44,7 +45,7 @@ namespace Content.Server.Commands.GameTicking case 2: if (!int.TryParse(args[0], out var id)) { - shell.SendText(player, $"{args[0]} is not a valid integer."); + shell.WriteLine($"{args[0]} is not a valid integer."); return; } @@ -52,21 +53,21 @@ namespace Content.Server.Commands.GameTicking mapName = args[1]; break; default: - shell.SendText(player, Help); + shell.WriteLine(Help); return; } - shell.ExecuteCommand(player, $"addmap {mapId} false"); - shell.ExecuteCommand(player, $"loadbp {mapId} \"{CommandParsing.Escape(mapName)}\" true"); - shell.ExecuteCommand(player, "aghost"); - shell.ExecuteCommand(player, $"tp 0 0 {mapId}"); + shell.ExecuteCommand($"addmap {mapId} false"); + shell.ExecuteCommand($"loadbp {mapId} \"{CommandParsing.Escape(mapName)}\" true"); + shell.ExecuteCommand("aghost"); + shell.ExecuteCommand($"tp 0 0 {mapId}"); var newGrid = mapManager.GetAllGrids().OrderByDescending(g => (int) g.Index).First(); var pauseManager = IoCManager.Resolve(); pauseManager.SetMapPaused(newGrid.ParentMapId, true); - shell.SendText(player, $"Created unloaded map from file {mapName} with id {mapId}. Use \"savebp {newGrid.Index} foo.yml\" to save the new grid as a map."); + shell.WriteLine($"Created unloaded map from file {mapName} with id {mapId}. Use \"savebp {newGrid.Index} foo.yml\" to save the new grid as a map."); } } } diff --git a/Content.Server/Commands/GameTicking/NewRoundCommand.cs b/Content.Server/Commands/GameTicking/NewRoundCommand.cs index da10407a77..98d90cfab4 100644 --- a/Content.Server/Commands/GameTicking/NewRoundCommand.cs +++ b/Content.Server/Commands/GameTicking/NewRoundCommand.cs @@ -2,20 +2,20 @@ using Content.Server.Administration; using Content.Server.Interfaces.GameTicking; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.GameTicking { [AdminCommand(AdminFlags.Server)] - public class NewRoundCommand : IClientCommand + public class NewRoundCommand : IConsoleCommand { public string Command => "restartround"; public string Description => "Moves the server from PostRound to a new PreRoundLobby."; public string Help => String.Empty; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var ticker = IoCManager.Resolve(); ticker.RestartRound(); diff --git a/Content.Server/Commands/GameTicking/ObserveCommand.cs b/Content.Server/Commands/GameTicking/ObserveCommand.cs index 4596418418..fcc0e867a6 100644 --- a/Content.Server/Commands/GameTicking/ObserveCommand.cs +++ b/Content.Server/Commands/GameTicking/ObserveCommand.cs @@ -1,20 +1,21 @@ -using Content.Server.Administration; +using Content.Server.Administration; using Content.Server.Interfaces.GameTicking; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.GameTicking { [AnyCommand] - class ObserveCommand : IClientCommand + class ObserveCommand : IConsoleCommand { public string Command => "observe"; public string Description => ""; public string Help => ""; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { return; @@ -24,4 +25,4 @@ namespace Content.Server.Commands.GameTicking ticker.MakeObserve(player); } } -} \ No newline at end of file +} diff --git a/Content.Server/Commands/GameTicking/RespawnCommand.cs b/Content.Server/Commands/GameTicking/RespawnCommand.cs index 94286c3bcd..9af7ed05d8 100644 --- a/Content.Server/Commands/GameTicking/RespawnCommand.cs +++ b/Content.Server/Commands/GameTicking/RespawnCommand.cs @@ -1,23 +1,24 @@ -using Content.Server.Interfaces.GameTicking; +using Content.Server.Interfaces.GameTicking; using Content.Server.Players; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; using Robust.Shared.Network; namespace Content.Server.Commands.GameTicking { - class RespawnCommand : IClientCommand + class RespawnCommand : IConsoleCommand { public string Command => "respawn"; public string Description => "Respawns a player, kicking them back to the lobby."; public string Help => "respawn [player]"; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (args.Length > 1) { - shell.SendText(player, "Must provide <= 1 argument."); + shell.WriteLine("Must provide <= 1 argument."); return; } @@ -29,7 +30,7 @@ namespace Content.Server.Commands.GameTicking { if (player == null) { - shell.SendText((IPlayerSession)null, "If not a player, an argument must be given."); + shell.WriteLine("If not a player, an argument must be given."); return; } @@ -37,7 +38,7 @@ namespace Content.Server.Commands.GameTicking } else if (!playerMgr.TryGetUserId(args[0], out userId)) { - shell.SendText(player, "Unknown player"); + shell.WriteLine("Unknown player"); return; } @@ -45,17 +46,16 @@ namespace Content.Server.Commands.GameTicking { if (!playerMgr.TryGetPlayerData(userId, out var data)) { - shell.SendText(player, "Unknown player"); + shell.WriteLine("Unknown player"); return; } data.ContentData().WipeMind(); - shell.SendText(player, - "Player is not currently online, but they will respawn if they come back online"); + shell.WriteLine("Player is not currently online, but they will respawn if they come back online"); return; } ticker.Respawn(targetPlayer); } } -} \ No newline at end of file +} diff --git a/Content.Server/Commands/GameTicking/SetGamePresetCommand.cs b/Content.Server/Commands/GameTicking/SetGamePresetCommand.cs index 5a5c1c7f90..b085164e96 100644 --- a/Content.Server/Commands/GameTicking/SetGamePresetCommand.cs +++ b/Content.Server/Commands/GameTicking/SetGamePresetCommand.cs @@ -1,24 +1,24 @@ using Content.Server.Administration; using Content.Server.Interfaces.GameTicking; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.GameTicking { [AdminCommand(AdminFlags.Server)] - class SetGamePresetCommand : IClientCommand + class SetGamePresetCommand : IConsoleCommand { public string Command => "setgamepreset"; public string Description => ""; public string Help => ""; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 1) { - shell.SendText(player, "Need exactly one argument."); + shell.WriteLine("Need exactly one argument."); return; } diff --git a/Content.Server/Commands/GameTicking/StartRoundCommand.cs b/Content.Server/Commands/GameTicking/StartRoundCommand.cs index b8f3804bbe..14a74caf86 100644 --- a/Content.Server/Commands/GameTicking/StartRoundCommand.cs +++ b/Content.Server/Commands/GameTicking/StartRoundCommand.cs @@ -3,26 +3,26 @@ using Content.Server.Administration; using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.GameTicking { [AdminCommand(AdminFlags.Server)] - class StartRoundCommand : IClientCommand + class StartRoundCommand : IConsoleCommand { public string Command => "startround"; public string Description => "Ends PreRoundLobby state and starts the round."; public string Help => String.Empty; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var ticker = IoCManager.Resolve(); if (ticker.RunLevel != GameRunLevel.PreRoundLobby) { - shell.SendText(player, "This can only be executed while the game is in the pre-round lobby."); + shell.WriteLine("This can only be executed while the game is in the pre-round lobby."); return; } diff --git a/Content.Server/Commands/GameTicking/TileWallsCommand.cs b/Content.Server/Commands/GameTicking/TileWallsCommand.cs index 668ea461c1..e2c7445763 100644 --- a/Content.Server/Commands/GameTicking/TileWallsCommand.cs +++ b/Content.Server/Commands/GameTicking/TileWallsCommand.cs @@ -1,8 +1,8 @@ -using Content.Server.Administration; +using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.Maps; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; @@ -12,15 +12,16 @@ using Robust.Shared.Map; namespace Content.Server.Commands.GameTicking { [AdminCommand(AdminFlags.Mapping)] - class TileWallsCommand : IClientCommand + class TileWallsCommand : IConsoleCommand { // ReSharper disable once StringLiteralTypo public string Command => "tilewalls"; public string Description => "Puts an underplating tile below every wall on a grid."; public string Help => $"Usage: {Command} | {Command}"; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; GridId gridId; switch (args.Length) @@ -28,7 +29,7 @@ namespace Content.Server.Commands.GameTicking case 0: if (player?.AttachedEntity == null) { - shell.SendText((IPlayerSession) null, "Only a player can run this command."); + shell.WriteLine("Only a player can run this command."); return; } @@ -37,28 +38,28 @@ namespace Content.Server.Commands.GameTicking case 1: if (!int.TryParse(args[0], out var id)) { - shell.SendText(player, $"{args[0]} is not a valid integer."); + shell.WriteLine($"{args[0]} is not a valid integer."); return; } gridId = new GridId(id); break; default: - shell.SendText(player, Help); + shell.WriteLine(Help); return; } var mapManager = IoCManager.Resolve(); if (!mapManager.TryGetGrid(gridId, out var grid)) { - shell.SendText(player, $"No grid exists with id {gridId}"); + shell.WriteLine($"No grid exists with id {gridId}"); return; } var entityManager = IoCManager.Resolve(); if (!entityManager.TryGetEntity(grid.GridEntityId, out var gridEntity)) { - shell.SendText(player, $"Grid {gridId} doesn't have an associated grid entity."); + shell.WriteLine($"Grid {gridId} doesn't have an associated grid entity."); return; } @@ -106,7 +107,7 @@ namespace Content.Server.Commands.GameTicking changed++; } - shell.SendText(player, $"Changed {changed} tiles."); + shell.WriteLine($"Changed {changed} tiles."); } } } diff --git a/Content.Server/Commands/GameTicking/ToggleDisallowLateJoinCommand.cs b/Content.Server/Commands/GameTicking/ToggleDisallowLateJoinCommand.cs index ffb1be19c0..d519ba5754 100644 --- a/Content.Server/Commands/GameTicking/ToggleDisallowLateJoinCommand.cs +++ b/Content.Server/Commands/GameTicking/ToggleDisallowLateJoinCommand.cs @@ -1,24 +1,24 @@ using Content.Server.Administration; using Content.Server.Interfaces.GameTicking; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.GameTicking { [AdminCommand(AdminFlags.Server)] - class ToggleDisallowLateJoinCommand : IClientCommand + class ToggleDisallowLateJoinCommand : IConsoleCommand { public string Command => "toggledisallowlatejoin"; public string Description => "Allows or disallows latejoining during mid-game."; public string Help => $"Usage: {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 1) { - shell.SendText(player, "Need exactly one argument."); + shell.WriteLine("Need exactly one argument."); return; } @@ -27,11 +27,11 @@ namespace Content.Server.Commands.GameTicking if (bool.TryParse(args[0], out var result)) { ticker.ToggleDisallowLateJoin(bool.Parse(args[0])); - shell.SendText(player, result ? "Late joining has been disabled." : "Late joining has been enabled."); + shell.WriteLine(result ? "Late joining has been disabled." : "Late joining has been enabled."); } else { - shell.SendText(player, "Invalid argument."); + shell.WriteLine("Invalid argument."); } } } diff --git a/Content.Server/Commands/GameTicking/ToggleReadyCommand.cs b/Content.Server/Commands/GameTicking/ToggleReadyCommand.cs index 0f56e148ce..f710c59fb1 100644 --- a/Content.Server/Commands/GameTicking/ToggleReadyCommand.cs +++ b/Content.Server/Commands/GameTicking/ToggleReadyCommand.cs @@ -1,20 +1,21 @@ -using Content.Server.Administration; +using Content.Server.Administration; using Content.Server.Interfaces.GameTicking; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.GameTicking { [AnyCommand] - class ToggleReadyCommand : IClientCommand + class ToggleReadyCommand : IConsoleCommand { public string Command => "toggleready"; public string Description => ""; public string Help => ""; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { return; @@ -24,4 +25,4 @@ namespace Content.Server.Commands.GameTicking ticker.ToggleReady(player, bool.Parse(args[0])); } } -} \ No newline at end of file +} diff --git a/Content.Server/Commands/HideContainedContextCommand.cs b/Content.Server/Commands/HideContainedContextCommand.cs index ad975313a1..caf3384abe 100644 --- a/Content.Server/Commands/HideContainedContextCommand.cs +++ b/Content.Server/Commands/HideContainedContextCommand.cs @@ -1,25 +1,26 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.EntitySystems; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects.Systems; namespace Content.Server.Commands { [AdminCommand(AdminFlags.Debug)] - public class HideContainedContextCommand : IClientCommand + public class HideContainedContextCommand : IConsoleCommand { public string Command => "hidecontainedcontext"; public string Description => $"Reverts the effects of {ShowContainedContextCommand.CommandName}"; public string Help => $"{Command}"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "You need to be a player to use this command."); + shell.WriteLine("You need to be a player to use this command."); return; } diff --git a/Content.Server/Commands/Hungry.cs b/Content.Server/Commands/Hungry.cs index 39daf0caf9..02ddebe18c 100644 --- a/Content.Server/Commands/Hungry.cs +++ b/Content.Server/Commands/Hungry.cs @@ -1,37 +1,38 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.Components.Nutrition; using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Nutrition; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; namespace Content.Server.Commands { [AdminCommand(AdminFlags.Debug)] - public class Hungry : IClientCommand + public class Hungry : IConsoleCommand { public string Command => "hungry"; public string Description => "Makes you hungry."; public string Help => $"{Command}"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "You cannot use this command unless you are a player."); + shell.WriteLine("You cannot use this command unless you are a player."); return; } if (player.AttachedEntity == null) { - shell.SendText(player, "You cannot use this command without an entity."); + shell.WriteLine("You cannot use this command without an entity."); return; } if (!player.AttachedEntity.TryGetComponent(out HungerComponent? hunger)) { - shell.SendText(player, $"Your entity does not have a {nameof(HungerComponent)} component."); + shell.WriteLine($"Your entity does not have a {nameof(HungerComponent)} component."); return; } diff --git a/Content.Server/Commands/HurtCommand.cs b/Content.Server/Commands/HurtCommand.cs index 1810852aaf..532af0304e 100644 --- a/Content.Server/Commands/HurtCommand.cs +++ b/Content.Server/Commands/HurtCommand.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Diagnostics.CodeAnalysis; using System.Text; @@ -6,8 +6,8 @@ using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -15,7 +15,7 @@ using Robust.Shared.IoC; namespace Content.Server.Commands { [AdminCommand(AdminFlags.Fun)] - class HurtCommand : IClientCommand + class HurtCommand : IConsoleCommand { public string Command => "hurt"; public string Description => "Ouch"; @@ -53,7 +53,7 @@ namespace Content.Server.Commands if (playerEntity == null) { - shell.SendText(player, $"You must have a player entity to use this command without specifying an entity.\n{Help}"); + shell.WriteLine($"You must have a player entity to use this command without specifying an entity.\n{Help}"); return false; } @@ -63,7 +63,7 @@ namespace Content.Server.Commands if (!EntityUid.TryParse(arg, out var entityUid)) { - shell.SendText(player, $"{arg} is not a valid entity uid.\n{Help}"); + shell.WriteLine($"{arg} is not a valid entity uid.\n{Help}"); return false; } @@ -72,7 +72,7 @@ namespace Content.Server.Commands if (!entityManager.TryGetEntity(entityUid, out var parsedEntity)) { - shell.SendText(player, $"No entity found with uid {entityUid}"); + shell.WriteLine($"No entity found with uid {entityUid}"); return false; } @@ -89,7 +89,7 @@ namespace Content.Server.Commands { if (!int.TryParse(args[1], out var amount)) { - shell.SendText(player, $"{args[1]} is not a valid damage integer."); + shell.WriteLine($"{args[1]} is not a valid damage integer."); func = null; return false; @@ -101,20 +101,19 @@ namespace Content.Server.Commands { if (!damageable.DamageClasses.ContainsKey(damageClass)) { - shell.SendText(player, $"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} can not be damaged with damage class {damageClass}"); + shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} can not be damaged with damage class {damageClass}"); return; } if (!damageable.ChangeDamage(damageClass, amount, ignoreResistances)) { - shell.SendText(player, $"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} received no damage."); + shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} received no damage."); return; } - shell.SendText(player, - $"Damaged entity {damageable.Owner.Name} with id {damageable.Owner.Uid} for {amount} {damageClass} damage{(ignoreResistances ? ", ignoring resistances." : ".")}"); + shell.WriteLine($"Damaged entity {damageable.Owner.Name} with id {damageable.Owner.Uid} for {amount} {damageClass} damage{(ignoreResistances ? ", ignoring resistances." : ".")}"); }; return true; @@ -126,37 +125,38 @@ namespace Content.Server.Commands { if (!damageable.DamageTypes.ContainsKey(damageType)) { - shell.SendText(player, $"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} can not be damaged with damage class {damageType}"); + shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} can not be damaged with damage class {damageType}"); return; } if (!damageable.ChangeDamage(damageType, amount, ignoreResistances)) { - shell.SendText(player, $"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} received no damage."); + shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} received no damage."); return; } - shell.SendText(player, $"Damaged entity {damageable.Owner.Name} with id {damageable.Owner.Uid} for {amount} {damageType} damage{(ignoreResistances ? ", ignoring resistances." : ".")}"); + shell.WriteLine($"Damaged entity {damageable.Owner.Name} with id {damageable.Owner.Uid} for {amount} {damageType} damage{(ignoreResistances ? ", ignoring resistances." : ".")}"); }; return true; } else { - shell.SendText(player, $"{args[0]} is not a valid damage class or type."); + shell.WriteLine($"{args[0]} is not a valid damage class or type."); var types = DamageTypes(); - shell.SendText(player, types); + shell.WriteLine(types); func = null; return false; } } - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; bool ignoreResistances; IEntity entity; Damage? damageFunc; @@ -172,12 +172,12 @@ namespace Content.Server.Commands types = types.Replace('e', 'é'); } - shell.SendText(player, types); + shell.WriteLine(types); return; // Not enough args case var n when n < 2: - shell.SendText(player, $"Invalid number of arguments ({args.Length}).\n{Help}"); + shell.WriteLine($"Invalid number of arguments ({args.Length}).\n{Help}"); return; case var n when n >= 2 && n <= 4: if (!TryParseDamageArgs(shell, player, args, out damageFunc)) @@ -198,7 +198,7 @@ namespace Content.Server.Commands { if (!bool.TryParse(args[3], out ignoreResistances)) { - shell.SendText(player, $"{args[3]} is not a valid boolean value for ignoreResistances.\n{Help}"); + shell.WriteLine($"{args[3]} is not a valid boolean value for ignoreResistances.\n{Help}"); return; } } @@ -209,13 +209,13 @@ namespace Content.Server.Commands break; default: - shell.SendText(player, $"Invalid amount of arguments ({args.Length}).\n{Help}"); + shell.WriteLine($"Invalid amount of arguments ({args.Length}).\n{Help}"); return; } if (!entity.TryGetComponent(out IDamageableComponent? damageable)) { - shell.SendText(player, $"Entity {entity.Name} with id {entity.Uid} does not have a {nameof(IDamageableComponent)}."); + shell.WriteLine($"Entity {entity.Name} with id {entity.Uid} does not have a {nameof(IDamageableComponent)}."); return; } diff --git a/Content.Server/Commands/Interactable/AnchorCommand.cs b/Content.Server/Commands/Interactable/AnchorCommand.cs index c84637ebb9..1d891fbb43 100644 --- a/Content.Server/Commands/Interactable/AnchorCommand.cs +++ b/Content.Server/Commands/Interactable/AnchorCommand.cs @@ -1,24 +1,25 @@ -#nullable enable +#nullable enable using System.Linq; using Content.Server.Administration; using Content.Server.GameObjects.Components; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Interactable { [AdminCommand(AdminFlags.Debug)] - class AnchorCommand : IClientCommand + class AnchorCommand : IConsoleCommand { public string Command => "anchor"; public string Description => "Anchors all entities in a radius around the user"; public string Help => $"Usage: {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player?.AttachedEntity == null) { return; @@ -26,19 +27,19 @@ namespace Content.Server.Commands.Interactable if (args.Length != 1) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } if (!int.TryParse(args[0], out var radius)) { - shell.SendText(player, $"{args[0]} isn't a valid integer."); + shell.WriteLine($"{args[0]} isn't a valid integer."); return; } if (radius < 0) { - shell.SendText(player, "Radius must be positive."); + shell.WriteLine("Radius must be positive."); return; } diff --git a/Content.Server/Commands/Interactable/TilePryCommand.cs b/Content.Server/Commands/Interactable/TilePryCommand.cs index 06c390373b..9691736f51 100644 --- a/Content.Server/Commands/Interactable/TilePryCommand.cs +++ b/Content.Server/Commands/Interactable/TilePryCommand.cs @@ -1,10 +1,10 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.Components.Interactable; using Content.Shared.Administration; using Content.Shared.Maps; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Map; @@ -15,14 +15,15 @@ namespace Content.Server.Commands.Interactable /// /// [AdminCommand(AdminFlags.Debug)] - class TilePryCommand : IClientCommand + class TilePryCommand : IConsoleCommand { public string Command => "tilepry"; public string Description => "Pries up all tiles in a radius around the user."; public string Help => $"Usage: {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player?.AttachedEntity == null) { return; @@ -30,19 +31,19 @@ namespace Content.Server.Commands.Interactable if (args.Length != 1) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } if (!int.TryParse(args[0], out var radius)) { - shell.SendText(player, $"{args[0]} isn't a valid integer."); + shell.WriteLine($"{args[0]} isn't a valid integer."); return; } if (radius < 0) { - shell.SendText(player, "Radius must be positive."); + shell.WriteLine("Radius must be positive."); return; } diff --git a/Content.Server/Commands/Interactable/UnAnchorCommand.cs b/Content.Server/Commands/Interactable/UnAnchorCommand.cs index 3eca799b2e..5fdf8c23f0 100644 --- a/Content.Server/Commands/Interactable/UnAnchorCommand.cs +++ b/Content.Server/Commands/Interactable/UnAnchorCommand.cs @@ -1,24 +1,25 @@ -#nullable enable +#nullable enable using System.Linq; using Content.Server.Administration; using Content.Server.GameObjects.Components; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Interactable { [AdminCommand(AdminFlags.Debug)] - class UnAnchorCommand : IClientCommand + class UnAnchorCommand : IConsoleCommand { public string Command => "unanchor"; public string Description => "Unanchors all anchorable entities in a radius around the user"; public string Help => $"Usage: {Command} "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player?.AttachedEntity == null) { return; @@ -26,19 +27,19 @@ namespace Content.Server.Commands.Interactable if (args.Length != 1) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } if (!int.TryParse(args[0], out var radius)) { - shell.SendText(player, $"{args[0]} isn't a valid integer."); + shell.WriteLine($"{args[0]} isn't a valid integer."); return; } if (radius < 0) { - shell.SendText(player, "Radius must be positive."); + shell.WriteLine("Radius must be positive."); return; } diff --git a/Content.Server/Commands/MachineLinking/SignalLinkerCommand.cs b/Content.Server/Commands/MachineLinking/SignalLinkerCommand.cs index a836885fe5..d729edddff 100644 --- a/Content.Server/Commands/MachineLinking/SignalLinkerCommand.cs +++ b/Content.Server/Commands/MachineLinking/SignalLinkerCommand.cs @@ -1,15 +1,15 @@ -using Content.Server.Administration; +using Content.Server.Administration; using Content.Server.GameObjects.EntitySystems; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; namespace Content.Server.Commands.MachineLinking { [AdminCommand(AdminFlags.Debug)] - public class SignalLinkerCommand : IClientCommand + public class SignalLinkerCommand : IConsoleCommand { public string Command => "signallink"; @@ -17,8 +17,9 @@ namespace Content.Server.Commands.MachineLinking public string Help => "signallink (on/off)"; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; bool? enable = null; if (args.Length > 0) { @@ -43,7 +44,7 @@ namespace Content.Server.Commands.MachineLinking } var ret = system.SignalLinkerKeybind(player.UserId, enable); - shell.SendText(player, ret ? "Enabled" : "Disabled"); + shell.WriteLine(ret ? "Enabled" : "Disabled"); } } -} \ No newline at end of file +} diff --git a/Content.Server/Commands/MakeSentientCommand.cs b/Content.Server/Commands/MakeSentientCommand.cs index 53197b3908..aa9c99cdb0 100644 --- a/Content.Server/Commands/MakeSentientCommand.cs +++ b/Content.Server/Commands/MakeSentientCommand.cs @@ -3,8 +3,8 @@ using Content.Server.Administration; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Movement; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -12,23 +12,23 @@ using Robust.Shared.IoC; namespace Content.Server.Commands { [AdminCommand(AdminFlags.Fun)] - public class MakeSentientCommand : IClientCommand + public class MakeSentientCommand : IConsoleCommand { public string Command => "makesentient"; public string Description => "Makes an entity sentient (able to be controlled by a player)"; public string Help => "makesentient "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 1) { - shell.SendText(player, "Wrong number of arguments."); + shell.WriteLine("Wrong number of arguments."); return; } if (!int.TryParse(args[0], out var id)) { - shell.SendText(player, "Invalid argument."); + shell.WriteLine("Invalid argument."); return; } @@ -38,7 +38,7 @@ namespace Content.Server.Commands if (!entityManager.TryGetEntity(entId, out var entity) || entity.Deleted) { - shell.SendText(player, "Invalid entity specified!"); + shell.WriteLine("Invalid entity specified!"); return; } diff --git a/Content.Server/Commands/Mobs/AddRoleCommand.cs b/Content.Server/Commands/Mobs/AddRoleCommand.cs index af714411f1..eedec5dfbc 100644 --- a/Content.Server/Commands/Mobs/AddRoleCommand.cs +++ b/Content.Server/Commands/Mobs/AddRoleCommand.cs @@ -3,15 +3,15 @@ using Content.Server.Mobs.Roles; using Content.Server.Players; using Content.Shared.Administration; using Content.Shared.Roles; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; using Robust.Shared.Prototypes; namespace Content.Server.Commands.Mobs { [AdminCommand(AdminFlags.Fun)] - public class AddRoleCommand : IClientCommand + public class AddRoleCommand : IConsoleCommand { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -21,11 +21,11 @@ namespace Content.Server.Commands.Mobs public string Help => "addrole \nThat role type is the actual C# type name."; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 2) { - shell.SendText(player, "Expected exactly 2 arguments."); + shell.WriteLine("Expected exactly 2 arguments."); return; } @@ -38,7 +38,7 @@ namespace Content.Server.Commands.Mobs } else { - shell.SendText(player, "Can't find that mind"); + shell.WriteLine("Can't find that mind"); } } } diff --git a/Content.Server/Commands/Mobs/MindInfoCommand.cs b/Content.Server/Commands/Mobs/MindInfoCommand.cs index 9a546b7ce9..520fdde43a 100644 --- a/Content.Server/Commands/Mobs/MindInfoCommand.cs +++ b/Content.Server/Commands/Mobs/MindInfoCommand.cs @@ -2,14 +2,14 @@ using Content.Server.Administration; using Content.Server.Players; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Mobs { [AdminCommand(AdminFlags.Admin)] - public class MindInfoCommand : IClientCommand + public class MindInfoCommand : IConsoleCommand { public string Command => "mindinfo"; @@ -17,11 +17,11 @@ namespace Content.Server.Commands.Mobs public string Help => "mindinfo "; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 1) { - shell.SendText(player, "Expected exactly 1 argument."); + shell.WriteLine("Expected exactly 1 argument."); return; } @@ -37,11 +37,11 @@ namespace Content.Server.Commands.Mobs builder.AppendFormat("{0} ", role.Name); } - shell.SendText(player, builder.ToString()); + shell.WriteLine(builder.ToString()); } else { - shell.SendText(player, "Can't find that mind"); + shell.WriteLine("Can't find that mind"); } } } diff --git a/Content.Server/Commands/Mobs/RemoveRoleCommand.cs b/Content.Server/Commands/Mobs/RemoveRoleCommand.cs index 691082f849..11763ea74e 100644 --- a/Content.Server/Commands/Mobs/RemoveRoleCommand.cs +++ b/Content.Server/Commands/Mobs/RemoveRoleCommand.cs @@ -3,15 +3,15 @@ using Content.Server.Mobs.Roles; using Content.Server.Players; using Content.Shared.Administration; using Content.Shared.Roles; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; using Robust.Shared.Prototypes; namespace Content.Server.Commands.Mobs { [AdminCommand(AdminFlags.Fun)] - public class RemoveRoleCommand : IClientCommand + public class RemoveRoleCommand : IConsoleCommand { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -21,11 +21,11 @@ namespace Content.Server.Commands.Mobs public string Help => "rmrole \nThat role type is the actual C# type name."; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 2) { - shell.SendText(player, "Expected exactly 2 arguments."); + shell.WriteLine("Expected exactly 2 arguments."); return; } @@ -38,7 +38,7 @@ namespace Content.Server.Commands.Mobs } else { - shell.SendText(player, "Can't find that mind"); + shell.WriteLine("Can't find that mind"); } } } diff --git a/Content.Server/Commands/Notify/PopupMsgCommand.cs b/Content.Server/Commands/Notify/PopupMsgCommand.cs index a86e07e217..524439e34e 100644 --- a/Content.Server/Commands/Notify/PopupMsgCommand.cs +++ b/Content.Server/Commands/Notify/PopupMsgCommand.cs @@ -1,8 +1,8 @@ using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.Interfaces; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -10,13 +10,13 @@ using Robust.Shared.IoC; namespace Content.Server.Commands.Notify { [AdminCommand(AdminFlags.Debug)] - public class PopupMsgCommand : IClientCommand + public class PopupMsgCommand : IConsoleCommand { public string Command => "srvpopupmsg"; public string Description => ""; public string Help => ""; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var entityMgr = IoCManager.Resolve(); diff --git a/Content.Server/Commands/Objectives/AddObjectiveCommand.cs b/Content.Server/Commands/Objectives/AddObjectiveCommand.cs index 233a2d1650..24785024e3 100644 --- a/Content.Server/Commands/Objectives/AddObjectiveCommand.cs +++ b/Content.Server/Commands/Objectives/AddObjectiveCommand.cs @@ -3,31 +3,31 @@ using Content.Server.Administration; using Content.Server.Objectives; using Content.Server.Players; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; using Robust.Shared.Prototypes; namespace Content.Server.Commands.Objectives { [AdminCommand(AdminFlags.Admin)] - public class AddObjectiveCommand : IClientCommand + public class AddObjectiveCommand : IConsoleCommand { public string Command => "addobjective"; public string Description => "Adds an objective to the player's mind."; public string Help => "addobjective "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 2) { - shell.SendText(player, "Expected exactly 2 arguments."); + shell.WriteLine("Expected exactly 2 arguments."); return; } var mgr = IoCManager.Resolve(); if (!mgr.TryGetPlayerDataByUsername(args[0], out var data)) { - shell.SendText(player, "Can't find the playerdata."); + shell.WriteLine("Can't find the playerdata."); return; } @@ -35,20 +35,20 @@ namespace Content.Server.Commands.Objectives var mind = data.ContentData()?.Mind; if (mind == null) { - shell.SendText(player, "Can't find the mind."); + shell.WriteLine("Can't find the mind."); return; } if (!IoCManager.Resolve() .TryIndex(args[1], out var objectivePrototype)) { - shell.SendText(player, $"Can't find matching ObjectivePrototype {objectivePrototype}"); + shell.WriteLine($"Can't find matching ObjectivePrototype {objectivePrototype}"); return; } if (!mind.TryAddObjective(objectivePrototype)) { - shell.SendText(player, "Objective requirements dont allow that objective to be added."); + shell.WriteLine("Objective requirements dont allow that objective to be added."); } } diff --git a/Content.Server/Commands/Objectives/ListObjectivesCommand.cs b/Content.Server/Commands/Objectives/ListObjectivesCommand.cs index c7b4338a14..536b4e536f 100644 --- a/Content.Server/Commands/Objectives/ListObjectivesCommand.cs +++ b/Content.Server/Commands/Objectives/ListObjectivesCommand.cs @@ -1,22 +1,23 @@ -#nullable enable +#nullable enable using System.Linq; using Content.Server.Administration; using Content.Server.Players; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Objectives { [AdminCommand(AdminFlags.Admin)] - public class ListObjectivesCommand : IClientCommand + public class ListObjectivesCommand : IConsoleCommand { public string Command => "lsobjectives"; public string Description => "Lists all objectives in a players mind."; public string Help => "lsobjectives []"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; IPlayerData? data; if (args.Length == 0 && player != null) { @@ -24,26 +25,26 @@ namespace Content.Server.Commands.Objectives } else if (player == null || !IoCManager.Resolve().TryGetPlayerDataByUsername(args[0], out data)) { - shell.SendText(player, "Can't find the playerdata."); + shell.WriteLine("Can't find the playerdata."); return; } var mind = data.ContentData()?.Mind; if (mind == null) { - shell.SendText(player, "Can't find the mind."); + shell.WriteLine("Can't find the mind."); return; } - shell.SendText(player, $"Objectives for player {data.UserId}:"); + shell.WriteLine($"Objectives for player {data.UserId}:"); var objectives = mind.AllObjectives.ToList(); if (objectives.Count == 0) { - shell.SendText(player, "None."); + shell.WriteLine("None."); } for (var i = 0; i < objectives.Count; i++) { - shell.SendText(player, $"- [{i}] {objectives[i]}"); + shell.WriteLine($"- [{i}] {objectives[i]}"); } } diff --git a/Content.Server/Commands/Objectives/RemoveObjectiveCommand.cs b/Content.Server/Commands/Objectives/RemoveObjectiveCommand.cs index 2fcc87f646..988090c25d 100644 --- a/Content.Server/Commands/Objectives/RemoveObjectiveCommand.cs +++ b/Content.Server/Commands/Objectives/RemoveObjectiveCommand.cs @@ -2,23 +2,23 @@ using Content.Server.Administration; using Content.Server.Players; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.IoC; namespace Content.Server.Commands.Objectives { [AdminCommand(AdminFlags.Admin)] - public class RemoveObjectiveCommand : IClientCommand + public class RemoveObjectiveCommand : IConsoleCommand { public string Command => "rmobjective"; public string Description => "Removes an objective from the player's mind."; public string Help => "rmobjective "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 2) { - shell.SendText(player, "Expected exactly 2 arguments."); + shell.WriteLine("Expected exactly 2 arguments."); return; } @@ -28,25 +28,24 @@ namespace Content.Server.Commands.Objectives var mind = data.ContentData()?.Mind; if (mind == null) { - shell.SendText(player, "Can't find the mind."); + shell.WriteLine("Can't find the mind."); return; } if (int.TryParse(args[1], out var i)) { - shell.SendText(player, - mind.TryRemoveObjective(i) - ? "Objective successfully removed!" - : "Objective removing failed. Maybe the index is out of bounds? Check lsobjectives!"); + shell.WriteLine(mind.TryRemoveObjective(i) + ? "Objective successfully removed!" + : "Objective removing failed. Maybe the index is out of bounds? Check lsobjectives!"); } else { - shell.SendText(player, $"Invalid index {args[1]}!"); + shell.WriteLine($"Invalid index {args[1]}!"); } } else { - shell.SendText(player, "Can't find the playerdata."); + shell.WriteLine("Can't find the playerdata."); } } } diff --git a/Content.Server/Commands/Observer/Ghost.cs b/Content.Server/Commands/Observer/Ghost.cs index 6aa887dd47..18be076486 100644 --- a/Content.Server/Commands/Observer/Ghost.cs +++ b/Content.Server/Commands/Observer/Ghost.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Observer; @@ -8,39 +8,40 @@ using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Mobs.State; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; namespace Content.Server.Commands.Observer { [AnyCommand] - public class Ghost : IClientCommand + public class Ghost : IConsoleCommand { public string Command => "ghost"; public string Description => "Give up on life and become a ghost."; public string Help => "ghost"; public bool CanReturn { get; set; } = true; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell?.SendText(player, "You have no session, you can't ghost."); + shell?.WriteLine("You have no session, you can't ghost."); return; } var mind = player.ContentData()?.Mind; if (mind == null) { - shell?.SendText(player, "You have no Mind, you can't ghost."); + shell?.WriteLine("You have no Mind, you can't ghost."); return; } if (!IoCManager.Resolve().OnGhostAttempt(mind, CanReturn)) { - shell?.SendText(player, "You can't ghost right now."); + shell?.WriteLine("You can't ghost right now."); return; } } diff --git a/Content.Server/Commands/RemoveExtraComponents.cs b/Content.Server/Commands/RemoveExtraComponents.cs index b5b269fb83..d2bb8bb2ce 100644 --- a/Content.Server/Commands/RemoveExtraComponents.cs +++ b/Content.Server/Commands/RemoveExtraComponents.cs @@ -1,8 +1,8 @@ #nullable enable using Content.Server.Administration; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -11,12 +11,12 @@ using Robust.Shared.Prototypes; namespace Content.Server.Commands { [AdminCommand(AdminFlags.Mapping)] - public class RemoveExtraComponents : IClientCommand + public class RemoveExtraComponents : IConsoleCommand { public string Command => "removeextracomponents"; public string Description => "Removes all components from all entities of the specified id if that component is not in its prototype.\nIf no id is specified, it matches all entities."; public string Help => $"{Command} / {Command}"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { var id = args.Length == 0 ? null : string.Join(" ", args); var entityManager = IoCManager.Resolve(); @@ -32,7 +32,7 @@ namespace Content.Server.Commands { if (!prototypeManager.TryIndex(id, out EntityPrototype prototype)) { - shell.SendText(player, $"No entity prototype found with id {id}."); + shell.WriteLine($"No entity prototype found with id {id}."); return; } @@ -68,7 +68,7 @@ namespace Content.Server.Commands } } - shell.SendText(player, $"Removed {components} components from {entities} entities{(id == null ? "." : $" with id {id}")}"); + shell.WriteLine($"Removed {components} components from {entities} entities{(id == null ? "." : $" with id {id}")}"); } } } diff --git a/Content.Server/Commands/SetAnchorCommand.cs b/Content.Server/Commands/SetAnchorCommand.cs index 532458d55e..3dc2a16cf5 100644 --- a/Content.Server/Commands/SetAnchorCommand.cs +++ b/Content.Server/Commands/SetAnchorCommand.cs @@ -1,8 +1,8 @@ #nullable enable using Content.Server.Administration; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; @@ -11,22 +11,22 @@ using Robust.Shared.IoC; namespace Content.Server.Commands { [AdminCommand(AdminFlags.Debug)] - public class SetAnchorCommand : IClientCommand + public class SetAnchorCommand : IConsoleCommand { public string Command => "setanchor"; public string Description => "Sets the anchoring state of an entity."; public string Help => "setanchor "; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length == 0 || args.Length > 2) { - shell.SendText(player, "Invalid number of argument!"); + shell.WriteLine("Invalid number of argument!"); return; } if (!int.TryParse(args[0], out var id)) { - shell.SendText(player, "Invalid argument specified!"); + shell.WriteLine("Invalid argument specified!"); return; } @@ -36,7 +36,7 @@ namespace Content.Server.Commands if (!entityManager.TryGetEntity(entId, out var entity) || entity.Deleted || !entity.TryGetComponent(out PhysicsComponent? physics)) { - shell.SendText(player, "Invalid entity specified!"); + shell.WriteLine("Invalid entity specified!"); return; } @@ -44,7 +44,7 @@ namespace Content.Server.Commands { if (!bool.TryParse(args[1], out var value)) { - shell.SendText(player, "Invalid argument specified!"); + shell.WriteLine("Invalid argument specified!"); return; } diff --git a/Content.Server/Commands/ShowContainedContextCommand.cs b/Content.Server/Commands/ShowContainedContextCommand.cs index 8afcc5501b..fc93c08a7f 100644 --- a/Content.Server/Commands/ShowContainedContextCommand.cs +++ b/Content.Server/Commands/ShowContainedContextCommand.cs @@ -1,15 +1,15 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.EntitySystems; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects.Systems; namespace Content.Server.Commands { [AdminCommand(AdminFlags.Debug)] - public class ShowContainedContextCommand : IClientCommand + public class ShowContainedContextCommand : IConsoleCommand { public const string CommandName = "showcontainedcontext"; @@ -18,11 +18,12 @@ namespace Content.Server.Commands public string Description => "Makes contained entities visible on the context menu, even when they shouldn't be."; public string Help => $"{Command}"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player == null) { - shell.SendText(player, "You need to be a player to use this command."); + shell.WriteLine("You need to be a player to use this command."); return; } diff --git a/Content.Server/Commands/Speech/AddAccent.cs b/Content.Server/Commands/Speech/AddAccent.cs index 530049c124..df410a8248 100644 --- a/Content.Server/Commands/Speech/AddAccent.cs +++ b/Content.Server/Commands/Speech/AddAccent.cs @@ -1,34 +1,35 @@ -#nullable enable +#nullable enable using System; using System.Linq; using Content.Server.Administration; using Content.Server.GameObjects.Components.Mobs.Speech; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; namespace Content.Server.Commands.Speech { [AdminCommand(AdminFlags.Fun)] - public class AddAccent : IClientCommand + public class AddAccent : IConsoleCommand { 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) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (player?.AttachedEntity == null) { - shell.SendText(player, "You don't have an entity!"); + shell.WriteLine("You don't have an entity!"); return; } if (args.Length == 0) { - shell.SendText(player, Help); + shell.WriteLine(Help); return; } @@ -44,7 +45,7 @@ namespace Content.Server.Commands.Speech { msg += $"{compFactory.GetRegistration(s).Name}\n"; } - shell.SendText(player, msg); + shell.WriteLine(msg); } else { @@ -58,7 +59,7 @@ namespace Content.Server.Commands.Speech } catch (Exception) { - shell.SendText(player, $"Accent {name} not found. Try {Command} ? to get a list of all appliable accents."); + shell.WriteLine($"Accent {name} not found. Try {Command} ? to get a list of all appliable accents."); return; } @@ -66,7 +67,7 @@ namespace Content.Server.Commands.Speech try { var comp = player.AttachedEntity.GetComponent(type); - shell.SendText(player, "You already have this accent!"); + shell.WriteLine("You already have this accent!"); return; } catch (Exception) diff --git a/Content.Server/Commands/StartSingularityEngineCommand.cs b/Content.Server/Commands/StartSingularityEngineCommand.cs index 1a0b703434..4a9695fae4 100644 --- a/Content.Server/Commands/StartSingularityEngineCommand.cs +++ b/Content.Server/Commands/StartSingularityEngineCommand.cs @@ -6,8 +6,8 @@ using Content.Server.GameObjects.Components.Singularity; using Content.Server.GameObjects.Components.PA; using Content.Shared.Administration; using Content.Shared.GameObjects.Components; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -15,17 +15,17 @@ using Robust.Shared.IoC; namespace Content.Server.Commands { [AdminCommand(AdminFlags.Admin)] - public class StartSingularityEngineCommand : IClientCommand + public class StartSingularityEngineCommand : IConsoleCommand { public string Command => "startsingularityengine"; public string Description => "Automatically turns on the particle accelerator and containment field emitters."; public string Help => $"{Command}"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 0) { - shell.SendText(player, $"Invalid amount of arguments: {args.Length}.\n{Help}"); + shell.WriteLine($"Invalid amount of arguments: {args.Length}.\n{Help}"); return; } @@ -41,7 +41,7 @@ namespace Content.Server.Commands pacb.SetStrength(ParticleAcceleratorPowerState.Level1); pacb.SwitchOn(); } - shell.SendText(player, "Done!"); + shell.WriteLine("Done!"); } } } diff --git a/Content.Server/Commands/StationEvents/StationEventCommand.cs b/Content.Server/Commands/StationEvents/StationEventCommand.cs index 95c8b910fd..99f28ce5a0 100644 --- a/Content.Server/Commands/StationEvents/StationEventCommand.cs +++ b/Content.Server/Commands/StationEvents/StationEventCommand.cs @@ -1,16 +1,16 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.GameObjects.EntitySystems.StationEvents; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Localization; namespace Content.Server.Commands.StationEvents { [AdminCommand(AdminFlags.Server)] - public sealed class StationEventCommand : IClientCommand + public sealed class StationEventCommand : IConsoleCommand { public string Command => "events"; public string Description => "Provides admin control to station events"; @@ -27,11 +27,12 @@ namespace Content.Server.Commands.StationEvents private const string RunHelp = "run : start a particular event now; is case-insensitive and not localized"; - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { + var player = shell.Player as IPlayerSession; if (args.Length == 0) { - shell.SendText(player, $"Invalid amount of arguments.\n{Help}"); + shell.WriteLine($"Invalid amount of arguments.\n{Help}"); return; } @@ -56,14 +57,14 @@ namespace Content.Server.Commands.StationEvents case "run": if (args.Length != 2) { - shell.SendText(player, $"Need 2 arguments, there were {args.Length}.\n{RunHelp}"); + shell.WriteLine($"Need 2 arguments, there were {args.Length}.\n{RunHelp}"); break; } Run(shell, player, args[1]); break; default: - shell.SendText(player, Loc.GetString($"Invalid events command.\n{Help}")); + shell.WriteLine(Loc.GetString($"Invalid events command.\n{Help}")); break; } } @@ -76,7 +77,7 @@ namespace Content.Server.Commands.StationEvents ? stationSystem.RunRandomEvent() : stationSystem.RunEvent(eventName); - shell.SendText(player, resultText); + shell.WriteLine(resultText); } private void Running(IConsoleShell shell, IPlayerSession? player) @@ -84,18 +85,18 @@ namespace Content.Server.Commands.StationEvents var eventName = EntitySystem.Get().CurrentEvent?.Name; if (!string.IsNullOrEmpty(eventName)) { - shell.SendText(player, eventName); + shell.WriteLine(eventName); } else { - shell.SendText(player, Loc.GetString("No station event running")); + shell.WriteLine(Loc.GetString("No station event running")); } } private void List(IConsoleShell shell, IPlayerSession? player) { var resultText = "Random\n" + EntitySystem.Get().GetEventNames(); - shell.SendText(player, resultText); + shell.WriteLine(resultText); } private void Pause(IConsoleShell shell, IPlayerSession? player) @@ -104,12 +105,12 @@ namespace Content.Server.Commands.StationEvents if (!stationEventSystem.Enabled) { - shell.SendText(player, Loc.GetString("Station events are already paused")); + shell.WriteLine(Loc.GetString("Station events are already paused")); } else { stationEventSystem.Enabled = false; - shell.SendText(player, Loc.GetString("Station events paused")); + shell.WriteLine(Loc.GetString("Station events paused")); } } @@ -119,19 +120,19 @@ namespace Content.Server.Commands.StationEvents if (stationEventSystem.Enabled) { - shell.SendText(player, Loc.GetString("Station events are already running")); + shell.WriteLine(Loc.GetString("Station events are already running")); } else { stationEventSystem.Enabled = true; - shell.SendText(player, Loc.GetString("Station events resumed")); + shell.WriteLine(Loc.GetString("Station events resumed")); } } private void Stop(IConsoleShell shell, IPlayerSession? player) { var resultText = EntitySystem.Get().StopEvent(); - shell.SendText(player, resultText); + shell.WriteLine(resultText); } } } diff --git a/Content.Server/GameObjects/Components/Body/BodyComponent.cs b/Content.Server/GameObjects/Components/Body/BodyComponent.cs index 000832af1d..8f9eea9aac 100644 --- a/Content.Server/GameObjects/Components/Body/BodyComponent.cs +++ b/Content.Server/GameObjects/Components/Body/BodyComponent.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using Content.Server.Commands.Observer; using Content.Shared.Audio; @@ -8,11 +8,12 @@ using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Mobs.State; using Content.Shared.GameObjects.Components.Movement; using Content.Shared.Utility; +using Robust.Server.Console; using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.EntitySystems; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.Audio; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.IoC; @@ -89,9 +90,9 @@ namespace Content.Server.GameObjects.Components.Body if (Owner.TryGetComponent(out IMobStateComponent? mobState) && mobState.IsDead()) { - var shell = IoCManager.Resolve(); + var host = IoCManager.Resolve(); - new Ghost().Execute(shell, (IPlayerSession) session, Array.Empty()); + new Ghost().Execute(new ConsoleShell(host, session), string.Empty, Array.Empty()); } } diff --git a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs index 3527e9fcfb..b402e587ae 100644 --- a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Content.Server.Administration.Commands; @@ -15,9 +15,9 @@ using Content.Shared.GameObjects.Verbs; using Content.Shared.Interfaces; using Robust.Server.Console; using Robust.Server.GameObjects.Components.Container; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Player; +using Robust.Shared.Console; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; @@ -686,9 +686,10 @@ namespace Content.Server.GameObjects.Components.GUI var entityId = target.Uid.ToString(); var command = new SetOutfitCommand(); - var shell = IoCManager.Resolve(); + var host = IoCManager.Resolve(); var args = new string[] {entityId}; - command.Execute(shell, user.PlayerSession(), args); + var session = user.PlayerSession(); + command.Execute(new ConsoleShell(host, session), $"{command.Command} {entityId}", args); } private static bool CanCommand(IEntity user) diff --git a/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs index 36b56d93f3..d069a9a0f5 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs @@ -10,7 +10,6 @@ using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Movement; using JetBrains.Annotations; using Robust.Server.AI; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs index ff61818fd9..478a3953b8 100644 --- a/Content.Server/GameTicking/GamePreset.cs +++ b/Content.Server/GameTicking/GamePreset.cs @@ -13,7 +13,6 @@ using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Mobs.State; using Robust.Shared.Network; using Robust.Shared.Interfaces.GameObjects; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.IoC; diff --git a/Content.Server/GlobalVerbs/MakeSentientVerb.cs b/Content.Server/GlobalVerbs/MakeSentientVerb.cs index 5206e89264..ff040dbb9b 100644 --- a/Content.Server/GlobalVerbs/MakeSentientVerb.cs +++ b/Content.Server/GlobalVerbs/MakeSentientVerb.cs @@ -2,8 +2,8 @@ using Content.Server.Commands; using Content.Server.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Verbs; using Robust.Server.Console; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; @@ -42,8 +42,11 @@ namespace Content.Server.GlobalVerbs if (!groupController.CanCommand(player, "makesentient")) return; - new MakeSentientCommand().Execute(IoCManager.Resolve(), player, - new[] {target.Uid.ToString()}); + var host = IoCManager.Resolve(); + var cmd = new MakeSentientCommand(); + var uidStr = target.Uid.ToString(); + cmd.Execute(new ConsoleShell(host, player), $"{cmd.Command} {uidStr}", + new[] {uidStr}); } } } diff --git a/Content.Server/Interfaces/Chat/IChatCommand.cs b/Content.Server/Interfaces/Chat/IChatCommand.cs deleted file mode 100644 index 09fff49d85..0000000000 --- a/Content.Server/Interfaces/Chat/IChatCommand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Robust.Shared.Console; -using Robust.Shared.Interfaces.Network; - -namespace Content.Server.Interfaces.Chat -{ - public interface IChatCommand : ICommand - { - void Execute(IChatManager manager, INetChannel client, params string[] args); - } -} diff --git a/Content.Server/Interfaces/GameTicking/IGameTicker.cs b/Content.Server/Interfaces/GameTicking/IGameTicker.cs index 02073511f1..0e84bf73cd 100644 --- a/Content.Server/Interfaces/GameTicking/IGameTicker.cs +++ b/Content.Server/Interfaces/GameTicking/IGameTicker.cs @@ -5,7 +5,6 @@ using Content.Server.Mobs; using Content.Shared.Roles; using Content.Shared.Preferences; using Robust.Server.Interfaces.Player; -using Robust.Server.Interfaces.Console; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Map; using Robust.Shared.Timing; diff --git a/Content.Server/Sandbox/SandboxManager.cs b/Content.Server/Sandbox/SandboxManager.cs index 4516b17a00..4bdc3a366a 100644 --- a/Content.Server/Sandbox/SandboxManager.cs +++ b/Content.Server/Sandbox/SandboxManager.cs @@ -9,7 +9,6 @@ using Content.Shared.Access; using Content.Shared.Sandbox; using Robust.Server.Console; using Robust.Server.GameObjects; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Placement; using Robust.Server.Interfaces.Player; using Robust.Server.Player; @@ -31,7 +30,7 @@ namespace Content.Server.Sandbox [Dependency] private readonly IPlacementManager _placementManager = default!; [Dependency] private readonly IConGroupController _conGroupController = default!; [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IConsoleShell _shell = default!; + [Dependency] private readonly IServerConsoleHost _host = default!; private bool _isSandboxEnabled; @@ -183,7 +182,7 @@ namespace Content.Server.Sandbox var player = _playerManager.GetSessionByChannel(message.MsgChannel); - _shell.ExecuteCommand(player, _conGroupController.CanCommand(player, "aghost") ? "aghost" : "ghost"); + _host.ExecuteCommand(player, _conGroupController.CanCommand(player, "aghost") ? "aghost" : "ghost"); } private void SandboxSuicideReceived(MsgSandboxSuicide message) @@ -194,7 +193,7 @@ namespace Content.Server.Sandbox } var player = _playerManager.GetSessionByChannel(message.MsgChannel); - _shell.ExecuteCommand(player, "suicide"); + _host.ExecuteCommand(player, "suicide"); } private void UpdateSandboxStatusForAll() diff --git a/Content.Server/ServerNotifyManager.cs b/Content.Server/ServerNotifyManager.cs index 8dd2d978f3..9c00fa92e1 100644 --- a/Content.Server/ServerNotifyManager.cs +++ b/Content.Server/ServerNotifyManager.cs @@ -4,7 +4,6 @@ using Content.Shared; using Content.Shared.Network.NetMessages; using Content.Shared.Administration; using Content.Shared.Interfaces; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; diff --git a/RobustToolbox b/RobustToolbox index 3e5efd5ed0..3eb6e067f9 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 3e5efd5ed06d85d4c874d6369c26569a67e3242a +Subproject commit 3eb6e067f9dc0155cf2490fe4adeb6e03e42412c From 8dea5328006e695ebbf15402ee993425fd9a40f4 Mon Sep 17 00:00:00 2001 From: tmtmtl30 <53132901+tmtmtl30@users.noreply.github.com> Date: Mon, 1 Feb 2021 17:02:42 -0800 Subject: [PATCH 41/61] removes collision from beds (#3038) * removes collision from beds * Updates bed collision mask. --- .../Prototypes/Entities/Constructible/Furniture/beds.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Resources/Prototypes/Entities/Constructible/Furniture/beds.yml b/Resources/Prototypes/Entities/Constructible/Furniture/beds.yml index 173ef58fd1..33ed616d29 100644 --- a/Resources/Prototypes/Entities/Constructible/Furniture/beds.yml +++ b/Resources/Prototypes/Entities/Constructible/Furniture/beds.yml @@ -13,12 +13,6 @@ bounds: "-0.45, -0.45, 0.05, 0.45" mask: - Impassable - - MobImpassable - - VaultImpassable - - SmallImpassable - layer: - - Opaque - - MobImpassable - VaultImpassable - SmallImpassable - type: Sprite From b0482dcb6387c2d2edfba603647b12f62768a906 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Tue, 2 Feb 2021 02:11:04 +0100 Subject: [PATCH 42/61] Add tag component and test (#2761) * Add tag component and test * Remove 0 capacity * Add tag component extensions * Change tags to be prototypes Co-authored-by: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> --- Content.Client/IgnoredComponents.cs | 1 + Content.IntegrationTests/Tests/Tag/TagTest.cs | 225 ++++++++++++++++ .../Components/Tag/TagComponent.cs | 251 ++++++++++++++++++ .../Components/Tag/TagComponentExtensions.cs | 243 +++++++++++++++++ Content.Shared/Prototypes/Tag/TagPrototype.cs | 31 +++ 5 files changed, 751 insertions(+) create mode 100644 Content.IntegrationTests/Tests/Tag/TagTest.cs create mode 100644 Content.Server/GameObjects/Components/Tag/TagComponent.cs create mode 100644 Content.Server/GameObjects/Components/Tag/TagComponentExtensions.cs create mode 100644 Content.Shared/Prototypes/Tag/TagPrototype.cs diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 101ce6c852..652a75edee 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -228,6 +228,7 @@ namespace Content.Client "MachineFrame", "MachineBoard", "ChemicalAmmo", + "Tag", "BiologicalSurgeryData", "CargoTelepad", "TraitorDeathMatchRedemption", diff --git a/Content.IntegrationTests/Tests/Tag/TagTest.cs b/Content.IntegrationTests/Tests/Tag/TagTest.cs new file mode 100644 index 0000000000..3159f38720 --- /dev/null +++ b/Content.IntegrationTests/Tests/Tag/TagTest.cs @@ -0,0 +1,225 @@ +#nullable enable +using System.Collections.Generic; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Tag; +using Content.Shared.Prototypes.Tag; +using NUnit.Framework; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.Tag +{ + [TestFixture] + [TestOf(typeof(TagComponent))] + public class TagTest : ContentIntegrationTest + { + private const string TagEntityId = "TagTestDummy"; + + // Register these three into the prototype manager + private const string StartingTag = "A"; + private const string AddedTag = "EIOU"; + private const string UnusedTag = "E"; + + // Do not register this one + private const string UnregisteredTag = "AAAAAAAAA"; + + private static readonly string Prototypes = $@" +- type: Tag + id: {StartingTag} + +- type: Tag + id: {AddedTag} + +- type: Tag + id: {UnusedTag} + +- type: entity + id: {TagEntityId} + name: {TagEntityId} + components: + - type: Tag + tags: + - {StartingTag}"; + + [Test] + public async Task TagComponentTest() + { + var options = new ServerContentIntegrationOption {ExtraPrototypes = Prototypes}; + var server = StartServerDummyTicker(options); + + await server.WaitIdleAsync(); + + var sMapManager = server.ResolveDependency(); + var sEntityManager = server.ResolveDependency(); + var sPrototypeManager = server.ResolveDependency(); + + IEntity sTagDummy = null!; + TagComponent sTagComponent = null!; + + await server.WaitPost(() => + { + sMapManager.CreateNewMapEntity(MapId.Nullspace); + sTagDummy = sEntityManager.SpawnEntity(TagEntityId, MapCoordinates.Nullspace); + sTagComponent = sTagDummy.GetComponent(); + }); + + await server.WaitAssertion(() => + { + // Has one tag, the starting tag + Assert.That(sTagComponent.Tags.Count, Is.EqualTo(1)); + + var startingTagPrototype = sPrototypeManager.Index(StartingTag); + Assert.That(sTagComponent.Tags, Contains.Item(startingTagPrototype)); + + // Single + Assert.True(sTagDummy.HasTag(StartingTag)); + Assert.True(sTagComponent.HasTag(StartingTag)); + + // Any + Assert.True(sTagDummy.HasAnyTag(StartingTag)); + Assert.True(sTagComponent.HasAnyTag(StartingTag)); + + // All + Assert.True(sTagDummy.HasAllTags(StartingTag)); + Assert.True(sTagComponent.HasAllTags(StartingTag)); + + // Does not have the added tag + var addedTagPrototype = sPrototypeManager.Index(AddedTag); + Assert.That(sTagComponent.Tags, Does.Not.Contains(addedTagPrototype)); + + // Single + Assert.False(sTagDummy.HasTag(AddedTag)); + Assert.False(sTagComponent.HasTag(AddedTag)); + + // Any + Assert.False(sTagDummy.HasAnyTag(AddedTag)); + Assert.False(sTagComponent.HasAnyTag(AddedTag)); + + // All + Assert.False(sTagDummy.HasAllTags(AddedTag)); + Assert.False(sTagComponent.HasAllTags(AddedTag)); + + // Does not have the unused tag + var unusedTagPrototype = sPrototypeManager.Index(UnusedTag); + Assert.That(sTagComponent.Tags, Does.Not.Contains(unusedTagPrototype)); + + // Single + Assert.False(sTagDummy.HasTag(UnusedTag)); + Assert.False(sTagComponent.HasTag(UnusedTag)); + + // Any + Assert.False(sTagDummy.HasAnyTag(UnusedTag)); + Assert.False(sTagComponent.HasAnyTag(UnusedTag)); + + // All + Assert.False(sTagDummy.HasAllTags(UnusedTag)); + Assert.False(sTagComponent.HasAllTags(UnusedTag)); + + // Throws when checking for an unregistered tag + Assert.Throws(() => + { + sPrototypeManager.Index(UnregisteredTag); + }); + + // Single + Assert.Throws(() => + { + sTagDummy.HasTag(UnregisteredTag); + }); + Assert.Throws(() => + { + sTagComponent.HasTag(UnregisteredTag); + }); + + // Any + Assert.Throws(() => + { + sTagDummy.HasAnyTag(UnregisteredTag); + }); + Assert.Throws(() => + { + sTagComponent.HasAnyTag(UnregisteredTag); + }); + + // All + Assert.Throws(() => + { + sTagDummy.HasAllTags(UnregisteredTag); + }); + Assert.Throws(() => + { + sTagComponent.HasAllTags(UnregisteredTag); + }); + + // Cannot add the starting tag again + Assert.That(sTagComponent.AddTag(StartingTag), Is.False); + Assert.That(sTagComponent.AddTags(StartingTag, StartingTag), Is.False); + Assert.That(sTagComponent.AddTags(new List {StartingTag, StartingTag}), Is.False); + + // Has the starting tag + Assert.That(sTagComponent.HasTag(StartingTag), Is.True); + Assert.That(sTagComponent.HasAllTags(StartingTag, StartingTag), Is.True); + Assert.That(sTagComponent.HasAllTags(new List {StartingTag, StartingTag}), Is.True); + Assert.That(sTagComponent.HasAnyTag(StartingTag, StartingTag), Is.True); + Assert.That(sTagComponent.HasAnyTag(new List {StartingTag, StartingTag}), Is.True); + + // Does not have the added tag yet + Assert.That(sTagComponent.HasTag(AddedTag), Is.False); + Assert.That(sTagComponent.HasAllTags(AddedTag, AddedTag), Is.False); + Assert.That(sTagComponent.HasAllTags(new List {AddedTag, AddedTag}), Is.False); + Assert.That(sTagComponent.HasAnyTag(AddedTag, AddedTag), Is.False); + Assert.That(sTagComponent.HasAnyTag(new List {AddedTag, AddedTag}), Is.False); + + // Has a combination of the two tags + Assert.That(sTagComponent.HasAnyTag(StartingTag, AddedTag), Is.True); + Assert.That(sTagComponent.HasAnyTag(new List {StartingTag, AddedTag}), Is.True); + + // Does not have both tags + Assert.That(sTagComponent.HasAllTags(StartingTag, AddedTag), Is.False); + Assert.That(sTagComponent.HasAllTags(new List {StartingTag, AddedTag}), Is.False); + + // Cannot remove a tag that does not exist + Assert.That(sTagComponent.RemoveTag(AddedTag), Is.False); + Assert.That(sTagComponent.RemoveTags(AddedTag, AddedTag), Is.False); + Assert.That(sTagComponent.RemoveTags(new List {AddedTag, AddedTag}), Is.False); + + // Can add the new tag + Assert.That(sTagComponent.AddTag(AddedTag), Is.True); + + // Cannot add it twice + Assert.That(sTagComponent.AddTag(AddedTag), Is.False); + + // Cannot add existing tags + Assert.That(sTagComponent.AddTags(StartingTag, AddedTag), Is.False); + Assert.That(sTagComponent.AddTags(new List {StartingTag, AddedTag}), Is.False); + + // Now has two tags + Assert.That(sTagComponent.Tags.Count, Is.EqualTo(2)); + + // Has both tags + Assert.That(sTagComponent.HasTag(StartingTag), Is.True); + Assert.That(sTagComponent.HasTag(AddedTag), Is.True); + Assert.That(sTagComponent.HasAllTags(StartingTag, StartingTag), Is.True); + Assert.That(sTagComponent.HasAllTags(AddedTag, StartingTag), Is.True); + Assert.That(sTagComponent.HasAllTags(new List {StartingTag, AddedTag}), Is.True); + Assert.That(sTagComponent.HasAllTags(new List {AddedTag, StartingTag}), Is.True); + Assert.That(sTagComponent.HasAnyTag(StartingTag, AddedTag), Is.True); + Assert.That(sTagComponent.HasAnyTag(AddedTag, StartingTag), Is.True); + + // Remove the existing starting tag + Assert.That(sTagComponent.RemoveTag(StartingTag), Is.True); + + // Remove the existing added tag + Assert.That(sTagComponent.RemoveTags(AddedTag, AddedTag), Is.True); + + // No tags left to remove + Assert.That(sTagComponent.RemoveTags(new List {StartingTag, AddedTag}), Is.False); + + // No tags left in the component + Assert.That(sTagComponent.Tags, Is.Empty); + }); + } + } +} diff --git a/Content.Server/GameObjects/Components/Tag/TagComponent.cs b/Content.Server/GameObjects/Components/Tag/TagComponent.cs new file mode 100644 index 0000000000..4894b0c848 --- /dev/null +++ b/Content.Server/GameObjects/Components/Tag/TagComponent.cs @@ -0,0 +1,251 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.Shared.Prototypes.Tag; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Tag +{ + [RegisterComponent] + public class TagComponent : Component + { + public override string Name => "Tag"; + + [ViewVariables] + private readonly HashSet _tags = new(); + + public IReadOnlySet Tags => _tags; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataReadWriteFunction( + "tags", + null!, + (ids) => + { + _tags.Clear(); + + if (ids == null) + { + return; + } + + AddTags(ids); + }, + () => + { + var ids = new HashSet(); + + foreach (var tag in _tags) + { + ids.Add(tag.ID); + } + + return ids; + }); + } + + /// + /// Tries to add a tag if it doesn't already exist. + /// + /// The tag to add. + /// true if it was added, false if it already existed. + /// + /// Thrown if no exists with the given id. + /// + public bool AddTag(string id) + { + var tag = IoCManager.Resolve().Index(id); + + return _tags.Add(tag); + } + + /// + /// Tries to add the given tags if they don't already exist. + /// + /// The tags to add. + /// true if any tags were added, false if they all already existed. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool AddTags(params string[] ids) + { + return AddTags(ids.AsEnumerable()); + } + + /// + /// Tries to add the given tags if they don't already exist. + /// + /// The tags to add. + /// true if any tags were added, false if they all already existed. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool AddTags(IEnumerable ids) + { + var count = _tags.Count; + var prototypeManager = IoCManager.Resolve(); + + foreach (var id in ids) + { + var tag = prototypeManager.Index(id); + + _tags.Add(tag); + } + + return _tags.Count > count; + } + + /// + /// Checks if a tag has been added. + /// + /// The tag to check for. + /// true if it exists, false otherwise. + /// + /// Thrown if no exists with the given id. + /// + public bool HasTag(string id) + { + var tag = IoCManager.Resolve().Index(id); + + return _tags.Contains(tag); + } + + /// + /// Checks if all of the given tags have been added. + /// + /// The tags to check for. + /// true if they all exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(params string[] ids) + { + return HasAllTags(ids.AsEnumerable()); + } + + /// + /// Checks if all of the given tags have been added. + /// + /// The tags to check for. + /// true if they all exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(IEnumerable ids) + { + var prototypeManager = IoCManager.Resolve(); + + foreach (var id in ids) + { + var tag = prototypeManager.Index(id); + + if (!_tags.Contains(tag)) + { + return false; + } + } + + return true; + } + + /// + /// Checks if any of the given tags have been added. + /// + /// The tags to check for. + /// true if any of them exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(params string[] ids) + { + return HasAnyTag(ids.AsEnumerable()); + } + + /// + /// Checks if any of the given tags have been added. + /// + /// The tags to check for. + /// true if any of them exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(IEnumerable ids) + { + var prototypeManager = IoCManager.Resolve(); + + foreach (var id in ids) + { + var tag = prototypeManager.Index(id); + + if (_tags.Contains(tag)) + { + return true; + } + } + + return false; + } + + /// + /// Tries to remove a tag if it exists. + /// + /// The tag to remove. + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool RemoveTag(string id) + { + var tag = IoCManager.Resolve().Index(id); + + return _tags.Remove(tag); + } + + /// + /// Tries to remove all of the given tags if they exist. + /// + /// The tags to remove. + /// + /// true if it was removed, false otherwise even if they didn't exist. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool RemoveTags(params string[] ids) + { + return RemoveTags(ids.AsEnumerable()); + } + + /// + /// Tries to remove all of the given tags if they exist. + /// + /// The tags to remove. + /// true if any tag was removed, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool RemoveTags(IEnumerable ids) + { + var count = _tags.Count; + var prototypeManager = IoCManager.Resolve(); + + foreach (var id in ids) + { + var tag = prototypeManager.Index(id); + + _tags.Remove(tag); + } + + return _tags.Count < count; + } + } +} diff --git a/Content.Server/GameObjects/Components/Tag/TagComponentExtensions.cs b/Content.Server/GameObjects/Components/Tag/TagComponentExtensions.cs new file mode 100644 index 0000000000..437b70af88 --- /dev/null +++ b/Content.Server/GameObjects/Components/Tag/TagComponentExtensions.cs @@ -0,0 +1,243 @@ +#nullable enable +using System.Collections.Generic; +using Content.Shared.Prototypes.Tag; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameObjects.Components.Tag +{ + public static class TagComponentExtensions + { + /// + /// Tries to add a tag to an entity if the tag doesn't already exist. + /// + /// The entity to add the tag to. + /// The tag to add. + /// + /// true if it was added, false otherwise even if it already existed. + /// + /// + /// Thrown if no exists with the given id. + /// + public static bool AddTag(this IEntity entity, string id) + { + return entity.EnsureComponent(out TagComponent tagComponent) && + tagComponent.AddTag(id); + } + + /// + /// Tries to add the given tags to an entity if the tags don't already exist. + /// + /// The entity to add the tag to. + /// The tags to add. + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public static bool AddTags(this IEntity entity, params string[] ids) + { + return entity.EnsureComponent(out TagComponent tagComponent) && + tagComponent.AddTags(ids); + } + + /// + /// Tries to add the given tags to an entity if the tags don't already exist. + /// + /// The entity to add the tag to. + /// The tags to add. + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public static bool AddTags(this IEntity entity, IEnumerable ids) + { + return entity.EnsureComponent(out TagComponent tagComponent) && + tagComponent.AddTags(ids); + } + + /// + /// Tries to add a tag to an entity if it has a + /// and the tag doesn't already exist. + /// + /// The entity to add the tag to. + /// The tag to add. + /// + /// true if it was added, false otherwise even if it already existed. + /// + /// + /// Thrown if no exists with the given id. + /// + public static bool TryAddTag(this IEntity entity, string id) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.AddTag(id); + } + + /// + /// Tries to add the given tags to an entity if it has a + /// and the tags don't already exist. + /// + /// The entity to add the tag to. + /// The tags to add. + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public static bool TryAddTags(this IEntity entity, params string[] ids) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.AddTags(ids); + } + + /// + /// Tries to add the given tags to an entity if it has a + /// and the tags don't already exist. + /// + /// The entity to add the tag to. + /// The tags to add. + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public static bool TryAddTags(this IEntity entity, IEnumerable ids) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.AddTags(ids); + } + + /// + /// Checks if a tag has been added to an entity. + /// + /// The entity to check. + /// The tag to check for. + /// true if it exists, false otherwise. + /// + /// Thrown if no exists with the given id. + /// + public static bool HasTag(this IEntity entity, string id) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.HasTag(id); + } + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// The entity to check. + /// The tags to check for. + /// true if they all exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public static bool HasAllTags(this IEntity entity, params string[] ids) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.HasAllTags(ids); + } + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// The entity to check. + /// The tags to check for. + /// true if they all exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public static bool HasAllTags(this IEntity entity, IEnumerable ids) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.HasAllTags(ids); + } + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// The entity to check. + /// The tags to check for. + /// true if any of them exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public static bool HasAnyTag(this IEntity entity, params string[] ids) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.HasAnyTag(ids); + } + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// The entity to check. + /// The tags to check for. + /// true if any of them exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public static bool HasAnyTag(this IEntity entity, IEnumerable ids) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.HasAnyTag(ids); + } + + /// + /// Tries to remove a tag from an entity if it exists. + /// + /// The entity to remove the tag from. + /// The tag to remove. + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if no exists with the given id. + /// + public static bool RemoveTag(this IEntity entity, string id) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.RemoveTag(id); + } + + /// + /// Tries to remove a tag from an entity if it exists. + /// + /// The entity to remove the tag from. + /// The tag to remove. + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// Thrown if one of the ids represents an unregistered . + /// + /// + public static bool RemoveTags(this IEntity entity, params string[] ids) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.RemoveTags(ids); + } + + /// + /// Tries to remove a tag from an entity if it exists. + /// + /// The entity to remove the tag from. + /// The tag to remove. + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public static bool RemoveTags(this IEntity entity, IEnumerable ids) + { + return entity.TryGetComponent(out TagComponent? tagComponent) && + tagComponent.RemoveTags(ids); + } + } +} diff --git a/Content.Shared/Prototypes/Tag/TagPrototype.cs b/Content.Shared/Prototypes/Tag/TagPrototype.cs new file mode 100644 index 0000000000..984faf1816 --- /dev/null +++ b/Content.Shared/Prototypes/Tag/TagPrototype.cs @@ -0,0 +1,31 @@ +#nullable enable +using JetBrains.Annotations; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Prototypes.Tag +{ + /// + /// Prototype representing a tag in YAML. + /// Meant to only have an ID property, as that is the only thing that + /// gets saved in TagComponent. + /// + [Prototype("Tag")] + public class TagPrototype : IPrototype, IIndexedPrototype, IExposeData + { + public string ID { get; [UsedImplicitly] private set; } = default!; + + public void ExposeData(ObjectSerializer serializer) + { + serializer.DataField(this, x => x.ID, "id", ""); + } + + public void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + ExposeData(serializer); + } + } +} From d0d2434fbac88ffbed7d63d56d003bd8361ef7bd Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 2 Feb 2021 03:39:31 +0100 Subject: [PATCH 43/61] Make Jenkinsfile more resilient against engine tag shenanigans --- Jenkinsfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index dfcd04b25e..892e50b646 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,6 +5,9 @@ pipeline { stage('Setup') { steps { sh 'git submodule update --init --recursive' + // Do a git fetch to make sure tags in the engine get pulled in if they've been added later. + // Can happen if somebody forgot to tag the engine then tried to fix it by tagging later. + sh 'git submodule foreach \\"git fetch\\"' } } stage('Build') { From a5492bc9432cbc58631c4d9aaa499a235f0e6f4b Mon Sep 17 00:00:00 2001 From: collinlunn <60152240+collinlunn@users.noreply.github.com> Date: Tue, 2 Feb 2021 05:20:24 -0600 Subject: [PATCH 44/61] More power nullability (#3070) --- .../NodeContainer/NodeContainerComponent.cs | 3 ++- .../NodeContainer/NodeGroups/AMENodeGroup.cs | 26 +++++++++---------- .../NodeGroups/INodeGroupManager.cs | 3 ++- .../NodeGroups/NodeGroupAttribute.cs | 3 ++- .../NodeGroups/NodeGroupFactory.cs | 3 ++- .../EmergencyLightComponent.cs | 17 ++++++------ .../PowerReceiverUsers/LightBulbComponent.cs | 7 ++--- .../PowerCellChargerComponent.cs | 3 ++- .../PoweredLightComponent.cs | 22 +++++++++------- .../WeaponCapacitorChargerComponent.cs | 3 ++- .../Power/BaseNetConnectorComponent.cs | 6 +++-- .../{ => Power}/BaseChargerSystem.cs | 3 ++- .../{ => Power}/BatteryDischargerSystem.cs | 5 ++-- .../{ => Power}/BatteryStorageSystem.cs | 5 ++-- .../{ => Power}/BatterySystem.cs | 3 ++- .../{ => Power}/PowerApcSystem.cs | 5 ++-- .../{ => Power}/PowerNetSystem.cs | 3 ++- .../{ => Power}/PowerSmesSystem.cs | 3 ++- .../PowerSolarControlConsoleSystem.cs | 3 ++- .../{ => Power}/PowerSolarSystem.cs | 3 ++- 20 files changed, 73 insertions(+), 56 deletions(-) rename Content.Server/GameObjects/EntitySystems/{ => Power}/BaseChargerSystem.cs (80%) rename Content.Server/GameObjects/EntitySystems/{ => Power}/BatteryDischargerSystem.cs (77%) rename Content.Server/GameObjects/EntitySystems/{ => Power}/BatteryStorageSystem.cs (76%) rename Content.Server/GameObjects/EntitySystems/{ => Power}/BatterySystem.cs (86%) rename Content.Server/GameObjects/EntitySystems/{ => Power}/PowerApcSystem.cs (89%) rename Content.Server/GameObjects/EntitySystems/{ => Power}/PowerNetSystem.cs (84%) rename Content.Server/GameObjects/EntitySystems/{ => Power}/PowerSmesSystem.cs (82%) rename Content.Server/GameObjects/EntitySystems/{ => Power}/PowerSolarControlConsoleSystem.cs (91%) rename Content.Server/GameObjects/EntitySystems/{ => Power}/PowerSolarSystem.cs (99%) diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeContainerComponent.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeContainerComponent.cs index adf31160cf..4915b51958 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeContainerComponent.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeContainerComponent.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Collections.Generic; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Server.GameObjects.Components.NodeContainer.Nodes; @@ -50,7 +51,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer } } - public override void HandleMessage(ComponentMessage message, IComponent component) + public override void HandleMessage(ComponentMessage message, IComponent? component) { base.HandleMessage(message, component); switch (message) diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/AMENodeGroup.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/AMENodeGroup.cs index 7e7edea5e6..113463ca47 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/AMENodeGroup.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/AMENodeGroup.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; using Content.Server.Explosions; @@ -24,12 +25,12 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups /// since any part connected to the node group can easily find the master. /// [ViewVariables] - private AMEControllerComponent _masterController; + private AMEControllerComponent? _masterController; [Dependency] private readonly IRobustRandom _random = default!; - public AMEControllerComponent MasterController => _masterController; + public AMEControllerComponent? MasterController => _masterController; private readonly List _cores = new(); @@ -52,20 +53,18 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups if (_masterController != null && _masterController?.Owner == node.Owner) { _masterController = null; } } - public void RefreshAMENodes(AMEControllerComponent controller) + public void RefreshAMENodes(AMEControllerComponent? controller) { if(_masterController == null && controller != null) { _masterController = controller; } - if (_cores != null) { - foreach (AMEShieldComponent core in _cores) - { - core.UnsetCore(); - } - _cores.Clear(); + foreach (AMEShieldComponent core in _cores) + { + core.UnsetCore(); } + _cores.Clear(); //Check each shield node to see if it meets core criteria foreach (Node node in Nodes) @@ -79,11 +78,12 @@ namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups .Select(entity => entity.TryGetComponent(out var adjshield) ? adjshield : null) .Where(adjshield => adjshield != null); - if (nodeNeighbors.Count() >= 8) { _cores.Add(shield); } + if (nodeNeighbors.Count() >= 8) + { + _cores.Add(shield); + } } - if (_cores == null) { return; } - foreach (AMEShieldComponent core in _cores) { core.SetCore(); diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroupManager.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroupManager.cs index 203d65e98f..186c2d5cc9 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroupManager.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/INodeGroupManager.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups { diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupAttribute.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupAttribute.cs index 5f47956656..6fd7ae37c6 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupAttribute.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupAttribute.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; namespace Content.Server.GameObjects.Components.NodeContainer.NodeGroups { diff --git a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs index 8f1aba5b74..f39d08a1cf 100644 --- a/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs +++ b/Content.Server/GameObjects/Components/NodeContainer/NodeGroups/NodeGroupFactory.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable + using System; using System.Collections.Generic; using System.Reflection; using Content.Server.GameObjects.Components.NodeContainer.Nodes; diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs index bf3044366b..55684c95a7 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/EmergencyLightComponent.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections.Generic; using Content.Shared.GameObjects.EntitySystems; @@ -63,7 +64,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece /// public void UpdateState() { - if (!Owner.TryGetComponent(out PowerReceiverComponent receiver)) + if (!Owner.TryGetComponent(out PowerReceiverComponent? receiver)) { return; } @@ -83,7 +84,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece public void OnUpdate(float frameTime) { - if (Owner.Deleted || !Owner.TryGetComponent(out BatteryComponent battery)) + if (Owner.Deleted || !Owner.TryGetComponent(out BatteryComponent? battery)) { return; } @@ -101,7 +102,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece battery.CurrentCharge += _chargingWattage * frameTime * _chargingEfficiency; if (battery.BatteryState == BatteryState.Full) { - if (Owner.TryGetComponent(out PowerReceiverComponent receiver)) + if (Owner.TryGetComponent(out PowerReceiverComponent? receiver)) { receiver.Load = 1; } @@ -113,12 +114,12 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece private void TurnOff() { - if (Owner.TryGetComponent(out SpriteComponent sprite)) + if (Owner.TryGetComponent(out SpriteComponent? sprite)) { sprite.LayerSetState(0, "emergency_light_off"); } - if (Owner.TryGetComponent(out PointLightComponent light)) + if (Owner.TryGetComponent(out PointLightComponent? light)) { light.Enabled = false; } @@ -126,18 +127,18 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece private void TurnOn() { - if (Owner.TryGetComponent(out SpriteComponent sprite)) + if (Owner.TryGetComponent(out SpriteComponent? sprite)) { sprite.LayerSetState(0, "emergency_light_on"); } - if (Owner.TryGetComponent(out PointLightComponent light)) + if (Owner.TryGetComponent(out PointLightComponent? light)) { light.Enabled = true; } } - public override void HandleMessage(ComponentMessage message, IComponent component) + public override void HandleMessage(ComponentMessage message, IComponent? component) { base.HandleMessage(message, component); switch (message) diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs index b7d72b392b..fe254ae026 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/LightBulbComponent.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using Content.Shared.Audio; using Content.Shared.GameObjects.EntitySystems; @@ -41,8 +42,8 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece /// /// Invoked whenever the state of the light bulb changes. /// - public event EventHandler OnLightBulbStateChange; - public event EventHandler OnLightColorChange; + public event EventHandler? OnLightBulbStateChange; + public event EventHandler? OnLightColorChange; private Color _color = Color.White; @@ -106,7 +107,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece public void UpdateColor() { - if (!Owner.TryGetComponent(out SpriteComponent sprite)) + if (!Owner.TryGetComponent(out SpriteComponent? sprite)) { return; } diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PowerCellChargerComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PowerCellChargerComponent.cs index 8c71ff18c0..ad205fac64 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PowerCellChargerComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PowerCellChargerComponent.cs @@ -1,4 +1,5 @@ -using Content.Shared.Interfaces.GameObjects.Components; +#nullable enable +using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs index 48f167d4fa..cda8f1b7cd 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; @@ -41,16 +42,16 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece [ViewVariables] private bool _on; private LightBulbType BulbType = LightBulbType.Tube; - [ViewVariables] private ContainerSlot _lightBulbContainer; + [ViewVariables] private ContainerSlot _lightBulbContainer = default!; [ViewVariables] - private LightBulbComponent LightBulb + private LightBulbComponent? LightBulb { get { if (_lightBulbContainer.ContainedEntity == null) return null; - _lightBulbContainer.ContainedEntity.TryGetComponent(out LightBulbComponent bulb); + _lightBulbContainer.ContainedEntity.TryGetComponent(out LightBulbComponent? bulb); return bulb; } @@ -65,12 +66,12 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece public bool InteractHand(InteractHandEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out IDamageableComponent damageableComponent)) + if (!eventArgs.User.TryGetComponent(out IDamageableComponent? damageableComponent)) { Eject(); return false; } - if(eventArgs.User.TryGetComponent(out HeatResistanceComponent heatResistanceComponent)) + if(eventArgs.User.TryGetComponent(out HeatResistanceComponent? heatResistanceComponent)) { if(CanBurn(heatResistanceComponent.GetHeatResistance())) { @@ -83,6 +84,9 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece bool CanBurn(int heatResistance) { + if (LightBulb == null) + return false; + return _lightState && heatResistance < LightBulb.BurningTemperature; } @@ -108,7 +112,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece private bool InsertBulb(IEntity bulb) { if (LightBulb != null) return false; - if (!bulb.TryGetComponent(out LightBulbComponent lightBulb)) return false; + if (!bulb.TryGetComponent(out LightBulbComponent? lightBulb)) return false; if (lightBulb.Type != BulbType) return false; var inserted = _lightBulbContainer.Insert(bulb); @@ -135,7 +139,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece if (!_lightBulbContainer.Remove(bulb.Owner)) return; - if (!user.TryGetComponent(out HandsComponent hands) + if (!user.TryGetComponent(out HandsComponent? hands) || !hands.PutInHand(bulb.Owner.GetComponent())) bulb.Owner.Transform.Coordinates = user.Transform.Coordinates; } @@ -149,7 +153,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece /// /// For attaching UpdateLight() to events. /// - public void UpdateLight(object sender, EventArgs e) + public void UpdateLight(object? sender, EventArgs? e) { UpdateLight(); } @@ -212,7 +216,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece _lightBulbContainer = ContainerManagerComponent.Ensure("light_bulb", Owner); } - public override void HandleMessage(ComponentMessage message, IComponent component) + public override void HandleMessage(ComponentMessage message, IComponent? component) { base.HandleMessage(message, component); switch (message) diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/WeaponCapacitorChargerComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/WeaponCapacitorChargerComponent.cs index e54cf3578f..5d03d47973 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/WeaponCapacitorChargerComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/WeaponCapacitorChargerComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; +#nullable enable +using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; diff --git a/Content.Server/GameObjects/Components/Power/BaseNetConnectorComponent.cs b/Content.Server/GameObjects/Components/Power/BaseNetConnectorComponent.cs index 66ac85b62d..44a5310c98 100644 --- a/Content.Server/GameObjects/Components/Power/BaseNetConnectorComponent.cs +++ b/Content.Server/GameObjects/Components/Power/BaseNetConnectorComponent.cs @@ -1,3 +1,5 @@ +#nullable enable +using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.GameObjects.Components.NodeContainer; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; @@ -15,7 +17,7 @@ namespace Content.Server.GameObjects.Components.Power [ViewVariables] public TNetType Net { get => _net; set => SetNet(value); } - private TNetType _net; + private TNetType _net = default!; //set in OnAdd() protected abstract TNetType NullNet { get; } @@ -68,7 +70,7 @@ namespace Content.Server.GameObjects.Components.Power protected abstract void RemoveSelfFromNet(TNetType net); - private bool TryFindNet(out TNetType foundNet) + private bool TryFindNet([NotNullWhen(true)] out TNetType? foundNet) { if (Owner.TryGetComponent(out var container)) { diff --git a/Content.Server/GameObjects/EntitySystems/BaseChargerSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/BaseChargerSystem.cs similarity index 80% rename from Content.Server/GameObjects/EntitySystems/BaseChargerSystem.cs rename to Content.Server/GameObjects/EntitySystems/Power/BaseChargerSystem.cs index d206e07461..d7a6a44afc 100644 --- a/Content.Server/GameObjects/EntitySystems/BaseChargerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/BaseChargerSystem.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerReceiverUsers; +#nullable enable +using Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerReceiverUsers; using JetBrains.Annotations; using Robust.Shared.GameObjects.Systems; diff --git a/Content.Server/GameObjects/EntitySystems/BatteryDischargerSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/BatteryDischargerSystem.cs similarity index 77% rename from Content.Server/GameObjects/EntitySystems/BatteryDischargerSystem.cs rename to Content.Server/GameObjects/EntitySystems/Power/BatteryDischargerSystem.cs index f6d9ab7cc5..8980ff8948 100644 --- a/Content.Server/GameObjects/EntitySystems/BatteryDischargerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/BatteryDischargerSystem.cs @@ -1,8 +1,7 @@ -using Content.Server.GameObjects.Components.Power.PowerNetComponents; +#nullable enable +using Content.Server.GameObjects.Components.Power.PowerNetComponents; using JetBrains.Annotations; -using Robust.Server.Interfaces.Timing; using Robust.Shared.GameObjects.Systems; -using Robust.Shared.IoC; namespace Content.Server.GameObjects.EntitySystems { diff --git a/Content.Server/GameObjects/EntitySystems/BatteryStorageSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/BatteryStorageSystem.cs similarity index 76% rename from Content.Server/GameObjects/EntitySystems/BatteryStorageSystem.cs rename to Content.Server/GameObjects/EntitySystems/Power/BatteryStorageSystem.cs index 8ace299025..725ac2ed9e 100644 --- a/Content.Server/GameObjects/EntitySystems/BatteryStorageSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/BatteryStorageSystem.cs @@ -1,8 +1,7 @@ -using Content.Server.GameObjects.Components.Power.PowerNetComponents; +#nullable enable +using Content.Server.GameObjects.Components.Power.PowerNetComponents; using JetBrains.Annotations; -using Robust.Server.Interfaces.Timing; using Robust.Shared.GameObjects.Systems; -using Robust.Shared.IoC; namespace Content.Server.GameObjects.EntitySystems { diff --git a/Content.Server/GameObjects/EntitySystems/BatterySystem.cs b/Content.Server/GameObjects/EntitySystems/Power/BatterySystem.cs similarity index 86% rename from Content.Server/GameObjects/EntitySystems/BatterySystem.cs rename to Content.Server/GameObjects/EntitySystems/Power/BatterySystem.cs index 438a58430f..c94defd61d 100644 --- a/Content.Server/GameObjects/EntitySystems/BatterySystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/BatterySystem.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Power; +#nullable enable +using Content.Server.GameObjects.Components.Power; using JetBrains.Annotations; using Robust.Shared.GameObjects.Systems; diff --git a/Content.Server/GameObjects/EntitySystems/PowerApcSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/PowerApcSystem.cs similarity index 89% rename from Content.Server/GameObjects/EntitySystems/PowerApcSystem.cs rename to Content.Server/GameObjects/EntitySystems/Power/PowerApcSystem.cs index aef620e58f..da177056dd 100644 --- a/Content.Server/GameObjects/EntitySystems/PowerApcSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/PowerApcSystem.cs @@ -1,10 +1,9 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using JetBrains.Annotations; -using Robust.Server.Interfaces.Timing; using Robust.Shared.GameObjects.Systems; -using Robust.Shared.IoC; namespace Content.Server.GameObjects.EntitySystems { diff --git a/Content.Server/GameObjects/EntitySystems/PowerNetSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/PowerNetSystem.cs similarity index 84% rename from Content.Server/GameObjects/EntitySystems/PowerNetSystem.cs rename to Content.Server/GameObjects/EntitySystems/Power/PowerNetSystem.cs index 86196dcc2e..944e801028 100644 --- a/Content.Server/GameObjects/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/PowerNetSystem.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Power.PowerNetComponents; +#nullable enable +using Content.Server.GameObjects.Components.Power.PowerNetComponents; using JetBrains.Annotations; using Robust.Shared.GameObjects.Systems; using Robust.Shared.IoC; diff --git a/Content.Server/GameObjects/EntitySystems/PowerSmesSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/PowerSmesSystem.cs similarity index 82% rename from Content.Server/GameObjects/EntitySystems/PowerSmesSystem.cs rename to Content.Server/GameObjects/EntitySystems/Power/PowerSmesSystem.cs index f6bc4cdeeb..989c82dd47 100644 --- a/Content.Server/GameObjects/EntitySystems/PowerSmesSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/PowerSmesSystem.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Power.PowerNetComponents; +#nullable enable +using Content.Server.GameObjects.Components.Power.PowerNetComponents; using JetBrains.Annotations; using Robust.Shared.GameObjects.Systems; diff --git a/Content.Server/GameObjects/EntitySystems/PowerSolarControlConsoleSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/PowerSolarControlConsoleSystem.cs similarity index 91% rename from Content.Server/GameObjects/EntitySystems/PowerSolarControlConsoleSystem.cs rename to Content.Server/GameObjects/EntitySystems/Power/PowerSolarControlConsoleSystem.cs index 68820fbd1b..555aab54bf 100644 --- a/Content.Server/GameObjects/EntitySystems/PowerSolarControlConsoleSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/PowerSolarControlConsoleSystem.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Power.PowerNetComponents; +#nullable enable +using Content.Server.GameObjects.Components.Power.PowerNetComponents; using JetBrains.Annotations; using Robust.Shared.GameObjects.Systems; diff --git a/Content.Server/GameObjects/EntitySystems/PowerSolarSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/PowerSolarSystem.cs similarity index 99% rename from Content.Server/GameObjects/EntitySystems/PowerSolarSystem.cs rename to Content.Server/GameObjects/EntitySystems/Power/PowerSolarSystem.cs index d2a437b40a..3e763f5516 100644 --- a/Content.Server/GameObjects/EntitySystems/PowerSolarSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/PowerSolarSystem.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Linq; using Content.Server.GameObjects.Components.Power.PowerNetComponents; using Content.Shared.Physics; From b7461168255124f348a387cfeb2691bd303d93f1 Mon Sep 17 00:00:00 2001 From: 20kdc Date: Tue, 2 Feb 2021 11:21:16 +0000 Subject: [PATCH 45/61] Command "setmind", so admins can forcibly relocate other players. (#3067) Y'all just know an admin is going to abuse this like Make Sentient/Control Mob, but oh well --- .../Administration/Commands/SetMindCommand.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 Content.Server/Administration/Commands/SetMindCommand.cs diff --git a/Content.Server/Administration/Commands/SetMindCommand.cs b/Content.Server/Administration/Commands/SetMindCommand.cs new file mode 100644 index 0000000000..d7309e79c1 --- /dev/null +++ b/Content.Server/Administration/Commands/SetMindCommand.cs @@ -0,0 +1,82 @@ +#nullable enable +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.Mobs; +using Content.Server.Players; +using Content.Shared.Administration; +using Robust.Server.Interfaces.Console; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; + +namespace Content.Server.Administration.Commands +{ + [AdminCommand(AdminFlags.Admin)] + class SetMindCommand : IClientCommand + { + public string Command => "setmind"; + + public string Description => Loc.GetString("Transfers a mind to the specified entity. The entity must have a MindComponent."); + + public string Help => Loc.GetString("Usage: {0} ", Command); + + public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + { + if (args.Length != 2) + { + shell.SendText(player, Loc.GetString("Wrong number of arguments.")); + return; + } + + if (!int.TryParse(args[0], out var entityUid)) + { + shell.SendText(player, Loc.GetString("EntityUid must be a number.")); + return; + } + + var entityManager = IoCManager.Resolve(); + + var eUid = new EntityUid(entityUid); + + if (!eUid.IsValid() || !entityManager.EntityExists(eUid)) + { + shell.SendText(player, Loc.GetString("Invalid entity ID.")); + return; + } + + var target = entityManager.GetEntity(eUid); + + if (!target.TryGetComponent(out var mindComponent)) + { + shell.SendText(player, Loc.GetString("Target entity does not have a mind (did you forget to make sentient?)")); + return; + } + + if (!IoCManager.Resolve().TryGetSessionByUsername(args[1], out var session)) + { + shell.SendText(player, Loc.GetString("Target player does not exist")); + return; + } + + // hm, does player have a mind? if not we may need to give them one + var playerCData = session.ContentData(); + if (playerCData == null) + { + shell.SendText(player, Loc.GetString("Target player does not have content data (wtf?)")); + return; + } + + var mind = playerCData.Mind; + if (mind == null) + { + mind = new Mind(session.UserId) + { + CharacterName = target.Name + }; + playerCData.Mind = mind; + } + mind.TransferTo(target); + } + } +} From 46790c5fea884e9e8c95678a8dd4c125b7f5c5dc Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Tue, 2 Feb 2021 13:41:21 +0100 Subject: [PATCH 46/61] Updates "setmind" command to use the new console API --- .../Administration/Commands/SetMindCommand.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Content.Server/Administration/Commands/SetMindCommand.cs b/Content.Server/Administration/Commands/SetMindCommand.cs index d7309e79c1..b868c9f20e 100644 --- a/Content.Server/Administration/Commands/SetMindCommand.cs +++ b/Content.Server/Administration/Commands/SetMindCommand.cs @@ -3,8 +3,8 @@ using Content.Server.GameObjects.Components.Mobs; using Content.Server.Mobs; using Content.Server.Players; using Content.Shared.Administration; -using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; +using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -13,7 +13,7 @@ using Robust.Shared.Localization; namespace Content.Server.Administration.Commands { [AdminCommand(AdminFlags.Admin)] - class SetMindCommand : IClientCommand + class SetMindCommand : IConsoleCommand { public string Command => "setmind"; @@ -21,17 +21,17 @@ namespace Content.Server.Administration.Commands public string Help => Loc.GetString("Usage: {0} ", Command); - public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + public void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 2) { - shell.SendText(player, Loc.GetString("Wrong number of arguments.")); + shell.WriteLine(Loc.GetString("Wrong number of arguments.")); return; } if (!int.TryParse(args[0], out var entityUid)) { - shell.SendText(player, Loc.GetString("EntityUid must be a number.")); + shell.WriteLine(Loc.GetString("EntityUid must be a number.")); return; } @@ -41,7 +41,7 @@ namespace Content.Server.Administration.Commands if (!eUid.IsValid() || !entityManager.EntityExists(eUid)) { - shell.SendText(player, Loc.GetString("Invalid entity ID.")); + shell.WriteLine(Loc.GetString("Invalid entity ID.")); return; } @@ -49,13 +49,13 @@ namespace Content.Server.Administration.Commands if (!target.TryGetComponent(out var mindComponent)) { - shell.SendText(player, Loc.GetString("Target entity does not have a mind (did you forget to make sentient?)")); + shell.WriteLine(Loc.GetString("Target entity does not have a mind (did you forget to make sentient?)")); return; } if (!IoCManager.Resolve().TryGetSessionByUsername(args[1], out var session)) { - shell.SendText(player, Loc.GetString("Target player does not exist")); + shell.WriteLine(Loc.GetString("Target player does not exist")); return; } @@ -63,7 +63,7 @@ namespace Content.Server.Administration.Commands var playerCData = session.ContentData(); if (playerCData == null) { - shell.SendText(player, Loc.GetString("Target player does not have content data (wtf?)")); + shell.WriteLine(Loc.GetString("Target player does not have content data (wtf?)")); return; } From b284c8266823223f91923e60e37281efdd424643 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 2 Feb 2021 22:28:23 +0100 Subject: [PATCH 47/61] >Expecting shell scripting to ever work sanely. --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 892e50b646..fd90f90fa7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,7 +7,7 @@ pipeline { sh 'git submodule update --init --recursive' // Do a git fetch to make sure tags in the engine get pulled in if they've been added later. // Can happen if somebody forgot to tag the engine then tried to fix it by tagging later. - sh 'git submodule foreach \\"git fetch\\"' + sh 'cd RobustToolbox && git fetch && cd ..' } } stage('Build') { From c40ac26cedd5c625459562206ece3d61b8fa3278 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 3 Feb 2021 14:05:31 +0100 Subject: [PATCH 48/61] A big hecking chemistry-related refactor. (#3055) * A big hecking chemistry-related refactor. Changed SolutionContainerCaps. It now describes "stock" behavior for interacting with solutions that is pre-implemented by SolutionContainerComponent. As such things like syringes do not check it anymore (on themselves) to see "can we remove reagent from ourselves". That's assumed by it... being a syringe. SolutionContainerCaps now has different flags more accurately describing possible reagent interaction behaviors. ISolutionInteractionsComponent is the interface that describes the common behaviors like "what happens when injected with a syringe". This is implemented by SolutionContainerComponent but could be implemented by other classes. One notable example that drove me to making this interface was the /vg/station circuit imprinter which splits reagent poured in into its two reservoir beakers. Having this interface allows us to do this "proxying" behavior hack-free. (the hacks in /vg/ code were somewhat dirty...). PourableComponent has been replaced SolutionTransferComponent. It now describes both give-and-take behavior for the common reagent containers. This is in line with /vg/'s /obj/item/weapon/reagent_containers architecture. "Taking" in this context is ONLY from reagent tanks like fuel tanks. Oh, should I mention that fuel tanks and such have a proper component now? They do. Because of this behavioral change, reagent tanks DO NOT have Pourable anymore. Removing from reagent tanks is now in the hands of the item used on them. Welders and fire extinguishers now have code for removing from them. This sounds bad at first but remember that all have quite unique behavior related to this: Welders cause explosions if lit and can ONLY be fueled at fuel tanks. Extinguishers can be filled at any tank, etc... The code for this is also simpler due to ISolutionInteractionsComponent now so... IAfterInteract now works like IInteractUsing with the Priority levels and "return true to block further handlers" behavior. This was necessary to make extinguishers prioritize taking from tanks over spraying. Explicitly coded interactions like welders refueling also means they refuse instantly to full now, which they didn't before. And it plays the sound. Etc... Probably more stuff I'm forgetting. * Review improvements. --- .../ActionBlocking/HandcuffComponent.cs | 15 +- .../Components/Atmos/GasAnalyzerComponent.cs | 6 +- .../Components/Body/MechanismComponent.cs | 6 +- .../Components/Body/Part/BodyPartComponent.cs | 6 +- .../Body/Surgery/SurgeryToolComponent.cs | 12 +- .../Components/Botany/PlantHolderComponent.cs | 20 +- .../Chemistry/HyposprayComponent.cs | 8 +- .../Components/Chemistry/InjectorComponent.cs | 87 +++--- .../Components/Chemistry/PillComponent.cs | 5 +- .../Components/Chemistry/PourableComponent.cs | 113 ------- .../Chemistry/ReagentTankComponent.cs | 35 +++ .../Chemistry/SolutionContainerComponent.cs | 210 +------------ .../Chemistry/SolutionTransferComponent.cs | 144 +++++++++ .../Chemistry/SolutionTransferVerbs.cs | 282 ++++++++++++++++++ .../GameObjects/Components/CrayonComponent.cs | 12 +- .../Components/Culinary/UtensilComponent.cs | 3 +- .../Components/Fluids/MopComponent.cs | 22 +- .../Components/Fluids/SpillableComponent.cs | 24 +- .../Components/Fluids/SprayComponent.cs | 16 +- .../Interactable/TilePryingComponent.cs | 3 +- .../Interactable/WelderComponent.cs | 39 ++- .../Items/FireExtinguisherComponent.cs | 46 ++- .../Items/FloorTileItemComponent.cs | 13 +- .../Components/Items/RCD/RCDAmmoComponent.cs | 7 +- .../Components/Items/RCD/RCDComponent.cs | 8 +- .../Components/Kitchen/MicrowaveComponent.cs | 9 +- .../Components/Medical/HealingComponent.cs | 14 +- .../Components/Nutrition/DrinkComponent.cs | 9 +- .../Components/Nutrition/FoodComponent.cs | 5 +- .../Components/Portal/TeleporterComponent.cs | 6 +- .../Components/Power/WirePlacerComponent.cs | 17 +- .../Ranged/Ammunition/SpeedLoaderComponent.cs | 6 +- .../EntitySystems/Click/InteractionSystem.cs | 27 +- Content.Shared/Chemistry/Solution.cs | 2 +- Content.Shared/Chemistry/SolutionCaps.cs | 46 --- .../Chemistry/SolutionContainerCaps.cs | 62 ++++ .../ISolutionInteractionsComponent.cs | 84 ++++++ .../SharedSolutionContainerComponent.cs | 47 ++- .../Components/Interaction/IAfterInteract.cs | 8 +- Resources/Audio/Effects/refill.ogg | Bin 0 -> 14607 bytes Resources/Maps/saltern.yml | 10 - .../Specific/Cooking/microwave.yml | 2 +- .../Constructible/Specific/hydroponics.yml | 3 +- .../Storage/StorageTanks/base_tank.yml | 6 +- .../Storage/StorageTanks/fuel_tank.yml | 6 +- .../Entities/Mobs/Species/human.yml | 2 +- .../Entities/Objects/Consumable/drinks.yml | 6 +- .../Objects/Consumable/drinks_bottles.yml | 2 +- .../Objects/Consumable/drinks_cans.yml | 4 +- .../Objects/Consumable/drinks_cups.yml | 2 +- .../Consumable/kitchen_reagent_containers.yml | 2 +- .../Objects/Consumable/trash_drinks.yml | 2 +- .../Objects/Misc/fire_extinguisher.yml | 2 +- .../Entities/Objects/Power/powercells.yml | 2 +- .../Entities/Objects/Specific/chemistry.yml | 12 +- .../Entities/Objects/Specific/janitor.yml | 4 +- .../Objects/Specific/rehydrateable.yml | 4 +- .../Entities/Objects/Tools/botany_tools.yml | 10 +- .../Entities/Objects/Tools/cowtools.yml | 4 +- .../Entities/Objects/Tools/welders.yml | 6 +- .../Guns/Ammunition/Shotgun/cartridges.yml | 4 +- .../Guns/Ammunition/Shotgun/projectiles.yml | 2 +- .../Entities/Objects/Weapons/Melee/spear.yml | 4 +- .../Prototypes/Entities/Objects/hypospray.yml | 2 +- SpaceStation14.sln.DotSettings | 1 + 65 files changed, 987 insertions(+), 601 deletions(-) delete mode 100644 Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs create mode 100644 Content.Server/GameObjects/Components/Chemistry/ReagentTankComponent.cs create mode 100644 Content.Server/GameObjects/Components/Chemistry/SolutionTransferComponent.cs create mode 100644 Content.Server/GameObjects/Components/Chemistry/SolutionTransferVerbs.cs delete mode 100644 Content.Shared/Chemistry/SolutionCaps.cs create mode 100644 Content.Shared/Chemistry/SolutionContainerCaps.cs create mode 100644 Content.Shared/GameObjects/Components/Chemistry/ISolutionInteractionsComponent.cs create mode 100644 Resources/Audio/Effects/refill.ogg diff --git a/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs b/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs index 18f4892d11..9e05ab7cd1 100644 --- a/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs +++ b/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs @@ -148,41 +148,41 @@ namespace Content.Server.GameObjects.Components.ActionBlocking return new HandcuffedComponentState(Broken ? BrokenState : string.Empty); } - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null || !ActionBlockerSystem.CanUse(eventArgs.User) || !eventArgs.Target.TryGetComponent(out var cuffed)) { - return; + return false; } if (eventArgs.Target == eventArgs.User) { eventArgs.User.PopupMessage(Loc.GetString("You can't cuff yourself!")); - return; + return true; } if (Broken) { eventArgs.User.PopupMessage(Loc.GetString("The cuffs are broken!")); - return; + return true; } if (!eventArgs.Target.TryGetComponent(out var hands)) { eventArgs.User.PopupMessage(Loc.GetString("{0:theName} has no hands!", eventArgs.Target)); - return; + return true; } if (cuffed.CuffedHandCount == hands.Count) { eventArgs.User.PopupMessage(Loc.GetString("{0:theName} has no free hands to handcuff!", eventArgs.Target)); - return; + return true; } if (!eventArgs.InRangeUnobstructed(_interactRange, ignoreInsideBlocker: true)) { eventArgs.User.PopupMessage(Loc.GetString("You are too far away to use the cuffs!")); - return; + return true; } eventArgs.User.PopupMessage(Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target)); @@ -190,6 +190,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking _audioSystem.PlayFromEntity(StartCuffSound, Owner); TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed); + return true; } /// diff --git a/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs b/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs index a63a27d274..586a332bdb 100644 --- a/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs @@ -253,18 +253,20 @@ namespace Content.Server.GameObjects.Components.Atmos } } - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (!eventArgs.CanReach) { eventArgs.User.PopupMessage(Loc.GetString("You can't reach there!")); - return; + return true; } if (eventArgs.User.TryGetComponent(out IActorComponent? actor)) { OpenInterface(actor.playerSession, eventArgs.ClickLocation); } + + return true; } diff --git a/Content.Server/GameObjects/Components/Body/MechanismComponent.cs b/Content.Server/GameObjects/Components/Body/MechanismComponent.cs index 1ff3f77f52..8dd4658573 100644 --- a/Content.Server/GameObjects/Components/Body/MechanismComponent.cs +++ b/Content.Server/GameObjects/Components/Body/MechanismComponent.cs @@ -36,11 +36,11 @@ namespace Content.Server.GameObjects.Components.Body } } - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null) { - return; + return false; } CloseAllSurgeryUIs(); @@ -61,6 +61,8 @@ namespace Content.Server.GameObjects.Components.Body eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("You can't fit it in!")); } } + + return true; } private void SendBodyPartListToUser(AfterInteractEventArgs eventArgs, IBody body) diff --git a/Content.Server/GameObjects/Components/Body/Part/BodyPartComponent.cs b/Content.Server/GameObjects/Components/Body/Part/BodyPartComponent.cs index 44c7cc5a7e..6a1c2e5220 100644 --- a/Content.Server/GameObjects/Components/Body/Part/BodyPartComponent.cs +++ b/Content.Server/GameObjects/Components/Body/Part/BodyPartComponent.cs @@ -99,12 +99,12 @@ namespace Content.Server.GameObjects.Components.Body.Part } } - public async Task AfterInteract(AfterInteractEventArgs eventArgs) + public async Task AfterInteract(AfterInteractEventArgs eventArgs) { // TODO BODY if (eventArgs.Target == null) { - return; + return false; } CloseAllSurgeryUIs(); @@ -116,6 +116,8 @@ namespace Content.Server.GameObjects.Components.Body.Part { SendSlots(eventArgs, body); } + + return true; } private void SendSlots(AfterInteractEventArgs eventArgs, IBody body) diff --git a/Content.Server/GameObjects/Components/Body/Surgery/SurgeryToolComponent.cs b/Content.Server/GameObjects/Components/Body/Surgery/SurgeryToolComponent.cs index ac1aaa3ad7..717bbccd99 100644 --- a/Content.Server/GameObjects/Components/Body/Surgery/SurgeryToolComponent.cs +++ b/Content.Server/GameObjects/Components/Body/Surgery/SurgeryToolComponent.cs @@ -50,16 +50,16 @@ namespace Content.Server.GameObjects.Components.Body.Surgery public IEntity? PerformerCache { get; private set; } - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null) { - return; + return false; } if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) { - return; + return false; } CloseAllSurgeryUIs(); @@ -101,20 +101,22 @@ namespace Content.Server.GameObjects.Components.Body.Surgery if (!part.SurgeryCheck(_surgeryType)) { NotUsefulPopup(); - return; + return true; } // ...do the surgery. if (part.AttemptSurgery(_surgeryType, part, this, eventArgs.User)) { - return; + return true; } // Log error if the surgery fails somehow. Logger.Debug($"Error when trying to perform surgery on ${nameof(IBodyPart)} {eventArgs.User.Name}"); throw new InvalidOperationException(); } + + return true; } public float BaseOperationTime { get => _baseOperateTime; set => _baseOperateTime = value; } diff --git a/Content.Server/GameObjects/Components/Botany/PlantHolderComponent.cs b/Content.Server/GameObjects/Components/Botany/PlantHolderComponent.cs index ec9fa8b951..64712083ab 100644 --- a/Content.Server/GameObjects/Components/Botany/PlantHolderComponent.cs +++ b/Content.Server/GameObjects/Components/Botany/PlantHolderComponent.cs @@ -12,6 +12,7 @@ using Content.Server.Utility; using Content.Shared.Audio; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Botany; +using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.Interfaces; @@ -718,23 +719,28 @@ namespace Content.Server.GameObjects.Components.Botany return true; } - if (usingItem.TryGetComponent(out SolutionContainerComponent? solution) && solution.CanRemoveSolutions) + if (usingItem.TryGetComponent(out ISolutionInteractionsComponent? solution) && solution.CanDrain) { - var amount = 5f; + var amount = ReagentUnit.New(5); var sprayed = false; if (usingItem.TryGetComponent(out SprayComponent? spray)) { sprayed = true; - amount = 1f; + amount = ReagentUnit.New(1); EntitySystem.Get().PlayFromEntity(spray.SpraySound, usingItem, AudioHelpers.WithVariation(0.125f)); } - var chemAmount = ReagentUnit.New(amount); + var split = solution.Drain(amount); + if (split.TotalVolume == 0) + { + user.PopupMessageCursor(Loc.GetString("{0:TheName} is empty!", usingItem)); + return true; + } - var split = solution.Solution.SplitSolution(chemAmount <= solution.Solution.TotalVolume ? chemAmount : solution.Solution.TotalVolume); - - user.PopupMessageCursor(Loc.GetString(sprayed ? $"You spray {Owner.Name} with {usingItem.Name}." : $"You transfer {split.TotalVolume.ToString()}u to {Owner.Name}")); + user.PopupMessageCursor(Loc.GetString( + sprayed ? "You spray {0:TheName}" : "You transfer {1}u to {0:TheName}", + Owner, split.TotalVolume)); _solutionContainer?.TryAddSolution(split); diff --git a/Content.Server/GameObjects/Components/Chemistry/HyposprayComponent.cs b/Content.Server/GameObjects/Components/Chemistry/HyposprayComponent.cs index 8b88d21d4b..7783b38f72 100644 --- a/Content.Server/GameObjects/Components/Chemistry/HyposprayComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/HyposprayComponent.cs @@ -52,10 +52,12 @@ namespace Content.Server.GameObjects.Components.Chemistry return TryDoInject(target, user); } - Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { - TryDoInject(eventArgs.Target, eventArgs.User); - return Task.CompletedTask; + if (!eventArgs.CanReach) + return false; + + return TryDoInject(eventArgs.Target, eventArgs.User); } private bool TryDoInject(IEntity? target, IEntity user) diff --git a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs index 275fdc9342..fa23f8c0de 100644 --- a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs @@ -28,21 +28,18 @@ namespace Content.Server.GameObjects.Components.Chemistry /// Whether or not the injector is able to draw from containers or if it's a single use /// device that can only inject. /// - [ViewVariables] - private bool _injectOnly; + [ViewVariables] private bool _injectOnly; /// /// Amount to inject or draw on each usage. If the injector is inject only, it will /// attempt to inject it's entire contents upon use. /// - [ViewVariables] - private ReagentUnit _transferAmount; + [ViewVariables] private ReagentUnit _transferAmount; /// /// Initial storage volume of the injector /// - [ViewVariables] - private ReagentUnit _initialMaxVolume; + [ViewVariables] private ReagentUnit _initialMaxVolume; private InjectorToggleMode _toggleState; @@ -68,19 +65,14 @@ namespace Content.Server.GameObjects.Components.Chemistry serializer.DataField(ref _injectOnly, "injectOnly", false); serializer.DataField(ref _initialMaxVolume, "initialMaxVolume", ReagentUnit.New(15)); serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5)); - serializer.DataField(ref _toggleState, "toggleState", _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw); + serializer.DataField(ref _toggleState, "toggleState", + _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw); } protected override void Startup() { base.Startup(); - var solution = Owner.EnsureComponent(); - solution.Capabilities = SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom; - - // Set _toggleState based on prototype - _toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw; - Dirty(); } @@ -116,58 +108,55 @@ namespace Content.Server.GameObjects.Components.Chemistry /// Called when clicking on entities while holding in active hand /// /// - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { - if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return; + if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) + return false; //Make sure we have the attacking entity - if (eventArgs.Target == null || !Owner.TryGetComponent(out SolutionContainerComponent? solution)) + if (eventArgs.Target == null || !Owner.HasComponent()) { - return; + return false; } var targetEntity = eventArgs.Target; // Handle injecting/drawing for solutions - if (targetEntity.TryGetComponent(out var targetSolution)) + if (targetEntity.TryGetComponent(out var targetSolution)) { if (ToggleState == InjectorToggleMode.Inject) { - if (solution.CanRemoveSolutions && targetSolution.CanAddSolutions) + if (targetSolution.CanInject) { TryInject(targetSolution, eventArgs.User); } else { - eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You aren't able to transfer to {0:theName}!", targetSolution.Owner)); + eventArgs.User.PopupMessage(eventArgs.User, + Loc.GetString("You aren't able to transfer to {0:theName}!", targetSolution.Owner)); } } else if (ToggleState == InjectorToggleMode.Draw) { - if (targetSolution.CanRemoveSolutions && solution.CanAddSolutions) + if (targetSolution.CanDraw) { TryDraw(targetSolution, eventArgs.User); } else { - eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You aren't able to draw from {0:theName}!", targetSolution.Owner)); + eventArgs.User.PopupMessage(eventArgs.User, + Loc.GetString("You aren't able to draw from {0:theName}!", targetSolution.Owner)); } } } - else // Handle injecting into bloodstream + // Handle injecting into bloodstream + else if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) && + ToggleState == InjectorToggleMode.Inject) { - if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) && ToggleState == InjectorToggleMode.Inject) - { - if (solution.CanRemoveSolutions) - { - TryInjectIntoBloodstream(bloodstream, eventArgs.User); - } - else - { - eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You aren't able to inject {0:theName}!", targetEntity)); - } - } + TryInjectIntoBloodstream(bloodstream, eventArgs.User); } + + return true; } /// @@ -193,7 +182,8 @@ namespace Content.Server.GameObjects.Components.Chemistry if (realTransferAmount <= 0) { - Owner.PopupMessage(user, Loc.GetString("You aren't able to inject {0:theName}!", targetBloodstream.Owner)); + Owner.PopupMessage(user, + Loc.GetString("You aren't able to inject {0:theName}!", targetBloodstream.Owner)); return; } @@ -213,12 +203,14 @@ namespace Content.Server.GameObjects.Components.Chemistry removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection); - Owner.PopupMessage(user, Loc.GetString("You inject {0}u into {1:theName}!", removedSolution.TotalVolume, targetBloodstream.Owner)); + Owner.PopupMessage(user, + Loc.GetString("You inject {0}u into {1:theName}!", removedSolution.TotalVolume, + targetBloodstream.Owner)); Dirty(); AfterInject(); } - private void TryInject(SolutionContainerComponent targetSolution, IEntity user) + private void TryInject(ISolutionInteractionsComponent targetSolution, IEntity user) { if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || solution.CurrentVolume == 0) { @@ -226,7 +218,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } // Get transfer amount. May be smaller than _transferAmount if not enough room - var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.EmptyVolume); + var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.InjectSpaceAvailable); if (realTransferAmount <= 0) { @@ -237,16 +229,12 @@ namespace Content.Server.GameObjects.Components.Chemistry // Move units from attackSolution to targetSolution var removedSolution = solution.SplitSolution(realTransferAmount); - if (!targetSolution.CanAddSolution(removedSolution)) - { - return; - } - removedSolution.DoEntityReaction(targetSolution.Owner, ReactionMethod.Injection); - targetSolution.TryAddSolution(removedSolution); + targetSolution.Inject(removedSolution); - Owner.PopupMessage(user, Loc.GetString("You transfer {0}u to {1:theName}", removedSolution.TotalVolume, targetSolution.Owner)); + Owner.PopupMessage(user, + Loc.GetString("You transfer {0}u to {1:theName}", removedSolution.TotalVolume, targetSolution.Owner)); Dirty(); AfterInject(); } @@ -260,7 +248,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } } - private void TryDraw(SolutionContainerComponent targetSolution, IEntity user) + private void TryDraw(ISolutionInteractionsComponent targetSolution, IEntity user) { if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || solution.EmptyVolume == 0) { @@ -268,7 +256,7 @@ namespace Content.Server.GameObjects.Components.Chemistry } // Get transfer amount. May be smaller than _transferAmount if not enough room - var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.CurrentVolume); + var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.DrawAvailable); if (realTransferAmount <= 0) { @@ -277,14 +265,15 @@ namespace Content.Server.GameObjects.Components.Chemistry } // Move units from attackSolution to targetSolution - var removedSolution = targetSolution.SplitSolution(realTransferAmount); + var removedSolution = targetSolution.Draw(realTransferAmount); if (!solution.TryAddSolution(removedSolution)) { return; } - Owner.PopupMessage(user, Loc.GetString("Drew {0}u from {1:theName}", removedSolution.TotalVolume, targetSolution.Owner)); + Owner.PopupMessage(user, + Loc.GetString("Drew {0}u from {1:theName}", removedSolution.TotalVolume, targetSolution.Owner)); Dirty(); AfterDraw(); } diff --git a/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs b/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs index d96e27cc60..f333605dd1 100644 --- a/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs @@ -59,14 +59,15 @@ namespace Content.Server.GameObjects.Components.Chemistry } // Feeding someone else - public async Task AfterInteract(AfterInteractEventArgs eventArgs) + public async Task AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null) { - return; + return false; } TryUseFood(eventArgs.User, eventArgs.Target); + return true; } public override bool TryUseFood(IEntity user, IEntity target, UtensilComponent utensilUsed = null) diff --git a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs b/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs deleted file mode 100644 index ac747c962a..0000000000 --- a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Threading.Tasks; -using Content.Shared.Chemistry; -using Content.Shared.Interfaces; -using Content.Shared.Interfaces.GameObjects.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Server.GameObjects.Components.Chemistry -{ - /// - /// Gives an entity click behavior for pouring reagents into - /// other entities and being poured into. The entity must have - /// a SolutionComponent or DrinkComponent for this to work. - /// (DrinkComponent adds a SolutionComponent if one isn't present). - /// - [RegisterComponent] - class PourableComponent : Component, IInteractUsing - { - public override string Name => "Pourable"; - - private ReagentUnit _transferAmount; - - /// - /// The amount of solution to be transferred from this solution when clicking on other solutions with it. - /// - [ViewVariables(VVAccess.ReadWrite)] - public ReagentUnit TransferAmount - { - get => _transferAmount; - set => _transferAmount = value; - } - - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5.0)); - } - - /// - /// Called when the owner of this component is clicked on with another entity. - /// The owner of this component is the target. - /// The entity used to click on this one is the attacker. - /// - /// Attack event args - /// - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - //Get target solution component - if (!Owner.TryGetComponent(out var targetSolution)) - return false; - - //Get attack solution component - var attackEntity = eventArgs.Using; - if (!attackEntity.TryGetComponent(out var attackSolution)) - return false; - - // Calculate possibe solution transfer - if (targetSolution.CanAddSolutions && attackSolution.CanRemoveSolutions) - { - // default logic (beakers and glasses) - // transfer solution from object in hand to attacked - return TryTransfer(eventArgs, attackSolution, targetSolution); - } - else if (targetSolution.CanRemoveSolutions && attackSolution.CanAddSolutions) - { - // storage tanks and sinks logic - // drain solution from attacked object to object in hand - return TryTransfer(eventArgs, targetSolution, attackSolution); - } - - // No transfer possible - return false; - } - - bool TryTransfer(InteractUsingEventArgs eventArgs, SolutionContainerComponent fromSolution, SolutionContainerComponent toSolution) - { - var fromEntity = fromSolution.Owner; - - if (!fromEntity.TryGetComponent(out var fromPourable)) - { - return false; - } - - //Get transfer amount. May be smaller than _transferAmount if not enough room - var realTransferAmount = ReagentUnit.Min(fromPourable.TransferAmount, toSolution.EmptyVolume); - - if (realTransferAmount <= 0) // Special message if container is full - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("{0:theName} is full!", toSolution.Owner)); - return false; - } - - //Move units from attackSolution to targetSolution - var removedSolution = fromSolution.SplitSolution(realTransferAmount); - - if (removedSolution.TotalVolume <= ReagentUnit.Zero) - { - return false; - } - - if (!toSolution.TryAddSolution(removedSolution)) - { - return false; - } - - Owner.PopupMessage(eventArgs.User, Loc.GetString("You transfer {0}u to {1:theName}.", removedSolution.TotalVolume, toSolution.Owner)); - - return true; - } - } -} diff --git a/Content.Server/GameObjects/Components/Chemistry/ReagentTankComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ReagentTankComponent.cs new file mode 100644 index 0000000000..0f654380fb --- /dev/null +++ b/Content.Server/GameObjects/Components/Chemistry/ReagentTankComponent.cs @@ -0,0 +1,35 @@ +using Content.Shared.Chemistry; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +#nullable enable + +namespace Content.Server.GameObjects.Components.Chemistry +{ + [RegisterComponent] + public class ReagentTankComponent : Component + { + public override string Name => "ReagentTank"; + + [ViewVariables(VVAccess.ReadWrite)] + public ReagentUnit TransferAmount { get; set; } + + [ViewVariables(VVAccess.ReadWrite)] + public ReagentTankType TankType { get; set; } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(this, c => c.TransferAmount, "transferAmount", ReagentUnit.New(10)); + serializer.DataField(this, c => c.TankType, "tankType", ReagentTankType.Unspecified); + } + } + + public enum ReagentTankType : byte + { + Unspecified, + Fuel + } +} diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs index d60b1dc0fb..36ff877874 100644 --- a/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionContainerComponent.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using Content.Server.Administration; using Content.Server.Eui; using Content.Server.GameObjects.Components.GUI; @@ -6,6 +6,7 @@ using Content.Shared.Administration; using Content.Shared.Chemistry; using Content.Shared.Eui; using Content.Shared.GameObjects.Components.Chemistry; +using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.GameObjects.Verbs; using Robust.Server.Interfaces.GameObjects; @@ -19,213 +20,8 @@ namespace Content.Server.GameObjects.Components.Chemistry { [RegisterComponent] [ComponentReference(typeof(SharedSolutionContainerComponent))] + [ComponentReference(typeof(ISolutionInteractionsComponent))] public class SolutionContainerComponent : SharedSolutionContainerComponent { - /// - /// Transfers solution from the held container to the target container. - /// - [Verb] - private sealed class FillTargetVerb : Verb - { - protected override void GetData(IEntity user, SolutionContainerComponent component, VerbData data) - { - if (!ActionBlockerSystem.CanInteract(user) || - !user.TryGetComponent(out var hands) || - hands.GetActiveHand == null || - hands.GetActiveHand.Owner == component.Owner || - !hands.GetActiveHand.Owner.TryGetComponent(out var solution) || - !solution.CanRemoveSolutions || - !component.CanAddSolutions) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? ""; - var myName = component.Owner.Prototype?.Name ?? ""; - - var locHeldEntityName = Loc.GetString(heldEntityName); - var locMyName = Loc.GetString(myName); - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", locHeldEntityName, locMyName); - } - - protected override void Activate(IEntity user, SolutionContainerComponent component) - { - if (!user.TryGetComponent(out var hands) || hands.GetActiveHand == null) - { - return; - } - - if (!hands.GetActiveHand.Owner.TryGetComponent(out var handSolutionComp) || - !handSolutionComp.CanRemoveSolutions || - !component.CanAddSolutions) - { - return; - } - - var transferQuantity = ReagentUnit.Min(component.MaxVolume - component.CurrentVolume, - handSolutionComp.CurrentVolume, ReagentUnit.New(10)); - - if (transferQuantity <= 0) - { - return; - } - - var transferSolution = handSolutionComp.SplitSolution(transferQuantity); - component.TryAddSolution(transferSolution); - } - } - - /// - /// Transfers solution from a target container to the held container. - /// - [Verb] - private sealed class EmptyTargetVerb : Verb - { - protected override void GetData(IEntity user, SolutionContainerComponent component, VerbData data) - { - if (!ActionBlockerSystem.CanInteract(user) || - !user.TryGetComponent(out var hands) || - hands.GetActiveHand == null || - hands.GetActiveHand.Owner == component.Owner || - !hands.GetActiveHand.Owner.TryGetComponent(out var solution) || - !solution.CanAddSolutions || - !component.CanRemoveSolutions) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? ""; - var myName = component.Owner.Prototype?.Name ?? ""; - - var locHeldEntityName = Loc.GetString(heldEntityName); - var locMyName = Loc.GetString(myName); - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", locMyName, locHeldEntityName); - return; - } - - protected override void Activate(IEntity user, SolutionContainerComponent component) - { - if (!user.TryGetComponent(out var hands) || hands.GetActiveHand == null) - { - return; - } - - if (!hands.GetActiveHand.Owner.TryGetComponent(out var handSolutionComp) || - !handSolutionComp.CanAddSolutions || - !component.CanRemoveSolutions) - { - return; - } - - var transferQuantity = ReagentUnit.Min(handSolutionComp.MaxVolume - handSolutionComp.CurrentVolume, - component.CurrentVolume, ReagentUnit.New(10)); - - if (transferQuantity <= 0) - { - return; - } - - var transferSolution = component.SplitSolution(transferQuantity); - handSolutionComp.TryAddSolution(transferSolution); - } - } - - [Verb] - private sealed class AdminAddReagentVerb : Verb - { - private const AdminFlags ReqFlags = AdminFlags.Fun; - - protected override void GetData(IEntity user, SolutionContainerComponent component, VerbData data) - { - data.Text = Loc.GetString("Add Reagent..."); - data.CategoryData = VerbCategories.Debug; - data.Visibility = VerbVisibility.Invisible; - - var adminManager = IoCManager.Resolve(); - - if (user.TryGetComponent(out var player)) - { - if (adminManager.HasAdminFlag(player.playerSession, ReqFlags)) - { - data.Visibility = VerbVisibility.Visible; - } - } - } - - protected override void Activate(IEntity user, SolutionContainerComponent component) - { - var groupController = IoCManager.Resolve(); - if (user.TryGetComponent(out var player)) - { - if (groupController.HasAdminFlag(player.playerSession, ReqFlags)) - OpenAddReagentMenu(player.playerSession, component); - } - } - - private static void OpenAddReagentMenu(IPlayerSession player, SolutionContainerComponent comp) - { - var euiMgr = IoCManager.Resolve(); - euiMgr.OpenEui(new AdminAddReagentEui(comp), player); - } - - private sealed class AdminAddReagentEui : BaseEui - { - private readonly SolutionContainerComponent _target; - [Dependency] private readonly IAdminManager _adminManager = default!; - - public AdminAddReagentEui(SolutionContainerComponent target) - { - _target = target; - - IoCManager.InjectDependencies(this); - } - - public override void Opened() - { - StateDirty(); - } - - public override EuiStateBase GetNewState() - { - return new AdminAddReagentEuiState - { - CurVolume = _target.CurrentVolume, - MaxVolume = _target.MaxVolume - }; - } - - public override void HandleMessage(EuiMessageBase msg) - { - switch (msg) - { - case AdminAddReagentEuiMsg.Close: - Close(); - break; - case AdminAddReagentEuiMsg.DoAdd doAdd: - // Double check that user wasn't de-adminned in the mean time... - // Or the target was deleted. - if (!_adminManager.HasAdminFlag(Player, ReqFlags) || _target.Deleted) - { - Close(); - return; - } - - _target.TryAddReagent(doAdd.ReagentId, doAdd.Amount, out _); - StateDirty(); - - if (doAdd.CloseAfter) - Close(); - - break; - } - } - } - } } } diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionTransferComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionTransferComponent.cs new file mode 100644 index 0000000000..b0f43572eb --- /dev/null +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionTransferComponent.cs @@ -0,0 +1,144 @@ +#nullable enable +using System.Threading.Tasks; +using Content.Shared.Chemistry; +using Content.Shared.GameObjects.Components.Chemistry; +using Content.Shared.Interfaces; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Chemistry +{ + /// + /// Gives click behavior for transferring to/from other reagent containers. + /// + [RegisterComponent] + public sealed class SolutionTransferComponent : Component, IAfterInteract + { + // Behavior is as such: + // If it's a reagent tank, TAKE reagent. + // If it's anything else, GIVE reagent. + // Of course, only if possible. + + public override string Name => "SolutionTransfer"; + + private ReagentUnit _transferAmount; + private bool _canReceive; + private bool _canSend; + + /// + /// The amount of solution to be transferred from this solution when clicking on other solutions with it. + /// + [ViewVariables(VVAccess.ReadWrite)] + public ReagentUnit TransferAmount + { + get => _transferAmount; + set => _transferAmount = value; + } + + /// + /// Can this entity take reagent from reagent tanks? + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool CanReceive + { + get => _canReceive; + set => _canReceive = value; + } + + /// + /// Can this entity give reagent to other reagent containers? + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool CanSend + { + get => _canSend; + set => _canSend = value; + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5)); + serializer.DataField(ref _canReceive, "canReceive", true); + serializer.DataField(ref _canSend, "canSend", true); + } + + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + { + if (!eventArgs.CanReach || eventArgs.Target == null) + return false; + + if (!Owner.TryGetComponent(out ISolutionInteractionsComponent? ownerSolution)) + return false; + + var target = eventArgs.Target; + if (!target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution)) + { + return false; + } + + if (CanReceive && target.TryGetComponent(out ReagentTankComponent? tank) + && ownerSolution.CanRefill && targetSolution.CanDrain) + { + var transferred = DoTransfer(targetSolution, ownerSolution, tank.TransferAmount, eventArgs.User); + if (transferred > 0) + { + var toTheBrim = ownerSolution.RefillSpaceAvailable == 0; + var msg = toTheBrim + ? "You fill {0:TheName} to the brim with {1}u from {2:theName}" + : "You fill {0:TheName} with {1}u from {2:theName}"; + + target.PopupMessage(eventArgs.User, Loc.GetString(msg, Owner, transferred, target)); + return true; + } + } + + if (CanSend && targetSolution.CanRefill && ownerSolution.CanDrain) + { + var transferred = DoTransfer(ownerSolution, targetSolution, TransferAmount, eventArgs.User); + + if (transferred > 0) + { + Owner.PopupMessage(eventArgs.User, Loc.GetString("You transfer {0}u to {1:theName}.", + transferred, target)); + + return true; + } + } + + return true; + } + + /// The actual amount transferred. + private static ReagentUnit DoTransfer( + ISolutionInteractionsComponent source, + ISolutionInteractionsComponent target, + ReagentUnit amount, + IEntity user) + { + if (source.DrainAvailable == 0) + { + source.Owner.PopupMessage(user, Loc.GetString("{0:TheName} is empty!", source.Owner)); + return ReagentUnit.Zero; + } + + if (target.RefillSpaceAvailable == 0) + { + target.Owner.PopupMessage(user, Loc.GetString("{0:TheName} is full!", target.Owner)); + return ReagentUnit.Zero; + } + + var actualAmount = + ReagentUnit.Min(amount, ReagentUnit.Min(source.DrainAvailable, target.RefillSpaceAvailable)); + + var solution = source.Drain(actualAmount); + target.Refill(solution); + + return actualAmount; + } + } +} diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionTransferVerbs.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionTransferVerbs.cs new file mode 100644 index 0000000000..8715ed027a --- /dev/null +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionTransferVerbs.cs @@ -0,0 +1,282 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Server.Administration; +using Content.Server.Eui; +using Content.Server.GameObjects.Components.GUI; +using Content.Shared.Administration; +using Content.Shared.Chemistry; +using Content.Shared.Eui; +using Content.Shared.GameObjects.Components.Chemistry; +using Content.Shared.GameObjects.EntitySystems.ActionBlocker; +using Content.Shared.GameObjects.Verbs; +using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; + +#nullable enable + +namespace Content.Server.GameObjects.Components.Chemistry +{ + internal abstract class SolutionTransferVerbBase : GlobalVerb + { + protected static bool GetHeldSolution( + IEntity holder, + [NotNullWhen(true)] + out IEntity? held, + [NotNullWhen(true)] + out ISolutionInteractionsComponent? heldSolution) + { + if (!holder.TryGetComponent(out HandsComponent? hands) + || hands.GetActiveHand == null + || !hands.GetActiveHand.Owner.TryGetComponent(out heldSolution)) + { + held = null; + heldSolution = null; + return false; + } + + held = heldSolution.Owner; + return true; + } + } + + /// + /// Transfers solution from the held container to the target container. + /// + [GlobalVerb] + internal sealed class SolutionFillTargetVerb : SolutionTransferVerbBase + { + public override void GetData(IEntity user, IEntity target, VerbData data) + { + if (!target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution) || + !ActionBlockerSystem.CanInteract(user) || + !GetHeldSolution(user, out var source, out var sourceSolution) || + source != target || + !sourceSolution.CanDrain || + !targetSolution.CanRefill) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + data.Visibility = VerbVisibility.Visible; + data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", source.Name, target.Name); + } + + public override void Activate(IEntity user, IEntity target) + { + if (!GetHeldSolution(user, out _, out var handSolutionComp)) + { + return; + } + + if (!handSolutionComp.CanDrain || + !target.TryGetComponent(out ISolutionInteractionsComponent? targetComp) || + !targetComp.CanRefill) + { + return; + } + + var transferQuantity = ReagentUnit.Min( + targetComp.RefillSpaceAvailable, + handSolutionComp.DrainAvailable, + ReagentUnit.New(10)); + + if (transferQuantity <= 0) + { + return; + } + + var transferSolution = handSolutionComp.Drain(transferQuantity); + targetComp.Refill(transferSolution); + } + } + + /// + /// Transfers solution from a target container to the held container. + /// + [GlobalVerb] + internal sealed class SolutionDrainTargetVerb : SolutionTransferVerbBase + { + public override void GetData(IEntity user, IEntity target, VerbData data) + { + if (!target.TryGetComponent(out ISolutionInteractionsComponent? sourceSolution) || + !ActionBlockerSystem.CanInteract(user) || + !GetHeldSolution(user, out var held, out var targetSolution) || + !sourceSolution.CanDrain || + !targetSolution.CanRefill) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + data.Visibility = VerbVisibility.Visible; + data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", held.Name, target.Name); + } + + public override void Activate(IEntity user, IEntity target) + { + if (!GetHeldSolution(user, out _, out var targetComp)) + { + return; + } + + if (!targetComp.CanRefill || + !target.TryGetComponent(out ISolutionInteractionsComponent? sourceComp) || + !sourceComp.CanDrain) + { + return; + } + + var transferQuantity = ReagentUnit.Min( + targetComp.RefillSpaceAvailable, + sourceComp.DrainAvailable, + ReagentUnit.New(10)); + + if (transferQuantity <= 0) + { + return; + } + + var transferSolution = sourceComp.Drain(transferQuantity); + targetComp.Refill(transferSolution); + } + } + + [GlobalVerb] + internal sealed class AdminAddReagentVerb : GlobalVerb + { + public override bool RequireInteractionRange => false; + public override bool BlockedByContainers => false; + + private const AdminFlags ReqFlags = AdminFlags.Fun; + + private static void OpenAddReagentMenu(IPlayerSession player, IEntity target) + { + var euiMgr = IoCManager.Resolve(); + euiMgr.OpenEui(new AdminAddReagentEui(target), player); + } + + public override void GetData(IEntity user, IEntity target, VerbData data) + { + // ISolutionInteractionsComponent doesn't exactly have an interface for "admin tries to refill this", so... + // Still have a path for SolutionContainerComponent in case it doesn't allow direct refilling. + if (!target.HasComponent() + && !(target.TryGetComponent(out ISolutionInteractionsComponent? interactions) + && interactions.CanInject)) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + data.Text = Loc.GetString("Add Reagent..."); + data.CategoryData = VerbCategories.Debug; + data.Visibility = VerbVisibility.Invisible; + + var adminManager = IoCManager.Resolve(); + + if (user.TryGetComponent(out var player)) + { + if (adminManager.HasAdminFlag(player.playerSession, ReqFlags)) + { + data.Visibility = VerbVisibility.Visible; + } + } + } + + public override void Activate(IEntity user, IEntity target) + { + var groupController = IoCManager.Resolve(); + if (user.TryGetComponent(out var player)) + { + if (groupController.HasAdminFlag(player.playerSession, ReqFlags)) + OpenAddReagentMenu(player.playerSession, target); + } + } + + private sealed class AdminAddReagentEui : BaseEui + { + private readonly IEntity _target; + [Dependency] private readonly IAdminManager _adminManager = default!; + + public AdminAddReagentEui(IEntity target) + { + _target = target; + + IoCManager.InjectDependencies(this); + } + + public override void Opened() + { + StateDirty(); + } + + public override EuiStateBase GetNewState() + { + if (_target.TryGetComponent(out SolutionContainerComponent? container)) + { + return new AdminAddReagentEuiState + { + CurVolume = container.CurrentVolume, + MaxVolume = container.MaxVolume + }; + } + + if (_target.TryGetComponent(out ISolutionInteractionsComponent? interactions)) + { + return new AdminAddReagentEuiState + { + // We don't exactly have an absolute total volume so good enough. + CurVolume = ReagentUnit.Zero, + MaxVolume = interactions.InjectSpaceAvailable + }; + } + + return new AdminAddReagentEuiState + { + CurVolume = ReagentUnit.Zero, + MaxVolume = ReagentUnit.Zero + }; + } + + public override void HandleMessage(EuiMessageBase msg) + { + switch (msg) + { + case AdminAddReagentEuiMsg.Close: + Close(); + break; + case AdminAddReagentEuiMsg.DoAdd doAdd: + // Double check that user wasn't de-adminned in the mean time... + // Or the target was deleted. + if (!_adminManager.HasAdminFlag(Player, ReqFlags) || _target.Deleted) + { + Close(); + return; + } + + var id = doAdd.ReagentId; + var amount = doAdd.Amount; + + if (_target.TryGetComponent(out SolutionContainerComponent? container)) + { + container.TryAddReagent(id, amount, out _); + } + else if (_target.TryGetComponent(out ISolutionInteractionsComponent? interactions)) + { + var solution = new Solution(id, amount); + interactions.Inject(solution); + } + + StateDirty(); + + if (doAdd.CloseAfter) + Close(); + + break; + } + } + } + } +} diff --git a/Content.Server/GameObjects/Components/CrayonComponent.cs b/Content.Server/GameObjects/Components/CrayonComponent.cs index 5c0067a4f7..d44644999a 100644 --- a/Content.Server/GameObjects/Components/CrayonComponent.cs +++ b/Content.Server/GameObjects/Components/CrayonComponent.cs @@ -109,19 +109,22 @@ namespace Content.Server.GameObjects.Components return false; } - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: false, popup: true, - collisionMask: Shared.Physics.CollisionGroup.MobImpassable)) return; + collisionMask: Shared.Physics.CollisionGroup.MobImpassable)) + { + return true; + } if (Charges <= 0) { eventArgs.User.PopupMessage(Loc.GetString("Not enough left.")); - return; + return true; } var entityManager = IoCManager.Resolve(); - + var entity = entityManager.SpawnEntity("CrayonDecal", eventArgs.ClickLocation); if (entity.TryGetComponent(out AppearanceComponent? appearance)) { @@ -138,6 +141,7 @@ namespace Content.Server.GameObjects.Components // Decrease "Ammo" Charges--; Dirty(); + return true; } void IDropped.Dropped(DroppedEventArgs eventArgs) diff --git a/Content.Server/GameObjects/Components/Culinary/UtensilComponent.cs b/Content.Server/GameObjects/Components/Culinary/UtensilComponent.cs index d6c8629c6b..01eaeb5068 100644 --- a/Content.Server/GameObjects/Components/Culinary/UtensilComponent.cs +++ b/Content.Server/GameObjects/Components/Culinary/UtensilComponent.cs @@ -107,9 +107,10 @@ namespace Content.Server.GameObjects.Components.Culinary serializer.DataField(ref _breakSound, "breakSound", "/Audio/Items/snap.ogg"); } - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { TryUseUtensil(eventArgs.User, eventArgs.Target); + return true; } private void TryUseUtensil(IEntity user, IEntity? target) diff --git a/Content.Server/GameObjects/Components/Fluids/MopComponent.cs b/Content.Server/GameObjects/Components/Fluids/MopComponent.cs index ad6e85b951..11fda8a54a 100644 --- a/Content.Server/GameObjects/Components/Fluids/MopComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/MopComponent.cs @@ -62,14 +62,16 @@ namespace Content.Server.GameObjects.Components.Fluids Owner.EnsureComponentWarn(out SolutionContainerComponent _); } - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { - if (!Owner.TryGetComponent(out SolutionContainerComponent? contents)) return; - if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return; + if (!Owner.TryGetComponent(out SolutionContainerComponent? contents)) + return false; + if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) + return false; if (CurrentVolume <= 0) { - return; + return true; } if (eventArgs.Target == null) @@ -77,12 +79,12 @@ namespace Content.Server.GameObjects.Components.Fluids // Drop the liquid on the mop on to the ground contents.SplitSolution(CurrentVolume).SpillAt(eventArgs.ClickLocation, "PuddleSmear"); - return; + return true; } if (!eventArgs.Target.TryGetComponent(out PuddleComponent? puddleComponent)) { - return; + return true; } // Essentially pickup either: // - _pickupAmount, @@ -101,7 +103,7 @@ namespace Content.Server.GameObjects.Components.Fluids } else { - return; + return true; } } else @@ -121,12 +123,12 @@ namespace Content.Server.GameObjects.Components.Fluids // Give some visual feedback shit's happening (for anyone who can't hear sound) Owner.PopupMessage(eventArgs.User, Loc.GetString("Swish")); - if (string.IsNullOrWhiteSpace(_pickupSound)) + if (!string.IsNullOrWhiteSpace(_pickupSound)) { - return; + EntitySystem.Get().PlayFromEntity(_pickupSound, Owner); } - EntitySystem.Get().PlayFromEntity(_pickupSound, Owner); + return true; } } } diff --git a/Content.Server/GameObjects/Components/Fluids/SpillableComponent.cs b/Content.Server/GameObjects/Components/Fluids/SpillableComponent.cs index 62b2a5ec6c..5026ede0e4 100644 --- a/Content.Server/GameObjects/Components/Fluids/SpillableComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/SpillableComponent.cs @@ -1,6 +1,5 @@ -using Content.Server.GameObjects.Components.Chemistry; -using Content.Shared.Chemistry; -using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Chemistry; +using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.GameObjects.Verbs; using Content.Shared.Interfaces; @@ -24,34 +23,37 @@ namespace Content.Server.GameObjects.Components.Fluids protected override void GetData(IEntity user, SpillableComponent component, VerbData data) { if (!ActionBlockerSystem.CanInteract(user) || - !component.Owner.TryGetComponent(out SolutionContainerComponent solutionComponent) || - !solutionComponent.CanRemoveSolutions) + !component.Owner.TryGetComponent(out ISolutionInteractionsComponent solutionComponent) || + !solutionComponent.CanDrain) { data.Visibility = VerbVisibility.Invisible; return; } data.Text = Loc.GetString("Spill liquid"); - data.Visibility = solutionComponent.CurrentVolume > ReagentUnit.Zero ? VerbVisibility.Visible : VerbVisibility.Disabled; + data.Visibility = solutionComponent.DrainAvailable > ReagentUnit.Zero + ? VerbVisibility.Visible + : VerbVisibility.Disabled; } protected override void Activate(IEntity user, SpillableComponent component) { - if (component.Owner.TryGetComponent(out var solutionComponent)) + if (component.Owner.TryGetComponent(out var solutionComponent)) { - if (!solutionComponent.CanRemoveSolutions) + if (!solutionComponent.CanDrain) { - user.PopupMessage(user, Loc.GetString("You can't pour anything from {0:theName}!", component.Owner)); + user.PopupMessage(user, + Loc.GetString("You can't pour anything from {0:theName}!", component.Owner)); } - if (solutionComponent.CurrentVolume.Float() <= 0) + if (solutionComponent.DrainAvailable <= 0) { user.PopupMessage(user, Loc.GetString("{0:theName} is empty!", component.Owner)); } // Need this as when we split the component's owner may be deleted var entityLocation = component.Owner.Transform.Coordinates; - var solution = solutionComponent.SplitSolution(solutionComponent.CurrentVolume); + var solution = solutionComponent.Drain(solutionComponent.DrainAvailable); solution.SpillAt(entityLocation, "PuddleSmear"); } } diff --git a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs index 8a45830637..1d020093b4 100644 --- a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs @@ -100,34 +100,34 @@ namespace Content.Server.GameObjects.Components.Fluids serializer.DataField(ref _safety, "safety", true); } - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (!ActionBlockerSystem.CanInteract(eventArgs.User)) - return; + return false; if (_hasSafety && _safety) { Owner.PopupMessage(eventArgs.User, Loc.GetString("Its safety is on!")); - return; + return true; } if (CurrentVolume <= 0) { Owner.PopupMessage(eventArgs.User, Loc.GetString("It's empty!")); - return; + return true; } var curTime = _gameTiming.CurTime; if(curTime < _cooldownEnd) - return; + return true; var playerPos = eventArgs.User.Transform.Coordinates; if (eventArgs.ClickLocation.GetGridId(_serverEntityManager) != playerPos.GetGridId(_serverEntityManager)) - return; + return true; if (!Owner.TryGetComponent(out SolutionContainerComponent contents)) - return; + return true; var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized; var threeQuarters = direction * 0.75f; @@ -183,6 +183,8 @@ namespace Content.Server.GameObjects.Components.Fluids cooldown.CooldownStart = _lastUseTime; cooldown.CooldownEnd = _cooldownEnd; } + + return true; } public bool UseEntity(UseEntityEventArgs eventArgs) diff --git a/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs b/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs index 3099b9779c..26edc3c903 100644 --- a/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs @@ -21,9 +21,10 @@ namespace Content.Server.GameObjects.Components.Interactable public override string Name => "TilePrying"; private bool _toolComponentNeeded = true; - public async Task AfterInteract(AfterInteractEventArgs eventArgs) + public async Task AfterInteract(AfterInteractEventArgs eventArgs) { TryPryTile(eventArgs.User, eventArgs.ClickLocation); + return true; } public override void ExposeData(ObjectSerializer serializer) diff --git a/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs b/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs index 21776ab4e8..76348dbeff 100644 --- a/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs @@ -2,6 +2,7 @@ using System; using System.Threading.Tasks; using Content.Server.Atmos; +using Content.Server.Explosions; using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.EntitySystems; @@ -10,12 +11,15 @@ using Content.Server.Interfaces.GameObjects; using Content.Server.Utility; using Content.Shared.Chemistry; using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; +using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; @@ -29,7 +33,7 @@ namespace Content.Server.GameObjects.Components.Interactable [ComponentReference(typeof(ToolComponent))] [ComponentReference(typeof(IToolComponent))] [ComponentReference(typeof(IHotItem))] - public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct, ISolutionChange, IHotItem + public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct, ISolutionChange, IHotItem, IAfterInteract { [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; @@ -293,5 +297,38 @@ namespace Content.Server.GameObjects.Components.Interactable } + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + { + if (eventArgs.Target == null || !eventArgs.CanReach) + { + return false; + } + + if (eventArgs.Target.TryGetComponent(out ReagentTankComponent? tank) + && tank.TankType == ReagentTankType.Fuel + && eventArgs.Target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution) + && targetSolution.CanDrain + && _solutionComponent != null) + { + if (WelderLit) + { + // Oh no no + eventArgs.Target.SpawnExplosion(); + return true; + } + + var trans = ReagentUnit.Min(_solutionComponent.EmptyVolume, targetSolution.DrainAvailable); + if (trans > 0) + { + var drained = targetSolution.Drain(trans); + _solutionComponent.TryAddSolution(drained); + + EntitySystem.Get().PlayFromEntity("/Audio/Effects/refill.ogg", Owner); + eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("Welder refueled")); + } + } + + return true; + } } } diff --git a/Content.Server/GameObjects/Components/Items/FireExtinguisherComponent.cs b/Content.Server/GameObjects/Components/Items/FireExtinguisherComponent.cs index 6159aa7f52..3edcd5b392 100644 --- a/Content.Server/GameObjects/Components/Items/FireExtinguisherComponent.cs +++ b/Content.Server/GameObjects/Components/Items/FireExtinguisherComponent.cs @@ -1,10 +1,52 @@ -using Robust.Shared.GameObjects; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Chemistry; +using Content.Shared.Chemistry; +using Content.Shared.GameObjects.Components.Chemistry; +using Content.Shared.Interfaces; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Localization; + +#nullable enable namespace Content.Server.GameObjects.Components.Items { [RegisterComponent] - public class FireExtinguisherComponent : Component + public class FireExtinguisherComponent : Component, IAfterInteract { public override string Name => "FireExtinguisher"; + + // Higher priority than sprays. + int IAfterInteract.Priority => 1; + + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + { + if (eventArgs.Target == null || !eventArgs.CanReach) + { + return false; + } + + if (eventArgs.Target.TryGetComponent(out ReagentTankComponent? tank) + && eventArgs.Target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution) + && targetSolution.CanDrain + && Owner.TryGetComponent(out SolutionContainerComponent? container)) + { + var trans = ReagentUnit.Min(container.EmptyVolume, targetSolution.DrainAvailable); + if (trans > 0) + { + var drained = targetSolution.Drain(trans); + container.TryAddSolution(drained); + + EntitySystem.Get().PlayFromEntity("/Audio/Effects/refill.ogg", Owner); + eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("{0:TheName} is now refilled", Owner)); + } + + return true; + } + + return false; + } } } diff --git a/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs b/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs index 57969eda49..341cdca33b 100644 --- a/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs +++ b/Content.Server/GameObjects/Components/Items/FloorTileItemComponent.cs @@ -56,10 +56,14 @@ namespace Content.Server.GameObjects.Components.Items EntitySystem.Get().PlayAtCoords("/Audio/Items/genhit.ogg", location, AudioHelpers.WithVariation(0.125f)); } - public async Task AfterInteract(AfterInteractEventArgs eventArgs) + public async Task AfterInteract(AfterInteractEventArgs eventArgs) { - if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return; - if (!Owner.TryGetComponent(out StackComponent stack)) return; + if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) + return true; + + if (!Owner.TryGetComponent(out StackComponent stack)) + return true; + var mapManager = IoCManager.Resolve(); var location = eventArgs.ClickLocation.AlignWithClosestGridTile(); @@ -88,10 +92,9 @@ namespace Content.Server.GameObjects.Components.Items PlaceAt(mapGrid, location, _tileDefinitionManager[_outputTiles[0]].TileId, mapGrid.TileSize / 2f); break; } - - } + return true; } } } diff --git a/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs b/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs index b79f422205..a06aa830b6 100644 --- a/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs +++ b/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs @@ -32,17 +32,17 @@ namespace Content.Server.GameObjects.Components.Items.RCD message.AddMarkup(Loc.GetString("It holds {0} charges.", refillAmmo)); } - public async Task AfterInteract(AfterInteractEventArgs eventArgs) + public async Task AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null || !eventArgs.Target.TryGetComponent(out RCDComponent rcdComponent) || !eventArgs.User.TryGetComponent(out IHandsComponent hands)) { - return; + return false; } if (rcdComponent.maxAmmo - rcdComponent._ammo < refillAmmo) { rcdComponent.Owner.PopupMessage(eventArgs.User, Loc.GetString("The RCD is full!")); - return; + return true; } rcdComponent._ammo = Math.Min(rcdComponent.maxAmmo, rcdComponent._ammo + refillAmmo); @@ -51,6 +51,7 @@ namespace Content.Server.GameObjects.Components.Items.RCD //Deleting a held item causes a lot of errors hands.Drop(Owner, false); Owner.Delete(); + return true; } } } diff --git a/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs b/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs index bef07051db..393a3780d8 100644 --- a/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs +++ b/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs @@ -94,7 +94,7 @@ namespace Content.Server.GameObjects.Components.Items.RCD message.AddMarkup(Loc.GetString("It's currently on {0} mode, and holds {1} charges.",_mode.ToString(), _ammo)); } - public async Task AfterInteract(AfterInteractEventArgs eventArgs) + public async Task AfterInteract(AfterInteractEventArgs eventArgs) { //No changing mode mid-RCD var startingMode = _mode; @@ -116,7 +116,7 @@ namespace Content.Server.GameObjects.Components.Items.RCD var result = await doAfterSystem.DoAfter(doAfterEventArgs); if (result == DoAfterStatus.Cancelled) { - return; + return true; } switch (_mode) @@ -146,12 +146,12 @@ namespace Content.Server.GameObjects.Components.Items.RCD airlock.Transform.LocalRotation = Owner.Transform.LocalRotation; //Now apply icon smoothing. break; default: - return; //I don't know why this would happen, but sure I guess. Get out of here invalid state! + return true; //I don't know why this would happen, but sure I guess. Get out of here invalid state! } _entitySystemManager.GetEntitySystem().PlayFromEntity("/Audio/Items/deconstruct.ogg", Owner); _ammo--; - + return true; } private bool IsRCDStillValid(AfterInteractEventArgs eventArgs, IMapGrid mapGrid, TileRef tile, Vector2i snapPos, RcdMode startingMode) diff --git a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs index 60aca373e1..4009844a5b 100644 --- a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs +++ b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs @@ -13,6 +13,7 @@ using Content.Server.Utility; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body.Part; +using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.Components.Power; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; @@ -213,10 +214,10 @@ namespace Content.Server.GameObjects.Components.Kitchen return false; } - if (itemEntity.TryGetComponent(out var attackPourable)) + if (itemEntity.TryGetComponent(out var attackPourable)) { - if (!itemEntity.TryGetComponent(out var attackSolution) - || !attackSolution.CanRemoveSolutions) + if (!itemEntity.TryGetComponent(out var attackSolution) + || !attackSolution.CanDrain) { return false; } @@ -235,7 +236,7 @@ namespace Content.Server.GameObjects.Components.Kitchen } //Move units from attackSolution to targetSolution - var removedSolution = attackSolution.SplitSolution(realTransferAmount); + var removedSolution = attackSolution.Drain(realTransferAmount); if (!solution.TryAddSolution(removedSolution)) { return false; diff --git a/Content.Server/GameObjects/Components/Medical/HealingComponent.cs b/Content.Server/GameObjects/Components/Medical/HealingComponent.cs index cb4684238a..abe0c0a83a 100644 --- a/Content.Server/GameObjects/Components/Medical/HealingComponent.cs +++ b/Content.Server/GameObjects/Components/Medical/HealingComponent.cs @@ -26,39 +26,41 @@ namespace Content.Server.GameObjects.Components.Medical serializer.DataField(this, h => h.Heal, "heal", new Dictionary()); } - public async Task AfterInteract(AfterInteractEventArgs eventArgs) + public async Task AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null) { - return; + return false; } if (!eventArgs.Target.TryGetComponent(out IDamageableComponent damageable)) { - return; + return true; } if (!ActionBlockerSystem.CanInteract(eventArgs.User)) { - return; + return true; } if (eventArgs.User != eventArgs.Target && !eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) { - return; + return true; } if (Owner.TryGetComponent(out StackComponent stack) && !stack.Use(1)) { - return; + return true; } foreach (var (type, amount) in Heal) { damageable.ChangeDamage(type, -amount, true); } + + return true; } } } diff --git a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs index 4990ac6075..cd41d3f2c5 100644 --- a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs @@ -3,11 +3,9 @@ using System.Threading.Tasks; using Content.Server.GameObjects.Components.Body.Behavior; using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.Fluids; -using Content.Server.GameObjects.EntitySystems; using Content.Shared.Audio; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Body; -using Content.Shared.GameObjects.Components.Body.Mechanism; using Content.Shared.GameObjects.Components.Nutrition; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; @@ -75,7 +73,7 @@ namespace Content.Server.GameObjects.Components.Nutrition _contents = Owner.AddComponent(); } - _contents.Capabilities = SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom; + _contents.Capabilities = SolutionContainerCaps.Refillable | SolutionContainerCaps.Drainable; Opened = _defaultToOpened; UpdateAppearance(); } @@ -113,9 +111,10 @@ namespace Content.Server.GameObjects.Components.Nutrition } //Force feeding a drink to someone. - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { TryUseDrink(eventArgs.Target, forced: true); + return true; } public void Examine(FormattedMessage message, bool inDetailsRange) @@ -131,7 +130,7 @@ namespace Content.Server.GameObjects.Components.Nutrition private bool TryUseDrink(IEntity target, bool forced = false) { - if (target == null || !_contents.CanRemoveSolutions) + if (target == null || !_contents.CanDrain) { return false; } diff --git a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs index b7318fdce1..0cb8436e11 100644 --- a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs @@ -100,14 +100,15 @@ namespace Content.Server.GameObjects.Components.Nutrition } // Feeding someone else - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null) { - return; + return false; } TryUseFood(eventArgs.User, eventArgs.Target); + return true; } public virtual bool TryUseFood(IEntity? user, IEntity? target, UtensilComponent? utensilUsed = null) diff --git a/Content.Server/GameObjects/Components/Portal/TeleporterComponent.cs b/Content.Server/GameObjects/Components/Portal/TeleporterComponent.cs index f53d4b8f1d..b92632aa3b 100644 --- a/Content.Server/GameObjects/Components/Portal/TeleporterComponent.cs +++ b/Content.Server/GameObjects/Components/Portal/TeleporterComponent.cs @@ -23,7 +23,7 @@ using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Portal { [RegisterComponent] - public class TeleporterComponent : Component, IAfterInteract + public class TeleporterComponent : Component, IAfterInteract { [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; [Dependency] private readonly IRobustRandom _spreadRandom = default!; @@ -78,7 +78,7 @@ namespace Content.Server.GameObjects.Components.Portal _state = newState; } - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (_teleporterType == TeleporterType.Directed) { @@ -89,6 +89,8 @@ namespace Content.Server.GameObjects.Components.Portal { TryRandomTeleport(eventArgs.User); } + + return true; } public void TryDirectedTeleport(IEntity user, MapCoordinates mapCoords) diff --git a/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs b/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs index 81d8654360..e3a33418ee 100644 --- a/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs +++ b/Content.Server/GameObjects/Components/Power/WirePlacerComponent.cs @@ -34,28 +34,29 @@ namespace Content.Server.GameObjects.Components.Power } /// - public async Task AfterInteract(AfterInteractEventArgs eventArgs) + public async Task AfterInteract(AfterInteractEventArgs eventArgs) { if (_wirePrototypeID == null) - return; - - if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return; + return true; + if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) + return true; if(!_mapManager.TryGetGrid(eventArgs.ClickLocation.GetGridId(Owner.EntityManager), out var grid)) - return; + return true; var snapPos = grid.SnapGridCellFor(eventArgs.ClickLocation, SnapGridOffset.Center); var snapCell = grid.GetSnapGridCell(snapPos, SnapGridOffset.Center); if(grid.GetTileRef(snapPos).Tile.IsEmpty) - return; + return true; foreach (var snapComp in snapCell) { if (snapComp.Owner.TryGetComponent(out var wire) && wire.WireType == _blockingWireType) { - return; + return true; } } if (Owner.TryGetComponent(out var stack) && !stack.Use(1)) - return; + return true; Owner.EntityManager.SpawnEntity(_wirePrototypeID, grid.GridTileToLocal(snapPos)); + return true; } } } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs index f674459c2a..5ddf430552 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs @@ -146,11 +146,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition return entity; } - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null) { - return; + return false; } // This area is dirty but not sure of an easier way to do it besides add an interface or somethin @@ -203,6 +203,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition { UpdateAppearance(); } + + return true; } async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index 552d3c43c5..40ee427b78 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -416,13 +416,8 @@ namespace Content.Server.GameObjects.EntitySystems.Click return; } - var afterInteracts = weapon.GetAllComponents().ToList(); var afterInteractEventArgs = new AfterInteractEventArgs(user, clickLocation, null, canReach); - - foreach (var afterInteract in afterInteracts) - { - await afterInteract.AfterInteract(afterInteractEventArgs); - } + await DoAfterInteract(weapon, afterInteractEventArgs); } /// @@ -465,13 +460,9 @@ namespace Content.Server.GameObjects.EntitySystems.Click } // If we aren't directly attacking the nearby object, lets see if our item has an after attack we can do - var afterAttacks = weapon.GetAllComponents().ToList(); var afterAttackEventArgs = new AfterInteractEventArgs(user, clickLocation, attacked, canReach: true); - foreach (var afterAttack in afterAttacks) - { - await afterAttack.AfterInteract(afterAttackEventArgs); - } + await DoAfterInteract(weapon, afterAttackEventArgs); } /// @@ -835,13 +826,21 @@ namespace Content.Server.GameObjects.EntitySystems.Click if (afterAtkMsg.Handled) return; - var afterAttacks = weapon.GetAllComponents().ToList(); + // See if we have a ranged attack interaction var afterAttackEventArgs = new AfterInteractEventArgs(user, clickLocation, attacked, canReach: false); + await DoAfterInteract(weapon, afterAttackEventArgs); + } + + private static async Task DoAfterInteract(IEntity weapon, AfterInteractEventArgs afterAttackEventArgs) + { + var afterAttacks = weapon.GetAllComponents().OrderByDescending(x => x.Priority).ToList(); - //See if we have a ranged attack interaction foreach (var afterAttack in afterAttacks) { - await afterAttack.AfterInteract(afterAttackEventArgs); + if (await afterAttack.AfterInteract(afterAttackEventArgs)) + { + return; + } } } diff --git a/Content.Shared/Chemistry/Solution.cs b/Content.Shared/Chemistry/Solution.cs index 7fb2315c1e..cd6d801210 100644 --- a/Content.Shared/Chemistry/Solution.cs +++ b/Content.Shared/Chemistry/Solution.cs @@ -92,7 +92,7 @@ namespace Content.Shared.Chemistry return ""; } - var majorReagent = Contents.OrderByDescending(reagent => reagent.Quantity).First(); ; + var majorReagent = Contents.OrderByDescending(reagent => reagent.Quantity).First(); return majorReagent.ReagentId; } diff --git a/Content.Shared/Chemistry/SolutionCaps.cs b/Content.Shared/Chemistry/SolutionCaps.cs deleted file mode 100644 index 3dada4d98e..0000000000 --- a/Content.Shared/Chemistry/SolutionCaps.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Robust.Shared.Serialization; - -namespace Content.Shared.Chemistry -{ - /// - /// These are the defined capabilities of a container of a solution. - /// - [Flags] - [Serializable, NetSerializable] - public enum SolutionContainerCaps - { - None = 0, - - /// - /// Can solutions be added into the container? - /// - AddTo = 1, - - /// - /// Can solutions be removed from the container? - /// - RemoveFrom = 2, - - /// - /// Allows the container to be placed in a ReagentDispenserComponent. - /// Otherwise it's considered to be too large or the improper shape to fit. - /// Allows us to have obscenely large containers that are harder to abuse in chem dispensers - /// since they can't be placed directly in them. - /// - FitsInDispenser = 4, - - /// - /// Can people examine the solution in the container or is it impossible to see? - /// - CanExamine = 8, - } - - public static class SolutionContainerCapsHelpers - { - public static bool HasCap(this SolutionContainerCaps cap, SolutionContainerCaps other) - { - return (cap & other) == other; - } - } -} diff --git a/Content.Shared/Chemistry/SolutionContainerCaps.cs b/Content.Shared/Chemistry/SolutionContainerCaps.cs new file mode 100644 index 0000000000..47d8a76371 --- /dev/null +++ b/Content.Shared/Chemistry/SolutionContainerCaps.cs @@ -0,0 +1,62 @@ +using System; +using Content.Shared.GameObjects.Components.Chemistry; +using Robust.Shared.Serialization; + +namespace Content.Shared.Chemistry +{ + /// + /// Define common interaction behaviors for + /// + /// + [Flags] + [Serializable, NetSerializable] + public enum SolutionContainerCaps : ushort + { + None = 0, + + /// + /// Reagents can be added with syringes. + /// + Injectable = 1 << 0, + + /// + /// Reagents can be removed with syringes. + /// + Drawable = 1 << 1, + + /// + /// Reagents can be easily added via all reagent containers. + /// Think pouring something into another beaker or into the gas tank of a car. + /// + Refillable = 1 << 2, + + /// + /// Reagents can be easily removed through any reagent container. + /// Think pouring this or draining from a water tank. + /// + Drainable = 1 << 3, + + /// + /// The contents of the solution can be examined directly. + /// + CanExamine = 1 << 4, + + /// + /// Allows the container to be placed in a ReagentDispenserComponent. + /// Otherwise it's considered to be too large or the improper shape to fit. + /// Allows us to have obscenely large containers that are harder to abuse in chem dispensers + /// since they can't be placed directly in them. + /// + FitsInDispenser = 1 << 5, + + OpenContainer = Refillable | Drainable | CanExamine + } + + public static class SolutionContainerCapsHelpers + { + public static bool HasCap(this SolutionContainerCaps cap, SolutionContainerCaps other) + { + return (cap & other) == other; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Chemistry/ISolutionInteractionsComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/ISolutionInteractionsComponent.cs new file mode 100644 index 0000000000..c88c46234d --- /dev/null +++ b/Content.Shared/GameObjects/Components/Chemistry/ISolutionInteractionsComponent.cs @@ -0,0 +1,84 @@ +using Content.Shared.Chemistry; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.Components.Chemistry +{ + /// + /// High-level solution transferring operations like "what happens when a syringe tries to inject this entity." + /// + /// + /// This interface is most often implemented by using + /// and setting the appropriate + /// + public interface ISolutionInteractionsComponent : IComponent + { + // + // INJECTING + // + + /// + /// Whether we CAN POTENTIALLY be injected with solutions by items like syringes. + /// + /// + /// + /// This should NOT change to communicate behavior like "the container is full". + /// Change to 0 for that. + /// + /// + /// If refilling is allowed () you should also always allow injecting. + /// + /// + bool CanInject => false; + + /// + /// The amount of solution space available for injecting. + /// + ReagentUnit InjectSpaceAvailable => ReagentUnit.Zero; + + /// + /// Actually inject reagents. + /// + void Inject(Solution solution) + { + + } + + // + // DRAWING + // + + bool CanDraw => false; + ReagentUnit DrawAvailable => ReagentUnit.Zero; + + Solution Draw(ReagentUnit amount) + { + return new(); + } + + + + // + // REFILLING + // + + bool CanRefill => false; + ReagentUnit RefillSpaceAvailable => ReagentUnit.Zero; + + void Refill(Solution solution) + { + + } + + // + // DRAINING + // + + bool CanDrain => false; + ReagentUnit DrainAvailable => ReagentUnit.Zero; + + Solution Drain(ReagentUnit amount) + { + return new(); + } + } +} diff --git a/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionContainerComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionContainerComponent.cs index e453f4aeda..0a7d1fe7d2 100644 --- a/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionContainerComponent.cs +++ b/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionContainerComponent.cs @@ -20,7 +20,7 @@ namespace Content.Shared.GameObjects.Components.Chemistry /// /// Holds a with a limited volume. /// - public abstract class SharedSolutionContainerComponent : Component, IExamine + public abstract class SharedSolutionContainerComponent : Component, IExamine, ISolutionInteractionsComponent { public override string Name => "SolutionContainer"; @@ -60,9 +60,11 @@ namespace Content.Shared.GameObjects.Components.Chemistry public bool CanUseWithChemDispenser => Capabilities.HasCap(SolutionContainerCaps.FitsInDispenser); - public bool CanAddSolutions => Capabilities.HasCap(SolutionContainerCaps.AddTo); + public bool CanInject => Capabilities.HasCap(SolutionContainerCaps.Injectable) || CanRefill; + public bool CanDraw => Capabilities.HasCap(SolutionContainerCaps.Drawable) || CanDrain; - public bool CanRemoveSolutions => Capabilities.HasCap(SolutionContainerCaps.RemoveFrom); + public bool CanRefill => Capabilities.HasCap(SolutionContainerCaps.Refillable); + public bool CanDrain => Capabilities.HasCap(SolutionContainerCaps.Drainable); public override void ExposeData(ObjectSerializer serializer) { @@ -71,7 +73,7 @@ namespace Content.Shared.GameObjects.Components.Chemistry serializer.DataField(this, x => x.CanReact, "canReact", true); serializer.DataField(this, x => x.MaxVolume, "maxVol", ReagentUnit.New(0)); serializer.DataField(this, x => x.Solution, "contents", new Solution()); - serializer.DataField(this, x => x.Capabilities, "caps", SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom | SolutionContainerCaps.CanExamine); + serializer.DataField(this, x => x.Capabilities, "caps", SolutionContainerCaps.None); } public void RemoveAllSolution() @@ -209,6 +211,43 @@ namespace Content.Shared.GameObjects.Components.Chemistry message.AddMarkup(Loc.GetString(messageString, colorHex, Loc.GetString(proto.PhysicalDescription))); } + ReagentUnit ISolutionInteractionsComponent.RefillSpaceAvailable => EmptyVolume; + ReagentUnit ISolutionInteractionsComponent.InjectSpaceAvailable => EmptyVolume; + ReagentUnit ISolutionInteractionsComponent.DrawAvailable => CurrentVolume; + ReagentUnit ISolutionInteractionsComponent.DrainAvailable => CurrentVolume; + + void ISolutionInteractionsComponent.Refill(Solution solution) + { + if (!CanRefill) + return; + + TryAddSolution(solution); + } + + void ISolutionInteractionsComponent.Inject(Solution solution) + { + if (!CanInject) + return; + + TryAddSolution(solution); + } + + Solution ISolutionInteractionsComponent.Draw(ReagentUnit amount) + { + if (!CanDraw) + return new Solution(); + + return SplitSolution(amount); + } + + Solution ISolutionInteractionsComponent.Drain(ReagentUnit amount) + { + if (!CanDrain) + return new Solution(); + + return SplitSolution(amount); + } + private void UpdateAppearance() { if (Owner.Deleted || !Owner.TryGetComponent(out var appearance)) diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs index 16299a23c9..716d0b3fd7 100644 --- a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs @@ -16,10 +16,16 @@ namespace Content.Shared.Interfaces.GameObjects.Components /// public interface IAfterInteract { + /// + /// The interaction priority. Higher numbers get called first. + /// + /// Priority defaults to 0 + int Priority => 0; + /// /// Called when we interact with nothing, or when we interact with an entity out of range that has no behavior /// - Task AfterInteract(AfterInteractEventArgs eventArgs); + Task AfterInteract(AfterInteractEventArgs eventArgs); } public class AfterInteractEventArgs : EventArgs diff --git a/Resources/Audio/Effects/refill.ogg b/Resources/Audio/Effects/refill.ogg new file mode 100644 index 0000000000000000000000000000000000000000..ec5c4dc34442acf29766ea43e4bae49ed2a4ff7f GIT binary patch literal 14607 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 True True + True True True True From 684ec60be6dc4c9b97f580891d0b04b30100020c Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 4 Feb 2021 00:20:48 +1100 Subject: [PATCH 49/61] Pausing content (#3061) * Change EntityQuery to not retrieve paused by default * GetAllComponents Co-authored-by: Metal Gear Sloth --- Content.Benchmarks/ComponentManagerGetAllComponents.cs | 2 +- Content.Client/Commands/DebugCommands.cs | 2 +- Content.Client/Commands/HideMechanismsCommand.cs | 2 +- Content.Client/Commands/ShowMechanismsCommand.cs | 2 +- .../GameObjects/Components/Observer/GhostComponent.cs | 2 +- .../GameObjects/EntitySystems/CameraRecoilSystem.cs | 2 +- .../GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs | 2 +- Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs | 2 +- Content.Client/GameObjects/EntitySystems/MarkerSystem.cs | 2 +- Content.Client/GameObjects/EntitySystems/MeleeLungeSystem.cs | 2 +- Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs | 2 +- .../GameObjects/EntitySystems/SubFloorHideSystem.cs | 2 +- Content.Client/StationEvents/RadiationPulseOverlay.cs | 2 +- Content.Server/Administration/Commands/DeleteComponent.cs | 2 +- Content.Server/Administration/Commands/WarpCommand.cs | 4 ++-- .../GameObjects/Components/Observer/GhostComponent.cs | 2 +- .../GameObjects/EntitySystems/AI/AiFactionTagSystem.cs | 2 +- .../GameObjects/EntitySystems/AntimatterEngineSystem.cs | 2 +- .../GameObjects/EntitySystems/AtmosExposedSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/BlockGameSystem.cs | 2 +- .../GameObjects/EntitySystems/CargoConsoleSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/ClimbSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/CloningSystem.cs | 4 ++-- Content.Server/GameObjects/EntitySystems/ConveyorSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/DisposableSystem.cs | 2 +- .../GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs | 4 +--- .../GameObjects/EntitySystems/ExpendableLightSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/GasAnalyzerSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/GasCanisterSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/GasTankSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/GravitySystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/HungerSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/InstrumentSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/LatheSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/ListeningSystem.cs | 2 +- .../GameObjects/EntitySystems/MedicalScannerSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/MetabolismSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/MicrowaveSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/MorgueSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/PlantSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/PointingSystem.cs | 4 ++-- .../GameObjects/EntitySystems/Power/BaseChargerSystem.cs | 2 +- .../GameObjects/EntitySystems/Power/BatterySystem.cs | 2 +- .../GameObjects/EntitySystems/Power/PowerSmesSystem.cs | 2 +- .../EntitySystems/Power/PowerSolarControlConsoleSystem.cs | 2 +- .../GameObjects/EntitySystems/Power/PowerSolarSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/ProjectileSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/PuddleSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/RadioSystem.cs | 2 +- .../GameObjects/EntitySystems/ReagentGrinderSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/RecyclerSystem.cs | 2 +- .../GameObjects/EntitySystems/RoguePointingSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/SingularitySystem.cs | 2 +- .../EntitySystems/StationEvents/RadiationPulseSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/StorageSystem.cs | 2 +- .../GameObjects/EntitySystems/StressTestMovementSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/StunSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/ThirstSystem.cs | 2 +- Content.Server/GameObjects/EntitySystems/VaporSystem.cs | 2 +- .../Objectives/Conditions/KillRandomPersonCondition.cs | 2 +- Content.Server/StationEvents/PowerGridCheck.cs | 2 +- Content.Shared/GameObjects/EntitySystems/MechanismSystem.cs | 2 +- .../GameObjects/EntitySystems/SharedDisposalUnitSystem.cs | 4 ++-- Content.Shared/GameObjects/EntitySystems/SlipperySystem.cs | 2 +- 65 files changed, 69 insertions(+), 71 deletions(-) diff --git a/Content.Benchmarks/ComponentManagerGetAllComponents.cs b/Content.Benchmarks/ComponentManagerGetAllComponents.cs index 46e6150f1e..ec8ae2f3d9 100644 --- a/Content.Benchmarks/ComponentManagerGetAllComponents.cs +++ b/Content.Benchmarks/ComponentManagerGetAllComponents.cs @@ -79,7 +79,7 @@ namespace Content.Benchmarks { var count = 0; - foreach (var _ in _componentManager.EntityQuery()) + foreach (var _ in _componentManager.EntityQuery(true)) { count += 1; } diff --git a/Content.Client/Commands/DebugCommands.cs b/Content.Client/Commands/DebugCommands.cs index 0ea5efc6c7..15879deece 100644 --- a/Content.Client/Commands/DebugCommands.cs +++ b/Content.Client/Commands/DebugCommands.cs @@ -52,7 +52,7 @@ namespace Content.Client.Commands .EnableAll = true; var components = IoCManager.Resolve().ComponentManager - .EntityQuery(); + .EntityQuery(true); foreach (var component in components) { diff --git a/Content.Client/Commands/HideMechanismsCommand.cs b/Content.Client/Commands/HideMechanismsCommand.cs index 9e7bde255c..bbe5d9f769 100644 --- a/Content.Client/Commands/HideMechanismsCommand.cs +++ b/Content.Client/Commands/HideMechanismsCommand.cs @@ -17,7 +17,7 @@ namespace Content.Client.Commands public void Execute(IConsoleShell shell, string argStr, string[] args) { var componentManager = IoCManager.Resolve(); - var mechanisms = componentManager.EntityQuery(); + var mechanisms = componentManager.EntityQuery(true); foreach (var mechanism in mechanisms) { diff --git a/Content.Client/Commands/ShowMechanismsCommand.cs b/Content.Client/Commands/ShowMechanismsCommand.cs index fe0381a3c6..85ab4583a4 100644 --- a/Content.Client/Commands/ShowMechanismsCommand.cs +++ b/Content.Client/Commands/ShowMechanismsCommand.cs @@ -19,7 +19,7 @@ namespace Content.Client.Commands public void Execute(IConsoleShell shell, string argStr, string[] args) { var componentManager = IoCManager.Resolve(); - var mechanisms = componentManager.EntityQuery(); + var mechanisms = componentManager.EntityQuery(true); foreach (var mechanism in mechanisms) { diff --git a/Content.Client/GameObjects/Components/Observer/GhostComponent.cs b/Content.Client/GameObjects/Components/Observer/GhostComponent.cs index 8af023096f..b42f8439e0 100644 --- a/Content.Client/GameObjects/Components/Observer/GhostComponent.cs +++ b/Content.Client/GameObjects/Components/Observer/GhostComponent.cs @@ -46,7 +46,7 @@ namespace Content.Client.GameObjects.Components.Observer private void SetGhostVisibility(bool visibility) { - foreach (var ghost in _componentManager.GetAllComponents(typeof(GhostComponent))) + foreach (var ghost in _componentManager.GetAllComponents(typeof(GhostComponent), true)) { if (ghost.Owner.TryGetComponent(out SpriteComponent? component)) { diff --git a/Content.Client/GameObjects/EntitySystems/CameraRecoilSystem.cs b/Content.Client/GameObjects/EntitySystems/CameraRecoilSystem.cs index f37f9c8539..6f6f598b4c 100644 --- a/Content.Client/GameObjects/EntitySystems/CameraRecoilSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/CameraRecoilSystem.cs @@ -11,7 +11,7 @@ namespace Content.Client.GameObjects.EntitySystems { base.FrameUpdate(frameTime); - foreach (var recoil in EntityManager.ComponentManager.EntityQuery()) + foreach (var recoil in EntityManager.ComponentManager.EntityQuery(true)) { recoil.FrameUpdate(frameTime); } diff --git a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs index 7f911cfcb4..c93905ded1 100644 --- a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs @@ -61,7 +61,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter if (_attachedEntity == null || _attachedEntity.Deleted) return; - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { if (!_knownComponents.Contains(comp)) { diff --git a/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs b/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs index 59c22113c1..4d4e7c907a 100644 --- a/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs @@ -44,7 +44,7 @@ namespace Content.Client.GameObjects.EntitySystems return; } - foreach (var instrumentComponent in EntityManager.ComponentManager.EntityQuery()) + foreach (var instrumentComponent in EntityManager.ComponentManager.EntityQuery(true)) { instrumentComponent.Update(frameTime); } diff --git a/Content.Client/GameObjects/EntitySystems/MarkerSystem.cs b/Content.Client/GameObjects/EntitySystems/MarkerSystem.cs index bec86abbb4..f6acf68b4f 100644 --- a/Content.Client/GameObjects/EntitySystems/MarkerSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/MarkerSystem.cs @@ -19,7 +19,7 @@ namespace Content.Client.GameObjects.EntitySystems private void UpdateMarkers() { - foreach (var markerComponent in EntityManager.ComponentManager.EntityQuery()) + foreach (var markerComponent in EntityManager.ComponentManager.EntityQuery(true)) { markerComponent.UpdateVisibility(); } diff --git a/Content.Client/GameObjects/EntitySystems/MeleeLungeSystem.cs b/Content.Client/GameObjects/EntitySystems/MeleeLungeSystem.cs index 3f180d4399..f61ae4506f 100644 --- a/Content.Client/GameObjects/EntitySystems/MeleeLungeSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/MeleeLungeSystem.cs @@ -11,7 +11,7 @@ namespace Content.Client.GameObjects.EntitySystems { base.FrameUpdate(frameTime); - foreach (var meleeLungeComponent in EntityManager.ComponentManager.EntityQuery()) + foreach (var meleeLungeComponent in EntityManager.ComponentManager.EntityQuery(true)) { meleeLungeComponent.Update(frameTime); } diff --git a/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs b/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs index 1133c1a8d2..f3d7883d8d 100644 --- a/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs @@ -34,7 +34,7 @@ namespace Content.Client.GameObjects.EntitySystems { base.FrameUpdate(frameTime); - foreach (var arcAnimationComponent in EntityManager.ComponentManager.EntityQuery()) + foreach (var arcAnimationComponent in EntityManager.ComponentManager.EntityQuery(true)) { arcAnimationComponent.Update(frameTime); } diff --git a/Content.Client/GameObjects/EntitySystems/SubFloorHideSystem.cs b/Content.Client/GameObjects/EntitySystems/SubFloorHideSystem.cs index d4f4352bd9..249df06da8 100644 --- a/Content.Client/GameObjects/EntitySystems/SubFloorHideSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/SubFloorHideSystem.cs @@ -35,7 +35,7 @@ namespace Content.Client.GameObjects.EntitySystems private void UpdateAll() { - foreach (var comp in EntityManager.ComponentManager.EntityQuery()) + foreach (var comp in EntityManager.ComponentManager.EntityQuery(true)) { if (!_mapManager.TryGetGrid(comp.Owner.Transform.GridID, out var grid)) return; diff --git a/Content.Client/StationEvents/RadiationPulseOverlay.cs b/Content.Client/StationEvents/RadiationPulseOverlay.cs index b08d38358a..91917ea9dc 100644 --- a/Content.Client/StationEvents/RadiationPulseOverlay.cs +++ b/Content.Client/StationEvents/RadiationPulseOverlay.cs @@ -124,7 +124,7 @@ namespace Content.Client.StationEvents _lastTick = _gameTiming.CurTime; var radiationPulses = _componentManager - .EntityQuery() + .EntityQuery(true) .ToList(); var screenHandle = (DrawingHandleScreen) handle; diff --git a/Content.Server/Administration/Commands/DeleteComponent.cs b/Content.Server/Administration/Commands/DeleteComponent.cs index 8a6777d7fc..5e6941b52f 100644 --- a/Content.Server/Administration/Commands/DeleteComponent.cs +++ b/Content.Server/Administration/Commands/DeleteComponent.cs @@ -33,7 +33,7 @@ namespace Content.Server.Administration.Commands } var componentType = registration.Type; - var components = entityManager.ComponentManager.GetAllComponents(componentType); + var components = entityManager.ComponentManager.GetAllComponents(componentType, true); var i = 0; diff --git a/Content.Server/Administration/Commands/WarpCommand.cs b/Content.Server/Administration/Commands/WarpCommand.cs index a8972487b7..9170dc2da0 100644 --- a/Content.Server/Administration/Commands/WarpCommand.cs +++ b/Content.Server/Administration/Commands/WarpCommand.cs @@ -43,7 +43,7 @@ namespace Content.Server.Administration.Commands if (location == "?") { var locations = string.Join(", ", - comp.EntityQuery() + comp.EntityQuery(true) .Select(p => p.Location) .Where(p => p != null) .OrderBy(p => p) @@ -64,7 +64,7 @@ namespace Content.Server.Administration.Commands var currentGrid = player.AttachedEntity.Transform.GridID; var entityManager = IoCManager.Resolve(); - var found = comp.EntityQuery() + var found = comp.EntityQuery(true) .Where(p => p.Location == location) .Select(p => p.Owner.Transform.Coordinates) .OrderBy(p => p, Comparer.Create((a, b) => diff --git a/Content.Server/GameObjects/Components/Observer/GhostComponent.cs b/Content.Server/GameObjects/Components/Observer/GhostComponent.cs index 8fd45b1bdc..15b909435b 100644 --- a/Content.Server/GameObjects/Components/Observer/GhostComponent.cs +++ b/Content.Server/GameObjects/Components/Observer/GhostComponent.cs @@ -144,7 +144,7 @@ namespace Content.Server.GameObjects.Components.Observer private List FindWaypoints() { var comp = IoCManager.Resolve(); - return comp.EntityQuery().ToList(); + return comp.EntityQuery(true).ToList(); } public void Examine(FormattedMessage message, bool inDetailsRange) diff --git a/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs index dc83c30d67..acfb7c0277 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs @@ -48,7 +48,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI yield break; } - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { if ((component.Factions & hostile) == 0) continue; diff --git a/Content.Server/GameObjects/EntitySystems/AntimatterEngineSystem.cs b/Content.Server/GameObjects/EntitySystems/AntimatterEngineSystem.cs index 3f4576fb84..0c73f45322 100644 --- a/Content.Server/GameObjects/EntitySystems/AntimatterEngineSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AntimatterEngineSystem.cs @@ -15,7 +15,7 @@ namespace Content.Server.GameObjects.EntitySystems _accumulatedFrameTime += frameTime; if (_accumulatedFrameTime >= 10) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.OnUpdate(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/AtmosExposedSystem.cs b/Content.Server/GameObjects/EntitySystems/AtmosExposedSystem.cs index 94b0c3adb6..8dad965d5e 100644 --- a/Content.Server/GameObjects/EntitySystems/AtmosExposedSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AtmosExposedSystem.cs @@ -21,7 +21,7 @@ namespace Content.Server.GameObjects.EntitySystems if (_lastUpdate < UpdateDelay) return; // creadth: everything exposable by atmos should be updated as well - foreach (var atmosExposedComponent in EntityManager.ComponentManager.EntityQuery()) + foreach (var atmosExposedComponent in EntityManager.ComponentManager.EntityQuery(true)) { var tile = atmosExposedComponent.Owner.Transform.Coordinates.GetTileAtmosphere(EntityManager); if (tile == null) continue; diff --git a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs index 2864d813b0..71b0856e99 100644 --- a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs @@ -143,7 +143,7 @@ namespace Content.Server.GameObjects.EntitySystems { base.Update(frameTime); - foreach (var (mapGridComponent, gridAtmosphereComponent) in EntityManager.ComponentManager.EntityQuery()) + foreach (var (mapGridComponent, gridAtmosphereComponent) in EntityManager.ComponentManager.EntityQuery(true)) { if (_pauseManager.IsGridPaused(mapGridComponent.GridIndex)) continue; diff --git a/Content.Server/GameObjects/EntitySystems/BlockGameSystem.cs b/Content.Server/GameObjects/EntitySystems/BlockGameSystem.cs index 37a881d1bf..5ebfe7dc5c 100644 --- a/Content.Server/GameObjects/EntitySystems/BlockGameSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/BlockGameSystem.cs @@ -63,7 +63,7 @@ namespace Content.Server.GameObjects.EntitySystems public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.DoGameTick(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/CargoConsoleSystem.cs b/Content.Server/GameObjects/EntitySystems/CargoConsoleSystem.cs index ad6ffad73c..c30bd6052f 100644 --- a/Content.Server/GameObjects/EntitySystems/CargoConsoleSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/CargoConsoleSystem.cs @@ -180,7 +180,7 @@ namespace Content.Server.GameObjects.EntitySystems private void SyncComponentsWithId(int id) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { if (!comp.ConnectedToDatabase || comp.Database.Id != id) continue; diff --git a/Content.Server/GameObjects/EntitySystems/ClimbSystem.cs b/Content.Server/GameObjects/EntitySystems/ClimbSystem.cs index f4d5b0f6cf..7f0a0a3857 100644 --- a/Content.Server/GameObjects/EntitySystems/ClimbSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ClimbSystem.cs @@ -9,7 +9,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.Update(); } diff --git a/Content.Server/GameObjects/EntitySystems/CloningSystem.cs b/Content.Server/GameObjects/EntitySystems/CloningSystem.cs index f759a5d139..438d26269d 100644 --- a/Content.Server/GameObjects/EntitySystems/CloningSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/CloningSystem.cs @@ -11,7 +11,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.Update(frameTime); } @@ -23,7 +23,7 @@ namespace Content.Server.GameObjects.EntitySystems { if (!Minds.ContainsValue(mind)) { - Minds.Add(Minds.Count(), mind); + Minds.Add(Minds.Count, mind); } } diff --git a/Content.Server/GameObjects/EntitySystems/ConveyorSystem.cs b/Content.Server/GameObjects/EntitySystems/ConveyorSystem.cs index b692bdd1bd..30a61761bf 100644 --- a/Content.Server/GameObjects/EntitySystems/ConveyorSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ConveyorSystem.cs @@ -11,7 +11,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/DisposableSystem.cs b/Content.Server/GameObjects/EntitySystems/DisposableSystem.cs index 536f0dfda4..eef8d319e8 100644 --- a/Content.Server/GameObjects/EntitySystems/DisposableSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/DisposableSystem.cs @@ -10,7 +10,7 @@ namespace Content.Server.GameObjects.EntitySystems public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs b/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs index 93dc9478bd..226d942b00 100644 --- a/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs @@ -17,10 +17,8 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter { base.Update(frameTime); - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { - if (comp.Owner.Paused) continue; - var cancelled = new List(0); var finished = new List(0); diff --git a/Content.Server/GameObjects/EntitySystems/ExpendableLightSystem.cs b/Content.Server/GameObjects/EntitySystems/ExpendableLightSystem.cs index 4feba72bb0..36c9c3ab87 100644 --- a/Content.Server/GameObjects/EntitySystems/ExpendableLightSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ExpendableLightSystem.cs @@ -9,7 +9,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var light in ComponentManager.EntityQuery()) + foreach (var light in ComponentManager.EntityQuery(true)) { light.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/GasAnalyzerSystem.cs b/Content.Server/GameObjects/EntitySystems/GasAnalyzerSystem.cs index cf452f4111..d0a65cfa6e 100644 --- a/Content.Server/GameObjects/EntitySystems/GasAnalyzerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/GasAnalyzerSystem.cs @@ -9,7 +9,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var analyzer in ComponentManager.EntityQuery()) + foreach (var analyzer in ComponentManager.EntityQuery(true)) { analyzer.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/GasCanisterSystem.cs b/Content.Server/GameObjects/EntitySystems/GasCanisterSystem.cs index a9c74af4f7..b94b2fed60 100644 --- a/Content.Server/GameObjects/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/GasCanisterSystem.cs @@ -10,7 +10,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { component.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/GasTankSystem.cs b/Content.Server/GameObjects/EntitySystems/GasTankSystem.cs index fe88bc52f3..657e618c1f 100644 --- a/Content.Server/GameObjects/EntitySystems/GasTankSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/GasTankSystem.cs @@ -23,7 +23,7 @@ namespace Content.Server.GameObjects.EntitySystems if (_timer < Interval) return; _timer = 0f; - foreach (var gasTank in EntityManager.ComponentManager.EntityQuery()) + foreach (var gasTank in EntityManager.ComponentManager.EntityQuery(true)) { gasTank.Update(); } diff --git a/Content.Server/GameObjects/EntitySystems/GravitySystem.cs b/Content.Server/GameObjects/EntitySystems/GravitySystem.cs index 24f6057ba9..ca0b8572a5 100644 --- a/Content.Server/GameObjects/EntitySystems/GravitySystem.cs +++ b/Content.Server/GameObjects/EntitySystems/GravitySystem.cs @@ -36,7 +36,7 @@ namespace Content.Server.GameObjects.EntitySystems { _internalTimer += frameTime; var gridsWithGravity = new List(); - foreach (var generator in ComponentManager.EntityQuery()) + foreach (var generator in ComponentManager.EntityQuery(true)) { if (generator.NeedsUpdate) { diff --git a/Content.Server/GameObjects/EntitySystems/HungerSystem.cs b/Content.Server/GameObjects/EntitySystems/HungerSystem.cs index bd8cd6a866..18a34dbae3 100644 --- a/Content.Server/GameObjects/EntitySystems/HungerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/HungerSystem.cs @@ -15,7 +15,7 @@ namespace Content.Server.GameObjects.EntitySystems if (_accumulatedFrameTime > 1) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.OnUpdate(_accumulatedFrameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/InstrumentSystem.cs b/Content.Server/GameObjects/EntitySystems/InstrumentSystem.cs index 80a50a93b8..a62c9b2962 100644 --- a/Content.Server/GameObjects/EntitySystems/InstrumentSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/InstrumentSystem.cs @@ -51,7 +51,7 @@ namespace Content.Server.GameObjects.EntitySystems { base.Update(frameTime); - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { component.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/LatheSystem.cs b/Content.Server/GameObjects/EntitySystems/LatheSystem.cs index 777c22c82c..7fc92095be 100644 --- a/Content.Server/GameObjects/EntitySystems/LatheSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/LatheSystem.cs @@ -9,7 +9,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { if (comp.Producing == false && comp.Queue.Count > 0) { diff --git a/Content.Server/GameObjects/EntitySystems/ListeningSystem.cs b/Content.Server/GameObjects/EntitySystems/ListeningSystem.cs index eb92727745..6b6a42a357 100644 --- a/Content.Server/GameObjects/EntitySystems/ListeningSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ListeningSystem.cs @@ -10,7 +10,7 @@ namespace Content.Server.GameObjects.EntitySystems { public void PingListeners(IEntity source, string message) { - foreach (var listener in ComponentManager.EntityQuery()) + foreach (var listener in ComponentManager.EntityQuery(true)) { // TODO: Map Position distance if (listener.CanListen(message, source)) diff --git a/Content.Server/GameObjects/EntitySystems/MedicalScannerSystem.cs b/Content.Server/GameObjects/EntitySystems/MedicalScannerSystem.cs index 01d1cd20ac..d7f00b8437 100644 --- a/Content.Server/GameObjects/EntitySystems/MedicalScannerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/MedicalScannerSystem.cs @@ -10,7 +10,7 @@ namespace Content.Server.GameObjects.EntitySystems public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/MetabolismSystem.cs b/Content.Server/GameObjects/EntitySystems/MetabolismSystem.cs index 7c33138ee6..80c6d099cf 100644 --- a/Content.Server/GameObjects/EntitySystems/MetabolismSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/MetabolismSystem.cs @@ -11,7 +11,7 @@ namespace Content.Server.GameObjects.EntitySystems { base.Update(frameTime); - foreach (var metabolism in ComponentManager.EntityQuery()) + foreach (var metabolism in ComponentManager.EntityQuery(true)) { metabolism.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/MicrowaveSystem.cs b/Content.Server/GameObjects/EntitySystems/MicrowaveSystem.cs index a1ccba35ed..5e789493e2 100644 --- a/Content.Server/GameObjects/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/MicrowaveSystem.cs @@ -10,7 +10,7 @@ namespace Content.Server.GameObjects.EntitySystems public override void Update(float frameTime) { base.Update(frameTime); - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.OnUpdate(); } diff --git a/Content.Server/GameObjects/EntitySystems/MorgueSystem.cs b/Content.Server/GameObjects/EntitySystems/MorgueSystem.cs index bbb18adca2..0692438de1 100644 --- a/Content.Server/GameObjects/EntitySystems/MorgueSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/MorgueSystem.cs @@ -16,7 +16,7 @@ namespace Content.Server.GameObjects.EntitySystems if (_accumulatedFrameTime >= 10) { - foreach (var morgue in ComponentManager.EntityQuery()) + foreach (var morgue in ComponentManager.EntityQuery(true)) { morgue.Update(); } diff --git a/Content.Server/GameObjects/EntitySystems/PlantSystem.cs b/Content.Server/GameObjects/EntitySystems/PlantSystem.cs index d2e776d6f4..5b43f9d1a0 100644 --- a/Content.Server/GameObjects/EntitySystems/PlantSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/PlantSystem.cs @@ -70,7 +70,7 @@ namespace Content.Server.GameObjects.EntitySystems _timer = 0f; - foreach (var plantHolder in _componentManager.EntityQuery()) + foreach (var plantHolder in _componentManager.EntityQuery(true)) { plantHolder.Update(); } diff --git a/Content.Server/GameObjects/EntitySystems/PointingSystem.cs b/Content.Server/GameObjects/EntitySystems/PointingSystem.cs index 0569600e7b..797171d780 100644 --- a/Content.Server/GameObjects/EntitySystems/PointingSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/PointingSystem.cs @@ -86,7 +86,7 @@ namespace Content.Server.GameObjects.EntitySystems else { return pointer.InRangeUnOccluded(coordinates, 15, e => e == pointer); - } + } } public bool TryPoint(ICommonSession? session, EntityCoordinates coords, EntityUid uid) @@ -199,7 +199,7 @@ namespace Content.Server.GameObjects.EntitySystems public override void Update(float frameTime) { - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { component.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/Power/BaseChargerSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/BaseChargerSystem.cs index d7a6a44afc..9b5ec578a4 100644 --- a/Content.Server/GameObjects/EntitySystems/Power/BaseChargerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/BaseChargerSystem.cs @@ -10,7 +10,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.OnUpdate(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/Power/BatterySystem.cs b/Content.Server/GameObjects/EntitySystems/Power/BatterySystem.cs index c94defd61d..92eb658962 100644 --- a/Content.Server/GameObjects/EntitySystems/Power/BatterySystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/BatterySystem.cs @@ -10,7 +10,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.OnUpdate(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/Power/PowerSmesSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/PowerSmesSystem.cs index 989c82dd47..a1922d2288 100644 --- a/Content.Server/GameObjects/EntitySystems/Power/PowerSmesSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/PowerSmesSystem.cs @@ -10,7 +10,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.OnUpdate(); } diff --git a/Content.Server/GameObjects/EntitySystems/Power/PowerSolarControlConsoleSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/PowerSolarControlConsoleSystem.cs index 555aab54bf..226b40c788 100644 --- a/Content.Server/GameObjects/EntitySystems/Power/PowerSolarControlConsoleSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/PowerSolarControlConsoleSystem.cs @@ -22,7 +22,7 @@ namespace Content.Server.GameObjects.EntitySystems if (_updateTimer >= 1) { _updateTimer -= 1; - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { component.UpdateUIState(); } diff --git a/Content.Server/GameObjects/EntitySystems/Power/PowerSolarSystem.cs b/Content.Server/GameObjects/EntitySystems/Power/PowerSolarSystem.cs index 3e763f5516..b2a8dfa5c3 100644 --- a/Content.Server/GameObjects/EntitySystems/Power/PowerSolarSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Power/PowerSolarSystem.cs @@ -87,7 +87,7 @@ namespace Content.Server.GameObjects.EntitySystems TotalPanelPower = 0; - foreach (var panel in ComponentManager.EntityQuery()) + foreach (var panel in ComponentManager.EntityQuery(true)) { // There's supposed to be rotational logic here, but that implies putting it somewhere. panel.Owner.Transform.WorldRotation = TargetPanelRotation; diff --git a/Content.Server/GameObjects/EntitySystems/ProjectileSystem.cs b/Content.Server/GameObjects/EntitySystems/ProjectileSystem.cs index 7838ef62e0..5c8483866c 100644 --- a/Content.Server/GameObjects/EntitySystems/ProjectileSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ProjectileSystem.cs @@ -11,7 +11,7 @@ namespace Content.Server.GameObjects.EntitySystems { base.Update(frameTime); - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { component.TimeLeft -= frameTime; diff --git a/Content.Server/GameObjects/EntitySystems/PuddleSystem.cs b/Content.Server/GameObjects/EntitySystems/PuddleSystem.cs index f36783a983..6b8f6ab87e 100644 --- a/Content.Server/GameObjects/EntitySystems/PuddleSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/PuddleSystem.cs @@ -28,7 +28,7 @@ namespace Content.Server.GameObjects.EntitySystems private void HandleTileChanged(object sender, TileChangedEventArgs eventArgs) { // If this gets hammered you could probably queue up all the tile changes every tick but I doubt that would ever happen. - foreach (var (puddle, snapGrid) in ComponentManager.EntityQuery()) + foreach (var (puddle, snapGrid) in ComponentManager.EntityQuery(true)) { // If the tile becomes space then delete it (potentially change by design) if (eventArgs.NewTile.GridIndex == puddle.Owner.Transform.GridID && diff --git a/Content.Server/GameObjects/EntitySystems/RadioSystem.cs b/Content.Server/GameObjects/EntitySystems/RadioSystem.cs index f5696c14f8..7915ea6056 100644 --- a/Content.Server/GameObjects/EntitySystems/RadioSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/RadioSystem.cs @@ -25,7 +25,7 @@ namespace Content.Server.GameObjects.EntitySystems _messages.Add(message); - foreach (var radio in ComponentManager.EntityQuery()) + foreach (var radio in ComponentManager.EntityQuery(true)) { if (radio.Channels.Contains(channel)) { diff --git a/Content.Server/GameObjects/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/GameObjects/EntitySystems/ReagentGrinderSystem.cs index 85a4682d4b..0122a7e18f 100644 --- a/Content.Server/GameObjects/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ReagentGrinderSystem.cs @@ -10,7 +10,7 @@ namespace Content.Server.GameObjects.EntitySystems public override void Update(float frameTime) { base.Update(frameTime); - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.OnUpdate(); } diff --git a/Content.Server/GameObjects/EntitySystems/RecyclerSystem.cs b/Content.Server/GameObjects/EntitySystems/RecyclerSystem.cs index c0904a1271..bea76c6e49 100644 --- a/Content.Server/GameObjects/EntitySystems/RecyclerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/RecyclerSystem.cs @@ -9,7 +9,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { component.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/RoguePointingSystem.cs b/Content.Server/GameObjects/EntitySystems/RoguePointingSystem.cs index 7686fb1e3a..e4fa11b019 100644 --- a/Content.Server/GameObjects/EntitySystems/RoguePointingSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/RoguePointingSystem.cs @@ -9,7 +9,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { component.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/SingularitySystem.cs b/Content.Server/GameObjects/EntitySystems/SingularitySystem.cs index 18a4c524ca..55cad3de26 100644 --- a/Content.Server/GameObjects/EntitySystems/SingularitySystem.cs +++ b/Content.Server/GameObjects/EntitySystems/SingularitySystem.cs @@ -20,7 +20,7 @@ namespace Content.Server.GameObjects.EntitySystems var shouldUpdate = curTimeSingulo >= 1f; var shouldPull = curTimePull >= 0.2f; if (!shouldUpdate && !shouldPull) return; - var singulos = ComponentManager.EntityQuery(); + var singulos = ComponentManager.EntityQuery(true); if (curTimeSingulo >= 1f) { diff --git a/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs b/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs index 9dd45401be..db4e2db866 100644 --- a/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs @@ -35,7 +35,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents { base.Update(frameTime); - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.Update(frameTime); var ent = comp.Owner; diff --git a/Content.Server/GameObjects/EntitySystems/StorageSystem.cs b/Content.Server/GameObjects/EntitySystems/StorageSystem.cs index 4753b332e6..d3dc608ce3 100644 --- a/Content.Server/GameObjects/EntitySystems/StorageSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/StorageSystem.cs @@ -23,7 +23,7 @@ namespace Content.Server.GameObjects.EntitySystems /// public override void Update(float frameTime) { - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { CheckSubscribedEntities(component); } diff --git a/Content.Server/GameObjects/EntitySystems/StressTestMovementSystem.cs b/Content.Server/GameObjects/EntitySystems/StressTestMovementSystem.cs index abdda79a09..ed941c83c5 100644 --- a/Content.Server/GameObjects/EntitySystems/StressTestMovementSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/StressTestMovementSystem.cs @@ -13,7 +13,7 @@ namespace Content.Server.GameObjects.EntitySystems { base.Update(frameTime); - foreach (var stressTest in ComponentManager.EntityQuery()) + foreach (var stressTest in ComponentManager.EntityQuery(true)) { var transform = stressTest.Owner.Transform; diff --git a/Content.Server/GameObjects/EntitySystems/StunSystem.cs b/Content.Server/GameObjects/EntitySystems/StunSystem.cs index 6e3a4459ba..05ce68c119 100644 --- a/Content.Server/GameObjects/EntitySystems/StunSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/StunSystem.cs @@ -11,7 +11,7 @@ namespace Content.Server.GameObjects.EntitySystems { base.Update(frameTime); - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { component.Update(frameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/ThirstSystem.cs b/Content.Server/GameObjects/EntitySystems/ThirstSystem.cs index dbb8816a49..64a057d791 100644 --- a/Content.Server/GameObjects/EntitySystems/ThirstSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ThirstSystem.cs @@ -15,7 +15,7 @@ namespace Content.Server.GameObjects.EntitySystems if (_accumulatedFrameTime > 1) { - foreach (var component in ComponentManager.EntityQuery()) + foreach (var component in ComponentManager.EntityQuery(true)) { component.OnUpdate(_accumulatedFrameTime); } diff --git a/Content.Server/GameObjects/EntitySystems/VaporSystem.cs b/Content.Server/GameObjects/EntitySystems/VaporSystem.cs index e842db680f..7dc8155f5f 100644 --- a/Content.Server/GameObjects/EntitySystems/VaporSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/VaporSystem.cs @@ -9,7 +9,7 @@ namespace Content.Server.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var vaporComp in ComponentManager.EntityQuery()) + foreach (var vaporComp in ComponentManager.EntityQuery(true)) { vaporComp.Update(frameTime); } diff --git a/Content.Server/Objectives/Conditions/KillRandomPersonCondition.cs b/Content.Server/Objectives/Conditions/KillRandomPersonCondition.cs index fd3a583d38..62cff5cc11 100644 --- a/Content.Server/Objectives/Conditions/KillRandomPersonCondition.cs +++ b/Content.Server/Objectives/Conditions/KillRandomPersonCondition.cs @@ -17,7 +17,7 @@ namespace Content.Server.Objectives.Conditions public override IObjectiveCondition GetAssigned(Mind mind) { var entityMgr = IoCManager.Resolve(); - var allHumans = entityMgr.ComponentManager.EntityQuery().Where(mc => + var allHumans = entityMgr.ComponentManager.EntityQuery(true).Where(mc => { var entity = mc.Mind?.OwnedEntity; return (entity?.GetComponentOrNull()?.IsAlive() ?? false) && mc.Mind != mind; diff --git a/Content.Server/StationEvents/PowerGridCheck.cs b/Content.Server/StationEvents/PowerGridCheck.cs index 31b000b2e7..90226c356e 100644 --- a/Content.Server/StationEvents/PowerGridCheck.cs +++ b/Content.Server/StationEvents/PowerGridCheck.cs @@ -43,7 +43,7 @@ namespace Content.Server.StationEvents { var componentManager = IoCManager.Resolve(); - foreach (var component in componentManager.EntityQuery()) + foreach (var component in componentManager.EntityQuery(true)) { component.PowerDisabled = true; _powered.Add(component.Owner); diff --git a/Content.Shared/GameObjects/EntitySystems/MechanismSystem.cs b/Content.Shared/GameObjects/EntitySystems/MechanismSystem.cs index d3e9dd0daa..75861219e6 100644 --- a/Content.Shared/GameObjects/EntitySystems/MechanismSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/MechanismSystem.cs @@ -12,7 +12,7 @@ namespace Content.Shared.GameObjects.EntitySystems { base.Update(frameTime); - foreach (var mechanism in ComponentManager.EntityQuery()) + foreach (var mechanism in ComponentManager.EntityQuery(true)) { mechanism.Update(frameTime); } diff --git a/Content.Shared/GameObjects/EntitySystems/SharedDisposalUnitSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedDisposalUnitSystem.cs index d957a9f203..b8fd1ead39 100644 --- a/Content.Shared/GameObjects/EntitySystems/SharedDisposalUnitSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/SharedDisposalUnitSystem.cs @@ -9,12 +9,12 @@ namespace Content.Shared.GameObjects.EntitySystems { public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.Update(frameTime); } - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var comp in ComponentManager.EntityQuery(true)) { comp.Update(frameTime); } diff --git a/Content.Shared/GameObjects/EntitySystems/SlipperySystem.cs b/Content.Shared/GameObjects/EntitySystems/SlipperySystem.cs index e5ad22f7ec..6a44dd5634 100644 --- a/Content.Shared/GameObjects/EntitySystems/SlipperySystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/SlipperySystem.cs @@ -10,7 +10,7 @@ namespace Content.Shared.GameObjects.EntitySystems /// public override void Update(float frameTime) { - foreach (var slipperyComp in ComponentManager.EntityQuery()) + foreach (var slipperyComp in ComponentManager.EntityQuery(true)) { slipperyComp.Update(); } From 937e261867cb86d0cda36597cfaedbddb5a01b3c Mon Sep 17 00:00:00 2001 From: Radrark <76271993+Radrark@users.noreply.github.com> Date: Wed, 3 Feb 2021 11:26:46 -0300 Subject: [PATCH 50/61] Add Smoke and Foam chemical reaction effects. (#2913) * Adds smoke reaction effect * smoke tweaks * address reviews * Smoke fix * Refactor smoke and add foam * Fix stuff * Remove thing * Little things * Address some comments * Address more things * More addressing * License stuff * Address refactor request * Small things * Add nullability * Update Content.Server/GameObjects/EntitySystems/SolutionAreaEffectSystem.cs Co-authored-by: Paul Ritter --- .../Components/Chemistry/FoamVisualizer.cs | 74 +++++++ .../Components/Chemistry/SmokeVisualizer.cs | 26 +++ Content.Client/IgnoredComponents.cs | 2 + .../ReactionEffects/AreaReactionEffect.cs | 162 +++++++++++++++ .../ReactionEffects/FoamAreaReactionEffect.cs | 16 ++ .../SmokeAreaReactionEffect.cs | 16 ++ .../Body/Respiratory/InternalsComponent.cs | 10 + .../FoamSolutionAreaEffectComponent.cs | 90 +++++++++ .../SmokeSolutionAreaEffectComponent.cs | 63 ++++++ .../Chemistry/SolutionAreaEffectComponent.cs | 188 ++++++++++++++++++ .../SolutionAreaEffectInceptionComponent.cs | 142 +++++++++++++ .../EntitySystems/SolutionAreaEffectSystem.cs | 19 ++ .../Components/Chemistry/FoamVisuals.cs | 13 ++ .../Components/Chemistry/SmokeVisuals.cs | 12 ++ Content.Shared/GameObjects/DrawDepth.cs | 5 +- Resources/Audio/Effects/license.txt | 2 + Resources/Audio/Effects/smoke.ogg | Bin 0 -> 24160 bytes .../Specific/Dispensers/chem_dispenser.yml | 1 + .../Entities/Effects/chemistry_effects.yml | 149 ++++++++++++++ Resources/Prototypes/Reagents/chemicals.yml | 19 ++ .../Recipes/Reactions/chemicals.yml | 114 +++++++++++ .../Effects/chemsmoke.rsi/chemsmoke.png | Bin 0 -> 111309 bytes .../Textures/Effects/chemsmoke.rsi/meta.json | 1 + .../Effects/foam.rsi/foam-dissolve.png | Bin 0 -> 6345 bytes Resources/Textures/Effects/foam.rsi/foam.png | Bin 0 -> 2508 bytes .../Textures/Effects/foam.rsi/ironfoam.png | Bin 0 -> 3071 bytes Resources/Textures/Effects/foam.rsi/meta.json | 1 + .../Textures/Effects/foam.rsi/metalfoam.png | Bin 0 -> 3233 bytes .../Effects/foam.rsi/mfoam-dissolve.png | Bin 0 -> 5550 bytes Resources/Textures/Effects/foam.rsi/mfoam.png | Bin 0 -> 2301 bytes 30 files changed, 1123 insertions(+), 2 deletions(-) create mode 100644 Content.Client/GameObjects/Components/Chemistry/FoamVisualizer.cs create mode 100644 Content.Client/GameObjects/Components/Chemistry/SmokeVisualizer.cs create mode 100644 Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs create mode 100644 Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs create mode 100644 Content.Server/Chemistry/ReactionEffects/SmokeAreaReactionEffect.cs create mode 100644 Content.Server/GameObjects/Components/Chemistry/FoamSolutionAreaEffectComponent.cs create mode 100644 Content.Server/GameObjects/Components/Chemistry/SmokeSolutionAreaEffectComponent.cs create mode 100644 Content.Server/GameObjects/Components/Chemistry/SolutionAreaEffectComponent.cs create mode 100644 Content.Server/GameObjects/Components/Chemistry/SolutionAreaEffectInceptionComponent.cs create mode 100644 Content.Server/GameObjects/EntitySystems/SolutionAreaEffectSystem.cs create mode 100644 Content.Shared/GameObjects/Components/Chemistry/FoamVisuals.cs create mode 100644 Content.Shared/GameObjects/Components/Chemistry/SmokeVisuals.cs create mode 100644 Resources/Audio/Effects/smoke.ogg create mode 100644 Resources/Prototypes/Entities/Effects/chemistry_effects.yml create mode 100644 Resources/Textures/Effects/chemsmoke.rsi/chemsmoke.png create mode 100644 Resources/Textures/Effects/chemsmoke.rsi/meta.json create mode 100644 Resources/Textures/Effects/foam.rsi/foam-dissolve.png create mode 100644 Resources/Textures/Effects/foam.rsi/foam.png create mode 100644 Resources/Textures/Effects/foam.rsi/ironfoam.png create mode 100644 Resources/Textures/Effects/foam.rsi/meta.json create mode 100644 Resources/Textures/Effects/foam.rsi/metalfoam.png create mode 100644 Resources/Textures/Effects/foam.rsi/mfoam-dissolve.png create mode 100644 Resources/Textures/Effects/foam.rsi/mfoam.png diff --git a/Content.Client/GameObjects/Components/Chemistry/FoamVisualizer.cs b/Content.Client/GameObjects/Components/Chemistry/FoamVisualizer.cs new file mode 100644 index 0000000000..79fb64a230 --- /dev/null +++ b/Content.Client/GameObjects/Components/Chemistry/FoamVisualizer.cs @@ -0,0 +1,74 @@ +#nullable enable +using System; +using Content.Shared.GameObjects.Components.Chemistry; +using JetBrains.Annotations; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.Components.Animations; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Shared.Maths; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; + +namespace Content.Client.GameObjects.Components.Chemistry +{ + [UsedImplicitly] + public class FoamVisualizer : AppearanceVisualizer + { + private const string AnimationKey = "foamdissolve_animation"; + private Animation _foamDissolve = new(); + public override void LoadData(YamlMappingNode node) + { + base.LoadData(node); + + var delay = 0.6f; + var state = "foam-dissolve"; + + if (node.TryGetNode("animationTime", out var delayNode)) + { + delay = delayNode.AsFloat(); + } + + if (node.TryGetNode("animationState", out var stateNode)) + { + state = stateNode.AsString(); + } + + _foamDissolve = new Animation {Length = TimeSpan.FromSeconds(delay)}; + var flick = new AnimationTrackSpriteFlick(); + _foamDissolve.AnimationTracks.Add(flick); + flick.LayerKey = FoamVisualLayers.Base; + flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame(state, 0f)); + } + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (component.TryGetData(FoamVisuals.State, out var state)) + { + if (state) + { + if (component.Owner.TryGetComponent(out AnimationPlayerComponent? animPlayer)) + { + if (!animPlayer.HasRunningAnimation(AnimationKey)) + animPlayer.Play(_foamDissolve, AnimationKey); + } + } + } + + if (component.TryGetData(FoamVisuals.Color, out var color)) + { + if (component.Owner.TryGetComponent(out ISpriteComponent? sprite)) + { + sprite.Color = color; + } + } + } + } + + public enum FoamVisualLayers : byte + { + Base + } +} diff --git a/Content.Client/GameObjects/Components/Chemistry/SmokeVisualizer.cs b/Content.Client/GameObjects/Components/Chemistry/SmokeVisualizer.cs new file mode 100644 index 0000000000..91f68b6f71 --- /dev/null +++ b/Content.Client/GameObjects/Components/Chemistry/SmokeVisualizer.cs @@ -0,0 +1,26 @@ +#nullable enable +using Content.Shared.GameObjects.Components.Chemistry; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Shared.Maths; + +namespace Content.Client.GameObjects.Components.Chemistry +{ + [UsedImplicitly] + public class SmokeVisualizer : AppearanceVisualizer + { + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (component.TryGetData(SmokeVisuals.Color, out var color)) + { + if (component.Owner.TryGetComponent(out ISpriteComponent? sprite)) + { + sprite.Color = color; + } + } + } + } +} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 652a75edee..0ad242868c 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -236,6 +236,8 @@ namespace Content.Client "SliceableFood", "DamageOtherOnHit", "DamageOnLand", + "SmokeSolutionAreaEffect", + "FoamSolutionAreaEffect", "GasFilter", "Recyclable", "SecretStash", diff --git a/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs new file mode 100644 index 0000000000..2079ac32b5 --- /dev/null +++ b/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs @@ -0,0 +1,162 @@ +#nullable enable +using System; +using Content.Server.GameObjects.Components.Chemistry; +using Content.Server.Interfaces.Chemistry; +using Content.Server.Utility; +using Content.Shared.Audio; +using JetBrains.Annotations; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Serialization; + +namespace Content.Server.Chemistry.ReactionEffects +{ + /// + /// Basically smoke and foam reactions. + /// + [UsedImplicitly] + public abstract class AreaReactionEffect : IReactionEffect + { + [Dependency] private readonly IMapManager _mapManager = default!; + + /// + /// Used for calculating the spread range of the effect based on the intensity of the reaction. + /// + private float _rangeConstant; + private float _rangeMultiplier; + private int _maxRange; + + /// + /// If true the reagents get diluted or concentrated depending on the range of the effect + /// + private bool _diluteReagents; + + /// + /// At what range should the reagents volume stay the same. If the effect range is higher than this then the reagents + /// will get diluted. If the effect range is lower than this then the reagents will get concentrated. + /// + private int _reagentDilutionStart; + + /// + /// Used to calculate dilution. Increasing this makes the reagents get more diluted. This means that a lower range + /// will be needed to make the reagents volume get closer to zero. + /// + private float _reagentDilutionFactor; + + /// + /// Used to calculate concentration. Reagents get linearly more concentrated as the range goes from + /// _reagentDilutionStart to zero. When the range is zero the reagents volume gets multiplied by this. + /// + private float _reagentMaxConcentrationFactor; + + /// + /// How many seconds will the effect stay, counting after fully spreading. + /// + private float _duration; + + /// + /// How many seconds between each spread step. + /// + private float _spreadDelay; + + /// + /// How many seconds between each remove step. + /// + private float _removeDelay; + + /// + /// The entity prototype that will be spawned as the effect. It needs a component derived from SolutionAreaEffectComponent. + /// + private string? _prototypeId; + + /// + /// Sound that will get played when this reaction effect occurs. + /// + private string? _sound; + + protected AreaReactionEffect() + { + IoCManager.InjectDependencies(this); + } + + public void ExposeData(ObjectSerializer serializer) + { + serializer.DataField(ref _rangeConstant, "rangeConstant",0f); + serializer.DataField(ref _rangeMultiplier, "rangeMultiplier",1.1f); + serializer.DataField(ref _maxRange, "maxRange", 10); + serializer.DataField(ref _diluteReagents, "diluteReagents", false); + serializer.DataField(ref _reagentDilutionStart, "reagentDilutionStart", 4); + serializer.DataField(ref _reagentDilutionFactor, "reagentDilutionFactor", 1f); + serializer.DataField(ref _reagentMaxConcentrationFactor, "reagentMaxConcentrationFactor",2f); + serializer.DataField(ref _duration, "duration", 10f); + serializer.DataField(ref _spreadDelay, "spreadDelay", 0.5f); + serializer.DataField(ref _removeDelay, "removeDelay", 0.5f); + serializer.DataField(ref _sound, "sound", null); + serializer.DataField(ref _prototypeId, "prototypeId", null); + + if (_prototypeId == null) + Logger.Error("prototypeId wasn't provided to AreaReactionEffect, check yaml"); + } + + public void React(IEntity solutionEntity, double intensity) + { + if (!solutionEntity.TryGetComponent(out SolutionContainerComponent? contents)) + return; + + var solution = contents.SplitSolution(contents.MaxVolume); + // We take the square root so it becomes harder to reach higher amount values + var amount = (int) Math.Round(_rangeConstant + _rangeMultiplier*Math.Sqrt(intensity)); + amount = Math.Min(amount, _maxRange); + + if (_diluteReagents) + { + // The maximum value of solutionFraction is _reagentMaxConcentrationFactor, achieved when amount = 0 + // The infimum of solutionFraction is 0, which is approached when amount tends to infinity + // solutionFraction is equal to 1 only when amount equals _reagentDilutionStart + float solutionFraction; + if (amount >= _reagentDilutionStart) + { + // Weird formulas here but basically when amount increases, solutionFraction gets closer to 0 in a reciprocal manner + // _reagentDilutionFactor defines how fast solutionFraction gets closer to 0 + solutionFraction = 1 / (_reagentDilutionFactor*(amount - _reagentDilutionStart) + 1); + } + else + { + // Here when amount decreases, solutionFraction gets closer to _reagentMaxConcentrationFactor in a linear manner + solutionFraction = amount * (1 - _reagentMaxConcentrationFactor) / _reagentDilutionStart + + _reagentMaxConcentrationFactor; + } + solution.RemoveSolution(solution.TotalVolume*(1-solutionFraction)); + } + + if (!_mapManager.TryFindGridAt(solutionEntity.Transform.MapPosition, out var grid)) return; + + var coords = grid.MapToGrid(solutionEntity.Transform.MapPosition); + + var ent = solutionEntity.EntityManager.SpawnEntity(_prototypeId, coords.SnapToGrid()); + + var areaEffectComponent = GetAreaEffectComponent(ent); + + if (areaEffectComponent == null) + { + Logger.Error("Couldn't get AreaEffectComponent from " + _prototypeId); + ent.Delete(); + return; + } + + areaEffectComponent.TryAddSolution(solution); + areaEffectComponent.Start(amount, _duration, _spreadDelay, _removeDelay); + + if (!string.IsNullOrEmpty(_sound)) + { + EntitySystem.Get().PlayFromEntity(_sound, solutionEntity, AudioHelpers.WithVariation(0.125f)); + } + } + + protected abstract SolutionAreaEffectComponent? GetAreaEffectComponent(IEntity entity); + } +} diff --git a/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs new file mode 100644 index 0000000000..edc6a26285 --- /dev/null +++ b/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs @@ -0,0 +1,16 @@ +#nullable enable +using Content.Server.GameObjects.Components.Chemistry; +using JetBrains.Annotations; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.Chemistry.ReactionEffects +{ + [UsedImplicitly] + public class FoamAreaReactionEffect : AreaReactionEffect + { + protected override SolutionAreaEffectComponent? GetAreaEffectComponent(IEntity entity) + { + return entity.GetComponentOrNull(); + } + } +} diff --git a/Content.Server/Chemistry/ReactionEffects/SmokeAreaReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/SmokeAreaReactionEffect.cs new file mode 100644 index 0000000000..3a78557321 --- /dev/null +++ b/Content.Server/Chemistry/ReactionEffects/SmokeAreaReactionEffect.cs @@ -0,0 +1,16 @@ +#nullable enable +using Content.Server.GameObjects.Components.Chemistry; +using JetBrains.Annotations; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.Chemistry.ReactionEffects +{ + [UsedImplicitly] + public class SmokeAreaReactionEffect : AreaReactionEffect + { + protected override SolutionAreaEffectComponent? GetAreaEffectComponent(IEntity entity) + { + return entity.GetComponentOrNull(); + } + } +} diff --git a/Content.Server/GameObjects/Components/Body/Respiratory/InternalsComponent.cs b/Content.Server/GameObjects/Components/Body/Respiratory/InternalsComponent.cs index 6c3380da4e..63594ffcee 100644 --- a/Content.Server/GameObjects/Components/Body/Respiratory/InternalsComponent.cs +++ b/Content.Server/GameObjects/Components/Body/Respiratory/InternalsComponent.cs @@ -59,5 +59,15 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory return true; } + public bool AreInternalsWorking() + { + return BreathToolEntity != null && + GasTankEntity != null && + BreathToolEntity.TryGetComponent(out BreathToolComponent? breathTool) && + breathTool.IsFunctional && + GasTankEntity.TryGetComponent(out GasTankComponent? gasTank) && + gasTank.Air != null; + } + } } diff --git a/Content.Server/GameObjects/Components/Chemistry/FoamSolutionAreaEffectComponent.cs b/Content.Server/GameObjects/Components/Chemistry/FoamSolutionAreaEffectComponent.cs new file mode 100644 index 0000000000..6b9e6dd3b5 --- /dev/null +++ b/Content.Server/GameObjects/Components/Chemistry/FoamSolutionAreaEffectComponent.cs @@ -0,0 +1,90 @@ +#nullable enable +using Content.Server.GameObjects.Components.Body.Circulatory; +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Shared.Chemistry; +using Content.Shared.GameObjects.Components.Chemistry; +using Content.Shared.GameObjects.Components.Inventory; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.Timers; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Server.GameObjects.Components.Chemistry +{ + [RegisterComponent] + [ComponentReference(typeof(SolutionAreaEffectComponent))] + public class FoamSolutionAreaEffectComponent : SolutionAreaEffectComponent + { + public override string Name => "FoamSolutionAreaEffect"; + + private string? _foamedMetalPrototype; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _foamedMetalPrototype, "foamedMetalPrototype", null); + } + + protected override void UpdateVisuals() + { + if (Owner.TryGetComponent(out AppearanceComponent? appearance) && + SolutionContainerComponent != null) + { + appearance.SetData(FoamVisuals.Color, SolutionContainerComponent.Color.WithAlpha(0.80f)); + } + } + + protected override void ReactWithEntity(IEntity entity, double solutionFraction) + { + if (SolutionContainerComponent == null) + return; + + if (!entity.TryGetComponent(out BloodstreamComponent? bloodstream)) + return; + + // TODO: Add a permeability property to clothing + // For now it just adds to protection for each clothing equipped + var protection = 0f; + if (entity.TryGetComponent(out InventoryComponent? inventory)) + { + foreach (var slot in inventory.Slots) + { + if (slot == EquipmentSlotDefines.Slots.BACKPACK || + slot == EquipmentSlotDefines.Slots.POCKET1 || + slot == EquipmentSlotDefines.Slots.POCKET2 || + slot == EquipmentSlotDefines.Slots.IDCARD) + continue; + + if (inventory.TryGetSlotItem(slot, out ItemComponent _)) + protection += 0.025f; + } + } + + var cloneSolution = SolutionContainerComponent.Solution.Clone(); + var transferAmount = ReagentUnit.Min(cloneSolution.TotalVolume * solutionFraction * (1 - protection), bloodstream.EmptyVolume); + var transferSolution = cloneSolution.SplitSolution(transferAmount); + + bloodstream.TryTransferSolution(transferSolution); + } + + protected override void OnKill() + { + if (Owner.Deleted) + return; + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(FoamVisuals.State, true); + } + Owner.SpawnTimer(600, () => + { + if (!string.IsNullOrEmpty(_foamedMetalPrototype)) + { + Owner.EntityManager.SpawnEntity(_foamedMetalPrototype, Owner.Transform.Coordinates); + } + Owner.Delete(); + }); + } + } +} diff --git a/Content.Server/GameObjects/Components/Chemistry/SmokeSolutionAreaEffectComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SmokeSolutionAreaEffectComponent.cs new file mode 100644 index 0000000000..f916725ad6 --- /dev/null +++ b/Content.Server/GameObjects/Components/Chemistry/SmokeSolutionAreaEffectComponent.cs @@ -0,0 +1,63 @@ +#nullable enable +using System.Linq; +using Content.Server.GameObjects.Components.Body.Circulatory; +using Content.Server.GameObjects.Components.Body.Respiratory; +using Content.Shared.Chemistry; +using Content.Shared.GameObjects.Components.Chemistry; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.GameObjects.Components.Chemistry +{ + [RegisterComponent] + [ComponentReference(typeof(SolutionAreaEffectComponent))] + public class SmokeSolutionAreaEffectComponent : SolutionAreaEffectComponent + { + public override string Name => "SmokeSolutionAreaEffect"; + + protected override void UpdateVisuals() + { + if (Owner.TryGetComponent(out AppearanceComponent? appearance) && + SolutionContainerComponent != null) + { + appearance.SetData(SmokeVisuals.Color, SolutionContainerComponent.Color); + } + } + + protected override void ReactWithEntity(IEntity entity, double solutionFraction) + { + if (SolutionContainerComponent == null) + return; + + if (!entity.TryGetComponent(out BloodstreamComponent? bloodstream)) + return; + + if (entity.TryGetComponent(out InternalsComponent? internals) && + internals.AreInternalsWorking()) + return; + + var cloneSolution = SolutionContainerComponent.Solution.Clone(); + var transferAmount = ReagentUnit.Min(cloneSolution.TotalVolume * solutionFraction, bloodstream.EmptyVolume); + var transferSolution = cloneSolution.SplitSolution(transferAmount); + + foreach (var reagentQuantity in transferSolution.Contents.ToArray()) + { + if (reagentQuantity.Quantity == ReagentUnit.Zero) continue; + var reagent = PrototypeManager.Index(reagentQuantity.ReagentId); + transferSolution.RemoveReagent(reagentQuantity.ReagentId,reagent.ReactionEntity(entity, ReactionMethod.Ingestion, reagentQuantity.Quantity)); + } + + bloodstream.TryTransferSolution(transferSolution); + } + + + protected override void OnKill() + { + if (Owner.Deleted) + return; + Owner.Delete(); + } + } +} diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionAreaEffectComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionAreaEffectComponent.cs new file mode 100644 index 0000000000..682cfac522 --- /dev/null +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionAreaEffectComponent.cs @@ -0,0 +1,188 @@ +#nullable enable +using System; +using System.Linq; +using Content.Server.GameObjects.Components.Atmos; +using Content.Server.Utility; +using Content.Shared.Chemistry; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.ComponentDependencies; +using Robust.Shared.GameObjects.Components.Transform; +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.Prototypes; + +namespace Content.Server.GameObjects.Components.Chemistry +{ + /// + /// Used to clone its owner repeatedly and group up them all so they behave like one unit, that way you can have + /// effects that cover an area. Inherited by and . + /// + public abstract class SolutionAreaEffectComponent : Component + { + [Dependency] protected readonly IMapManager MapManager = default!; + [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; + + [ComponentDependency] protected readonly SnapGridComponent? SnapGridComponent = default!; + [ComponentDependency] protected readonly SolutionContainerComponent? SolutionContainerComponent = default!; + public int Amount { get; set; } + public SolutionAreaEffectInceptionComponent? Inception { get; set; } + + /// + /// Adds an to owner so the effect starts spreading and reacting. + /// + /// The range of the effect + /// + /// + /// + public void Start(int amount, float duration, float spreadDelay, float removeDelay) + { + if (Inception != null) + return; + + if (Owner.HasComponent()) + return; + + Amount = amount; + var inception = Owner.AddComponent(); + + inception.Add(this); + inception.Setup(amount, duration, spreadDelay, removeDelay); + } + + /// + /// Gets called by an AreaEffectInceptionComponent. "Clones" Owner into the four directions and copies the + /// solution into each of them. + /// + public void Spread() + { + if (Owner.Prototype == null) + { + Logger.Error("AreaEffectComponent needs its owner to be spawned by a prototype."); + return; + } + + if (SnapGridComponent == null) + { + Logger.Error("AreaEffectComponent attached to " + Owner.Prototype.ID + + " couldn't get SnapGridComponent from owner."); + return; + } + + void SpreadToDir(Direction dir) + { + foreach (var neighbor in SnapGridComponent.GetInDir(dir)) + { + if (neighbor.TryGetComponent(out SolutionAreaEffectComponent? comp) && comp.Inception == Inception) + return; + + if (neighbor.TryGetComponent(out AirtightComponent? airtight) && airtight.AirBlocked) + return; + } + + var newEffect = + Owner.EntityManager.SpawnEntity(Owner.Prototype.ID, SnapGridComponent.DirectionToGrid(dir)); + + if (!newEffect.TryGetComponent(out SolutionAreaEffectComponent? effectComponent)) + { + newEffect.Delete(); + return; + } + + if (SolutionContainerComponent != null) + { + effectComponent.TryAddSolution(SolutionContainerComponent.Solution.Clone()); + } + + effectComponent.Amount = Amount - 1; + Inception?.Add(effectComponent); + } + + SpreadToDir(Direction.North); + SpreadToDir(Direction.East); + SpreadToDir(Direction.South); + SpreadToDir(Direction.West); + + } + + /// + /// Gets called by an AreaEffectInceptionComponent. + /// Removes this component from its inception and calls OnKill(). The implementation of OnKill() should + /// eventually delete the entity. + /// + public void Kill() + { + Inception?.Remove(this); + OnKill(); + } + + protected abstract void OnKill(); + + /// + /// Gets called by an AreaEffectInceptionComponent. + /// Makes this effect's reagents react with the tile its on and with the entities it covers. Also calls + /// ReactWithEntity on the entities so inheritors can implement more specific behavior. + /// + /// How many times will this get called over this area effect's duration, averaged + /// with the other area effects from the inception. + public void React(float averageExposures) + { + if (SolutionContainerComponent == null) + return; + + var mapGrid = MapManager.GetGrid(Owner.Transform.GridID); + var tile = mapGrid.GetTileRef(Owner.Transform.Coordinates.ToVector2i(Owner.EntityManager, MapManager)); + + var solutionFraction = 1 / Math.Floor(averageExposures); + + foreach (var reagentQuantity in SolutionContainerComponent.ReagentList.ToArray()) + { + if (reagentQuantity.Quantity == ReagentUnit.Zero) continue; + var reagent = PrototypeManager.Index(reagentQuantity.ReagentId); + + // React with the tile the effect is on + reagent.ReactionTile(tile, reagentQuantity.Quantity * solutionFraction); + + // Touch every entity on the tile + foreach (var entity in tile.GetEntitiesInTileFast()) + { + reagent.ReactionEntity(entity, ReactionMethod.Touch, reagentQuantity.Quantity * solutionFraction); + } + } + + foreach (var entity in tile.GetEntitiesInTileFast()) + { + ReactWithEntity(entity, solutionFraction); + } + } + + protected abstract void ReactWithEntity(IEntity entity, double solutionFraction); + + public void TryAddSolution(Solution solution) + { + if (solution.TotalVolume == 0) + return; + + if (SolutionContainerComponent == null) + return; + + var addSolution = + solution.SplitSolution(ReagentUnit.Min(solution.TotalVolume, SolutionContainerComponent.EmptyVolume)); + + SolutionContainerComponent.TryAddSolution(addSolution); + + UpdateVisuals(); + } + + protected abstract void UpdateVisuals(); + + public override void OnRemove() + { + base.OnRemove(); + Inception?.Remove(this); + } + } +} diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionAreaEffectInceptionComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionAreaEffectInceptionComponent.cs new file mode 100644 index 0000000000..2eb1ccebdc --- /dev/null +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionAreaEffectInceptionComponent.cs @@ -0,0 +1,142 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Chemistry +{ + /// + /// The "mastermind" of a SolutionAreaEffect group. It gets updated by the SolutionAreaEffectSystem and tells the + /// group when to spread, react and remove itself. This makes the group act like a single unit. + /// + /// It should only be manually added to an entity by the and not with a prototype. + [RegisterComponent] + public class SolutionAreaEffectInceptionComponent : Component + { + public override string Name => "AreaEffectInception"; + + private const float ReactionDelay = 0.5f; + + private readonly HashSet _group = new(); + + [ViewVariables] private float _lifeTimer; + [ViewVariables] private float _spreadTimer; + [ViewVariables] private float _reactionTimer; + + [ViewVariables] private int _amountCounterSpreading; + [ViewVariables] private int _amountCounterRemoving; + + /// + /// How much time to wait after fully spread before starting to remove itself. + /// + [ViewVariables] private float _duration; + + /// + /// Time between each spread step. Decreasing this makes spreading faster. + /// + [ViewVariables] private float _spreadDelay; + + /// + /// Time between each remove step. Decreasing this makes removing faster. + /// + [ViewVariables] private float _removeDelay; + + /// + /// How many times will the effect react. As some entities from the group last a different amount of time than + /// others, they will react a different amount of times, so we calculate the average to make the group behave + /// a bit more uniformly. + /// + [ViewVariables] private float _averageExposures; + + public void Setup(int amount, float duration, float spreadDelay, float removeDelay) + { + _amountCounterSpreading = amount; + _duration = duration; + _spreadDelay = spreadDelay; + _removeDelay = removeDelay; + + // So the first square reacts immediately after spawning + _reactionTimer = ReactionDelay; + /* + The group takes amount*spreadDelay seconds to fully spread, same with fully disappearing. + The outer squares will last duration seconds. + The first square will last duration + how many seconds the group takes to fully spread and fully disappear, so + it will last duration + amount*(spreadDelay+removeDelay). + Thus, the average lifetime of the smokes will be (outerSmokeLifetime + firstSmokeLifetime)/2 = duration + amount*(spreadDelay+removeDelay)/2 + */ + _averageExposures = (duration + amount * (spreadDelay+removeDelay) / 2)/ReactionDelay; + } + + public void InceptionUpdate(float frameTime) + { + _group.RemoveWhere(effect => effect.Deleted); + if (_group.Count == 0) + return; + + // Make every outer square from the group spread + if (_amountCounterSpreading > 0) + { + _spreadTimer += frameTime; + if (_spreadTimer > _spreadDelay) + { + _spreadTimer -= _spreadDelay; + + var outerEffects = new HashSet(_group.Where(effect => effect.Amount == _amountCounterSpreading)); + foreach (var effect in outerEffects) + { + effect.Spread(); + } + + _amountCounterSpreading -= 1; + } + } + // Start counting for _duration after fully spreading + else + { + _lifeTimer += frameTime; + } + + // Delete every outer square + if (_lifeTimer > _duration) + { + _spreadTimer += frameTime; + if (_spreadTimer > _removeDelay) + { + _spreadTimer -= _removeDelay; + + var outerEffects = new HashSet(_group.Where(effect => effect.Amount == _amountCounterRemoving)); + foreach (var effect in outerEffects) + { + effect.Kill(); + } + + _amountCounterRemoving += 1; + } + } + + // Make every square from the group react with the tile and entities + _reactionTimer += frameTime; + if (_reactionTimer > ReactionDelay) + { + _reactionTimer -= ReactionDelay; + foreach (var effect in _group) + { + effect.React(_averageExposures); + } + } + } + + public void Add(SolutionAreaEffectComponent effect) + { + _group.Add(effect); + effect.Inception = this; + } + + public void Remove(SolutionAreaEffectComponent effect) + { + _group.Remove(effect); + effect.Inception = null; + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/SolutionAreaEffectSystem.cs b/Content.Server/GameObjects/EntitySystems/SolutionAreaEffectSystem.cs new file mode 100644 index 0000000000..79062a6d2a --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/SolutionAreaEffectSystem.cs @@ -0,0 +1,19 @@ +#nullable enable +using Content.Server.GameObjects.Components.Chemistry; +using JetBrains.Annotations; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Server.GameObjects.EntitySystems +{ + [UsedImplicitly] + public class SolutionAreaEffectSystem : EntitySystem + { + public override void Update(float frameTime) + { + foreach (var inception in ComponentManager.EntityQuery()) + { + inception.InceptionUpdate(frameTime); + } + } + } +} diff --git a/Content.Shared/GameObjects/Components/Chemistry/FoamVisuals.cs b/Content.Shared/GameObjects/Components/Chemistry/FoamVisuals.cs new file mode 100644 index 0000000000..065ae3da7f --- /dev/null +++ b/Content.Shared/GameObjects/Components/Chemistry/FoamVisuals.cs @@ -0,0 +1,13 @@ +#nullable enable +using System; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Chemistry +{ + [Serializable, NetSerializable] + public enum FoamVisuals : byte + { + State, + Color + } +} diff --git a/Content.Shared/GameObjects/Components/Chemistry/SmokeVisuals.cs b/Content.Shared/GameObjects/Components/Chemistry/SmokeVisuals.cs new file mode 100644 index 0000000000..0ded6dd5c0 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Chemistry/SmokeVisuals.cs @@ -0,0 +1,12 @@ +#nullable enable +using System; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Chemistry +{ + [Serializable, NetSerializable] + public enum SmokeVisuals : byte + { + Color + } +} diff --git a/Content.Shared/GameObjects/DrawDepth.cs b/Content.Shared/GameObjects/DrawDepth.cs index 9f445d2e48..eea05d7d94 100644 --- a/Content.Shared/GameObjects/DrawDepth.cs +++ b/Content.Shared/GameObjects/DrawDepth.cs @@ -22,7 +22,8 @@ namespace Content.Shared.GameObjects Objects = DrawDepthTag.Default, Items = DrawDepthTag.Default + 1, Mobs = DrawDepthTag.Default + 2, - Ghosts = DrawDepthTag.Default + 3, - Overlays = DrawDepthTag.Default + 4, + Effects = DrawDepthTag.Default + 3, + Ghosts = DrawDepthTag.Default + 4, + Overlays = DrawDepthTag.Default + 5, } } diff --git a/Resources/Audio/Effects/license.txt b/Resources/Audio/Effects/license.txt index 67629091c3..2587ffff73 100644 --- a/Resources/Audio/Effects/license.txt +++ b/Resources/Audio/Effects/license.txt @@ -1,2 +1,4 @@ hit_kick.ogg is made by Taira Komori (https://taira-komori.jpn.org/freesounden.html) + +smoke.ogg taken from https://github.com/tgstation/tgstation/blob/a5d362ce84e4f0c61026236d5ec84d3c81553664/sound/effects/smoke.ogg \ No newline at end of file diff --git a/Resources/Audio/Effects/smoke.ogg b/Resources/Audio/Effects/smoke.ogg new file mode 100644 index 0000000000000000000000000000000000000000..1a1aa3733edabd0d0d0002e81ecfa98503d7e983 GIT binary patch literal 24160 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 literal 0 HcmV?d00001 diff --git a/Resources/Prototypes/Entities/Constructible/Specific/Dispensers/chem_dispenser.yml b/Resources/Prototypes/Entities/Constructible/Specific/Dispensers/chem_dispenser.yml index f7de3d2bf4..92840e7a79 100644 --- a/Resources/Prototypes/Entities/Constructible/Specific/Dispensers/chem_dispenser.yml +++ b/Resources/Prototypes/Entities/Constructible/Specific/Dispensers/chem_dispenser.yml @@ -21,6 +21,7 @@ - chem.Water - chem.Ethanol - chem.Glucose + - chem.Sugar - chem.Hydrogen - chem.Oxygen - chem.Sulfur diff --git a/Resources/Prototypes/Entities/Effects/chemistry_effects.yml b/Resources/Prototypes/Entities/Effects/chemistry_effects.yml new file mode 100644 index 0000000000..00e78fce7f --- /dev/null +++ b/Resources/Prototypes/Entities/Effects/chemistry_effects.yml @@ -0,0 +1,149 @@ +- type: entity + id: Smoke + name: smoke + abstract: true + components: + - type: Sprite + drawdepth: Effects + sprite: Effects/chemsmoke.rsi + state: chemsmoke + - type: Appearance + visuals: + - type: SmokeVisualizer + - type: Occluder + sizeX: 32 + sizeY: 32 + - type: SnapGrid + offset: Center + - type: SmokeSolutionAreaEffect + - type: SolutionContainer + maxVol: 600 + +- type: entity + id: Foam + name: foam + abstract: true + components: + - type: Sprite + netsync: false + drawdepth: Effects + color: "#ffffffcc" #Add some transparency + sprite: Effects/foam.rsi + state: foam + layers: + - state: foam + map: ["enum.FoamVisualLayers.Base"] + - type: AnimationPlayer + - type: Appearance + visuals: + - type: FoamVisualizer + animationTime: 0.6 + animationState: foam-dissolve + - type: SnapGrid + offset: Center + - type: Physics + shapes: + - !type:PhysShapeAabb + bounds: "-0.4,-0.4,0.4,0.4" + layer: + - MobImpassable + - type: FoamSolutionAreaEffect + - type: SolutionContainer + maxVol: 600 + - type: Slippery + +- type: entity + id: IronMetalFoam + name: iron metal foam + abstract: true + parent: Foam + components: + - type: Sprite + state: mfoam + layers: + - state: mfoam + map: ["enum.FoamVisualLayers.Base"] + - type: Appearance + visuals: + - type: FoamVisualizer + animationTime: 0.6 + animationState: mfoam-dissolve + - type: FoamSolutionAreaEffect + foamedMetalPrototype: FoamedIronMetal + +- type: entity + id: AluminiumMetalFoam + name: aluminium metal foam + abstract: true + parent: Foam + components: + - type: Sprite + state: mfoam + layers: + - state: mfoam + map: ["enum.FoamVisualLayers.Base"] + - type: Appearance + visuals: + - type: FoamVisualizer + animationTime: 0.6 + animationState: mfoam-dissolve + - type: FoamSolutionAreaEffect + foamedMetalPrototype: FoamedAluminiumMetal + +- type: entity + id: BaseFoamedMetal + name: base foamed metal + description: Keeps the air in and the greytide out. + abstract: true + placement: + mode: SnapgridCenter + snap: + - Wall + components: + - type: RCDDeconstructWhitelist + - type: Clickable + - type: InteractionOutline + - type: Sprite + netsync: false + drawdepth: Walls + - type: Physics + shapes: + - !type:PhysShapeAabb + layer: + - Opaque + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + - type: Occluder + sizeX: 32 + sizeY: 32 + - type: SnapGrid + offset: Center + - type: Airtight + - type: Damageable + resistances: metallicResistances + - type: Destructible + thresholds: + 50: + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + +- type: entity + id: FoamedIronMetal + name: foamed iron metal + parent: BaseFoamedMetal + components: + - type: Sprite + sprite: Effects/foam.rsi + state: ironfoam + +- type: entity + id: FoamedAluminiumMetal + name: foamed aluminium metal + parent: BaseFoamedMetal + components: + - type: Sprite + sprite: Effects/foam.rsi + state: metalfoam diff --git a/Resources/Prototypes/Reagents/chemicals.yml b/Resources/Prototypes/Reagents/chemicals.yml index d7af2363f1..2a67394e45 100644 --- a/Resources/Prototypes/Reagents/chemicals.yml +++ b/Resources/Prototypes/Reagents/chemicals.yml @@ -126,6 +126,15 @@ - !type:AdjustHealth amount: -8 +- type: reagent + id: chem.FluorosulfuricAcid + name: fluorosulfuric acid + desc: An extremely corrosive chemical substance. + physicalDecs: strong-smelling + color: "#5050ff" + boilingPoint: 165 + meltingPoint: -87 + - type: reagent id: chem.TableSalt name: table salt @@ -241,3 +250,13 @@ meltingPoint: -80.7 tileReactions: - !type:FlammableTileReaction {} + +- type: reagent + id: chem.Fluorosurfactant + name: fluorosurfactant + desc: A perfluoronated sulfonic acid that forms a foam when mixed with water. + physicalDesc: opaque + color: "#9e6b38" + boilingPoint: 190.0 # Perfluorooctanoic Acid. + meltingPoint: 45.0 + diff --git a/Resources/Prototypes/Recipes/Reactions/chemicals.yml b/Resources/Prototypes/Recipes/Reactions/chemicals.yml index 05045b484f..f6f16984ad 100644 --- a/Resources/Prototypes/Recipes/Reactions/chemicals.yml +++ b/Resources/Prototypes/Recipes/Reactions/chemicals.yml @@ -40,6 +40,20 @@ products: chem.PolytrinicAcid: 3 +- type: reaction + id: react.FluorosulfuricAcid + reactants: + chem.Fluorine: + amount: 1 + chem.Hydrogen: + amount: 1 + chem.Potassium: + amount: 1 + chem.SulfuricAcid: + amount: 1 + products: + chem.FluorosulfuricAcid: 4 + - type: reaction id: react.PotassiumExplosion reactants: @@ -57,6 +71,94 @@ scaled: true #Scaled proportionally to amount of potassium and water maxScale: 30 #Explosion strength stops scaling at 30 potassium + 30 water +- type: reaction + id: react.Smoke + reactants: + chem.Phosphorus: + amount: 1 + chem.Potassium: + amount: 1 + chem.Sugar: + amount: 1 + effects: + - !type:SmokeAreaReactionEffect + rangeConstant: 0 + rangeMultiplier: 1.1 #Range formula: rangeConstant + rangeMultiplier*sqrt(ReactionUnits) + maxRange: 10 + duration: 10 + spreadDelay: 0.5 + removeDelay: 0.5 + diluteReagents: false + prototypeId: Smoke + sound: /Audio/Effects/smoke.ogg + +- type: reaction + id: react.Foam + reactants: + chem.Fluorosurfactant: + amount: 1 + chem.Water: + amount: 1 + effects: + - !type:FoamAreaReactionEffect + rangeConstant: 0 + rangeMultiplier: 1.1 #Range formula: rangeConstant + rangeMultiplier*sqrt(ReactionUnits) + maxRange: 10 + duration: 10 + spreadDelay: 1 + removeDelay: 0 + diluteReagents: true + reagentDilutionStart: 4 #At what range should the reagents start diluting + reagentDilutionFactor: 1 + reagentMaxConcentrationFactor: 2 #The reagents will get multiplied by this number if the range turns out to be 0 + prototypeId: Foam + +- type: reaction + id: react.IronMetalFoam + reactants: + chem.Iron: + amount: 3 + chem.FoamingAgent: + amount: 1 + chem.FluorosulfuricAcid: + amount: 1 + effects: + - !type:FoamAreaReactionEffect + rangeConstant: 0 + rangeMultiplier: 1.1 + maxRange: 10 + duration: 10 + spreadDelay: 1 + removeDelay: 0 + diluteReagents: true + reagentDilutionStart: 4 + reagentDilutionFactor: 1 + reagentMaxConcentrationFactor: 2 + prototypeId: IronMetalFoam + +- type: reaction + id: react.AluminiumMetalFoam + reactants: + chem.Aluminium: + amount: 3 + chem.FoamingAgent: + amount: 1 + chem.FluorosulfuricAcid: + amount: 1 + effects: + - !type:FoamAreaReactionEffect + rangeConstant: 0 + rangeMultiplier: 1.1 + maxRange: 10 + duration: 10 + spreadDelay: 1 + removeDelay: 0 + diluteReagents: true + reagentDilutionStart: 4 + reagentDilutionFactor: 1 + reagentMaxConcentrationFactor: 2 + prototypeId: AluminiumMetalFoam + - type: reaction id: react.TableSalt reactants: @@ -100,3 +202,15 @@ amount: 1 products: chem.Water: 2 + +- type: reaction + id: react.Fluorosurfactant + reactants: + chem.Carbon: + amount: 2 + chem.Fluorine: + amount: 2 + chem.SulfuricAcid: + amount: 1 + products: + chem.Fluorosurfactant: 5 diff --git a/Resources/Textures/Effects/chemsmoke.rsi/chemsmoke.png b/Resources/Textures/Effects/chemsmoke.rsi/chemsmoke.png new file mode 100644 index 0000000000000000000000000000000000000000..e85d4463347dfe34461e3bdbb970fdddbb8546ba GIT binary patch literal 111309 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 T6~&l&00000NkvXXu0mjf8SYrJ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/foam.rsi/mfoam-dissolve.png b/Resources/Textures/Effects/foam.rsi/mfoam-dissolve.png new file mode 100644 index 0000000000000000000000000000000000000000..cd6d4a4cc1973f8649bc116b2f2bba6fc35b5fa4 GIT binary patch literal 5550 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 literal 0 HcmV?d00001 From 861271ea44df032336fb18cd2aed20ad8c2ec7cd Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Wed, 3 Feb 2021 21:43:31 +0100 Subject: [PATCH 51/61] Fixes MIDI input button not being toggleable. --- Content.Client/Instruments/InstrumentMenu.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Client/Instruments/InstrumentMenu.xaml b/Content.Client/Instruments/InstrumentMenu.xaml index 7f1588021c..0825557f22 100644 --- a/Content.Client/Instruments/InstrumentMenu.xaml +++ b/Content.Client/Instruments/InstrumentMenu.xaml @@ -4,7 +4,7 @@ - + From d45835e863c421ca17219280b58d3934c561bb45 Mon Sep 17 00:00:00 2001 From: Remie Richards Date: Wed, 3 Feb 2021 22:07:13 +0000 Subject: [PATCH 52/61] Janitor trashbag upgrade + FANCY ANIMATIONS (#3058) * Janitor trashbag upgrade + FANCY ANIMATIONS * Review, Bug fixes and Sounds - Fixed hand-pickup animation playing if the entity originated from inside a container (e.g. bag on the ground) or from inside ourselves (e.g. something in our own inventory) * Fix/Change. Just log if AnimateEntityPickup is called with an entity that has no SpriteComponent. * More explicit log message. Error log. * Merge. Fix. --- .../Animations/ReusableAnimations.cs | 56 ++++++++ .../Components/Items/HandsComponent.cs | 28 +++- .../Storage/ClientStorageComponent.cs | 29 +++- .../Components/GUI/HandsComponent.cs | 9 +- .../Items/Storage/ServerStorageComponent.cs | 124 +++++++++++++++++- .../GameObjects/EntitySystems/HandsSystem.cs | 4 +- .../Components/Items/SharedHandsComponent.cs | 19 ++- .../Storage/SharedStorageComponent.cs | 20 ++- Resources/Audio/Effects/trashbag1.ogg | Bin 0 -> 11999 bytes Resources/Audio/Effects/trashbag2.ogg | Bin 0 -> 16979 bytes Resources/Audio/Effects/trashbag3.ogg | Bin 0 -> 16399 bytes .../Effects/Markers/clientsideclone.yml | 8 ++ .../Entities/Objects/Consumable/trash.yml | 13 +- .../Entities/Objects/Specific/janitor.yml | 15 +++ .../SoundCollections/storage_rustle.yml | 7 + .../Janitorial}/trashbag.rsi/icon-0.png | Bin .../Janitorial}/trashbag.rsi/icon-1.png | Bin .../Janitorial}/trashbag.rsi/icon-2.png | Bin .../Janitorial}/trashbag.rsi/icon-3.png | Bin .../Janitorial}/trashbag.rsi/icon.png | Bin .../Janitorial}/trashbag.rsi/meta.json | 0 21 files changed, 309 insertions(+), 23 deletions(-) create mode 100644 Content.Client/Animations/ReusableAnimations.cs create mode 100644 Resources/Audio/Effects/trashbag1.ogg create mode 100644 Resources/Audio/Effects/trashbag2.ogg create mode 100644 Resources/Audio/Effects/trashbag3.ogg create mode 100644 Resources/Prototypes/Entities/Effects/Markers/clientsideclone.yml rename Resources/Textures/Objects/{Consumable/Trash => Specific/Janitorial}/trashbag.rsi/icon-0.png (100%) rename Resources/Textures/Objects/{Consumable/Trash => Specific/Janitorial}/trashbag.rsi/icon-1.png (100%) rename Resources/Textures/Objects/{Consumable/Trash => Specific/Janitorial}/trashbag.rsi/icon-2.png (100%) rename Resources/Textures/Objects/{Consumable/Trash => Specific/Janitorial}/trashbag.rsi/icon-3.png (100%) rename Resources/Textures/Objects/{Consumable/Trash => Specific/Janitorial}/trashbag.rsi/icon.png (100%) rename Resources/Textures/Objects/{Consumable/Trash => Specific/Janitorial}/trashbag.rsi/meta.json (100%) diff --git a/Content.Client/Animations/ReusableAnimations.cs b/Content.Client/Animations/ReusableAnimations.cs new file mode 100644 index 0000000000..5b93efe5ae --- /dev/null +++ b/Content.Client/Animations/ReusableAnimations.cs @@ -0,0 +1,56 @@ +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.Components.Animations; +using Robust.Shared.Animations; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using System; + +namespace Content.Client.Animations +{ + public static class ReusableAnimations + { + + public static void AnimateEntityPickup(IEntity entity, EntityCoordinates initialPosition, Vector2 finalPosition) + { + var animatableClone = entity.EntityManager.SpawnEntity("clientsideclone", initialPosition); + animatableClone.Name = entity.Name; + + if(!entity.TryGetComponent(out SpriteComponent sprite0)) + { + Logger.Error($"Entity ({0}) couldn't be animated for pickup since it doesn't have a {1}!", entity.Name, nameof(SpriteComponent)); + return; + } + var sprite = animatableClone.GetComponent(); + sprite.CopyFrom(sprite0); + + var animations = animatableClone.GetComponent(); + animations.AnimationCompleted += (s) => { + animatableClone.Delete(); + }; + + animations.Play(new Animation + { + Length = TimeSpan.FromMilliseconds(125), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(ITransformComponent), + Property = nameof(ITransformComponent.WorldPosition), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackComponentProperty.KeyFrame(initialPosition.Position, 0), + new AnimationTrackComponentProperty.KeyFrame(finalPosition, 0.125f) + } + } + } + }, "fancy_pickup_anim"); + } + + } +} diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs index 21f112d031..a59f0333f9 100644 --- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs +++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs @@ -1,14 +1,23 @@ -#nullable enable +#nullable enable +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Client.Animations; using Content.Client.UserInterface; using Content.Shared.GameObjects.Components.Items; +using Robust.Client.Animations; using Robust.Client.GameObjects; +using Robust.Client.GameObjects.Components.Animations; using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Shared.Animations; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Players; using Robust.Shared.ViewVariables; namespace Content.Client.GameObjects.Components.Items @@ -244,6 +253,23 @@ namespace Content.Client.GameObjects.Components.Items } } + public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) + { + base.HandleNetworkMessage(message, netChannel, session); + + switch (message) + { + case AnimatePickupEntityMessage msg: + { + if (Owner.EntityManager.TryGetEntity(msg.EntityId, out var entity)) + { + ReusableAnimations.AnimateEntityPickup(entity, msg.EntityPosition, Owner.Transform.WorldPosition); + } + break; + } + } + } + public void SendChangeHand(string index) { SendNetworkMessage(new ClientChangedHandMsg(index)); diff --git a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs index 1d6043ff18..c97b1e2f91 100644 --- a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs +++ b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs @@ -1,17 +1,23 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using Content.Client.Animations; using Content.Client.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Storage; using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.Components.Animations; using Robust.Client.Graphics.Drawing; using Robust.Client.Interfaces.GameObjects.Components; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Animations; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.GameObjects.Components; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; using Robust.Shared.Maths; @@ -77,6 +83,9 @@ namespace Content.Client.GameObjects.Components.Storage case CloseStorageUIMessage _: CloseUI(); break; + case AnimateInsertingEntitiesMessage msg: + HandleAnimatingInsertingEntities(msg); + break; } } @@ -92,6 +101,24 @@ namespace Content.Client.GameObjects.Components.Storage Window.BuildEntityList(); } + /// + /// Animate the newly stored entities in flying towards this storage's position + /// + /// + private void HandleAnimatingInsertingEntities(AnimateInsertingEntitiesMessage msg) + { + for (var i = 0; msg.StoredEntities.Count > i; i++) + { + var entityId = msg.StoredEntities[i]; + var initialPosition = msg.EntityPositions[i]; + + if (Owner.EntityManager.TryGetEntity(entityId, out var entity)) + { + ReusableAnimations.AnimateEntityPickup(entity, initialPosition, Owner.Transform.WorldPosition); + } + } + } + /// /// Opens the storage UI if closed. Closes it if opened. /// diff --git a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs index 0182636f77..0139761d21 100644 --- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -176,9 +176,16 @@ namespace Content.Server.GameObjects.Components.GUI Dirty(); + var position = item.Owner.Transform.Coordinates; + var contained = item.Owner.IsInContainer(); var success = hand.Container.Insert(item.Owner); if (success) { + //If the entity isn't in a container, and it isn't located exactly at our position (i.e. in our own storage), then we can safely play the animation + if (position != Owner.Transform.Coordinates && !contained) + { + SendNetworkMessage(new AnimatePickupEntityMessage(item.Owner.Uid, position)); + } item.Owner.Transform.LocalPosition = Vector2.Zero; OnItemChanged?.Invoke(); } diff --git a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs index fda487377c..b50a23bb4d 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.Audio; using Content.Shared.GameObjects.Components.Storage; @@ -24,6 +26,7 @@ using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.Log; +using Robust.Shared.Map; using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -36,7 +39,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage [RegisterComponent] [ComponentReference(typeof(IActivate))] [ComponentReference(typeof(IStorageComponent))] - public class ServerStorageComponent : SharedStorageComponent, IInteractUsing, IUse, IActivate, IStorageComponent, IDestroyAct, IExAct + public class ServerStorageComponent : SharedStorageComponent, IInteractUsing, IUse, IActivate, IStorageComponent, IDestroyAct, IExAct, IAfterInteract { private const string LoggerName = "Storage"; @@ -44,6 +47,8 @@ namespace Content.Server.GameObjects.Components.Items.Storage private readonly Dictionary _sizeCache = new(); private bool _occludesLight; + private bool _quickInsert; //Can insert storables by "attacking" them with the storage entity + private bool _areaInsert; //"Attacking" with the storage entity causes it to insert all nearby storables after a delay private bool _storageInitialCalculated; private int _storageUsed; private int _storageCapacityMax; @@ -184,7 +189,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage /// /// The player to insert an entity from /// true if inserted, false otherwise - public bool PlayerInsertEntity(IEntity player) + public bool PlayerInsertHeldEntity(IEntity player) { EnsureInitialCalculated(); @@ -212,6 +217,24 @@ namespace Content.Server.GameObjects.Components.Items.Storage return true; } + /// + /// Inserts an Entity () in the world into storage, informing if it fails. + /// is *NOT* held, see . + /// + /// The player to insert an entity with + /// true if inserted, false otherwise + public bool PlayerInsertEntityInWorld(IEntity player, IEntity toInsert) + { + EnsureInitialCalculated(); + + if (!Insert(toInsert)) + { + Owner.PopupMessage(player, "Can't insert."); + return false; + } + return true; + } + /// /// Opens the storage UI for an entity /// @@ -343,6 +366,8 @@ namespace Content.Server.GameObjects.Components.Items.Storage serializer.DataField(ref _storageCapacityMax, "capacity", 10000); serializer.DataField(ref _occludesLight, "occludesLight", true); + serializer.DataField(ref _quickInsert, "quickInsert", false); + serializer.DataField(ref _areaInsert, "areaInsert", false); serializer.DataField(this, x => x.StorageSoundCollection, "storageSoundCollection", string.Empty); //serializer.DataField(ref StorageUsed, "used", 0); } @@ -418,7 +443,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage break; } - PlayerInsertEntity(player); + PlayerInsertHeldEntity(player); break; } @@ -449,7 +474,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage return false; } - return PlayerInsertEntity(eventArgs.User); + return PlayerInsertHeldEntity(eventArgs.User); } /// @@ -469,6 +494,97 @@ namespace Content.Server.GameObjects.Components.Items.Storage ((IUse) this).UseEntity(new UseEntityEventArgs { User = eventArgs.User }); } + /// + /// Allows a user to pick up entities by clicking them, or pick up all entities in a certain radius + /// arround a click. + /// + /// + /// + async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + { + if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return false; + + // Pick up all entities in a radius around the clicked location. + // The last half of the if is because carpets exist and this is terrible + if(_areaInsert && (eventArgs.Target == null || !eventArgs.Target.HasComponent())) + { + var validStorables = new List(); + foreach (var entity in Owner.EntityManager.GetEntitiesInRange(eventArgs.ClickLocation, 1)) + { + if (!entity.Transform.IsMapTransform + || entity == eventArgs.User + || !entity.HasComponent()) + continue; + validStorables.Add(entity); + } + + //If there's only one then let's be generous + if (validStorables.Count > 1) + { + var doAfterSystem = EntitySystem.Get(); + var doAfterArgs = new DoAfterEventArgs(eventArgs.User, 0.2f * validStorables.Count, CancellationToken.None, Owner) + { + BreakOnStun = true, + BreakOnDamage = true, + BreakOnUserMove = true, + NeedHand = true, + }; + var result = await doAfterSystem.DoAfter(doAfterArgs); + if (result != DoAfterStatus.Finished) return true; + } + + var successfullyInserted = new List(); + var successfullyInsertedPositions = new List(); + foreach (var entity in validStorables) + { + // Check again, situation may have changed for some entities, but we'll still pick up any that are valid + if (!entity.Transform.IsMapTransform + || entity == eventArgs.User + || !entity.HasComponent()) + continue; + var coords = entity.Transform.Coordinates; + if (PlayerInsertEntityInWorld(eventArgs.User, entity)) + { + successfullyInserted.Add(entity.Uid); + successfullyInsertedPositions.Add(coords); + } + } + + // If we picked up atleast one thing, play a sound and do a cool animation! + if (successfullyInserted.Count>0) + { + PlaySoundCollection(StorageSoundCollection); + SendNetworkMessage( + new AnimateInsertingEntitiesMessage( + successfullyInserted, + successfullyInsertedPositions + ) + ); + } + return true; + } + // Pick up the clicked entity + else if(_quickInsert) + { + if (eventArgs.Target == null + || !eventArgs.Target.Transform.IsMapTransform + || eventArgs.Target == eventArgs.User + || !eventArgs.Target.HasComponent()) + return false; + var position = eventArgs.Target.Transform.Coordinates; + if(PlayerInsertEntityInWorld(eventArgs.User, eventArgs.Target)) + { + SendNetworkMessage(new AnimateInsertingEntitiesMessage( + new List() { eventArgs.Target.Uid }, + new List() { position } + )); + return true; + } + return true; + } + return false; + } + void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs) { var storedEntities = StoredEntities?.ToList(); diff --git a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs index 471f9d369c..0450d0e459 100644 --- a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; @@ -216,7 +216,7 @@ namespace Content.Server.GameObjects.EntitySystems if (heldItem != null) { - storageComponent.PlayerInsertEntity(plyEnt); + storageComponent.PlayerInsertHeldEntity(plyEnt); } else { diff --git a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs index 83e24cdadb..e3fba91eb5 100644 --- a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs +++ b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs @@ -1,7 +1,8 @@ -#nullable enable +#nullable enable using System; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Map; using Robust.Shared.Serialization; namespace Content.Shared.GameObjects.Components.Items @@ -127,4 +128,20 @@ namespace Content.Shared.GameObjects.Components.Items Middle, Right } + + /// + /// Component message for displaying an animation of an entity flying towards the owner of a HandsComponent + /// + [Serializable, NetSerializable] + public class AnimatePickupEntityMessage : ComponentMessage + { + public readonly EntityUid EntityId; + public readonly EntityCoordinates EntityPosition; + public AnimatePickupEntityMessage(EntityUid entity, EntityCoordinates entityPosition) + { + Directed = true; + EntityId = entity; + EntityPosition = entityPosition; + } + } } diff --git a/Content.Shared/GameObjects/Components/Storage/SharedStorageComponent.cs b/Content.Shared/GameObjects/Components/Storage/SharedStorageComponent.cs index 2bdbc43363..6cc256d41c 100644 --- a/Content.Shared/GameObjects/Components/Storage/SharedStorageComponent.cs +++ b/Content.Shared/GameObjects/Components/Storage/SharedStorageComponent.cs @@ -1,12 +1,14 @@ -#nullable enable +#nullable enable using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Map; using Robust.Shared.Serialization; namespace Content.Shared.GameObjects.Components.Storage @@ -100,6 +102,22 @@ namespace Content.Shared.GameObjects.Components.Storage } } + /// + /// Component message for displaying an animation of entities flying into a storage entity + /// + [Serializable, NetSerializable] + public class AnimateInsertingEntitiesMessage : ComponentMessage + { + public readonly List StoredEntities; + public readonly List EntityPositions; + public AnimateInsertingEntitiesMessage(List storedEntities, List entityPositions) + { + Directed = true; + StoredEntities = storedEntities; + EntityPositions = entityPositions; + } + } + /// /// Component message for removing a contained entity from the storage entity /// diff --git a/Resources/Audio/Effects/trashbag1.ogg b/Resources/Audio/Effects/trashbag1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..2324af18c6a7329c9b70717ed30c11c603d3a38a GIT binary patch literal 11999 zcmaia1z1#HxAz$uL_kVVz@Zs>=n?6Xp<`&IyQGzpR5}Nwr8@*EK?LdU5GfJqQc_g# zJK+Dl@B7{Fd+t5Yp8f2z*WPRGwbpN~v(C(@+1O|S7{EUljqBe*ODekygbw26;%08= zel-P=t@vk&H{^QI1W~)%`Cr%7&MQj)@30CI(Lev!)sOj?5eG=uvwLjIt>$I}b+j|r zxn>Vlg!1vi`FQzw`JrrTPR@3Z-K^ZLoIPMyydl_sx5A`lbRYm8I0ILPb_b5dqX2*e z08E)+L`l|)utIb$+tXC^!>ds@Z$v6OqMOPh8s77-4k}92sIAO7HE~+bXa2|m%f!zxl zw?6vfOg94dlAK@yzCLJ(GT&A~lIs1flKj~F+iZ1X(l@y3CXmYTX&sBIRvcqL_YuTK z=06?&wH{FLEYdjCmL$@6SM0-KMX7EetG{YNfk04AKp}x#v4(uGhGux2N$rqRgG_Lo zUrtL~1qEK7#=2fsvtFLFUjF*2VTMiq`b}YmAH$5#!px|i|I>c>FP~ktYt^9;Acql= zvI@&%xRdt&hgj$<*t{iF<9~J^=Koq$X##n}CA5K(G4) zuqk-R_M?@DkClo4<%BD@1Aru)rrVXKAGC%h*QA@EuDjT*yUHwRiYtr!&sWejUO+|Y zv#e9>1M&Fd&92Q72(qQh#gifYD+yc>p8FM=K9+toj37#1%PA$#nEVi-ox%FHG&SSe zgN`HAtDrAo18LnkX=CZ`BZ`FSg?~p+Ni%ROODDiN>WxgI9%?gio!vw_Zt0vJxL1EH zeZAx@IXH?yXRy}4)&9%xA6S$=${hKCSOIrLe9CZmi*F2$&MhVW9rwl-K!7}9SZAb3^)qWnn+mT z--rU83bl-v!lAwnlIN*z1F&K+D*n@P-k(T@K|8~skBLCOraaXv0Bele6bO4rLt z|E1@AW0>(r&*fSF<=GgM+t2?yu>LD@05E7muVWHz9u4o!3{jQF|Lfp?MUE?ZZvst! z0+UiLlgb3=@S%X(k-#{aoQ8mk7KQORh36!tm6nk4q@dL#%4*unYOcY`u+d+?;h%u{ z8#c?cp8pX!S0TcHh+Tb{i1qKt$zhBAA`;7}n8>7`$nrGBCL+BwEqkjpkKn&Wj%{Rd zYGiSELGXgKOF!7?eT<4*Lg%klWSU2a9WcKr7iY9M+``v7EqZK01cZA0Mr1$2s$~G zB+@Nj+F48w55CTXor}Q9?V<`TCgCSU++^^GmtqxbNzYPLa8!vm#(veMT^>3dn}pP_VT z{FOkemdYFj3e|v`J8?p#_^)`NjCHN%&QSW40fw_-E2sV|R9Bp5r#(Tc*^HH0qvtZ4 zUAmopUP)OxTUl9YS%XtqRi$CxW?5d@K&?$#RmDJUb$ONJ4oIykDXZlyE8{G$t@u^O zxzkWm)?7JI`#Phle5c{yMknV^^W(Crs`A?1D{6D)PLJzOGp}C}Y_O<&yScQv*?Fhg zb*RCKCckX^J^NtC>w(&Xoo1doPkIfM=OWs8RTtF5$;3O`@GPv>Nwlr<*8s?1;s;{X zh2|}|?TzzYaIWWwL&8iXw$LUWJZV{FMp^kztusew#g4QF%HKHIYFXG=|F-k8aIE2M zOkUY`GyC9sP!N2f$AzYam&SNr2qa|HR^ITp=sUw7b(rsQ{dIr!)HA=D5WhDL#0KOF z8)O5Lx%3OrEp4K2`yPUNf^b~s!R6AC&FEfc0 zX$wP7XyWD;sv{k8(KDJ3HAPeAd(Z+(q#YM}!oa??XUZV@mIVplMuFvsB7R}r6#-41qw-pOAW3T0p9$SXvLVDlDK>Lc$x!4V~dLG~kW`QbR>p54n~T zS5G=)4-{n8eZ0^{FcBbsX#~dqCmLg;Yw&ntDnHXZZRZq+R8e@X~Hd z!&p$bmV;o?jQthC&`E42-5psuGo={=5-glrr_+58{-wFOJy*|+dF7RG_L}5GQ26>M zgJ=YL!W?9ks2L6JFm!xdlwuDO(lw*%*FU*C3Z70GR9wkW0-q4>ac@s_z^oI92}lOw zTjgA(kgKqj3fCM_v?FmFLa^gY47{xtMFz?_{7gB|cQ5BF|kT4Wdfqp3@w*YNR#&8t_1W^N1;-IU)Qx)I*z({Z^e{O3WY>tSh@x4ZsR^7d+7Jbsk{`;&A~6EP z!%@(S1cX(Ofef;;ThF12zHr98LU0xYas7I_0}>=S48H|{1#Dd4HRfG8Ayz7Ub^*(4 zB?6(K8KdFP$;~6-8<{eAu{a~{4HCfH9*}_8pt3~n`9;&-|f2(qN((79b$A60S)W5g?QM3QMdjFqBh7Oe=e*B-;pZ zfj_u+breKkcW!WA=}1BZ7J8kj;E8c)L!ZMo3qnEnF4iVF-mBzS8cE%;m$T+;o~ zyaHY@p$Wz(l|=;@Kn4!r(H*#8^)u$t^`QO13^8LKB{*Yn1NiLx7zlj$+Vy#I!JCfp z&)2@D2a9d#YinP<7-)jN$hkrby(|($)fGnQLqRwEoePcsuQ`(Y8-Jk%0&3&$I<&g< zHMH<@!8sT$2r{Z#|69OagE5p8EalK3=f87kQc%Wqd56}#LL_LKE5bEYuSfXw zSLV82O#gT9RV@fY6!R(zGj1~F4=^|itaM3(2)K9X2z~qczmBERgWd3bYQAEg^Wqt)kv*2z;Z2;sLxQ z^!!1R5OS(MP*5lq49I!iJ_a|!!um?eM?gr>8|pq`hY65`_7Nt8>k*QYl?COQ-v{)J zO#5&H31edFqHb*RwPPDEctS&=SoGoD5c2rI5HjZ{Uv8>3eZapjS4i~t1sDR*0YKtS zC<8+}B9ntlPGaDSv!^6L}%m(9!bHVuq_*gkPI5|1l z`1#@M`~o6dpFYEcgf|wK7uVihq+&joFFrpi#NZ`;pZD~_T8G?Nr+R$4Iiu3$fMIl5 zT$Vo48bYeT`Q9cJFl}ob@-TEdtNi6 zFHJByi^!}5Un*7ai2J87l;hr0P#7K=k65M|h9@pQJ-5L8_0CY?eu`r9cXOEHk1uD^ zW|mvcJ}&GU>(bFfYW!FhFsIq*u$LAD8|JEWf;x;4>bGXyw2fm;C3-dYLprI4FaHp9 z=Im{*e5&+vH%i8IGJp)7v*W1pZq?DY;}K!*k4&=bG=#;(&UVfEd{ikv8zBv|=V{I6 zgE*eo0Zg*;6>0o)?GLLO{d!lUZ=dbqo3?tYJvN*?*J~iHMCg;ieIh{R*tjc?r~&eX!mv) z@#+~Ger^IVzFwr(sCO)>KMZgK?A{hfIUzD&&aF;U8_>e#JKL6Ri~Q9eWI3aKoj>TY zE&Rr)z}CRWvT)Jc1-3YG-+g;=P}jltlOupH$}@+q;Do$n(RYyMUX&3m40E}`@f}g! zg?f=9jJWOgSMz5o1c4qY{Q8pv_tY=yU~@ep{@=|$_FJz#z5?Y(AK(wNU!m>RByqL`1bh|!3C-_|`3{XV%d?y9`Tx#{wzhvS|{Prya@ z?B2R=J7kZ2T{Oe?P9ycyXg2S6@7a|ZCbwx@53(66H>SJz9Jd;6KkAoznBrgTjl@*I~P8ISD_G{*v38yVjy6~?%#>Ypnxy|g3U zIZBy$&#BJBp!!-9Du`1jUicWo2At}Vro((R-6M79PemV`!7ypn|WSdC%g)owi z6cve=63`gQFQFc7j?;q7s+*g6#j(WXMS1EXuy3E~6ZqlaX{mwh$CuJ@g)Wvlex!jH z%NV0CM{hYbr{muI!#=rCV*-Y4{6 zG^u#3K{$01RsF_GJTmdQ@1{Xz!sVlmPT|31xb$qWr@(ufG<>E@bu1=sm&Y4o36eZ! zN_VWfXyTf~m*n}T3r7+{owzhpWa4kOokP0TB5&nr4wKma@x9#$>>0JiWXJ)U?VLU$O+Kj`&Cl*h>Q^~Z6lq0H6;)qk&uhV|wV>`l@4S~g zoC>eVBz8@z8>^b>1OOu(%wbrh#1D#f?>n|ReFWogkG7WhC!H}6?<0;{Vi;C`y0B9L;eoR%>l^ug{63|0}A$pfwIWob&QnzithU(vZUHmI;z3i!eO9>TX*|aL{-R>jJ zvmHGe3UY#5*1Ym%v<5VnN&xEjQTc<0nY|_9xW3_#V!}>j;P=X>Vn8SOqT z>`2twMT8p^o^>0eA}CrT`vb>wom;y897F`^&5IAXp-MR{qtDkMRBW?>X*|4CTHUujFg=c&*l~6dn`1 z?nWK9xI%_;l-cd9y^-`)r3U@|oPi@snjiaae^1jJN7<6R)#rW> z0c|?!FLNTboXR#AZ*ZRAuV$agRiT#(xZn&0PNiN(evLa{Kg_Zk_7hTyiIlLBv2tJN z{ZcP+M0}-2+BGX-^n?^WkBS(j_Z}cMn z+)$^C8Xo+^#XLF5X!Tkqmk_HQRkd#T(~14DySUTy==;ie`xH--00AP;kMi{<1m5S~ zP}pFxuZ#zbZIbR8*J|d|{8y)SsV{rI=ctR(mn3U*&bvIlP+;Q8*89HR zB7dr4Ms}JREmFDia*CWHxZwi`xtfdd>`x>%0Wrm+iU)GfexL>_Xll3C;tH6bKd}_E z=N3$0oN2*TSiV#ZnxfVxr@|5>{T`+Qe;N#v{ar~e4(ZoNR*QHl3SRt@+&A61Q_3iV zak4HZ&K7mS|LxNau2ECoKb*c=u{)ZE)uBJ%YUCF;m(EH(43hGj)o=2)aj)dtw(w-8 z8%j2OU->q&S-!o~_R>#hZ|MiD>iObwzFm@J?BSHXIXR;K?xTZ%Onn_i?}gFoXjU%m zCOqbfm_fOqV?g^3Hs$e9aJ0z58J;>kaAyB;YhMaNY9`-Nt!vLxdSUc1^rYL~&|EZ3 zt2&VUPd5b^L>8Q-=E&Z-rw{msHIh)^84Onmr>l? zDyRB%QTK%$pv~t$HYrrAkKvT#`&2JHjlB54uo^>u^g#`EoJ#paS-fP1sr~AQ!i+2h zFj%bW2KzG})w>bQz)yW2gEoS?-Hm`8XxHCSb5q%*&ROtj6 zfiVY!~N-f3s`jAnW{M+3)R;F zVb<>+H#f7wc7j~@+A)4`uM3b0JC$s4dwuwz<2&o9jcoJFHKf?(;TEllJ8;YCp<3pP z_jtnU#94OJUYoS4Vh;cYJao#-gZY{H77th zM;J)lA$cqJxv%#&yH?owVBm*!%;pPl)|$&`{(=5cB*4o zi33i4ji_^n?#tlWXeS7jNeg$6v$KEj8}CkRX8N&1ClaS)9dddjSUF8}UIFpRGHxWZ z1YP%v7Pqx3M2QF9#JGUhyp!hCIg3ZIa`S@PUtWe>SM1QIF_l&<%-o@2$~EHMa1#XU z)p_zveXgUfSPNsx-Xpx*Io!tcMRI;M?VO|MFW8W4E3a}69y@CJtLy8h%w5hcY_ge% z&eyHRQ|~s^HFa@HCc1M16)g0)!*wb0)Ds5;pNSmdEk)?=`5}@gvG31!eUnEDqBMe^ z6rc1y!bS6)gkP||rIYQO7Ni9>i3WQ^f-M@V-BzMJRc8=_g!3jpX70aaXGxM8`)bd< z)}HbubABA(lKV!4O-OuQMl-+ev?Bm#qf0Zg9$@DvmN-QT9S_AnysQsR+!9i_QP!IN zwEIx8tu*Y0BML~zUn}jWz)|f>#h?C0x2D$p-sHIuA!eWyhJ}Iko&2&s^6FNb8=7My z5h|B45=-2NmC7c4kEkZ&>bP1~_pMh6ZksL!muLu9>}}v9gOiGo%(wa`q&r+LBI#1L zF+K%vSvZQ#ONOMB%^xdI4cIQQ|Khs+D`IkRn>lP3qt*OJNMh%K4ZN$kSLcna$W%}q zTjucRK@-cnsfL=jf+lO86-xZDSH9e3DEqO1!9KJ$xhqg#lJn{?V_x|Ymhd)TgbByA zSQcsihc91_BkFKp53$PW+)YQ0>c{{XJ-aD`3{EXT@=PF*Jy5VDQ*{q2xxDYL#3njx zyXh1dt$}Ohz~vZje&IS3bYd`{rTc7Zt9+}J_fLv)X}9wq2}X|dk!Us~Hmz|}&>ciz@byN#Dt0z;f;E*e3l(CAG6y4zYqyBNNux{4_h#~%w^ zxaH##F2Ahh#s;*Wm2f*4`1<0WX2jf|R6K@-a0Sb_xVeU6oqlfW_P^b)5zp;}rwVCR z_ZViARq*9y=PcGbUPq%CAj`v=I>MiKIx2Sblq3jV7i1jOqo&9*ru|W}YNi1YU}^g} z#!50lDs1WEOT4ujPLK!Y*Hj(3%CHK62)QIN@BZxh{3GX!g9+}^mZ!dAB7UN;KTV9U zu+xhZiZkCeW{#%z{95mb2#X2Wcxp_!lQu%Wk;uiaeu!`7S(AFFQcyuDzplFgF~0P; z^Jcek%h=?av7hN#D3L4wPKn1dR-#bB-ffG)gP$i)#a><9&J~mL^fTiVcK?_!jX;n?PPhd0SxVt|wVY(92sS=*@Y%WbIS}wl+=U!umu(neErDd(U1yYH|BB zI(2k`n&N4wlDt?YS9Uuw~k;*NEy8_WH4f!UukiL|PNW z6DL}X^`cT&+{m?Wown?YSS+@xloL|>X?}gV-5h{!-?MvB_wYX9F7HlslOsPlfPU>Y zYQP-k#gH`MnN5}vq4>Z@OsQrjOh=>=24XTtXTrQh^j{Ll_@?{e`y}{90xj+5F3FCgYaHp%DGBP38-OZ+{ z_Y=$vbKp&;L@l%HZ(kqZyy;QCWNCw^6P+Fii2P)U-@0>au zkqnc0Kg(7n!?<`~^0~+}&Ztdmp{~6&#>M(fjezukV(nS~>5+_9c68>e5*ti_mv3%?l?lei27`+TFtM`nz&~b3 zhHBv8k>s8mndgZ{C!a~Xa4@mS%sp?F`d zY1YHQ;=Z1YOVyLKCP^2^nzzJJJ(yL0n%gh43k82mZl`YHh$Q$|anSYBM^Ac>P~tnu zt%k1p-#Z@@rsemTYly+f{W-a%8QNd7&$TXmczJUcSEBO~mS>$M^F}(ZVDZiBjiYJ@ zAcZc(1Ta@qV)qhdz&nT{=8<(%@|>i*jfB==Y`JZUO;RoSE@ZuG(@(~`6CU;WMXhcu zxwT0RMBIQp0!Jng@PEXg^Npp-Ht98I?>3{oK76&)!RLpM!TEQtxr~=v)_o$U79&A& zuL>o4`gbQ~WW36ZD^C_bV1+iRs5e4)89U@Q;5DiR`@fhPKXlz0RCR)09&#Lj}q+-^4w~R83en&Zt+3<84lZRwi0(UfqZ^|3TOo%!|~3KvR^& zeN(jT*Gy$n4C}%$zHZWOW}OHHu^G%Rr1UMRFV0&1x*76=%ItIlUTatb^r^pIpuT&a zCOvYvXj|*8Ji_g1)3PCT8p8$zD268BnB@SiHcum*+ErQ_D|8SK<;!MYNN~m%oyV-2kZmk(WKr1S{r&t~&s~{}k))zu(9%nvbfaOXN!wGJ zot`GHXusE$-l`F|`c0L5@FUE+*ED_8eB7g?h`e}8a^6_gVA}rVtCi(Af#W{N^B9%n9;X(zs6 z%T2nx`RlSeJC_Z9aoP!qY6uFYPNeKYI**MjHMa_B?vUJ;Q8{;7XrS?J*-5g& zj?H>tylqkL&!~}Zp3&ywZtJHR?`G!&Tcq$Cm%#ShRh3~_x9u!Ka8eYs-zi&@-){f2 zJ@5K%aYe^Qz)Z-rLh+c%#vv{`vc_;|Fb!=qJGrhJSKwk0ZI^L7q;p*a?wE4I&K)gj zATVoZ^@Iz5K`p3D@6j{=ySqjmy?8L1fL^Uio`?j2(XyD<51;u1t0(TgKNzoD2#c>M z*=Jh(K`pIns3Ip{Ev&CSQ7l1l%wUXHoObw-F;S0yJQv4em1BaE3+d>7_pZxy6@F&o zB>r2Mr2uS?eDuMD0^!wmcws@2l)Q7>9A+3oVanaJ~(e~hecn@(|q9s%&*44*WrFP~L^2No#& z3m_$muJjwm4yf=@S_9(F8pl_MDqj}7`agW_-{9&p+x9E}mB!oKhm1LHiPml4;Ld!a zf!VB3mnZ;G-Q=rt5fB?GMyPb!h6APxKo3=C#71ceaF{XsvkmU)>qVYTGlSY+o36+w zP9I%bTg(izG~w1Bfq7d9_&;r2VB$KAjnK5b)s60#@ZHQTqo@5sc&0BS+UC?cC2psY zi7g{P9Bh_>G#60>p8qmws4J7^%zvoB|B`{SS;no~r9PT-y2Hg%H}WZUrFo1%WZvPT8`6Ix$Pc*zE*a6_<Q z(nU62fz#ZU5d*uvkDSc%jS8@&*M+i%vQcZ>j+%V)7vkG0auU}`_%xslKl^dtvu$pE zZsV8#T;|kF?+bB;o_(oRRb;9*^|Dj|ZtyN-)eUy=qQNeo^0_<5N0m=HU9Ld`xw5;i zWQt@wHpK&NFV7sopL3)ZrJFrQYp3sIP@Vlzr^t@*C)~WVS#p=|)^FeU9Ch;PDmf-A z3RHCFkrr0~d-@FO03Tq7GF7(BB`a!b z9TD|)v&lIB;4{4NZm)Ma3nn3v&0MwPA>_#9#{Jzwdvrp#Q*(5tt=p_Z?FL(|u5M;} z%KOt*t&T4Pt?aLC-6?esQlvsdlH_dp=3e$W1UHltg-IB)V80K-oHa~Qb^4U@sSJu0 zGZ7Y2!mgF9nNz%p9X~ER5tXop*qG0LCf>x)kPv;3VA!ETR3u^vb%zV5ZXzL@Zk$q$ z!q%!+VCZhW&U~QhF2(TShjv2zBer`rfFerz&XbB!9^Z)v#DqhW%l6OZ#Y1xRwywiG;2hY>a1Vl~Ia+e5e|C-$*v4Oe%kd6EZ zMZ>(k`TgH{X4nnVV)lEAfpX`0A-m!}AKda{Lk;auBz5Uts_;`&LkqaSFT!PxKg)bh z8&m3Qli#itFnP7%(Jv}9R*os0lQ5{3>9SModo&n$k7GN~&Vza_o?1VahG9%JwSv}& zNl?Y<{zT*~c|P6J`?k5kZ~60-4`b<51bh4NEs_K7uxw0ZNN5Mth@S1g{iXfOAiFda zsDvD!JV^P7-k}H)HNnbBA{X3~w_x*;`SJ^{6zkd2`K@-i$qO6%2NN)LejK}?RDaC7 zf9{R(xF7e;D@7QpmrTYv*=AQ>vqQV-TjDpCI9eZ!cjs7kZEq5@`|e%}VhhBQ%w&aD zQqMIl8{-^n7*~@aE3Oej)WP zkG&yTEV@Nb&gjz1w_5Auhu{XkKq0=}Hcw+6P;vfXgx7NTINpi+r#_S3pxP*x=_jKL z_99px<8PKYmL|TuAGe&9otv_E^70>=X<1`@(qU?aakDX6sSo>mR~;Su!%cBW2XXLp zqXWn*tmJN$s!<G6hI!Akf-xV_&-DdZL>X0&+m_y5p9s{K52;ZbY+*Ti zQuJuaI3UY<0*2s3j(s8h)AjXdS`gVl?{D3@oF}UIF?HRJI>!--;%PJdKuf(9*=|bliWkVEk1`0J9rAw_cJjhDhvGzKd&mhYDCY6C8Pw zNlerGfHRN+x}cM+VPL0HGcM37a`gU?x#z1xM_c9aaAV_5`_*sGm#Hr-{l%@SdiFn+ zMPtaITeIEvr)NIW_^%B1mc8;O?{X&n5L16UV||hE7U$2k{x>mN=3d+c1lw~1j1u1o z0|})`_aM=DNi#F5@9a_WFJu#mjU3fK*VL&4q0|ay$1Tb~hkz5LX@m$Pqh@?S3sy*nov^2UG1zo14W z85UyiYMY7r7Mv#^EH?ONCVlS(DRU{&UG#|ujy4yI;yi?gSO$WbyjuQ+i*Z7Cm0*Fi z=F8>1e63X2t1!k%J|>UzNYQvgvP6CtjPQr%C|^#41D7|{uG87pB6n&QUUAEtU=uWW zw_}B#_#tVtBJQ!+jo7y{V@E9-e0Cy9zLm6+#6H*+^m4|f@%%S7egbajd0l&KAmEMJ zUap7xZGfz$szW$nO}to1m`+AH`RA9=(l<I!(W=sK zr-gX|^%35uk__K=VL0n%OMz9NGM-GQ$B?S}09_D#)`5{eYKzP0J@B8wu_{=<7q}A7 z+SA{7$0Z4IUY^C<=rCmp2mF#qnj>eu$jE<|73X}z9MW|Guofy^`ckVdc(yS~>R;l$ zfiq+b8ubmObTz;RN~KE!vlmAMLKV}E(%iBRDcb7^=u~vEf_|Lkvl-eVZ}-STv>{>xbWvV| z!^bZ|-RP#@&`97<9(=OgMCfKz6*@<@gfV%X@1IcYOxE+)AE}7zv6!s znPKR4#|vky9Ig{)IX6kjnay1`J2)6MyhZVy$DNToZPF$p-=<|%?NFsz8NCtX)m}G~ z&TfHymiU0H>wFC}Ojpu}6Gy7I+V7a;kLk|jdjZBwvzu? zMg|wD^o<)~;D*M;zO<>-#(oA!4UeJ)0|47aNQyiJOZlxb4kr)A16N^0PGjU{*-nV` z<@te#Y-40WvTP^CN%A}=Iguu2ObtD>fq!L%A8g*iX`)lDb0NAHO%8bKK9Foo)qt1NbBIzPF zo-8|kE&J}jobcv$06>V9XxNcx9Bd6`rbQ=C4QKupXSo%yDc&sdf4&0#;svY-d5(Fi zwLd(2ywP8?_=DNv6~K!k|C0nB5MJ<|EORdNYML80^B}(hD{HYoLN$xFy&^s9uLo^s z@b|&K1WsfO=V#1iI?qTWXO{eHd17S2)l@8i`|yvlQAhBNz~ihIGT|!LG{JMV<}we< z+p)l{h~zBV=62P8{Qd)r3jOSve(qiHjEMCtn|1^}aO>VQ`{y*mTi^ccj8^eR zastVGV_}W4$VaHs+22G;7}Vf0&&H&}&abAbrr~C% zmFBw98lt!Ay0hZDvl3%~8~VQk>wiTK030;Ie`7M)IGS}dJ4jvx;h%&5D{>sMMiYp} z6R2bwsN@zHr!P4at~ll~#g#bZRIv5tv0WE&%v8Aa7CFrp)y$UM%+^}WbXtA2TK*F- z|H5Wx#r1zg&Rd93aL4XTB|`sqFya*G^b!=?ZL_y7PU z*vY|Ykxua<_WYXgtcO%E1>CR&LwLbuXza+`C=@R7!nFKtiQI$8SKpNBu*GD9VyHjF zlA#Qg>PjDcga>bX&;TU>z@K%DbqLRCMtVG7Y=#vfCwYvUwj^mm8pxE2#}1?}#W2~= zE?STtFHXjjiOea+Fo~!EPXfS!KS%|0@z@YTSO9=AvN9qtBw2X|co?rd!FoWX4B{@! zR+-_3Ck~!qh0O^<1C|h}%m7D;f~SNJvV-P?=>Pz-J_ztfgpR{h9Dqg(_~t-0o5Uf5 zMJ!ZFRksidkliK(`vs9AVzzmEMQ!cIPA%1U*F{ZF!))zNO$*0QXC-9!)^`{0jq}Qq zE0}7uY-ZHzxZXSy(yIU{P6HJ<;%`s@C=lOsy@i zY+$UcWUOkaeyn6XYbmensF`S}%c`w9Yq>xgWIXF|sI0B6YB+zRcGR4WIG%N|_>=-C zORG*hDmpss&pI5ZTI`65Do=apCkN^#8ZOQ{-Z#0DE2+6|CF|{Lfc3C5aL?7b3u&<9 z?X7v705e$l!#(?ns2x15(Vh_8H*&=wX!J3*#3B>C(#o2w%Br&ldxpX4GZ7^<|4J`r`Y5b14jfeAScHAucDV|NI%HX9?3k39Rg zu0>yv5k}*{*noJ$2IhoN0XYMt|3?S_Ecp?Z3L5<|nG2foDOs^9D=fLQJZP9GSrKGS zp8S=5giJM7dMrB$17rzIUQmWBC{YC26eKSz+tinSHU2?Xj03V_N?y>mt{C~M9gS{+ z#&%R}IwOrx()30EvpUKzMil{-6l2MNnAE4hKrUJkmZ~VmP?4@^2D2(D#*qQBwqWVl zvn~^XXB1=TNCQW(RP2~WGAT#Mz^umKg#VRske&=?l@XK(0@b7>Do?Ejs;|DReqHZ@?#{m%*@)QboQc!eWWm;4AMKw^B;5e*fb=nbZWR}4!W z911)x4hFlYKorDHM-4^^5D113IHHraK^e}<%JkGFsc&1yS8Wgzm^U1C$+EIFSPBn( zNz>OiQLDmIz>;O{C?;^ofxx%M(VU^m;331S#?`FF3(Da5#51V|2avW6c<&OFT8@DL{>T7;|#{|SWSe>j6qDu%|aH?*0n*yIG$>0GjXyohxLJ(MZV_-qS&_FPn5_>+Z_|}5 zm<*acd>8^N-aiP_-eV6%p{*Z*N4x~}-vJl`Kmq_HqL5KgWOB!%#=VP2OF&P=NWx48 z!xsSf2I2n>;7d7f1!VPkA4f>RU9f}9*Ch4PB zoQ(0oo`YjUq)%tJdki5jEGx%lR590s5-kc9zy$&bX(R4s&|&q59iiCSo2L~|qF8aJ zV3Pug&ScrZ9url!X@v3VTG!PV3he@ZwF>K73=0c1a0!o`+du`PZ!f-1f`9Kp&$juF;ia1M1W z^S}vF`aCeIkFX{qPJ4?zO&Y2M;{)NV4Ds%nyw=Ktlro3(b-Z=sX`rUPL7yZkx{~?o zSa;vyV10EYQS$a%kbMYiGawtL8}(Qkjed7FSBS^6ed*9^MePG|qjgjoB2Lzd&&1Pt z)4DAw{N$MmDDN`_k3>@lQVWEkuj^**X0p_J#N7!bHt+(!K zI&<_rNz*gyS!(?TTWsUe?Bku+tgw}NKTxMd#O?fP$(VnZZjDR_F+tc)xkWB$Pp#pWP)1D;KFD1lt&*NuU`?Tj3|(iUttlJ{Jr4 zJfvp7P9vCH!H&HspjVOZl+!t(9vw8F=H2Nh2;sJ7#Y9v-xQ-U25WC?9`Ssft!R((z zVaDcSA$ibTW^=%awe-gm9i3P13|v=pEFJ)tCbS$JA>dc{)}rT4tSNqa-l?>K8nvCi zBm;wxf~CB5>?jeOrwJ*`8K3piRcD=9(~v$t`9h{sK|MO=ejWc zhedfgxGWj>e@tbYl$mWleI(3{sm_L>-Dp$B-~1sKEKW}$O1RJPE}!wJWvvV3s)lq! zEO$#rR@JHyAeW?VI(YGelmh;{&36+D)p)&D`$d!(`AGu#Xq4x2_G`nWb<3!mdnEc%N z1&v`6>%}9t5)qCNcM+pL{U_8T%N6kbo{tDVH0V|38iqja>Gfbbt2XtXxA9L?Z*I83 zSEEga$VQi}s(6QxHD_SoxUr63WHH1I)MO zc9++eqgWbr=}-pGAD7njU_Y@v883EEX^e8RLUYpESbRf2N+aId0L{mEJ9Cvpre@>f zHt{VcX)Wp*e|pHvqY0} zS8e7HCZGxEv+^zJ|Ls%P$*CH0ETCyt7YXe-8qjxgu<&{MTgwh-J$_ILv>QZu;cLhX z18PTbrtDsdV6_Y z!5_O=XIEo#R*!DUnn*z9ffoA(SF6;S^}Arpc@)1;{}25sxiA(sURjPCQ!;=gm0j1V z`00;{dmdt-4TaCTh?{NE=t)?TdH2D}VK1xuVx@L5!5mF|4_mF(iBilt0@&H}7&Yi5 zi5)IHs~Q-lFbR4n@VG;_mwk9Vrm=qb1fJI>)NlN}h*4)0a%j+A&3`rK=ejou^fxq%_*)lk2Gq1|e8*b3m6e^9W|;Ie!m_Y4dj zbeaL6TsB)Dmz;=edd7VklCsEAWup$xzIi5No9;MgvdVB@hNYkCz+!IjDeq3X5}B(= z5=JB9r#iHP8=4nus6~K+O>G#A#sqcJF)uKtX?06|hIB2kKHeHBVNV43ID4-qO!Rax zva5(@U^^zf1igqt(gE}}p|OV!VW~ViA0Z1ISX$Jm+Y?~Y3e$ZoNIj~;J~L%#m`{bp zoE&A~a=EuY$?2`i=;g@B)hMh++D)fY$K#jQiTmHNE)|`-3qL+F8)-KHz0^eGY^1c9 za%+bmcrxzw%r2pvPy|xWl*C2l9L8r014pfk)v}<1$QuIPzGkfku(mq;YG zPeDi@H&7U4zCd7G?j;-yA{el1O^uCktlpa_RM^ct!0G$89Fyb$t(jt7vwodA2Ickz zo1^8UiLqIV(&be8cZ^wCLzGFP_on66VAV8KlTh&hPan`?U=>d^rEy)qm(%^ER-yIO zNPYL>b8$T!M5ErX#a2sZ!!PK&)bB_PlBSQYn8xkJQ&By7*c$zz$QE^cB63fwq;} z9@b>!o|XAXa{zCu*7$gT^8L7jdE57?ZrU>GZ!_P-%SvnW(ms&H6+2H;X>;#5RIR{! zJM^-BlE|VrNPK@oZ*qe;$s03x+IFk1;7fmS;}k-F?{pX0(#kd8dU)$4lKpU_d`+SJ z!*pT*(!5|ZA5&hDKi*M_m_3*Odal?m5`9y9Ng8V#if)U#M5Bz~;Z#;ddZE%ZHI-vH zDVa%En+o|`!qqhCqDscqM8THirj{nmtk9p_oIK@Yn6^ZkP&za&;uj4nD%4bAXr&9U z3@di_0VCUuuXmJ5=jp%qj|Ic7p6@K&P4Ap%eQq`%&Z}NYWt)elrUX+>*_YylRTyf< z0?`STtJ8*idzOEbl!bIl`1^A|+SmwO?SGGiU7l?r^a`oZL{}HwZmsuov%*w)P{wn- ze&bJ(o(eau7(7ja@}N*UK5`ytKSZVR6I{Z#&On^*Ll7}ErUtJHjfMFOG% zIBcZ?EALiBgAXh$hQuvOf^;cSX=TY&1vVh-Z5P3vWuKIg2KdTfBM>Me*Bo6hbCxpNqp)n$t5Tt>3zhd~-5@H@ zx0f!j)>I3fb1?tPxht2lPXSJ8qzIz=!cPUPf9Me8X|Lu&^}7<2vuC2=VXa;xzD))p zFF(PDmHZlH7H2%AiAZx4Z*ye`_&9Y+wT$YRs$rvdM-Ev_;xHd{e|C0rH^R2`!MaoR z@{(VhprTXLR+% zy_-Z=7o{o~ji{$biW@7z1^%R?I>o%~zl){OB_8E5tx$f?>tkl9$*5aJlg*O#f? z11go)SbP$PNZcG>EE3-^DdaLN6dG-rvJ`TsD<)Zk2INM3FPB#~y@WnE9Mw*P=2#c| zM6aQS5fV9-pHOA9KkI($(0|iAkMQYNHpANaL)EgM#zr_CVJVU|_I{^K1)jF~{q zRtax*i%gS&2NeHv)nwQqHM37haZOlyX74f*E97fI?_j-#IPbkB{canSA1`&T9brY4 z&%Z74Hg*HP=sMREvt&p-@I2>TfzC|Oawbo^c)0z3l?NU3a4F>+7d1YTZUi zGah&bS=~4ib?q=1>%`(u@-jumu?OAZX<7xYwI^GJHT5T~@BJ!FVqO?hGiiDq-j_4c ziH+i4!9ZU(M`a1y-yn$gD1x80+UR$-oJiNGmc~RoJ`A8bgy3%b1LX0AIU3`~#HAQS zS)%31)#~}`*NZYffWGv1DkQX3yg6Q0%+u@G7J5=@+LE;^I#}8`gHsj34|%ndi&eO@ z%mSt}9i9UG+A6W7tWl#k_gXV-?~k?CA)fNIb*}qeD6aCLew?ML^+pQ?R}fMA<#OnacJ$ZwsX7%XI_cMZ=iq?pxPHffD$+|nvG+5Q0f7#2yNv09(b zl@hI0mRM>~Z!}Hh#PM0vzW8=pRcSdaiNlfQC7C{7_<658f@nHb?vBE2k)Pb@9I zYmh0}>*w(JlC$!n_t~YpbIa?51C=3UDV<*Lwz@4KxYnAe zr?I2MtHTNQ$}SvJDP zlTM9*H}w&wJd|cfzz6gmSzzY(I1{OmiJqWHRhe5kE(QWpht8kKXP8VP{2Vxno;K!` z*q+ZA*e$Hj*W*lcbODGrglF7F1oZ!THJ~s4zrXo_0yk5_pbd!VXz6I#ILM+CTNBJ zp{OJ2aCbGOqwdwjLy%qG{?Ru}rDrRP+U7pIr8%k)6Bd(e5_u3WOUZP$ic5`Q@Zmnj z3g3gYQ19}0poz0oGG}8bj=e*2NqN^j)G>81r7y!6pUy9$*|IAKL75D-v;^t(rm7)# zEutZwwYBs6@&G$hEsjIf9)NJ#g0^S8lS(n{%+y*5r+6QB^rS?*l_dGFmClVu6L5>xD=RC&W@W-4#fNcjkH0ZAFC=noWw9T(Ntl-CMrB2&pPps9UpKjEVK(#GvH#DkJU7=KPAj&mV5t4}xuW zUJprz#jQ&(PUO|szkW6ow=wfHpr;WB5YNa&48d@9P@apndYkm21bOlVEnLgB>)=tO z%@U|GE7Z^a+MUhk?1`N1FCGYAQSj>ftOjW8m_e_}&>0IBF}PVRlANcY@*nW=E4`yE;$SUZRqwk`CFEEZ9N26&RzWJ1f!S)b?r>ND61QW18JRPUrn>&ZCq_ zZ?@$58~GkYwSHhq*c#g&;LmZy66OlXJvEhvMMf*Q8yoe2trH!k(>Tc=vL?nCVm*>T z2uBrg$7 zdAV7gZ=sq)>rPm(D(R$_nK$-qJ4z0^_!?XhKEERVRKcZ*3P zm49()vJNN@Mrv5e;Q%$VG+pZ7awTjPIn}T*%BP8+UME>@wx<4|l2kaWI+F25;6pZE z|HW3yT2~S;+vx&{Edc+(U(GPvA><-C?{DyFXd%c4O`~H?{T*4dNcKER&B-$w4?rJ@ zNq>ZD5EN+R{lV7C+5262{RmYy6RncEzu?S_?QHc$p7s%Lk=D`R#@CVM&8H;MpfE9; zrg1+4^@8S4BB;OT;wTG(Im+y?DG29?peQbBy3F~O1%i+ypdu10J!IyPpc{i=tzrwx zjYL(ERyC8oBeouU9!gg&KL3F{ zm=TDSh(RL}$g72y`*Eu{pPv7nKEqNE^sZqj^3IyeZi@@gc(H~rPqv+zu@r>UUDOj$ z!}?|4()afEw_)YaxYVI%yUCO*h|fnW!;jt-D`?By+^k>yJPm(;JKy5;BBDm&JoQsj zH8KkH;?ZYM9HKCOaAxqX{o_vMM&9Qkdb&yX@s9-YF-+q6fLoObuC{(%hejB^Yupr< z!o!zuUOJ1|vlpvLo0+g?pw6XXO=b2Sy}lGG0f{Y<9b4VvRJ*u}W)XubK^6zbOez** zE$gG8q5N58HG=s0xNt-R8jktO-&U=4T(ACIW$t`!^?^bFl1CRM+Fj)GIRPGh3QR`8 zJ|KTGHn*h%9!@b=a@2j_MvPcvxVh2sBi~QrCwwmaIAlIO6q78&tXL!|J|3=6&cO5wfm@p`z84_yFr+;&+e?Ne>ww-sy&sy=rU&n<3(O7-B0V{O~ujC_$7Q9$@-{9k8eQAnntOD^>v~s z2M*t-X}9Orz$l(|+yX@EXg=;G)$ajr+StGGJ6ruHog5YkRzoCo$b2nb>N)8d+BKtYxSqk3S$iuKi<9zKNJn3qx$tu1b|*}O?~j2J*AwsXFlOd&A=a8{a&mv zSx0YeX5%&5ur=z+PC1Z#T}R9_o1@FEnTKGTTJ=(xKXl?hpLUVh8G``TSy^vPsJg5*0gV z@;F5{kAw-xF?+h=R@8i3zvK509i-0T$KV&XV3vD-O|>h}a=PbsRiwhoCArMZ*vMAl zMP;SZQ?cB(&Q8u8TrhnCb#j3s7MWIcpP6DYzvNCMyBkmF1v*oIB;tZz>r&!keZb?R z+@7MOEu!KH|9IGTooyV#7c&cIOJh}HWXk+;R24x8bo^7}(wal=3w6u^*GA?T zr8sh<1~6G9fteCNjEJk5`fGFL7H*7qAbCuGOlk=?rl-?@`=0@J<4cUW(XaW@PheQ0hrbI-M8 zY5gvH8RKVC{uN~O5ZVMp4@ade$mXq#}ON2d3DO!c?UsgKputGSsBgqZ3DvknyNAVmwiKM zvHZ;Ih{egQGnZm+FZ+N%e4$XlE~1=rK`=!&#rE@Li~yl6FqUH4;B3)aaeJ*)-@+;v zPvV_^LBnSz?hFoh$a)Jc3$wZHAcCcS@6AqZM&d(&klkys3aD4QCS%})MG%uAk(F^p zseUt<)0hTV7=G_qb9~FB+d^$YjaTz`L^MHEq;aogfK@bMdD<0YJh; z3<2}gbAk2mkT!w+wJHh7ZzbeKzaaOggb;H(a3?R*O$hdz=H+O zsDuY29cpG_MGe+&7dQ}Qdo%jpk| zDW5K`U3m$X)C632MvAs36;MD`Bz)p(ugmSy%LpfjUCieD0^cd^gu`X?4tL^VkR2mTn75tR?5p2^ zKMv&bYs20i$xSU3Szkmczx~b&`c9wnZ+zS{8Q2sJ0>Ym4;_ZWh6sY`AcaUw+hy;-; z0p2iPiVC^-Zd}^*q~O(z#ODXQTG;L%C2I#I84B&c@)LmD%Y%{`(q+AHc7|Ka*z#<+ zA4@T(!?b;*c+WN$@5LAZ0gdk&Thy1%dDnpFH{%ojR&p_HIWnyHopxSTuh3R=F(w4d zbPVD0O{gU%?s)iAWlRD@9*zO_VpR18VyrjV$yT|fXiJ2H?Q#e{ZddxN%4}2uC%9AW zTXy96-E17yoR=`zkh}}fhiete2dDJt(8_;kGx%-iAp9za1P(-krs2`uy{)%WCPm5k z^dOUSC8|uPu@){q+1~q7``+zWBwRiQWIx%hJp=2B_&L^i$s$BcmUJ2X0gvwtG^|rF zD#%R}UN_5$taSIrTvWYGKA3t(EK(?%{3f6=x)v!+L17n5^#+*Jq2-O{-voaaShdaL zC)!&Y-6WLql+=AcVF}J9fxRoA zu=?D{sLmnvJakpV;qnu*IQ$`yIvJshbvHL12?G0(RbCd&akD2TlL1kW0)m8axI#7! zuV$UPT+AB96?s!}hzM{!pj?qb)qq2yGFG6malbpA#0_UKm4Wj}y7FSXd+xw{~RMMjnObqNc04t#`CIP!-Su&sH1) zb1-(OL=ix>y~O<@b{2HE>VNcswWa4D;eORj=!`n6db#aobs` zdujBCgivaBTz3n%S{huda!Gi#jQuQYDd!gSzqdO2R{s0DjkmWw`l$4RNWSmV0hu{I ztgUXXuYzCgprN9p0|MDtSJviM_SY9kGQVzpSlPm#!crxSeXX`%3}gG?ZwQd8nhnv} zEi{h|`FyqGN!a_~?(TT%6^{R|+pRx+?b_}1%LajY5p8tql=VW4A=gU6;p0(Gl|J^u z<#+jI*Of`4EhmfTM=u~VH%3E9o2~HIEdh4a=6KvO7<-4Nh#5FJ1sS(@Dg9gAL--1Z z7?Sxy`)i}`t++2N)nV0zmOkp!_&YHCq;$2T*eZ=SZO@J+LwaV^G%~KwvUv|lo%adg z)DkC{&Rx$<*N^C=D&{hl^v?QAc0F;)DjvV~NQYEe9b|jACT|OU-)#^T07tXQa6&zZ zRaiasmAfYnW%R_s7BPYhYVN*dqr90}d|n`1yUWIG=X8(S!ugcv^Nf{DZtLSD@y06y z^vbW2anqiC5mW@eo75i$8pgoyjY$~Du>&5&#;kuBNY*E#ASSq$^@#OIMFpP1@K#R5 z$e6feJe(1)+H$CRW=iEv8`oSEQ5gEheVhfDD>8h2UA|__2;6_0z(~#c#=BJ&Lv5dR z%9Ali)ACU~GPz;ONjmLbR1r9rYH-D)roGTcr#{jDxk0;@hdP?Ua+Pk0CgQi$0&Md2 z#q3K|#yqdApfJFP1OYa1f^fjE1DfcaphY*5y0r0%(Ij39s3^Shmk_E_(TH+<2L@C$ z3AsfG^e1&T;=@^$Kp78h^jSueqA@QKXZo~<;~E|y9;D5lAB8vM)N`#ftRp2U+kt~S zfG`}B=@)rT6GO-c>rr%_?LSQlzZLHaDgZ1LTVHI=WFa@7C!)(}H1RP(8av?M=pfUz ze%dx5VxE)xDY`v8LKXhjD;R-5Mce=K0j|a9*N@l;=oLsY5>6`*`0;BNa;D9*v=$)@ zKq_uAL76e5IR|CR{r!?(BPtcklECZk_OttpTE|3lx=Wm~C1Blys;P>Q-0+k|2;~Bw zW_zW8x3Ip`l0o>3Mh@<+W>P4y7TRv`?(=h&2vR?33zbQ{IlBI=abD7vfju!w@{esh^}~db*}d#LsmaJ)I7I{&K>;b5(UT zEDM@FX;ZsWy?X4q3SVI?ct+0k%_Ifjam+hICx zp$OF)9Pm}l0vpylE*^3~?O*`YXbcT%{=M$%%dE9vsqcqJBNF+8hMfDahy5P4)0lU(s zqExgG(euEuPCJ>}PLEQ3q+z3zPQ@ZW*n`VlBc~)Zvb6Q(1^?-{8(aIyCGLyW&8cXs zi(jIXfMud3)q2A7o|H{>(0S-V01pBBDeZ1ZAgD43CG0)c#|kcCOR+|%#~xg^wH(S! zU9JJ5Z9xKIbn1b%R?aCc?-iBb&$sS@Vlwp#U38#dc5sXv`FR7;Hq@B2O+)&)l9T4Z zt;=8iIvu4(GmFPL3<$Q^1WT+)yX+g`_XHySq%v0R6tZ)AC(CP0(0(s3NRtU0<;Txz zWcGF}i0kLcNZ2xtwaTE8c?kVTta?+*!~wc_|BG|u2DNwTu_?zKl17YuD%d!PF za;BfT>%BjkD0ZdL2)6Hzqz(#}l_rUPRy$|P75Ny1SQn<8R+u1f2_T%d8bj<_b8K<% zrp)DY-IY2_T7u*`=6o6d?E~RNuczr{gt96hjg2>u@OrA2Q^8&b>Fuo5{(CjPy9gXm~$ z1VD5DPIi?`HS-sym*3Cd?0ku4j29)YGxD-vNoC^(rMG$Zu=Db%i5{JeB-H|11G#4O9+_Mqmz(_oiKV-5XUSaA)c6~C>hzfiRTkiU&A`C<^xd2B&rb$B5z}LsAGvBIq0>=oQ}+iH zoiil-lAd1*rRXxs^XHvM+v0gNrH>?zFvK7;T`AXQd^mfETC^;}*HEu^r72bH^*6NQ zb;HB~qvUKN7%B~l8o>l-EHZTw!;arb7Pj{lf;VuY8fY(ZY>-c3$(Ijh=*?wa+K({$ zV7WA?ls_?d^`W+%orTZ8q?<`@)D7a80y;_)LE#cMrN_KmUsl{?Nn1VPl3urm4Q~#Z zFxLnLiQ2k%ze*nMPS`{8CAW-_%L(;nZNi>ni4+iDknru!YOQ{8siw+M+^d4H!pE$n z7Wz@2?`PV=%--;9X6$y94%bqgem?Xga17wf-H~f?1OhqKINt?X?J>p}2ybRI=0LRM zS$FdgX#3kKq_t&im9Eli>oz*{6yVB1?x9qMyj}}cRe=zzn)WRy?_%vdb^^h_IF3<$ z^+8JU*Pr)D$1W`dU;?V|yR-;!R-|L}H+T{7#I}KQ)rHl?ej2%o4@Y?k9$C}KH8~aP zM37C-$HhjfG!)wx!O&@F%*!4n40?#vm4Y{Oj=4D~bl>oKYh+Ten(m>?5)f$ix;-(G z9O&@m>?wwa78+PiU%7s}_l)7SC+$A*Cf{GH)2lH>%{=nr9IKxHf|V##C}CG23h6v1 zo?-C}Ho^U#zwRdlC3cTkb2N z3g#!hmi#E5|J9WS`Wwd7Ym}V$J1!^w;bC`239sf`_Q1Jqi@Fg7fXmI(T4-g1v)E!7 zwFKMiEVo6cz_LeVw)~J1G_6kd!77G(vsU{GEJX3Wq7@M<+hY2Cqdq^tW(}*CYZR)q zUZ>-ue9`MU>8I_ZyqZKkcDB3Cn#uknXYwL|%d?L(!KZin`8@VHkp2z2IyNlM=P{h0 zo0Ql{#TPBm-P;My=VcSI`qAm8wN+JfsWK&~_ShtlyB9xVPoH&GUOZVVuN`pZL=@xR zE>Kl7+GGF+PO56AekW;WS*`y$DJg?GddR>u6boqQN_)&2hbWRiqOB8OgP zz3;@8As22AH-Q=+UTY28nRPN4^eJgi?BXS zvswQVIT1ZW4A|B88PE{CSFjWBXFUmWnPd~_MI`3q0lz$QZWfb*3|p7m|DcZqVCXwe z%d<9#Hi{g~5d7)q>SLJR)b%;-KOa*hgyE2$df~8ZTSt`mdW93;Y(9f;%}Yu`OFIks10!e{IPDC;1vbFw{PA%^1xd!k8z9kHNNUn52<` zwQRHcr;fyzhcJI0@2^r~N906W0+YNd$#&IzdLR1pM~_>y&)K6q*&$E>xtg1`{Ns2* zYkfW~=WP|D1vIr?^!pgoSo>cS-U+%lzH1qNu>C38)76&sp7X|B|NW1b82P4c>KY3* z^u9)`StzT|D-=TR3~VusP8XokXFM7xiJ(rX)M9Vh+a^ z;3t{AlB=81FK^flVRxdv_ysk%tRsnGUTI9$@%m5MZoSXzi}SC&u(Z}4bG@C%t0m*4 zOppFk+pL!7&7#KHMr^F*diC&^aZ7uYIh4)JsjtE$IvZax*&OV*sF6dRQ%@qAuPc&6 z2M=VNtW@znGFT!0XbX3EE|Zd66Hm$1je6n-8k{9#^IJr3?J~FvU$#EE6WAjDw(s~@ zta1_a@v&(TM#Kin`BZ7wV&eAs*nmV8*8peKVP!xbLe>jIn>x&JpoL(XF~ri6;}#)s z6*mlRa=jtS@EdEBy=#4r#Z3hyfxZTxcT8|PQlFnfzBlrQX7>NDC%Os|t;bY^Ndla9Py=uT_1r%d<*KC#MJB-Ia`xlNRuXoU@K zie6vAfz&0Xu0+MxK&yb$0ttg7F;_fuZS?K(u>8bai66`{o6y#!{^pIH*F<}=-I)m1 z!6%-?y(nh(ji;3J(lxEdEF-h(-*deQ8RHyFD1BTZUnF#oFp`BKmaD`|Yxyxp=oh|3 z&ft|wk>n7wHHXiwq0wYy?gQMkhR0flIZm+>jI2|L51ML4^j(`TN|MwRN;QurL7(<6B8&%Ev$y9DhI#I}(|Mm?FY@Sk3lKbyf6lajvFY~n z!I8PLc<npG@JmS?;E8anqN3tgeRT8%*Bk;T;XTmvHW33k%3~xs`qA_F yM~9*HL|g=N8%Y_T94m{gT+P`6z$}vXMS9-BmL!ZbxuT*1IET}9Q@vXP@&5qU2c($* literal 0 HcmV?d00001 diff --git a/Resources/Audio/Effects/trashbag3.ogg b/Resources/Audio/Effects/trashbag3.ogg new file mode 100644 index 0000000000000000000000000000000000000000..08719273b51030778a32411e1fae71afe923d933 GIT binary patch literal 16399 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 literal 0 HcmV?d00001 diff --git a/Resources/Prototypes/Entities/Effects/Markers/clientsideclone.yml b/Resources/Prototypes/Entities/Effects/Markers/clientsideclone.yml new file mode 100644 index 0000000000..a3c662c28a --- /dev/null +++ b/Resources/Prototypes/Entities/Effects/Markers/clientsideclone.yml @@ -0,0 +1,8 @@ +- type: entity + name: clientsideclone + id: clientsideclone + abstract: true + components: + - type: Sprite + - type: Physics + - type: AnimationPlayer diff --git a/Resources/Prototypes/Entities/Objects/Consumable/trash.yml b/Resources/Prototypes/Entities/Objects/Consumable/trash.yml index 71490e6b8b..deabc3b5df 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/trash.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/trash.yml @@ -152,18 +152,7 @@ components: - type: Sprite sprite: Objects/Consumable/Trash/tastybread.rsi - - -- type: entity - name: trash bag - parent: TrashBase - id: TrashBag - components: - - type: Sprite - sprite: Objects/Consumable/Trash/trashbag.rsi - - - type: Storage - capacity: 125 + - type: entity name: tray (trash) diff --git a/Resources/Prototypes/Entities/Objects/Specific/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/janitor.yml index a21de46789..2aea0da9b9 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/janitor.yml @@ -234,3 +234,18 @@ reagents: - ReagentId: chem.SpaceCleaner Quantity: 100 + + +- type: entity + name: trash bag + id: TrashBag + parent: BaseItem + components: + - type: Sprite + sprite: Objects/Specific/Janitorial/trashbag.rsi + state: icon + - type: Storage + capacity: 125 + quickInsert: true + areaInsert: true + storageSoundCollection: trashBagRustle diff --git a/Resources/Prototypes/SoundCollections/storage_rustle.yml b/Resources/Prototypes/SoundCollections/storage_rustle.yml index d7f6c09586..3fbfc80535 100644 --- a/Resources/Prototypes/SoundCollections/storage_rustle.yml +++ b/Resources/Prototypes/SoundCollections/storage_rustle.yml @@ -6,3 +6,10 @@ - /Audio/Effects/rustle3.ogg - /Audio/Effects/rustle4.ogg - /Audio/Effects/rustle5.ogg + +- type: soundCollection + id: trashBagRustle + files: + - /Audio/Effects/trashbag1.ogg + - /Audio/Effects/trashbag2.ogg + - /Audio/Effects/trashbag3.ogg \ No newline at end of file diff --git a/Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/icon-0.png b/Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/icon-0.png similarity index 100% rename from Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/icon-0.png rename to Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/icon-0.png diff --git a/Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/icon-1.png b/Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/icon-1.png similarity index 100% rename from Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/icon-1.png rename to Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/icon-1.png diff --git a/Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/icon-2.png b/Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/icon-2.png similarity index 100% rename from Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/icon-2.png rename to Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/icon-2.png diff --git a/Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/icon-3.png b/Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/icon-3.png similarity index 100% rename from Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/icon-3.png rename to Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/icon-3.png diff --git a/Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/icon.png b/Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/icon.png similarity index 100% rename from Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/icon.png rename to Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/icon.png diff --git a/Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/meta.json b/Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/meta.json similarity index 100% rename from Resources/Textures/Objects/Consumable/Trash/trashbag.rsi/meta.json rename to Resources/Textures/Objects/Specific/Janitorial/trashbag.rsi/meta.json From a4563d2e753d85994503c377a390921bbd469101 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 4 Feb 2021 01:24:37 +0100 Subject: [PATCH 53/61] Fix uplink item spawning. Actually spawns for the person who USES the uplink, not who owns it. Can't believe that was an actual bug. Also puts it in your active hand if possible. --- .../Components/GUI/HandsComponent.cs | 21 ++++++++++++++++++- .../Components/PDA/PDAComponent.cs | 10 ++++++++- .../Interfaces/PDA/IPDAUplinkManager.cs | 12 +++++++++-- Content.Server/PDA/PDAUplinkManager.cs | 20 ++++++++---------- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs index 0139761d21..2c7aad758e 100644 --- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -198,14 +198,33 @@ namespace Content.Server.GameObjects.Components.GUI return success; } + /// + /// Drops the item if doesn't have hands. + /// + public static void PutInHandOrDropStatic(IEntity mob, ItemComponent item, bool mobCheck = true) + { + if (!mob.TryGetComponent(out HandsComponent? hands)) + { + DropAtFeet(mob, item); + return; + } + + hands.PutInHandOrDrop(item, mobCheck); + } + public void PutInHandOrDrop(ItemComponent item, bool mobCheck = true) { if (!PutInHand(item, mobCheck)) { - item.Owner.Transform.Coordinates = Owner.Transform.Coordinates; + DropAtFeet(Owner, item); } } + private static void DropAtFeet(IEntity mob, ItemComponent item) + { + item.Owner.Transform.Coordinates = mob.Transform.Coordinates; + } + public bool CanPutInHand(ItemComponent item, bool mobCheck = true) { if (mobCheck && !ActionBlockerSystem.CanPickup(Owner)) diff --git a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs index efd7c1da4b..cf65690de8 100644 --- a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs +++ b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs @@ -135,12 +135,20 @@ namespace Content.Server.GameObjects.Components.PDA case PDAUplinkBuyListingMessage buyMsg: { - if (!_uplinkManager.TryPurchaseItem(_syndicateUplinkAccount, buyMsg.ItemId)) + if (message.Session.AttachedEntity == null) + break; + + if (!_uplinkManager.TryPurchaseItem(_syndicateUplinkAccount, buyMsg.ItemId, + message.Session.AttachedEntity.Transform.Coordinates, out var entity)) { SendNetworkMessage(new PDAUplinkInsufficientFundsMessage(), message.Session.ConnectedClient); break; } + HandsComponent.PutInHandOrDropStatic( + message.Session.AttachedEntity, + entity.GetComponent()); + SendNetworkMessage(new PDAUplinkBuySuccessMessage(), message.Session.ConnectedClient); break; } diff --git a/Content.Server/Interfaces/PDA/IPDAUplinkManager.cs b/Content.Server/Interfaces/PDA/IPDAUplinkManager.cs index f58fc5d755..0b9f78dba3 100644 --- a/Content.Server/Interfaces/PDA/IPDAUplinkManager.cs +++ b/Content.Server/Interfaces/PDA/IPDAUplinkManager.cs @@ -1,11 +1,15 @@ +#nullable enable using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Content.Shared.GameObjects.Components.PDA; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Map; namespace Content.Server.Interfaces.PDA { public interface IPDAUplinkManager { - public IReadOnlyDictionary FetchListings => null; + public IReadOnlyDictionary FetchListings { get; } void Initialize(); @@ -13,7 +17,11 @@ namespace Content.Server.Interfaces.PDA public bool ChangeBalance(UplinkAccount acc, int amt); - public bool TryPurchaseItem(UplinkAccount acc, string itemId); + public bool TryPurchaseItem( + UplinkAccount? acc, + string itemId, + EntityCoordinates spawnCoords, + [NotNullWhen(true)] out IEntity? purchasedItem); } } diff --git a/Content.Server/PDA/PDAUplinkManager.cs b/Content.Server/PDA/PDAUplinkManager.cs index 2cdae4e8ca..b4d96e6980 100644 --- a/Content.Server/PDA/PDAUplinkManager.cs +++ b/Content.Server/PDA/PDAUplinkManager.cs @@ -1,4 +1,6 @@ +#nullable enable using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; @@ -8,6 +10,7 @@ using Content.Shared.GameObjects.Components.PDA; using Content.Shared.Prototypes.PDA; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Map; using Robust.Shared.Prototypes; namespace Content.Server.PDA @@ -17,14 +20,13 @@ namespace Content.Server.PDA [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; - private List _accounts; - private Dictionary _listings; + private readonly List _accounts = new(); + private readonly Dictionary _listings = new(); public IReadOnlyDictionary FetchListings => _listings; public void Initialize() { - _listings = new Dictionary(); foreach (var item in _prototypeManager.EnumeratePrototypes()) { var newListing = new UplinkListingData(item.ListingName, item.ItemId, item.Price, item.Category, @@ -32,8 +34,6 @@ namespace Content.Server.PDA RegisterUplinkListing(newListing); } - - _accounts = new List(); } private void RegisterUplinkListing(UplinkListingData listing) @@ -53,7 +53,7 @@ namespace Content.Server.PDA { var entity = _entityManager.GetEntity(acc.AccountHolder); - if (entity.TryGetComponent(out MindComponent mindComponent) && !mindComponent.HasMind) + if (entity.TryGetComponent(out MindComponent? mindComponent) && !mindComponent.HasMind) { return false; } @@ -86,8 +86,9 @@ namespace Content.Server.PDA return true; } - public bool TryPurchaseItem(UplinkAccount acc, string itemId) + public bool TryPurchaseItem(UplinkAccount? acc, string itemId, EntityCoordinates spawnCoords, [NotNullWhen(true)] out IEntity? purchasedItem) { + purchasedItem = null; if (acc == null) { return false; @@ -108,10 +109,7 @@ namespace Content.Server.PDA return false; } - var player = _entityManager.GetEntity(acc.AccountHolder); - var hands = player.GetComponent(); - hands.PutInHandOrDrop(_entityManager.SpawnEntity(listing.ItemId, - player.Transform.Coordinates).GetComponent()); + purchasedItem = _entityManager.SpawnEntity(listing.ItemId, spawnCoords); return true; } } From 29918b2810f39976d13a1b321da0954ddecc25a6 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 4 Feb 2021 04:16:36 +0100 Subject: [PATCH 54/61] Fix suspicion round end timer, actually show it. --- .../EntitySystems/SuspicionEndTimerSystem.cs | 23 ++++++ .../UserInterface/Suspicion/SuspicionGui.xaml | 8 ++ .../{SuspicionGui.cs => SuspicionGui.xaml.cs} | 61 ++++++++------ .../Suspicion/SuspicionRoleComponent.cs | 2 +- .../GameMode/SuspicionEndTimerSystem.cs | 82 +++++++++++++++++++ .../{ => GameMode}/SuspicionRoleSystem.cs | 4 +- .../GameTicking/GameRules/RuleSuspicion.cs | 46 ++++------- .../EntitySystemMessages/SuspicionMessages.cs | 15 ++++ 8 files changed, 180 insertions(+), 61 deletions(-) create mode 100644 Content.Client/GameObjects/EntitySystems/SuspicionEndTimerSystem.cs create mode 100644 Content.Client/UserInterface/Suspicion/SuspicionGui.xaml rename Content.Client/UserInterface/Suspicion/{SuspicionGui.cs => SuspicionGui.xaml.cs} (61%) create mode 100644 Content.Server/GameObjects/EntitySystems/GameMode/SuspicionEndTimerSystem.cs rename Content.Server/GameObjects/EntitySystems/{ => GameMode}/SuspicionRoleSystem.cs (92%) create mode 100644 Content.Shared/GameObjects/EntitySystemMessages/SuspicionMessages.cs diff --git a/Content.Client/GameObjects/EntitySystems/SuspicionEndTimerSystem.cs b/Content.Client/GameObjects/EntitySystems/SuspicionEndTimerSystem.cs new file mode 100644 index 0000000000..0186bf4a01 --- /dev/null +++ b/Content.Client/GameObjects/EntitySystems/SuspicionEndTimerSystem.cs @@ -0,0 +1,23 @@ +using System; +using Content.Shared.GameObjects.EntitySystemMessages; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Client.GameObjects.EntitySystems +{ + public sealed class SuspicionEndTimerSystem : EntitySystem + { + public TimeSpan? EndTime { get; private set; } + + public override void Initialize() + { + base.Initialize(); + + SubscribeNetworkEvent(RxTimerMessage); + } + + private void RxTimerMessage(SuspicionMessages.SetSuspicionEndTimerMessage ev) + { + EndTime = ev.EndTime; + } + } +} diff --git a/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml b/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml new file mode 100644 index 0000000000..fb22a6d1fa --- /dev/null +++ b/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/Content.Client/UserInterface/Suspicion/SuspicionGui.cs b/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml.cs similarity index 61% rename from Content.Client/UserInterface/Suspicion/SuspicionGui.cs rename to Content.Client/UserInterface/Suspicion/SuspicionGui.xaml.cs index d2213379fe..a9694fbe20 100644 --- a/Content.Client/UserInterface/Suspicion/SuspicionGui.cs +++ b/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml.cs @@ -1,48 +1,41 @@ -using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using Content.Client.GameObjects.Components.Suspicion; +using Content.Client.GameObjects.EntitySystems; using Content.Shared.Interfaces; +using Robust.Client.AutoGenerated; using Robust.Client.Player; using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Shared.Interfaces.GameObjects; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Timing; using static Robust.Client.UserInterface.Controls.BaseButton; +#nullable enable + namespace Content.Client.UserInterface.Suspicion { - public class SuspicionGui : Control + [GenerateTypedNameReferences] + public partial class SuspicionGui : Control { [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; - private readonly VBoxContainer _container; - private readonly Button _roleButton; - - private string _previousRoleName; + private string? _previousRoleName; private bool _previousAntagonist; public SuspicionGui() { + RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); - AddChild(_container = new VBoxContainer - { - SeparationOverride = 0, - Children = - { - (_roleButton = new Button - { - Name = "Suspicion Role Button" - }) - } - }); - - _roleButton.CustomMinimumSize = (200, 60); - _roleButton.OnPressed += RoleButtonPressed; + RoleButton.OnPressed += RoleButtonPressed; + RoleButton.CustomMinimumSize = (200, 60); } private void RoleButtonPressed(ButtonEventArgs obj) @@ -67,11 +60,15 @@ namespace Content.Client.UserInterface.Suspicion role.Owner.PopupMessage(message); } - private bool TryGetComponent(out SuspicionRoleComponent suspicion) + private bool TryGetComponent([NotNullWhen(true)] out SuspicionRoleComponent? suspicion) { suspicion = default; + if (_playerManager.LocalPlayer?.ControlledEntity == null) + { + return false; + } - return _playerManager?.LocalPlayer?.ControlledEntity?.TryGetComponent(out suspicion) == true; + return _playerManager.LocalPlayer.ControlledEntity.TryGetComponent(out suspicion); } public void UpdateLabel() @@ -88,6 +85,18 @@ namespace Content.Client.UserInterface.Suspicion return; } + var endTime = EntitySystem.Get().EndTime; + if (endTime == null) + { + TimerLabel.Visible = false; + } + else + { + var diff = _timing.CurTime - endTime.Value; + TimerLabel.Visible = true; + TimerLabel.Text = $"{diff:mm\\:ss}"; + } + if (_previousRoleName == suspicion.Role && _previousAntagonist == suspicion.Antagonist) { return; @@ -99,8 +108,8 @@ namespace Content.Client.UserInterface.Suspicion var buttonText = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(_previousRoleName); buttonText = Loc.GetString(buttonText); - _roleButton.Text = buttonText; - _roleButton.ModulateSelfOverride = _previousAntagonist ? Color.Red : Color.Green; + RoleButton.Text = buttonText; + RoleButton.ModulateSelfOverride = _previousAntagonist ? Color.Red : Color.Green; Visible = true; } diff --git a/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs index 641080e643..c436c06b12 100644 --- a/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs +++ b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Mobs; -using Content.Server.GameObjects.EntitySystems; +using Content.Server.GameObjects.EntitySystems.GameMode; using Content.Server.Mobs; using Content.Server.Mobs.Roles; using Content.Server.Mobs.Roles.Suspicion; diff --git a/Content.Server/GameObjects/EntitySystems/GameMode/SuspicionEndTimerSystem.cs b/Content.Server/GameObjects/EntitySystems/GameMode/SuspicionEndTimerSystem.cs new file mode 100644 index 0000000000..9e46b5c3a3 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/GameMode/SuspicionEndTimerSystem.cs @@ -0,0 +1,82 @@ +using System; +using System.Linq; +using Content.Server.Interfaces.GameTicking; +using Content.Shared.GameObjects.EntitySystemMessages; +using Content.Shared.GameTicking; +using JetBrains.Annotations; +using Robust.Server.Interfaces.Player; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; + +#nullable enable + +namespace Content.Server.GameObjects.EntitySystems.GameMode +{ + [UsedImplicitly] + public sealed class SuspicionEndTimerSystem : EntitySystem, IResettingEntitySystem + { + [Dependency] private readonly IPlayerManager _playerManager = null!; + [Dependency] private readonly IGameTiming _timing = null!; + [Dependency] private readonly IGameTicker _gameTicker = null!; + + private TimeSpan? _endTime; + + public TimeSpan? EndTime + { + get => _endTime; + set + { + _endTime = value; + SendUpdateToAll(); + } + } + + public override void Initialize() + { + base.Initialize(); + + _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged; + } + + public override void Shutdown() + { + base.Shutdown(); + + _playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged; + } + + private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + if (e.NewStatus == SessionStatus.InGame) + { + SendUpdateTimerMessage(e.Session); + } + } + + private void SendUpdateToAll() + { + foreach (var player in _playerManager.GetAllPlayers().Where(p => p.Status == SessionStatus.InGame)) + { + SendUpdateTimerMessage(player); + } + } + + private void SendUpdateTimerMessage(IPlayerSession player) + { + var msg = new SuspicionMessages.SetSuspicionEndTimerMessage + { + EndTime = EndTime + }; + + EntityNetworkManager.SendSystemNetworkMessage(msg, player.ConnectedClient); + } + + void IResettingEntitySystem.Reset() + { + EndTime = null; + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs b/Content.Server/GameObjects/EntitySystems/GameMode/SuspicionRoleSystem.cs similarity index 92% rename from Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs rename to Content.Server/GameObjects/EntitySystems/GameMode/SuspicionRoleSystem.cs index 7bfb191404..e805bab473 100644 --- a/Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/GameMode/SuspicionRoleSystem.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Content.Server.GameObjects.Components.Suspicion; using Content.Shared.GameTicking; using JetBrains.Annotations; using Robust.Shared.GameObjects.Systems; -namespace Content.Server.GameObjects.EntitySystems +namespace Content.Server.GameObjects.EntitySystems.GameMode { [UsedImplicitly] public class SuspicionRoleSystem : EntitySystem, IResettingEntitySystem diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs index 610748cd5e..f1d66028b7 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -2,6 +2,7 @@ using System.Threading; using Content.Server.GameObjects.Components.Suspicion; using Content.Server.GameObjects.EntitySystems; +using Content.Server.GameObjects.EntitySystems.GameMode; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; using Content.Server.Mobs.Roles.Suspicion; @@ -14,6 +15,7 @@ using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Localization; using Timer = Robust.Shared.Timers.Timer; @@ -31,9 +33,10 @@ namespace Content.Server.GameTicking.GameRules [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IGameTicker _gameTicker = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IGameTiming _timing = default!; private readonly CancellationTokenSource _checkTimerCancel = new(); - private CancellationTokenSource _maxTimerCancel = new(); + private TimeSpan _endTime; public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromSeconds(CCVars.SuspicionMaxTimeSeconds.DefaultValue); public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10); @@ -42,63 +45,37 @@ namespace Content.Server.GameTicking.GameRules { RoundMaxTime = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.SuspicionMaxTimeSeconds)); + _endTime = _timing.CurTime + RoundMaxTime; + _chatManager.DispatchServerAnnouncement(Loc.GetString("There are traitors on the station! Find them, and kill them!")); bool Predicate(IPlayerSession session) => session.ContentData()?.Mind?.HasRole() ?? false; EntitySystem.Get().PlayGlobal("/Audio/Misc/tatoralert.ogg", AudioParams.Default, Predicate); + EntitySystem.Get().EndTime = _endTime; EntitySystem.Get().AccessType = DoorSystem.AccessTypes.AllowAllNoExternal; Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token); - - _gameTicker.OnRunLevelChanged += RunLevelChanged; } public override void Removed() { base.Removed(); - _gameTicker.OnRunLevelChanged -= RunLevelChanged; - EntitySystem.Get().AccessType = DoorSystem.AccessTypes.Id; + EntitySystem.Get().EndTime = null; _checkTimerCancel.Cancel(); } - public void RestartTimer() - { - _maxTimerCancel.Cancel(); - _maxTimerCancel = new CancellationTokenSource(); - Timer.Spawn(RoundMaxTime, TimerFired, _maxTimerCancel.Token); - } - - public void StopTimer() - { - _maxTimerCancel.Cancel(); - } - - private void TimerFired() + private void Timeout() { _chatManager.DispatchServerAnnouncement(Loc.GetString("Time has run out for the traitors!")); EndRound(Victory.Innocents); } - private void RunLevelChanged(GameRunLevelChangedEventArgs args) - { - switch (args.NewRunLevel) - { - case GameRunLevel.InRound: - RestartTimer(); - break; - case GameRunLevel.PreRoundLobby: - case GameRunLevel.PostRound: - StopTimer(); - break; - } - } - private void CheckWinConditions() { if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin)) @@ -145,6 +122,11 @@ namespace Content.Server.GameTicking.GameRules _chatManager.DispatchServerAnnouncement(Loc.GetString("The innocents are dead! The traitors win.")); EndRound(Victory.Traitors); } + else if (_timing.CurTime > _endTime) + { + _chatManager.DispatchServerAnnouncement(Loc.GetString("Time has run out for the traitors!")); + EndRound(Victory.Innocents); + } } private enum Victory diff --git a/Content.Shared/GameObjects/EntitySystemMessages/SuspicionMessages.cs b/Content.Shared/GameObjects/EntitySystemMessages/SuspicionMessages.cs new file mode 100644 index 0000000000..c540b0a4a5 --- /dev/null +++ b/Content.Shared/GameObjects/EntitySystemMessages/SuspicionMessages.cs @@ -0,0 +1,15 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.EntitySystemMessages +{ + public static class SuspicionMessages + { + [Serializable, NetSerializable] + public sealed class SetSuspicionEndTimerMessage : EntitySystemMessage + { + public TimeSpan? EndTime; + } + } +} From b9aa789bc4a1fecccd8489c6c69608dbe44437d3 Mon Sep 17 00:00:00 2001 From: 20kdc Date: Thu, 4 Feb 2021 09:47:04 +0000 Subject: [PATCH 55/61] Pressure & volume pumps can be toggled on/off now. (#3046) This tidbit might or might not be suitable for a separate component. Tell me in PR comments I guess. --- .../Components/Atmos/Piping/Pumps/BasePumpComponent.cs | 8 +++++++- .../Atmos/Piping/Pumps/PressurePumpComponent.cs | 2 ++ .../Components/Atmos/Piping/Pumps/VolumePumpComponent.cs | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs index 4b7eb31895..2cf6ed46ff 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs @@ -4,6 +4,7 @@ using Content.Server.Atmos; using Content.Server.GameObjects.Components.NodeContainer; using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Content.Shared.GameObjects.Components.Atmos; +using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; @@ -16,7 +17,7 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps /// /// Transfer gas from one to another. /// - public abstract class BasePumpComponent : Component + public abstract class BasePumpComponent : Component, IActivate { /// /// If the pump is currently pumping. @@ -100,6 +101,11 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps _appearance?.SetData(PumpVisuals.VisualState, new PumpVisualState(_initialInletDirection, _initialOutletDirection, PumpEnabled)); } + public void Activate(ActivateEventArgs eventArgs) + { + PumpEnabled = !PumpEnabled; + } + private void SetPipes() { _inletPipe = null; diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/PressurePumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/PressurePumpComponent.cs index 3ccff576bb..4135ef4389 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/PressurePumpComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/PressurePumpComponent.cs @@ -1,6 +1,7 @@ #nullable enable using System; using Content.Server.Atmos; +using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -9,6 +10,7 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps { [RegisterComponent] [ComponentReference(typeof(BasePumpComponent))] + [ComponentReference(typeof(IActivate))] public class PressurePumpComponent : BasePumpComponent { public override string Name => "PressurePump"; diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/VolumePumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/VolumePumpComponent.cs index d9c8c27a28..51824e1738 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/VolumePumpComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/VolumePumpComponent.cs @@ -1,6 +1,7 @@ #nullable enable using System; using Content.Server.Atmos; +using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -9,6 +10,7 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps { [RegisterComponent] [ComponentReference(typeof(BasePumpComponent))] + [ComponentReference(typeof(IActivate))] public class VolumePumpComponent : BasePumpComponent { [ViewVariables(VVAccess.ReadWrite)] From 82a97857acfa423cb2696299ee2d193e04da7f82 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Thu, 4 Feb 2021 11:04:19 +0100 Subject: [PATCH 56/61] Move TagComponent from server to shared (#3076) * Move TagComponent to shared * Fix test * Not a web edited commit No sir * Update Content.Shared/GameObjects/Components/Tag/TagComponentState.cs Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- Content.Client/IgnoredComponents.cs | 1 - Content.IntegrationTests/Tests/Tag/TagTest.cs | 7 +- .../Components/Tag/TagComponent.cs | 111 +++++++++++++----- .../Components/Tag/TagComponentExtensions.cs | 2 +- .../Components/Tag/TagComponentState.cs | 15 +++ Content.Shared/GameObjects/ContentNetIDs.cs | 1 + 6 files changed, 100 insertions(+), 37 deletions(-) rename {Content.Server => Content.Shared}/GameObjects/Components/Tag/TagComponent.cs (76%) rename {Content.Server => Content.Shared}/GameObjects/Components/Tag/TagComponentExtensions.cs (99%) create mode 100644 Content.Shared/GameObjects/Components/Tag/TagComponentState.cs diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 0ad242868c..c176efa82d 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -228,7 +228,6 @@ namespace Content.Client "MachineFrame", "MachineBoard", "ChemicalAmmo", - "Tag", "BiologicalSurgeryData", "CargoTelepad", "TraitorDeathMatchRedemption", diff --git a/Content.IntegrationTests/Tests/Tag/TagTest.cs b/Content.IntegrationTests/Tests/Tag/TagTest.cs index 3159f38720..90b985fb91 100644 --- a/Content.IntegrationTests/Tests/Tag/TagTest.cs +++ b/Content.IntegrationTests/Tests/Tag/TagTest.cs @@ -1,7 +1,7 @@ #nullable enable using System.Collections.Generic; using System.Threading.Tasks; -using Content.Server.GameObjects.Components.Tag; +using Content.Shared.GameObjects.Components.Tag; using Content.Shared.Prototypes.Tag; using NUnit.Framework; using Robust.Shared.Interfaces.GameObjects; @@ -69,9 +69,8 @@ namespace Content.IntegrationTests.Tests.Tag { // Has one tag, the starting tag Assert.That(sTagComponent.Tags.Count, Is.EqualTo(1)); - - var startingTagPrototype = sPrototypeManager.Index(StartingTag); - Assert.That(sTagComponent.Tags, Contains.Item(startingTagPrototype)); + sPrototypeManager.Index(StartingTag); + Assert.That(sTagComponent.Tags, Contains.Item(StartingTag)); // Single Assert.True(sTagDummy.HasTag(StartingTag)); diff --git a/Content.Server/GameObjects/Components/Tag/TagComponent.cs b/Content.Shared/GameObjects/Components/Tag/TagComponent.cs similarity index 76% rename from Content.Server/GameObjects/Components/Tag/TagComponent.cs rename to Content.Shared/GameObjects/Components/Tag/TagComponent.cs index 4894b0c848..d01333b13d 100644 --- a/Content.Server/GameObjects/Components/Tag/TagComponent.cs +++ b/Content.Shared/GameObjects/Components/Tag/TagComponent.cs @@ -8,7 +8,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; -namespace Content.Server.GameObjects.Components.Tag +namespace Content.Shared.GameObjects.Components.Tag { [RegisterComponent] public class TagComponent : Component @@ -16,9 +16,9 @@ namespace Content.Server.GameObjects.Components.Tag public override string Name => "Tag"; [ViewVariables] - private readonly HashSet _tags = new(); + private readonly HashSet _tags = new(); - public IReadOnlySet Tags => _tags; + public IReadOnlySet Tags => _tags; public override void ExposeData(ObjectSerializer serializer) { @@ -38,17 +38,44 @@ namespace Content.Server.GameObjects.Components.Tag AddTags(ids); }, - () => - { - var ids = new HashSet(); + () => _tags); + } - foreach (var tag in _tags) - { - ids.Add(tag.ID); - } + public override ComponentState GetComponentState() + { + var tags = new string[_tags.Count]; + var i = 0; - return ids; - }); + foreach (var tag in _tags) + { + tags[i] = tag; + } + + return new TagComponentState(tags); + } + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + if (curState is not TagComponentState state) + { + return; + } + + _tags.Clear(); + + var prototypeManager = IoCManager.Resolve(); + + foreach (var tag in state.Tags) + { + GetTagOrThrow(tag, prototypeManager); + _tags.Add(tag); + } + } + + private TagPrototype GetTagOrThrow(string id, IPrototypeManager? manager = null) + { + manager ??= IoCManager.Resolve(); + return manager.Index(id); } /// @@ -61,9 +88,16 @@ namespace Content.Server.GameObjects.Components.Tag /// public bool AddTag(string id) { - var tag = IoCManager.Resolve().Index(id); + GetTagOrThrow(id); + var added = _tags.Add(id); - return _tags.Add(tag); + if (added) + { + Dirty(); + return true; + } + + return false; } /// @@ -94,12 +128,17 @@ namespace Content.Server.GameObjects.Components.Tag foreach (var id in ids) { - var tag = prototypeManager.Index(id); - - _tags.Add(tag); + GetTagOrThrow(id, prototypeManager); + _tags.Add(id); } - return _tags.Count > count; + if (_tags.Count > count) + { + Dirty(); + return true; + } + + return false; } /// @@ -112,9 +151,8 @@ namespace Content.Server.GameObjects.Components.Tag /// public bool HasTag(string id) { - var tag = IoCManager.Resolve().Index(id); - - return _tags.Contains(tag); + GetTagOrThrow(id); + return _tags.Contains(id); } /// @@ -144,9 +182,9 @@ namespace Content.Server.GameObjects.Components.Tag foreach (var id in ids) { - var tag = prototypeManager.Index(id); + GetTagOrThrow(id, prototypeManager); - if (!_tags.Contains(tag)) + if (!_tags.Contains(id)) { return false; } @@ -182,9 +220,9 @@ namespace Content.Server.GameObjects.Components.Tag foreach (var id in ids) { - var tag = prototypeManager.Index(id); + GetTagOrThrow(id, prototypeManager); - if (_tags.Contains(tag)) + if (_tags.Contains(id)) { return true; } @@ -205,9 +243,15 @@ namespace Content.Server.GameObjects.Components.Tag /// public bool RemoveTag(string id) { - var tag = IoCManager.Resolve().Index(id); + GetTagOrThrow(id); - return _tags.Remove(tag); + if (_tags.Remove(id)) + { + Dirty(); + return true; + } + + return false; } /// @@ -240,12 +284,17 @@ namespace Content.Server.GameObjects.Components.Tag foreach (var id in ids) { - var tag = prototypeManager.Index(id); - - _tags.Remove(tag); + GetTagOrThrow(id, prototypeManager); + _tags.Remove(id); } - return _tags.Count < count; + if (_tags.Count < count) + { + Dirty(); + return true; + } + + return false; } } } diff --git a/Content.Server/GameObjects/Components/Tag/TagComponentExtensions.cs b/Content.Shared/GameObjects/Components/Tag/TagComponentExtensions.cs similarity index 99% rename from Content.Server/GameObjects/Components/Tag/TagComponentExtensions.cs rename to Content.Shared/GameObjects/Components/Tag/TagComponentExtensions.cs index 437b70af88..9fbbb4fa45 100644 --- a/Content.Server/GameObjects/Components/Tag/TagComponentExtensions.cs +++ b/Content.Shared/GameObjects/Components/Tag/TagComponentExtensions.cs @@ -5,7 +5,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Prototypes; -namespace Content.Server.GameObjects.Components.Tag +namespace Content.Shared.GameObjects.Components.Tag { public static class TagComponentExtensions { diff --git a/Content.Shared/GameObjects/Components/Tag/TagComponentState.cs b/Content.Shared/GameObjects/Components/Tag/TagComponentState.cs new file mode 100644 index 0000000000..6c29189c72 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Tag/TagComponentState.cs @@ -0,0 +1,15 @@ +#nullable enable +using Robust.Shared.GameObjects; + +namespace Content.Shared.GameObjects.Components.Tag +{ + public class TagComponentState : ComponentState + { + public TagComponentState(string[] tags) : base(ContentNetIDs.TAG) + { + Tags = tags; + } + + public string[] Tags { get; } + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 4ce0dfa358..3c3388662c 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -90,6 +90,7 @@ public const uint ACTIONS = 1083; public const uint DAMAGEABLE = 1084; public const uint MAGBOOTS = 1085; + public const uint TAG = 1086; // Net IDs for integration tests. public const uint PREDICTION_TEST = 10001; From 735c6159d8b901f3d60d905e7fa672a7a3d035cf Mon Sep 17 00:00:00 2001 From: tmtmtl30 <53132901+tmtmtl30@users.noreply.github.com> Date: Thu, 4 Feb 2021 03:13:13 -0800 Subject: [PATCH 57/61] Removes SmallImpassable from melee swing collision mask (#3036) * Alters melee swing collision mask. * Part one... * Part two. --- .../Components/Weapon/Melee/MeleeWeaponComponent.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs index 6fd87d0236..ccc1f99b0c 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs @@ -6,6 +6,7 @@ using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Items; using Content.Shared.Interfaces.GameObjects.Components; +using Content.Shared.Physics; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; @@ -203,7 +204,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee for (var i = 0; i < increments; i++) { var castAngle = new Angle(baseAngle + increment * i); - var res = _physicsManager.IntersectRay(mapId, new CollisionRay(position, castAngle.ToVec(), 23), Range, ignore).FirstOrDefault(); + var res = _physicsManager.IntersectRay(mapId, new CollisionRay(position, castAngle.ToVec(), (int) (CollisionGroup.Impassable|CollisionGroup.MobImpassable)), Range, ignore).FirstOrDefault(); if (res.HitEntity != null) { resSet.Add(res.HitEntity); From b2c1ab27956031a6631d94120f292da1b8e33131 Mon Sep 17 00:00:00 2001 From: Metal Gear Sloth Date: Thu, 4 Feb 2021 23:35:18 +1100 Subject: [PATCH 58/61] Update submodule --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 3eb6e067f9..18fcab6f71 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 3eb6e067f9dc0155cf2490fe4adeb6e03e42412c +Subproject commit 18fcab6f710947b640b839c458b52193fad2acf7 From 655316a67c50b62ca4d9e1fdf1ac52d8c2b6a396 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> Date: Thu, 4 Feb 2021 13:37:26 +0100 Subject: [PATCH 59/61] Add playback slider to instruments. (#3071) --- .../Instruments/InstrumentComponent.cs | 52 +++++++++++++------ .../Instruments/InstrumentMenu.xaml | 9 +++- .../Instruments/InstrumentMenu.xaml.cs | 44 +++++++++++++++- .../Instruments/InstrumentComponent.cs | 40 +++----------- 4 files changed, 90 insertions(+), 55 deletions(-) diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index 85d6119014..e24e60cf4e 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; using Content.Client.GameObjects.EntitySystems; -using Content.Shared; using Content.Shared.GameObjects.Components.Instruments; using Content.Shared.Physics; using Robust.Client.Audio.Midi; @@ -11,13 +10,11 @@ using Robust.Shared.Audio.Midi; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.Timers; using Robust.Shared.GameObjects.Systems; -using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Players; using Robust.Shared.Serialization; -using Robust.Shared.Timers; using Robust.Shared.ViewVariables; namespace Content.Client.GameObjects.Components.Instruments @@ -162,6 +159,41 @@ namespace Content.Client.GameObjects.Components.Instruments [ViewVariables] public bool IsRendererAlive => _renderer != null; + [ViewVariables] + public int PlayerTotalTick => _renderer?.PlayerTotalTick ?? 0; + + [ViewVariables] + public int PlayerTick + { + get => _renderer?.PlayerTick ?? 0; + set + { + if (!IsRendererAlive || _renderer!.Status != MidiRendererStatus.File) return; + + _midiEventBuffer.Clear(); + + _renderer.PlayerTick = value; + var tick = _renderer.SequencerTick; + + // We add a "all notes off" message. + for (byte i = 0; i < 16; i++) + { + _midiEventBuffer.Add(new MidiEvent() + { + Tick = tick, Type = 176, + Control = 123, Velocity = 0, Channel = i, + }); + } + + // Now we add a Reset All Controllers message. + _midiEventBuffer.Add(new MidiEvent() + { + Tick = tick, Type = 176, + Control = 121, Value = 0, + }); + } + } + public override void Initialize() { base.Initialize(); @@ -390,20 +422,6 @@ namespace Content.Client.GameObjects.Components.Instruments /// The received midi event private void RendererOnMidiEvent(MidiEvent midiEvent) { - // avoid of out-of-band, unimportant or unsupported events - switch (midiEvent.Type) - { - case 0x80: // NOTE_OFF - case 0x90: // NOTE_ON - case 0xa0: // KEY_PRESSURE - case 0xb0: // CONTROL_CHANGE - case 0xd0: // CHANNEL_PRESSURE - case 0xe0: // PITCH_BEND - break; - default: - return; - } - _midiEventBuffer.Add(midiEvent); } diff --git a/Content.Client/Instruments/InstrumentMenu.xaml b/Content.Client/Instruments/InstrumentMenu.xaml index 0825557f22..23e4b75a9b 100644 --- a/Content.Client/Instruments/InstrumentMenu.xaml +++ b/Content.Client/Instruments/InstrumentMenu.xaml @@ -1,18 +1,23 @@ - + - + + + + + + diff --git a/Content.Client/Instruments/InstrumentMenu.xaml.cs b/Content.Client/Instruments/InstrumentMenu.xaml.cs index e9debae7e1..9ead93cc77 100644 --- a/Content.Client/Instruments/InstrumentMenu.xaml.cs +++ b/Content.Client/Instruments/InstrumentMenu.xaml.cs @@ -14,11 +14,14 @@ using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Containers; +using Robust.Shared.Input; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Maths; using Robust.Shared.Timers; +using Robust.Shared.Timing; +using Range = Robust.Client.UserInterface.Controls.Range; namespace Content.Client.Instruments { @@ -46,6 +49,7 @@ namespace Content.Client.Instruments LoopButton.Disabled = !_owner.Instrument.IsMidiOpen; LoopButton.Pressed = _owner.Instrument.LoopMidi; StopButton.Disabled = !_owner.Instrument.IsMidiOpen; + PlaybackSlider.MouseFilter = _owner.Instrument.IsMidiOpen ? MouseFilterMode.Pass : MouseFilterMode.Ignore; if (!_midiManager.IsAvailable) { @@ -74,6 +78,8 @@ namespace Content.Client.Instruments FileButton.OnPressed += MidiFileButtonOnOnPressed; LoopButton.OnToggled += MidiLoopButtonOnOnToggled; StopButton.OnPressed += MidiStopButtonOnPressed; + PlaybackSlider.OnValueChanged += PlaybackSliderSeek; + PlaybackSlider.OnKeyBindUp += PlaybackSliderKeyUp; } private void InstrumentOnMidiPlaybackEnded() @@ -85,12 +91,15 @@ namespace Content.Client.Instruments { LoopButton.Disabled = disabled; StopButton.Disabled = disabled; + + // Whether to allow the slider to receive events.. + PlaybackSlider.MouseFilter = !disabled ? MouseFilterMode.Pass : MouseFilterMode.Ignore; } private async void MidiFileButtonOnOnPressed(BaseButton.ButtonEventArgs obj) { var filters = new FileDialogFilters(new FileDialogFilters.Group("mid", "midi")); - var file = await _fileDialogManager.OpenFile(filters); + await using var file = await _fileDialogManager.OpenFile(filters); // The following checks are only in place to prevent players from playing MIDI songs locally. // There are equivalents for these checks on the server. @@ -107,7 +116,7 @@ namespace Content.Client.Instruments return; MidiStopButtonOnPressed(null); - var memStream = new MemoryStream((int) file.Length); + await using var memStream = new MemoryStream((int) file.Length); // 100ms delay is due to a race condition or something idk. // While we're waiting, load it into memory. await Task.WhenAll(Timer.Delay(100), file.CopyToAsync(memStream)); @@ -165,5 +174,36 @@ namespace Content.Client.Instruments { _owner.Instrument.LoopMidi = obj.Pressed; } + + private void PlaybackSliderSeek(Range _) + { + // Do not seek while still grabbing. + if (PlaybackSlider.Grabbed) return; + + _owner.Instrument.PlayerTick = (int)Math.Ceiling(PlaybackSlider.Value); + } + + private void PlaybackSliderKeyUp(GUIBoundKeyEventArgs args) + { + if (args.Function != EngineKeyFunctions.UIClick) return; + _owner.Instrument.PlayerTick = (int)Math.Ceiling(PlaybackSlider.Value); + } + + protected override void Update(FrameEventArgs args) + { + base.Update(args); + + if (!_owner.Instrument.IsMidiOpen) + { + PlaybackSlider.MaxValue = 1; + PlaybackSlider.SetValueWithoutEvent(0); + return; + } + + if (PlaybackSlider.Grabbed) return; + + PlaybackSlider.MaxValue = _owner.Instrument.PlayerTotalTick; + PlaybackSlider.SetValueWithoutEvent(_owner.Instrument.PlayerTick); + } } } diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index 233874b533..6651e20708 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -4,9 +4,7 @@ using System.Linq; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.EntitySystems; using Content.Server.Utility; -using Content.Shared; using Content.Shared.GameObjects.Components.Instruments; -using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; @@ -18,10 +16,7 @@ using Robust.Server.Player; using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; -using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Network; -using Robust.Shared.Interfaces.Timing; -using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Players; using Robust.Shared.Serialization; @@ -41,9 +36,6 @@ namespace Content.Server.GameObjects.Components.Instruments IUse, IThrown { - [Dependency] private readonly IGameTiming _gameTiming = default!; - - private static readonly TimeSpan OneSecAgo = TimeSpan.FromSeconds(-1); private InstrumentSystem _instrumentSystem = default!; /// @@ -60,9 +52,6 @@ namespace Content.Server.GameObjects.Components.Instruments [ViewVariables] private float _timer = 0f; - [ViewVariables(VVAccess.ReadOnly)] - private TimeSpan _lastMeasured = TimeSpan.MinValue; - [ViewVariables] private int _batchesDropped = 0; @@ -213,15 +202,6 @@ namespace Content.Server.GameObjects.Components.Instruments var minTick = midiEventMsg.MidiEvent.Min(x => x.Tick); if (_lastSequencerTick > minTick) { - var now = _gameTiming.RealTime; - var oneSecAGo = now.Add(OneSecAgo); - if (_lastMeasured < oneSecAGo) - { - _lastMeasured = now; - _laggedBatches = 0; - _batchesDropped = 0; - } - _laggedBatches++; if (_respectMidiLimits) @@ -246,15 +226,6 @@ namespace Content.Server.GameObjects.Components.Instruments if (++_midiEventCount > maxMidiEventsPerSecond || midiEventMsg.MidiEvent.Length > maxMidiEventsPerBatch) { - var now = _gameTiming.RealTime; - var oneSecAGo = now.Add(OneSecAgo); - if (_lastMeasured < oneSecAGo) - { - _lastMeasured = now; - _laggedBatches = 0; - _batchesDropped = 0; - } - _batchesDropped++; send = false; @@ -266,7 +237,7 @@ namespace Content.Server.GameObjects.Components.Instruments } var maxTick = midiEventMsg.MidiEvent.Max(x => x.Tick); - _lastSequencerTick = Math.Max(maxTick, minTick + 1); + _lastSequencerTick = Math.Max(maxTick, minTick); break; case InstrumentStartMidiMessage startMidi: if (session != _instrumentPlayer) @@ -386,15 +357,14 @@ namespace Content.Server.GameObjects.Components.Instruments UserInterface?.CloseAll(); + if(Handheld) + EntitySystem.Get().DropAllItemsInHands(mob, false); + if (mob != null && mob.TryGetComponent(out StunnableComponent? stun)) { stun.Stun(1); Clean(); } - else - { - EntitySystem.Get().DropAllItemsInHands(mob, false); - } InstrumentPlayer = null; @@ -406,6 +376,8 @@ namespace Content.Server.GameObjects.Components.Instruments _timer = 0f; _midiEventCount = 0; + _laggedBatches = 0; + _batchesDropped = 0; } } From 6b8cc9fc409464ae3b9de445113a8abc81c9504d Mon Sep 17 00:00:00 2001 From: Swept Date: Thu, 4 Feb 2021 12:44:27 +0000 Subject: [PATCH 60/61] Deprecates stationstation.yml (#2668) * Initial * Change default map load to saltern Co-authored-by: Metal Gear Sloth --- .../Tests/SaveLoadSaveTest.cs | 5 +- Resources/Maps/stationstation.yml | 5941 ----------------- 2 files changed, 3 insertions(+), 5943 deletions(-) delete mode 100644 Resources/Maps/stationstation.yml diff --git a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs index 23ab959a3e..9c1e3e11ab 100644 --- a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs @@ -77,7 +77,7 @@ namespace Content.IntegrationTests.Tests /// Loads the default map, runs it for 5 ticks, then assert that it did not change. /// [Test] - public async Task LoadSaveTicksSaveStationStation() + public async Task LoadSaveTicksSaveSaltern() { var server = StartServerDummyTicker(); await server.WaitIdleAsync(); @@ -92,7 +92,8 @@ namespace Content.IntegrationTests.Tests { var mapId = mapManager.CreateMap(); pauseMgr.AddUninitializedMap(mapId); - grid = mapLoader.LoadBlueprint(mapId, "Maps/stationstation.yml"); + pauseMgr.SetMapPaused(mapId, true); + grid = mapLoader.LoadBlueprint(mapId, "Maps/saltern.yml"); mapLoader.SaveBlueprint(grid.Index, "load save ticks save 1.yml"); }); diff --git a/Resources/Maps/stationstation.yml b/Resources/Maps/stationstation.yml deleted file mode 100644 index 9a52655710..0000000000 --- a/Resources/Maps/stationstation.yml +++ /dev/null @@ -1,5941 +0,0 @@ -meta: - format: 2 - name: DemoStation - author: Space-Wizards - postmapinit: false -tilemap: - 0: space - 1: floor_asteroid_coarse_sand0 - 2: floor_asteroid_coarse_sand1 - 3: floor_asteroid_coarse_sand2 - 4: floor_asteroid_coarse_sand_dug - 5: floor_asteroid_sand - 6: floor_asteroid_tile - 7: floor_dark - 8: floor_elevator_shaft - 9: floor_freezer - 10: floor_gold - 11: floor_green_circuit - 12: floor_hydro - 13: floor_lino - 14: floor_mono - 15: floor_reinforced - 16: floor_rock_vault - 17: floor_showroom - 18: floor_snow - 19: floor_steel - 20: floor_steel_dirty - 21: floor_techmaint - 22: floor_white - 23: floor_wood - 24: plating - 25: underplating -grids: -- settings: - chunksize: 16 - tilesize: 1 - snapsize: 1 - chunks: - - ind: "-1,0" - tiles: EwAAABMAAAATAAAAEwAAABMAAAAZAAAAGQAAABkAAAAZAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGQAAABUAAAAZAAAAGQAAABkAAAAZAAAAEwAAABMAAAATAAAAEwAAABkAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAZAAAAGQAAABkAAAAZAAAAGQAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAAAAAAEwAAABMAAAATAAAAAAAAAAAAAAAAAAAAAAAAABkAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAAAAAABMAAAATAAAAEwAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - - ind: "-1,-1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAZAAAAGQAAABUAAAAVAAAAFQAAABUAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAGQAAABkAAAAVAAAAFQAAABkAAAAVAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAZAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAGQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABMAAAATAAAAEwAAABMAAAATAAAAGQAAABkAAAAZAAAAGQAAABkAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABkAAAAVAAAAGQAAABkAAAAZAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABkAAAAZAAAAGQAAABkAAAAVAAAAGQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABMAAAATAAAAEwAAABMAAAATAAAAGQAAABUAAAAZAAAAGQAAABkAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAATAAAAEwAAABMAAAATAAAAEwAAABkAAAAVAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAEwAAABMAAAATAAAAEwAAABMAAAAZAAAAFQAAABkAAAAZAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGQAAABUAAAAZAAAAGQAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABkAAAAVAAAAGQAAABkAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAZAAAAFQAAABkAAAAZAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAAA== - - ind: "-1,1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - - ind: "0,1" - tiles: AAAAABkAAAATAAAAEwAAABkAAAAZAAAAGQAAABkAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAEwAAAAAAAAAZAAAAEwAAABMAAAAZAAAAFQAAABkAAAAZAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGQAAABYAAAAAAAAAGQAAABMAAAATAAAAGQAAABUAAAAZAAAAGQAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAZAAAAAAAAABkAAAATAAAAEwAAABkAAAAVAAAAGQAAABkAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAFgAAAAAAAAAZAAAAEwAAABMAAAAZAAAAFQAAABkAAAAVAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAAAAAAGQAAABMAAAATAAAAGQAAABUAAAAZAAAAGQAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAWAAAAAAAAABkAAAATAAAAEwAAABkAAAAVAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAFgAAAAAAAAAZAAAAEwAAABMAAAAZAAAAFQAAABkAAAAVAAAAFQAAABUAAAAZAAAAFQAAABUAAAAZAAAAGQAAABYAAAAAAAAAGQAAABMAAAATAAAAGQAAABUAAAAZAAAAFQAAABUAAAAVAAAAGQAAABUAAAAVAAAAGQAAABYAAAAWAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAVAAAAGQAAABUAAAAVAAAAFQAAABkAAAAVAAAAFQAAABkAAAAZAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAFQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABYAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABUAAAAZAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAWAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAVAAAAGQAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - - ind: "0,0" - tiles: EwAAABkAAAATAAAAEwAAABkAAAAVAAAAFQAAABUAAAAZAAAAAAAAAAAAAAAAAAAAGQAAABYAAAAWAAAAFgAAABkAAAAZAAAAEwAAABMAAAAZAAAAFQAAABUAAAAVAAAAGQAAAAAAAAAAAAAAAAAAABkAAAAWAAAAFgAAABYAAAATAAAAEwAAABMAAAATAAAAGQAAABkAAAAZAAAAGQAAABkAAAAAAAAAAAAAAAAAAAAZAAAAFgAAABYAAAAWAAAAEwAAABMAAAATAAAAEwAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABkAAAAZAAAAFgAAABMAAAATAAAAEwAAABMAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAFgAAABYAAAATAAAAEwAAABMAAAATAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABYAAAAWAAAAEwAAABMAAAATAAAAEwAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAWAAAAFgAAABMAAAATAAAAEwAAABMAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAFgAAABYAAAAZAAAAGQAAABMAAAATAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABYAAAAWAAAAGQAAABkAAAATAAAAEwAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAWAAAAFgAAABkAAAAVAAAAEwAAABMAAAAVAAAAGQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABkAAAAVAAAAFgAAABYAAAAZAAAAGQAAABMAAAATAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAATAAAAAAAAABkAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAAAAAAAAZAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAAAAAAGQAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAAAAAABkAAAATAAAAEwAAABkAAAAVAAAAGQAAABkAAAAZAAAAFgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAAA== - - ind: "0,-1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAAFQAAABUAAAAVAAAAGQAAABkAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAABkAAAAVAAAAFQAAABkAAAAZAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAAFQAAABUAAAAVAAAAGQAAABkAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAABUAAAAVAAAAFQAAABkAAAAZAAAAGQAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUAAAAVAAAAFQAAABUAAAAZAAAAFQAAABUAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAAFQAAABUAAAAVAAAAFQAAABkAAAAZAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAZAAAAGQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAFgAAABYAAAAZAAAAGQAAABUAAAAVAAAAGQAAABkAAAAZAAAAGQAAABkAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABYAAAAWAAAAEwAAABkAAAATAAAAEwAAABkAAAAVAAAAFQAAABUAAAAZAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAWAAAAFgAAABMAAAATAAAAEwAAABMAAAAZAAAAFQAAABUAAAAVAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAFgAAABYAAAATAAAAEwAAABMAAAATAAAAGQAAABUAAAAVAAAAFQAAABkAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABYAAAAWAAAAEwAAABMAAAATAAAAEwAAABkAAAAVAAAAFQAAABUAAAAZAAAAAAAAAAAAAAAAAAAAGQAAABkAAAAZAAAAFgAAAA== - - ind: "1,-1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGQAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGQAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABYAAAAWAAAAGQAAABkAAAAZAAAAFgAAABkAAAAZAAAAGQAAAA== - - ind: "-2,0" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - - ind: "-2,-1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEwAAAA== - - ind: "1,0" - tiles: FgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAAAAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGQAAAAAAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAAAAAAFgAAABkAAAAZAAAAGQAAABYAAAAZAAAAGQAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAAAAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAZAAAAFgAAABkAAAAWAAAAGQAAABkAAAAZAAAAGQAAAAAAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAAAAAAAAAAAAAAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGQAAABYAAAAWAAAAFgAAABYAAAAWAAAAGQAAAAAAAAAAAAAAAAAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAAAAAAAAAAAAAAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAAAAAAAAAAAAAAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGQAAABYAAAAWAAAAFgAAABYAAAAWAAAAGQAAAAAAAAAAAAAAAAAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAAAAAAAAAAAAAAAAATAAAAEwAAABMAAAATAAAAEwAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAAAAAAAAAAAAAAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEwAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAAAAAAAAAAAAAAAAAAA== - - ind: "2,-1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABkAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAWAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAFgAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAABYAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAWAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAFgAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABkAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - - ind: "1,1" - tiles: EwAAABMAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAWAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAGQAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAABYAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAWAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAFgAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAABYAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAWAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAFgAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAABYAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAWAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAFgAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAABYAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== -entities: -- uid: 0 - type: GlassStack - components: - - parent: 216 - pos: 8.560405,21.456738 - type: Transform - - anchored: False - type: Physics -- uid: 1 - type: solid_wall - components: - - parent: 216 - pos: 8.5,-2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 2 - type: LightTube - components: - - parent: 3 - type: Transform - - anchored: False - type: Physics -- uid: 3 - type: Poweredlight - components: - - parent: 216 - pos: 8,16.5 - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 2 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 4 - type: ApcExtensionCableStack1 - components: - - parent: 216 - pos: 10.577456,21.424059 - type: Transform - - anchored: False - type: Physics -- uid: 5 - type: ChairOfficeLight - components: - - parent: 216 - pos: 9.5,18.5 - rot: 3.141592653589793 rad - type: Transform -- uid: 6 - type: ChairOfficeLight - components: - - parent: 216 - pos: 9.5,16.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 7 - type: Chair - components: - - parent: 216 - pos: 12.5,17.5 - type: Transform -- uid: 8 - type: solid_wall - components: - - parent: 216 - pos: 8.5,-1.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 9 - type: solid_wall - components: - - parent: 216 - pos: 8.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 10 - type: solid_wall - components: - - parent: 216 - pos: 8.5,-3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 11 - type: Table - components: - - parent: 216 - pos: 13.5,17.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 12 - type: Table - components: - - parent: 216 - pos: 11.5,21.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 13 - type: Table - components: - - parent: 216 - pos: 10.5,21.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 14 - type: Table - components: - - parent: 216 - pos: 9.5,21.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 15 - type: Table - components: - - parent: 216 - pos: 8.5,21.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 16 - type: Autolathe - components: - - parent: 216 - pos: -4.5,-5.5 - type: Transform - - recipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - Crowbar - - Multitool - type: LatheDatabase -- uid: 17 - type: Autolathe - components: - - parent: 216 - pos: 13.5,18.5 - type: Transform - - recipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - Crowbar - - Multitool - type: LatheDatabase -- uid: 18 - type: Protolathe - components: - - parent: 216 - pos: 8.5,17.5 - type: Transform - - protolatherecipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - Crowbar - - Multitool - type: ProtolatheDatabase -- uid: 19 - type: BaseResearchAndDevelopmentPointSource - components: - - parent: 216 - pos: 13.5,16.5 - type: Transform -- uid: 20 - type: ComputerResearchAndDevelopment - components: - - parent: 216 - pos: 8.5,18.5 - type: Transform - - deadThreshold: 100 - type: BreakableConstruction -- uid: 21 - type: ResearchAndDevelopmentServer - components: - - parent: 216 - pos: 11.5,24.5 - rot: 1.5707963267948966 rad - type: Transform - - points: 343000 - type: ResearchServer -- uid: 22 - type: LightBulb - components: - - parent: 23 - type: Transform - - anchored: False - type: Physics -- uid: 23 - type: PoweredSmallLight - components: - - parent: 216 - pos: 7.5,26 - rot: 1.5707963267948966 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 22 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 24 - type: Catwalk - components: - - parent: 216 - pos: 13.5,24.5 - rot: 3.141592653589793 rad - type: Transform -- uid: 25 - type: LightBulb - components: - - parent: 26 - type: Transform - - anchored: False - type: Physics -- uid: 26 - type: PoweredSmallLight - components: - - parent: 216 - pos: 11,24.5 - rot: 3.141592653589793 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 25 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 27 - type: Poweredlight - components: - - parent: 216 - pos: 2,23.5 - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 28 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 28 - type: LightTube - components: - - parent: 27 - type: Transform - - anchored: False - type: Physics -- uid: 29 - type: LightTube - components: - - parent: 30 - type: Transform - - anchored: False - type: Physics -- uid: 30 - type: Poweredlight - components: - - parent: 216 - pos: 2,17.5 - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 29 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 31 - type: LightBulb - components: - - parent: 32 - type: Transform - - anchored: False - type: Physics -- uid: 32 - type: PoweredSmallLight - components: - - parent: 216 - pos: 18,17.5 - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 31 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 33 - type: LightTube - components: - - parent: 34 - type: Transform - - anchored: False - type: Physics -- uid: 34 - type: Poweredlight - components: - - parent: 216 - pos: 18,27.5 - rot: 3.141592653589793 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 33 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 35 - type: LightTube - components: - - parent: 36 - type: Transform - - anchored: False - type: Physics -- uid: 36 - type: Poweredlight - components: - - parent: 216 - pos: 18,22.5 - rot: 3.141592653589793 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 35 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 37 - type: Poweredlight - components: - - parent: 216 - pos: 14,18.5 - rot: 3.141592653589793 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 38 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 38 - type: LightTube - components: - - parent: 37 - type: Transform - - anchored: False - type: Physics -- uid: 39 - type: LightTube - components: - - parent: 40 - type: Transform - - anchored: False - type: Physics -- uid: 40 - type: Poweredlight - components: - - parent: 216 - pos: 12.5,12 - rot: 1.5707963267948966 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 39 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 41 - type: LightTube - components: - - parent: 42 - type: Transform - - anchored: False - type: Physics -- uid: 42 - type: Poweredlight - components: - - parent: 216 - pos: 6.5,12 - rot: 1.5707963267948966 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 41 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 43 - type: Table - components: - - parent: 216 - pos: 9.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 44 - type: Airlock - components: - - parent: 216 - pos: 7.5,20.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 45 - type: Airlock - components: - - parent: 216 - pos: 5.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 46 - type: AirlockScience - components: - - parent: 216 - pos: 16.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 47 - type: AirlockScience - components: - - parent: 216 - pos: 16.5,18.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 48 - type: AirlockScience - components: - - parent: 216 - pos: 14.5,24.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 49 - type: AirlockScienceGlass - components: - - parent: 216 - pos: 14.5,20.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 50 - type: solid_wall - components: - - parent: 216 - pos: 1.5,23.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 51 - type: solid_wall - components: - - parent: 216 - pos: 1.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 52 - type: solid_wall - components: - - parent: 216 - pos: 1.5,21.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 53 - type: solid_wall - components: - - parent: 216 - pos: 1.5,20.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 54 - type: solid_wall - components: - - parent: 216 - pos: 1.5,19.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 55 - type: solid_wall - components: - - parent: 216 - pos: 1.5,18.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 56 - type: solid_wall - components: - - parent: 216 - pos: 1.5,17.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 57 - type: solid_wall - components: - - parent: 216 - pos: 1.5,16.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 58 - type: solid_wall - components: - - parent: 216 - pos: 1.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 59 - type: Catwalk - components: - - parent: 216 - pos: 6.5,20.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 60 - type: Catwalk - components: - - parent: 216 - pos: 6.5,24.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 61 - type: Catwalk - components: - - parent: 216 - pos: 5.5,16.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 62 - type: solid_wall - components: - - parent: 216 - pos: 7.5,28.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 63 - type: solid_wall - components: - - parent: 216 - pos: 7.5,27.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 64 - type: solid_wall - components: - - parent: 216 - pos: 7.5,21.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 65 - type: solid_wall - components: - - parent: 216 - pos: 7.5,26.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 66 - type: solid_wall - components: - - parent: 216 - pos: 4.5,27.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 67 - type: solid_wall - components: - - parent: 216 - pos: 4.5,26.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 68 - type: solid_wall - components: - - parent: 216 - pos: 4.5,25.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 69 - type: solid_wall - components: - - parent: 216 - pos: 10.5,24.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 70 - type: solid_wall - components: - - parent: 216 - pos: 10.5,23.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 71 - type: solid_wall - components: - - parent: 216 - pos: 10.5,25.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 72 - type: solid_wall - components: - - parent: 216 - pos: 4.5,28.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 73 - type: solid_wall - components: - - parent: 216 - pos: 8.5,26.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 74 - type: solid_wall - components: - - parent: 216 - pos: 9.5,26.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 75 - type: solid_wall - components: - - parent: 216 - pos: 10.5,26.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 76 - type: solid_wall - components: - - parent: 216 - pos: 11.5,26.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 77 - type: solid_wall - components: - - parent: 216 - pos: 12.5,26.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 78 - type: solid_wall - components: - - parent: 216 - pos: 13.5,26.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 79 - type: solid_wall - components: - - parent: 216 - pos: 18.5,28.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 80 - type: solid_wall - components: - - parent: 216 - pos: 18.5,27.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 81 - type: solid_wall - components: - - parent: 216 - pos: 18.5,26.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 82 - type: solid_wall - components: - - parent: 216 - pos: 18.5,25.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 83 - type: solid_wall - components: - - parent: 216 - pos: 18.5,24.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 84 - type: solid_wall - components: - - parent: 216 - pos: 18.5,23.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 85 - type: solid_wall - components: - - parent: 216 - pos: 14.5,28.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 86 - type: solid_wall - components: - - parent: 216 - pos: 14.5,27.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 87 - type: solid_wall - components: - - parent: 216 - pos: 14.5,26.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 88 - type: solid_wall - components: - - parent: 216 - pos: 14.5,25.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 89 - type: solid_wall - components: - - parent: 216 - pos: 14.5,23.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 90 - type: solid_wall - components: - - parent: 216 - pos: 13.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 91 - type: solid_wall - components: - - parent: 216 - pos: 12.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 92 - type: solid_wall - components: - - parent: 216 - pos: 11.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 93 - type: solid_wall - components: - - parent: 216 - pos: 10.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 94 - type: solid_wall - components: - - parent: 216 - pos: 9.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 95 - type: solid_wall - components: - - parent: 216 - pos: 8.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 96 - type: solid_wall - components: - - parent: 216 - pos: 14.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 97 - type: solid_wall - components: - - parent: 216 - pos: 14.5,21.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 98 - type: solid_wall - components: - - parent: 216 - pos: 7.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 99 - type: solid_wall - components: - - parent: 216 - pos: 6.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 100 - type: solid_wall - components: - - parent: 216 - pos: 28.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 101 - type: solid_wall - components: - - parent: 216 - pos: 27.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 102 - type: solid_wall - components: - - parent: 216 - pos: 26.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 103 - type: solid_wall - components: - - parent: 216 - pos: 25.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 104 - type: solid_wall - components: - - parent: 216 - pos: 24.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 105 - type: solid_wall - components: - - parent: 216 - pos: 23.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 106 - type: solid_wall - components: - - parent: 216 - pos: 22.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 107 - type: solid_wall - components: - - parent: 216 - pos: 21.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 108 - type: solid_wall - components: - - parent: 216 - pos: 20.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 109 - type: solid_wall - components: - - parent: 216 - pos: 19.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 110 - type: solid_wall - components: - - parent: 216 - pos: 18.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 111 - type: solid_wall - components: - - parent: 216 - pos: 18.5,21.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 112 - type: solid_wall - components: - - parent: 216 - pos: 18.5,20.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 113 - type: solid_wall - components: - - parent: 216 - pos: 18.5,19.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 114 - type: solid_wall - components: - - parent: 216 - pos: 18.5,18.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 115 - type: solid_wall - components: - - parent: 216 - pos: 18.5,17.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 116 - type: solid_wall - components: - - parent: 216 - pos: 18.5,16.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 117 - type: solid_wall - components: - - parent: 216 - pos: 18.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 118 - type: solid_wall - components: - - parent: 216 - pos: 17.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 119 - type: solid_wall - components: - - parent: 216 - pos: 17.5,18.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 120 - type: solid_wall - components: - - parent: 216 - pos: 15.5,18.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 121 - type: solid_wall - components: - - parent: 216 - pos: 14.5,19.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 122 - type: solid_wall - components: - - parent: 216 - pos: 14.5,18.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 123 - type: solid_wall - components: - - parent: 216 - pos: 14.5,17.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 124 - type: solid_wall - components: - - parent: 216 - pos: 14.5,16.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 125 - type: solid_wall - components: - - parent: 216 - pos: 15.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 126 - type: solid_wall - components: - - parent: 216 - pos: 14.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 127 - type: solid_wall - components: - - parent: 216 - pos: 13.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 128 - type: solid_wall - components: - - parent: 216 - pos: 12.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 129 - type: solid_wall - components: - - parent: 216 - pos: 11.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 130 - type: solid_wall - components: - - parent: 216 - pos: 10.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 131 - type: solid_wall - components: - - parent: 216 - pos: 8.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 132 - type: solid_wall - components: - - parent: 216 - pos: 7.5,19.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 133 - type: solid_wall - components: - - parent: 216 - pos: 7.5,18.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 134 - type: solid_wall - components: - - parent: 216 - pos: 7.5,17.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 135 - type: solid_wall - components: - - parent: 216 - pos: 7.5,16.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 136 - type: solid_wall - components: - - parent: 216 - pos: 7.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 137 - type: solid_wall - components: - - parent: 216 - pos: 4.5,15.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 138 - type: solid_wall - components: - - parent: 216 - pos: 4.5,16.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 139 - type: solid_wall - components: - - parent: 216 - pos: 4.5,17.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 140 - type: solid_wall - components: - - parent: 216 - pos: 4.5,18.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 141 - type: solid_wall - components: - - parent: 216 - pos: 4.5,19.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 142 - type: solid_wall - components: - - parent: 216 - pos: 4.5,20.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 143 - type: solid_wall - components: - - parent: 216 - pos: 4.5,21.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 144 - type: solid_wall - components: - - parent: 216 - pos: 4.5,22.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 145 - type: solid_wall - components: - - parent: 216 - pos: 4.5,23.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 146 - type: solid_wall - components: - - parent: 216 - pos: 4.5,24.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 147 - type: LockerChemistry - components: - - parent: 216 - pos: 27.5,6.5 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 148 - type: LockerMedical - components: - - parent: 216 - pos: 27.5,5.5 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 149 - type: LockerMedical - components: - - parent: 216 - pos: 29.5,-1.5 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 150 - type: LockerMedical - components: - - parent: 216 - pos: 30.5,-1.5 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 151 - type: CrateMedical - components: - - parent: 216 - pos: 31.5,-1.5 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 152 - type: CrateMedical - components: - - parent: 216 - pos: 32.5,-1.5 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 153 - type: Beaker - components: - - parent: 216 - pos: 33.62275,-4.634824 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 154 - type: Beaker - components: - - parent: 216 - pos: 33.62275,-4.228574 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 155 - type: Beaker - components: - - parent: 216 - pos: 33.388374,-4.431699 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 156 - type: Beaker - components: - - parent: 216 - pos: 33.357124,-4.166074 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 157 - type: Beaker - components: - - parent: 216 - pos: 33.357124,-4.837949 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 158 - type: LargeBeaker - components: - - parent: 216 - pos: 33.544624,-5.572324 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 159 - type: LargeBeaker - components: - - parent: 216 - pos: 33.294624,-5.181699 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 160 - type: LargeBeaker - components: - - parent: 216 - pos: 33.18525,-5.681699 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 161 - type: Table - components: - - parent: 216 - pos: 33.5,-5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 162 - type: Table - components: - - parent: 216 - pos: 33.5,-4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 163 - type: Table - components: - - parent: 216 - pos: 33.5,-3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 164 - type: Table - components: - - parent: 216 - pos: 33.5,-2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 165 - type: Table - components: - - parent: 216 - pos: 33.5,-1.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 166 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: 19.5,-3.5 - rot: 1.5707963267948966 rad - type: Transform -- uid: 167 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: 17.5,8.5 - rot: 1.5707963267948966 rad - type: Transform -- uid: 168 - type: Ointment - components: - - parent: 216 - pos: 18.77326,6.653532 - rot: 1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 169 - type: Ointment - components: - - parent: 216 - pos: 18.49201,6.059782 - rot: 1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 170 - type: Brutepack - components: - - parent: 216 - pos: 18.601385,5.512907 - rot: 1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 171 - type: Brutepack - components: - - parent: 216 - pos: 18.476385,4.841032 - rot: 1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 172 - type: LightTube - components: - - parent: 173 - type: Transform - - anchored: False - type: Physics -- uid: 173 - type: Poweredlight - components: - - parent: 216 - pos: 23.5,-6 - rot: 1.5707963267948966 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 172 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 174 - type: ComputerMedicalRecords - components: - - parent: 216 - pos: 22.5,-5.5 - rot: 1.5707963267948966 rad - type: Transform - - deadThreshold: 100 - type: BreakableConstruction -- uid: 175 - type: MedkitFilled - components: - - parent: 216 - pos: 13.632214,1.5673001 - rot: 3.141592653589793 rad - type: Transform - - anchored: False - type: Physics - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 176 - type: MedkitFilled - components: - - parent: 216 - pos: 13.460339,0.6141751 - rot: 3.141592653589793 rad - type: Transform - - anchored: False - type: Physics - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 177 - type: VendingMachineWallMedical - components: - - parent: 216 - pos: 1.5,-3.5 - rot: 3.141592653589793 rad - type: Transform -- uid: 178 - type: VendingMachineMedical - components: - - parent: 216 - pos: 25.5,-5.5 - rot: 3.141592653589793 rad - type: Transform -- uid: 179 - type: VendingMachineMedical - components: - - parent: 216 - pos: 27.5,-5.5 - rot: 3.141592653589793 rad - type: Transform -- uid: 180 - type: VendingMachineMedical - components: - - parent: 216 - pos: 29.5,3.5 - rot: 3.141592653589793 rad - type: Transform -- uid: 181 - type: LightTube - components: - - parent: 182 - type: Transform - - anchored: False - type: Physics -- uid: 182 - type: Poweredlight - components: - - parent: 216 - pos: 28,7.5 - rot: 3.141592653589793 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 181 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 183 - type: LightTube - components: - - parent: 184 - type: Transform - - anchored: False - type: Physics -- uid: 184 - type: Poweredlight - components: - - parent: 216 - pos: 30,1.5 - rot: 3.141592653589793 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 183 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 185 - type: LightTube - components: - - parent: 186 - type: Transform - - anchored: False - type: Physics -- uid: 186 - type: Poweredlight - components: - - parent: 216 - pos: 31.5,-6 - rot: 1.5707963267948966 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 185 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 187 - type: solid_wall - components: - - parent: 216 - pos: 6.5,2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 188 - type: solid_wall - components: - - parent: 216 - pos: 5.5,2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 189 - type: Table - components: - - parent: 216 - pos: 24.5,-5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 190 - type: Table - components: - - parent: 216 - pos: 23.5,-5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 191 - type: LightTube - components: - - parent: 192 - type: Transform - - anchored: False - type: Physics -- uid: 192 - type: Poweredlight - components: - - parent: 216 - pos: 16.5,-6 - rot: 1.5707963267948966 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 191 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 193 - type: LightTube - components: - - parent: 194 - type: Transform - - anchored: False - type: Physics -- uid: 194 - type: Poweredlight - components: - - parent: 216 - pos: 22,9.5 - rot: 3.141592653589793 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 193 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 195 - type: LightTube - components: - - parent: 196 - type: Transform - - anchored: False - type: Physics -- uid: 196 - type: Poweredlight - components: - - parent: 216 - pos: 14,9.5 - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 195 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 197 - type: solid_wall - components: - - parent: 216 - pos: 8.5,2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 198 - type: LightTube - components: - - parent: 199 - type: Transform - - anchored: False - type: Physics -- uid: 199 - type: Poweredlight - components: - - parent: 216 - pos: 13.5,3 - rot: -1.5707963267948966 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 198 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 200 - type: LightTube - components: - - parent: 201 - type: Transform - - anchored: False - type: Physics -- uid: 201 - type: Poweredlight - components: - - parent: 216 - pos: 22.5,3 - rot: -1.5707963267948966 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 200 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 202 - type: LightTube - components: - - parent: 203 - type: Transform - - anchored: False - type: Physics -- uid: 203 - type: Poweredlight - components: - - parent: 216 - pos: 17.5,4 - rot: 1.5707963267948966 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 202 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 204 - type: solid_wall - components: - - parent: 216 - pos: 7.5,2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 205 - type: solid_wall - components: - - parent: 216 - pos: 8.5,0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 206 - type: Airlock - components: - - parent: 216 - pos: 13.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 207 - type: Airlock - components: - - parent: 216 - pos: 4.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 208 - type: Airlock - components: - - parent: 216 - pos: 1.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 209 - type: Poweredlight - components: - - parent: 216 - pos: 8,-1.5 - rot: 3.141592653589793 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 306 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 210 - type: solid_wall - components: - - parent: 216 - pos: 8.5,1.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 211 - type: Beaker - components: - - parent: 216 - pos: 25.291822,10.667244 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 212 - type: Beaker - components: - - parent: 216 - pos: 24.541822,10.635994 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 213 - type: Beaker - components: - - parent: 216 - pos: 26.416822,10.651619 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 214 - type: AirlockMedicalGlass - components: - - parent: 216 - pos: 26.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 215 - type: AirlockMedicalGlass - components: - - parent: 216 - pos: 20.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 216 - components: - - parent: null - type: Transform - - index: 0 - type: MapGrid -- uid: 217 - type: LaserGun - components: - - parent: 216 - pos: -1.47174,4.550247 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - startingCharge: 1000 - type: PowerCell - - containers: - BatteryBarrel-powercell-container: - entities: - - 383 - type: Content.Server.GameObjects.ContainerSlot - BatteryBarrel-ammo-container: - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 218 - type: LaserGun - components: - - parent: 216 - pos: -0.6748645,4.487747 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - startingCharge: 1000 - type: PowerCell - - containers: - BatteryBarrel-powercell-container: - entities: - - 384 - type: Content.Server.GameObjects.ContainerSlot - BatteryBarrel-ammo-container: - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 219 - type: Brutepack - components: - - parent: 216 - pos: -2.106966,-1.457896 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 220 - type: Ointment - components: - - parent: 216 - pos: -1.481966,-1.317271 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 221 - type: Spear - components: - - parent: 216 - pos: -4.144312,7.499083 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 222 - type: Spear - components: - - parent: 216 - pos: -1.238062,7.436583 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 223 - type: PowerCellSmallHigh - components: - - parent: 216 - pos: -2.67511,-10.351 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 224 - type: PowerCellSmallHigh - components: - - parent: 216 - pos: -2.55011,-10.6635 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 225 - type: ClothingOuterVest - components: - - parent: 216 - pos: 1.412994,7.507263 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 226 - type: solid_wall - components: - - parent: 216 - pos: -7.5,0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 227 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 228 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 229 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-2.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 230 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-3.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 231 - type: solid_wall - components: - - parent: 216 - pos: 0.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 232 - type: solid_wall - components: - - parent: 216 - pos: -0.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 233 - type: solid_wall - components: - - parent: 216 - pos: 3.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 234 - type: solid_wall - components: - - parent: 216 - pos: 4.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 235 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-10.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 236 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-11.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 237 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-12.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 238 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-13.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 239 - type: solid_wall - components: - - parent: 216 - pos: 2.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 240 - type: solid_wall - components: - - parent: 216 - pos: 1.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 241 - type: solid_wall - components: - - parent: 216 - pos: -1.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 242 - type: PoweredSmallLight - components: - - parent: 216 - pos: -4.5,-5 - rot: 1.5707963267949 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 246 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 243 - type: MetalStack - components: - - parent: 216 - pos: -15.5,-5.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 244 - type: AirlockMedicalGlass - components: - - parent: 216 - pos: 16.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 245 - type: PoweredSmallLight - components: - - parent: 216 - pos: 0.5,-5 - rot: 1.5707963267949 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 385 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 246 - type: LightBulb - components: - - parent: 242 - type: Transform - - anchored: False - type: Physics -- uid: 247 - type: AirlockMedicalGlass - components: - - parent: 216 - pos: 15.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 248 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-9.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 249 - type: solid_wall - components: - - parent: 216 - pos: -10.5,-7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 250 - type: AirlockEngineering - components: - - parent: 216 - pos: -12.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 251 - type: solid_wall - components: - - parent: 216 - pos: -10.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 252 - type: solid_wall - components: - - parent: 216 - pos: -10.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 253 - type: solid_wall - components: - - parent: 216 - pos: -10.5,-3.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 254 - type: solid_wall - components: - - parent: 216 - pos: -10.5,-2.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 255 - type: solid_wall - components: - - parent: 216 - pos: -10.5,-1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 256 - type: solid_wall - components: - - parent: 216 - pos: -3.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 257 - type: solid_wall - components: - - parent: 216 - pos: 1.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 258 - type: solid_wall - components: - - parent: 216 - pos: 0.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 259 - type: solid_wall - components: - - parent: 216 - pos: -0.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 260 - type: solid_wall - components: - - parent: 216 - pos: -1.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 261 - type: solid_wall - components: - - parent: 216 - pos: -2.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 262 - type: solid_wall - components: - - parent: 216 - pos: -3.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 263 - type: solid_wall - components: - - parent: 216 - pos: -4.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 264 - type: solid_wall - components: - - parent: 216 - pos: -5.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 265 - type: solid_wall - components: - - parent: 216 - pos: -6.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 266 - type: solid_wall - components: - - parent: 216 - pos: 4.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 267 - type: solid_wall - components: - - parent: 216 - pos: 5.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 268 - type: solid_wall - components: - - parent: 216 - pos: 6.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 269 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 270 - type: solid_wall - components: - - parent: 216 - pos: -2.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 271 - type: solid_wall - components: - - parent: 216 - pos: -6.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 272 - type: solid_wall - components: - - parent: 216 - pos: -5.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 273 - type: solid_wall - components: - - parent: 216 - pos: -4.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 274 - type: solid_wall - components: - - parent: 216 - pos: 6.5,-10.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 275 - type: solid_wall - components: - - parent: 216 - pos: 6.5,-11.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 276 - type: solid_wall - components: - - parent: 216 - pos: 6.5,-12.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 277 - type: solid_wall - components: - - parent: 216 - pos: 6.5,-13.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 278 - type: solid_wall - components: - - parent: 216 - pos: 6.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 279 - type: solid_wall - components: - - parent: 216 - pos: 5.5,-14.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 280 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 281 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 282 - type: solid_wall - components: - - parent: 216 - pos: -8.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 283 - type: solid_wall - components: - - parent: 216 - pos: -9.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 284 - type: solid_wall - components: - - parent: 216 - pos: -10.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 285 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 286 - type: Catwalk - components: - - parent: 216 - pos: -6.5,-6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 287 - type: Catwalk - components: - - parent: 216 - pos: -8.5,-6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 288 - type: solid_wall - components: - - parent: 216 - pos: 5.5,-7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 289 - type: solid_wall - components: - - parent: 216 - pos: 5.5,-9.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 290 - type: solid_wall - components: - - parent: 216 - pos: 6.5,-9.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 291 - type: solid_wall - components: - - parent: 216 - pos: 6.5,-7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 292 - type: Catwalk - components: - - parent: 216 - pos: 4.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 293 - type: LargeBeaker - components: - - parent: 216 - pos: 23.494947,7.0422435 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 294 - type: solid_wall - components: - - parent: 216 - pos: 7.5,-9.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 295 - type: AirlockExternal - components: - - parent: 216 - pos: 7.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 296 - type: AirlockExternal - components: - - parent: 216 - pos: 5.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 297 - type: AirlockEngineering - components: - - parent: 216 - pos: -7.5,-6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 298 - type: AirlockEngineering - components: - - parent: 216 - pos: 3.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 299 - type: AirlockEngineering - components: - - parent: 216 - pos: 2.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 300 - type: solid_wall - components: - - parent: 216 - pos: 6.5,-6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 301 - type: solid_wall - components: - - parent: 216 - pos: 6.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 302 - type: Table - components: - - parent: 216 - pos: -3.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 303 - type: Table - components: - - parent: 216 - pos: -2.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 304 - type: Table - components: - - parent: 216 - pos: -1.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 305 - type: Table - components: - - parent: 216 - pos: -0.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 306 - type: LightTube - components: - - parent: 209 - type: Transform - - anchored: False - type: Physics -- uid: 307 - type: CrateGeneric - components: - - parent: 216 - pos: 5.5,-6.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 308 - type: Table - components: - - parent: 216 - pos: 23.5,5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 309 - type: Table - components: - - parent: 216 - pos: 23.5,6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 310 - type: Catwalk - components: - - parent: 216 - pos: 12.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 311 - type: Catwalk - components: - - parent: 216 - pos: 5.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 312 - type: solid_wall - components: - - parent: 216 - pos: 1.5,14.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 313 - type: solid_wall - components: - - parent: 216 - pos: 1.5,13.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 314 - type: solid_wall - components: - - parent: 216 - pos: 1.5,12.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 315 - type: solid_wall - components: - - parent: 216 - pos: -7.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 316 - type: solid_wall - components: - - parent: 216 - pos: -6.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 317 - type: solid_wall - components: - - parent: 216 - pos: -5.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 318 - type: solid_wall - components: - - parent: 216 - pos: -4.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 319 - type: solid_wall - components: - - parent: 216 - pos: -3.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 320 - type: solid_wall - components: - - parent: 216 - pos: -2.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 321 - type: solid_wall - components: - - parent: 216 - pos: -1.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 322 - type: solid_wall - components: - - parent: 216 - pos: -0.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 323 - type: solid_wall - components: - - parent: 216 - pos: 0.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 324 - type: solid_wall - components: - - parent: 216 - pos: 1.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 325 - type: solid_wall - components: - - parent: 216 - pos: 1.5,9.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 326 - type: solid_wall - components: - - parent: 216 - pos: 4.5,9.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 327 - type: Catwalk - components: - - parent: 216 - pos: 2.5,-11.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 328 - type: Catwalk - components: - - parent: 216 - pos: -4.5,-11.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 329 - type: solid_wall - components: - - parent: 216 - pos: 12.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 330 - type: solid_wall - components: - - parent: 216 - pos: 11.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 331 - type: solid_wall - components: - - parent: 216 - pos: 10.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 332 - type: solid_wall - components: - - parent: 216 - pos: 9.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 333 - type: solid_wall - components: - - parent: 216 - pos: 8.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 334 - type: solid_wall - components: - - parent: 216 - pos: 7.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 335 - type: solid_wall - components: - - parent: 216 - pos: 6.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 336 - type: solid_wall - components: - - parent: 216 - pos: 5.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 337 - type: solid_wall - components: - - parent: 216 - pos: 4.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 338 - type: solid_wall - components: - - parent: 216 - pos: 5.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 339 - type: solid_wall - components: - - parent: 216 - pos: 6.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 340 - type: solid_wall - components: - - parent: 216 - pos: 7.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 341 - type: solid_wall - components: - - parent: 216 - pos: 8.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 342 - type: solid_wall - components: - - parent: 216 - pos: 9.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 343 - type: solid_wall - components: - - parent: 216 - pos: 10.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 344 - type: solid_wall - components: - - parent: 216 - pos: 11.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 345 - type: solid_wall - components: - - parent: 216 - pos: 12.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 346 - type: MetalStack - components: - - parent: 216 - pos: 9.435405,21.503613 - type: Transform - - anchored: False - type: Physics -- uid: 347 - type: MetalStack - components: - - parent: 216 - pos: 9.654155,21.628613 - type: Transform - - anchored: False - type: Physics -- uid: 348 - type: MagazinePistolSmg - components: - - parent: 484 - type: Transform - - anchored: False - type: Physics - - containers: - RangedMagazine-magazine: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 349 - type: MagazinePistolSmg - components: - - parent: 485 - type: Transform - - anchored: False - type: Physics - - containers: - RangedMagazine-magazine: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 350 - type: Multitool - components: - - parent: 216 - pos: -1.249865,-10.43489 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 351 - type: Catwalk - components: - - parent: 216 - pos: -2.5,-12.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 352 - type: Catwalk - components: - - parent: 216 - pos: 1.5,-12.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 353 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: -1.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 354 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: 0.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 355 - type: Table - components: - - parent: 216 - pos: -3.5,-10.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 356 - type: Table - components: - - parent: 216 - pos: -2.5,-10.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 357 - type: Table - components: - - parent: 216 - pos: -1.5,-10.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 358 - type: Table - components: - - parent: 216 - pos: -0.5,-10.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 359 - type: solid_wall - components: - - parent: 216 - pos: -7.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 360 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: -3.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 361 - type: ClothingOuterVest - components: - - parent: 216 - pos: 0.5223687,7.507263 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 362 - type: LockerGeneric - components: - - parent: 216 - pos: 1.5,-10.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - Content.Server.GameObjects.Components.EntityStorageComponent149: - type: Robust.Server.GameObjects.Components.Container.Container - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 363 - type: MedkitFilled - components: - - parent: 216 - pos: -3.209215,-1.486604 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 364 - type: MedkitFilled - components: - - parent: 216 - pos: -4.146715,-1.408479 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 365 - type: LockerGeneric - components: - - parent: 216 - pos: 0.5,-10.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - Content.Server.GameObjects.Components.EntityStorageComponent153: - type: Robust.Server.GameObjects.Components.Container.Container - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 366 - type: FireExtinguisher - components: - - parent: 216 - pos: -1.297692,-5.396082 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 367 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: -0.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 368 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: -5.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 369 - type: ComputerSupplyRequest - components: - - parent: 216 - pos: 0.5,-5.5 - rot: -1.5707963267949 rad - type: Transform - - products: - - cargo.dice - - cargo.flashlight - - cargo.Medkit - type: GalacticMarket - - deadThreshold: 100 - type: BreakableConstruction -- uid: 370 - type: ComputerSupplyOrdering - components: - - parent: 216 - pos: 0.5,0.5 - rot: -1.5707963267949 rad - type: Transform - - products: - - cargo.dice - - cargo.flashlight - - cargo.Medkit - type: GalacticMarket - - deadThreshold: 100 - type: BreakableConstruction -- uid: 371 - type: Table - components: - - parent: 216 - pos: -4.5,-1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 372 - type: Table - components: - - parent: 216 - pos: -1.5,-1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 373 - type: Table - components: - - parent: 216 - pos: -2.5,-1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 374 - type: Table - components: - - parent: 216 - pos: -3.5,-1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 375 - type: GravityGenerator - components: - - parent: 216 - pos: 6.5,0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 376 - type: Catwalk - components: - - parent: 216 - pos: 4.5,-13.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 377 - type: Catwalk - components: - - parent: 216 - pos: -5.5,-13.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 378 - type: solid_wall - components: - - parent: 216 - pos: 13.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 379 - type: solid_wall - components: - - parent: 216 - pos: 14.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 380 - type: solid_wall - components: - - parent: 216 - pos: 13.5,9.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 381 - type: solid_wall - components: - - parent: 216 - pos: 13.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 382 - type: solid_wall - components: - - parent: 216 - pos: 13.5,7.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 383 - type: PowerCellSmallStandard - components: - - parent: 217 - type: Transform - - anchored: False - type: Physics -- uid: 384 - type: PowerCellSmallStandard - components: - - parent: 218 - type: Transform - - anchored: False - type: Physics -- uid: 385 - type: LightBulb - components: - - parent: 245 - type: Transform - - anchored: False - type: Physics -- uid: 386 - type: WallLight - components: - - parent: 216 - pos: -0.5,-14 - rot: 1.5707963267949 rad - type: Transform -- uid: 387 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: -9.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 388 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: -0.5,5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 389 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: -5.5,5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 390 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: -0.5,-2.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 391 - type: SpawnPointLatejoin - components: - - parent: 216 - pos: -5.5,-2.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 392 - type: Catwalk - components: - - parent: 216 - pos: 9.5,0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 393 - type: Catwalk - components: - - parent: 216 - pos: 9.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 394 - type: Catwalk - components: - - parent: 216 - pos: 9.5,2.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 395 - type: Catwalk - components: - - parent: 216 - pos: 9.5,3.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 396 - type: Catwalk - components: - - parent: 216 - pos: 9.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 397 - type: VendingMachineYouTool - components: - - parent: 216 - pos: -11.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 398 - type: ApcExtensionCableStack1 - components: - - parent: 216 - pos: 10.561831,21.767809 - type: Transform - - anchored: False - type: Physics -- uid: 399 - type: solid_wall - components: - - parent: 216 - pos: 13.5,6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 400 - type: solid_wall - components: - - parent: 216 - pos: 13.5,5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 401 - type: GlassStack - components: - - parent: 216 - pos: 8.57603,21.566113 - type: Transform - - anchored: False - type: Physics -- uid: 402 - type: Table - components: - - parent: 216 - pos: -6.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 403 - type: Table - components: - - parent: 216 - pos: -5.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 404 - type: Table - components: - - parent: 216 - pos: -4.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 405 - type: Table - components: - - parent: 216 - pos: -3.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 406 - type: Table - components: - - parent: 216 - pos: -2.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 407 - type: Table - components: - - parent: 216 - pos: -1.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 408 - type: Table - components: - - parent: 216 - pos: -0.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 409 - type: Table - components: - - parent: 216 - pos: 0.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 410 - type: Table - components: - - parent: 216 - pos: 1.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 411 - type: Table - components: - - parent: 216 - pos: -4.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 412 - type: Table - components: - - parent: 216 - pos: -3.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 413 - type: Table - components: - - parent: 216 - pos: -2.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 414 - type: Table - components: - - parent: 216 - pos: -1.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 415 - type: Table - components: - - parent: 216 - pos: -0.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 416 - type: AirlockMedicalGlass - components: - - parent: 216 - pos: 26.5,-3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 417 - type: AirlockMedicalGlass - components: - - parent: 216 - pos: 28.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 418 - type: Catwalk - components: - - parent: 216 - pos: -9.5,0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 419 - type: solid_wall - components: - - parent: 216 - pos: 13.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 420 - type: solid_wall - components: - - parent: 216 - pos: 25.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 421 - type: solid_wall - components: - - parent: 216 - pos: 23.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 422 - type: solid_wall - components: - - parent: 216 - pos: 17.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 423 - type: solid_wall - components: - - parent: 216 - pos: -10.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 424 - type: solid_wall - components: - - parent: 216 - pos: -10.5,0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 425 - type: Airlock - components: - - parent: 216 - pos: -9.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 426 - type: LightTube - components: - - parent: 427 - type: Transform - - anchored: False - type: Physics -- uid: 427 - type: Poweredlight - components: - - parent: 216 - pos: 0.5,1 - rot: -1.5707963267949 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 426 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 428 - type: ChairOfficeLight - components: - - parent: 216 - pos: -3.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 429 - type: ChairOfficeDark - components: - - parent: 216 - pos: 0.5,-6.5 - rot: 1.5707963267949 rad - type: Transform -- uid: 430 - type: Poweredlight - components: - - parent: 216 - pos: -6.5,1 - rot: -1.5707963267949 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 531 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 431 - type: MetalStack - components: - - parent: 216 - pos: -15.5,-5.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 432 - type: Stool - components: - - parent: 216 - pos: -1.5,-9.5 - rot: 1.5707963267949 rad - type: Transform -- uid: 433 - type: ChairOfficeLight - components: - - parent: 216 - pos: -3.5,-2.5 - rot: 1.5707963267949 rad - type: Transform -- uid: 434 - type: ChairOfficeLight - components: - - parent: 216 - pos: -2.5,-2.5 - rot: 1.5707963267949 rad - type: Transform -- uid: 435 - type: ChairOfficeLight - components: - - parent: 216 - pos: -2.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 436 - type: Stool - components: - - parent: 216 - pos: -2.5,-6.5 - rot: 1.5707963267949 rad - type: Transform -- uid: 437 - type: LightBulb - components: - - parent: 466 - type: Transform - - anchored: False - type: Physics -- uid: 438 - type: WardrobeScience - components: - - parent: 216 - pos: 12.5,21.5 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 439 - type: solid_wall - components: - - parent: 216 - pos: 18.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 440 - type: solid_wall - components: - - parent: 216 - pos: 19.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 441 - type: solid_wall - components: - - parent: 216 - pos: 21.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 442 - type: solid_wall - components: - - parent: 216 - pos: 22.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 443 - type: solid_wall - components: - - parent: 216 - pos: 22.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 444 - type: solid_wall - components: - - parent: 216 - pos: 22.5,5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 445 - type: solid_wall - components: - - parent: 216 - pos: 22.5,6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 446 - type: solid_wall - components: - - parent: 216 - pos: 22.5,7.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 447 - type: solid_wall - components: - - parent: 216 - pos: 22.5,9.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 448 - type: solid_wall - components: - - parent: 216 - pos: 22.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 449 - type: solid_wall - components: - - parent: 216 - pos: 21.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 450 - type: solid_wall - components: - - parent: 216 - pos: 22.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 451 - type: LargeBeaker - components: - - parent: 216 - pos: 23.510572,7.7141185 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics -- uid: 452 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 453 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 454 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-2.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 455 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-3.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 456 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 457 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 458 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 459 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 460 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 461 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-9.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 462 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-10.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 463 - type: Catwalk - components: - - parent: 216 - pos: 9.5,-11.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 464 - type: Catwalk - components: - - parent: 216 - pos: 8.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 465 - type: AirlockEngineering - components: - - parent: 216 - pos: 4.5,-2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 466 - type: PoweredSmallLight - components: - - parent: 216 - pos: 5,-9.5 - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 437 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 467 - type: solid_wall - components: - - parent: 216 - pos: 7.5,-7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 468 - type: ToolboxElectricalFilled - components: - - parent: 216 - pos: -0.8099712,-5.21454 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 469 - type: ToolboxElectricalFilled - components: - - parent: 216 - pos: -0.5597038,-5.679647 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 470 - type: FlashlightLantern - components: - - parent: 216 - pos: -1.934832,-5.154238 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - flashlight_cell_container: - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 471 - type: FlashlightLantern - components: - - parent: 216 - pos: -2.017696,-5.71715 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - flashlight_cell_container: - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 472 - type: Crowbar - components: - - parent: 216 - pos: -2.861032,-5.524786 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 473 - type: ClothingUniformJumpsuitEngineering - components: - - parent: 216 - pos: -0.6474335,-10.27245 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 474 - type: ClothingMaskGas - components: - - parent: 216 - pos: -0.2880585,-10.69432 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 475 - type: ClothingOuterVest - components: - - parent: 216 - pos: -0.9130585,-10.66307 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 476 - type: ClothingBeltUtilityFilled - components: - - parent: 216 - pos: -1.895102,-10.33495 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 477 - type: ClothingBeltUtilityFilled - components: - - parent: 216 - pos: -1.770102,-10.63182 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 478 - type: MagazinePistolSmg - components: - - parent: 216 - pos: -6.605512,7.638151 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - magazine_bullet_container: - type: Robust.Server.GameObjects.Components.Container.Container - RangedMagazine-magazine: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 479 - type: MagazinePistolSmg - components: - - parent: 216 - pos: -6.339887,7.669401 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - magazine_bullet_container: - type: Robust.Server.GameObjects.Components.Container.Container - RangedMagazine-magazine: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 480 - type: MagazinePistolSmg - components: - - parent: 216 - pos: -6.027387,7.622526 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - magazine_bullet_container: - type: Robust.Server.GameObjects.Components.Container.Container - RangedMagazine-magazine: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 481 - type: ClothingBackpack - components: - - parent: 216 - pos: -5.089887,7.591276 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 482 - type: ClothingBackpack - components: - - parent: 216 - pos: -4.683637,7.606901 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - containers: - storagebase: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 483 - type: ClothingHandsGlovesColorBlack - components: - - parent: 216 - pos: -3.386762,7.466276 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 484 - type: SmgC20r - components: - - parent: 216 - pos: -2.524035,7.579326 - rot: -1.5707963267949 rad - type: Transform - - maxAngle: 59.99999999999999 - type: MagazineBarrel - - anchored: False - type: Physics - - containers: - ballistics_chamber_0: - type: Content.Server.GameObjects.ContainerSlot - ballistic_gun_magazine: - type: Content.Server.GameObjects.ContainerSlot - MagazineBarrel-chamber: - type: Content.Server.GameObjects.ContainerSlot - MagazineBarrel-magazine: - entities: - - 348 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 485 - type: SmgC20r - components: - - parent: 216 - pos: -1.94591,7.485576 - rot: -1.5707963267949 rad - type: Transform - - maxAngle: 59.99999999999999 - type: MagazineBarrel - - anchored: False - type: Physics - - containers: - ballistics_chamber_0: - type: Content.Server.GameObjects.ContainerSlot - ballistic_gun_magazine: - type: Content.Server.GameObjects.ContainerSlot - MagazineBarrel-chamber: - type: Content.Server.GameObjects.ContainerSlot - MagazineBarrel-magazine: - entities: - - 349 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 486 - type: solid_wall - components: - - parent: 216 - pos: -10.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 487 - type: solid_wall - components: - - parent: 216 - pos: -11.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 488 - type: solid_wall - components: - - parent: 216 - pos: -10.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 489 - type: solid_wall - components: - - parent: 216 - pos: -9.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 490 - type: solid_wall - components: - - parent: 216 - pos: -8.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 491 - type: solid_wall - components: - - parent: 216 - pos: -7.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 492 - type: solid_wall - components: - - parent: 216 - pos: -8.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 493 - type: solid_wall - components: - - parent: 216 - pos: -5.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 494 - type: solid_wall - components: - - parent: 216 - pos: -6.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 495 - type: solid_wall - components: - - parent: 216 - pos: -7.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 496 - type: solid_wall - components: - - parent: 216 - pos: -0.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 497 - type: solid_wall - components: - - parent: 216 - pos: 0.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 498 - type: solid_wall - components: - - parent: 216 - pos: 1.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 499 - type: solid_wall - components: - - parent: 216 - pos: 1.5,0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 500 - type: solid_wall - components: - - parent: 216 - pos: 1.5,-3.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 501 - type: solid_wall - components: - - parent: 216 - pos: 4.5,-3.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 502 - type: WardrobeScience - components: - - parent: 216 - pos: 13.5,21.5 - rot: -1.5707963267948966 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 503 - type: solid_wall - components: - - parent: 216 - pos: 4.5,-1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 504 - type: solid_wall - components: - - parent: 216 - pos: 4.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 505 - type: solid_wall - components: - - parent: 216 - pos: 4.5,0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 506 - type: solid_wall - components: - - parent: 216 - pos: 4.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 507 - type: solid_wall - components: - - parent: 216 - pos: 4.5,2.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 508 - type: solid_wall - components: - - parent: 216 - pos: 4.5,3.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 509 - type: solid_wall - components: - - parent: 216 - pos: 4.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 510 - type: solid_wall - components: - - parent: 216 - pos: 4.5,5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 511 - type: solid_wall - components: - - parent: 216 - pos: 4.5,6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 512 - type: solid_wall - components: - - parent: 216 - pos: 4.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 513 - type: solid_wall - components: - - parent: 216 - pos: 4.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 514 - type: solid_wall - components: - - parent: 216 - pos: 1.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 515 - type: solid_wall - components: - - parent: 216 - pos: 0.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 516 - type: solid_wall - components: - - parent: 216 - pos: -0.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 517 - type: solid_wall - components: - - parent: 216 - pos: -1.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 518 - type: solid_wall - components: - - parent: 216 - pos: -2.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 519 - type: solid_wall - components: - - parent: 216 - pos: -3.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 520 - type: solid_wall - components: - - parent: 216 - pos: -4.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 521 - type: solid_wall - components: - - parent: 216 - pos: -5.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 522 - type: solid_wall - components: - - parent: 216 - pos: -6.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 523 - type: solid_wall - components: - - parent: 216 - pos: -7.5,8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 524 - type: solid_wall - components: - - parent: 216 - pos: -7.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 525 - type: solid_wall - components: - - parent: 216 - pos: -7.5,6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 526 - type: solid_wall - components: - - parent: 216 - pos: -7.5,5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 527 - type: ClothingHandsGlovesLeather - components: - - parent: 216 - pos: -4.332221,4.64238 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 528 - type: ClothingHandsGlovesLeather - components: - - parent: 216 - pos: -3.519721,4.64238 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 529 - type: ClothingHandsGlovesLeather - components: - - parent: 216 - pos: -2.597846,4.61113 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 530 - type: LedLightTube - components: - - parent: 216 - pos: -3.511025,-10.35149 - rot: -1.5707963267949 rad - type: Transform - - color: '#EEEEFFFF' - type: Sprite - - anchored: False - type: Physics -- uid: 531 - type: LightTube - components: - - parent: 430 - type: Transform - - anchored: False - type: Physics -- uid: 532 - type: Poweredlight - components: - - parent: 216 - pos: -1.5,8 - rot: -1.5707963267949 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 533 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 533 - type: LightTube - components: - - parent: 532 - type: Transform - - anchored: False - type: Physics -- uid: 534 - type: Poweredlight - components: - - parent: 216 - pos: 4,3.5 - rot: 3.14159265358979 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 535 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 535 - type: LightTube - components: - - parent: 534 - type: Transform - - anchored: False - type: Physics -- uid: 536 - type: WallLight - components: - - parent: 216 - pos: -7,-10.5 - type: Transform -- uid: 537 - type: PoweredSmallLight - components: - - parent: 216 - pos: -10,-5.5 - rot: 3.14159265358979 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 538 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 538 - type: LightBulb - components: - - parent: 537 - type: Transform - - anchored: False - type: Physics -- uid: 539 - type: solid_wall - components: - - parent: 216 - pos: -15.5,2.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 540 - type: solid_wall - components: - - parent: 216 - pos: -15.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 541 - type: solid_wall - components: - - parent: 216 - pos: -15.5,3.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 542 - type: solid_wall - components: - - parent: 216 - pos: -14.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 543 - type: solid_wall - components: - - parent: 216 - pos: -12.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 544 - type: solid_wall - components: - - parent: 216 - pos: -15.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 545 - type: solid_wall - components: - - parent: 216 - pos: -14.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 546 - type: solid_wall - components: - - parent: 216 - pos: -11.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 547 - type: solid_wall - components: - - parent: 216 - pos: -14.5,5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 548 - type: solid_wall - components: - - parent: 216 - pos: -14.5,6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 549 - type: solid_wall - components: - - parent: 216 - pos: -12.5,6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 550 - type: solid_wall - components: - - parent: 216 - pos: -12.5,5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 551 - type: solid_wall - components: - - parent: 216 - pos: -16.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 552 - type: solid_wall - components: - - parent: 216 - pos: -16.5,0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 553 - type: solid_wall - components: - - parent: 216 - pos: -16.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 554 - type: solid_wall - components: - - parent: 216 - pos: -16.5,-1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 555 - type: solid_wall - components: - - parent: 216 - pos: -16.5,-2.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 556 - type: solid_wall - components: - - parent: 216 - pos: -16.5,-3.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 557 - type: solid_wall - components: - - parent: 216 - pos: -16.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 558 - type: solid_wall - components: - - parent: 216 - pos: -16.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 559 - type: solid_wall - components: - - parent: 216 - pos: -16.5,-6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 560 - type: solid_wall - components: - - parent: 216 - pos: -16.5,-7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 561 - type: solid_wall - components: - - parent: 216 - pos: -16.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 562 - type: solid_wall - components: - - parent: 216 - pos: -15.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 563 - type: solid_wall - components: - - parent: 216 - pos: -14.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 564 - type: solid_wall - components: - - parent: 216 - pos: -13.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 565 - type: solid_wall - components: - - parent: 216 - pos: -12.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 566 - type: solid_wall - components: - - parent: 216 - pos: -11.5,-8.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 567 - type: AirlockExternal - components: - - parent: 216 - pos: -13.5,4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 568 - type: AirlockExternal - components: - - parent: 216 - pos: -13.5,6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 569 - type: AirlockEngineering - components: - - parent: 216 - pos: -13.5,1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 570 - type: Table - components: - - parent: 216 - pos: -15.5,-5.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 571 - type: Table - components: - - parent: 216 - pos: -15.5,-1.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 572 - type: solid_wall - components: - - parent: 216 - pos: 23.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 573 - type: solid_wall - components: - - parent: 216 - pos: 24.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 574 - type: solid_wall - components: - - parent: 216 - pos: 25.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 575 - type: Table - components: - - parent: 216 - pos: -15.5,-0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 576 - type: Catwalk - components: - - parent: 216 - pos: -14.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 577 - type: Catwalk - components: - - parent: 216 - pos: -13.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 578 - type: Catwalk - components: - - parent: 216 - pos: -12.5,7.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 579 - type: LockerToolFilled - components: - - parent: 216 - pos: -11.5,-5.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 580 - type: LockerToolFilled - components: - - parent: 216 - pos: -11.5,-4.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 581 - type: LockerToolFilled - components: - - parent: 216 - pos: -11.5,-3.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 582 - type: LockerToolFilled - components: - - parent: 216 - pos: -11.5,-2.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 583 - type: LockerToolFilled - components: - - parent: 216 - pos: -11.5,-1.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics - - IsPlaceable: False - type: PlaceableSurface - - containers: - EntityStorageComponent: - type: Robust.Server.GameObjects.Components.Container.Container - type: ContainerContainer -- uid: 584 - type: GlassStack - components: - - parent: 216 - pos: -15.5,-3.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 585 - type: AirlockEngineering - components: - - parent: 216 - pos: -10.5,-6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 586 - type: Autolathe - components: - - parent: 216 - pos: -14.5,-7.5 - rot: -1.5707963267949 rad - type: Transform - - recipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - Crowbar - - Multitool - type: LatheDatabase -- uid: 587 - type: Autolathe - components: - - parent: 216 - pos: -13.5,-7.5 - rot: -1.5707963267949 rad - type: Transform - - recipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - Crowbar - - Multitool - type: LatheDatabase -- uid: 588 - type: Autolathe - components: - - parent: 216 - pos: -12.5,-7.5 - rot: -1.5707963267949 rad - type: Transform - - recipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - Crowbar - - Multitool - type: LatheDatabase -- uid: 589 - type: Poweredlight - components: - - parent: 216 - pos: -11,-5.5 - rot: 3.14159265358979 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 590 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 590 - type: LightTube - components: - - parent: 589 - type: Transform - - anchored: False - type: Physics -- uid: 591 - type: Poweredlight - components: - - parent: 216 - pos: -11,-0.5 - rot: 3.14159265358979 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 592 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 592 - type: LightTube - components: - - parent: 591 - type: Transform - - anchored: False - type: Physics -- uid: 593 - type: Poweredlight - components: - - parent: 216 - pos: -16,-0.5 - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 594 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 594 - type: LightTube - components: - - parent: 593 - type: Transform - - anchored: False - type: Physics -- uid: 595 - type: Poweredlight - components: - - parent: 216 - pos: -16,-5.5 - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 596 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 596 - type: LightTube - components: - - parent: 595 - type: Transform - - anchored: False - type: Physics -- uid: 597 - type: Poweredlight - components: - - parent: 216 - pos: -15,3.5 - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 598 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 598 - type: LightTube - components: - - parent: 597 - type: Transform - - anchored: False - type: Physics -- uid: 599 - type: PoweredSmallLight - components: - - parent: 216 - pos: -14.5,7 - rot: -1.5707963267949 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 600 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 600 - type: LightBulb - components: - - parent: 599 - type: Transform - - anchored: False - type: Physics -- uid: 601 - type: ApcExtensionCableStack1 - components: - - parent: 216 - pos: -15.5,-0.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 602 - type: ApcExtensionCableStack1 - components: - - parent: 216 - pos: -15.5,-0.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 603 - type: solid_wall - components: - - parent: 216 - pos: 26.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 604 - type: solid_wall - components: - - parent: 216 - pos: 27.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 605 - type: Catwalk - components: - - parent: 216 - pos: -11.5,-6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 606 - type: Catwalk - components: - - parent: 216 - pos: -9.5,-6.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 607 - type: Poweredlight - components: - - parent: 216 - pos: -10.5,4 - rot: -1.5707963267949 rad - type: Transform - - color: '#FFFFFFFF' - type: PointLight - - containers: - light_bulb: - entities: - - 608 - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 608 - type: LightTube - components: - - parent: 607 - type: Transform - - anchored: False - type: Physics -- uid: 609 - type: MetalStack - components: - - parent: 216 - pos: -15.5,-4.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 610 - type: MetalStack - components: - - parent: 216 - pos: -15.5,-4.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 611 - type: solid_wall - components: - - parent: 216 - pos: 7.5,-4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 612 - type: solid_wall - components: - - parent: 216 - pos: 8.5,-4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 613 - type: solid_wall - components: - - parent: 216 - pos: 28.5,11.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 614 - type: solid_wall - components: - - parent: 216 - pos: 28.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 615 - type: solid_wall - components: - - parent: 216 - pos: 28.5,9.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 616 - type: solid_wall - components: - - parent: 216 - pos: 28.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 617 - type: solid_wall - components: - - parent: 216 - pos: 28.5,7.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 618 - type: solid_wall - components: - - parent: 216 - pos: 28.5,6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 619 - type: solid_wall - components: - - parent: 216 - pos: 28.5,5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 620 - type: solid_wall - components: - - parent: 216 - pos: 26.5,-2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 621 - type: solid_wall - components: - - parent: 216 - pos: 26.5,-1.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 622 - type: solid_wall - components: - - parent: 216 - pos: 25.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 623 - type: solid_wall - components: - - parent: 216 - pos: 26.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 624 - type: solid_wall - components: - - parent: 216 - pos: 27.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 625 - type: Table - components: - - parent: 216 - pos: -15.5,-4.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 626 - type: Table - components: - - parent: 216 - pos: -15.5,-3.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 627 - type: Table - components: - - parent: 216 - pos: -15.5,0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 628 - type: ApcExtensionCableStack1 - components: - - parent: 216 - pos: -15.5,0.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 629 - type: ApcExtensionCableStack1 - components: - - parent: 216 - pos: -15.5,0.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 630 - type: GlassStack - components: - - parent: 216 - pos: -15.5,-1.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 631 - type: GlassStack - components: - - parent: 216 - pos: -15.5,-1.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 632 - type: GlassStack - components: - - parent: 216 - pos: -15.5,-3.5 - rot: -1.5707963267949 rad - type: Transform - - anchored: False - type: Physics -- uid: 633 - type: VendingMachineEngivend - components: - - parent: 216 - pos: -11.5,0.5 - rot: -1.5707963267949 rad - type: Transform -- uid: 634 - type: Table - components: - - parent: 216 - pos: 18.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 635 - type: Table - components: - - parent: 216 - pos: 21.5,6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 636 - type: Table - components: - - parent: 216 - pos: 20.5,6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 637 - type: Table - components: - - parent: 216 - pos: 18.5,6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 638 - type: Table - components: - - parent: 216 - pos: 19.5,6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 639 - type: Table - components: - - parent: 216 - pos: 18.5,5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 640 - type: Table - components: - - parent: 216 - pos: 22.5,8.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 641 - type: Table - components: - - parent: 216 - pos: 24.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 642 - type: ChairOfficeLight - components: - - parent: 216 - pos: 19.5,4.5 - rot: 3.141592653589793 rad - type: Transform -- uid: 643 - type: ChairOfficeLight - components: - - parent: 216 - pos: 20.5,5.5 - rot: 1.5707963267948966 rad - type: Transform -- uid: 644 - type: ChairOfficeLight - components: - - parent: 216 - pos: 23.5,8.5 - rot: 3.141592653589793 rad - type: Transform -- uid: 645 - type: ChairOfficeLight - components: - - parent: 216 - pos: 24.5,5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 646 - type: Chair - components: - - parent: 216 - pos: 14.5,6.5 - type: Transform -- uid: 647 - type: Chair - components: - - parent: 216 - pos: 14.5,8.5 - type: Transform -- uid: 648 - type: Chair - components: - - parent: 216 - pos: 14.5,7.5 - type: Transform -- uid: 649 - type: chem_dispenser - components: - - parent: 216 - pos: 23.5,9.5 - type: Transform - - containers: - ReagentDispenser-reagentContainerContainer: - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 650 - type: Table - components: - - parent: 216 - pos: 23.5,7.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 651 - type: Catwalk - components: - - parent: 216 - pos: 0.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 652 - type: Table - components: - - parent: 216 - pos: 25.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 653 - type: Table - components: - - parent: 216 - pos: 23.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 654 - type: Table - components: - - parent: 216 - pos: 24.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 655 - type: Table - components: - - parent: 216 - pos: 26.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 656 - type: Table - components: - - parent: 216 - pos: 27.5,10.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 657 - type: ComputerMedicalRecords - components: - - parent: 216 - pos: 21.5,5.5 - rot: 3.141592653589793 rad - type: Transform - - deadThreshold: 100 - type: BreakableConstruction -- uid: 658 - type: MedicalScanner - components: - - parent: 216 - pos: 18.5,-1.5 - rot: 3.141592653589793 rad - type: Transform - - containers: - MedicalScanner-bodyContainer: - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 659 - type: MedicalScanner - components: - - parent: 216 - pos: 18.5,-5.5 - rot: 3.141592653589793 rad - type: Transform - - containers: - MedicalScanner-bodyContainer: - type: Content.Server.GameObjects.ContainerSlot - type: ContainerContainer -- uid: 660 - type: Table - components: - - parent: 216 - pos: 13.5,2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 661 - type: Table - components: - - parent: 216 - pos: 13.5,0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 662 - type: Table - components: - - parent: 216 - pos: 13.5,1.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 663 - type: solid_wall - components: - - parent: 216 - pos: 22.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 664 - type: solid_wall - components: - - parent: 216 - pos: 17.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 665 - type: solid_wall - components: - - parent: 216 - pos: 18.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 666 - type: solid_wall - components: - - parent: 216 - pos: 20.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 667 - type: solid_wall - components: - - parent: 216 - pos: 21.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 668 - type: solid_wall - components: - - parent: 216 - pos: 19.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 669 - type: solid_wall - components: - - parent: 216 - pos: 14.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 670 - type: solid_wall - components: - - parent: 216 - pos: 13.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 671 - type: solid_wall - components: - - parent: 216 - pos: 12.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 672 - type: solid_wall - components: - - parent: 216 - pos: 12.5,2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 673 - type: solid_wall - components: - - parent: 216 - pos: 12.5,1.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 674 - type: solid_wall - components: - - parent: 216 - pos: 12.5,0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 675 - type: solid_wall - components: - - parent: 216 - pos: 12.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 676 - type: solid_wall - components: - - parent: 216 - pos: 13.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 677 - type: solid_wall - components: - - parent: 216 - pos: 14.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 678 - type: solid_wall - components: - - parent: 216 - pos: 13.5,-1.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 679 - type: solid_wall - components: - - parent: 216 - pos: 13.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 680 - type: solid_wall - components: - - parent: 216 - pos: 13.5,-5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 681 - type: solid_wall - components: - - parent: 216 - pos: 13.5,-4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 682 - type: solid_wall - components: - - parent: 216 - pos: 13.5,-3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 683 - type: solid_wall - components: - - parent: 216 - pos: 13.5,-2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 684 - type: solid_wall - components: - - parent: 216 - pos: 14.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 685 - type: solid_wall - components: - - parent: 216 - pos: 15.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 686 - type: solid_wall - components: - - parent: 216 - pos: 16.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 687 - type: solid_wall - components: - - parent: 216 - pos: 17.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 688 - type: solid_wall - components: - - parent: 216 - pos: 18.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 689 - type: solid_wall - components: - - parent: 216 - pos: 19.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 690 - type: solid_wall - components: - - parent: 216 - pos: 20.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 691 - type: solid_wall - components: - - parent: 216 - pos: 21.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 692 - type: solid_wall - components: - - parent: 216 - pos: 22.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 693 - type: solid_wall - components: - - parent: 216 - pos: 23.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 694 - type: solid_wall - components: - - parent: 216 - pos: 24.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 695 - type: solid_wall - components: - - parent: 216 - pos: 25.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 696 - type: solid_wall - components: - - parent: 216 - pos: 26.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 697 - type: solid_wall - components: - - parent: 216 - pos: 26.5,-5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 698 - type: solid_wall - components: - - parent: 216 - pos: 26.5,-4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 699 - type: solid_wall - components: - - parent: 216 - pos: 27.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 700 - type: solid_wall - components: - - parent: 216 - pos: 28.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 701 - type: solid_wall - components: - - parent: 216 - pos: 29.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 702 - type: solid_wall - components: - - parent: 216 - pos: 30.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 703 - type: solid_wall - components: - - parent: 216 - pos: 31.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 704 - type: solid_wall - components: - - parent: 216 - pos: 32.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 705 - type: solid_wall - components: - - parent: 216 - pos: 33.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 706 - type: solid_wall - components: - - parent: 216 - pos: 34.5,-6.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 707 - type: solid_wall - components: - - parent: 216 - pos: 34.5,-5.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 708 - type: solid_wall - components: - - parent: 216 - pos: 34.5,-4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 709 - type: solid_wall - components: - - parent: 216 - pos: 34.5,-3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 710 - type: solid_wall - components: - - parent: 216 - pos: 34.5,-2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 711 - type: solid_wall - components: - - parent: 216 - pos: 34.5,-1.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 712 - type: solid_wall - components: - - parent: 216 - pos: 34.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 713 - type: solid_wall - components: - - parent: 216 - pos: 33.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 714 - type: solid_wall - components: - - parent: 216 - pos: 32.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 715 - type: solid_wall - components: - - parent: 216 - pos: 31.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 716 - type: solid_wall - components: - - parent: 216 - pos: 30.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 717 - type: solid_wall - components: - - parent: 216 - pos: 29.5,-0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 718 - type: solid_wall - components: - - parent: 216 - pos: 30.5,0.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 719 - type: solid_wall - components: - - parent: 216 - pos: 30.5,1.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 720 - type: solid_wall - components: - - parent: 216 - pos: 30.5,2.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 721 - type: solid_wall - components: - - parent: 216 - pos: 30.5,3.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 722 - type: solid_wall - components: - - parent: 216 - pos: 30.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 723 - type: solid_wall - components: - - parent: 216 - pos: 29.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 724 - type: solid_wall - components: - - parent: 216 - pos: 28.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -- uid: 725 - type: solid_wall - components: - - parent: 216 - pos: 27.5,4.5 - rot: -1.5707963267948966 rad - type: Transform -... From 3be3b7da6402d35fd71e741d471dc70af6248429 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 4 Feb 2021 15:29:47 +0100 Subject: [PATCH 61/61] Fix suspicion timer going negative and counting up. The sign was never shown but it's still silly. --- .../UserInterface/Suspicion/SuspicionGui.xaml.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml.cs b/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml.cs index a9694fbe20..1ac94bc906 100644 --- a/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml.cs +++ b/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using Content.Client.GameObjects.Components.Suspicion; @@ -92,7 +93,11 @@ namespace Content.Client.UserInterface.Suspicion } else { - var diff = _timing.CurTime - endTime.Value; + var diff = endTime.Value - _timing.CurTime; + if (diff < TimeSpan.Zero) + { + diff = TimeSpan.Zero; + } TimerLabel.Visible = true; TimerLabel.Text = $"{diff:mm\\:ss}"; }