using System.Threading; using System.Threading.Tasks; using Content.Server.NPC.Components; using Content.Shared.CombatMode; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat.Melee; /// /// Attacks the specified key in melee combat. /// public sealed partial class MeleeOperator : HTNOperator, IHtnConditionalShutdown { [Dependency] private readonly IEntityManager _entManager = default!; /// /// When to shut the task down. /// [DataField("shutdownState")] public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.TaskFinished; /// /// Key that contains the target entity. /// [DataField("targetKey", required: true)] public string TargetKey = default!; /// /// Minimum damage state that the target has to be in for us to consider attacking. /// [DataField("targetState")] public MobState TargetState = MobState.Alive; // Like movement we add a component and pass it off to the dedicated system. public override void Startup(NPCBlackboard blackboard) { base.Startup(blackboard); var melee = _entManager.EnsureComponent(blackboard.GetValue(NPCBlackboard.Owner)); melee.MissChance = blackboard.GetValueOrDefault(NPCBlackboard.MeleeMissChance, _entManager); melee.Target = blackboard.GetValue(TargetKey); } public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, CancellationToken cancelToken) { // Don't attack if they're already as wounded as we want them. if (!blackboard.TryGetValue(TargetKey, out var target, _entManager)) { return (false, null); } if (_entManager.TryGetComponent(target, out var mobState) && mobState.CurrentState > TargetState) { return (false, null); } return (true, null); } public void ConditionalShutdown(NPCBlackboard blackboard) { var owner = blackboard.GetValue(NPCBlackboard.Owner); _entManager.System().SetInCombatMode(owner, false); _entManager.RemoveComponent(owner); blackboard.Remove(TargetKey); } public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status) { base.TaskShutdown(blackboard, status); ConditionalShutdown(blackboard); } public override void PlanShutdown(NPCBlackboard blackboard) { base.PlanShutdown(blackboard); ConditionalShutdown(blackboard); } public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) { base.Update(blackboard, frameTime); var owner = blackboard.GetValue(NPCBlackboard.Owner); HTNOperatorStatus status; if (_entManager.TryGetComponent(owner, out var combat) && blackboard.TryGetValue(TargetKey, out var target, _entManager) && target != EntityUid.Invalid) { combat.Target = target; // Success if (_entManager.TryGetComponent(target, out var mobState) && mobState.CurrentState > TargetState) { status = HTNOperatorStatus.Finished; } else { switch (combat.Status) { case CombatStatus.TargetOutOfRange: case CombatStatus.Normal: status = HTNOperatorStatus.Continuing; break; default: status = HTNOperatorStatus.Failed; break; } } } else { status = HTNOperatorStatus.Failed; } // Mark it as finished to continue the plan. if (status == HTNOperatorStatus.Continuing && ShutdownState == HTNPlanState.PlanFinished) { status = HTNOperatorStatus.Finished; } return status; } }