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);
- }
-}