using System; using Content.Server.GameObjects.Components.Metabolism; using Content.Server.Interfaces; using Content.Server.Utility; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Chemistry { /// /// Server behavior for reagent injectors and syringes. Can optionally support both /// injection and drawing or just injection. Can inject/draw reagents from solution /// containers, and can directly inject into a mobs bloodstream. /// [RegisterComponent] public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse { #pragma warning disable 649 [Dependency] private readonly IServerNotifyManager _notifyManager; #pragma warning restore 649 /// /// 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; /// /// 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; /// /// Initial storage volume of the injector /// [ViewVariables] private ReagentUnit _initialMaxVolume; /// /// 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; /// /// Internal solution container /// [ViewVariables] private SolutionComponent _internalContents; public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); serializer.DataField(ref _injectOnly, "injectOnly", false); serializer.DataField(ref _initialMaxVolume, "initialMaxVolume", ReagentUnit.New(15)); serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5)); } protected override void Startup() { base.Startup(); _internalContents = Owner.GetComponent(); _internalContents.Capabilities |= SolutionCaps.Injector; //Set _toggleState based on prototype _toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw; } /// /// Toggle between draw/inject state if applicable /// private void Toggle(IEntity user) { if (_injectOnly) { return; } string msg; switch (_toggleState) { case InjectorToggleMode.Inject: _toggleState = InjectorToggleMode.Draw; msg = "Now drawing"; break; case InjectorToggleMode.Draw: _toggleState = InjectorToggleMode.Inject; msg = "Now injecting"; break; default: throw new ArgumentOutOfRangeException(); } _notifyManager.PopupMessage(Owner, user, Loc.GetString(msg)); Dirty(); } /// /// Called when clicking on entities while holding in active hand /// /// void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return; //Make sure we have the attacking entity if (eventArgs.Target == null || !_internalContents.Injector) { return; } var targetEntity = eventArgs.Target; //Handle injecting/drawing for solutions if (targetEntity.TryGetComponent(out var targetSolution) && targetSolution.Injectable) { if (_toggleState == InjectorToggleMode.Inject) { TryInject(targetSolution, eventArgs.User); } else if (_toggleState == InjectorToggleMode.Draw) { TryDraw(targetSolution, eventArgs.User); } } else //Handle injecting into bloodstream { if (targetEntity.TryGetComponent(out var bloodstream) && _toggleState == InjectorToggleMode.Inject) { TryInjectIntoBloodstream(bloodstream, eventArgs.User); } } } /// /// 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, IEntity user) { if (_internalContents.CurrentVolume == 0) { return; } //Get transfer amount. May be smaller than _transferAmount if not enough room var realTransferAmount = ReagentUnit.Min(_transferAmount, targetBloodstream.EmptyVolume); if (realTransferAmount <= 0) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("Container full.")); return; } //Move units from attackSolution to targetSolution var removedSolution = _internalContents.SplitSolution(realTransferAmount); if (!targetBloodstream.TryTransferSolution(removedSolution)) { return; } _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("Injected {0}u", removedSolution.TotalVolume)); Dirty(); } private void TryInject(SolutionComponent targetSolution, IEntity user) { if (_internalContents.CurrentVolume == 0) { return; } //Get transfer amount. May be smaller than _transferAmount if not enough room var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.EmptyVolume); if (realTransferAmount <= 0) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("Container full.")); return; } //Move units from attackSolution to targetSolution var removedSolution = _internalContents.SplitSolution(realTransferAmount); if (!targetSolution.TryAddSolution(removedSolution)) { return; } _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("Injected {0}u", removedSolution.TotalVolume)); Dirty(); } private void TryDraw(SolutionComponent targetSolution, IEntity user) { if (_internalContents.EmptyVolume == 0) { return; } //Get transfer amount. May be smaller than _transferAmount if not enough room var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.CurrentVolume); if (realTransferAmount <= 0) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("Container empty")); return; } //Move units from attackSolution to targetSolution var removedSolution = targetSolution.SplitSolution(realTransferAmount); if (!_internalContents.TryAddSolution(removedSolution)) { return; } _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("Drew {0}u", removedSolution.TotalVolume)); Dirty(); } public override ComponentState GetComponentState() { return new InjectorComponentState(_internalContents.CurrentVolume, _internalContents.MaxVolume, _toggleState); } } }