Skip to content

[Main Branch] Ballistic-Driven Accuracy, Kinematic Leading & Native Full-Auto…#565

Open
Skyki15 wants to merge 3 commits into
themrdemonized:all-in-one-vs2022-wpofrom
Skyki15:all-in-one-vs2022-wpo
Open

[Main Branch] Ballistic-Driven Accuracy, Kinematic Leading & Native Full-Auto…#565
Skyki15 wants to merge 3 commits into
themrdemonized:all-in-one-vs2022-wpofrom
Skyki15:all-in-one-vs2022-wpo

Conversation

@Skyki15

@Skyki15 Skyki15 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

This PR modernizes NPC combat in xray-monolith: deterministic hit resolution,
center-mass aiming, velocity-based leading, decoupled movement accuracy, and
native full-auto fire. All changes are applied on the main branch and build
successfully (Release | x64, engine-vs2022.sln).

================================================================================
OVERALL GAMEPLAY SUMMARY

Before this PR, AI combat suffered from several stacked engine-level handicaps:

  • RNG "ghost bullets" could ignore physically valid hits on the player
  • NPCs hard-aimed at the player's head (instant headshot clustering)
  • Shots aimed at where the target was, not where they would be when hit
  • Running NPCs got massive artificial dispersion multipliers
  • Full-auto weapons fired in stuttering 4–10 round bursts with forced pauses

After this PR:

  • If a bullet hits your mesh, it deals damage — no RNG override
  • NPCs default to upper-torso aim (bip01_spine1), tunable per NPC via LTX/DLTX
  • All NPCs use full intercept prediction against moving targets
  • NPC accuracy while moving matches their standing posture/scope dispersion
  • Full-auto weapons fire continuously at native weapon fire_modes rate

================================================================================

  1. ABOLISH GHOST BULLET RNG
    ================================================================================

FILE
xray-monolith\src\xrGame\Level_bullet_manager_firetrace.cpp

PROBLEM
When a bullet intersected the player actor's bounding sphere, the engine rolled
RNG against Actual Hit Probability (AHP) before running a real mesh collision
test. AHP combined difficulty settings, weapon hit_probability, shooter
hit_probability_factor, and distance falloff (hit_probability_max_dist).

If the roll failed, the bullet played a whine sound and passed through without
damage — a "ghost bullet" — even though the ray already intersected the player.
Stalkers/NPCs were unaffected; only the player actor went through this gate.

IMPLEMENTATION
Removed the entire AHP block (hpf/ahp calculations and Random.randF gate).
Actor hits now always run cform->_RayQuery() for physical mesh collision:
- Mesh hit → damage applies
- Bounding-sphere graze but mesh miss → whine sound, no damage (unchanged)

Left inert (optional follow-up cleanup):
Actor/CWeapon/specific_character hit_probability members and LTX values

GAMEPLAY CHANGE
AI shots that physically connect always register damage on the player.
Difficulty hit_probability_* and weapon hit_probability LTX values no longer
suppress valid hits. Whine sounds still play on near-misses that fail mesh test.

================================================================================
2. MODERNIZED AIMING — CENTER-MASS DEFAULT & DLTX SUPPORT

FILES
xray-monolith\src\xrGame\sight_manager_target.cpp
xray-monolith\src\xrGame\ai\stalker\ai_stalker.cpp

PROBLEM
CSightManager::aim_target() fell back to bip01_head for player and living
stalker targets. Combined with dispersion, this caused frequent instant
headshots. Per-stalker aim_bone_id existed but was script-only — never loaded
from config by default.

On main branch, aim logic uses g_actor for player target detection.
MT branch additionally handles HUD first-person view (camera position when
player is in HUDview) via cast_actor()/cast_stalker().

IMPLEMENTATION
sight_manager_target.cpp:
- Fallback aim bone changed from bip01_head → bip01_spine1
- Main: uses g_actor for player; ::aim_target("bip01_spine1", ...) fallback
- MT: uses cast_actor()/cast_stalker(); HUDview → camera position

