diff options
author | Tor Andersson <tor@ccxvii.net> | 2022-12-16 20:13:02 +0100 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2023-02-18 13:02:38 +0100 |
commit | bf4e77c7d4fe7f9bc79e07700ca0a1e9a42ebc37 (patch) | |
tree | 1c7d18b5f3c31f3c64f186c7a54a601631d632c3 | |
parent | e1a636df18394d60f808f210235045fbf2533291 (diff) | |
download | nevsky-bf4e77c7d4fe7f9bc79e07700ca0a1e9a42ebc37.tar.gz |
1st edition supply rules.
-rw-r--r-- | data.js | 2 | ||||
-rw-r--r-- | play.js | 24 | ||||
-rw-r--r-- | rules.js | 253 | ||||
-rw-r--r-- | tools/gendata.js | 2 |
4 files changed, 190 insertions, 91 deletions
@@ -136,7 +136,7 @@ lords:[ {"side":"Teutonic","name":"Andreas","full_name":"Andreas von Felben","title":"Landmeister in Livonia","seats":[9,13],"marshal":2,"fealty":2,"service":4,"lordship":3,"command":3,"forces":{"knights":1,"sergeants":2,"men_at_arms":1},"assets":{"transport":2,"prov":2},"ships":1,"vassals":[0,1,2],"image":0}, {"side":"Teutonic","name":"Heinrich","full_name":"Heinrich","title":"Bishop of Ă–sel-Wiek","seats":[8],"marshal":0,"fealty":3,"service":4,"lordship":2,"command":1,"forces":{"knights":1,"sergeants":1,"men_at_arms":1},"assets":{"coin":1,"ship":2,"prov":1},"ships":1,"vassals":[3,4],"image":1}, {"side":"Teutonic","name":"Hermann","full_name":"Hermann","title":"Bishop of Dorpat","seats":[7,12],"marshal":1,"fealty":4,"service":4,"lordship":3,"command":3,"forces":{"knights":1,"sergeants":1,"men_at_arms":1,"militia":1},"assets":{"transport":1,"coin":1,"prov":1},"ships":0,"vassals":[5,6,7],"image":2}, -{"side":"Teutonic","name":"Knud & Abel","full_name":"Knud & Abel","title":"Princes of Denmark","seats":[0,null],"marshal":0,"fealty":2,"service":3,"lordship":3,"command":2,"forces":{"knights":1,"sergeants":1,"men_at_arms":2,"militia":1},"assets":{"ship":2,"prov":2},"ships":1,"vassals":[8,9,10],"image":3}, +{"side":"Teutonic","name":"Knud & Abel","full_name":"Knud & Abel","title":"Princes of Denmark","seats":[0,1],"marshal":0,"fealty":2,"service":3,"lordship":3,"command":2,"forces":{"knights":1,"sergeants":1,"men_at_arms":2,"militia":1},"assets":{"ship":2,"prov":2},"ships":1,"vassals":[8,9,10],"image":3}, {"side":"Teutonic","name":"Rudolf","full_name":"Rudolf von Kassel","title":"Castellan of Wenden","seats":[13],"marshal":0,"fealty":5,"service":2,"lordship":1,"command":3,"forces":{"knights":1,"sergeants":1,"men_at_arms":1},"assets":{"transport":1,"prov":1},"ships":0,"vassals":[11,12,13],"image":4}, {"side":"Teutonic","name":"Yaroslav","full_name":"Yaroslav","title":"Exile of Pskov","seats":[12],"marshal":0,"fealty":4,"service":2,"lordship":1,"command":2,"forces":{"knights":1,"light_horse":1,"men_at_arms":1},"assets":{"transport":1,"prov":1},"ships":0,"vassals":[14],"image":5}, {"side":"Russian","name":"Aleksandr","full_name":"Aleksandr","title":"Prince of Novgorod","seats":[24,27],"marshal":2,"fealty":0,"service":6,"lordship":4,"command":3,"forces":{"knights":3,"men_at_arms":2},"assets":{"transport":2},"ships":1,"vassals":[15,16,17,18,19],"image":0}, @@ -1242,16 +1242,14 @@ function on_update() { action_button("use_legate", "Legate") - action_button("end_spoils", "End spoils") - action_button("end_avoid_battle", "End avoid battle") action_button("withdraw", "Withdraw") action_button("end_withdraw", "End withdraw") action_button("surrender", "Surrender") action_button("siegeworks", "Siegeworks") - action_button("sail", "Sail") action_button("march", "March") + action_button("sail", "Sail") action_button("siege", "Siege") action_button("storm", "Storm") action_button("sally", "Sally") @@ -1259,7 +1257,6 @@ function on_update() { action_button("forage", "Forage") action_button("ravage", "Ravage") action_button("tax", "Tax") - action_button("pass", "Pass") action_button("ship", "Ship") action_button("boat", "Boat") @@ -1268,22 +1265,25 @@ function on_update() { action_button("capability", "Capability") + action_button("deploy", "Deploy") action_button("discard", "Discard") - action_button("play", "Play") action_button("hold", "Hold") - action_button("deploy", "Deploy") + action_button("play", "Play") - action_button("done", "Done") - action_button("unfed", "Unfed") - action_button("end_plan", "End plan") - action_button("end_feed", "End feed") - action_button("end_pay", "End pay") - action_button("end_disband", "End disband") action_button("end_actions", "End actions") + action_button("end_avoid_battle", "End avoid battle") + action_button("end_disband", "End disband") + action_button("end_feed", "End feed") action_button("end_levy", "End levy") action_button("end_muster", "End muster") + action_button("end_pay", "End pay") + action_button("end_plan", "End plan") action_button("end_setup", "End setup") + action_button("end_spoils", "End spoils") + action_button("end_supply", "End supply") + action_button("pass", "Pass") + action_button("done", "Done") action_button("undo", "Undo") } @@ -2,7 +2,9 @@ // TODO: delay pay step if there is no feed or disband to be done -// TODO: Supply (+ lodya) +// TODO: Lodya capability during supply! +// TODO: 2nd edition supply rule - no reuse of transports +// TODO: 2nd edition ravage cost // CAPABILITIES // TODO: Ransom (T) @@ -404,15 +406,15 @@ function enemy_player() { return null } -function get_global_assets(type) { +function get_spoils(type) { return pack4_get(game.assets, type) } -function set_global_assets(type, n) { +function set_spoils(type, n) { game.assets = pack4_set(game.assets, type, n) } -function add_global_assets(type, n) { +function add_spoils(type, n) { game.assets = pack4_set(game.assets, type, pack4_get(game.assets) + n) } @@ -806,7 +808,7 @@ function is_lord_at_friendly_locale(lord) { return is_friendly_locale(loc) } -function for_each_seat(lord, fn) { +function for_each_seat(lord, fn, repeat = false) { let list = data.lords[lord].seats for (let seat of list) @@ -815,26 +817,25 @@ function for_each_seat(lord, fn) { if (is_teutonic_lord(lord)) { if (has_global_capability(AOW_TEUTONIC_ORDENSBURGEN)) { for (let commandery of COMMANDERIES) - if (!set_has(list, commandery)) + if (repeat || !set_has(list, commandery)) fn(commandery) } } if (is_russian_lord(lord)) { if (has_global_capability(AOW_RUSSIAN_ARCHBISHOPRIC)) - if (!set_has(list, LOC_NOVGOROD)) + if (repeat || !set_has(list, LOC_NOVGOROD)) fn(LOC_NOVGOROD) } if (lord === LORD_YAROSLAV) { if (has_conquered_marker(LOC_PSKOV)) - if (!set_has(list, LOC_PSKOV)) + if (repeat || !set_has(list, LOC_PSKOV)) fn(LOC_PSKOV) } } -function is_lord_at_seat(lord) { - let here = get_lord_locale(lord) +function is_lord_seat(lord, here) { let result = false for_each_seat(lord, seat => { if (seat === here) @@ -843,6 +844,10 @@ function is_lord_at_seat(lord) { return result } +function is_lord_at_seat(lord) { + return is_lord_seat(lord, get_lord_locale(lord)) +} + function has_free_seat(lord) { let result = false for_each_seat(lord, seat => { @@ -1290,7 +1295,8 @@ exports.setup = function (seed, scenario, options) { approach: 0, avoid: 0, - assets: 0, + spoils: 0, + supply: 0, } update_aliases() @@ -2275,8 +2281,6 @@ function is_first_march() { function goto_actions() { log_h2(`L${game.command}`) - game.state = "actions" - game.who = game.command game.count = 0 game.extra = 0 @@ -2307,6 +2311,15 @@ function goto_actions() { if (this_lord_has_house_of_suzdal()) ++game.extra } + + resume_actions() + update_supply() +} + +function resume_actions() { + game.state = "actions" + game.who = game.command + game.where = NOWHERE } function spend_action(cost) { @@ -2660,8 +2673,8 @@ function march_with_group_3() { conquer_trade_route(to) } - game.where = NOWHERE - game.state = "actions" + resume_actions() + update_supply() } // === ACTION: MARCH - AVOID BATTLE === @@ -2687,13 +2700,13 @@ function stronghold_capacity(loc) { function spoil_prov(lord) { log("Discarded Provender.") add_lord_assets(lord, PROV, -1) - add_global_assets(PROV, 1) + add_spoils(PROV, 1) } function spoil_loot(lord) { log("Discarded Loot.") add_lord_assets(lord, LOOT, -1) - add_global_assets(LOOT, 1) + add_spoils(LOOT, 1) } function can_avoid_battle(to, way) { @@ -3061,8 +3074,7 @@ function end_siege() { set_lord_moved(lord, 1) spend_all_actions() - - game.state = "actions" + resume_actions() } // === ACTION: STORM === @@ -3089,27 +3101,49 @@ function can_action_sally(avail) { function goto_sally() { log("TODO: Sally") spend_action(1) + resume_actions() } // === ACTION: SUPPLY === -function can_supply_from_sea() { +function update_supply() { + // TODO: Lodya - select boat OR ship (we count both here...) + let season = current_season() + let here = get_lord_locale(game.command) + let boats = 0 + let carts = 0 + let sleds = 0 + let ships = 0 + + if (season === SUMMER) { + carts = get_shared_assets(here, CART) + } if (season === SUMMER || season === RASPUTITSA) { - let here = get_lord_locale(game.command) - let ships = count_shared_ships() + count_shared_cogs_not_in_locale(here) - return ships > 0 + boats = count_shared_boats() + ships = count_shared_ships() + count_shared_cogs_not_in_locale(here) + } + if (season === EARLY_WINTER || season === LATE_WINTER) { + sleds = get_shared_assets(here, SLED) } + + if (ships > 2) + ships = 2 + + let sources = list_supply_sources(ships) + let reachable = filter_reachable_supply_sources(sources, boats, carts, sleds) + let supply_seats = filter_usable_supply_seats(reachable) + let supply_seaports = filter_usable_supply_seaports(reachable, ships) + + game.supply = { supply_seats, supply_seaports, seats: 2, boats, carts, sleds, ships } } -function list_supply_sources() { +function list_supply_sources(ships) { let sources = [] - for_each_seat(game.command, seat => { - set_add(sources, seat) - }) + for_each_seat(game.command, seat => { set_add(sources, seat) }, false) - if (can_supply_from_sea()) { + if (ships > 0) { if (game.active === TEUTONS) for (let port of data.seaports) set_add(sources, port) @@ -3120,8 +3154,23 @@ function list_supply_sources() { return sources } -function search_supply_rec(result, seen, sources, here, boats, carts, sleds) { - console.log("search", "".padStart(16-(boats+carts+sleds), "-"), here) +function filter_reachable_supply_sources(sources, boats, carts, sleds) { + let result = [] + search_supply_reachable(result, [], sources, get_lord_locale(game.command), boats, carts, sleds) + return result +} + +function search_supply_reachable(result, seen, sources, here, boats, carts, sleds) { + if (set_has(seen, here)) + return + + if (has_unbesieged_enemy_lord(here)) + return + if (is_unbesieged_enemy_stronghold(here)) + return + if (is_friendly_territory(here) && has_conquered_marker(here)) + if (!has_siege_marker(here)) + return set_add(seen, here) @@ -3130,72 +3179,120 @@ function search_supply_rec(result, seen, sources, here, boats, carts, sleds) { if (boats > 0) for (let next of data.locales[here].adjacent_by_waterway) - search_supply_rec(result, seen, sources, next, boats - 1, carts, sleds) + search_supply_reachable(result, seen, sources, next, boats - 1, carts, sleds) if (carts > 0) for (let next of data.locales[here].adjacent_by_trackway) - search_supply_rec(result, seen, sources, next, boats, carts - 1, sleds) + search_supply_reachable(result, seen, sources, next, boats, carts - 1, sleds) if (sleds > 0) for (let next of data.locales[here].adjacent) - search_supply_rec(result, seen, sources, next, boats, carts, sleds - 1) + search_supply_reachable(result, seen, sources, next, boats, carts, sleds - 1) set_delete(seen, here) } -function search_supply() { - let sources = list_supply_sources() - let start = get_lord_locale(game.command) - - let result = [] - - let boats = get_global_assets(BOAT) - let carts = get_global_assets(CART) - let sleds = get_global_assets(SLED) - - console.log("SUPPLY", sources, boats, carts, sleds) - - search_supply_rec(result, [], sources, start, boats, carts, sleds) +function filter_usable_supply_seats(reachable) { + let sources = [] + for_each_seat( + game.command, + (seat) => { + if (set_has(reachable, seat)) + sources.push(seat) + }, + true + ) + return sources +} - console.log(" => ", result) +function filter_usable_supply_seaports(reachable, ships) { + if (ships > 0) { + let sources = [] + if (game.active === TEUTONS) { + for (let port of data.seaports) { + if (set_has(reachable, port)) { + set_add(sources, port) + } + } + } + if (game.active === RUSSIANS) { + if (set_has(reachable, LOC_NOVGOROD)) { + set_add(sources, LOC_NOVGOROD) + } + } + return sources + } + return null } function can_action_supply(avail) { if (avail < 1) return false - return true + return can_supply() +} + +function can_supply() { + if (game.supply.seats > 0 && game.supply.supply_seats.length > 0) + return true + if (game.supply.ships > 0 && game.supply.supply_seaports.length > 0) + return true + return false } function goto_supply() { push_undo() - game.state = "supply" + game.state = "supply_source" +} - // TODO: Lodya - select boat OR ship (we count both here...) +states.supply_source = { + prompt() { + if (!can_supply()) { + view.prompt = "Supply: No valid supply sources." + return + } - let season = current_season() - let here = get_lord_locale(game.command) - let boats = 0, carts = 0, sleds = 0, ships = 0 - if (season === SUMMER) { - carts = get_shared_assets(here, CART) - } - if (season === SUMMER || season === RASPUTITSA) { - boats = count_shared_boats() - ships = count_shared_ships() + count_shared_cogs_not_in_locale(here) - } - if (season === EARLY_WINTER || season === LATE_WINTER) { - sleds = get_shared_assets(here, SLED) - } + view.prompt = "Supply: Select supply source and route -" + + if (game.supply.boats > 0) + view.prompt += ` ${game.supply.boats} boat` + if (game.supply.carts > 0) + view.prompt += ` ${game.supply.carts} cart` + if (game.supply.sleds > 0) + view.prompt += ` ${game.supply.sleds} sled` + if (game.supply.ships > 0) + view.prompt += ` ${game.supply.ships} ship` + + if (game.supply.seats > 0) + for (let source of game.supply.supply_seats) + gen_action_locale(source) + if (game.supply.ships > 0) + for (let source of game.supply.supply_seaports) + gen_action_locale(source) + view.actions.end_supply = 1 + }, + locale(source) { + // TODO: 2nd ed - no reusing of transports! - set_global_assets(BOAT, boats) - set_global_assets(CART, carts) - set_global_assets(SLED, sleds) - set_global_assets(SHIP, ships) -} + if (game.supply.supply_seats.includes(source)) { + log(`Supplied from seat at %${source}.`) + array_remove_item(game.supply.supply_seats, source) + game.supply.seats-- + } else { + log(`Supplied from seaport at %${source}.`) + game.supply.ships-- + } -states.supply = { - prompt() { - view.prompt = "Supply: Select supply sources." + add_lord_assets(game.command, PROV, 1) - search_supply() + if (!can_supply()) + end_supply() }, + end_supply: end_supply, +} + +function end_supply() { + game.supply = 0 + spend_action(1) + resume_actions() + update_supply() } // === ACTION: FORAGE === @@ -3219,6 +3316,7 @@ function goto_forage() { log(`Foraged at %${here}`) add_lord_assets(game.command, PROV, 1) spend_action(1) + resume_actions() } // === ACTION: RAVAGE === @@ -3329,8 +3427,7 @@ function ravage_location(here, there) { } spend_action(1) - - game.state = "actions" + resume_actions() } // === ACTION: TAX === @@ -3364,6 +3461,7 @@ function goto_tax() { add_lord_assets(game.command, COIN, 1) spend_all_actions() + resume_actions() if (lord_has_capability(game.command, AOW_RUSSIAN_VELIKY_KNYAZ)) { logi("Veliky Knyaz") @@ -3445,7 +3543,8 @@ states.sail = { if (overflow > 0) { view.prompt = `Sailing with ${ships} ships and ${horses} horses. Discard loot or provender.` - // TODO: how strict is greed? + // TODO: stricter greed! + // TODO: if 1 ship, 1 loot, 1 prov - cannot discard prov then loot! if (loot > 0 || prov > 0) { for (let lord of game.group) { if (loot > 0) @@ -3493,8 +3592,8 @@ states.sail = { conquer_trade_route(to) spend_all_actions() - - game.state = "actions" + resume_actions() + update_supply() }, } diff --git a/tools/gendata.js b/tools/gendata.js index e922246..04365b8 100644 --- a/tools/gendata.js +++ b/tools/gendata.js @@ -405,7 +405,7 @@ let lords = [ name: "Knud & Abel", full_name: "Knud & Abel", title: "Princes of Denmark", - seats: seats("Reval, Wesenburg"), + seats: seats("Reval, Wesenberg"), marshal: 0, fealty: 2, service: 3, |