From f64505bea1b6dd11edf5cc37a3875823fbeb7f33 Mon Sep 17 00:00:00 2001 From: kosticia Date: Tue, 17 Jun 2025 03:08:08 +0300 Subject: [PATCH] predicted hyposprays (#38046) * G O I D A * how * now proper * a * Update HypospraySystem.cs * good catch * Apply suggestions from code review --------- Co-authored-by: ScarKy0 Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- ...tem.cs => HyposprayStatusControlSystem.cs} | 3 +- .../Components/HyposprayComponent.cs | 18 ++- .../EntitySystems/HypospraySystem.cs | 143 ++++++++++++------ .../EntitySystems/SharedHypospraySystem.cs | 61 -------- 4 files changed, 115 insertions(+), 110 deletions(-) rename Content.Client/Chemistry/EntitySystems/{HypospraySystem.cs => HyposprayStatusControlSystem.cs} (70%) rename {Content.Server => Content.Shared}/Chemistry/EntitySystems/HypospraySystem.cs (65%) delete mode 100644 Content.Shared/Chemistry/EntitySystems/SharedHypospraySystem.cs diff --git a/Content.Client/Chemistry/EntitySystems/HypospraySystem.cs b/Content.Client/Chemistry/EntitySystems/HyposprayStatusControlSystem.cs similarity index 70% rename from Content.Client/Chemistry/EntitySystems/HypospraySystem.cs rename to Content.Client/Chemistry/EntitySystems/HyposprayStatusControlSystem.cs index ee7aa3aafe..4dfc8506d2 100644 --- a/Content.Client/Chemistry/EntitySystems/HypospraySystem.cs +++ b/Content.Client/Chemistry/EntitySystems/HyposprayStatusControlSystem.cs @@ -5,8 +5,9 @@ using Content.Shared.Chemistry.EntitySystems; namespace Content.Client.Chemistry.EntitySystems; -public sealed class HypospraySystem : SharedHypospraySystem +public sealed class HyposprayStatusControlSystem : EntitySystem { + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!; public override void Initialize() { base.Initialize(); diff --git a/Content.Shared/Chemistry/Components/HyposprayComponent.cs b/Content.Shared/Chemistry/Components/HyposprayComponent.cs index 1e3a4751f4..ca20e1c22f 100644 --- a/Content.Shared/Chemistry/Components/HyposprayComponent.cs +++ b/Content.Shared/Chemistry/Components/HyposprayComponent.cs @@ -1,20 +1,32 @@ using Content.Shared.FixedPoint; using Robust.Shared.GameStates; -using Robust.Shared.Serialization; using Robust.Shared.Audio; namespace Content.Shared.Chemistry.Components; -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +/// +/// Component that allows an entity instantly transfer liquids by interacting with objects that have solutions. +/// +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState] public sealed partial class HyposprayComponent : Component { + /// + /// Solution that will be used by hypospray for injections. + /// [DataField] public string SolutionName = "hypospray"; + /// + /// Amount of the units that will be transfered. + /// + [AutoNetworkedField] [DataField] - [ViewVariables(VVAccess.ReadWrite)] public FixedPoint2 TransferAmount = FixedPoint2.New(5); + /// + /// Sound that will be played when injecting. + /// [DataField] public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg"); diff --git a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs b/Content.Shared/Chemistry/EntitySystems/HypospraySystem.cs similarity index 65% rename from Content.Server/Chemistry/EntitySystems/HypospraySystem.cs rename to Content.Shared/Chemistry/EntitySystems/HypospraySystem.cs index 19ad64d046..97cf9396d2 100644 --- a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/HypospraySystem.cs @@ -1,26 +1,30 @@ -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Chemistry.Components; +using Content.Shared.Administration.Logs; using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Hypospray.Events; -using Content.Shared.Chemistry; 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.Interaction; using Content.Shared.Mobs.Components; +using Content.Shared.Popups; using Content.Shared.Timing; +using Content.Shared.Verbs; using Content.Shared.Weapons.Melee.Events; -using Content.Server.Body.Components; -using System.Linq; -using Robust.Server.Audio; +using Robust.Shared.Audio.Systems; -namespace Content.Server.Chemistry.EntitySystems; +namespace Content.Shared.Chemistry.EntitySystems; -public sealed class HypospraySystem : SharedHypospraySystem +public sealed class HypospraySystem : EntitySystem { - [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly ReactiveSystem _reactiveSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!; + [Dependency] private readonly UseDelaySystem _useDelay = default!; public override void Initialize() { @@ -29,21 +33,10 @@ public sealed class HypospraySystem : SharedHypospraySystem SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent(OnAttack); SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent>(AddToggleModeVerb); } - private bool TryUseHypospray(Entity entity, EntityUid target, EntityUid user) - { - // if target is ineligible but is a container, try to draw from the container if allowed - if (entity.Comp.CanContainerDraw - && !EligibleEntity(target, EntityManager, entity) - && _solutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _)) - { - return TryDraw(entity, target, drawableSolution.Value, user); - } - - return TryDoInject(entity, target, user); - } - + #region Ref events private void OnUseInHand(Entity entity, ref UseInHandEvent args) { if (args.Handled) @@ -52,7 +45,7 @@ public sealed class HypospraySystem : SharedHypospraySystem args.Handled = TryDoInject(entity, args.User, args.User); } - public void OnAfterInteract(Entity entity, ref AfterInteractEvent args) + private void OnAfterInteract(Entity entity, ref AfterInteractEvent args) { if (args.Handled || !args.CanReach || args.Target == null) return; @@ -60,19 +53,35 @@ public sealed class HypospraySystem : SharedHypospraySystem args.Handled = TryUseHypospray(entity, args.Target.Value, args.User); } - public void OnAttack(Entity entity, ref MeleeHitEvent args) + private void OnAttack(Entity entity, ref MeleeHitEvent args) { - if (!args.HitEntities.Any()) + if (args.HitEntities is []) return; - TryDoInject(entity, args.HitEntities.First(), args.User); + TryDoInject(entity, args.HitEntities[0], args.User); + } + + #endregion + + #region Draw/Inject + private bool TryUseHypospray(Entity entity, EntityUid target, EntityUid user) + { + // if target is ineligible but is a container, try to draw from the container if allowed + if (entity.Comp.CanContainerDraw + && !EligibleEntity(target, entity) + && _solutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _)) + { + return TryDraw(entity, target, drawableSolution.Value, user); + } + + return TryDoInject(entity, target, user); } public bool TryDoInject(Entity entity, EntityUid target, EntityUid user) { var (uid, component) = entity; - if (!EligibleEntity(target, EntityManager, component)) + if (!EligibleEntity(target, component)) return false; if (TryComp(uid, out UseDelayComponent? delayComp)) @@ -89,13 +98,13 @@ public sealed class HypospraySystem : SharedHypospraySystem if (selfEvent.Cancelled) { - _popup.PopupEntity(Loc.GetString(selfEvent.InjectMessageOverride ?? "hypospray-cant-inject", ("owner", Identity.Entity(target, EntityManager))), target, user); + _popup.PopupClient(Loc.GetString(selfEvent.InjectMessageOverride ?? "hypospray-cant-inject", ("owner", Identity.Entity(target, EntityManager))), target, user); return false; } target = selfEvent.TargetGettingInjected; - if (!EligibleEntity(target, EntityManager, component)) + if (!EligibleEntity(target, component)) return false; // Target event @@ -104,13 +113,13 @@ public sealed class HypospraySystem : SharedHypospraySystem if (targetEvent.Cancelled) { - _popup.PopupEntity(Loc.GetString(targetEvent.InjectMessageOverride ?? "hypospray-cant-inject", ("owner", Identity.Entity(target, EntityManager))), target, user); + _popup.PopupClient(Loc.GetString(targetEvent.InjectMessageOverride ?? "hypospray-cant-inject", ("owner", Identity.Entity(target, EntityManager))), target, user); return false; } target = targetEvent.TargetGettingInjected; - if (!EligibleEntity(target, EntityManager, component)) + if (!EligibleEntity(target, component)) return false; // The target event gets priority for the overriden message. @@ -123,17 +132,17 @@ public sealed class HypospraySystem : SharedHypospraySystem 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); + _popup.PopupClient(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); + _popup.PopupClient(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); + _popup.PopupClient(Loc.GetString(msgFormat ?? "hypospray-component-inject-other-message", ("other", target)), target, user); if (target != user) { @@ -142,7 +151,7 @@ public sealed class HypospraySystem : SharedHypospraySystem // meleeSys.SendLunge(angle, user); } - _audio.PlayPvs(component.InjectSound, user); + _audio.PlayPredicted(component.InjectSound, target, 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 @@ -154,7 +163,7 @@ public sealed class HypospraySystem : SharedHypospraySystem if (realTransferAmount <= 0) { - _popup.PopupEntity(Loc.GetString("hypospray-component-transfer-already-full-message", ("owner", target)), target, user); + _popup.PopupClient(Loc.GetString("hypospray-component-transfer-already-full-message", ("owner", target)), target, user); return true; } @@ -175,7 +184,7 @@ public sealed class HypospraySystem : SharedHypospraySystem return true; } - private bool TryDraw(Entity entity, Entity target, Entity targetSolution, EntityUid user) + private bool TryDraw(Entity entity, EntityUid target, Entity targetSolution, EntityUid user) { if (!_solutionContainers.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln, out var solution) || solution.AvailableVolume == 0) @@ -189,34 +198,78 @@ public sealed class HypospraySystem : SharedHypospraySystem if (realTransferAmount <= 0) { - _popup.PopupEntity( + _popup.PopupClient( Loc.GetString("injector-component-target-is-empty-message", ("target", Identity.Entity(target, EntityManager))), entity.Owner, user); return false; } - var removedSolution = _solutionContainers.Draw(target.Owner, targetSolution, realTransferAmount); + var removedSolution = _solutionContainers.Draw(target, targetSolution, realTransferAmount); if (!_solutionContainers.TryAddSolution(soln.Value, removedSolution)) { return false; } - _popup.PopupEntity(Loc.GetString("injector-component-draw-success-message", + _popup.PopupClient(Loc.GetString("injector-component-draw-success-message", ("amount", removedSolution.Volume), ("target", Identity.Entity(target, EntityManager))), entity.Owner, user); return true; } - private bool EligibleEntity(EntityUid entity, IEntityManager entMan, HyposprayComponent component) + private bool EligibleEntity(EntityUid entity, 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); + ? HasComp(entity) && + HasComp(entity) + : HasComp(entity); } + + #endregion + + #region Verbs + + // + // 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 || entity.Comp.InjectOnly) + return; + + 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); + var msg = (entity.Comp.OnlyAffectsMobs && entity.Comp.CanContainerDraw) ? "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); + } + + #endregion } diff --git a/Content.Shared/Chemistry/EntitySystems/SharedHypospraySystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedHypospraySystem.cs deleted file mode 100644 index 6985ae693c..0000000000 --- a/Content.Shared/Chemistry/EntitySystems/SharedHypospraySystem.cs +++ /dev/null @@ -1,61 +0,0 @@ -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 || entity.Comp.InjectOnly) - 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 && entity.Comp.CanContainerDraw) ? "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); - } -} -- 2.51.2