summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-11-25 19:35:27 +0100
committerTor Andersson <tor@ccxvii.net>2024-01-08 16:36:47 +0100
commitdc6e8cd3fe0106c6233685e6d637c8b17cd635ea (patch)
tree0c8f7b5e7ac0b560f81a2af2c1984abcc7e80974
parentf49494f1d9cfcd22affed0329a1d12087a90583c (diff)
downloadtable-battles-dc6e8cd3fe0106c6233685e6d637c8b17cd635ea.tar.gz
Reactions.
-rw-r--r--rules.js199
1 files changed, 188 insertions, 11 deletions
diff --git a/rules.js b/rules.js
index aa2bd66..5cad2c4 100644
--- a/rules.js
+++ b/rules.js
@@ -864,7 +864,18 @@ function is_reaction(c, a) {
return (a.type === "Screen" || a.type === "Counterattack" || a.type === "Absorb")
}
+function is_mandatory_reaction(c, a) {
+ return (
+ a.requirement !== "Voluntary" &&
+ a.requirement !== "Pair, Voluntary"
+ )
+}
+
function can_take_action(c, a) {
+ if (a.type === "Attack" && find_target_of_attack(a) < 0)
+ return false
+ if (a.type === "Command" && find_target_of_command(a) < 0)
+ return false
if (a.type === "Bombard" || a.type === "Attack" || a.type === "Command") {
if (data.cards[c].special)
return check_cube_requirement(c, a.requirement)
@@ -989,18 +1000,17 @@ function current_action() {
return data.cards[game.selected].actions[game.action]
}
-function find_target_of_attack() {
- let a = current_action()
+function find_target_of_attack(a) {
for (let c of a.target_list) {
if (game.front[0].includes(c))
return c
if (game.front[1].includes(c))
return c
}
+ return -1
}
-function find_target_of_command(c) {
- let a = current_action()
+function find_target_of_command(a) {
for (let c of a.target_list) {
if (game.reserve[0].includes(c))
return c
@@ -1025,7 +1035,7 @@ states.bombard = {
states.attack = {
prompt() {
- let t = find_target_of_attack()
+ let t = find_target_of_attack(current_action())
view.prompt = "Attack " + card_name(t) + "."
view.selected = game.selected
gen_action_card(t)
@@ -1033,15 +1043,25 @@ states.attack = {
card(c) {
log(card_name(game.selected) + " attacked " + card_name(c) + ".")
game.target = c
- apply_attack(current_action())
- pay_for_action(game.selected)
- end_action_phase()
+ if (can_opponent_react()) {
+ clear_undo()
+ set_opponent_active()
+ game.state = "react"
+ } else {
+ resume_attack()
+ }
},
}
+function resume_attack() {
+ apply_attack(current_action())
+ pay_for_action(game.selected)
+ end_action_phase()
+}
+
states.command = {
prompt() {
- let t = find_target_of_command()
+ let t = find_target_of_command(current_action())
view.prompt = "Bring " + card_name(t) + " out of reserve."
view.selected = game.selected
gen_action_card(t)
@@ -1082,6 +1102,160 @@ function end_action_phase() {
goto_roll_phase()
}
+// === REACTION ===
+
+function can_opponent_react() {
+ let p = 1 - player_index()
+ for (let c of game.front[p])
+ if (can_card_react(c))
+ return true
+ return false
+}
+
+function can_card_react(c) {
+ let has_dice = has_any_dice_on_card(c)
+ let has_cube = has_any_cubes_on_card(c)
+ if (has_dice || has_cube) {
+ if (data.cards[c].actions.length >= 1)
+ if (is_reaction(c, data.cards[c].actions[0]))
+ if (can_take_reaction(c, data.cards[c].actions[0]))
+ return true
+ if (data.cards[c].actions.length >= 2)
+ if (is_reaction(c, data.cards[c].actions[1]))
+ if (can_take_reaction(c, data.cards[c].actions[1]))
+ return true
+ }
+ return false
+}
+
+function can_take_reaction(c, a) {
+ switch (a.type) {
+ default:
+ throw new Error("invalid reaction: " + a.type)
+ case "Screen":
+ if (!a.target_list.includes(game.selected))
+ return false
+ break
+ case "Counterattack":
+ // if _this_ formation is attacked
+ if (game.target !== c)
+ return false
+ if (find_target_of_attack(a) < 0)
+ return false
+ break
+ case "Absorb":
+ if (!a.target_list.includes(game.target))
+ return false
+ break
+ }
+
+ if (data.cards[c].special)
+ return check_cube_requirement(c, a.requirement)
+ else
+ return check_dice_requirement(c, a.requirement)
+}
+
+states.react = {
+ prompt() {
+ view.prompt = "React to " + card_name(game.selected) + " attack!"
+ view.selected = game.selected
+
+ let voluntary = true
+ let p = player_index()
+ for (let c of game.front[p]) {
+ let has_dice = has_any_dice_on_card(c)
+ let has_cube = has_any_cubes_on_card(c)
+ if (has_dice || has_cube) {
+ if (data.cards[c].actions.length >= 1) {
+ if (is_reaction(c, data.cards[c].actions[0])) {
+ if (can_take_reaction(c, data.cards[c].actions[0])) {
+ if (is_mandatory_reaction(c, data.cards[c].actions[0]))
+ voluntary = false
+ gen_action_action1(c)
+ }
+ }
+ }
+ if (data.cards[c].actions.length >= 2) {
+ if (is_reaction(c, data.cards[c].actions[1])) {
+ if (can_take_reaction(c, data.cards[c].actions[1])) {
+ if (is_mandatory_reaction(c, data.cards[c].actions[1]))
+ voluntary = false
+ gen_action_action2(c)
+ }
+ }
+ }
+ }
+ }
+
+ if (voluntary)
+ view.actions.pass = 1
+ },
+ a1(c) {
+ goto_take_reaction(c, 0)
+ },
+ a2(c) {
+ goto_take_reaction(c, 1)
+ },
+ pass() {
+ set_opponent_active()
+ resume_attack()
+ },
+}
+
+function goto_take_reaction(c, ix) {
+ let a = data.cards[c].actions[ix]
+ switch (a.type) {
+ case "Screen":
+ goto_screen(c, a)
+ break
+ case "Absorb":
+ goto_absorb(c, a)
+ break
+ case "Counterattack":
+ goto_counterattack(c, a)
+ break
+ }
+}
+
+function goto_screen(c, a) {
+ log(card_name(c) + " screened.")
+ game.reacted = 1
+ pay_for_action(c)
+ set_opponent_active()
+ pay_for_action(game.selected)
+ end_action_phase()
+}
+
+function goto_absorb(c, a) {
+ log(card_name(c) + " absorbed.")
+ game.reacted = 1
+ pay_for_action(c)
+ game.target = c
+ set_opponent_active()
+ // TODO: absorb effect!
+ end_action_phase()
+}
+
+function goto_counterattack(c, a) {
+ pay_for_action(c)
+ game.reacted = 1
+ log(card_name(c) + " counterattacked.")
+
+ let save_selected = game.selected
+ let save_target = game.target
+
+ game.selected = c
+ game.target = find_target_of_attack(a)
+
+ apply_attack(a.effect)
+
+ game.selected = save_selected
+ game.target = save_target
+
+ set_opponent_active()
+ resume_attack()
+}
+
// === ATTACK EFFECTS ===
function apply_self() {
@@ -1092,6 +1266,10 @@ function apply_hit() {
remove_sticks(game.target, 1)
}
+function apply_hit_plus_hit_per_die() {
+ remove_sticks(game.target, 1 + count_dice_on_card(game.selected))
+}
+
function apply_hit_per_die() {
remove_sticks(game.target, count_dice_on_card(game.selected))
}
@@ -1134,8 +1312,7 @@ function apply_attack(a) {
break
case "1 hit, PLUS 1 hit per die. 1 self per action.":
- apply_hit()
- apply_hit_per_die()
+ apply_hit_plus_hit_per_die()
apply_self()
break
}