summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-06-25 11:40:54 +0200
committerTor Andersson <tor@ccxvii.net>2023-07-07 19:05:52 +0200
commitd91729e13fd3081907bf38b5a6823aab161c6509 (patch)
tree5fd098cb0ea4db756a81110b57f851188b25c501
parent0648e171052fb2ecc6a9ee94b33dede1846dec99 (diff)
downloadtime-of-crisis-d91729e13fd3081907bf38b5a6823aab161c6509.tar.gz
Combat overlay to show battle lines expanded.
-rw-r--r--play.css13
-rw-r--r--play.html4
-rw-r--r--play.js136
-rw-r--r--rules.js53
-rw-r--r--tools/boxes.svg59
5 files changed, 228 insertions, 37 deletions
diff --git a/play.css b/play.css
index 779794a..03453d0 100644
--- a/play.css
+++ b/play.css
@@ -636,3 +636,16 @@ body.shift #zenobia { background-image: url(images/rival_back.png) }
gap: 18px;
min-height: 350px;
}
+
+/* COMBAT MASK */
+
+#combat_mask {
+ box-sizing: border-box;
+ position: absolute;
+ z-index: 100;
+ border-radius: 12px;
+ box-shadow: 0px 2px 8px #000;
+ background-color: #666;
+ border: 2px solid #444;
+ opacity: 0.8;
+}
diff --git a/play.html b/play.html
index 98db87a..18f4068 100644
--- a/play.html
+++ b/play.html
@@ -125,9 +125,11 @@
</svg>
+<div id="pieces">
+
<div id="active_event"></div>
-<div id="pieces">
+<div id="combat_mask"></div>
<div id="crisis_die_1" class="hide" style="right:175px;top:530px"></div>
<div id="crisis_die_2" class="hide" style="right:125px;top:530px"></div>
diff --git a/play.js b/play.js
index 1b72d44..cf7d345 100644
--- a/play.js
+++ b/play.js
@@ -300,6 +300,11 @@ const BOXES = {
"Goths Dice": [1730,195,100,50],
"Sassanids Dice": [2380,895,100,50],
"Nomads Dice": [570,1520,100,50],
+ "Franks Battle": [750,210,430,180],
+ "Alamanni Battle": [1240,210,430,180],
+ "Goths Battle": [1670,245,430,180],
+ "Sassanids Battle": [2070,980,430,180],
+ "Nomads Battle": [670,1400,430,180],
}
const LAYOUT_XY = [
@@ -326,6 +331,26 @@ const LAYOUT_XY = [
BOXES["Pontus Euxinus XY"],
]
+const LAYOUT_BATTLE = [
+ BOXES["Italia Capital"],
+ BOXES["Asia Capital"],
+ BOXES["Gallia Capital"],
+ BOXES["Macedonia Capital"],
+ BOXES["Pannonia Capital"],
+ BOXES["Thracia Capital"],
+ BOXES["Britannia Capital"],
+ BOXES["Galatia Capital"],
+ BOXES["Syria Capital"],
+ BOXES["Aegyptus Capital"],
+ BOXES["Africa Capital"],
+ BOXES["Hispania Capital"],
+ BOXES["Alamanni Battle"],
+ BOXES["Franks Battle"],
+ BOXES["Goths Battle"],
+ BOXES["Sassanids Battle"],
+ BOXES["Nomads Battle"],
+]
+
const LAYOUT_SUPPORT = [
BOXES["Italia Support 1"],
BOXES["Asia Support"],
@@ -611,6 +636,7 @@ let ui = {
hand: document.getElementById("hand"),
draw: document.getElementById("draw"),
active_event: document.getElementById("active_event"),
+ combat_mask: document.getElementById("combat_mask"),
discard: document.getElementById("discard"),
played: document.getElementById("played"),
market: document.getElementById("market"),
@@ -856,9 +882,39 @@ function on_init() {
let stack_count = new Array(21).fill(0)
-function layout_stack(id, list, region, in_capital, dx, dy) {
+let battle_width = 0
+const BATTLE_MIN = 3
+const BATTLE_H_MARGIN = 12
+const BATTLE_V_MARGIN = 12
+const BATTLE_H_GAP = 8
+const BATTLE_V_GAP = 16
+
+function layout_battle_stack(is_att, list, region) {
+ let [ x, y, w, h ] = LAYOUT_BATTLE[region]
+ x += w >> 1
+ if (region < 12)
+ y -= 7
+
+ if (list.length > battle_width)
+ battle_width = list.length
+
+ w = list.length * 60 + (list.length - 1) * BATTLE_H_GAP
+ x -= w / 2
+
+ y += BATTLE_V_MARGIN
+ if (is_att)
+ y += 60 + BATTLE_V_GAP
+
+ for (let item of list) {
+ item.style.left = x + "px"
+ item.style.top = y + "px"
+ item.style.zIndex = 101
+ x += 60 + BATTLE_H_GAP
+ }
+}
+
+function layout_stack(id, list, region, in_capital, dx, dy, z = 1) {
let [ x, y, w, h ] = LAYOUT_XY[region]
- let z = 1
x += w >> 1
y += h >> 1
@@ -885,7 +941,6 @@ function layout_stack(id, list, region, in_capital, dx, dy) {
item.style.left = x + "px"
item.style.top = y + "px"
item.style.zIndex = z
- item.my_stack = id
x -= dx
y -= dy
z += 1
@@ -900,7 +955,6 @@ function layout_available(list, dx, x0, y0) {
item.style.left = x + "px"
item.style.top = y + "px"
item.style.zIndex = z
- item.my_stack = 0
x += dx
z -= 1
}
@@ -946,6 +1000,18 @@ function layout_barbarian_dice(black, white, tribe) {
}
}
+function is_battle_stack(region, type, id) {
+ if (view.battle_region !== region)
+ return false
+ if (type === "general" && view.battle.attacker === id)
+ return true
+ if (type === "militia" && view.battle.attacker < 0)
+ return true
+ if (type === "militia" && view.battle.type === "militia")
+ return true
+ return view.battle.type === type && view.battle.target === id
+}
+
function on_update() {
let player_count = view.legacy.length
@@ -975,6 +1041,8 @@ function on_update() {
ui.discard.replaceChildren()
ui.market.replaceChildren()
+ battle_width = BATTLE_MIN
+
for (let pi = 0; pi < player_count; ++pi) {
let legacy = view.legacy[pi]
let turns = view.emperor_turns[pi]
@@ -1109,15 +1177,16 @@ function on_update() {
for (let region = 0; region < 12 + 5; ++region) {
for (let tribe = 0; tribe < 5; ++tribe) {
+ let battle = []
let active_barbarians = []
let inactive_barbarians = []
for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id) {
- if (id === CNIVA || id === ARDASHIR || id === SHAPUR)
- continue
let loc = get_barbarian_location(id)
let inactive = is_barbarian_inactive(id)
if (loc === region) {
- if (inactive)
+ if (is_battle_stack(region, "barbarians", tribe) && (region < 12 || !inactive))
+ battle.push(ui.barbarians[id])
+ else if (inactive)
inactive_barbarians.push(ui.barbarians[id])
else
active_barbarians.push(ui.barbarians[id])
@@ -1130,22 +1199,21 @@ function on_update() {
layout_stack(-1, inactive_barbarians, region, false, 8, 8)
if (active_barbarians.length > 0)
layout_stack(-1, active_barbarians, region, false, 8, 8)
+ if (battle.length > 0)
+ layout_battle_stack(0, battle, region)
}
}
- for (let id of [ CNIVA, ARDASHIR, SHAPUR ]) {
- let loc = get_barbarian_location(id)
- if (loc !== AVAILABLE)
- layout_stack(-1, [ ui.barbarians[id] ], loc, false, 8, 8)
- }
-
for (let id = 0; id < 3; ++id) {
let loc = get_rival_emperor_location(id)
if (loc === AVAILABLE)
hide(ui.rival_emperors[id])
else {
show(ui.rival_emperors[id])
- layout_stack(-1, [ ui.rival_emperors[id] ], loc, false, 8, 8)
+ if (is_battle_stack(loc, "rival_emperor", id))
+ layout_battle_stack(0, [ ui.rival_emperors[id] ], loc)
+ else
+ layout_stack(-1, [ ui.rival_emperors[id] ], loc, false, 8, 8)
}
}
@@ -1170,10 +1238,17 @@ function on_update() {
}
}
if (lone_militia) {
- if (has_militia_castra(region))
- layout_stack(-1, [ ui.mcastra[region], ui.militia[region] ], region, true, 16, 16)
- else
- layout_stack(-1, [ ui.militia[region] ], region, true, 16, 16)
+ if (is_battle_stack(region, "militia", region)) {
+ if (has_militia_castra(region))
+ layout_battle_stack(view.battle.attacker < 0, [ ui.mcastra[region], ui.militia[region] ], region)
+ else
+ layout_battle_stack(view.battle.attacker < 0, [ ui.militia[region] ], region, true)
+ } else {
+ if (has_militia_castra(region))
+ layout_stack(-1, [ ui.mcastra[region], ui.militia[region] ], region, true, 16, 16)
+ else
+ layout_stack(-1, [ ui.militia[region] ], region, true, 16, 16)
+ }
}
}
@@ -1235,10 +1310,10 @@ function on_update() {
if (has_militia(region) && inside)
stack.push(ui.militia[region])
- if (inside)
- layout_stack(pi * 6 + ai, stack, region, true, 16, 16)
+ if (is_battle_stack(region, "general", pi * 6 + ai))
+ layout_battle_stack(pi * 6 + ai === view.battle.attacker, stack, region)
else
- layout_stack(pi * 6 + ai, stack, region, false, 16, 16)
+ layout_stack(pi * 6 + ai, stack, region, inside, 16, 16)
} else {
avail_stack.push(e)
}
@@ -1278,6 +1353,25 @@ function on_update() {
layout_available(avail_stack, 64, pi * 625 + 325, 27)
}
+ if (view.battle) {
+ let [ x, y, w, h ] = LAYOUT_BATTLE[view.battle_region]
+ if (view.battle_region < 12)
+ y -= 7
+ x += w >> 1
+
+ w = battle_width * 60 + (battle_width - 1) * BATTLE_H_GAP + BATTLE_H_MARGIN * 2
+ x -= w / 2
+ h = 120 + BATTLE_V_GAP + BATTLE_V_MARGIN * 2
+
+ show(ui.combat_mask)
+ ui.combat_mask.style.left = x + "px"
+ ui.combat_mask.style.top = y + "px"
+ ui.combat_mask.style.width = w + "px"
+ ui.combat_mask.style.height = h + "px"
+ } else {
+ hide(ui.combat_mask)
+ }
+
ui.body.classList.toggle("military", view.color === 0)
ui.body.classList.toggle("senate", view.color === 1)
ui.body.classList.toggle("populace", view.color === 2)
diff --git a/rules.js b/rules.js
index 2102329..afce366 100644
--- a/rules.js
+++ b/rules.js
@@ -2989,14 +2989,15 @@ function gen_initiate_battle(where) {
function format_battle_target() {
switch (game.battle.type) {
- case "militia": return "militia"
+ case "militia": return PLAYER_NAMES[get_province_player(game.battle.target)] + " militia"
case "barbarians": return BARBARIAN_NAME[game.battle.target]
- case "general": return GENERAL_NAME[game.battle.target]
+ case "general": return PLAYER_NAMES[game.battle.target / 6 | 0] + " army"
case "rival_emperor": return RIVAL_EMPEROR_NAME[game.battle.target]
}
}
states.battle = {
+ show_battle: true,
inactive: "Combat",
prompt() {
prompt("Initiate Battle against " + format_battle_target() + " in " + REGION_NAME[game.where] + ".")
@@ -3023,10 +3024,18 @@ states.battle = {
}
function format_hits() {
- return `${game.battle.ahits} hits to attacker vs ${game.battle.dhits} hits to defender`
+ let s = "Defender rolled " + game.battle.ahits + " hit"
+ if (game.battle.ahits !== 1)
+ s += "s"
+ s += ", attacker rolled " + game.battle.dhits + " hit"
+ if (game.battle.dhits !== 1)
+ s += "s"
+ return s
}
states.flanking_maneuver = {
+ show_battle: true,
+ get inactive() { return "Flanking Maneuver. " + format_hits() },
inactive: "Flanking Maneuver",
prompt() {
prompt("Flanking Maneuver: " + format_hits() + ".")
@@ -3299,13 +3308,14 @@ function goto_assign_hits_on_defender() {
if (has_hits_on_defender())
game.state = "assign_hits_on_defender"
else
- goto_combat_victory()
+ game.state = "combat_victory"
}
states.assign_hits_on_attacker = {
- inactive: "Combat",
+ show_battle: true,
+ get inactive() { return "Combat. " + format_hits() },
prompt() {
- prompt("Combat: " + format_hits() + " \u2013 assign " + (game.battle.ahits - game.battle.ataken) + " hits to attacker!")
+ prompt("Combat: " + format_hits() + ".")
if (game.battle.attacker < 0)
gen_hits_militia()
else
@@ -3332,9 +3342,10 @@ states.assign_hits_on_attacker = {
}
states.assign_hits_on_defender = {
- inactive: "Combat",
+ show_battle: true,
+ get inactive() { return "Combat. " + format_hits() },
prompt() {
- prompt("Combat: " + format_hits() + " \u2013 assign " + (game.battle.dhits - game.battle.dtaken) + " hits to defender!")
+ prompt("Combat: " + format_hits() + ".")
switch (game.battle.type) {
case "militia":
gen_hits_militia()
@@ -3400,6 +3411,25 @@ function is_defender_eliminated() {
return false
}
+states.combat_victory = {
+ show_battle: true,
+ inactive: "Combat",
+ prompt() {
+ let de = is_defender_eliminated()
+ let ae = is_attacker_eliminated()
+ if (de && ae)
+ prompt("Combat: There is no winner.")
+ else if (de || game.battle.dtaken > game.battle.ataken)
+ prompt("Combat: You win the battle!")
+ else
+ prompt("Combat: The defenders win the battle!")
+ view.actions.done = 1
+ },
+ done() {
+ goto_combat_victory()
+ },
+}
+
function goto_combat_victory() {
let de = is_defender_eliminated()
let ae = is_attacker_eliminated()
@@ -3489,6 +3519,7 @@ function end_battle() {
// Deselect eliminated general...
if (game.selected_general >= 0 && get_general_location(game.selected_general) === AVAILABLE)
game.selected_general = -1
+
if (game.battle.killed) {
if (can_free_increase_support_level(game.where)) {
game.state = "free_increase_support_level"
@@ -3498,6 +3529,7 @@ function end_battle() {
game.killed |= game.battle.killed
}
}
+
game.battle = null
game.state = "take_actions"
}
@@ -4257,6 +4289,11 @@ exports.view = function (state, player_name) {
emperor_turns: game.emperor_turns,
}
+ if (game.battle && states[game.state].show_battle) {
+ view.battle = game.battle
+ view.battle_region = game.where
+ }
+
if (game.state === "game_over") {
view.prompt = game.victory
} else if (game.current !== player) {
diff --git a/tools/boxes.svg b/tools/boxes.svg
index 8f81dcb..435d0f7 100644
--- a/tools/boxes.svg
+++ b/tools/boxes.svg
@@ -40,9 +40,9 @@
inkscape:window-height="480"
id="namedview6"
showgrid="true"
- inkscape:zoom="1.4142136"
- inkscape:cx="859.33219"
- inkscape:cy="1408.35"
+ inkscape:zoom="0.89299613"
+ inkscape:cx="474.18327"
+ inkscape:cy="1325.4002"
inkscape:current-layer="svg4"
inkscape:document-rotation="0">
<inkscape:grid
@@ -52,11 +52,11 @@
<image
sodipodi:absref="/home/tor/src/rally/public/time-of-crisis/map75.png"
xlink:href="../map75.png"
- style="display:inline;image-rendering:pixelated"
- width="2550"
- height="1650"
+ sodipodi:insensitive="true"
id="image2"
- sodipodi:insensitive="true" />
+ height="1650"
+ width="2550"
+ style="display:inline;image-rendering:pixelated" />
<rect
style="fill:#e60000;fill-opacity:0.325203;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
id="rect873"
@@ -426,4 +426,49 @@
y="1520"
ry="2.0349298"
inkscape:label="Nomads Dice" />
+ <rect
+ style="fill:#0027e6;fill-opacity:0.325203;stroke:none;stroke-width:12.3648;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect908-3-3"
+ width="430"
+ height="180"
+ x="750"
+ y="210"
+ ry="7.325747"
+ inkscape:label="Franks Battle" />
+ <rect
+ style="fill:#0027e6;fill-opacity:0.325203;stroke:none;stroke-width:12.3648;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect908-3-3-6"
+ width="430"
+ height="180"
+ x="1240"
+ y="210"
+ ry="7.325747"
+ inkscape:label="Alamanni Battle" />
+ <rect
+ style="fill:#0027e6;fill-opacity:0.325203;stroke:none;stroke-width:12.3648;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect908-3-3-6-7"
+ width="430"
+ height="180"
+ x="1670"
+ y="245"
+ ry="7.325747"
+ inkscape:label="Goths Battle" />
+ <rect
+ style="fill:#0027e6;fill-opacity:0.325203;stroke:none;stroke-width:12.3648;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect908-3-3-6-7-5"
+ width="430"
+ height="180"
+ x="2070"
+ y="980"
+ ry="7.325747"
+ inkscape:label="Sassanids Battle" />
+ <rect
+ style="fill:#0027e6;fill-opacity:0.325203;stroke:none;stroke-width:12.3648;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.581301"
+ id="rect908-3-3-6-7-5-3"
+ width="430"
+ height="180"
+ x="670"
+ y="1400"
+ ry="7.325747"
+ inkscape:label="Nomads Battle" />
</svg>