diff --git a/Content.Server/Chemistry/Components/InjectorComponent.cs b/Content.Server/Chemistry/Components/InjectorComponent.cs
index 532bf4537b..79ac277de2 100644
--- a/Content.Server/Chemistry/Components/InjectorComponent.cs
+++ b/Content.Server/Chemistry/Components/InjectorComponent.cs
@@ -34,10 +34,8 @@ namespace Content.Server.Chemistry.Components
/// containers, and can directly inject into a mobs bloodstream.
///
[RegisterComponent]
- public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse
+ public class InjectorComponent : SharedInjectorComponent
{
- [Dependency] private readonly IEntityManager _entities = default!;
-
public const string SolutionName = "injector";
///
@@ -46,7 +44,7 @@ namespace Content.Server.Chemistry.Components
///
[ViewVariables]
[DataField("injectOnly")]
- private bool _injectOnly;
+ public bool InjectOnly;
///
/// Amount to inject or draw on each usage. If the injector is inject only, it will
@@ -54,7 +52,7 @@ namespace Content.Server.Chemistry.Components
///
[ViewVariables(VVAccess.ReadWrite)]
[DataField("transferAmount")]
- private FixedPoint2 _transferAmount = FixedPoint2.New(5);
+ public FixedPoint2 TransferAmount = FixedPoint2.New(5);
///
/// Injection delay (seconds) when the target is a mob.
@@ -90,331 +88,5 @@ namespace Content.Server.Chemistry.Components
Dirty();
}
}
-
- protected override void Startup()
- {
- base.Startup();
-
- Dirty();
- }
-
- ///
- /// Toggle between draw/inject state if applicable
- ///
- private void Toggle(EntityUid user)
- {
- if (_injectOnly)
- {
- return;
- }
-
- string msg;
- switch (ToggleState)
- {
- case InjectorToggleMode.Inject:
- ToggleState = InjectorToggleMode.Draw;
- msg = "injector-component-drawing-text";
- break;
- case InjectorToggleMode.Draw:
- ToggleState = InjectorToggleMode.Inject;
- msg = "injector-component-injecting-text";
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
-
- Owner.PopupMessage(user, Loc.GetString(msg));
- }
-
- ///
- /// Called when clicking on entities while holding in active hand
- ///
- ///
- async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
- {
- if (CancelToken != null)
- {
- CancelToken.Cancel();
- return true;
- }
-
- if (!eventArgs.CanReach)
- return false;
-
- var solutionsSys = EntitySystem.Get();
- //Make sure we have the attacking entity
- if (eventArgs.Target is not {Valid: true} target ||
- !_entities.HasComponent(Owner))
- {
- return false;
- }
-
- // Is the target a mob? If yes, use a do-after to give them time to respond.
- if (_entities.HasComponent(target) ||
- _entities.HasComponent(target))
- {
- if (!await TryInjectDoAfter(eventArgs.User, target))
- return true;
- }
-
- // Handle injecting/drawing for solutions
- if (ToggleState == InjectorToggleMode.Inject)
- {
- if (solutionsSys.TryGetInjectableSolution(target, out var injectableSolution))
- {
- TryInject(target, injectableSolution, eventArgs.User, false);
- }
- else if (solutionsSys.TryGetRefillableSolution(target, out var refillableSolution))
- {
- TryInject(target, refillableSolution, eventArgs.User, true);
- }
- else if (_entities.TryGetComponent(target, out BloodstreamComponent? bloodstream))
- {
- TryInjectIntoBloodstream(bloodstream, eventArgs.User);
- }
- else
- {
- eventArgs.User.PopupMessage(eventArgs.User,
- Loc.GetString("injector-component-cannot-transfer-message",
- ("target", target)));
- }
- }
- else if (ToggleState == InjectorToggleMode.Draw)
- {
- if (solutionsSys.TryGetDrawableSolution(target, out var drawableSolution))
- {
- TryDraw(target, drawableSolution, eventArgs.User);
- }
- else
- {
- eventArgs.User.PopupMessage(eventArgs.User,
- Loc.GetString("injector-component-cannot-draw-message",
- ("target", target)));
- }
- }
-
- return true;
- }
-
- ///
- /// Send informative pop-up messages and wait for a do-after to complete.
- ///
- public async Task TryInjectDoAfter(EntityUid user, EntityUid target)
- {
- var popupSys = EntitySystem.Get();
-
- // Create a pop-up for the user
- popupSys.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, Filter.Entities(user));
-
- if (!EntitySystem.Get().TryGetSolution(Owner, SolutionName, out var solution))
- return false;
-
- // Get entity for logging. Log with EntityUids when?
- var logSys = EntitySystem.Get();
-
- var actualDelay = MathF.Max(Delay, 1f);
- if (user != target)
- {
- // Create a pop-up for the target
- var userName = _entities.GetComponent(user).EntityName;
- popupSys.PopupEntity(Loc.GetString("injector-component-injecting-target",
- ("user", userName)), user, Filter.Entities(target));
-
- // Check if the target is incapacitated or in combat mode and modify time accordingly.
- if (_entities.TryGetComponent(target, out var mobState) &&
- mobState.IsIncapacitated())
- {
- actualDelay /= 2;
- }
- else if (_entities.TryGetComponent(target, out var combat) &&
- combat.IsInCombatMode)
- {
- // Slightly increase the delay when the target is in combat mode. Helps prevents cheese injections in
- // combat with fast syringes & lag.
- actualDelay += 1;
- }
-
- // Add an admin log, using the "force feed" log type. It's not quite feeding, but the effect is the same.
- if (ToggleState == InjectorToggleMode.Inject)
- {
- logSys.Add(LogType.ForceFeed,
- $"{_entities.ToPrettyString(user):user} is attempting to inject {_entities.ToPrettyString(target):target} with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}");
- // TODO solution pretty string.
- }
- }
- else
- {
- // Self-injections take half as long.
- actualDelay /= 2;
-
- if (ToggleState == InjectorToggleMode.Inject)
- logSys.Add(LogType.Ingestion,
- $"{_entities.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}.");
- //TODO solution pretty string.
- }
-
- CancelToken = new();
- var status = await EntitySystem.Get().WaitDoAfter(
- new DoAfterEventArgs(user, actualDelay, CancelToken.Token, target)
- {
- BreakOnUserMove = true,
- BreakOnDamage = true,
- BreakOnStun = true,
- BreakOnTargetMove = true,
- MovementThreshold = 1.0f
- });
- CancelToken = null;
-
- return status == DoAfterStatus.Finished;
- }
-
- ///
- /// Called when use key is pressed when held in active hand
- ///
- ///
- ///
- bool IUse.UseEntity(UseEntityEventArgs eventArgs)
- {
- Toggle(eventArgs.User);
- return true;
- }
-
- private void TryInjectIntoBloodstream(BloodstreamComponent targetBloodstream, EntityUid user)
- {
- // Get transfer amount. May be smaller than _transferAmount if not enough room
- var realTransferAmount = FixedPoint2.Min(_transferAmount, targetBloodstream.Solution.AvailableVolume);
-
- if (realTransferAmount <= 0)
- {
- Owner.PopupMessage(user,
- Loc.GetString("injector-component-cannot-inject-message", ("target", targetBloodstream.Owner)));
- return;
- }
-
- // Move units from attackSolution to targetSolution
- var removedSolution =
- EntitySystem.Get().SplitSolution(user, targetBloodstream.Solution, realTransferAmount);
-
- var bloodstreamSys = EntitySystem.Get();
- bloodstreamSys.TryAddToBloodstream((targetBloodstream).Owner, removedSolution, targetBloodstream);
-
- removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection);
-
- Owner.PopupMessage(user,
- Loc.GetString("injector-component-inject-success-message",
- ("amount", removedSolution.TotalVolume),
- ("target", targetBloodstream.Owner)));
- Dirty();
- AfterInject();
- }
-
- private void TryInject(EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill)
- {
- if (!EntitySystem.Get().TryGetSolution(Owner, SolutionName, out var solution)
- || solution.CurrentVolume == 0)
- {
- return;
- }
-
- // Get transfer amount. May be smaller than _transferAmount if not enough room
- var realTransferAmount = FixedPoint2.Min(_transferAmount, targetSolution.AvailableVolume);
-
- if (realTransferAmount <= 0)
- {
- Owner.PopupMessage(user,
- Loc.GetString("injector-component-target-already-full-message", ("target", targetEntity)));
- return;
- }
-
- // Move units from attackSolution to targetSolution
- var removedSolution = EntitySystem.Get().SplitSolution(Owner, solution, realTransferAmount);
-
- removedSolution.DoEntityReaction(targetEntity, ReactionMethod.Injection);
-
- if (!asRefill)
- {
- EntitySystem.Get()
- .Inject(targetEntity, targetSolution, removedSolution);
- }
- else
- {
- EntitySystem.Get()
- .Refill(targetEntity, targetSolution, removedSolution);
- }
-
- Owner.PopupMessage(user,
- Loc.GetString("injector-component-transfer-success-message",
- ("amount", removedSolution.TotalVolume),
- ("target", targetEntity)));
- Dirty();
- AfterInject();
- }
-
- private void AfterInject()
- {
- // Automatically set syringe to draw after completely draining it.
- if (EntitySystem.Get().TryGetSolution(Owner, SolutionName, out var solution)
- && solution.CurrentVolume == 0)
- {
- ToggleState = InjectorToggleMode.Draw;
- }
- }
-
- private void AfterDraw()
- {
- // Automatically set syringe to inject after completely filling it.
- if (EntitySystem.Get().TryGetSolution(Owner, SolutionName, out var solution)
- && solution.AvailableVolume == 0)
- {
- ToggleState = InjectorToggleMode.Inject;
- }
- }
-
- private void TryDraw(EntityUid targetEntity, Solution targetSolution, EntityUid user)
- {
- if (!EntitySystem.Get().TryGetSolution(Owner, SolutionName, out var solution)
- || solution.AvailableVolume == 0)
- {
- return;
- }
-
- // Get transfer amount. May be smaller than _transferAmount if not enough room
- var realTransferAmount = FixedPoint2.Min(_transferAmount, targetSolution.DrawAvailable);
-
- if (realTransferAmount <= 0)
- {
- Owner.PopupMessage(user,
- Loc.GetString("injector-component-target-is-empty-message", ("target", targetEntity)));
- return;
- }
-
- // Move units from attackSolution to targetSolution
- var removedSolution = EntitySystem.Get()
- .Draw(targetEntity, targetSolution, realTransferAmount);
-
- if (!EntitySystem.Get().TryAddSolution(targetEntity, solution, removedSolution))
- {
- return;
- }
-
- Owner.PopupMessage(user,
- Loc.GetString("injector-component-draw-success-message",
- ("amount", removedSolution.TotalVolume),
- ("target", targetEntity)));
- Dirty();
- AfterDraw();
- }
-
-
- public override ComponentState GetComponentState()
- {
- _entities.EntitySysManager.GetEntitySystem()
- .TryGetSolution(Owner, SolutionName, out var solution);
-
- var currentVolume = solution?.CurrentVolume ?? FixedPoint2.Zero;
- var maxVolume = solution?.MaxVolume ?? FixedPoint2.Zero;
-
- return new InjectorComponentState(currentVolume, maxVolume, ToggleState);
- }
}
}
diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs
new file mode 100644
index 0000000000..870278cac6
--- /dev/null
+++ b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs
@@ -0,0 +1,385 @@
+using System;
+using System.Threading;
+using Content.Server.Body.Components;
+using Content.Server.Chemistry.Components;
+using Content.Server.Chemistry.Components.SolutionManager;
+using Content.Server.CombatMode;
+using Content.Server.DoAfter;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Database;
+using Content.Shared.FixedPoint;
+using Content.Shared.Hands;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Helpers;
+using Content.Shared.MobState.Components;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameStates;
+using Robust.Shared.Localization;
+using Robust.Shared.Player;
+
+namespace Content.Server.Chemistry.EntitySystems;
+
+public sealed partial class ChemistrySystem
+{
+ private void InitializeInjector()
+ {
+ SubscribeLocalEvent(OnSolutionChange);
+ SubscribeLocalEvent(OnInjectorDeselected);
+ SubscribeLocalEvent(OnInjectorStartup);
+ SubscribeLocalEvent(OnInjectorUse);
+ SubscribeLocalEvent(OnInjectorAfterInteract);
+ SubscribeLocalEvent(OnInjectorGetState);
+
+ SubscribeLocalEvent(OnInjectionComplete);
+ SubscribeLocalEvent(OnInjectionCancelled);
+ }
+
+ private static void OnInjectionCancelled(InjectionCancelledEvent ev)
+ {
+ ev.Component.CancelToken = null;
+ }
+
+ private void OnInjectionComplete(InjectionCompleteEvent ev)
+ {
+ var component = ev.Component;
+ var user = ev.User;
+ var target = ev.Target;
+
+ component.CancelToken = null;
+
+ // Handle injecting/drawing for solutions
+ if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Inject)
+ {
+ if (_solutions.TryGetInjectableSolution(target, out var injectableSolution))
+ {
+ TryInject(component, target, injectableSolution, user, false);
+ }
+ else if (_solutions.TryGetRefillableSolution(target, out var refillableSolution))
+ {
+ TryInject(component, target, refillableSolution, user, true);
+ }
+ else if (TryComp(target, out var bloodstream))
+ {
+ TryInjectIntoBloodstream(component, bloodstream, user);
+ }
+ else
+ {
+ _popup.PopupEntity(Loc.GetString("injector-component-cannot-transfer-message",
+ ("target", target)), component.Owner, Filter.Entities(user));
+ }
+ }
+ else if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Draw)
+ {
+ if (_solutions.TryGetDrawableSolution(target, out var drawableSolution))
+ {
+ TryDraw(component, target, drawableSolution, user);
+ }
+ else
+ {
+ _popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message",
+ ("target", target)), component.Owner, Filter.Entities(user));
+ }
+ }
+ }
+
+ private static void OnInjectorDeselected(EntityUid uid, InjectorComponent component, HandDeselectedEvent args)
+ {
+ component.CancelToken?.Cancel();
+ component.CancelToken = null;
+ }
+
+ private void OnSolutionChange(EntityUid uid, InjectorComponent component, SolutionChangedEvent args)
+ {
+ Dirty(component);
+ }
+
+ private void OnInjectorGetState(EntityUid uid, InjectorComponent component, ref ComponentGetState args)
+ {
+ _solutions.TryGetSolution(uid, InjectorComponent.SolutionName, out var solution);
+
+ var currentVolume = solution?.CurrentVolume ?? FixedPoint2.Zero;
+ var maxVolume = solution?.MaxVolume ?? FixedPoint2.Zero;
+
+ args.State = new SharedInjectorComponent.InjectorComponentState(currentVolume, maxVolume, component.ToggleState);
+ }
+
+ private void OnInjectorAfterInteract(EntityUid uid, InjectorComponent component, AfterInteractEvent args)
+ {
+ if (args.Handled || !args.CanReach) return;
+
+ if (component.CancelToken != null)
+ {
+ component.CancelToken.Cancel();
+ component.CancelToken = null;
+ args.Handled = true;
+ return;
+ }
+
+ if (!_blocker.CanInteract(args.User))
+ return;
+
+ //Make sure we have the attacking entity
+ if (args.Target is not { Valid: true } target ||
+ !HasComp(uid))
+ {
+ return;
+ }
+
+ // Is the target a mob? If yes, use a do-after to give them time to respond.
+ if (HasComp(target) ||
+ HasComp(target))
+ {
+ InjectDoAfter(component, args.User, target);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ private void OnInjectorStartup(EntityUid uid, InjectorComponent component, ComponentStartup args)
+ {
+ Dirty(component);
+ }
+
+ private void OnInjectorUse(EntityUid uid, InjectorComponent component, UseInHandEvent args)
+ {
+ if (args.Handled) return;
+
+ Toggle(component, args.User);
+ args.Handled = true;
+ }
+
+ ///
+ /// Toggle between draw/inject state if applicable
+ ///
+ private void Toggle(InjectorComponent component, EntityUid user)
+ {
+ if (component.InjectOnly)
+ {
+ return;
+ }
+
+ string msg;
+ switch (component.ToggleState)
+ {
+ case SharedInjectorComponent.InjectorToggleMode.Inject:
+ component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Draw;
+ msg = "injector-component-drawing-text";
+ break;
+ case SharedInjectorComponent.InjectorToggleMode.Draw:
+ component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Inject;
+ msg = "injector-component-injecting-text";
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ _popup.PopupEntity(Loc.GetString(msg), component.Owner, Filter.Entities(user));
+ }
+
+ ///
+ /// Send informative pop-up messages and wait for a do-after to complete.
+ ///
+ private void InjectDoAfter(InjectorComponent component, EntityUid user, EntityUid target)
+ {
+ // Create a pop-up for the user
+ _popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, Filter.Entities(user));
+
+ if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution))
+ return;
+
+ // Get entity for logging. Log with EntityUids when?
+ var actualDelay = MathF.Max(component.Delay, 1f);
+ if (user != target)
+ {
+ // Create a pop-up for the target
+ var userName = MetaData(user).EntityName;
+ _popup.PopupEntity(Loc.GetString("injector-component-injecting-target",
+ ("user", userName)), user, Filter.Entities(target));
+
+ // Check if the target is incapacitated or in combat mode and modify time accordingly.
+ if (TryComp(target, out var mobState) && mobState.IsIncapacitated())
+ {
+ actualDelay /= 2;
+ }
+ else if (TryComp(target, out var combat) && combat.IsInCombatMode)
+ {
+ // Slightly increase the delay when the target is in combat mode. Helps prevents cheese injections in
+ // combat with fast syringes & lag.
+ actualDelay += 1;
+ }
+
+ // Add an admin log, using the "force feed" log type. It's not quite feeding, but the effect is the same.
+ if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Inject)
+ {
+ _logs.Add(LogType.ForceFeed,
+ $"{EntityManager.ToPrettyString(user):user} is attempting to inject {EntityManager.ToPrettyString(target):target} with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}");
+ // TODO solution pretty string.
+ }
+ }
+ else
+ {
+ // Self-injections take half as long.
+ actualDelay /= 2;
+
+ if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Inject)
+ _logs.Add(LogType.Ingestion,
+ $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}.");
+ //TODO solution pretty string.
+ }
+
+ component.CancelToken = new CancellationTokenSource();
+
+ _doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, component.CancelToken.Token, target)
+ {
+ BreakOnUserMove = true,
+ BreakOnDamage = true,
+ BreakOnStun = true,
+ BreakOnTargetMove = true,
+ MovementThreshold = 0.1f,
+ BroadcastFinishedEvent = new InjectionCompleteEvent()
+ {
+ Component = component,
+ User = user,
+ Target = target,
+ },
+ BroadcastCancelledEvent = new InjectionCancelledEvent()
+ {
+ Component = component,
+ }
+ });
+ }
+
+ private void TryInjectIntoBloodstream(InjectorComponent component, BloodstreamComponent targetBloodstream, EntityUid user)
+ {
+ // Get transfer amount. May be smaller than _transferAmount if not enough room
+ var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetBloodstream.Solution.AvailableVolume);
+
+ if (realTransferAmount <= 0)
+ {
+ _popup.PopupEntity(Loc.GetString("injector-component-cannot-inject-message", ("target", targetBloodstream.Owner)),
+ component.Owner, Filter.Entities(user));
+ return;
+ }
+
+ // Move units from attackSolution to targetSolution
+ var removedSolution = _solutions.SplitSolution(user, targetBloodstream.Solution, realTransferAmount);
+
+ _blood.TryAddToBloodstream((targetBloodstream).Owner, removedSolution, targetBloodstream);
+
+ removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection);
+
+ _popup.PopupEntity(Loc.GetString("injector-component-inject-success-message",
+ ("amount", removedSolution.TotalVolume),
+ ("target", targetBloodstream.Owner)), component.Owner, Filter.Entities(user));
+
+ Dirty(component);
+ AfterInject(component);
+ }
+
+ private void TryInject(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill)
+ {
+ if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
+ || solution.CurrentVolume == 0)
+ {
+ return;
+ }
+
+ // Get transfer amount. May be smaller than _transferAmount if not enough room
+ var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.AvailableVolume);
+
+ if (realTransferAmount <= 0)
+ {
+ _popup.PopupEntity(Loc.GetString("injector-component-target-already-full-message", ("target", targetEntity)),
+ component.Owner, Filter.Entities(user));
+ return;
+ }
+
+ // Move units from attackSolution to targetSolution
+ var removedSolution = _solutions.SplitSolution(component.Owner, solution, realTransferAmount);
+
+ removedSolution.DoEntityReaction(targetEntity, ReactionMethod.Injection);
+
+ if (!asRefill)
+ {
+ _solutions.Inject(targetEntity, targetSolution, removedSolution);
+ }
+ else
+ {
+ _solutions.Refill(targetEntity, targetSolution, removedSolution);
+ }
+
+ _popup.PopupEntity(Loc.GetString("injector-component-transfer-success-message",
+ ("amount", removedSolution.TotalVolume),
+ ("target", targetEntity)), component.Owner, Filter.Entities(user));
+
+ Dirty(component);
+ AfterInject(component);
+ }
+
+ private void AfterInject(InjectorComponent component)
+ {
+ // Automatically set syringe to draw after completely draining it.
+ if (_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
+ && solution.CurrentVolume == 0)
+ {
+ component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Draw;
+ }
+ }
+
+ private void AfterDraw(InjectorComponent component)
+ {
+ // Automatically set syringe to inject after completely filling it.
+ if (_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
+ && solution.AvailableVolume == 0)
+ {
+ component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Inject;
+ }
+ }
+
+ private void TryDraw(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user)
+ {
+ if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
+ || solution.AvailableVolume == 0)
+ {
+ return;
+ }
+
+ // Get transfer amount. May be smaller than _transferAmount if not enough room
+ var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.DrawAvailable);
+
+ if (realTransferAmount <= 0)
+ {
+ _popup.PopupEntity(Loc.GetString("injector-component-target-is-empty-message", ("target", targetEntity)),
+ component.Owner, Filter.Entities(user));
+ return;
+ }
+
+ // Move units from attackSolution to targetSolution
+ var removedSolution = _solutions.Draw(targetEntity, targetSolution, realTransferAmount);
+
+ if (!_solutions.TryAddSolution(targetEntity, solution, removedSolution))
+ {
+ return;
+ }
+
+ _popup.PopupEntity(Loc.GetString("injector-component-draw-success-message",
+ ("amount", removedSolution.TotalVolume),
+ ("target", targetEntity)), component.Owner, Filter.Entities(user));
+
+ Dirty(component);
+ AfterDraw(component);
+ }
+
+ private sealed class InjectionCompleteEvent : EntityEventArgs
+ {
+ public InjectorComponent Component { get; init; } = default!;
+ public EntityUid User { get; init; }
+ public EntityUid Target { get; init; }
+ }
+
+ private sealed class InjectionCancelledEvent : EntityEventArgs
+ {
+ public InjectorComponent Component { get; init; } = default!;
+ }
+}
diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs
new file mode 100644
index 0000000000..cd194a1ad0
--- /dev/null
+++ b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs
@@ -0,0 +1,25 @@
+using Content.Server.Administration.Logs;
+using Content.Server.Body.Systems;
+using Content.Server.DoAfter;
+using Content.Server.Popups;
+using Content.Shared.ActionBlocker;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+
+namespace Content.Server.Chemistry.EntitySystems;
+
+public sealed partial class ChemistrySystem : EntitySystem
+{
+ [Dependency] private readonly ActionBlockerSystem _blocker = default!;
+ [Dependency] private readonly AdminLogSystem _logs = default!;
+ [Dependency] private readonly BloodstreamSystem _blood = default!;
+ [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly SolutionContainerSystem _solutions = default!;
+
+ public override void Initialize()
+ {
+ InitializeHypospray();
+ InitializeInjector();
+ }
+}
diff --git a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs
similarity index 89%
rename from Content.Server/Chemistry/EntitySystems/HypospraySystem.cs
rename to Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs
index e19151cb76..ab29879a7c 100644
--- a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs
+++ b/Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs
@@ -6,13 +6,10 @@ using Robust.Shared.GameObjects;
namespace Content.Server.Chemistry.EntitySystems
{
- [UsedImplicitly]
- public class HypospraySystem : EntitySystem
+ public sealed partial class ChemistrySystem
{
- public override void Initialize()
+ private void InitializeHypospray()
{
- base.Initialize();
-
SubscribeLocalEvent(OnAfterInteract);
SubscribeLocalEvent(OnClickAttack);
SubscribeLocalEvent(OnSolutionChange);
@@ -27,6 +24,7 @@ namespace Content.Server.Chemistry.EntitySystems
{
if (!args.CanReach)
return;
+
var target = args.Target;
var user = args.User;
diff --git a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs
deleted file mode 100644
index 06843489ce..0000000000
--- a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Content.Server.Chemistry.Components;
-using Content.Shared.Hands;
-using JetBrains.Annotations;
-using Robust.Shared.GameObjects;
-using System;
-
-namespace Content.Server.Chemistry.EntitySystems
-{
- [UsedImplicitly]
- public class InjectorSystem : EntitySystem
- {
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnSolutionChange);
- SubscribeLocalEvent(OnInjectorDeselected);
- }
-
- private void OnInjectorDeselected(EntityUid uid, InjectorComponent component, HandDeselectedEvent args)
- {
- if (component.CancelToken != null)
- {
- component.CancelToken.Cancel();
- component.CancelToken = null;
- }
- }
-
- private void OnSolutionChange(EntityUid uid, InjectorComponent component, SolutionChangedEvent args)
- {
- Dirty(component);
- }
- }
-}
diff --git a/Content.Shared/Chemistry/Components/SharedInjectorComponent.cs b/Content.Shared/Chemistry/Components/SharedInjectorComponent.cs
index 60d009aebe..4b2fa63c21 100644
--- a/Content.Shared/Chemistry/Components/SharedInjectorComponent.cs
+++ b/Content.Shared/Chemistry/Components/SharedInjectorComponent.cs
@@ -1,5 +1,4 @@
using System;
-using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
@@ -10,20 +9,20 @@ namespace Content.Shared.Chemistry.Components
///
/// Shared class for injectors & syringes
///
- [NetworkedComponent()]
- public class SharedInjectorComponent : Component
+ [NetworkedComponent, ComponentProtoName("Injector")]
+ public abstract class SharedInjectorComponent : Component
{
///
/// Component data used for net updates. Used by client for item status ui
///
[Serializable, NetSerializable]
- protected sealed class InjectorComponentState : ComponentState
+ public sealed class InjectorComponentState : ComponentState
{
public FixedPoint2 CurrentVolume { get; }
public FixedPoint2 TotalVolume { get; }
public InjectorToggleMode CurrentMode { get; }
- public InjectorComponentState(FixedPoint2 currentVolume, FixedPoint2 totalVolume, InjectorToggleMode currentMode)
+ public InjectorComponentState(FixedPoint2 currentVolume, FixedPoint2 totalVolume, SharedInjectorComponent.InjectorToggleMode currentMode)
{
CurrentVolume = currentVolume;
TotalVolume = totalVolume;
@@ -31,7 +30,7 @@ namespace Content.Shared.Chemistry.Components
}
}
- public enum InjectorToggleMode
+ public enum InjectorToggleMode : byte
{
Inject,
Draw