summaryrefslogtreecommitdiff
path: root/play.js
diff options
context:
space:
mode:
Diffstat (limited to 'play.js')
-rw-r--r--play.js729
1 files changed, 729 insertions, 0 deletions
diff --git a/play.js b/play.js
new file mode 100644
index 0000000..e6f05dd
--- /dev/null
+++ b/play.js
@@ -0,0 +1,729 @@
+"use strict"
+
+const MAP_DPI = 75
+
+const round = Math.round
+const floor = Math.floor
+const ceil = Math.ceil
+
+function pack1_get(word, n) {
+ return (word >>> n) & 1
+}
+
+function pack4_get(word, n) {
+ n = n << 2
+ return (word >>> n) & 15
+}
+
+function is_lord_action(lord) {
+ return !!(view.actions && view.actions.lord && view.actions.lord.includes(lord))
+}
+
+function is_service_action(lord) {
+ return !!(view.actions && view.actions.service && view.actions.service.includes(lord))
+}
+
+function is_vassal_action(vassal) {
+ return !!(view.actions && view.actions.vassal && view.actions.vassal.includes(vassal))
+}
+
+function is_locale_action(locale) {
+ return !!(view.actions && view.actions.locale && view.actions.locale.includes(locale))
+}
+
+const force_type_count = 7
+const force_type_name = [ "knights", "sergeants", "light_horse", "asiatic_horse", "men_at_arms", "militia", "serfs" ]
+
+const asset_type_count = 7
+const asset_type_name = [ "prov", "coin", "loot", "cart", "sled", "boat", "ship" ]
+const asset_type_x3 = [ 1, 1, 1, 0, 0, 0, 0 ]
+
+const first_teutonic_region = 0
+const last_teutonic_region = 23
+const first_russian_region = 24
+const last_russian_region = 52
+
+function is_teutonic_region(loc) {
+ return loc >= first_teutonic_region && loc <= last_teutonic_region
+}
+
+function is_russian_region(loc) {
+ return loc >= first_russian_region && loc <= last_russian_region
+}
+
+function count_teutonic_vp() {
+ let vp = 0
+ for (let loc of view.conquered)
+ if (is_russian_region(loc))
+ vp += data.locales[loc].vp << 1
+ for (let loc of view.ravaged)
+ if (is_russian_region(loc))
+ vp += 1
+ return vp
+}
+
+function count_russian_vp() {
+ let vp = view.veche_vp * 2
+ for (let loc of view.conquered)
+ if (is_teutonic_region(loc))
+ vp += data.locales[loc].vp << 1
+ for (let loc of view.ravaged)
+ if (is_teutonic_region(loc))
+ vp += 1
+ return vp
+}
+
+function is_card_in_use(c) {
+ if (view.global_cards.includes(c))
+ return true
+ if (view.lords.cards.includes(c))
+ return true
+ if (c === 18 || c === 19 || c === 20)
+ return true
+ if (c === 39 || c === 40 || c === 41)
+ return true
+ return false
+}
+
+function for_each_teutonic_arts_of_war(fn) {
+ for (let i = 0; i < 21; ++i)
+ fn(i)
+}
+
+function for_each_russian_arts_of_war(fn) {
+ for (let i = 21; i < 42; ++i)
+ fn(i)
+}
+
+function for_each_friendly_arts_of_war(fn) {
+ if (player === "Teutons")
+ for_each_teutonic_arts_of_war(fn)
+ else
+ for_each_russian_arts_of_war(fn)
+}
+
+function for_each_enemy_arts_of_war(fn) {
+ if (player !== "Teutons")
+ for_each_teutonic_arts_of_war(fn)
+ else
+ for_each_russian_arts_of_war(fn)
+}
+
+const original_boxes = {
+ "way crossroads": [1500,4717,462,149],
+ "way wirz": [1295,4526,175,350],
+ "way peipus-east": [2232,4197,220,480],
+ "way peipus-north": [2053,3830,361,228],
+ // "way peipus-west": [1988,4141,218,520],
+ "calendar summer box1": [40,168,590,916],
+ "calendar summer box2": [650,168,590,916],
+ "calendar winter box3": [1313,168,590,916],
+ "calendar winter box4": [1922,168,590,916],
+ "calendar winter box5": [2587,168,590,916],
+ "calendar winter box6": [3196,168,590,916],
+ "calendar rasputitsa box7": [3860,168,590,916],
+ "calendar rasputitsa box8": [4470,168,590,916],
+ "calendar summer box9": [40,1120,590,916],
+ "calendar summer box10": [650,1120,590,916],
+ "calendar winter box11": [1313,1120,590,916],
+ "calendar winter box12": [1922,1120,590,916],
+ "calendar winter box13": [2587,1120,590,916],
+ "calendar winter box14": [3196,1120,590,916],
+ "calendar rasputitsa box15": [3860,1120,590,916],
+ "calendar rasputitsa box16": [4470,1120,590,916],
+ // "victory": [176,185,210,210],
+ // "turn": [402,185,210,210],
+}
+
+const calendar_xy = [
+ [0, 0],
+ [40,168],
+ [650,168],
+ [1313,168],
+ [1922,168],
+ [2587,168],
+ [3196,168],
+ [3860,168],
+ [4470,168],
+ [40,1120],
+ [650,1120],
+ [1313,1120],
+ [1922,1120],
+ [2587,1120],
+ [3196,1120],
+ [3860,1120],
+ [4470,1120],
+ [4470, 2052],
+].map(([x,y])=>[x/4|0,y/4|0])
+
+const locale_xy = []
+
+const ui = {
+ locale: [],
+ locale_extra: [],
+ lord_cylinder: [],
+ lord_service: [],
+ lord_mat: [],
+ vassal_service: [],
+ forces: [],
+ routed: [],
+ assets: [],
+ c1: [],
+ c2: [],
+ arts_of_war: [],
+ boxes: {},
+ veche: document.getElementById("veche"),
+ arts_of_war_dialog: document.getElementById("arts_of_war"),
+ arts_of_war_list: document.getElementById("arts_of_war_list"),
+ p1_global: document.getElementById("p1_global"),
+ p2_global: document.getElementById("p2_global"),
+ turn: document.getElementById("turn"),
+ vp1: document.getElementById("vp1"),
+ vp2: document.getElementById("vp2"),
+}
+
+let locale_layout = new Array(data.locales.length).fill(0)
+let calendar_layout = new Array(18).fill(0)
+
+function clean_name(name) {
+ return name.toLowerCase().replaceAll("&", "and").replaceAll(" ", "_")
+}
+
+const extra_size_100 = {
+ town: [ 60, 42 ],
+ castle: [ 60, 42 ],
+ fort: [ 72, 42 ],
+ traderoute: [ 72, 42 ],
+ bishopric: [ 84, 60 ],
+ city: [ 132, 72 ],
+ archbishopric: [ 156, 96 ],
+}
+
+const extra_size = {
+ town: [ 45, 32 ],
+ castle: [ 45, 32 ],
+ fort: [ 54, 32 ],
+ traderoute: [ 54, 32 ],
+ bishopric: [ 63, 45 ],
+ city: [ 100, 54 ],
+ archbishopric: [ 117, 72 ],
+}
+
+function toggle_pieces() {
+ document.getElementById("pieces").classList.toggle("hide")
+}
+
+function on_click_locale(evt) {
+ if (evt.button === 0) {
+ let id = evt.target.dataset.locale | 0
+ send_action('locale', id)
+ }
+}
+
+function on_focus_locale(evt) {
+ let id = evt.target.dataset.locale | 0
+ document.getElementById("status").textContent = `(${id}) ${data.locales[id].name} - ${data.locales[id].type}`
+}
+
+function on_click_cylinder(evt) {
+ if (evt.button === 0) {
+ let id = evt.target.dataset.lord | 0
+ send_action('lord', id)
+ }
+}
+
+function on_click_arts_of_war(evt) {
+console.log("AOW CLICK", evt.target.dataset.arts_of_war)
+ if (evt.button === 0) {
+ let id = evt.target.dataset.arts_of_war | 0
+ send_action('arts_of_war', id)
+ }
+}
+
+function on_focus_cylinder(evt) {
+ let id = evt.target.dataset.lord | 0
+ document.getElementById("status").textContent = `(${id}) ${data.lords[id].full_name} [${data.lords[id].command}] - ${data.lords[id].title}`
+}
+
+function on_click_lord_service_marker(evt) {
+ if (evt.button === 0) {
+ let id = evt.target.dataset.lord | 0
+ send_action('lord_service', id)
+ }
+}
+
+function on_focus_lord_service_marker(evt) {
+ let id = evt.target.dataset.lord | 0
+ document.getElementById("status").textContent = `(${id}) ${data.lords[id].full_name} - ${data.lords[id].title}`
+}
+
+function on_click_vassal_service_marker(evt) {
+ if (evt.button === 0) {
+ let id = evt.target.dataset.vassal | 0
+ send_action('vassal', id)
+ }
+}
+
+function on_focus_vassal_service_marker(evt) {
+ let id = evt.target.dataset.vassal | 0
+ let vassal = data.vassals[id]
+ let lord = data.lords[vassal.lord]
+ document.getElementById("status").textContent = `(${id}) ${lord.name} / ${vassal.name}`
+}
+
+function on_blur(evt) {
+ document.getElementById("status").textContent = ""
+}
+
+function on_focus_card_tip(c) {
+}
+
+function on_blur_card_tip(c) {
+}
+
+function sub_card_name(match, p1) {
+ let x = p1 | 0
+ let n = data.cards[x].name
+ return `<span class="card_tip" onmouseenter="on_focus_card_tip(${x})" onmouseleave="on_blur_card_tip(${x})">${n}</span>`
+}
+
+function on_focus_locale_tip(loc) {
+ ui.locale[loc].classList.add("tip")
+ ui.locale_extra[loc].classList.add("tip")
+}
+
+function on_blur_locale_tip(loc) {
+ ui.locale[loc].classList.remove("tip")
+ ui.locale_extra[loc].classList.remove("tip")
+}
+
+function on_click_locale_tip(loc) {
+ ui.locale[loc].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" })
+}
+
+function sub_locale_name(match, p1) {
+ let x = p1 | 0
+ let n = data.locales[x].name
+ return `<span class="locale_tip" onmouseenter="on_focus_locale_tip(${x})" onmouseleave="on_blur_locale_tip(${x})" onclick="on_click_locale_tip(${x})">${n}</span>`
+}
+
+function on_log(text) {
+ let p = document.createElement("div")
+
+ if (text.match(/^>>/)) {
+ text = text.substring(2)
+ p.className = "ii"
+ }
+
+ if (text.match(/^>/)) {
+ text = text.substring(1)
+ p.className = "i"
+ }
+
+ text = text.replace(/&/g, "&amp;")
+ text = text.replace(/</g, "&lt;")
+ text = text.replace(/>/g, "&gt;")
+
+ text = text.replace(/#(\d+)/g, sub_card_name)
+ text = text.replace(/%(\d+)/g, sub_locale_name)
+
+ if (text.match(/^\.h1/)) {
+ text = text.substring(4)
+ p.className = "h1"
+ }
+ if (text.match(/^\.h2/)) {
+ text = text.substring(4)
+ if (text.startsWith("Teuton"))
+ p.className = "h2 teutonic"
+ else if (text.startsWith("Russian"))
+ p.className = "h2 russian"
+ else
+ p.className = "h2"
+ }
+ if (text.match(/^\.h3/)) {
+ text = text.substring(4)
+ p.className = "h3"
+ }
+ if (text.match(/^\.h4/)) {
+ text = text.substring(4)
+ p.className = "h4"
+ }
+
+ p.innerHTML = text
+ return p
+}
+
+function layout_locale_item(loc, e) {
+ let [x, y] = locale_xy[loc]
+ x += locale_layout[loc] * (46 + 6)
+ e.style.top = (y - 23) + "px"
+ e.style.left = (x - 23) + "px"
+ locale_layout[loc] ++
+}
+
+function layout_calendar_item(loc, e) {
+ let [x, y] = calendar_xy[loc]
+ y += 66 + calendar_layout[loc] * 42
+ x += 24 + calendar_layout[loc] * 6
+ e.style.top = (y + 4) + "px"
+ e.style.left = (x + 4) + "px"
+ calendar_layout[loc] ++
+}
+
+function add_force(parent, type) {
+ // TODO: reuse pool of elements?
+ build_div(parent, "unit " + force_type_name[type], "force", type)
+}
+
+function add_asset(parent, type, n) {
+ // TODO: reuse pool of elements?
+ build_div(parent, "asset " + asset_type_name[type] + " x"+n, "asset", type)
+}
+
+function update_forces(parent, forces) {
+ parent.replaceChildren()
+ for (let i = 0; i < force_type_count; ++i) {
+ let n = pack4_get(forces, i)
+ for (let k = 0; k < n; ++k) {
+ add_force(parent, i)
+ }
+ }
+}
+
+function update_assets(parent, assets) {
+ parent.replaceChildren()
+ for (let i = 0; i < asset_type_count; ++i) {
+ let n = pack4_get(assets, i)
+ while (n >= 4) {
+ add_asset(parent, i, 4)
+ n -= 4
+ }
+ if (asset_type_x3[i]) {
+ while (n >= 3) {
+ add_asset(parent, i, 3)
+ n -= 3
+ }
+ }
+ while (n >= 2) {
+ add_asset(parent, i, 2)
+ n -= 2
+ }
+ while (n >= 1) {
+ add_asset(parent, i, 1)
+ n -= 1
+ }
+ }
+}
+
+function update_vassals(parent, lord_ix) {
+ for (let v of data.lords[lord_ix].vassals) {
+ let e = ui.vassal_service[v]
+ if (view.vassals[v] === 0) {
+ e.classList.remove("hide")
+ parent.appendChild(e)
+ } else {
+ e.classList.add("hide")
+ }
+ e.classList.toggle("action", is_vassal_action(v))
+ }
+}
+
+function update_lord_mat(ix) {
+ update_assets(ui.assets[ix], view.lords.assets[ix])
+ update_vassals(ui.assets[ix], ix)
+ update_forces(ui.forces[ix], view.lords.forces[ix])
+ update_forces(ui.routed[ix], view.lords.routed_forces[ix])
+}
+
+function update_lord(ix) {
+ let locale = view.lords.locale[ix]
+ let service = view.lords.service[ix]
+ if (locale < 0) {
+ ui.lord_cylinder[ix].classList.add("hide")
+ ui.lord_service[ix].classList.add("hide")
+ ui.lord_mat[ix].classList.add("hide")
+ ui.lord_mat[ix].classList.remove("action")
+ return
+ }
+ if (locale < 100) {
+ layout_locale_item(locale, ui.lord_cylinder[ix])
+ layout_calendar_item(service, ui.lord_service[ix])
+ ui.lord_cylinder[ix].classList.remove("hide")
+ ui.lord_service[ix].classList.remove("hide")
+ ui.lord_mat[ix].classList.remove("hide")
+ update_lord_mat(ix)
+ } else {
+ layout_calendar_item(locale - 100, ui.lord_cylinder[ix])
+ ui.lord_cylinder[ix].classList.remove("hide")
+ ui.lord_service[ix].classList.add("hide")
+ ui.lord_mat[ix].classList.add("hide")
+ }
+ ui.lord_cylinder[ix].classList.toggle("action", is_lord_action(ix))
+ ui.lord_service[ix].classList.toggle("action", is_service_action(ix))
+
+ ui.lord_cylinder[ix].classList.toggle("selected", ix === view.who)
+ ui.lord_mat[ix].classList.toggle("selected", ix === view.who)
+}
+
+function update_locale(loc) {
+ ui.locale[loc].classList.toggle("action", is_locale_action(loc))
+ if (ui.locale_extra[loc])
+ ui.locale_extra[loc].classList.toggle("action", is_locale_action(loc))
+}
+
+function update_arts_of_war() {
+ if (view.actions && view.actions.arts_of_war) {
+ ui.arts_of_war_dialog.classList.remove("hide")
+ ui.arts_of_war_list.replaceChildren()
+ for_each_friendly_arts_of_war(c => {
+ if (!is_card_in_use(c)) {
+ let elt = ui.arts_of_war[c]
+ ui.arts_of_war_list.appendChild(elt)
+ elt.classList.toggle("action", view.actions.arts_of_war.includes(c))
+ elt.classList.toggle("disabled", !view.actions.arts_of_war.includes(c))
+ }
+ })
+ } else {
+ ui.arts_of_war_dialog.classList.add("hide")
+ for (let c = 0; c < 42; ++c) {
+ let elt = ui.arts_of_war[c]
+ elt.classList.remove("action")
+ elt.classList.remove("disabled")
+ }
+ }
+
+ ui.p1_global.replaceChildren()
+ for_each_teutonic_arts_of_war(c => {
+ if (view.global_cards.includes(c))
+ ui.p1_global.appendChild(ui.arts_of_war[c])
+ })
+
+ ui.p2_global.replaceChildren()
+ for_each_russian_arts_of_war(c => {
+ if (view.global_cards.includes(c))
+ ui.p2_global.appendChild(ui.arts_of_war[c])
+ })
+
+ for (let ix = 0; ix < data.lords.length; ++ix) {
+ let side = ix < 6 ? "teutonic" : "russian"
+ let c = view.lords.cards[(ix << 1) + 0]
+ if (c < 0)
+ ui.c1[ix].classList = `c1 card ${side} hide`
+ else
+ ui.c1[ix].classList = `c1 card ${side} aow_${c}`
+ c = view.lords.cards[(ix << 1) + 1]
+ if (c < 0)
+ ui.c2[ix].classList = `c2 card ${side} hide`
+ else
+ ui.c2[ix].classList = `c2 card ${side} aow_${c}`
+ }
+}
+
+function on_update() {
+ locale_layout.fill(0)
+ calendar_layout.fill(0)
+
+ for (let ix = 0; ix < data.lords.length; ++ix) {
+ if (view.lords[ix] === null) {
+ ui.lord_cylinder[ix].classList.add("hide")
+ ui.lord_service[ix].classList.add("hide")
+ ui.lord_mat[ix].classList.add("hide")
+ } else {
+ ui.lord_cylinder[ix].classList.remove("hide")
+ update_lord(ix)
+ }
+ }
+
+ for (let loc = 0; loc < data.locales.length; ++loc) {
+ update_locale(loc)
+ }
+
+ if (view.turn & 1)
+ ui.turn.className = `marker circle turn campaign t${view.turn>>1}`
+ else
+ ui.turn.className = `marker circle turn levy t${view.turn>>1}`
+
+ let vp1 = count_teutonic_vp()
+ let vp2 = count_russian_vp()
+ if ((vp1 >> 1) === (vp2 >> 1)) {
+ if (vp1 & 1)
+ ui.vp1.className = `marker circle victory teutonic stack v${vp1>>1} half`
+ else
+ ui.vp1.className = `marker circle victory teutonic stack v${vp1>>1}`
+ if (vp2 & 1)
+ ui.vp2.className = `marker circle victory russian stack v${vp2>>1} half`
+ else
+ ui.vp2.className = `marker circle victory russian stack v${vp2>>1}`
+ } else {
+ if (vp1 & 1)
+ ui.vp1.className = `marker circle victory teutonic v${vp1>>1} half`
+ else
+ ui.vp1.className = `marker circle victory teutonic v${vp1>>1}`
+ if (vp2 & 1)
+ ui.vp2.className = `marker circle victory russian v${vp2>>1} half`
+ else
+ ui.vp2.className = `marker circle victory russian v${vp2>>1}`
+ }
+
+ update_arts_of_war()
+
+ action_button("ship", "Ship")
+ action_button("boat", "Boat")
+ action_button("cart", "Cart")
+ action_button("sled", "Sled")
+
+ action_button("capability", "Capability")
+
+ action_button("done", "Done")
+ action_button("end_levy", "End levy")
+ action_button("end_muster", "End muster")
+ action_button("end_setup", "End setup")
+ action_button("undo", "Undo")
+}
+
+function build_div(parent, className, dataname, datavalue, onclick) {
+ let e = document.createElement("div")
+ e.className = className
+ if (dataname)
+ e.dataset[dataname] = datavalue
+ if (onclick)
+ e.addEventListener("mousedown", onclick)
+ parent.appendChild(e)
+ return e
+}
+
+function build_lord_mat(lord, ix, side, name) {
+ let parent = document.getElementById(side === 'teutonic' ? "p1_court" : "p2_court")
+ let mat = build_div(parent, `mat ${side} ${name} hide`)
+ let bg = build_div(mat, "background")
+ ui.forces[ix] = build_div(bg, "forces", "lord", ix)
+ ui.routed[ix] = build_div(bg, "routed", "lord", ix)
+ ui.assets[ix] = build_div(bg, "assets", "lord", ix)
+ ui.c1[ix] = build_div(mat, `c1 card ${side} hide`, "lord", ix)
+ ui.c2[ix] = build_div(mat, `c2 card ${side} hide`, "lord", ix)
+ ui.lord_mat[ix] = mat
+}
+
+function build_arts_of_war(side, c) {
+ let card = ui.arts_of_war[c] = document.createElement("div")
+ card.className = `card ${side} aow_${c}`
+ card.dataset.arts_of_war = c
+ card.addEventListener("mousedown", on_click_arts_of_war)
+}
+
+function build_map() {
+ data.locales.forEach((locale, ix) => {
+ let e = ui.locale[ix] = document.createElement("div")
+ let region = clean_name(locale.region)
+ e.className = "locale " + locale.type + " " + region
+ // XXX e.classList.add("action")
+ let x = round(locale.box.x * MAP_DPI / 300)
+ let y = round(locale.box.y * MAP_DPI / 300)
+ let w = floor((locale.box.x+locale.box.w) * MAP_DPI / 300) - x
+ let h = floor((locale.box.y+locale.box.h) * MAP_DPI / 300) - y
+ if (locale.type === 'town') {
+ locale_xy[ix] = [ round(x + w / 2), y - 24 ]
+ x -= 11
+ y -= 5
+ w += 16
+ h += 5
+ } else if (locale.type === 'region') {
+ locale_xy[ix] = [ round(x + w / 2), round(y + h / 2) ]
+ x -= 3
+ y -= 4
+ } else {
+ locale_xy[ix] = [ round(x + w / 2), y - 36 ]
+ x -= 2
+ y -= 2
+ w -= 2
+ h -= 2
+ }
+ e.style.left = x + "px"
+ e.style.top = y + "px"
+ e.style.width = w + "px"
+ e.style.height = h + "px"
+ e.dataset.locale = ix
+ e.addEventListener("mousedown", on_click_locale)
+ e.addEventListener("mouseenter", on_focus_locale)
+ e.addEventListener("mouseleave", on_blur)
+ document.getElementById("locales").appendChild(e)
+
+ if (locale.type !== 'region') {
+ e = ui.locale_extra[ix] = document.createElement("div")
+ e.className = "locale_extra " + locale.type + " " + region
+ // XXX e.classList.add("action")
+ let cx = x + (w >> 1) + 4
+ let ew = extra_size[locale.type][0]
+ let eh = extra_size[locale.type][1]
+ e.style.top = (y - eh) + "px"
+ e.style.left = (cx - ew/2) + "px"
+ e.style.width = ew + "px"
+ e.style.height = eh + "px"
+ e.dataset.locale = ix
+ e.addEventListener("mousedown", on_click_locale)
+ e.addEventListener("mouseenter", on_focus_locale)
+ e.addEventListener("mouseleave", on_blur)
+ document.getElementById("locales").appendChild(e)
+ }
+ })
+
+ let x = 160
+ let y = 2740
+ data.lords.forEach((lord, ix) => {
+ let e = ui.lord_cylinder[ix] = document.createElement("div")
+ e.className = "cylinder lord " + clean_name(lord.side) + " " + clean_name(lord.name) + " hide"
+ e.dataset.lord = ix
+ e.addEventListener("mousedown", on_click_cylinder)
+ e.addEventListener("mouseenter", on_focus_cylinder)
+ e.addEventListener("mouseleave", on_blur)
+ document.getElementById("pieces").appendChild(e)
+
+ e = ui.lord_service[ix] = document.createElement("div")
+ e.className = "service_marker lord image" + lord.image + " " + clean_name(lord.side) + " " + clean_name(lord.name) + " hide"
+ e.dataset.lord = ix
+ e.addEventListener("mousedown", on_click_lord_service_marker)
+ e.addEventListener("mouseenter", on_focus_lord_service_marker)
+ e.addEventListener("mouseleave", on_blur)
+ document.getElementById("pieces").appendChild(e)
+
+ build_lord_mat(lord, ix, clean_name(lord.side), clean_name(lord.name))
+
+ x += 70
+ })
+
+ data.vassals.forEach((vassal, ix) => {
+ let lord = data.lords[vassal.lord]
+ let e = ui.vassal_service[ix] = document.createElement("div")
+ e.className = "service_marker vassal image" + vassal.image + " " + clean_name(lord.side) + " " + clean_name(vassal.name) + " hide"
+ e.dataset.vassal = ix
+ e.addEventListener("mousedown", on_click_vassal_service_marker)
+ e.addEventListener("mouseenter", on_focus_vassal_service_marker)
+ e.addEventListener("mouseleave", on_blur)
+ document.getElementById("pieces").appendChild(e)
+ })
+
+ for (let name in original_boxes) {
+ let x = round(original_boxes[name][0] * MAP_DPI / 300)
+ let y = round(original_boxes[name][1] * MAP_DPI / 300)
+ let w = round(original_boxes[name][2] * MAP_DPI / 300) - 8
+ let h = round(original_boxes[name][3] * MAP_DPI / 300) - 8
+ let e = ui.boxes[name] = document.createElement("div")
+ e.className = "box " + name
+ // XXX e.classList.add("action")
+ e.style.left = x + "px"
+ e.style.top = y + "px"
+ e.style.width = w + "px"
+ e.style.height = h + "px"
+ document.getElementById("boxes").appendChild(e)
+ }
+
+ for (let c = 0; c < 21; ++c)
+ build_arts_of_war("teutonic", c)
+ for (let c = 21; c < 42; ++c)
+ build_arts_of_war("russian", c)
+}
+
+build_map()
+// drag_element_with_mouse("#battle", "#battle_header")
+drag_element_with_mouse("#arts_of_war", "#arts_of_war_header")
+scroll_with_middle_mouse("main")