ai_stalker.cpp (CAI_Stalker::reload):
aim_bone_id(READ_IF_EXISTS(pSettings, r_string, section,
"default_aim_bone", "bip01_spine1"));

ai_stalker.cpp — global aim_target() (main uses LL_GetTransform bone matrix):
Shared bone lookup used by sight_manager and CAI_Stalker::aim_target()
MT branch uses LL_GetBoneWorldPosition API instead

DLTX / LTX example:
![m_stalker_monolith_experienced]
default_aim_bone = bip01_spine1

Per-NPC override: bip01_head for snipers, bip01_spine2 for lower torso, etc.
Scripts can still override via aim_bone_id() at runtime.

NOT CHANGED: LookAtActor net_Spawn callback still uses bip01_head (visual only).

GAMEPLAY CHANGE
NPCs default to upper-torso aim instead of head snapping.
Significantly reduces unrealistic headshot clustering.
Per-NPC aim bone tuning without recompiling.

================================================================================
3. VELOCITY-BASED AIM LEADING (FULL INTERCEPT)

FILE
xray-monolith\src\xrGame\sight_manager_target.cpp

PROBLEM
SetFirePointLookAngles() computed yaw/pitch toward the target's current bone
position. No prediction for target movement or bullet travel time — NPCs always
shot at where you were, not where you would be when the bullet arrived.

IMPLEMENTATION
Added helpers in anonymous namespace (SetFirePointLookAngles combat path only):

get_object_velocity()
Physics velocity from actor/stalker via character_physics_support;
position-history fallback for other objects.

compute_lead_time()
Solves ballistic intercept: |r + v*t| = bullet_speed * t
Fallback: distance / bullet_speed when no valid quadratic root.

apply_velocity_leading()
Reads bullet_speed from active weapon LTX (fallback 1000 m/s).
Offsets aim point: target_pos += target_velocity * lead_time

Rank scaling was initially added then removed — all NPCs use full intercept
(lead_time multiplier = 1.0, no Rank() factor).

SetPointLookAngles() unchanged (non-combat look, no leading).

GAMEPLAY CHANGE
All NPCs lead moving targets at full strength regardless of rank.
Stationary targets unchanged (zero velocity → leading skipped).
Per-weapon bullet_speed from LTX drives lead calculation.
Moving players face significantly smarter AI gunfire.

================================================================================
4. DECOUPLED WEAPON ACCURACY (MOVEMENT DISPERSION CLEANUP)

FILE
xray-monolith\src\xrGame\ai\stalker\ai_stalker_fire.cpp

PROBLEM
GetWeaponAccuracy() applied separate walk/run LTX multipliers when the NPC
was moving (path not completed). Example: disp_run_stand = 6.3 in m_stalker.ltx
could push dispersion 3–45× higher while running — causing visible intentional
misses at point-blank even when aim was on target.

IMPLEMENTATION
Removed the entire !path_completed() movement branch.

GetWeaponAccuracy() now always uses body-state dispersion:
stand + scope → m_disp_stand_stand_zoom
stand → m_disp_stand_stand
crouch + scope → m_disp_stand_crouch_zoom
crouch → m_disp_stand_crouch
fallback → m_disp_stand_stand

Unchanged: m_fRankDisperison, Lua _g.CAI_Stalker__GetWeaponAccuracy hook,
g_dispersion_base / g_dispersion_factor.

Inert LTX (no longer read for movement): disp_walk_stand, disp_walk_crouch,
disp_run_stand, disp_run_crouch.

GAMEPLAY CHANGE
NPCs no longer spray wildly just because they are walking or running.
Point-blank shots while moving are much more accurate.
Accuracy driven by rank dispersion, weapon, posture, and scope — not path state.

