Skip to content

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 = 0lotr2_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):

  1. Sum over infantry types 0–6 (siege types 7–10 are excluded):

strength = Σ unit_count[type] × auto_calc_multiplier[type]

  1. If a recruit-bonus stack is present, add bonus_count × auto_calc_multiplier[bonus_type].

  2. If strength < 1, set strength = 1.

  3. 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 via lotr2_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 via lotr2_game_battle_halve_loser_army_strength (per-type count ≥ 11 → half; < 11 → 0); if total < 50, army disbanded (message 0x120).
  • 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 × int16 per 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:

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.EXE and GoG Lords2.exe. Reimplementation must key off the same build as the session binary.
  • Multiplayer uses opcodes 0x3c / 0x3d for 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_split before 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_ack before sync (host setting only?).