summaryrefslogtreecommitdiff
path: root/rules.ts
diff options
context:
space:
mode:
Diffstat (limited to 'rules.ts')
-rw-r--r--rules.ts188
1 files changed, 140 insertions, 48 deletions
diff --git a/rules.ts b/rules.ts
index f8878b6..06d2418 100644
--- a/rules.ts
+++ b/rules.ts
@@ -25,6 +25,7 @@
NAVAL BLOCKADE - for Tax and Tax Collectors
NAVAL BLOCKADE - Great Ships
+ flee markers
limited troop pieces (burgundians/mercenaries)
disbanded vassals show back on calendar
@@ -563,6 +564,13 @@ function is_adjacent(a: Locale, b: Locale) {
return is_stronghold(a) && is_stronghold(b) && set_has(data.locales[a].adjacent, b)
}
+function find_sea_mask(here: Locale) {
+ if (is_port_1(here)) return 1
+ if (is_port_2(here)) return 2
+ if (is_port_3(here)) return 4
+ return 0
+}
+
function find_ports(here: Locale, lord: Lord): Locale[] {
// for Parley, Supply, and Tax purposes only (not Disembark)
@@ -1285,12 +1293,10 @@ function add_lord_assets(lord: Lord, n: Asset, x: number) {
}
function drop_prov(lord: Lord) {
- push_undo()
add_lord_assets(lord, PROV, -1)
}
function drop_cart(lord: Lord) {
- push_undo()
add_lord_assets(lord, CART, -1)
}
@@ -2016,7 +2022,7 @@ function parley_ic_cost(lord: Lord, spend: number) {
let cost = common_ic_cost(lord, spend)
// Note: use of game.where
- cost += map_get(game.parley, game.where, 0)
+ cost += map_get(game.parley, game.where, 0) >> 3 /* scaled by 8 to account for seas crossed */
if (game.active === LANCASTER) {
if (is_event_in_play(EVENT_YORK_AN_HONEST_TALE_SPEEDS_BEST)) {
@@ -2074,7 +2080,7 @@ function parley_ic_success(lord: Lord) {
function common_ic_rating(lord: Lord, spend: number) {
let here = get_lord_locale(lord)
- let rating = data.lords[lord].influence
+ let rating = get_lord_influence(lord)
rating += spend
if (game.active === YORK) {
if (is_event_in_play(EVENT_YORK_YORKIST_PARADE))
@@ -2779,20 +2785,22 @@ function shipwreck_lord(lord: Lord) {
}
function disband_lord(lord: Lord) {
- let turn = current_turn()
- set_lord_calendar(lord, turn + (6 - data.lords[lord].influence))
- log(`Disband L${lord} T${get_lord_calendar(lord)}.`)
+ let from = get_lord_locale(lord)
+ set_lord_calendar(lord, current_turn() + (6 - get_lord_influence(lord)))
+ if (is_exile_box(from))
+ set_lord_in_exile(lord)
+ log(`Disband L${lord} to T${get_lord_calendar(lord)}.`)
clear_lord(lord)
}
function exile_lord(lord: Lord) {
if (lord_has_capability(lord, AOW_YORK_ENGLAND_IS_MY_HOME) && !is_event_in_play(EVENT_LANCASTER_BLOCKED_FORD)) {
logcap(AOW_YORK_ENGLAND_IS_MY_HOME)
- log(`Disband L${lord} T${current_turn() + 1}`)
+ log(`Disband L${lord} to T${current_turn() + 1}`)
set_lord_calendar(lord, current_turn() + 1)
clear_lord(lord)
} else {
- set_lord_calendar(lord, current_turn() + 6 - data.lords[lord].influence)
+ set_lord_calendar(lord, current_turn() + 6 - get_lord_influence(lord))
set_lord_in_exile(lord)
log(`Exile L${lord} to T${get_lord_calendar(lord)}.`)
clear_lord(lord)
@@ -3916,7 +3924,18 @@ states.command = {
},
}
-// === 4.5 ACTION: SUPPLY (SEARCH) ===
+// === 4.5 ACTION: SUPPLY ===
+
+/*
+ Supply has these possibilities:
+ - from exile box to port at same sea (or all seas if great ships)
+ - from stronghold via ways to port
+ - from stronghold via ways to non-port
+ Naval Blockade
+ We check source (exile box) and destination (port) only.
+ If using great ships to supply from Ireland to port on the north sea,
+ and Warwick is in the English Channel, we do not detect this case!
+*/
function can_supply_at(source: Locale, ships: number) {
// if theoretically possible to supply from this source (does not check carts or ships)
@@ -3938,6 +3957,7 @@ function search_supply_by_way(result, start: Locale, carts: number, ships: numbe
while (queue.length > 0) {
let here = queue.shift()
let dist = search_dist[here]
+ let next_dist = dist + 8
if (can_supply_at(here, ships)) {
if (result)
@@ -3947,7 +3967,6 @@ function search_supply_by_way(result, start: Locale, carts: number, ships: numbe
}
if (is_friendly_locale(here)) {
- let next_dist = dist + 1
if (next_dist <= carts) {
for (let next of data.locales[here].adjacent) {
if (!search_seen[next]) {
@@ -3972,7 +3991,7 @@ function search_supply_by_sea(result, here: Locale) {
for (let next of find_ports(here, game.command)) {
if (can_supply_at(next, 1)) {
if (result)
- map_set(result, next, 0)
+ map_set(result, next, 0 | find_sea_mask(here) | find_sea_mask(next))
else
return true
}
@@ -3993,7 +4012,6 @@ function search_supply(result) {
return result
}
-// === 4.5 ACTION: SUPPLY ===
function command_has_harbingers() {
return (
@@ -4054,7 +4072,7 @@ function modify_supply(loc: Locale, supply: number) {
let carts = count_shared_carts(here, true)
// Must carry supply over land with one cart per provender per way
- let distance = map_get(game.supply, loc, 0)
+ let distance = map_get(game.supply, loc, 0) >> 3
if (distance > 0)
supply = Math.min(supply, Math.floor(carts / distance))
@@ -4122,7 +4140,8 @@ states.supply_source = {
if (port_supply > 0 && stronghold_supply === 0) {
game.where = loc
- if (can_naval_blockade(game.where)) {
+ // blockade at source or destination
+ if (can_naval_blockade(get_lord_locale(game.command)) || can_naval_blockade(game.where)) {
game.state = "blockade_supply"
} else {
use_port_supply(loc, port_supply)
@@ -4169,7 +4188,8 @@ states.select_supply_type = {
end_supply()
},
port() {
- if (can_naval_blockade(game.where)) {
+ // blockade at source or destination
+ if (can_naval_blockade(get_lord_locale(game.command)) || can_naval_blockade(game.where)) {
game.state = "blockade_supply"
} else {
use_port_supply(game.where, get_port_supply_amount(game.where))
@@ -4320,7 +4340,7 @@ states.sail = {
}
states.blockade_sail = {
- inactive: "Muster",
+ inactive: "Sail",
prompt() {
view.prompt = "Sail: Warwick may naval blockade this sail action."
view.actions.roll = 1
@@ -4481,10 +4501,12 @@ function search_tax(result, start: Locale, lord: Lord) {
let queue = [ start ]
while (queue.length > 0) {
let here = queue.shift()
+ let dist = search_dist[here]
+ let next_dist = dist + 8
if (can_tax_at(here, lord)) {
if (result)
- set_add(result, here)
+ map_set(result, here, dist)
else
return true
}
@@ -4493,6 +4515,7 @@ function search_tax(result, start: Locale, lord: Lord) {
for (let next of data.locales[here].adjacent) {
if (!search_seen[next]) {
search_seen[next] = 1
+ search_dist[next] = next_dist
queue.push(next)
}
}
@@ -4500,12 +4523,14 @@ function search_tax(result, start: Locale, lord: Lord) {
for (let next of find_ports(here, lord)) {
if (!search_seen[next]) {
search_seen[next] = 1
+ search_dist[next] = next_dist
queue.push(next)
}
}
}
}
}
+
if (result)
return result
else
@@ -4556,8 +4581,10 @@ states.tax = {
prompt() {
if (game.where === NOWHERE) {
view.prompt = "Tax: Choose a stronghold."
- for (let loc of search_tax([], get_lord_locale(game.command), game.command))
- gen_action_locale(loc)
+ map_for_each_key(
+ search_tax([], get_lord_locale(game.command), game.command),
+ gen_action_locale
+ )
} else {
view.prompt = `Tax: ${locale_name[game.where]}.`
prompt_influence_check(game.command)
@@ -4565,11 +4592,18 @@ states.tax = {
},
locale(loc) {
game.where = loc
- // TODO: naval blockade if only reachable by sea
if (loc === get_lord_seat(game.command)) {
log("Tax at S" + game.where + ".")
do_tax(game.command, game.where, 1)
end_tax()
+ } else {
+ let dist = map_get(
+ search_tax([], get_lord_locale(game.command), game.command),
+ loc,
+ 0
+ )
+ if (can_naval_blockade_route(dist))
+ game.state = "blockade_tax"
}
},
check(spend) {
@@ -4581,6 +4615,20 @@ states.tax = {
},
}
+states.blockade_tax = {
+ inactive: "Tax",
+ prompt() {
+ view.prompt = "Tax: Warwick may naval blockade this tax action."
+ view.actions.roll = 1
+ },
+ roll() {
+ if (roll_blockade())
+ game.state = "tax"
+ else
+ end_tax()
+ },
+}
+
function apply_so_wise_so_young(lord) {
if (lord_has_capability(lord, AOW_YORK_SO_WISE_SO_YOUNG)) {
logcap(AOW_YORK_SO_WISE_SO_YOUNG)
@@ -4655,6 +4703,8 @@ function can_parley_at(loc: Locale) {
}
function search_parley_levy(result, start: Locale, lord: Lord) {
+ let ships = count_shared_ships(start, false)
+
search_dist.fill(0)
search_seen.fill(0)
search_seen[start] = 1
@@ -4663,7 +4713,7 @@ function search_parley_levy(result, start: Locale, lord: Lord) {
while (queue.length > 0) {
let here = queue.shift()
let dist = search_dist[here]
- let next_dist = dist + 1
+ let next_dist = dist + 8
if (can_parley_at(here)) {
if (result)
@@ -4681,11 +4731,11 @@ function search_parley_levy(result, start: Locale, lord: Lord) {
}
}
- if (here === start && is_exile_box(here) && count_shared_ships(start, false) > 0) {
+ if (ships > 0 && (is_seaport(here) || is_exile_box(here))) {
for (let next of find_ports(here, lord)) {
if (!search_seen[next]) {
search_seen[next] = 1
- search_dist[next] = next_dist
+ search_dist[next] = next_dist | find_sea_mask(here) | find_sea_mask(next)
queue.push(next)
}
}
@@ -4738,12 +4788,12 @@ function search_parley_campaign(here: Locale, lord: Lord) {
if (is_friendly_locale(here)) {
for (let next of data.locales[here].adjacent)
if (can_parley_at(next))
- map_set(result, next, 1)
+ map_set(result, next, 8)
if (is_exile_box(here) && count_shared_ships(here, false) > 0)
for (let next of find_ports(here, lord))
if (can_parley_at(next))
- map_set(result, next, 1)
+ map_set(result, next, 8 | find_sea_mask(here) | find_sea_mask(next))
}
return result
@@ -4857,8 +4907,8 @@ states.parley = {
game.where = loc
let here = get_lord_locale(game.command)
if (!is_adjacent(here, loc)) {
- // TODO: check interaction of Naval Blockade with Great Ships when parleying across multiple seas
- if (can_naval_blockade(here))
+ let dist = map_get(game.parley, loc, 0)
+ if (can_naval_blockade_route(dist))
game.state = "blockade_parley"
}
},
@@ -5526,7 +5576,7 @@ states.choose_exile = {
push_undo()
give_up_spoils(lord)
- reduce_influence(data.lords[lord].influence + count_vassals_with_lord(lord))
+ reduce_influence(get_lord_influence(lord) + count_vassals_with_lord(lord))
exile_lord(lord)
@@ -6382,16 +6432,12 @@ function can_play_suspicion() {
return highest_friendly_influence() > lowest_enemy_influence()
}
-function get_printed_lord_influence(lord) {
- return data.lords[lord].influence
-}
-
function lowest_enemy_influence() {
let score = 10
for (let lord of all_enemy_lords()) {
if (get_lord_locale(lord) === game.battle.where) {
- if (get_printed_lord_influence(lord) < score) {
- score = get_printed_lord_influence(lord)
+ if (get_lord_influence(lord) < score) {
+ score = get_lord_influence(lord)
}
}
}
@@ -6402,8 +6448,8 @@ function highest_friendly_influence() {
let score = 0
for (let lord of all_friendly_lords()) {
if (get_lord_locale(lord) === game.battle.where) {
- if (get_printed_lord_influence(lord) > score) {
- score = get_printed_lord_influence(lord)
+ if (get_lord_influence(lord) > score) {
+ score = get_lord_influence(lord)
}
}
}
@@ -6416,10 +6462,10 @@ states.suspicion_1 = {
view.prompt = "Suspicion: Choose a lord to check influence."
let lowest = lowest_enemy_influence()
for (let lord of game.battle.array)
- if (is_friendly_lord(lord) && get_printed_lord_influence(lord) > lowest)
+ if (is_friendly_lord(lord) && get_lord_influence(lord) > lowest)
gen_action_lord(lord)
for (let lord of game.battle.reserves)
- if (is_friendly_lord(lord) && get_printed_lord_influence(lord) > lowest)
+ if (is_friendly_lord(lord) && get_lord_influence(lord) > lowest)
gen_action_lord(lord)
},
lord(lord) {
@@ -6451,9 +6497,9 @@ states.suspicion_3 = {
inactive: "Suspicion",
prompt() {
view.prompt = "Suspicion: Disband one enemy lord with lower influence rating."
- let highest = get_printed_lord_influence(game.who)
+ let highest = get_lord_influence(game.who)
for (let lord of game.battle.array)
- if (is_enemy_lord(lord) && get_printed_lord_influence(lord) < highest)
+ if (is_enemy_lord(lord) && get_lord_influence(lord) < highest)
gen_action_lord(lord)
},
lord(lord) {
@@ -7870,7 +7916,7 @@ function goto_battle_influence() {
let influence = 0
for (let lord of game.battle.routed)
if (is_friendly_lord(lord))
- influence += data.lords[lord].influence + count_vassals_with_lord(lord)
+ influence += get_lord_influence(lord) + count_vassals_with_lord(lord)
reduce_influence(influence)
goto_battle_spoils()
@@ -9070,7 +9116,7 @@ states.disembark_to = {
}
function disband_influence_penalty(lord: Lord) {
- let influence = data.lords[lord].influence
+ let influence = get_lord_influence(lord)
for (let v of all_vassals)
if (is_vassal_mustered_with(v, lord))
influence += 1
@@ -10777,13 +10823,26 @@ function can_naval_blockade(here: Locale) {
return false
}
+function can_naval_blockade_route(mask: number) {
+ if (game.active === LANCASTER && is_naval_blockade_in_play()) {
+ let w = get_lord_locale(LORD_WARWICK_Y)
+ if ((mask & 1) && is_port_1(w))
+ return true
+ if ((mask & 2) && is_port_2(w))
+ return true
+ if ((mask & 4) && is_port_3(w))
+ return true
+ }
+ return false
+}
+
function roll_blockade() {
let roll = roll_die()
if (roll <= 2) {
- log("Naval Blockade B" + roll)
+ log("Naval Blockade 1-2: W" + roll)
return true
} else {
- log("Naval Blockade W" + roll)
+ log("Naval Blockade 1-2: B" + roll)
return false
}
}
@@ -11685,8 +11744,10 @@ states.tax_collectors_lord = {
prompt() {
if (game.where === NOWHERE) {
view.prompt = `Tax Collectors: ${lord_name[game.who]}. Choose a stronghold.`
- for (let loc of search_tax([], get_lord_locale(game.who), game.who))
- gen_action_locale(loc)
+ map_for_each_key(
+ search_tax([], get_lord_locale(game.who), game.who),
+ gen_action_locale
+ )
} else {
view.prompt = `Tax Collectors: Tax ${locale_name[game.where]} with ${lord_name[game.who]}.`
prompt_influence_check(game.who)
@@ -11694,10 +11755,17 @@ states.tax_collectors_lord = {
},
locale(loc) {
game.where = loc
- // TODO: naval blockade if only reachable by sea
if (loc === get_lord_seat(game.who)) {
do_tax(game.who, game.where, 2)
end_tax_collectors_lord()
+ } else {
+ let dist = map_get(
+ search_tax([], get_lord_locale(game.who), game.who),
+ loc,
+ 0
+ )
+ if (can_naval_blockade_route(dist))
+ game.state = "blockade_tax_collectors"
}
},
check(spend) {
@@ -11709,6 +11777,20 @@ states.tax_collectors_lord = {
},
}
+states.blockade_tax_collectors = {
+ inactive: "Tax Collectors",
+ prompt() {
+ view.prompt = "Tax Collectors: Warwick may naval blockade this tax action."
+ view.actions.roll = 1
+ },
+ roll() {
+ if (roll_blockade())
+ game.state = "tax_collectors_lord"
+ else
+ end_tax_collectors_lord()
+ },
+}
+
function end_tax_collectors_lord() {
game.where = NOWHERE
game.who = NOBODY
@@ -13092,6 +13174,16 @@ function map_delete(map, item) {
}
}
+function map_for_each_key(map, f) {
+ for (let i = 0; i < map.length; i += 2)
+ f(map[i])
+}
+
+function map_for_each(map, f) {
+ for (let i = 0; i < map.length; i += 2)
+ f(map[i], map[i+1])
+}
+
// === FUZZING ASSERTS ===
const mutually_exclusive_lords = [