Auto-battle calculation (quick resolve)#
Summary#
When two armies meet, the player may fight the battle tactically (real-time figures on the battle map) or auto-calculate the outcome. Auto-calculation runs entirely on the campaign layer: it compares weighted army strengths, derives a winner and survivor percentage from a static lookup table, updates army records, then applies province capture and diplomacy through the normal post-battle path.
The same casualty formula is also invoked after a played battle when the player returns to the campaign map, applied to whatever unit counts remain in the army records.
Play vs auto-calculate#
| Choice | Player action | Code path |
|---|---|---|
| Fight | Battle prep screen (mode 0x12), Play button |
turn_phase = 1 → tactical entry (FUN_004778a0, screen mode 0x29) |
| Auto-calculate | Battle prep screen, Auto-calculate button | turn_phase = 0 → lotr2_game_ui_enter_battle_prep_screen |
| Auto-calculate | Game option disables battle prep | lotr2_game_turn_phase4_ack == 0 |
| Auto-calculate | No human on either side | lotr2_game_battle_prep_init_combat returns 0 |
| Auto-calculate | Multiplayer prep timeout | ~20–30 s with no click (FUN_004bbea7) |
The game-options toggle is lotr2_game_turn_phase4_ack (eng table 0x12, row 4
in the options dialog). When off (0), battles skip the prep screen and
auto-calculate immediately unless both armies are human-controlled AI matchups
(DAT_004ee89c == 1 forces prep when the option is on).
Rules / algorithm#
1. Compute combat strength#
For each army, lotr2_game_army_calc_combat_strength(army_slot):
- Sum over infantry types 0–6 (siege types 7–10 are excluded):
strength = Σ unit_count[type] × auto_calc_multiplier[type]
-
If a recruit-bonus stack is present, add
bonus_count × auto_calc_multiplier[bonus_type]. -
If
strength < 1, setstrength = 1. -
Add flat +20 (
0x14).
Multipliers come from a static table (see
../../structs/lotr2_unit_stats.md).
They are not the UI-only army strength display multipliers.
2. Siege defender bonus#
When the encounter is a siege (DAT_0053e8fc != 0), multiply defender
strength only by a castle-tier percentage before comparing sides:
Castle tier (DAT_0055401c) |
Defender strength × |
|---|---|
| 0 | 160% |
| 1 | 200% |
| 2 | 250% |
| 3 | 320% |
| 4+ | 400% |
Attacker strength is unchanged.
3. Strength ratio and survivor percentage#
Let A = attacker strength, D = defender strength (after siege bonus).
if A < D:
ratio = (D × 100) / A // defender stronger
winner = defender
else:
ratio = (A × 100) / D // attacker stronger or equal
winner = attacker
survivor_pct = lotr2_game_lookup_threshold(ratio, casualty_table, default=100)
Lookup walks threshold pairs in order; on the first pair where
ratio < threshold, return the paired survivor percentage. If no pair matches
(ratio ≥ last threshold), return 100 (winner keeps all remaining troops).
4. Apply casualties#
lotr2_game_battle_apply_casualty_split:
- Winner (
DAT_0057c924): for each of 7 unit stacks and any recruit bonus,new_count = (old_count × survivor_pct) / 100(integer truncation vialotr2_game_mul_percent). Recompute total headcount. - Loser: zero all 7 stacks, remove recruit bonus, set total headcount to 0.
5. Resolve campaign outcome#
lotr2_game_battle_resolve_siege_outcome:
- Province capture when attacker wins and siege/raid flags require it.
- Normal auto-calc / played-to-completion: loser army released
(
lotr2_game_army_release_and_clear_links). - Tactical timeout (
DAT_0056d5c8 == 1, set when battle timer expires): loser stacks halved vialotr2_game_battle_halve_loser_army_strength(per-type count ≥ 11 → half; < 11 → 0); if total < 50, army disbanded (message0x120). - Diplomacy attitude delta +20 toward the victor; end-turn and upkeep refresh.
Casualty threshold tables#
Regenerate with python3 tools/ghidra/dump_combat_tables.py --all.
Retail (LORDS2.EXE @ 0x004f9f10)#
| If ratio < | Winner keeps |
|---|---|
| 105 | 10% |
| 120 | 25% |
| 150 | 40% |
| 180 | 55% |
| 220 | 70% |
| 280 | 80% |
| 360 | 90% |
| 500 | 95% |
| 750 | 97% |
| 1000 | 98% |
| ≥ 1000 | 100% (default) |
GoG (Lords2.exe @ 0x004de710)#
| If ratio < | Winner keeps |
|---|---|
| 110 | 10% |
| 130 | 20% |
| 160 | 30% |
| 180 | 40% |
| 220 | 50% |
| 270 | 65% |
| 360 | 80% |
| 500 | 90% |
| 700 | 95% |
| 900 | 98% |
| ≥ 900 | 100% (default) |
Example (retail): attacker strength 300, defender 100 → ratio = 300. 300 < 360 → winner (attacker) keeps 90% of each stack; defender destroyed.
Example (retail): attacker 120, defender 100 → ratio = 120. 120 < 150 → winner keeps 40%.
Data model#
- Army unit counts: 7 ×
int16per army record + optional recruit bonus byte. - Winner slot: global
DAT_0057c924(attacker or defender army index). - Siege flag:
DAT_0053e8fc; castle tier:DAT_0055401c.
See ../../structs/lotr2_battle_figure.md
for tactical battle representation (separate from this campaign-layer formula).
Evidence#
| Symbol | Address (retail) | Role |
|---|---|---|
lotr2_game_battle_apply_casualty_split |
0x004c365d |
Core auto-calc casualty split |
lotr2_game_army_calc_combat_strength |
0x004c3c05 |
Weighted strength + 20 |
lotr2_game_lookup_threshold |
0x00404e4b |
Ratio → survivor % |
lotr2_game_mul_percent |
0x00404d6b |
(a × b) / 100 |
lotr2_game_ratio_percent |
0x00404dc1 |
(a × 100) / b |
lotr2_game_battle_resolve_siege_outcome |
0x004c3cde |
Post-battle campaign effects |
lotr2_game_battle_prep_init_combat |
0x004bf4d0 |
Prep screen / human-side check |
lotr2_game_ui_enter_battle_prep_screen |
0x0043b622 |
Auto-calc from prep UI |
lotr2_game_battle_start_from_army_pair |
0x004a7158 |
Open-field encounter entry |
lotr2_game_battle_on_army_enter_province |
0x004a6c68 |
Province invasion entry |
lotr2_game_turn_phase4_ack |
0x0059b8a4 |
Auto-resolve game option |
Evidence docs:
../../architecture/combat/MODULE.md../../architecture/game/MODULE.md../../structs/lotr2_unit_stats.md
Confidence#
confirmed — strength formula, siege defender bonus tiers, lookup semantics,
and both retail/GoG static tables verified by PE dump
(tools/ghidra/dump_combat_tables.py) and decompilation of
lotr2_game_battle_apply_casualty_split in src/Lords2-gog.exe.c.
likely — exact English label for the lotr2_game_turn_phase4_ack game-option
row (eng table 0x12 index 4); battle prep button captions (eng table 0x50).
Interop notes#
- Auto-calc strength multipliers and casualty thresholds differ between
retail
LORDS2.EXEand GoGLords2.exe. Reimplementation must key off the same build as the session binary. - Multiplayer uses opcodes
0x3c/0x3dfor battle prep entry; wire details belong in../networking/. - Played battles use the tactical casualty model (100-point damage accumulator);
auto-calc does not. Both paths converge on
lotr2_game_battle_apply_casualty_splitbefore campaign resolution.
Open questions#
- Confirm eng-string text for auto-resolve option and prep-screen buttons from
l2.eng. - Whether MP clients must agree on
turn_phase4_ackbefore sync (host setting only?).