================================================================================
5. NATIVE FULL-AUTO FIRE (WEAPON QUEUE MANAGEMENT)

FILE
xray-monolith\src\xrGame\WeaponMagazined.cpp

PROBLEM
NPC firing was artificially burst-limited:
1. CObjectHandlerPlanner::set_queue_params() set random queue sizes
(auto_min/max_queue_size_close = 4–10) with QueueWait pauses between bursts
2. Planner overrode weapon queue size after OnH_A_Chield attach
3. switch2_Fire() always set m_bFireSingleShot = true

Full-auto weapons fired in stuttering bursts instead of continuous fire.

IMPLEMENTATION
SetQueueSize(int size) — refactored:
fire_modes weapon, mode < 0 → WEAPON_ININITE_QUEUE (-1), full-auto
fire_modes weapon, mode > 0 → m_iQueueSize = mode (single/burst count)
No fire_modes, NPC parent → WEAPON_ININITE_QUEUE
No fire_modes, player → passed size unchanged (legacy)
Ignores planner/script burst overrides when fire mode applies

OnH_A_Chield():
Fire-mode weapons → SetQueueSize(GetCurrentFireMode()) for all parents
Non-fire-mode NPC weapons → WEAPON_ININITE_QUEUE

switch2_Fire():
m_bFireSingleShot = (m_iQueueSize >= 0)

Fire mode mapping (fire_modes LTX):
-1 → infinite queue, continuous auto
1 → single shot
2 → 2-round burst, etc.

GAMEPLAY CHANGE
NPCs with full-auto weapons fire continuously while engaging.
Burst/single modes still respected via weapon fire_modes LTX.
Eliminates engine-level stutter between artificial burst gaps.
Player weapon behavior unchanged.

================================================================================
MT BRANCH BUILD FIXES (MT ONLY — NOT NEEDED ON MAIN)

During MT branch integration, sight_manager_target.cpp had a bad merge that caused
compile errors (missing braces, duplicate logic). Fixed on MT:

  • Clean aim_target() block: MT cast_actor/HUDview/camera + bip01_spine1 fallback
  • Restored global aim_target() in ai_stalker.cpp (LNK2001 unresolved external)
  • Uses MT LL_GetBoneWorldPosition API

Main branch uses g_actor and existing LL_GetTransform aim_target(); no MT merge
fixes required.

Build verified: engine-vs2022.sln Rebuild Release|x64 — success (both branches).

================================================================================
FILES MODIFIED (SUMMARY)

Level_bullet_manager_firetrace.cpp Ghost bullet RNG removed
sight_manager_target.cpp Center-mass aim, velocity leading, MT aim
ai_stalker.cpp default_aim_bone LTX, global aim_target()
ai_stalker_fire.cpp Movement dispersion removed
WeaponMagazined.cpp Native full-auto queue management

================================================================================
OPTIONAL FOLLOW-UP CLEANUP (NOT IN SCOPE)

  • Remove inert hit_probability infrastructure (Actor, Weapon, specific_character)
  • Remove inert disp_walk_* / disp_run_* load from ai_stalker.cpp reload()
  • Remove inert auto_queue_size effect documentation from stalker LTX guides

… Fire

ABOLISH GHOST BULLET RNG (hit_probability)
MODERNIZED AIMING — CENTER-MASS DEFAULT & DLTX SUPPORT
VELOCITY-BASED AIM LEADING (FULL INTERCEPT)
DECOUPLED WEAPON ACCURACY (MOVEMENT DISPERSION CLEANUP)
NATIVE FULL-AUTO FIRE (WEAPON QUEUE MANAGEMENT)
@Skyki15 Skyki15 changed the title [NPC] Ballistic-Driven Accuracy, Kinematic Leading & Native Full-Auto… [Main Branch] Ballistic-Driven Accuracy, Kinematic Leading & Native Full-Auto… Jun 9, 2026
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.

1 participant