--- /dev/null
+using Robust.Server.Containers;
+
+namespace Content.Server.NPC.HTN.Preconditions;
+
+/// <summary>
+/// Checks if the owner in container or not
+/// </summary>
+public sealed partial class InContainerPrecondition : HTNPrecondition
+{
+ private ContainerSystem _container = default!;
+
+ [ViewVariables(VVAccess.ReadWrite)] [DataField("isInContainer")] public bool IsInContainer = true;
+
+ public override void Initialize(IEntitySystemManager sysManager)
+ {
+ base.Initialize(sysManager);
+ _container = sysManager.GetEntitySystem<ContainerSystem>();
+ }
+
+ public override bool IsMet(NPCBlackboard blackboard)
+ {
+ var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+ return IsInContainer && _container.IsEntityInContainer(owner) ||
+ !IsInContainer && !_container.IsEntityInContainer(owner);
+ }
+}
--- /dev/null
+using Robust.Server.Containers;
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat;
+
+public sealed partial class ContainerOperator : HTNOperator
+{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ private ContainerSystem _container = default!;
+ private EntityQuery<TransformComponent> _transformQuery;
+
+ [DataField("shutdownState")]
+ public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.TaskFinished;
+
+ [DataField("targetKey", required: true)]
+ public string TargetKey = default!;
+
+ public override void Initialize(IEntitySystemManager sysManager)
+ {
+ base.Initialize(sysManager);
+ _container = sysManager.GetEntitySystem<ContainerSystem>();
+ _transformQuery = _entManager.GetEntityQuery<TransformComponent>();
+ }
+
+ public override void Startup(NPCBlackboard blackboard)
+ {
+ base.Startup(blackboard);
+ var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+ if (!_container.TryGetOuterContainer(owner, _transformQuery.GetComponent(owner), out var outerContainer) && outerContainer == null)
+ return;
+
+ var target = outerContainer.Owner;
+ blackboard.SetValue(TargetKey, target);
+ }
+
+ public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+ {
+ return HTNOperatorStatus.Finished;
+ }
+}
--- /dev/null
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.NPC.Components;
+using Content.Server.Storage.EntitySystems;
+using Content.Shared.CombatMode;
+using Robust.Server.Containers;
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat.Melee;
+
+public sealed partial class EscapeOperator : HTNOperator, IHtnConditionalShutdown
+{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ private ContainerSystem _container = default!;
+ private EntityStorageSystem _entityStorage = default!;
+
+ [DataField("shutdownState")]
+ public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.TaskFinished;
+
+ [DataField("targetKey", required: true)]
+ public string TargetKey = default!;
+
+ public override void Initialize(IEntitySystemManager sysManager)
+ {
+ base.Initialize(sysManager);
+ _container = sysManager.GetEntitySystem<ContainerSystem>();
+ _entityStorage = sysManager.GetEntitySystem<EntityStorageSystem>();
+ }
+
+ public override void Startup(NPCBlackboard blackboard)
+ {
+ base.Startup(blackboard);
+ var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+ var target = blackboard.GetValue<EntityUid>(TargetKey);
+
+ if (_entityStorage.TryOpenStorage(owner, target))
+ {
+ TaskShutdown(blackboard, HTNOperatorStatus.Finished);
+ return;
+ }
+
+ var melee = _entManager.EnsureComponent<NPCMeleeCombatComponent>(owner);
+ melee.MissChance = blackboard.GetValueOrDefault<float>(NPCBlackboard.MeleeMissChance, _entManager);
+ melee.Target = target;
+ }
+
+ public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+ CancellationToken cancelToken)
+ {
+ var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+ if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager))
+ {
+ return (false, null);
+ }
+
+ if (!_container.IsEntityInContainer(owner))
+ {
+ return (false, null);
+ }
+
+ if (_entityStorage.TryOpenStorage(owner, target))
+ {
+ return (false, null);
+ }
+
+ return (true, null);
+ }
+
+ public void ConditionalShutdown(NPCBlackboard blackboard)
+ {
+ var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+ _entManager.System<SharedCombatModeSystem>().SetInCombatMode(owner, false);
+ _entManager.RemoveComponent<NPCMeleeCombatComponent>(owner);
+ blackboard.Remove<EntityUid>(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<EntityUid>(NPCBlackboard.Owner);
+ HTNOperatorStatus status;
+
+ if (_entManager.TryGetComponent<NPCMeleeCombatComponent>(owner, out var combat) &&
+ blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager))
+ {
+ combat.Target = target;
+
+ // Success
+ if (!_container.IsEntityInContainer(owner))
+ {
+ status = HTNOperatorStatus.Finished;
+ }
+ else
+ {
+ if (_entityStorage.TryOpenStorage(owner, target))
+ {
+ 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;
+ }
+}
--- /dev/null
+using Content.Shared.Movement.Pulling.Components;
+using Content.Shared.Movement.Pulling.Systems;
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat;
+
+public sealed partial class UnPullOperator : HTNOperator
+{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ private PullingSystem _pulling = default!;
+
+ private EntityQuery<PullableComponent> _pullableQuery;
+
+ [DataField("shutdownState")]
+ public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.TaskFinished;
+
+ public override void Initialize(IEntitySystemManager sysManager)
+ {
+ base.Initialize(sysManager);
+ _pulling = sysManager.GetEntitySystem<PullingSystem>();
+ _pullableQuery = _entManager.GetEntityQuery<PullableComponent>();
+ }
+
+ public override void Startup(NPCBlackboard blackboard)
+ {
+ base.Startup(blackboard);
+ var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+ _pulling.TryStopPull(owner, _pullableQuery.GetComponent(owner), owner);
+ }
+
+ public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+ {
+ return HTNOperatorStatus.Finished;
+ }
+}
--- /dev/null
+using Content.Server.Buckle.Systems;
+using Content.Shared.Buckle.Components;
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat;
+
+public sealed partial class UnbuckleOperator : HTNOperator
+{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ private BuckleSystem _buckle = default!;
+
+ [DataField("shutdownState")]
+ public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.TaskFinished;
+
+ public override void Initialize(IEntitySystemManager sysManager)
+ {
+ base.Initialize(sysManager);
+ _buckle = sysManager.GetEntitySystem<BuckleSystem>();
+ }
+
+ public override void Startup(NPCBlackboard blackboard)
+ {
+ base.Startup(blackboard);
+ var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+ if (!_entManager.TryGetComponent<BuckleComponent>(owner, out var buckle) || !buckle.Buckled)
+ return;
+
+ _buckle.TryUnbuckle(owner, owner, true, buckle);
+ }
+
+ public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+ {
+ return HTNOperatorStatus.Finished;
+ }
+}
- !type:HTNCompoundTask
task: PickupMeleeCompound
+ - preconditions:
+ - !type:BuckledPrecondition
+ isBuckled: true
+ tasks:
+ - !type:HTNPrimitiveTask
+ shutdownState: TaskFinished
+ operator: !type:UnbuckleOperator
+
+ - preconditions:
+ - !type:InContainerPrecondition
+ isInContainer: true
+ tasks:
+ - !type:HTNCompoundTask
+ task: EscapeCompound
+
+ - preconditions:
+ - !type:PulledPrecondition
+ isPulled: true
+ tasks:
+ - !type:HTNPrimitiveTask
+ shutdownState: TaskFinished
+ operator: !type:UnPullOperator
+
# Melee combat (unarmed or otherwise)
- tasks:
- !type:HTNPrimitiveTask
proto: NearbyMeleeTargets
key: Target
+- type: htnCompound
+ id: EscapeCompound
+ branches:
+ - tasks:
+ - !type:HTNPrimitiveTask
+ shutdownState: TaskFinished
+ operator: !type:ContainerOperator
+ targetKey: Target
+ - !type:HTNPrimitiveTask
+ operator: !type:EscapeOperator
+ targetKey: Target
+ preconditions:
+ - !type:InContainerPrecondition
+ isInContainer: true
+
- type: htnCompound
id: MeleeAttackOrderedTargetCompound
branches: