From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Wed, 6 Dec 2023 21:30:32 +0000 (+0000) Subject: medibot fixes and refactoring (#21852) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=47359cf70fe40913b205cb8b3e85a84546487256;p=space-station-14.git medibot fixes and refactoring (#21852) Co-authored-by: deltanedas <@deltanedas:kde.org> --- diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs index 052262cac5..c9c495cba5 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs @@ -2,7 +2,9 @@ using Content.Server.Chat.Systems; using Content.Server.NPC.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Damage; +using Content.Shared.Emag.Components; using Content.Shared.Interaction; +using Content.Shared.Mobs.Components; using Content.Shared.Popups; using Content.Shared.Silicons.Bots; using Robust.Shared.Audio; @@ -65,29 +67,23 @@ public sealed partial class MedibotInjectOperator : HTNOperator var total = damage.TotalDamage; - if (total == 0) + // always inject healthy patients when emagged + if (total == 0 && !_entMan.HasComponent(owner)) return HTNOperatorStatus.Failed; - if (total >= MedibotComponent.EmergencyMedDamageThreshold) - { - _entMan.EnsureComponent(target); - _solution.TryAddReagent(target, injectable, botComp.EmergencyMed, botComp.EmergencyMedAmount, out var accepted); - _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, ChatTransmitRange.GhostRangeLimit); - return HTNOperatorStatus.Finished; - } - - if (total >= MedibotComponent.StandardMedDamageThreshold && total <= MedibotComponent.StandardMedDamageThresholdStop) - { - _entMan.EnsureComponent(target); - _solution.TryAddReagent(target, injectable, botComp.StandardMed, botComp.StandardMedAmount, out var accepted); - _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, ChatTransmitRange.GhostRangeLimit); - return HTNOperatorStatus.Finished; - } - - return HTNOperatorStatus.Failed; + if (!_entMan.TryGetComponent(target, out var mobState)) + return HTNOperatorStatus.Failed; + + var state = mobState.CurrentState; + var treatment = botComp.Treatments[mobState.CurrentState]; + if (!treatment.IsValid(total)) + return HTNOperatorStatus.Failed; + + _entMan.EnsureComponent(target); + _solution.TryAddReagent(target, injectable, 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 dccd9f3fa4..a71091ad97 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyInjectableOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyInjectableOperator.cs @@ -15,6 +15,7 @@ public sealed partial class PickNearbyInjectableOperator : HTNOperator { [Dependency] private readonly IEntityManager _entManager = default!; private EntityLookupSystem _lookup = default!; + private MedibotSystem _medibot = default!; private PathfindingSystem _pathfinding = default!; [DataField("rangeKey")] public string RangeKey = NPCBlackboard.MedibotInjectRange; @@ -35,6 +36,7 @@ public sealed partial class PickNearbyInjectableOperator : HTNOperator { base.Initialize(sysManager); _lookup = sysManager.GetEntitySystem(); + _medibot = sysManager.GetEntitySystem(); _pathfinding = sysManager.GetEntitySystem(); } @@ -46,37 +48,46 @@ public sealed partial class PickNearbyInjectableOperator : HTNOperator if (!blackboard.TryGetValue(RangeKey, out var range, _entManager)) return (false, null); + if (!_entManager.TryGetComponent(owner, out var medibot)) + return (false, null); + var damageQuery = _entManager.GetEntityQuery(); var injectQuery = _entManager.GetEntityQuery(); var recentlyInjected = _entManager.GetEntityQuery(); var mobState = _entManager.GetEntityQuery(); + var emaggedQuery = _entManager.GetEntityQuery(); foreach (var entity in _lookup.GetEntitiesInRange(owner, range)) { - if (mobState.HasComponent(entity) && + if (mobState.TryGetComponent(entity, out var state) && injectQuery.HasComponent(entity) && damageQuery.TryGetComponent(entity, out var damage) && !recentlyInjected.HasComponent(entity)) - //Only go towards a target if the bot can actually help them or if the medibot is emagged - if (damage.TotalDamage > MedibotComponent.StandardMedDamageThreshold && - damage.TotalDamage <= MedibotComponent.StandardMedDamageThresholdStop || - damage.TotalDamage > MedibotComponent.EmergencyMedDamageThreshold || - _entManager.HasComponent(owner)) + { + // no treating dead bodies + if (!_medibot.TryGetTreatment(medibot, state.CurrentState, out var treatment)) + continue; + + // Only go towards a target if the bot can actually help them or if the medibot is emagged + // note: this and the actual injecting don't check for specific damage types so for example, + // radiation damage will trigger injection but the tricordrazine won't heal it. + if (!emaggedQuery.HasComponent(entity) && !treatment.IsValid(damage.TotalDamage)) + continue; + + //Needed to make sure it doesn't sometimes stop right outside it's interaction range + var pathRange = SharedInteractionSystem.InteractionRange - 1f; + var path = await _pathfinding.GetPath(owner, entity, pathRange, cancelToken); + + if (path.Result == PathResult.NoPath) + continue; + + return (true, new Dictionary() { - //Needed to make sure it doesn't sometimes stop right outside it's interaction range - var pathRange = SharedInteractionSystem.InteractionRange - 1f; - var path = await _pathfinding.GetPath(owner, entity, pathRange, cancelToken); - - if (path.Result == PathResult.NoPath) - continue; - - return (true, new Dictionary() - { - {TargetKey, entity}, - {TargetMoveKey, _entManager.GetComponent(entity).Coordinates}, - {NPCBlackboard.PathfindKey, path}, - }); - } + {TargetKey, entity}, + {TargetMoveKey, _entManager.GetComponent(entity).Coordinates}, + {NPCBlackboard.PathfindKey, path}, + }); + } } return (false, null); diff --git a/Content.Shared/Silicons/Bots/EmaggableMedibotComponent.cs b/Content.Shared/Silicons/Bots/EmaggableMedibotComponent.cs index a75d6113c9..73775aaf91 100644 --- a/Content.Shared/Silicons/Bots/EmaggableMedibotComponent.cs +++ b/Content.Shared/Silicons/Bots/EmaggableMedibotComponent.cs @@ -1,40 +1,28 @@ -using Content.Shared.Chemistry.Reagent; +using Content.Shared.Mobs; using Robust.Shared.Audio; using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Silicons.Bots; /// -/// Replaced the medibot's meds with these when emagged. Could be poison, could be fun. +/// Replaces the medibot's meds with these when emagged. Could be poison, could be fun. /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent] [Access(typeof(MedibotSystem))] public sealed partial class EmaggableMedibotComponent : Component { /// - /// Med the bot will inject when UNDER the standard med damage threshold. + /// Treatments to replace from the original set. /// - [DataField("standardMed", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public string StandardMed = "Tricordrazine"; - - [DataField("standardMedAmount"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public float StandardMedAmount = 15f; - - /// - /// Med the bot will inject when OVER the emergency med damage threshold. - /// - [DataField("emergencyMed", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public string EmergencyMed = "Inaprovaline"; - - [DataField("emergencyMedAmount"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public float EmergencyMedAmount = 15f; + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + public Dictionary Replacements = new(); /// /// Sound to play when the bot has been emagged /// - [DataField("sparkSound")] public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks") + [DataField] + public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks") { - Params = AudioParams.Default.WithVolume(8f), + Params = AudioParams.Default.WithVolume(8f) }; } diff --git a/Content.Shared/Silicons/Bots/MedibotComponent.cs b/Content.Shared/Silicons/Bots/MedibotComponent.cs index 74d074ffd8..9bd45e9a6a 100644 --- a/Content.Shared/Silicons/Bots/MedibotComponent.cs +++ b/Content.Shared/Silicons/Bots/MedibotComponent.cs @@ -1,6 +1,8 @@ using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Content.Shared.Mobs; using Robust.Shared.Audio; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Prototypes; namespace Content.Shared.Silicons.Bots; @@ -13,30 +15,56 @@ namespace Content.Shared.Silicons.Bots; public sealed partial class MedibotComponent : Component { /// - /// Med the bot will inject when UNDER the standard med damage threshold. + /// Treatments the bot will apply for each mob state. /// - [DataField("standardMed", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string StandardMed = "Tricordrazine"; + [DataField(required: true)] + public Dictionary Treatments = new(); - [DataField("standardMedAmount")] - public float StandardMedAmount = 30f; + /// + /// Sound played after injecting a patient. + /// + [DataField("injectSound")] + public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg"); +} +/// +/// An injection to treat the patient with. +/// +[DataDefinition] +public sealed partial class MedibotTreatment +{ /// - /// Med the bot will inject when OVER the emergency med damage threshold. + /// Reagent to inject into the patient. /// - [DataField("emergencyMed", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string EmergencyMed = "Inaprovaline"; + [DataField(required: true)] + public ProtoId Reagent = string.Empty; - [DataField("emergencyMedAmount")] - public float EmergencyMedAmount = 15f; + /// + /// How much of the reagent to inject. + /// + [DataField(required: true)] + public FixedPoint2 Quantity; /// - /// Sound played after injecting a patient. + /// Do nothing when the patient is at or below this total damage. + /// When null this will inject meds into completely healthy patients. /// - [DataField("injectSound")] - public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg"); + [DataField] + public FixedPoint2? MinDamage; - public const float StandardMedDamageThreshold = 0f; - public const float StandardMedDamageThresholdStop = 50f; - public const float EmergencyMedDamageThreshold = 100f; + /// + /// Do nothing when the patient is at or above this total damage. + /// Useful for tricordrazine which does nothing above 50 damage. + /// + [DataField] + public FixedPoint2? MaxDamage; + + /// + /// Returns whether the treatment will probably work for an amount of damage. + /// Doesn't account for specific damage types only total amount. + /// + public bool IsValid(FixedPoint2 damage) + { + return (MaxDamage == null || damage < MaxDamage) && (MinDamage == null || damage > MinDamage); + } } diff --git a/Content.Shared/Silicons/Bots/MedibotSystem.cs b/Content.Shared/Silicons/Bots/MedibotSystem.cs index 464e95b77f..3ab73149c0 100644 --- a/Content.Shared/Silicons/Bots/MedibotSystem.cs +++ b/Content.Shared/Silicons/Bots/MedibotSystem.cs @@ -1,11 +1,12 @@ using Content.Shared.Emag.Systems; -using Robust.Shared.Audio; +using Content.Shared.Mobs; using Robust.Shared.Audio.Systems; +using System.Diagnostics.CodeAnalysis; namespace Content.Shared.Silicons.Bots; /// -/// Handles emagging medibots +/// Handles emagging medibots and provides api. /// public sealed class MedibotSystem : EntitySystem { @@ -25,10 +26,22 @@ public sealed class MedibotSystem : EntitySystem _audio.PlayPredicted(comp.SparkSound, uid, args.UserUid); - medibot.StandardMed = comp.StandardMed; - medibot.StandardMedAmount = comp.StandardMedAmount; - medibot.EmergencyMed = comp.EmergencyMed; - medibot.EmergencyMedAmount = comp.EmergencyMedAmount; + foreach (var (state, treatment) in comp.Replacements) + { + medibot.Treatments[state] = treatment; + } + args.Handled = true; } + + /// + /// Get a treatment for a given mob state. + /// + /// + /// This only exists because allowing other execute would allow modifying the dictionary, and Read access does not cover TryGetValue. + /// + public bool TryGetTreatment(MedibotComponent comp, MobState state, [NotNullWhen(true)] out MedibotTreatment? treatment) + { + return comp.Treatments.TryGetValue(state, out treatment); + } } diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index bdb908af36..aa6e440cf6 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -314,13 +314,25 @@ description: No substitute for a doctor, but better than nothing. components: - type: Medibot + treatments: + Alive: + reagent: Tricordrazine + quantity: 30 + minDamage: 0 + maxDamage: 50 + Critical: + reagent: Inaprovaline + quantity: 15 - type: EmaggableMedibot - # when you are fine, medibot will help you go sleep - standardMed: ChloralHydrate - standardMedAmount: 15 - # when you are crit, medibot will help you have fun - emergencyMed: SpaceDrugs - emergencyMedAmount: 25 + replacements: + # when you are fine, medibot will help you go sleep + Alive: + reagent: ChloralHydrate + quantity: 15 + # when you are crit, medibot will help you have fun + Critical: + reagent: SpaceDrugs + quantity: 25 - type: Sprite sprite: Mobs/Silicon/Bots/medibot.rsi state: medibot