Skip to content

feat(ai): per-NPC npc:set_movement_hold movement lever - #645

Merged
themrdemonized merged 1 commit into
themrdemonized:prfrom
damiansirbu:npc-movement-hold
Sep 7, 2026
Merged

themrdemonized merged 1 commit into
themrdemonized:prfrom
damiansirbu:npc-movement-hold

Conversation

@damiansirbu

Copy link
Copy Markdown
Contributor

Summary

Adds npc:set_movement_hold(bool) on CAI_Stalker. While true, parse_velocity_mask takes its existing Stand branch: speed 0, movement type Stand, path and destination preserved. Movement resumes when the flag clears. Default false. When unset the change is one bool read added to the existing Stand condition, behavior stays vanilla. The engine computes no condition, the caller decides when to hold.

Use case

A standing NPC hit mid reaction gets started running by the combat planner while the hit animation still owns the legs. The body slides with no walk cycle. Scripts can read the reaction (is_hit_anim_playing from #629, critically_wounded) but cannot stop the mover, that path is C++. With this bind a script holds the NPC while the reaction plays and releases after. The slide is gone and the NPC resumes its route. It also covers any scripted plant-in-place that today needs a smart cover or an animation override.

Constraint

The flag persists while the object is online and has no decay. The caller owns clearing it, a stale true holds the NPC until cleared. Not serialized, a reconstructed object comes back false.

Files changed

  • ai_stalker.h / ai_stalker.cpp / ai_stalker_inline.h: m_movement_hold bool, ctor init false, accessors (17 lines)
  • script_game_object.h / script_game_object_inventory_owner.cpp / script_game_object_script3.cpp: the bind, stalker cast with error log, .def next to set_vision_speed (15 lines)
  • stalker_movement_manager_base.cpp: one line in the parse_velocity_mask Stand condition
  • lua_help_ex.script: manifest entry

8 files, 33 insertions, 1 deletion.

Lua usage

-- hold while an engine hit reaction owns a standing body, release after
if npc:critically_wounded() or (npc:is_hit_anim_playing() and npc:movement_type() == move.stand) then
    npc:set_movement_hold(true)
else
    npc:set_movement_hold(false)
end

Testing

Local DX11 build from this branch, 0 errors. Field session on a full GAMMA install with a live consumer wiring the condition above onto a 200ms monitor plus the hit callback:

  • 144 hold transitions in one fight session. 72 set (32 on the hit callback, 21 on the flinch read, 19 on the stagger read). 72 cleared (44 released when the reaction ended, 28 on death). Zero flags left set at session end.
  • Single-flinch holds released about 1s after set. Continuous fire chained flinches into multi-second holds and the NPC stayed planted through them.
  • Held NPCs resumed their path on release. Standing NPCs hit mid reaction no longer slide.
  • Not exercised: the setter on a non-stalker (the error-log guard path) and an A/B of the unset path, which adds one bool read to the existing condition.

Per-object bool on CAI_Stalker. While set, parse_velocity_mask takes its existing Stand branch, so the engine mover cannot start the NPC and the path survives for resume. Default false keeps vanilla byte-identical. The engine computes no condition. The caller decides when to hold and owns clearing the flag.
@themrdemonized
themrdemonized changed the base branch from all-in-one-vs2022-wpo to pr September 7, 2026 05:08
@themrdemonized
themrdemonized merged commit 8853461 into themrdemonized:pr Sep 7, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants