diff --git a/Content.Client/Chemistry/Components/HyposprayComponent.cs b/Content.Client/Chemistry/Components/HyposprayComponent.cs deleted file mode 100644 index 705b79ad84..0000000000 --- a/Content.Client/Chemistry/Components/HyposprayComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Chemistry.Components; -using Content.Shared.FixedPoint; - -namespace Content.Client.Chemistry.Components -{ - [RegisterComponent] - public sealed partial class HyposprayComponent : SharedHyposprayComponent - { - [ViewVariables] - public FixedPoint2 CurrentVolume; - [ViewVariables] - public FixedPoint2 TotalVolume; - [ViewVariables(VVAccess.ReadWrite)] - public bool UiUpdateNeeded; - } -} diff --git a/Content.Client/Chemistry/EntitySystems/HypospraySystem.cs b/Content.Client/Chemistry/EntitySystems/HypospraySystem.cs new file mode 100644 index 0000000000..ee7aa3aafe --- /dev/null +++ b/Content.Client/Chemistry/EntitySystems/HypospraySystem.cs @@ -0,0 +1,15 @@ +using Content.Client.Chemistry.UI; +using Content.Client.Items; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; + +namespace Content.Client.Chemistry.EntitySystems; + +public sealed class HypospraySystem : SharedHypospraySystem +{ + public override void Initialize() + { + base.Initialize(); + Subs.ItemStatus(ent => new HyposprayStatusControl(ent, _solutionContainers)); + } +} diff --git a/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs index 12eb7f3d14..0131a283c8 100644 --- a/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs +++ b/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs @@ -1,4 +1,3 @@ -using Content.Client.Chemistry.Components; using Content.Client.Chemistry.UI; using Content.Client.Items; using Content.Shared.Chemistry.Components; @@ -13,17 +12,5 @@ public sealed class InjectorSystem : SharedInjectorSystem { base.Initialize(); Subs.ItemStatus(ent => new InjectorStatusControl(ent, SolutionContainers)); - SubscribeLocalEvent(OnHandleHyposprayState); - Subs.ItemStatus(ent => new HyposprayStatusControl(ent)); - } - - private void OnHandleHyposprayState(EntityUid uid, HyposprayComponent component, ref ComponentHandleState args) - { - if (args.Current is not HyposprayComponentState cState) - return; - - component.CurrentVolume = cState.CurVolume; - component.TotalVolume = cState.MaxVolume; - component.UiUpdateNeeded = true; } } diff --git a/Content.Client/Chemistry/UI/HyposprayStatusControl.cs b/Content.Client/Chemistry/UI/HyposprayStatusControl.cs index bd85cd546c..4a4d90dc4d 100644 --- a/Content.Client/Chemistry/UI/HyposprayStatusControl.cs +++ b/Content.Client/Chemistry/UI/HyposprayStatusControl.cs @@ -1,6 +1,8 @@ -using Content.Client.Chemistry.Components; using Content.Client.Message; using Content.Client.Stylesheets; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.FixedPoint; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Timing; @@ -9,34 +11,48 @@ namespace Content.Client.Chemistry.UI; public sealed class HyposprayStatusControl : Control { - private readonly HyposprayComponent _parent; + private readonly Entity _parent; private readonly RichTextLabel _label; + private readonly SharedSolutionContainerSystem _solutionContainers; - public HyposprayStatusControl(HyposprayComponent parent) + private FixedPoint2 PrevVolume; + private FixedPoint2 PrevMaxVolume; + private bool PrevOnlyAffectsMobs; + + public HyposprayStatusControl(Entity parent, SharedSolutionContainerSystem solutionContainers) { _parent = parent; - _label = new RichTextLabel {StyleClasses = {StyleNano.StyleClassItemStatus}}; + _solutionContainers = solutionContainers; + _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } }; AddChild(_label); - - Update(); } protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); - if (!_parent.UiUpdateNeeded) + + if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution)) return; - Update(); - } - public void Update() - { + // only updates the UI if any of the details are different than they previously were + if (PrevVolume == solution.Volume + && PrevMaxVolume == solution.MaxVolume + && PrevOnlyAffectsMobs == _parent.Comp.OnlyAffectsMobs) + return; - _parent.UiUpdateNeeded = false; + PrevVolume = solution.Volume; + PrevMaxVolume = solution.MaxVolume; + PrevOnlyAffectsMobs = _parent.Comp.OnlyAffectsMobs; - _label.SetMarkup(Loc.GetString( - "hypospray-volume-text", - ("currentVolume", _parent.CurrentVolume), - ("totalVolume", _parent.TotalVolume))); + var modeStringLocalized = Loc.GetString(_parent.Comp.OnlyAffectsMobs switch + { + false => "hypospray-all-mode-text", + true => "hypospray-mobs-only-mode-text", + }); + + _label.SetMarkup(Loc.GetString("hypospray-volume-label", + ("currentVolume", solution.Volume), + ("totalVolume", solution.MaxVolume), + ("modeString", modeStringLocalized))); } } diff --git a/Content.Server/Body/Components/BloodstreamComponent.cs b/Content.Server/Body/Components/BloodstreamComponent.cs index d448c4aab2..1d8aa9ffd3 100644 --- a/Content.Server/Body/Components/BloodstreamComponent.cs +++ b/Content.Server/Body/Components/BloodstreamComponent.cs @@ -11,7 +11,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.Body.Components { - [RegisterComponent, Access(typeof(BloodstreamSystem), (typeof(ChemistrySystem)))] + [RegisterComponent, Access(typeof(BloodstreamSystem), typeof(ReactionMixerSystem))] public sealed partial class BloodstreamComponent : Component { public static string DefaultChemicalsSolutionName = "chemicals"; diff --git a/Content.Server/Chemistry/Components/HyposprayComponent.cs b/Content.Server/Chemistry/Components/HyposprayComponent.cs deleted file mode 100644 index 2a80cec801..0000000000 --- a/Content.Server/Chemistry/Components/HyposprayComponent.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Chemistry.Components; -using Content.Shared.FixedPoint; -using Robust.Shared.Audio; - -namespace Content.Server.Chemistry.Components -{ - [RegisterComponent] - public sealed partial class HyposprayComponent : SharedHyposprayComponent - { - // TODO: This should be on clumsycomponent. - [DataField("clumsyFailChance")] - [ViewVariables(VVAccess.ReadWrite)] - public float ClumsyFailChance = 0.5f; - - [DataField("transferAmount")] - [ViewVariables(VVAccess.ReadWrite)] - public FixedPoint2 TransferAmount = FixedPoint2.New(5); - - [DataField("injectSound")] - public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg"); - - /// - /// Whether or not the hypo is able to inject only into mobs. On false you can inject into beakers/jugs - /// - [DataField("onlyMobs")] - public bool OnlyMobs = true; - } -} diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs deleted file mode 100644 index c4f22dc63a..0000000000 --- a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Content.Server.Administration.Logs; -using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Server.Interaction; -using Content.Server.Popups; -using Content.Shared.Chemistry; -using Robust.Shared.Audio.Systems; - -namespace Content.Server.Chemistry.EntitySystems; - -public sealed partial class ChemistrySystem : EntitySystem -{ - [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly InteractionSystem _interaction = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly ReactiveSystem _reactiveSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SolutionContainerSystem _solutionContainers = default!; - - public override void Initialize() - { - // Why ChemMaster duplicates reagentdispenser nobody knows. - InitializeHypospray(); - InitializeMixing(); - } -} diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs deleted file mode 100644 index be8faec984..0000000000 --- a/Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Content.Server.Chemistry.Components; -using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.Components.SolutionManager; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Database; -using Content.Shared.FixedPoint; -using Content.Shared.Forensics; -using Content.Shared.IdentityManagement; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Mobs.Components; -using Content.Shared.Timing; -using Content.Shared.Weapons.Melee.Events; -using Robust.Shared.GameStates; -using System.Diagnostics.CodeAnalysis; -using System.Linq; - -namespace Content.Server.Chemistry.EntitySystems -{ - public sealed partial class ChemistrySystem - { - [Dependency] private readonly UseDelaySystem _useDelay = default!; - - private void InitializeHypospray() - { - SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent(OnAttack); - SubscribeLocalEvent(OnSolutionChange); - SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(OnHypoGetState); - } - - private void OnHypoGetState(Entity entity, ref ComponentGetState args) - { - args.State = _solutionContainers.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out _, out var solution) - ? new HyposprayComponentState(solution.Volume, solution.MaxVolume) - : new HyposprayComponentState(FixedPoint2.Zero, FixedPoint2.Zero); - } - - private void OnUseInHand(Entity entity, ref UseInHandEvent args) - { - if (args.Handled) - return; - - TryDoInject(entity, args.User, args.User); - args.Handled = true; - } - - private void OnSolutionChange(Entity entity, ref SolutionContainerChangedEvent args) - { - Dirty(entity); - } - - public void OnAfterInteract(Entity entity, ref AfterInteractEvent args) - { - if (!args.CanReach) - return; - - var target = args.Target; - var user = args.User; - - TryDoInject(entity, target, user); - } - - public void OnAttack(Entity entity, ref MeleeHitEvent args) - { - if (!args.HitEntities.Any()) - return; - - TryDoInject(entity, args.HitEntities.First(), args.User); - } - - public bool TryDoInject(Entity hypo, EntityUid? target, EntityUid user) - { - var (uid, component) = hypo; - - if (!EligibleEntity(target, _entMan, component)) - return false; - - if (TryComp(uid, out UseDelayComponent? delayComp)) - { - if (_useDelay.IsDelayed((uid, delayComp))) - return false; - } - - - string? msgFormat = null; - - if (target == user) - msgFormat = "hypospray-component-inject-self-message"; - else if (EligibleEntity(user, _entMan, component) && _interaction.TryRollClumsy(user, component.ClumsyFailChance)) - { - msgFormat = "hypospray-component-inject-self-clumsy-message"; - target = user; - } - - if (!_solutionContainers.TryGetSolution(uid, component.SolutionName, out var hypoSpraySoln, out var hypoSpraySolution) || hypoSpraySolution.Volume == 0) - { - _popup.PopupCursor(Loc.GetString("hypospray-component-empty-message"), user); - return true; - } - - if (!_solutionContainers.TryGetInjectableSolution(target.Value, out var targetSoln, out var targetSolution)) - { - _popup.PopupCursor(Loc.GetString("hypospray-cant-inject", ("target", Identity.Entity(target.Value, _entMan))), user); - return false; - } - - _popup.PopupCursor(Loc.GetString(msgFormat ?? "hypospray-component-inject-other-message", ("other", target)), user); - - if (target != user) - { - _popup.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target.Value, target.Value); - // TODO: This should just be using melee attacks... - // meleeSys.SendLunge(angle, user); - } - - _audio.PlayPvs(component.InjectSound, user); - - // Medipens and such use this system and don't have a delay, requiring extra checks - // BeginDelay function returns if item is already on delay - if (delayComp != null) - _useDelay.TryResetDelay((uid, delayComp)); - - // Get transfer amount. May be smaller than component.TransferAmount if not enough room - var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.AvailableVolume); - - if (realTransferAmount <= 0) - { - _popup.PopupCursor(Loc.GetString("hypospray-component-transfer-already-full-message", ("owner", target)), user); - return true; - } - - // Move units from attackSolution to targetSolution - var removedSolution = _solutionContainers.SplitSolution(hypoSpraySoln.Value, realTransferAmount); - - if (!targetSolution.CanAddSolution(removedSolution)) - return true; - _reactiveSystem.DoEntityReaction(target.Value, removedSolution, ReactionMethod.Injection); - _solutionContainers.TryAddSolution(targetSoln.Value, removedSolution); - - var ev = new TransferDnaEvent { Donor = target.Value, Recipient = uid }; - RaiseLocalEvent(target.Value, ref ev); - - // same LogType as syringes... - _adminLogger.Add(LogType.ForceFeed, $"{_entMan.ToPrettyString(user):user} injected {_entMan.ToPrettyString(target.Value):target} with a solution {SolutionContainerSystem.ToPrettyString(removedSolution):removedSolution} using a {_entMan.ToPrettyString(uid):using}"); - - return true; - } - - static bool EligibleEntity([NotNullWhen(true)] EntityUid? entity, IEntityManager entMan, HyposprayComponent component) - { - // 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. - // But this is 14, we dont do what SS13 does just because SS13 does it. - return component.OnlyMobs - ? entMan.HasComponent(entity) && - entMan.HasComponent(entity) - : entMan.HasComponent(entity); - } - } -} diff --git a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs b/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs new file mode 100644 index 0000000000..dfbe45c035 --- /dev/null +++ b/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs @@ -0,0 +1,197 @@ +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Database; +using Content.Shared.FixedPoint; +using Content.Shared.Forensics; +using Content.Shared.IdentityManagement; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Mobs.Components; +using Content.Shared.Timing; +using Content.Shared.Weapons.Melee.Events; +using Content.Server.Interaction; +using Content.Server.Body.Components; +using Content.Server.Chemistry.Containers.EntitySystems; +using Robust.Shared.GameStates; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Robust.Server.Audio; + +namespace Content.Server.Chemistry.EntitySystems; + +public sealed class HypospraySystem : SharedHypospraySystem +{ + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly InteractionSystem _interaction = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnAttack); + SubscribeLocalEvent(OnUseInHand); + } + + private void UseHypospray(Entity entity, EntityUid target, EntityUid user) + { + // if target is ineligible but is a container, try to draw from the container + if (!EligibleEntity(target, EntityManager, entity) + && _solutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _)) + { + TryDraw(entity, target, drawableSolution.Value, user); + } + + TryDoInject(entity, target, user); + } + + private void OnUseInHand(Entity entity, ref UseInHandEvent args) + { + if (args.Handled) + return; + + TryDoInject(entity, args.User, args.User); + args.Handled = true; + } + + public void OnAfterInteract(Entity entity, ref AfterInteractEvent args) + { + if (args.Handled || !args.CanReach || args.Target == null) + return; + + UseHypospray(entity, args.Target.Value, args.User); + args.Handled = true; + } + + public void OnAttack(Entity entity, ref MeleeHitEvent args) + { + if (!args.HitEntities.Any()) + return; + + TryDoInject(entity, args.HitEntities.First(), args.User); + } + + public bool TryDoInject(Entity entity, EntityUid target, EntityUid user) + { + var (uid, component) = entity; + + if (!EligibleEntity(target, EntityManager, component)) + return false; + + if (TryComp(uid, out UseDelayComponent? delayComp)) + { + if (_useDelay.IsDelayed((uid, delayComp))) + return false; + } + + string? msgFormat = null; + + if (target == user) + msgFormat = "hypospray-component-inject-self-message"; + else if (EligibleEntity(user, EntityManager, component) && _interaction.TryRollClumsy(user, component.ClumsyFailChance)) + { + msgFormat = "hypospray-component-inject-self-clumsy-message"; + target = user; + } + + if (!_solutionContainers.TryGetSolution(uid, component.SolutionName, out var hypoSpraySoln, out var hypoSpraySolution) || hypoSpraySolution.Volume == 0) + { + _popup.PopupEntity(Loc.GetString("hypospray-component-empty-message"), target, user); + return true; + } + + if (!_solutionContainers.TryGetInjectableSolution(target, out var targetSoln, out var targetSolution)) + { + _popup.PopupEntity(Loc.GetString("hypospray-cant-inject", ("target", Identity.Entity(target, EntityManager))), target, user); + return false; + } + + _popup.PopupEntity(Loc.GetString(msgFormat ?? "hypospray-component-inject-other-message", ("other", target)), target, user); + + if (target != user) + { + _popup.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, target); + // TODO: This should just be using melee attacks... + // meleeSys.SendLunge(angle, user); + } + + _audio.PlayPvs(component.InjectSound, user); + + // Medipens and such use this system and don't have a delay, requiring extra checks + // BeginDelay function returns if item is already on delay + if (delayComp != null) + _useDelay.TryResetDelay((uid, delayComp)); + + // Get transfer amount. May be smaller than component.TransferAmount if not enough room + var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.AvailableVolume); + + if (realTransferAmount <= 0) + { + _popup.PopupEntity(Loc.GetString("hypospray-component-transfer-already-full-message", ("owner", target)), target, user); + return true; + } + + // Move units from attackSolution to targetSolution + var removedSolution = _solutionContainers.SplitSolution(hypoSpraySoln.Value, realTransferAmount); + + if (!targetSolution.CanAddSolution(removedSolution)) + return true; + _reactiveSystem.DoEntityReaction(target, removedSolution, ReactionMethod.Injection); + _solutionContainers.TryAddSolution(targetSoln.Value, removedSolution); + + var ev = new TransferDnaEvent { Donor = target, Recipient = uid }; + RaiseLocalEvent(target, ref ev); + + // same LogType as syringes... + _adminLogger.Add(LogType.ForceFeed, $"{EntityManager.ToPrettyString(user):user} injected {EntityManager.ToPrettyString(target):target} with a solution {SolutionContainerSystem.ToPrettyString(removedSolution):removedSolution} using a {EntityManager.ToPrettyString(uid):using}"); + + return true; + } + + private void TryDraw(Entity entity, Entity target, Entity targetSolution, EntityUid user) + { + if (!_solutionContainers.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln, + out var solution) || solution.AvailableVolume == 0) + { + return; + } + + // Get transfer amount. May be smaller than _transferAmount if not enough room, also make sure there's room in the injector + var realTransferAmount = FixedPoint2.Min(entity.Comp.TransferAmount, targetSolution.Comp.Solution.Volume, + solution.AvailableVolume); + + if (realTransferAmount <= 0) + { + _popup.PopupEntity( + Loc.GetString("injector-component-target-is-empty-message", + ("target", Identity.Entity(target, EntityManager))), + entity.Owner, user); + return; + } + + var removedSolution = _solutionContainers.Draw(target.Owner, targetSolution, realTransferAmount); + + if (!_solutionContainers.TryAddSolution(soln.Value, removedSolution)) + { + return; + } + + _popup.PopupEntity(Loc.GetString("injector-component-draw-success-message", + ("amount", removedSolution.Volume), + ("target", Identity.Entity(target, EntityManager))), entity.Owner, user); + } + + private bool EligibleEntity(EntityUid entity, IEntityManager entMan, HyposprayComponent component) + { + // 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. + // But this is 14, we dont do what SS13 does just because SS13 does it. + return component.OnlyAffectsMobs + ? entMan.HasComponent(entity) && + entMan.HasComponent(entity) + : entMan.HasComponent(entity); + } +} diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystemMixer.cs b/Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs similarity index 76% rename from Content.Server/Chemistry/EntitySystems/ChemistrySystemMixer.cs rename to Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs index 0230671ec9..032374d4a5 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemistrySystemMixer.cs +++ b/Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs @@ -1,13 +1,20 @@ using Content.Shared.Chemistry.Reaction; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; +using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Popups; namespace Content.Server.Chemistry.EntitySystems; -public sealed partial class ChemistrySystem +public sealed partial class ReactionMixerSystem : EntitySystem { - public void InitializeMixing() + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainers = default!; + + public override void Initialize() { + base.Initialize(); + SubscribeLocalEvent(OnAfterInteract); } diff --git a/Content.Shared/Chemistry/Components/HyposprayComponent.cs b/Content.Shared/Chemistry/Components/HyposprayComponent.cs new file mode 100644 index 0000000000..05d202aaaa --- /dev/null +++ b/Content.Shared/Chemistry/Components/HyposprayComponent.cs @@ -0,0 +1,33 @@ +using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Audio; + +namespace Content.Shared.Chemistry.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class HyposprayComponent : Component +{ + [DataField] + public string SolutionName = "hypospray"; + + // TODO: This should be on clumsycomponent. + [DataField] + [ViewVariables(VVAccess.ReadWrite)] + public float ClumsyFailChance = 0.5f; + + [DataField] + [ViewVariables(VVAccess.ReadWrite)] + public FixedPoint2 TransferAmount = FixedPoint2.New(5); + + [DataField] + public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg"); + + /// + /// Decides whether you can inject everything or just mobs. + /// When you can only affect mobs, you're capable of drawing from beakers. + /// + [AutoNetworkedField] + [DataField(required: true)] + public bool OnlyAffectsMobs = false; +} diff --git a/Content.Shared/Chemistry/Components/SharedHyposprayComponent.cs b/Content.Shared/Chemistry/Components/SharedHyposprayComponent.cs deleted file mode 100644 index a8df6be109..0000000000 --- a/Content.Shared/Chemistry/Components/SharedHyposprayComponent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Content.Shared.FixedPoint; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; - -namespace Content.Shared.Chemistry.Components; - -[NetworkedComponent()] -public abstract partial class SharedHyposprayComponent : Component -{ - [DataField("solutionName")] - public string SolutionName = "hypospray"; -} - -[Serializable, NetSerializable] -public sealed class HyposprayComponentState : ComponentState -{ - public FixedPoint2 CurVolume { get; } - public FixedPoint2 MaxVolume { get; } - - public HyposprayComponentState(FixedPoint2 curVolume, FixedPoint2 maxVolume) - { - CurVolume = curVolume; - MaxVolume = maxVolume; - } -} diff --git a/Content.Shared/Chemistry/EntitySystems/SharedHypospraySystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedHypospraySystem.cs new file mode 100644 index 0000000000..f91e5621f0 --- /dev/null +++ b/Content.Shared/Chemistry/EntitySystems/SharedHypospraySystem.cs @@ -0,0 +1,61 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Timing; +using Content.Shared.Verbs; +using Content.Shared.Popups; +using Robust.Shared.Player; +using Content.Shared.Administration.Logs; + +namespace Content.Shared.Chemistry.EntitySystems; + +public abstract class SharedHypospraySystem : EntitySystem +{ + [Dependency] protected readonly UseDelaySystem _useDelay = default!; + [Dependency] protected readonly SharedPopupSystem _popup = default!; + [Dependency] protected readonly SharedSolutionContainerSystem _solutionContainers = default!; + [Dependency] protected readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] protected readonly ReactiveSystem _reactiveSystem = default!; + + public override void Initialize() + { + SubscribeLocalEvent>(AddToggleModeVerb); + } + + // + // Uses the OnlyMobs field as a check to implement the ability + // to draw from jugs and containers with the hypospray + // Toggleable to allow people to inject containers if they prefer it over drawing + // + private void AddToggleModeVerb(Entity entity, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || args.Hands == null) + return; + + var (_, component) = entity; + var user = args.User; + var verb = new AlternativeVerb + { + Text = Loc.GetString("hypospray-verb-mode-label"), + Act = () => + { + ToggleMode(entity, user); + } + }; + args.Verbs.Add(verb); + } + + private void ToggleMode(Entity entity, EntityUid user) + { + SetMode(entity, !entity.Comp.OnlyAffectsMobs); + string msg = entity.Comp.OnlyAffectsMobs ? "hypospray-verb-mode-inject-mobs-only" : "hypospray-verb-mode-inject-all"; + _popup.PopupClient(Loc.GetString(msg), entity, user); + } + + public void SetMode(Entity entity, bool onlyAffectsMobs) + { + if (entity.Comp.OnlyAffectsMobs == onlyAffectsMobs) + return; + + entity.Comp.OnlyAffectsMobs = onlyAffectsMobs; + Dirty(entity); + } +} diff --git a/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs index 7e41cb39bd..6c43c1d5f0 100644 --- a/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs @@ -37,10 +37,7 @@ public abstract class SharedInjectorSystem : EntitySystem if (!args.CanAccess || !args.CanInteract || args.Hands == null) return; - if (!HasComp(args.User)) - return; var user = args.User; - var (_, component) = entity; var min = component.MinimumTransferAmount; diff --git a/Resources/Locale/en-US/chemistry/components/hypospray-component.ftl b/Resources/Locale/en-US/chemistry/components/hypospray-component.ftl index 7acbe8664c..52dbf9010e 100644 --- a/Resources/Locale/en-US/chemistry/components/hypospray-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/hypospray-component.ftl @@ -1,13 +1,21 @@ ## UI -hypospray-volume-text = Volume: [color=white]{$currentVolume}/{$totalVolume}[/color] +hypospray-all-mode-text = Only Injects +hypospray-mobs-only-mode-text = Draws and Injects +hypospray-invalid-text = Invalid +hypospray-volume-label = Volume: [color=white]{$currentVolume}/{$totalVolume}u[/color] + Mode: [color=white]{$modeString}[/color] ## Entity hypospray-component-inject-other-message = You inject {$other}. hypospray-component-inject-self-message = You inject yourself. hypospray-component-inject-self-clumsy-message = Oops! You injected yourself. -hypospray-component-empty-message = It's empty! +hypospray-component-empty-message = Nothing to inject. hypospray-component-feel-prick-message = You feel a tiny prick! hypospray-component-transfer-already-full-message = {$owner} is already full! hypospray-cant-inject = Can't inject into {$target}! + +hypospray-verb-mode-label = Toggle Container Draw +hypospray-verb-mode-inject-all = You cannot draw from containers anymore. +hypospray-verb-mode-inject-mobs-only = You can now draw from containers. diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml index 3d28487d68..abcabd7481 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml @@ -18,7 +18,7 @@ - type: ExaminableSolution solution: hypospray - type: Hypospray - onlyMobs: false + onlyAffectsMobs: false - type: UseDelay delay: 0.5 - type: StaticPrice @@ -49,7 +49,7 @@ - type: ExaminableSolution solution: hypospray - type: Hypospray - onlyMobs: false + onlyAffectsMobs: false - type: UseDelay delay: 0.5 @@ -73,6 +73,7 @@ - type: ExaminableSolution solution: hypospray - type: Hypospray + onlyAffectsMobs: false - type: UseDelay delay: 0.5 @@ -113,6 +114,7 @@ - type: Hypospray solutionName: pen transferAmount: 15 + onlyAffectsMobs: false - type: Appearance - type: SolutionContainerVisuals maxFillLevels: 1 @@ -202,6 +204,7 @@ - type: Hypospray solutionName: pen transferAmount: 20 + onlyAffectsMobs: false - type: SolutionContainerManager solutions: pen: @@ -232,6 +235,7 @@ - type: Hypospray solutionName: pen transferAmount: 20 + onlyAffectsMobs: false - type: SolutionContainerManager solutions: pen: @@ -262,6 +266,7 @@ - type: Hypospray solutionName: pen transferAmount: 20 + onlyAffectsMobs: false - type: SolutionContainerManager solutions: pen: @@ -293,6 +298,7 @@ - type: Hypospray solutionName: pen transferAmount: 30 + onlyAffectsMobs: false - type: SolutionContainerManager solutions: pen: @@ -330,6 +336,7 @@ - type: Hypospray solutionName: pen transferAmount: 30 + onlyAffectsMobs: false - type: StaticPrice price: 500 - type: Tag @@ -389,6 +396,7 @@ - type: Hypospray solutionName: pen transferAmount: 30 + onlyAffectsMobs: false - type: StaticPrice price: 500 - type: Tag @@ -410,7 +418,7 @@ - type: ExaminableSolution solution: hypospray - type: Hypospray - onlyMobs: false + onlyAffectsMobs: false - type: UseDelay delay: 0.5 - type: StaticPrice # A new shitcurity meta