Skip to content

Fix drag soft-lock when dragging unconscious mercs - #3

Open
tais wants to merge 2 commits into
masterfrom
fix/drag-unconscious-softlock
Open

tais wants to merge 2 commits into
masterfrom
fix/drag-unconscious-softlock

Conversation

@tais

@tais tais commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the intermittent soft-lock / ghosted-merc bug when dragging unconscious mercenaries in tactical combat.

SOLDIERTYPE::IsDragging() defaults to aStopIfConditionNotSatisfied = true, which makes it call CancelDrag() — and reposition the dragged body via EVENT_InternalSetSoldierPosition — whenever the drag condition isn't currently satisfied. That side effect was being triggered from read-only consumers including the pathfinding cost loops in FindBestPath / PlotPath, AP cost queries, movement noise, the drag face icon, and carried weight.

Two consequences in combat:

  • Constant cancellation / "ghost" merc. Every UI tick (cursor pathing, AI pathing, reachable scans) re-ran IsDragging(true), which kept cancelling the drag and yanking the body back to tile-center, so the drag never visibly "stuck."
  • Soft-lock. Repositioning a soldier from inside FindBestPath mutates gpWorldLevelData while the path search is iterating it. FindBestPath isn't re-entrant — that corrupts the search state and the engine ends up waiting on a move that never completes.

Fix: pass false at the 8 read-only call sites so they only read state. A genuinely-dragging soldier still has the body adjacent, so IsDragging(false) returns TRUE and the drag AP/noise/weight modifiers still apply. Only the spurious "condition not satisfied → cancel + teleport body" branch is removed. The genuine state-changing callers (the move executor at Soldier Control.cpp:11519, the ESC handler at Turn Based Input.cpp:2224) keep the cancelling form.

Touches: PATHAI.cpp ×3, Points.cpp ×2, opplist.cpp, Faces.cpp, Items.cpp. +16/-8 lines. Mechanical one-token edit per site.

Related: commit 3d529400 ("Copy drag info to dummy soldier (1dot13#255)") already patched one symptom of the same IsDragging/pathfinding collision (1-tile location shift on load with A* pathfinding).

Test plan

  • Compiles cleanly
  • Drag an unconscious merc one or more tiles in turn-based combat with A* pathfinding enabled — drag should hold, body should follow the dragger without snapping back
  • Repeat with another merc nearby running AI turns (heavy pathfinding) — no freeze
  • Drag still cancels correctly on ESC, on the dragged merc getting up, and when conditions actually become invalid (target moved out of reach, dragger stands up)
  • AP cost while dragging is unchanged (drag modifier still applies)

tais added 2 commits May 25, 2026 16:48
IsDragging() defaults to aStopIfConditionNotSatisfied=true, so these
read-only cost queries could call CancelDrag() and reposition the
dragged body mid-pathfind, corrupting FindBestPath's view of the world
and freezing the game. Pass false so they only read state.
Same IsDragging() side-effect as the path fix: these read-only
queries (movement noise, face drag icon, carried weight) must not
cancel the drag or reposition the dragged body. Pass false.
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