]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
medibot fixes and refactoring (#21852)
authordeltanedas <39013340+deltanedas@users.noreply.github.com>
Wed, 6 Dec 2023 21:30:32 +0000 (21:30 +0000)
committerGitHub <noreply@github.com>
Wed, 6 Dec 2023 21:30:32 +0000 (08:30 +1100)
Co-authored-by: deltanedas <@deltanedas:kde.org>
Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs
Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyInjectableOperator.cs
Content.Shared/Silicons/Bots/EmaggableMedibotComponent.cs
Content.Shared/Silicons/Bots/MedibotComponent.cs
Content.Shared/Silicons/Bots/MedibotSystem.cs
Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml

index 052262cac51c3a1e40dfda9416aa538f58935d39..c9c495cba52e2091b90ad68b923f5c1f6fa666c1 100644 (file)
@@ -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<EmaggedComponent>(owner))
             return HTNOperatorStatus.Failed;
 
-        if (total >= MedibotComponent.EmergencyMedDamageThreshold)
-        {
-            _entMan.EnsureComponent<NPCRecentlyInjectedComponent>(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<NPCRecentlyInjectedComponent>(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<MobStateComponent>(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<NPCRecentlyInjectedComponent>(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;
     }
 }
index dccd9f3fa4f22efff6ef6d3726c6638b90f61931..a71091ad97dd421560cdd19d45ec76f9535d7a54 100644 (file)
@@ -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<EntityLookupSystem>();
+        _medibot = sysManager.GetEntitySystem<MedibotSystem>();
         _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
     }
 
@@ -46,37 +48,46 @@ public sealed partial class PickNearbyInjectableOperator : HTNOperator
         if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager))
             return (false, null);
 
+        if (!_entManager.TryGetComponent<MedibotComponent>(owner, out var medibot))
+            return (false, null);
+
         var damageQuery = _entManager.GetEntityQuery<DamageableComponent>();
         var injectQuery = _entManager.GetEntityQuery<InjectableSolutionComponent>();
         var recentlyInjected = _entManager.GetEntityQuery<NPCRecentlyInjectedComponent>();
         var mobState = _entManager.GetEntityQuery<MobStateComponent>();
+        var emaggedQuery = _entManager.GetEntityQuery<EmaggedComponent>();
 
         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<EmaggedComponent>(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<string, object>()
                 {
-                    //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<string, object>()
-                    {
-                        {TargetKey, entity},
-                        {TargetMoveKey, _entManager.GetComponent<TransformComponent>(entity).Coordinates},
-                        {NPCBlackboard.PathfindKey, path},
-                    });
-                }
+                    {TargetKey, entity},
+                    {TargetMoveKey, _entManager.GetComponent<TransformComponent>(entity).Coordinates},
+                    {NPCBlackboard.PathfindKey, path},
+                });
+            }
         }
 
         return (false, null);
index a75d6113c95659dc8f7b2cd3ff893e0baca2a95f..73775aaf91ac87cb8e3b9b422a6f8499bacd618d 100644 (file)
@@ -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;
 
 /// <summary>
-/// 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.
 /// </summary>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[RegisterComponent, NetworkedComponent]
 [Access(typeof(MedibotSystem))]
 public sealed partial class EmaggableMedibotComponent : Component
 {
     /// <summary>
-    /// Med the bot will inject when UNDER the standard med damage threshold.
+    /// Treatments to replace from the original set.
     /// </summary>
-    [DataField("standardMed", customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
-    public string StandardMed = "Tricordrazine";
-
-    [DataField("standardMedAmount"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
-    public float StandardMedAmount = 15f;
-
-    /// <summary>
-    /// Med the bot will inject when OVER the emergency med damage threshold.
-    /// </summary>
-    [DataField("emergencyMed", customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>)), 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<MobState, MedibotTreatment> Replacements = new();
 
     /// <summary>
     /// Sound to play when the bot has been emagged
     /// </summary>
-    [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)
     };
 }
index 74d074ffd858d62ef9fccaf0ea0798aabef91338..9bd45e9a6a51633166f944b34373bf04426508f4 100644 (file)
@@ -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
 {
     /// <summary>
-    /// Med the bot will inject when UNDER the standard med damage threshold.
+    /// Treatments the bot will apply for each mob state.
     /// </summary>
-    [DataField("standardMed", customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
-    public string StandardMed = "Tricordrazine";
+    [DataField(required: true)]
+    public Dictionary<MobState, MedibotTreatment> Treatments = new();
 
-    [DataField("standardMedAmount")]
-    public float StandardMedAmount = 30f;
+    /// <summary>
+    /// Sound played after injecting a patient.
+    /// </summary>
+    [DataField("injectSound")]
+    public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg");
+}
 
+/// <summary>
+/// An injection to treat the patient with.
+/// </summary>
+[DataDefinition]
+public sealed partial class MedibotTreatment
+{
     /// <summary>
-    /// Med the bot will inject when OVER the emergency med damage threshold.
+    /// Reagent to inject into the patient.
     /// </summary>
-    [DataField("emergencyMed", customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
-    public string EmergencyMed = "Inaprovaline";
+    [DataField(required: true)]
+    public ProtoId<ReagentPrototype> Reagent = string.Empty;
 
-    [DataField("emergencyMedAmount")]
-    public float EmergencyMedAmount = 15f;
+    /// <summary>
+    /// How much of the reagent to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public FixedPoint2 Quantity;
 
     /// <summary>
-    /// 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.
     /// </summary>
-    [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;
+    /// <summary>
+    /// Do nothing when the patient is at or above this total damage.
+    /// Useful for tricordrazine which does nothing above 50 damage.
+    /// </summary>
+    [DataField]
+    public FixedPoint2? MaxDamage;
+
+    /// <summary>
+    /// Returns whether the treatment will probably work for an amount of damage.
+    /// Doesn't account for specific damage types only total amount.
+    /// </summary>
+    public bool IsValid(FixedPoint2 damage)
+    {
+        return (MaxDamage == null || damage < MaxDamage) && (MinDamage == null || damage > MinDamage);
+    }
 }
index 464e95b77fe5128f4531509b841debdc6ac40b1d..3ab73149c08e483e4ce3ab33963697017c1220b2 100644 (file)
@@ -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;
 
 /// <summary>
-/// Handles emagging medibots
+/// Handles emagging medibots and provides api.
 /// </summary>
 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;
     }
+
+    /// <summary>
+    /// Get a treatment for a given mob state.
+    /// </summary>
+    /// <remarks>
+    /// This only exists because allowing other execute would allow modifying the dictionary, and Read access does not cover TryGetValue.
+    /// </remarks>
+    public bool TryGetTreatment(MedibotComponent comp, MobState state, [NotNullWhen(true)] out MedibotTreatment? treatment)
+    {
+        return comp.Treatments.TryGetValue(state, out treatment);
+    }
 }
index bdb908af36158f9fee9c94fca957303ff4ded256..aa6e440cf6eea3376ecf4476c3a21832950d1d54 100644 (file)
   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