using System.Threading; using System.Threading.Tasks; using Content.Shared.NPC.Components; using Content.Server.NPC.Pathfinding; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Damage.Components; using Content.Shared.Damage.Systems; using Content.Shared.Interaction; using Content.Shared.Mobs.Components; using Content.Shared.Silicons.Bots; using Content.Shared.Emag.Components; using Content.Shared.FixedPoint; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific; public sealed partial class PickNearbyInjectableOperator : HTNOperator { [Dependency] private readonly IEntityManager _entManager = default!; private MedibotSystem _medibot = default!; private PathfindingSystem _pathfinding = default!; private DamageableSystem _damageable = default!; private EntityQuery _damageQuery = default!; private EntityQuery _injectQuery = default!; private EntityQuery _recentlyInjected = default!; private EntityQuery _mobState = default!; private EntityQuery _emaggedQuery = default!; [DataField("rangeKey")] public string RangeKey = NPCBlackboard.MedibotInjectRange; /// /// Target entity to inject /// [DataField("targetKey", required: true)] public string TargetKey = string.Empty; /// /// Target entitycoordinates to move to. /// [DataField("targetMoveKey", required: true)] public string TargetMoveKey = string.Empty; public override void Initialize(IEntitySystemManager sysManager) { base.Initialize(sysManager); _medibot = sysManager.GetEntitySystem(); _pathfinding = sysManager.GetEntitySystem(); _damageable = sysManager.GetEntitySystem(); _damageQuery = _entManager.GetEntityQuery(); _injectQuery = _entManager.GetEntityQuery(); _recentlyInjected = _entManager.GetEntityQuery(); _mobState = _entManager.GetEntityQuery(); _emaggedQuery = _entManager.GetEntityQuery(); } public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, CancellationToken cancelToken) { var owner = blackboard.GetValue(NPCBlackboard.Owner); if (!blackboard.TryGetValue(RangeKey, out var range, _entManager)) return (false, null); if (!_entManager.TryGetComponent(owner, out var medibot)) return (false, null); if (!blackboard.TryGetValue>>("TargetList", out var patients, _entManager)) return (false, null); foreach (var (entity, _) in patients) { if (_mobState.TryGetComponent(entity, out var state) && _injectQuery.HasComponent(entity) && _damageQuery.TryGetComponent(entity, out var damage) && !_recentlyInjected.HasComponent(entity)) { // 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) && _damageable.GetTotalDamage((entity, damage)) == FixedPoint2.Zero) 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() { {TargetKey, entity}, {TargetMoveKey, _entManager.GetComponent(entity).Coordinates}, {NPCBlackboard.PathfindKey, path}, }); } } return (false, null); } }