From dc6e8cd3fe0106c6233685e6d637c8b17cd635ea Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sat, 25 Nov 2023 19:35:27 +0100 Subject: Reactions. --- rules.js | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file 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 } -- cgit v1.2.3