summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rules.ts615
1 files changed, 304 insertions, 311 deletions
diff --git a/rules.ts b/rules.ts
index 2dfb703..df10dff 100644
--- a/rules.ts
+++ b/rules.ts
@@ -192,7 +192,6 @@ interface Battle {
attacker: Player,
loser: Player,
array: Lord[],
- ah: number[],
valour: number[],
fled: Lord[],
routed: Lord[],
@@ -295,6 +294,7 @@ interface State {
take_ship?(): void,
tax?(): void,
+ vanguard?(): void,
final_charge?(): void,
commission_of_array?(): void,
loyalty_and_trust?(): void,
@@ -1382,6 +1382,24 @@ function lord_has_unrouted_units(lord: Lord) {
return result
}
+function lord_has_unrouted_troops(lord: Lord) {
+ // Don't check here for Retinue or Vassals.
+ for (let x of simple_force_type) {
+ if (get_lord_forces(lord, x) > 0)
+ return true
+ }
+ return false
+}
+
+function lord_has_routed_troops(lord: Lord) {
+ // Don't check here for Retinue or Vassals.
+ for (let x of simple_force_type) {
+ if (get_lord_routed_forces(lord, x) > 0)
+ return true
+ }
+ return false
+}
+
function find_lord_with_capability_card(c: Card) {
for (let lord of all_friendly_lords())
if (lord_has_capability_card(lord, c))
@@ -1868,6 +1886,7 @@ function automatic_success(lord: Lord, score) {
&& game.state === "parley")
score = 6
if (game.active === YORK
+ && is_levy_phase()
&& game.levy_flags.succession === 1
&& game.state === "parley")
score = 6
@@ -5205,6 +5224,13 @@ function take_spoils(type: Asset) {
*/
+const battle_strike_positions = [ D1, D2, D3, A1, A2, A3 ]
+
+const battle_steps = [
+ { name: "Archery", hits: count_archery_hits },
+ { name: "Melee", hits: count_melee_hits },
+]
+
function remove_lord_from_battle(lord) {
if (set_has(game.battle.reserves, lord)) {
array_remove(game.battle.reserves, lord)
@@ -5219,7 +5245,7 @@ function remove_lord_from_battle(lord) {
}
function get_lord_array_position(lord: Lord) {
- for (let p = 0; p < 12; ++p)
+ for (let p of battle_strike_positions)
if (game.battle.array[p] === lord)
return p
return -1
@@ -5243,13 +5269,6 @@ function filled(pos) {
return false
}
-const battle_strike_positions = [ D1, D2, D3, A1, A2, A3 ]
-
-const battle_steps = [
- { name: "Archery", hits: count_archery_hits },
- { name: "Melee", hits: count_melee_hits },
-]
-
function count_archery_hits(lord: Lord) {
let hits = 0
hits += get_lord_forces(lord, LONGBOWMEN) << 2
@@ -5313,7 +5332,7 @@ function is_battle_over() {
function has_no_unrouted_forces() {
// All unrouted lords are either in battle array or in reserves
- for (let p = 0; p < 6; ++p)
+ for (let p of battle_strike_positions)
if (is_friendly_lord(game.battle.array[p]))
return false
for (let lord of game.battle.reserves)
@@ -5339,7 +5358,10 @@ function is_melee_step() {
}
function has_strike(pos: number) {
- return game.battle.ah[pos] > 0
+ let lord = game.battle.array[pos]
+ if (lord !== NOBODY)
+ return count_lord_hits(lord) > 0
+ return false
}
// Capabilities adding troops at start of the battle
@@ -5440,7 +5462,6 @@ function goto_battle() {
NOBODY, NOBODY, NOBODY,
NOBODY, NOBODY, NOBODY
],
- ah: [ 0, 0, 0, 0, 0, 0 ],
valour: Array(lord_count).fill(0),
routed_vassals: [],
engagements: [],
@@ -6053,78 +6074,6 @@ function is_leeward_battle_line_in_play(lord: Lord) {
return false
}
-// === BATTLE EVENT: CULVERINS AND FALCONETS ===
-
-function goto_culverins() {
- let can_play = false
- for (let lord of game.battle.array) {
- if (is_lancaster_lord(lord) && lord_has_capability(lord, AOW_LANCASTER_CULVERINS_AND_FALCONETS))
- can_play = true
- if (is_york_lord(lord) && lord_has_capability(lord, AOW_YORK_CULVERINS_AND_FALCONETS))
- can_play = true
- }
- if (can_play) {
- set_active_defender()
- game.state = "culverins_and_falconets"
- game.who = NOBODY
- }
- else {
- goto_engagement_total_hits()
- }
-}
-
-function artillery_hits(ahits) {
- if (is_attacker()) {
- game.battle.attacker_artillery = ahits*2
- }
- if (is_defender()) {
- game.battle.defender_artillery = ahits*2
- }
-}
-
-states.culverins_and_falconets = {
- inactive: "Culverins and Falconets",
- prompt() {
- let done = true
- view.prompt = `Use Culverin and Falconets ?`
- for (let lord of game.battle.array) {
- if (lord !== NOBODY) {
- if (is_friendly_lord(lord) && (lord_has_capability(lord, AOW_YORK_CULVERINS_AND_FALCONETS))) {
- gen_action_card(AOW_YORK_CULVERINS_AND_FALCONETS)
- done = false
- }
- if (is_friendly_lord(lord) && (lord_has_capability(lord, AOW_LANCASTER_CULVERINS_AND_FALCONETS))) {
- gen_action_card(AOW_LANCASTER_CULVERINS_AND_FALCONETS)
- done = false
- }
- }
- }
- if (done) {
- view.prompt = "Culverins and Falconets : Done"
- }
- view.actions.done = 1
- },
- card(c) {
- let die = roll_die()
- let lord = find_lord_with_capability_card(c)
- if (is_event_in_play(EVENT_YORK_PATRICK_DE_LA_MOTE) && game.active === YORK) {
- let die2 = roll_die()
- die += die2
- }
- logi(`${data.lords[lord].name} Artillery does ${die} hits`)
- artillery_hits(die)
- discard_lord_capability(lord, c)
- },
- done() {
- if (is_defender()) {
- set_active_enemy()
- }
- else {
- goto_engagement_total_hits()
- }
- }
-}
-
// === BATTLE EVENT: SWIFT MANEUVER ===
function is_swift_maneuver_in_play() {
@@ -6152,6 +6101,76 @@ states.swift_maneuver = {
},
}
+// === BATTLE CAPABILITY: CULVERINS AND FALCONETS ===
+
+function is_culverins_and_falconets_in_battle() {
+ for (let p of battle_strike_positions) {
+ let lord = game.battle.array[p]
+ if (lord !== NOBODY) {
+ if (lord_has_capability(lord, AOW_LANCASTER_CULVERINS_AND_FALCONETS))
+ return true
+ if (lord_has_capability(lord, AOW_YORK_CULVERINS_AND_FALCONETS))
+ return true
+ }
+ }
+ return false
+}
+
+function goto_culverins_and_falconets() {
+ if (is_culverins_and_falconets_in_battle())
+ game.state = "culverins_and_falconets"
+ else
+ end_culverins_and_falconets()
+}
+
+function end_culverins_and_falconets() {
+ if (game.active === game.battle.attacker) {
+ goto_flee()
+ } else {
+ set_active_enemy()
+ goto_culverins_and_falconets()
+ }
+}
+
+states.culverins_and_falconets = {
+ inactive: "Culverins and Falconets",
+ prompt() {
+ view.prompt = `Use Culverins and Falconets?`
+ if (game.active === LANCASTER)
+ gen_action_card(AOW_LANCASTER_CULVERINS_AND_FALCONETS)
+ else
+ gen_action_card(AOW_YORK_CULVERINS_AND_FALCONETS)
+ view.actions.done = 1
+ },
+ card(c) {
+ let lord = find_lord_with_capability_card(c)
+ let die1 = roll_die()
+ let die2 = 0
+
+ logcap(c)
+
+ if (is_event_in_play(EVENT_YORK_PATRICK_DE_LA_MOTE) && game.active === YORK) {
+ logcap(EVENT_YORK_PATRICK_DE_LA_MOTE)
+ die2 = roll_die()
+ logi(`${data.lords[lord].name} Artillery does ${die1} + ${die2} hits`)
+ } else {
+ logi(`${data.lords[lord].name} Artillery does ${die1} hits`)
+ }
+
+
+ if (is_attacker())
+ game.battle.attacker_artillery = (die1 + die2)
+ else
+ game.battle.defender_artillery = (die1 + die2)
+
+ discard_lord_capability(lord, c)
+ end_culverins_and_falconets()
+ },
+ done() {
+ end_culverins_and_falconets()
+ },
+}
+
// === BATTLE CAPABILITY: FINAL CHARGE ===
function can_final_charge() {
@@ -6174,15 +6193,13 @@ states.final_charge = {
},
final_charge() {
logcap(AOW_YORK_FINAL_CHARGE)
+ log_hits("+3", lord_name[find_lord_with_capability_card(AOW_YORK_FINAL_CHARGE)])
+ log_hits("+1", "Final Charge")
game.battle.final_charge = 1
if (game.battle.attacker === YORK) {
- log_hits(1, "attacker hit")
- log_hits(3, "defender hit")
game.battle.ahits += 1
game.battle.dhits += 3
} else {
- log_hits(3, "attacker hit")
- log_hits(1, "defender hit")
game.battle.ahits += 3
game.battle.dhits += 1
}
@@ -6193,6 +6210,43 @@ states.final_charge = {
},
}
+// === BATTLE CAPABILITY: VANGUARD ===
+
+function is_vanguard_in_battle() {
+ for (let p of battle_strike_positions) {
+ let lord = game.battle.array[p]
+ if (lord !== NOBODY) {
+ if (lord_has_capability(lord, AOW_YORK_VANGUARD))
+ return true
+ }
+ }
+ return false
+}
+
+states.vanguard = {
+ prompt() {
+ view.prompt = "Vanguard: This Lord may choose his Engagement to be the only one."
+ view.actions.vanguard = 1
+ view.actions.done = 1
+ },
+ vanguard() {
+ let lord = find_lord_with_capability_card(AOW_YORK_VANGUARD)
+
+ // Filter out engagements that don't contain Vanguard lord
+ game.battle.engagements = game.battle.engagements.filter(engagement => {
+ for (let p of engagement)
+ if (game.battle.array[p] === lord)
+ return true
+ return false
+ })
+
+ goto_determine_engagements()
+ },
+ done() {
+ goto_determine_engagements()
+ },
+}
+
// === 4.4.2 BATTLE ROUNDS ===
/*
@@ -6222,14 +6276,19 @@ states.final_charge = {
*/
function goto_battle_rounds() {
- set_active_defender()
log_h4(`Battle Round ${game.battle.round}`)
- goto_flee()
+ if (game.battle.round === 1) {
+ set_active_defender()
+ goto_culverins_and_falconets()
+ } else {
+ goto_flee()
+ }
}
// === 4.4.2 BATTLE ROUNDS: FLEE ===
function goto_flee() {
+ set_active_defender()
game.state = "flee_battle"
}
@@ -6238,24 +6297,22 @@ function end_flee() {
end_battle_round()
return
}
- set_active_enemy()
- if (game.active !== game.battle.attacker) {
+ set_active_enemy()
+ if (game.active !== game.battle.attacker)
goto_reposition_battle()
- } else {
- goto_flee()
- }
}
states.flee_battle = {
inactive: "Flee",
prompt() {
view.prompt = "Battle: Select Lords to Flee from the Field?"
- for (let p = 0; p < 6; ++p) {
- if (is_friendly_lord(game.battle.array[p])) {
+ for (let lord of game.battle.reserves)
+ if (is_friendly_lord(lord))
+ gen_action_lord(lord)
+ for (let p of battle_strike_positions)
+ if (is_friendly_lord(game.battle.array[p]))
gen_action_lord(game.battle.array[p])
- }
- }
view.actions.done = 1
},
done() {
@@ -6323,7 +6380,7 @@ function end_reposition_center() {
if (is_attacker())
goto_reposition_center()
else
- goto_first_engagement()
+ goto_determine_engagements()
}
function can_reposition_advance() {
@@ -6456,57 +6513,27 @@ function determine_engagements() {
return results
}
-function goto_first_engagement() {
- game.battle.step = 0
+function goto_determine_engagements() {
game.battle.engagements = determine_engagements()
- goto_engagement()
-}
-
-function goto_next_step() {
- let end = 2
- game.battle.step++
- if (game.battle.step >= end)
- end_engagement()
- else
- goto_engagement()
-}
-function goto_engagement() {
- if (is_battle_over()) {
- end_battle_round()
- return
- }
-
- log_h5(battle_steps[game.battle.step].name)
-
- // Generate hits
- game.battle.ah = [ 0, 0, 0, 0, 0, 0 ]
-
- for (let pos of battle_strike_positions) {
- let lord = game.battle.array[pos]
- if (lord !== NOBODY) {
- let hits = count_lord_hits(lord)
-
- game.battle.ah[pos] = hits
- }
+ if (game.battle.round === 1 && is_vanguard_in_battle()) {
+ set_active(YORK)
+ game.state = "vanguard"
+ } else {
+ goto_select_engagement()
}
-
- resume_engagement()
-}
-
-function find_engagement_index(pos) {
- return game.battle.engagements.findIndex(e => e.includes(pos))
}
-function end_engagement() {
- game.battle.engagements.shift()
-
- if (game.battle.engagements.length > 0) {
- game.battle.step = 0
- goto_engagement()
- } else {
+function goto_select_engagement() {
+ set_active_attacker()
+ if (game.battle.engagements.length === 0)
+ // if round is over
goto_end_battle_round()
- }
+ else if (game.battle.engagements.length === 1)
+ // if no choice, just select the one engagement
+ goto_missile_strike_step()
+ else
+ game.state = "select_engagement"
}
states.select_engagement = {
@@ -6522,68 +6549,80 @@ states.select_engagement = {
}
},
lord(lord) {
- let idx = find_engagement_index(get_lord_array_position(lord))
- let eng = game.battle.engagements[idx]
- array_remove(game.battle.engagements, idx)
- game.battle.engagements.unshift(eng)
- set_active_defender()
- if (game.battle.round === 1 && is_archery_step()) {
- goto_culverins()
- }
- else {
- goto_engagement_total_hits()
- }
+ // move selected engagement to head of list
+ let pos = get_lord_array_position(lord)
+ let idx = game.battle.engagements.findIndex(e => e.includes(pos))
+
+ let swap = game.battle.engagements[0]
+ game.battle.engagements[0] = game.battle.engagements[idx]
+ game.battle.engagements[idx] = swap
+
+ goto_missile_strike_step()
},
}
-function resume_engagement() {
- if (game.battle.engagements.length === 1 || is_melee_step()) {
- if (game.battle.round === 1 && is_archery_step()) {
- goto_culverins()
- }
- else {
- goto_engagement_total_hits()
- }
- // only one engagement, so no choices on order
- } else {
- set_active_attacker()
- game.state = "select_engagement"
- }
+function goto_missile_strike_step() {
+ game.battle.step = 0
+ goto_total_hits()
+}
+
+function goto_melee_strike_step() {
+ game.battle.step = 1
+ goto_total_hits()
+}
+
+function end_battle_strike_step() {
+ if (game.battle.step === 0)
+ goto_melee_strike_step()
+ else
+ end_engagement()
+}
+
+function end_engagement() {
+ game.battle.engagements.shift()
+ goto_select_engagement()
}
// === 4.4.2 BATTLE ROUNDS: TOTAL HITS (ROUND UP) ===
-// for each battle step:
-// generate strikes for each lord
-// while strikes remain:
-// create list of strike groups (choose left/right both rows)
-// select strike group
-// create target group (choose if sally)
-// total strikes and roll for walls
-// while hits remain:
-// assign hit to unit in target group
-// if lord routs:
-// forget choice of left/right strike group in current row
-// create new target group (choose if left/right/sally)
-
-function goto_engagement_total_hits() {
+function goto_total_hits() {
+ set_active_defender()
+
+ if (is_battle_over()) {
+ end_battle_round()
+ return
+ }
+
let ahits = 0
let dhits = 0
+ log_h5(battle_steps[game.battle.step].name)
+
for (let pos of game.battle.engagements[0]) {
- if (pos === A1 || pos === A2 || pos === A3) {
- ahits += game.battle.ah[pos]
- if (game.battle.attacker_artillery > 0) {
- ahits += game.battle.attacker_artillery
- }
- }
- else {
- dhits += game.battle.ah[pos]
- if (game.battle.defender_artillery > 0) {
- dhits += game.battle.defender_artillery
- }
+ let lord = game.battle.array[pos]
+ if (lord !== NOBODY) {
+ let hits = count_lord_hits(lord)
+ log_hits(hits / 2, lord_name[lord])
+ if (pos === A1 || pos === A2 || pos === A3)
+ ahits += hits
+ else
+ dhits += hits
}
}
+
+ if (game.battle.attacker_artillery > 0) {
+ log_hits(game.battle.attacker_artillery, "attacker artillery")
+ ahits += game.battle.attacker_artillery << 1
+ }
+ if (game.battle.defender_artillery > 0) {
+ log_hits(game.battle.dhits, "defender artillery")
+ dhits += game.battle.defender_artillery << 1
+ }
+
+ // use artillery only once
+ game.battle.attacker_artillery = 0
+ game.battle.defender_artillery = 0
+
if (ahits & 1)
ahits = (ahits >> 1) + 1
else
@@ -6597,10 +6636,6 @@ function goto_engagement_total_hits() {
game.battle.ahits = ahits
game.battle.dhits = dhits
- log_br()
- log_hits(game.battle.ahits, "attacker hit")
- log_hits(game.battle.dhits, "defender hit")
-
game.battle.target = null
game.battle.final_charge = 0
@@ -6614,57 +6649,11 @@ function goto_engagement_total_hits() {
goto_defender_assign_hits()
}
-function continue_engagement() {
- for (let pos of battle_strike_positions) {
- let lord = game.battle.array[pos]
- if (lord !== NOBODY)
- if (will_lord_rout(lord))
- rout_lord(lord)
- }
-
- end_assign_hits()
-}
-
function log_hits(total, name) {
- if (total === 1)
- logi(`${total} ${name}`)
- else if (total > 1)
- logi(`${total} ${name}s`)
- else
- logi(`No ${name}s`)
-}
-
-// === 4.4.2 BATTLE ROUNDS: APPLY HITS / PROTECTION / ROLL BY HIT / ROUT ===
-
-function goto_defender_assign_hits() {
- set_active_defender()
- if (game.battle.ahits === 0)
- return end_defender_assign_hits()
-
- if (no_remaining_targets())
- return end_defender_assign_hits()
-
- goto_assign_hits()
-}
-
-function goto_assign_hits() {
- game.state = "assign_hits"
- if (game.battle.target === null) {
- let targets: Lord[] = []
- for (let pos of game.battle.engagements[0]) {
- let lord = game.battle.array[pos]
- if (is_friendly_lord(lord)) {
- targets.push(pos)
- }
- }
- game.battle.target = targets
- }
+ logi(`${total} ${name}`)
}
-function end_defender_assign_hits() {
- game.battle.target = null
- goto_attacker_assign_hits()
-}
+// === 4.4.2 BATTLE ROUNDS: ROLL BY HIT (PROTECTION ROLL, VALOUR RE-ROLL, FORCES ROUT) ===
function no_remaining_targets() {
for (let pos of game.battle.engagements[0]) {
@@ -6676,42 +6665,52 @@ function no_remaining_targets() {
return true
}
-function goto_attacker_assign_hits() {
- set_active_attacker()
-
- if (game.battle.dhits === 0)
- return end_attacker_assign_hits()
-
- if (no_remaining_targets())
- return end_attacker_assign_hits()
-
- goto_assign_hits()
+function goto_defender_assign_hits() {
+ set_active_defender()
+ if (game.battle.ahits === 0 || no_remaining_targets())
+ end_defender_assign_hits()
+ else
+ goto_assign_hits()
}
-function end_attacker_assign_hits() {
- continue_engagement()
+function end_defender_assign_hits() {
+ game.battle.target = null
+ game.battle.ahits = 0
+ goto_attacker_assign_hits()
}
-function end_assign_hits() {
- for (let pos of game.battle.engagements[0]) {
- game.battle.ah[pos] = 0
- }
+function goto_attacker_assign_hits() {
+ set_active_attacker()
+ if (game.battle.dhits === 0 || no_remaining_targets())
+ end_attacker_assign_hits()
+ else
+ goto_assign_hits()
+}
+function end_attacker_assign_hits() {
game.battle.target = null
- game.battle.ahits = 0
game.battle.dhits = 0
-
- goto_next_step()
+ end_battle_strike_step()
}
-function for_each_target(fn) {
- for (let target of game.battle.target) {
- fn(game.battle.array[target])
+function goto_assign_hits() {
+ game.state = "assign_hits"
+ if (game.battle.target === null) {
+ let targets: Lord[] = []
+ for (let pos of game.battle.engagements[0]) {
+ let lord = game.battle.array[pos]
+ if (is_friendly_lord(lord)) {
+ targets.push(pos)
+ }
+ }
+ game.battle.target = targets
}
}
function prompt_hit_forces() {
- for_each_target(lord => {
+ for (let target of game.battle.target) {
+ let lord = game.battle.array[target]
+
// Note: Must take hit from Final Charge on Retinue.
if (lord === LORD_RICHARD_III && game.battle.final_charge) {
gen_action_retinue(lord)
@@ -6735,7 +6734,7 @@ function prompt_hit_forces() {
if (!set_has(game.battle.routed_vassals, v))
gen_action_vassal(v)
})
- })
+ }
}
states.assign_hits = {
@@ -6775,42 +6774,6 @@ states.assign_hits = {
},
}
-function rout_lord(lord: Lord) {
- log(`L${lord} Routed.`)
-
- let pos = get_lord_array_position(lord)
-
- // Remove from battle array
- game.battle.array[pos] = NOBODY
- set_add(game.battle.routed, lord)
-}
-
-function lord_has_unrouted_troops(lord: Lord) {
- // Don't check here for Retinue or Vassals.
- for (let x of simple_force_type) {
- if (get_lord_forces(lord, x) > 0)
- return true
- }
- return false
-}
-
-function lord_has_routed_troops(lord: Lord) {
- // Don't check here for Retinue or Vassals.
- for (let x of simple_force_type) {
- if (get_lord_routed_forces(lord, x) > 0)
- return true
- }
- return false
-}
-
-function will_lord_rout(lord: Lord) {
- if (get_lord_routed_forces(lord, RETINUE) > 0)
- return true
- if (!lord_has_unrouted_troops(lord))
- return true
- return false
-}
-
function rout_unit(lord: Lord, type: Force, v: Vassal = NOVASSAL) {
if (type === VASSAL) {
rout_vassal(lord, v)
@@ -6953,14 +6916,47 @@ states.spend_valour = {
},
}
-// === 4.4.2 BATTLE ROUNDS: NEW ROUND ===
+// === 4.4.2 BATTLE ROUNDS: LORD ROUT ===
+
+function rout_lord(lord: Lord) {
+ log(`L${lord} Routed.`)
+
+ let pos = get_lord_array_position(lord)
+
+ // Remove from battle array
+ game.battle.array[pos] = NOBODY
+ set_add(game.battle.routed, lord)
+}
+
+function will_lord_rout(lord: Lord) {
+ if (get_lord_routed_forces(lord, RETINUE) > 0)
+ return true
+ if (!lord_has_unrouted_troops(lord))
+ return true
+ return false
+}
function goto_end_battle_round() {
+ log_h5("Lord Rout")
+
+ // TODO: manually rout lords for clarity?
+
+ for (let pos of battle_strike_positions) {
+ let lord = game.battle.array[pos]
+ if (lord !== NOBODY)
+ if (will_lord_rout(lord))
+ rout_lord(lord)
+ }
+
end_battle_round()
}
+// === 4.4.2 BATTLE ROUNDS: NEW ROUND ===
+
function end_battle_round() {
+ game.battle.engagements = null
game.battle.ravine = NOBODY
+
let attacker_loser = null
set_active_attacker()
if (has_no_unrouted_forces()) {
@@ -6986,10 +6982,7 @@ function end_battle_round() {
}
game.battle.round++
-
- // TODO: goto_battle_rounds() instead?
- set_active_defender()
- goto_flee()
+ goto_battle_rounds()
}
// === 4.4.3 ENDING THE BATTLE ===