diff options
-rw-r--r-- | play.css | 21 | ||||
-rw-r--r-- | play.html | 27 | ||||
-rw-r--r-- | play.js | 65 | ||||
-rw-r--r-- | rules.js | 190 |
4 files changed, 266 insertions, 37 deletions
@@ -76,6 +76,27 @@ path.selected { stroke: yellow; } .box.loc.action{background-color:#fff5;border-color:white;} .box.selected{border-color:yellow;} +#capabilities .token { + position: static; + pointer-events: auto; +} +#capabilities { + display: flex; + justify-content: center; + align-content: center; + flex-wrap: wrap; + gap: 4px; +} +#insurgent_momentum { + display: flex; + flex-wrap: wrap; + gap: 17px; +} +.card.momentum { + width: 186px; + height: 261px; + border-radius: 12px; +} .piece { position: absolute; @@ -141,7 +141,6 @@ <div id="PRES1" class="box president" style="top:129px;left:240px;width:76px;height:168px"></div> <div id="PRES2" class="box president" style="top:129px;left:322px;width:78px;height:168px"></div> <div id="PRES3" class="box president" style="top:129px;left:406px;width:76px;height:168px"></div> - <div id="SOP_A1" class="box" style="top:438px;left:1261px;width:74px;height:74px"></div> <div id="SOP_A2" class="box" style="top:438px;left:1336px;width:74px;height:74px"></div> <div id="SOP_B1" class="box" style="top:516px;left:1261px;width:74px;height:74px"></div> @@ -150,15 +149,25 @@ <div id="SOP_C2" class="box" style="top:595px;left:1336px;width:74px;height:74px"></div> <div id="SOP_PASS" class="box" style="top:663px;left:1112px;width:105px;height:49px"></div> -<div id="IM1" class="box" style="top:1935px;left:14px;width:196px;height:274px"></div> -<div id="IM2" class="box" style="top:1935px;left:220px;width:196px;height:274px"></div> +<div id="insurgent_momentum" style="position:absolute;top:1940px;left:19px;width:1250px;height:270px"> +<div id="mom_12" class="momentum card card_12 hide"></div> +<div id="mom_17" class="momentum card card_17 hide"></div> +<div id="mom_22" class="momentum card card_22 hide"></div> +<div id="mom_27" class="momentum card card_27 hide"></div> +<div id="mom_42" class="momentum card card_42 hide"></div> +<div id="mom_67" class="momentum card card_67 hide"></div> +</div> -<div id="GOVT_CAPS" class="box" style="top:461px;left:224px;width:207px;height:149px"></div> -<div id="GOVT_BASES" class="box" style="top:371px;left:287px;width:177px;height:55px"></div> -<div id="CARTELS_SHIPMENTS" class="box" style="top:1728px;left:1391px;width:145px;height:125px"></div> -<div id="CARTELS_BASES" class="box" style="top:2117px;left:1373px;width:183px;height:305px"></div> -<div id="AUC_BASES" class="box" style="top:2386px;left:446px;width:360px;height:55px"></div> -<div id="FARC_BASES" class="box" style="top:2295px;left:446px;width:543px;height:56px"></div> +<div id="capabilities" style="position:absolute;top:508px;left:223px;width:208px;height:102px"> +<div id="cap_first_div" class="hide token shaded first_div"></div> +<div id="cap_ospina" class="hide token shaded ospina"></div> +<div id="cap_tapias" class="hide token shaded tapias"></div> +<div id="cap_seventh_sf" class="hide token shaded seventh_sf"></div> +<div id="cap_mtn_bns" class="hide token shaded mtn_bns"></div> +<div id="cap_black_hawks" class="hide token shaded black_hawks"></div> +<div id="cap_ndsc" class="hide token shaded ndsc"></div> +<div id="cap_meteoro" class="hide token shaded meteoro"></div> +</div> </div> @@ -44,6 +44,25 @@ const SOP_C2 = 6 const SOP_PASS = 7 const INELIGIBLE = 8 +const capability_events = [ 1, 2, 3, 7, 9, 10, 11, 13 ] +const momentum_events = [ 12, 17, 22, 27, 42, 67 ] + +const CAP_1ST_DIV = 1 +const CAP_OSPINA = 2 +const CAP_TAPIAS = 3 +const CAP_7TH_SF = 7 +const CAP_MTN_BNS = 9 +const CAP_BLACK_HAWKS = 10 +const CAP_NDSC = 11 +const CAP_METEORO = 13 + +const MOM_PLAN_COLOMBIA = 12 +const MOM_MADRID_DONORS = 17 +const MOM_ALFONSO_CANO = 22 +const MOM_MISIL_ANTIAEREO = 27 +const MOM_SENADO_CAMARA = 42 +const MOM_MEXICAN_TRAFFICKERS = 67 + let ui = { favicon: document.getElementById("favicon"), header: document.querySelector("header"), @@ -56,6 +75,24 @@ let ui = { document.getElementById("role_FARC_+_Cartels"), document.getElementById("role_AUC_+_Cartels"), ], + capabilities: { + [CAP_1ST_DIV]: document.getElementById("cap_first_div"), + [CAP_OSPINA]: document.getElementById("cap_ospina"), + [CAP_TAPIAS]: document.getElementById("cap_tapias"), + [CAP_7TH_SF]: document.getElementById("cap_seventh_sf"), + [CAP_MTN_BNS]: document.getElementById("cap_mtn_bns"), + [CAP_BLACK_HAWKS]: document.getElementById("cap_black_hawks"), + [CAP_NDSC]: document.getElementById("cap_ndsc"), + [CAP_METEORO]: document.getElementById("cap_meteoro"), + }, + momentum: { + [MOM_PLAN_COLOMBIA]: document.getElementById("mom_12"), + [MOM_MADRID_DONORS]: document.getElementById("mom_17"), + [MOM_ALFONSO_CANO]: document.getElementById("mom_22"), + [MOM_MISIL_ANTIAEREO]: document.getElementById("mom_27"), + [MOM_SENADO_CAMARA]: document.getElementById("mom_42"), + [MOM_MEXICAN_TRAFFICKERS]: document.getElementById("mom_67"), + }, spaces: [], control: [], support: [], @@ -235,6 +272,11 @@ function init_ui() { register_action(ui.resources[AUC], "resources", AUC) register_action(ui.resources[CARTELS], "resources", CARTELS) + for (let c of momentum_events) + register_card_tip(ui.momentum[c], c) + for (let c of capability_events) + register_card_tip(ui.capabilities[c], c) + for (let i = 0; i < data.spaces.length; ++i) { let id = data.spaces[i].id let type = data.spaces[i].type @@ -676,6 +718,23 @@ function on_update() { ui.tokens.president.style.left = [ 0, "254px", "337px", "420px" ][view.president] + for (let cap of capability_events) { + let shaded = set_has(view.capabilities, -cap) + let unshaded = set_has(view.capabilities, cap) + console.log("CAP", cap, shaded, unshaded) + if (shaded || unshaded) { + ui.capabilities[cap].classList.toggle("shaded", shaded) + ui.capabilities[cap].classList.toggle("unshaded", unshaded) + ui.capabilities[cap].classList.toggle("hide", false) + } else { + ui.capabilities[cap].classList.toggle("hide", true) + } + } + + for (let cap of momentum_events) { + ui.momentum[cap].classList.toggle("hide", !set_has(view.momentum, cap)) + } + if (view.propaganda > 0) { ui.tokens.propaganda.style.top = "744px" ui.tokens.propaganda.style.left = (1124 + 75 * (view.propaganda - 1)) + "px" @@ -854,6 +913,12 @@ function on_update() { action_button("undo", "Undo") } +function register_card_tip(e, c) { + e.onmouseenter = () => //console.log("FOCUS ME", c) + on_focus_card_tip(c) + e.onmouseleave = on_blur_card_tip +} + function on_focus_card_tip(c) { document.getElementById("card_tip").className = "card card_" + c } @@ -22,6 +22,56 @@ const NAME_GOVT_AUC = "Government + AUC" const NAME_FARC_CARTELS = "FARC + Cartels" const NAME_AUC_CARTELS = "AUC + Cartels" +const capability_events = [ 1, 2, 3, 7, 9, 10, 11, 13 ] +const momentum_events = [ 12, 17, 22, 27, 42, 67 ] + +const CAP_1ST_DIV = 1 +const CAP_OSPINA = 2 +const CAP_TAPIAS = 3 +const CAP_7TH_SF = 7 +const CAP_MTN_BNS = 9 +const CAP_BLACK_HAWKS = 10 +const CAP_NDSC = 11 +const CAP_METEORO = 13 + +const MOM_PLAN_COLOMBIA = 12 +const MOM_MADRID_DONORS = 17 +const MOM_ALFONSO_CANO = 22 +const MOM_MISIL_ANTIAEREO = 27 +const MOM_SENADO_CAMARA = 42 +const MOM_MEXICAN_TRAFFICKERS = 67 + +// Events with no shaded/unshaded variants +const single_events = [ 19, 36, 46, 53, 54, 63, 65, 69 ] + +const event_name_unshaded = { + [CAP_1ST_DIV]: "Jointness.", + [CAP_OSPINA]: "COIN experts take charge.", + [CAP_TAPIAS]: "CO tightens civil-military bonds.", + [CAP_7TH_SF]: "Infrastructure protection training.", + [CAP_MTN_BNS]: "Elites guard high-altitude corridors.", + [CAP_BLACK_HAWKS]: "US helos delivered.", + [CAP_NDSC]: "Military-police jointness.", + [CAP_METEORO]: "Transport protection units.", +} + +const event_name_shaded = { + [CAP_1ST_DIV]: "Service parochialism.", + [CAP_OSPINA]: "COIN strategy eludes Army.", + [CAP_TAPIAS]: "Civil-military rivalries fester.", + [CAP_7TH_SF]: "US training ineffective.", + [CAP_MTN_BNS]: "Equipment not delivered.", + [CAP_BLACK_HAWKS]: "Delivery of US helos delayed.", + [CAP_NDSC]: "Military-police rivalry.", + [CAP_METEORO]: "Transport security deemphasized.", + [MOM_PLAN_COLOMBIA]: "US aid focuses on drug war.", + [MOM_MADRID_DONORS]: "EU aid focuses on reconstruction.", + [MOM_ALFONSO_CANO]: "Ideologue.", + [MOM_MISIL_ANTIAEREO]: "MANPADs feared.", + [MOM_SENADO_CAMARA]: "Insurgent sympathies.", + [MOM_MEXICAN_TRAFFICKERS]: "New routes to US market.", +} + const faction_name = [ NAME_GOVT, NAME_FARC, NAME_AUC, NAME_CARTELS ] // Factions @@ -205,6 +255,8 @@ exports.setup = function (seed, scenario, options) { farc_control: 0, govt_control: 0, support: Array(23).fill(NEUTRAL), + momentum: [], + capabilities: [], // positive = unshaded, negative = shaded farc_zones: [], terror: [], sabotage: [], @@ -947,8 +999,10 @@ states.limop_or_event = { function goto_event() { log_h2(faction_name[game.current] + " - Event") - log("TODO: Event") - resume_event_card() + if (set_has(single_events, this_card())) + execute_event() + else + game.state = "event" } function goto_op_only() { @@ -984,10 +1038,6 @@ function end_operation() { resume_event_card() } -function can_use_special_activity() { - return game.sa === 1 -} - function action_remove() { push_undo() game.save_state = game.state @@ -1020,7 +1070,7 @@ states.remove = { }, done() { game.state = game.save_state - game.save_state = 0 + delete game.save_state }, } @@ -1123,9 +1173,17 @@ function select_op_space(s, cost) { // OPERATION: TRAIN +function can_govt_train_base(s) { + return count_bases(s) < 2 && count_cubes(s) >= 3 +} + +function can_govt_train_place(s) { + return is_city(s) || has_piece(s, GOVT, BASE) +} + states.train = { prompt() { - view.prompt = "Train: Select spaces to place cubes." + view.prompt = "Train: Select City or Department." if (game.sa) { view.actions.air_lift = 1 @@ -1135,24 +1193,25 @@ states.train = { // Any Departments or Cities if (can_select_op_space(3)) { for (let s = first_space; s <= last_dept; ++s) - if (is_city(s) || has_piece(s, GOVT, BASE)) + if (can_govt_train_place(s)) if (!is_selected_op_space(s)) gen_action_space(s) } - if (game.op.spaces.length > 0) - view.actions.next = 1 + view.actions.next = 1 }, space(s) { push_undo() - logi(`S${s}.`) + log(`Train in S${s}.`) select_op_space(s, 3) - game.state = "train_place" - game.op.where = s - game.op.count = 6 + if (is_city(s) || has_piece(s, GOVT, BASE)) { + game.state = "train_place" + game.op.where = s + game.op.count = 6 + } }, next() { push_undo() @@ -1173,11 +1232,10 @@ states.train_place = { view.actions.next = 1 }, piece(p) { - push_undo() place_piece(p, game.op.where) + update_control() if (--game.op.count == 0) game.state = "train" - update_control() }, next() { game.op.count = 0 @@ -1190,12 +1248,14 @@ states.train_base_or_civic = { view.prompt = "Train: Build Base or buy Civic Action in one selected space?" view.actions.base = 1 + if (game.resources[game.current] >= 3) view.actions.civic = 1 else view.actions.civic = 0 - view.actions.end_operation = 1 + if (game.op.spaces.length > 0) + view.actions.end_operation = 1 }, base() { push_undo() @@ -1214,14 +1274,14 @@ states.train_base = { prompt() { if (game.op.where < 0) { view.prompt = `Train: Replace 3 cubes with a Base.` - for (let s of game.op.spaces) { - if (count_bases(s) < 2 && count_cubes(s) >= 3) - gen_action_space(s) - } + for (let s = first_space; s <= last_dept; ++s) + if (can_govt_train_base(s)) + if (is_selected_op_space(s) || can_select_op_space(3)) + gen_action_space(s) } else { if (game.op.count < 0) { view.prompt = `Train: All done.` - view.actions.done = 1 + view.actions.end_operation = 1 } else if (game.op.count > 0) { view.prompt = `Train: ${game.op.count} cubes with a Base.` gen_piece_in_space(game.op.where, GOVT, POLICE) @@ -1238,7 +1298,8 @@ states.train_base = { game.op.count = 3 }, piece(p) { - push_undo() + if (!is_selected_op_space(game.op.where)) + select_op_space(game.op.where, 3) if (game.op.count > 0) remove_piece(p) else @@ -1255,16 +1316,15 @@ states.train_civic = { if (game.op.where < 0) { view.prompt = `Train: Buy Civic Action.` if (res >= 3) { - for (let s of game.op.spaces) { + for (let s = first_space; s <= last_dept; ++s) if (can_govt_civic_action(s)) - gen_action_space(s) - } + if (is_selected_op_space(s) || can_select_op_space(3)) + gen_action_space(s) } } else { view.prompt = `Train: Buy Civic Action in ${space_name[game.op.where]}.` view.where = game.op.where if (res >= 3 && can_govt_civic_action(game.op.where)) { - //gen_action("resources", game.current) gen_action_space(game.op.where) } else { view.prompt = `Train: All done.` @@ -1745,6 +1805,9 @@ states.assault_space = { game.op.targeted |= target_faction(p) remove_piece(p) update_control() + + // TODO: 3.2.5 Drug Bust + if (--game.op.count === 0 || !can_assault_space(game.op.where)) game.state = "assault" }, @@ -2199,6 +2262,9 @@ states.attack_remove = { game.op.targeted |= target_faction(p) remove_piece(p) update_control() + + // TODO: Captured Goods + if (--game.op.count === 0 || !has_exposed_enemy_piece(game.op.where, game.current)) game.state = "attack" }, @@ -2718,6 +2784,8 @@ states.kidnap_space = { let g = is_city_or_loc(s) && game.resources[GOVT] > 0 let c = has_piece(s, CARTELS, BASE) && game.resources[CARTELS] > 0 + // TODO: Drug Ransom if targeting Cartels with Shipment + if (g) gen_action_resources(GOVT) if (c) @@ -3046,6 +3114,8 @@ states.bribe_space = { gen_piece_in_space(game.sa.where, AUC, GUERRILLA) } + // TODO: Contraband + if (did_maximum_damage(game.sa.targeted)) view.actions.next = 1 else @@ -3228,6 +3298,8 @@ exports.view = function (state, role) { farc_control: game.farc_control, support: game.support, farc_zones: game.farc_zones, + capabilities: game.capabilities, + momentum: game.momentum, terror: game.terror, sabotage: game.sabotage, } @@ -3304,6 +3376,68 @@ exports.is_checkpoint = function (a, b) { return a.turn !== b.turn } +// === EVENTS === + +states.event = { + prompt() { + let c = this_card() + view.prompt = `${data.card_name[c]}: Choose effect.` + view.actions.shaded = 1 + view.actions.unshaded = 1 + }, + shaded() { + execute_shaded_event() + }, + unshaded() { + execute_unshaded_event() + }, +} + +function execute_event() { + let c = this_card() + log(`C${c} - Event`) + + log("TODO") + resume_event_card() +} + +function execute_unshaded_event() { + let c = this_card() + log(`C${c} - Unshaded`) + + if (set_has(capability_events, c)) { + logi(event_name_unshaded[c]) + set_add(game.capabilities, c) + resume_event_card() + return + } + + log("TODO") + resume_event_card() +} + +function execute_shaded_event() { + let c = this_card() + log(`C${c} - Shaded`) + + if (set_has(capability_events, c)) { + logi(event_name_shaded[c]) + set_add(game.capabilities, -c) + resume_event_card() + return + } + + if (set_has(momentum_events, c)) { + logi(event_name_shaded[c]) + set_add(game.momentum, c) + resume_event_card() + return + } + + log("TODO") + resume_event_card() +} + // === COMMON LIBRARY === function clear_undo() { |