diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs index 2cc735194f..bac9cfcf40 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs @@ -1,5 +1,5 @@ using Content.Server.Chat.Systems; -using Content.Server.NPC.Components; +using Content.Shared.NPC.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Damage; using Content.Shared.Emag.Components; @@ -55,34 +55,11 @@ public sealed partial class MedibotInjectOperator : HTNOperator if (!_entMan.TryGetComponent(owner, out var botComp)) return HTNOperatorStatus.Failed; - - if (!_entMan.TryGetComponent(target, out var damage)) + if (!_medibot.CheckInjectable((owner, botComp), target) || !_medibot.TryInject((owner, botComp), target)) return HTNOperatorStatus.Failed; - if (!_solutionContainer.TryGetInjectableSolution(target, out var injectable, out _)) - return HTNOperatorStatus.Failed; - - if (!_interaction.InRangeUnobstructed(owner, target)) - return HTNOperatorStatus.Failed; - - var total = damage.TotalDamage; - - // always inject healthy patients when emagged - if (total == 0 && !_entMan.HasComponent(owner)) - return HTNOperatorStatus.Failed; - - if (!_entMan.TryGetComponent(target, out var mobState)) - return HTNOperatorStatus.Failed; - - var state = mobState.CurrentState; - if (!_medibot.TryGetTreatment(botComp, mobState.CurrentState, out var treatment) || !treatment.IsValid(total)) - return HTNOperatorStatus.Failed; - - _entMan.EnsureComponent(target); - _solutionContainer.TryAddReagent(injectable.Value, treatment.Reagent, treatment.Quantity, out _); - _popup.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, target); - _audio.PlayPvs(botComp.InjectSound, target); _chat.TrySendInGameICMessage(owner, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, hideChat: true, hideLog: true); + return HTNOperatorStatus.Finished; } } diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyInjectableOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyInjectableOperator.cs index a71091ad97..f351d582c6 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyInjectableOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyInjectableOperator.cs @@ -1,6 +1,6 @@ using System.Threading; using System.Threading.Tasks; -using Content.Server.NPC.Components; +using Content.Shared.NPC.Components; using Content.Server.NPC.Pathfinding; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Damage; diff --git a/Content.Server/NPC/Systems/NPCPerceptionSystem.RecentlyInjected.cs b/Content.Server/NPC/Systems/NPCPerceptionSystem.RecentlyInjected.cs index dd4ff6c65f..6d8cb9dfe5 100644 --- a/Content.Server/NPC/Systems/NPCPerceptionSystem.RecentlyInjected.cs +++ b/Content.Server/NPC/Systems/NPCPerceptionSystem.RecentlyInjected.cs @@ -1,4 +1,4 @@ -using Content.Server.NPC.Components; +using Content.Shared.NPC.Components; namespace Content.Server.NPC.Systems; diff --git a/Content.Server/NPC/Components/NPCRecentlyInjectedComponent.cs b/Content.Shared/NPC/Components/NPCRecentlyInjectedComponent.cs similarity index 78% rename from Content.Server/NPC/Components/NPCRecentlyInjectedComponent.cs rename to Content.Shared/NPC/Components/NPCRecentlyInjectedComponent.cs index 0bc5dce55b..b79c4dd9ef 100644 --- a/Content.Server/NPC/Components/NPCRecentlyInjectedComponent.cs +++ b/Content.Shared/NPC/Components/NPCRecentlyInjectedComponent.cs @@ -1,8 +1,10 @@ -namespace Content.Server.NPC.Components +using Robust.Shared.GameStates; + +namespace Content.Shared.NPC.Components { /// Added when a medibot injects someone /// So they don't get injected again for at least a minute. - [RegisterComponent] + [RegisterComponent, NetworkedComponent] public sealed partial class NPCRecentlyInjectedComponent : Component { [ViewVariables(VVAccess.ReadWrite), DataField("accumulator")] diff --git a/Content.Shared/Silicons/Bots/MedibotSystem.cs b/Content.Shared/Silicons/Bots/MedibotSystem.cs index 3ab73149c0..68f930931c 100644 --- a/Content.Shared/Silicons/Bots/MedibotSystem.cs +++ b/Content.Shared/Silicons/Bots/MedibotSystem.cs @@ -1,6 +1,15 @@ +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; +using Content.Shared.Interaction; using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.NPC.Components; +using Content.Shared.Popups; using Robust.Shared.Audio.Systems; +using Robust.Shared.Serialization; using System.Diagnostics.CodeAnalysis; namespace Content.Shared.Silicons.Bots; @@ -11,12 +20,18 @@ namespace Content.Shared.Silicons.Bots; public sealed class MedibotSystem : EntitySystem { [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private SharedInteractionSystem _interaction = default!; + [Dependency] private SharedSolutionContainerSystem _solutionContainer = default!; + [Dependency] private SharedPopupSystem _popup = default!; + [Dependency] private SharedDoAfterSystem _doAfter = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnInject); } private void OnEmagged(EntityUid uid, EmaggableMedibotComponent comp, ref GotEmaggedEvent args) @@ -34,6 +49,25 @@ public sealed class MedibotSystem : EntitySystem args.Handled = true; } + private void OnInteract(Entity medibot, ref UserActivateInWorldEvent args) + { + if (!CheckInjectable(medibot!, args.Target, true)) return; + + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, 2f, new MedibotInjectDoAfterEvent(), args.User, args.Target) + { + BlockDuplicate = true, + BreakOnMove = true, + }); + } + + private void OnInject(EntityUid uid, MedibotComponent comp, ref MedibotInjectDoAfterEvent args) + { + if (args.Cancelled) return; + + if (args.Target is { } target) + TryInject(uid, target); + } + /// /// Get a treatment for a given mob state. /// @@ -44,4 +78,66 @@ public sealed class MedibotSystem : EntitySystem { return comp.Treatments.TryGetValue(state, out treatment); } + + /// + /// Checks if the target can be injected. + /// + public bool CheckInjectable(Entity medibot, EntityUid target, bool manual = false) + { + if (!Resolve(medibot, ref medibot.Comp, false)) return false; + + if (HasComp(target)) + { + _popup.PopupClient(Loc.GetString("medibot-recently-injected"), medibot, medibot); + return false; + } + + if (!TryComp(target, out var mobState)) return false; + if (!TryComp(target, out var damageable)) return false; + if (!_solutionContainer.TryGetInjectableSolution(target, out _, out _)) return false; + + if (mobState.CurrentState != MobState.Alive && mobState.CurrentState != MobState.Critical) + { + _popup.PopupClient(Loc.GetString("medibot-target-dead"), medibot, medibot); + return false; + } + + var total = damageable.TotalDamage; + if (total == 0 && !HasComp(medibot)) + { + _popup.PopupClient(Loc.GetString("medibot-target-healthy"), medibot, medibot); + return false; + } + + if (!TryGetTreatment(medibot.Comp, mobState.CurrentState, out var treatment) || !treatment.IsValid(total) && !manual) return false; + + return true; + } + + /// + /// Tries to inject the target. + /// + public bool TryInject(Entity medibot, EntityUid target) + { + if (!Resolve(medibot, ref medibot.Comp, false)) return false; + + if (!_interaction.InRangeUnobstructed(medibot.Owner, target)) return false; + + if (!TryComp(target, out var mobState)) return false; + if (!TryGetTreatment(medibot.Comp, mobState.CurrentState, out var treatment)) return false; + if (!_solutionContainer.TryGetInjectableSolution(target, out var injectable, out _)) return false; + + EnsureComp(target); + _solutionContainer.TryAddReagent(injectable.Value, treatment.Reagent, treatment.Quantity, out _); + + _popup.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, target); + _popup.PopupClient(Loc.GetString("medibot-target-injected"), medibot, medibot); + + _audio.PlayPredicted(medibot.Comp.InjectSound, medibot, medibot); + + return true; + } } + +[Serializable, NetSerializable] +public sealed partial class MedibotInjectDoAfterEvent : SimpleDoAfterEvent { } diff --git a/Resources/Locale/en-US/npc/medibot.ftl b/Resources/Locale/en-US/npc/medibot.ftl index b645220119..79be6d371b 100644 --- a/Resources/Locale/en-US/npc/medibot.ftl +++ b/Resources/Locale/en-US/npc/medibot.ftl @@ -1,2 +1,7 @@ medibot-start-inject = Hold still, please. medibot-finish-inject = All done. + +medibot-target-dead = The patient is dead. +medibot-target-healthy = The patient is already healthy. +medibot-target-injected = The patient was injected. +medibot-recently-injected = The patient was recently injected. diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 682b220ca2..6a6e4285eb 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -351,6 +351,7 @@ pack: MedibotAds - type: Inventory templateId: medibot + - type: DoAfter - type: entity parent: