summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2022-12-16 20:13:02 +0100
committerTor Andersson <tor@ccxvii.net>2023-02-18 13:02:38 +0100
commitbf4e77c7d4fe7f9bc79e07700ca0a1e9a42ebc37 (patch)
tree1c7d18b5f3c31f3c64f186c7a54a601631d632c3
parente1a636df18394d60f808f210235045fbf2533291 (diff)
downloadnevsky-bf4e77c7d4fe7f9bc79e07700ca0a1e9a42ebc37.tar.gz
1st edition supply rules.
-rw-r--r--data.js2
-rw-r--r--play.js24
-rw-r--r--rules.js253
-rw-r--r--tools/gendata.js2
4 files changed, 190 insertions, 91 deletions
diff --git a/data.js b/data.js
index c1a60fc..2791617 100644
--- a/data.js
+++ b/data.js
@@ -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},
diff --git a/play.js b/play.js
index ca763aa..680db16 100644
--- a/play.js
+++ b/play.js
@@ -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")
}
diff --git a/rules.js b/rules.js
index 85f4745..f153cba 100644
--- a/rules.js
+++ b/rules.js
@@ -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,