diff --git a/gamedata/scripts/lua_help_ex.script b/gamedata/scripts/lua_help_ex.script index 97d0205257..7d305dbc70 100644 --- a/gamedata/scripts/lua_help_ex.script +++ b/gamedata/scripts/lua_help_ex.script @@ -679,6 +679,7 @@ void set_hit_redirect(float max, float falloff) // Per-object bonus toward the last attacker in enemy selection, replacing the flat CQB step. max < 0 keeps vanilla. max >= 0 subtracts max*(1 - d2/falloff^2) from the attacker cost within falloff metres, so a close attacker is prioritised and a distant one fades to 0. The victim turns toward the shooter without a forced sighting. Not serialized, re-set on spawn. void set_view_distance_factor(float factor) // Per-object view-DISTANCE factor, the range sibling of set_vision_speed: multiplies object_visible_distance's return, composing with the per-state max_view_distance LTX multiplier. 1.0 = vanilla, higher sees farther, negative clamps to 0. Persists while online; re-set on spawn. void set_health_restore_boost(float value) // Per-object additive passive health-restore boost: adds value to the condition's m_fV_HealthRestore velocity applied every UpdateHealth while alive (base ~0.00003/s for stalkers), so the entity regenerates HP over time with no item. 0 = vanilla, higher = faster idle regen, negative clamps to 0. Stalkers and monsters (any CEntityAlive). Persists while online; re-set on spawn (condition rebuilt on re-online). + void set_movement_hold(bool value) // Per-object movement hold: while true, parse_velocity_mask routes into its Stand branch, so the stalker stays planted (speed 0, movement type Stand) with the path and destination preserved; movement resumes when cleared. false = vanilla. Stalkers only. Not serialized; the caller owns clearing it - a stale true holds the NPC in place indefinitely. // Stalker NPCs function get_enable_anomalies_pathfinding() diff --git a/src/xrGame/ai/stalker/ai_stalker.cpp b/src/xrGame/ai/stalker/ai_stalker.cpp index b87e227d8a..740289120b 100644 --- a/src/xrGame/ai/stalker/ai_stalker.cpp +++ b/src/xrGame/ai/stalker/ai_stalker.cpp @@ -87,7 +87,8 @@ CAI_Stalker::CAI_Stalker() : m_fire_queue_size_k(-1.f), m_fire_queue_interval_k(-1.f), m_take_items_enabled(true), - m_death_sound_enabled(true) + m_death_sound_enabled(true), + m_movement_hold(false) { m_pPhysics_support = NULL; m_animation_manager = NULL; diff --git a/src/xrGame/ai/stalker/ai_stalker.h b/src/xrGame/ai/stalker/ai_stalker.h index ef24bbbb53..7353884e58 100644 --- a/src/xrGame/ai/stalker/ai_stalker.h +++ b/src/xrGame/ai/stalker/ai_stalker.h @@ -879,9 +879,13 @@ class CAI_Stalker : IC void death_sound_enabled(bool value); IC bool death_sound_enabled() const; + IC void movement_hold(bool value); + IC bool movement_hold() const; + private: bool m_take_items_enabled; bool m_death_sound_enabled; + bool m_movement_hold; public: smart_cover::cover const* get_current_smart_cover(); diff --git a/src/xrGame/ai/stalker/ai_stalker_inline.h b/src/xrGame/ai/stalker/ai_stalker_inline.h index 78e4140a0d..d83679970f 100644 --- a/src/xrGame/ai/stalker/ai_stalker_inline.h +++ b/src/xrGame/ai/stalker/ai_stalker_inline.h @@ -212,6 +212,16 @@ IC bool CAI_Stalker::death_sound_enabled() const return (m_death_sound_enabled); } +IC void CAI_Stalker::movement_hold(bool value) +{ + m_movement_hold = value; +} + +IC bool CAI_Stalker::movement_hold() const +{ + return (m_movement_hold); +} + IC u32 CAI_Stalker::pstl_min_queue_size_far() const { return (m_pstl_min_queue_size_far); diff --git a/src/xrGame/script_game_object.h b/src/xrGame/script_game_object.h index 9174768b0e..6f3adff9a5 100644 --- a/src/xrGame/script_game_object.h +++ b/src/xrGame/script_game_object.h @@ -844,6 +844,7 @@ class CScriptGameObject void set_hit_redirect(float max, float falloff); void set_view_distance_factor(float value); void set_health_restore_boost(float value); + void set_movement_hold(bool value); bool can_kill_enemy(); bool can_kill_member(); bool fire_make_sense(); diff --git a/src/xrGame/script_game_object_inventory_owner.cpp b/src/xrGame/script_game_object_inventory_owner.cpp index 6d5834d28e..d03beb5449 100644 --- a/src/xrGame/script_game_object_inventory_owner.cpp +++ b/src/xrGame/script_game_object_inventory_owner.cpp @@ -1957,6 +1957,19 @@ bool CScriptGameObject::is_hit_anim_playing() return (entity_alive->character_physics_support()->is_hit_anim_playing()); } +void CScriptGameObject::set_movement_hold(bool value) +{ + CAI_Stalker* stalker = smart_cast(&object()); + if (!stalker) + { + ai().script_engine().script_log(ScriptStorage::eLuaMessageTypeError, + "CAI_Stalker : cannot access class member set_movement_hold!"); + return; + } + + stalker->movement_hold(value); +} + bool CScriptGameObject::can_kill_enemy() { CAI_Stalker* stalker = smart_cast(&object()); diff --git a/src/xrGame/script_game_object_script3.cpp b/src/xrGame/script_game_object_script3.cpp index ff0ba64508..491a6fc234 100644 --- a/src/xrGame/script_game_object_script3.cpp +++ b/src/xrGame/script_game_object_script3.cpp @@ -68,6 +68,7 @@ class_ script_register_game_object2(class_ .def("set_hit_redirect", SAFE_WRAP(&CScriptGameObject::set_hit_redirect)) .def("set_view_distance_factor", SAFE_WRAP(&CScriptGameObject::set_view_distance_factor)) .def("set_health_restore_boost", SAFE_WRAP(&CScriptGameObject::set_health_restore_boost)) + .def("set_movement_hold", SAFE_WRAP(&CScriptGameObject::set_movement_hold)) .def("active_sound_count", SAFE_WRAP((int (CScriptGameObject::*)())(&CScriptGameObject::active_sound_count))) .def("active_sound_count", SAFE_WRAP((int (CScriptGameObject::*)(bool))(&CScriptGameObject::active_sound_count))) .def("best_cover", SAFE_WRAP(&CScriptGameObject::best_cover)) diff --git a/src/xrGame/stalker_movement_manager_base.cpp b/src/xrGame/stalker_movement_manager_base.cpp index 1aa009a64f..935297beb0 100644 --- a/src/xrGame/stalker_movement_manager_base.cpp +++ b/src/xrGame/stalker_movement_manager_base.cpp @@ -429,6 +429,7 @@ void stalker_movement_manager_base::parse_velocity_mask(stalker_movement_params& sight_manager_enable_guard guard(object().sight(), true); if ( + object().movement_hold() || (movement_params.m_movement_type == eMovementTypeStand) || path().empty() || (path().size() <= detail().curr_travel_point_index()) ||