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;
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;
}
}
{
[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;
{
base.Initialize(sysManager);
_lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+ _medibot = sysManager.GetEntitySystem<MedibotSystem>();
_pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
}
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);
-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)
};
}
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;
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);
+ }
}
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
{
_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);
+ }
}
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