"use strict" /* global view, data, player, send_action, action_button, scroll_with_middle_mouse */ const first_piece = data.first_piece const last_piece = data.last_piece const last_cavalry = 9 const faction_name = [ "Delhi Sultanate", "Bahmani Kingdom", "Vijayanagara Empire", "Mongol Invaders" ] const faction_flags = [ "images/Flags_DS.png", "images/Flags_BK.png", "images/Flags_VE.png", "images/Flags_MI.png" ] // :r !python3 tools/genlayout.py // modified const LAYOUT = { "Andhra DS": [835, 1050], "Andhra BK": [660, 1040], "Andhra VE": [680, 1200], "Andhra mongols": [0, 0], "Bengal DS": [1170, 460], "Bengal BK": [1060, 535], "Bengal VE": [1142, 665], "Bengal mongols": [0, 0], "Gondwana DS": [1000, 690], "Gondwana BK": [820, 710], "Gondwana VE": [855, 835], "Gondwana mongols": [0, 0], "Gujarat DS": [320, 630], "Gujarat BK": [140, 620], "Gujarat VE": [380, 760], "Gujarat mongols": [0, 0], "Jaunpur DS": [985, 425], "Jaunpur BK": [795, 545], "Jaunpur VE": [915, 570], "Jaunpur mongols": [0, 0], "Karnataka DS": [625, 1300], "Karnataka BK": [475, 1220], "Karnataka VE": [560, 1400], "Karnataka mongols": [0, 0], "Madhyadesh DS": [670, 745], "Madhyadesh BK": [600, 920], "Madhyadesh VE": [700, 890], "Madhyadesh mongols": [0, 0], "Maharashtra DS": [430, 900], "Maharashtra BK": [510, 990], "Maharashtra VE": [470, 1095], "Maharashtra mongols": [0, 0], "Malwa DS": [685, 585], "Malwa BK": [515, 620], "Malwa VE": [500, 735], "Malwa mongols": [0, 0], "Orissa DS": [1168, 800], "Orissa BK": [1060, 860], "Orissa VE": [905, 970], "Orissa mongols": [0, 0], "Rajput Kingdoms DS": [420, 380], "Rajput Kingdoms BK": [220, 460], "Rajput Kingdoms VE": [380, 480], "Rajput Kingdoms mongols": [0, 0], "Sindh DS": [60, 320], "Sindh BK": [40, 470], "Sindh VE": [20, 530], "Sindh mongols": [0, 0], "Tamilakam DS": [770, 1330], "Tamilakam BK": [700, 1460], "Tamilakam VE": [650, 1540], "Tamilakam mongols": [0, 0], "Delhi BK": [100, 100], // unused "Delhi VE": [100, 100], // unused "Delhi DS": [685, 420], "Delhi mongols": [575, 350], "Mountain Passes DS": [263, 183], "Mountain Passes BK": [236, 110], "Mountain Passes VE": [318, 105], "Mountain Passes mongols": [404, 127], "Punjab DS": [380, 238], "Punjab BK": [279, 262], "Punjab VE": [477, 266], "Punjab mongols": [515, 184], "BK_DI_2" : [160 + 20, 1186 + 43], "BK_DI_4" : [302 + 20, 1186 + 43], "VE_DI_1" : [ 89 + 20, 1292 + 43], "VE_DI_2" : [160 + 20, 1292 + 43], "VE_DI_3" : [231 + 20, 1292 + 43], "VE_DI_4" : [302 + 20, 1292 + 43], } /* COMMON */ function set_has(set, item) { let a = 0 let b = set.length - 1 while (a <= b) { let m = (a + b) >> 1 let x = set[m] if (item < x) b = m - 1 else if (item > x) a = m + 1 else return true } return false } function map_get(map, key, missing) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m << 1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else return map[(m << 1) + 1] } return missing } /* DATA */ // Spaces const S_ANDHRA = 0 const S_BENGAL = 1 const S_GONDWANA = 2 const S_GUJARAT = 3 const S_JAUNPUR = 4 const S_KARNATAKA = 5 const S_MADHYADESH = 6 const S_MAHARASHTRA = 7 const S_MALWA = 8 const S_ORISSA = 9 const S_RAJPUT_KINGDOMS = 10 const S_SINDH = 11 const S_TAMILAKAM = 12 const S_DELHI = 13 const S_MOUNTAIN_PASSES = 14 const S_PUNJAB = 15 const S_MONGOL_INVADERS = 16 const S_DS_AVAILABLE = 17 const S_BK_AVAILABLE = 18 const S_VE_AVAILABLE = 19 const S_BK_INF_2 = 20 const S_BK_INF_4 = 21 const S_VE_INF_1 = 22 const S_VE_INF_2 = 23 const S_VE_INF_3 = 24 const S_VE_INF_4 = 25 const last_province = S_PUNJAB const space_name = data.space_name const space_id = [ "space_Andhra", "space_Bengal", "space_Gondwana", "space_Gujarat", "space_Jaunpur", "space_Karnataka", "space_Madhyadesh", "space_Maharashtra", "space_Malwa", "space_Orissa", "space_Rajput_Kingdoms", "space_Sindh", "space_Tamilakam", "space_Delhi", "space_Mountain_Passes", "space_Punjab", ] const bb_loc = { 0: {"x": 522, "y": 1260}, // S_ANDHRA 1: {"x": 776, "y": 730}, // S_BENGAL 2: {"x": 640, "y": 920}, // S_GONDWANA 3: {"x": 82, "y": 841}, // S_GUJARAT 4: {"x": 666, "y": 625}, // S_JAUNPUR 5: {"x": 698, "y": 1262}, // S_KARNATAKA 6: {"x": 446, "y": 987}, // S_MADHYADESH 7: {"x": 379, "y": 1140}, // S_MAHARASHTRA 8: {"x": 397, "y": 798}, // S_MALWA 9: {"x": 770, "y": 1068}, // S_ORISSA 10: {"x": 81, "y": 568}, // S_RAJPUT_KINGDOMS 11: {"x": 163, "y": 311}, // S_SINDH 12: {"x": 804, "y": 1357}, // S_TAMILAKAM 13: {"x": 401, "y": 536}, // S_DELHI 14: {"x": 152, "y": 235}, // S_MOUNTAIN_PASSES 15: {"x": 195, "y": 314}, // S_PUNJAB } /* LAYOUT DATA */ // modified from tools/parse-layout.js const layout = { "circles": { "provinces": { "Sindh": { "cx": 65, "cy": 393, "rx": 34, "ry": 34 }, "Rajput Kingdoms": { "cx": 329, "cy": 403, "rx": 34, "ry": 34 }, "Malwa": { "cx": 605, "cy": 572, "rx": 34, "ry": 34 }, "Jaunpur": { "cx": 894, "cy": 455, "rx": 34, "ry": 34 }, "Bengal": { "cx": 1192, "cy": 536, "rx": 34, "ry": 34 }, "Orissa": { "cx": 1034, "cy": 858, "rx": 34, "ry": 34 }, "Gondwana": { "cx": 913, "cy": 737, "rx": 34, "ry": 34 }, "Madhyadesh": { "cx": 670, "cy": 817, "rx": 34, "ry": 34 }, "Andhra": { "cx": 743, "cy": 1090, "rx": 34, "ry": 34 }, "Maharashtra": { "cx": 438, "cy": 969, "rx": 34, "ry": 34 }, "Gujarat": { "cx": 220, "cy": 678, "rx": 34, "ry": 34 }, "Karnataka": { "cx": 550, "cy": 1278, "rx": 34, "ry": 34 }, "Tamilakam": { "cx": 704, "cy": 1399, "rx": 34, "ry": 34 } }, "mongol_invasion_regions": { "Mountain Passes": { "cx": 302, "cy": 140, "rx": 83, "ry": 28 }, "Punjab": { "cx": 478, "cy": 220, "rx": 58, "ry": 19 }, "Delhi": { "cx": 647, "cy": 375, "rx": 148, "ry": 148 } } }, "rects": { "available_boxes": { "MI_available": { "x": 18, "y": 94, "w": 192, "h": 126 }, "DS_available": { "x": 792, "y": 85, "w": 400, "h": 237 }, "BK_available": { "x": 16, "y": 902, "w": 250, "h": 237 }, "VE_available": { "x": 16, "y": 1401, "w": 251, "h": 236 } }, "tracks": { "Track 24": { "x": 1198, "y": 403, "w": 61, "h": 61 }, "Track 18": { "x": 1197, "y": 14, "w": 62, "h": 63 }, "Track 0": { "x": 15, "y": 14, "w": 62, "h": 63 }, "BK Influence 0": { "x": 18, "y": 1186, "w": 61, "h": 63 }, "BK Influence 1": { "x": 89, "y": 1186, "w": 61, "h": 63 }, "BK Influence 2": { "x": 160, "y": 1186, "w": 61, "h": 63 }, "BK Influence 3": { "x": 231, "y": 1186, "w": 61, "h": 63 }, "BK Influence 4": { "x": 302, "y": 1186, "w": 61, "h": 63 }, "VE Influence 0": { "x": 18, "y": 1292, "w": 61, "h": 63 }, "VE Influence 1": { "x": 89, "y": 1292, "w": 61, "h": 63 }, "VE Influence 2": { "x": 160, "y": 1292, "w": 61, "h": 63 }, "VE Influence 3": { "x": 231, "y": 1291, "w": 61, "h": 63 }, "VE Influence 4": { "x": 302, "y": 1292, "w": 61, "h": 63 } }, "sequence_of_play": { "Limited Command": { "x": 854, "y": 1305, "w": 90, "h": 54 }, "Eligible Factions": { "x": 854, "y": 1367, "w": 91, "h": 201 }, "Pass": { "x": 854, "y": 1578, "w": 90, "h": 56 }, "Ineligible Factions": { "x": 1166, "y": 1367, "w": 90, "h": 201 }, "Command and Decree": { "x": 1016, "y": 1371, "w": 77, "h": 77 }, "Event or Command": { "x": 1016, "y": 1488, "w": 77, "h": 77 } } } } /* STATE */ function piece_space(p) { return view.pieces[p] } /* BUILD */ let ui = { map: document.getElementById("map"), favicon: document.getElementById("favicon"), header: document.querySelector("header"), status: document.getElementById("status"), spaces: [], control: [], cavalry: [], card_tip: document.getElementById("card_tip"), this_card: document.getElementById("this_card"), shaded_event: document.getElementById("shaded_event"), unshaded_event: document.getElementById("unshaded_event"), gk_shaded_event: document.getElementById("gk_shaded_event"), gk_unshaded_event: document.getElementById("gk_unshaded_event"), deck_outer: document.getElementById("deck_outer"), deck_size: document.getElementById("deck_size"), of_gods_and_kings: document.getElementById("of_gods_and_kings"), dynasty_card: document.getElementById("dynasty_card"), discard_pile: document.getElementById("discard_pile"), discard: [ document.getElementById("stack1"), document.getElementById("stack2"), document.getElementById("stack3"), document.getElementById("stack4"), ], tokens: { token_ds_vp: document.getElementById("token_ds_vp"), token_bk_vp: document.getElementById("token_bk_vp"), token_ve_vp: document.getElementById("token_ve_vp"), token_bk_influence: document.getElementById("token_bk_influence"), token_ve_influence: document.getElementById("token_ve_influence"), token_mongol_cavalry: document.getElementById("token_mongol_cavalry"), }, pieces: [], resources: [ document.getElementById("ds_resources"), document.getElementById("bk_resources"), document.getElementById("ve_resources"), ], cylinder: [ document.getElementById("ds_cylinder"), document.getElementById("bk_cylinder"), document.getElementById("ve_cylinder"), ], pool: [ document.getElementById("pool_a"), document.getElementById("pool_d"), ], dice: [], attack_table: document.getElementById("attack_table"), attack_header: document.getElementById("attack_header"), attack_attacker: document.getElementById("name_attacker"), attack_defender: document.getElementById("name_defender"), attacker_flag: document.querySelectorAll(".attacker_flag"), defender_flag: document.querySelectorAll(".defender_flag") } function create(t, p, ...c) { let e = document.createElement(t) Object.assign(e, p) e.append(c) return e } function register_action(e, action, id) { e.my_action = action e.my_id = id e.onmousedown = on_click_action } function is_action(action, arg) { if (arg === undefined) return !!(view.actions && view.actions[action] === 1) return !!(view.actions && view.actions[action] && set_has(view.actions[action], arg)) } function toggle_pieces() { if (ui.map.classList.contains("hide_tokens")) { ui.map.classList.remove("hide_tokens") ui.map.classList.remove("hide_pieces") } else if (ui.map.classList.contains("hide_pieces")) { ui.map.classList.add("hide_tokens") } else { ui.map.classList.add("hide_pieces") } } function on_click_action(evt) { if (evt.button === 0) send_action(evt.target.my_action, evt.target.my_id) } function init_ui() { register_action(ui.this_card, "event", undefined) register_action(ui.dynasty_card, "dynasty_card", undefined) register_action(ui.unshaded_event, "unshaded", undefined) register_action(ui.shaded_event, "shaded", undefined) register_action(ui.gk_unshaded_event, "gk_unshaded", undefined) register_action(ui.gk_shaded_event, "gk_shaded", undefined) register_action(ui.resources[DS], "resources", DS) register_action(ui.resources[BK], "resources", BK) register_action(ui.resources[VE], "resources", VE) register_action(ui.tokens.token_bk_influence, "influence", BK) register_action(ui.tokens.token_ve_influence, "influence", VE) register_action(ui.tokens.token_mongol_cavalry, "token", 10) ui.this_card.onmouseenter = on_focus_this_event ui.this_card.onmouseleave = on_blur_event ui.shaded_event.onmouseenter = on_focus_shaded_event ui.shaded_event.onmouseleave = on_focus_this_event ui.unshaded_event.onmouseenter = on_focus_unshaded_event ui.unshaded_event.onmouseleave = on_focus_this_event ui.gk_shaded_event.onmouseenter = on_focus_gk_shaded_event ui.gk_shaded_event.onmouseleave = on_focus_this_event ui.gk_unshaded_event.onmouseenter = on_focus_gk_unshaded_event ui.gk_unshaded_event.onmouseleave = on_focus_this_event // // Make combat table draggable // dragElement(ui.attack_table) // player cavalry tokens for (let i = 0; i <= LAST_CAVALRY; ++i) { let e = null ui.cavalry[i] = e = create("div", { className: "token cavalry charge" }) document.getElementById("tokens").appendChild(e) register_action(e, "token", i) } for (let s = 0; s < 26; ++s) { let e = null // tributary tokens if (s < S_DELHI) { let { cx, cy } = layout.circles.provinces[space_name[s]] ui.control[s] = e = create("div", { className: "token tributary" }) e.style.left = (cx - 24) + "px" e.style.top = (cy - 24) + "px" document.getElementById("tokens").appendChild(e) } // svg spaces if (s < S_DELHI || s === S_MOUNTAIN_PASSES || s === S_PUNJAB) { ui.spaces[s] = e = document.getElementById("svgmap").getElementById(space_id[s]) register_action(e, "space", s) } // delhi if (s == S_DELHI) { let { cx, cy, rx, ry } = layout.circles.mongol_invasion_regions[space_name[s]] ui.spaces[s] = e = create("div", { className: "space province" }) e.style.left = (cx - rx) + "px" e.style.top = (cy - ry) + "px" e.style.width = (rx * 2 - 8) + "px" e.style.height = (ry * 2 - 8) + "px" register_action(e, "space", s) document.getElementById("spaces").appendChild(e) } // boxes if (s >= S_MONGOL_INVADERS && s <= S_VE_AVAILABLE) { let { x, y, w, h } = layout.rects.available_boxes[space_name[s]] ui.spaces[s] = e = create("div", { className: "space box" }) e.style.left = x + "px" e.style.top = y + "px" e.style.width = (w - 4) + "px" e.style.height = (h - 4) + "px" register_action(e, "space", s) document.getElementById("spaces").appendChild(e) } } ui.spaces[S_MONGOL_INVADERS].classList.toggle("mi", true) ui.spaces[S_DS_AVAILABLE].classList.toggle("ds", true) ui.spaces[S_BK_AVAILABLE].classList.toggle("bk", true) ui.spaces[S_VE_AVAILABLE].classList.toggle("ve", true) function create_piece(c, action, id, x, y) { let e = create("div", { className: c, my_action: action, my_id: id, my_x_offset: x, my_y_offset: y, onmousedown: on_click_action }) document.getElementById("pieces").appendChild(e) return e } function create_piece_list(faction, type, c, x, y) { for (let p = first_piece[faction][type]; p <= last_piece[faction][type]; ++p) ui.pieces[p] = create_piece(c, "piece", p, x, y) } ui.pieces = [] create_piece_list(DS, DISC, "piece ds disc", -4, 10) create_piece_list(DS, ELITE, "piece ds governor", 0, 0) create_piece_list(DS, TROOPS, "piece ds cube", 0, 0) create_piece_list(BK, DISC, "piece bk disc", -4, 10) create_piece_list(BK, ELITE, "piece bk amir", 2, 0) create_piece_list(VE, DISC, "piece ve disc", -4, 10) create_piece_list(VE, ELITE, "piece ve raja", 2, 0) create_piece_list(MI, TROOPS, "piece mongol cube", 2, 0) // dice for (let d = 0; d < 6; ++d) { let e = null let pool = (d < 4) ? "pool_a" : "pool_d" ui.dice[d] = e = create("div", { className: "die d0" }) register_action(e, "die", d) document.getElementById(pool).append(e) } } /* UPDATE */ function action_menu_item(action) { let menu = document.getElementById(action + "_menu") if (view.actions && action in view.actions) { menu.classList.toggle("hide", false) menu.classList.toggle("disabled", view.actions[action] === 0) return 1 } else { menu.classList.toggle("hide", true) return 0 } } function action_menu(menu, action_list) { let x = 0 for (let action of action_list) x |= action_menu_item(action) menu.classList.toggle("hide", !x) } const LAYOUT_CACHE = { Center: [], DS: [], BK: [], VE: [], mongols: [], } function get_layout_xy(s, f = "Center") { if (!LAYOUT_CACHE[f][s]) { let id = (f !== "Center") ? data.spaces[s].id + " " + f : data.spaces[s].id LAYOUT_CACHE[f][s] = LAYOUT[id] } return LAYOUT_CACHE[f][s] } function filter_calvary_tokens(list, faction) { for (let i = 0; i <= last_cavalry; ++i) if (view.cavalry[i] === faction) list.push(ui.cavalry[i]) } function filter_piece_list(list, space, faction, type) { for (let i = first_piece[faction][type]; i <= last_piece[faction][type]; ++i) if (view.pieces[i] === space) list.push(ui.pieces[i]) } function layout_available(faction, type, xorig, yorig) { let list = [] filter_piece_list(list, AVAILABLE, faction, type) layout_pieces(list, xorig, yorig + 35, null, AVAILABLE) } function layout_influence() { let e = ui.tokens.token_bk_influence e.style.left = 25 + 71 * view.inf[BK] + "px" e = ui.tokens.token_ve_influence e.style.left = 25 + 71 * view.inf[VE] + "px" } function layout_pieces(mains, xorig, yorig, discs, f, others) { const dx = 17 const dy = 11 let off_x = 0 let off_y = 0 function layout_piece_rowcol(nrow, ncol, row, col, e, z, offset_x, offset_y) { // basic piece size = 29x36 let x = xorig - (row * dx - col * dx) - 15 + off_x + offset_x let y = yorig - (row * dy + col * dy) - 24 + off_y + offset_y let xo = e.my_x_offset let yo = e.my_y_offset e.style.left = (xo + x) + "px" e.style.top = (yo + y) + "px" e.style.zIndex = y e.my_x = x + 15 e.my_y = y + 24 e.my_z = z } function layout_p(list, offset_x=0, offset_y=0) { let nrow = Math.round(Math.sqrt(list.length)) let ncol = Math.ceil(list.length / nrow) let z = 50 let i = 0 for (let row = 0; row < nrow; ++row) for (let col = 0; col < ncol; ++col) if (i < list.length) layout_piece_rowcol(nrow, ncol, row, col, list[list.length-(++i)], z--, offset_x, offset_y) } if (mains.length > 0) { layout_p(mains) } if (discs && discs.length > 0) layout_discs(discs, xorig + off_x, yorig + 12 + off_y, f) if (others && others.length > 0) { let nrow = Math.round(Math.sqrt(others.length)) layout_p(others, 12 + nrow*12, 26) } } function place_piece(p, x, y, z) { p.style.left = x + "px" p.style.top = y + "px" if (z) p.style.zIndex = z p.my_x = x p.my_y = y p.my_z = z } function layout_discs(list, xc, yc, f) { let x_corr = f === DS ? -32 : 32 for (let disc of list) place_piece(disc, xc - 20 + x_corr, yc - 10, 52) } function layout_available_bases(list, x0, y0, rows, dx, dy) { let x = x0 let y = y0 for (let i = 0; i < list.length; ++i) { place_piece(list[list.length-i-1], x - 44 - 6, y + 8) y += dy if (i % rows === rows - 1) { x -= dx y = y0 } } } function layout_cavalry(list, x0, y0, dy) { let x = x0 let y = y0 let ll = list.length for (let i = 0; i < ll; ++i) { place_piece(list[ll-i-1], x, y + (dy * (ll-i)), ll-i) } } let sop_xy = [ [876, 1445, 0, 13], // ELIGIBLE [876, 1307, 11, 0], // SOP_LIMITED_COMMAND [1032, 1385, 0, 0], // SOP_COMMAND_DECREE [1032, 1505, 0, 0], // SOP_EVENT_OR_COMMAND [877, 1583, 11, 0], // SOP_PASS [1190, 1445, 0, 13], // INELIGIBLE ] function layout_sop() { let i, z let n_sop, offset for (let [sop, [x, y, dx, dy]] of sop_xy.entries()) { n_sop = view.cylinder.filter(v => v === sop).length i = 0 z = 1 for (let faction of view.order) { if (view.cylinder[faction] === sop) { offset = n_sop === 1 ? 0 : (-1 + i * (2/(n_sop - 1))) * n_sop place_piece(ui.cylinder[faction], x + dx * offset, y + dy * offset, z) i += 1 z += 1 } ui.cylinder[faction].classList.toggle("empty", view.marked & (16 << faction)) } } } function layout_score_cell(list, x, y, dx, dy) { let z = 1 if (list.length > 1) { if (dy > 0) y -= 12 if (dy < 0) y += 12 if (dx > 0) x -= 12 if (dx < 0) x += 12 } for (let p of list) { if (p.classList.contains("token")) place_piece(p, x - 24, y - 24, z) else place_piece(p, x - 22, y - 24, z) x += dx y += dy z += 1 } } function layout_score() { let list = [] let x, y for (let i = 0; i <= 24; ++i) { list.length = 0 if (view.vp[DS] === i) list.push(ui.tokens.token_ds_vp) if (view.vp[BK] === i) list.push(ui.tokens.token_bk_vp) if (view.vp[VE] === i) list.push(ui.tokens.token_ve_vp) for (let faction = 0; faction < 3; ++faction) if (view.resources[faction] === i) list.push(ui.resources[faction]) // X: 15 -> 1198 (0-18) // Y: 14 -> 403 (18-24) if (i < 18) y = 20 else y = 20 + (i - 18) * 65 if (i < 18) x = 23 + (i * 65.6) else x = 1204 x = Math.round(x) + 24 y = Math.round(y) + 24 if (i < 17) layout_score_cell(list, x, y, 0, 28) else if (i === 17) layout_score_cell(list, x, y, -18, 25) else layout_score_cell(list, x, y, -41, 0) } } function update_rebels(faction) { let p0 = first_piece[faction][ELITE] let p1 = last_piece[faction][ELITE] for (let i = 0, p = p0; p <= p1; ++i, ++p) { if (view.rebel[faction] & (1 << i)) ui.pieces[p].classList.add("rebel") else ui.pieces[p].classList.remove("rebel") } } function make_card_class_name(c) { return "card card_" + c } function update_player_active(name, x) { if (roles[name]) roles[name].element.classList.toggle("active", x) } function update_player_info(name, x) { if (roles[name]) roles[name].stat.textContent = x } function update_dice_box() { ui.attack_header.textContent = "Attack in " + data.space_name[view.attack.where] ui.attack_attacker.textContent = faction_name[view.attack.attacker] + " - " + view.attack.n_units[0] + " unit" + (view.attack.n_units[0] > 1 ? "s" : "") ui.attack_defender.textContent = faction_name[view.attack.target] + " - " + view.attack.n_units[1] + " unit" + (view.attack.n_units[1] > 1 ? "s" : "") ui.attacker_flag.forEach(function(element){ element.src = faction_flags[view.attack.attacker]; }); ui.defender_flag.forEach(function(element){ element.src = faction_flags[view.attack.target]; }); if (ui.attack_table.classList.contains("hide")) show_dice_box(ui.attack_table) } function show_dice_box(box) { box.classList.remove("hide") box.classList.add("show") box.style.top = null box.style.left = null box.setAttribute("open", true) // calculate size let w = box.clientWidth let h = box.clientHeight // center where possible let x = bb_loc[view.attack.where].x let y = bb_loc[view.attack.where].y box.style.top = y + "px" box.style.left = x + "px" } function is_stacked() { let card_panel = document.getElementById('card_panel').children; let e1 = card_panel[0].getBoundingClientRect(); let e2 = card_panel[1].getBoundingClientRect(); return Math.abs(e1.left - e2.left) < 1; } function update_discard() { if (view.discard) { let stacked = is_stacked() ui.discard_pile.style.flexDirection = (stacked ? "column" : "row") for (let i = 0; i < 4; ++i) { if (i < view.discard.length) { ui.discard[i].className = "card card_" + view.discard[i] + " " + (stacked ? "stack" : "side") } else { ui.discard[i].className = "" } } } else { for (let i = 0; i < 4; ++i) ui.discard[i].className = "" } } window.addEventListener("resize", update_discard) let once = true function on_update() { if (once) { init_ui() once = false } let curr = view.current === -1 ? VE : view.current switch (player) { case "Delhi Sultanate": ui.favicon.href = faction_flags[DS]; break case "Bahmani Kingdom": ui.favicon.href = faction_flags[BK]; break case "Vijayanagara Empire": ui.favicon.href = faction_flags[VE]; break case "Solo": ui.favicon.href = faction_flags[curr]; break case "Observer": ui.favicon.href = faction_flags[curr]; break } // Dice rolling if (view.dice.reduce((sum, num) => sum + num, 0) > 0) update_dice_box() else ui.attack_table.classList.add("hide") ui.header.classList.toggle("ds", view.current === DS) ui.header.classList.toggle("bk", view.current === BK) ui.header.classList.toggle("ve", view.current === VE) ui.tokens.token_bk_influence.classList.toggle("action", is_action("influence", BK)) ui.tokens.token_ve_influence.classList.toggle("action", is_action("influence", VE)) ui.resources[DS].classList.toggle("action", is_action("resources", DS)) ui.resources[BK].classList.toggle("action", is_action("resources", BK)) ui.resources[VE].classList.toggle("action", is_action("resources", VE)) for (let i = 0; i <= LAST_CAVALRY; ++i) ui.cavalry[i].classList.toggle("action", is_action("token", i)) update_player_active(NAME_DS, view.current === DS) update_player_active(NAME_VE, view.current === VE) update_player_active(NAME_BK, view.current === BK) update_player_info(NAME_DS, view.vp[0]) update_player_info(NAME_BK, view.vp[1]) update_player_info(NAME_VE, view.vp[2]) ui.this_card.className = make_card_class_name(view.deck[0]) if (view.deck[1] === 0) { ui.deck_outer.className = "hide" } else { if (view.deck[2] === 0) ui.deck_outer.className = "card card_back" else if (view.deck[2] === 1) ui.deck_outer.className = "card card_45_back" else if (view.deck[2] === 2) ui.deck_outer.className = "card card_46_back" else if (view.deck[2] === 3) ui.deck_outer.className = "card card_47_back" ui.deck_size.textContent = `${view.deck[1]}` } if (view.deck[3][1] !== null) ui.of_gods_and_kings.className = make_card_class_name(view.deck[3][0]) else ui.of_gods_and_kings.className = "hide" // Dynasty card if (view.succ > 0) ui.dynasty_card.className = "card card_dynasty_tughlaq" else ui.dynasty_card.className = "card card_dynasty_khalji" // Discard stack update_discard() ui.this_card.classList.toggle("action", !!(view.actions && view.actions.event === 1)) ui.dynasty_card.classList.toggle("action", !!(view.actions && view.actions.dynasty_card === 1)) ui.shaded_event.classList.toggle("action", !!(view.actions && view.actions.shaded === 1)) ui.unshaded_event.classList.toggle("action", !!(view.actions && view.actions.unshaded === 1)) ui.gk_shaded_event.classList.toggle("action", !!(view.actions && view.actions.gk_shaded === 1)) ui.gk_unshaded_event.classList.toggle("action", !!(view.actions && view.actions.gk_unshaded === 1)) layout_score() let elites = [ ] let troops = [ ] let discs = [ ] let items = [ ] for (let s = 0; s < data.spaces.length; ++s) { let xy if (s <= last_province) { troops.length = elites.length = discs.length = 0 filter_piece_list(discs, s, DS, DISC) filter_piece_list(elites, s, DS, ELITE) filter_piece_list(troops, s, DS, TROOPS) xy = get_layout_xy(s, "DS") layout_pieces(troops, xy[0], xy[1], discs, DS, elites) troops.length = elites.length = discs.length = 0 filter_piece_list(discs, s, BK, DISC) filter_piece_list(elites, s, BK, ELITE) xy = get_layout_xy(s, "BK") layout_pieces(elites, xy[0], xy[1], discs, BK) troops.length = elites.length = discs.length = 0 filter_piece_list(discs, s, VE, DISC) filter_piece_list(elites, s, VE, ELITE) xy = get_layout_xy(s, "VE") layout_pieces(elites, xy[0], xy[1], discs, VE) troops.length = elites.length = discs.length = 0 filter_piece_list(troops, s, MI, TROOPS) xy = get_layout_xy(s, "mongols") layout_pieces(troops, xy[0], xy[1], discs, MI) } else if (s >= S_BK_INF_2 && s <= S_BK_INF_4) { items.length = discs.length = 0 filter_piece_list(items, s, BK, ELITE) xy = get_layout_xy(s) layout_pieces(items, xy[0], xy[1], discs, BK) } else if (s >= S_VE_INF_1 && s <= S_VE_INF_4) { items.length = discs.length = 0 filter_piece_list(items, s, VE, ELITE) xy = get_layout_xy(s) layout_pieces(items, xy[0], xy[1], discs, VE) } // Action highlighting if (s <= S_MONGOL_INVADERS) { ui.spaces[s].classList.toggle("action", is_action("space", s)) ui.spaces[s].classList.toggle("selected", view.where === s) ui.spaces[s].classList.toggle("campaign", set_has(view.campaign_spaces, s) && !is_action("space", s)) } // Control if (s < S_DELHI) { if (view.tributary & (1<= 0) ui.dice[i].className = "die d" + v ui.dice[i].classList.toggle("action", is_action("die", i)) if (view.attack && view.attack.n_units && view.dice[i] <= view.attack.n_units[i < 4 ? 0 : 1] && view.dice[i] < 6) ui.dice[i].classList.toggle("hit", true) else ui.dice[i].classList.toggle("hit", false) } action_menu(document.getElementById("negotiate_menu"), [ "transfer_resources", "ask_resources", "transfer_cavalry", "ask_cavalry", "ping" ]) // Influence layout_influence() // Select Faction action_button("ds", "Delhi Sultanate") action_button("bk", "Bahmani Kingdom") action_button("ve", "Vijayanagara Empire") confirm_action_button("choose_ds", "Delhi Sultanate", "Choose Delhi Sultanate to execute this event?") confirm_action_button("choose_bk", "Bahmani Kingdom", "Choose Bahmani Kingdom to execute this event?") confirm_action_button("choose_ve", "Vijayanagara Empire", "Choose Vijayanagara Empire to execute this event?") // SOP buttons action_button("command_decree", "Command & Decree") action_button("event_command", "Event or Command") action_button("lim_command", "Limited Command") action_button("pass", "Pass") // DS commands action_button("conscript", "Conscript") action_button("end_conscript", "End Conscript") action_button("march", "March") action_button("end_march", "End March") action_button("govern", "Govern") action_button("end_govern", "End Govern") // Command buttons action_button("rally", "Rally") action_button("end_rally", "End Rally") action_button("migrate", "Migrate") action_button("end_migrate", "End Migrate") action_button("rebel", "Rebel") action_button("end_rebel", "End Rebel") action_button("attack", "Attack") action_button("end_attack", "End Attack") action_button("roll", "Roll") // Decree buttons action_button("tax", "Tax") action_button("end_tax", "End Tax") action_button("trade", "Trade") action_button("end_trade", "End Trade") action_button("build", "Build") action_button("end_build", "End Build") action_button("compel", "Compel") action_button("end_compel", "End Compel") action_button("conspire", "Conspire") action_button("end_conspire", "End Conspire") // DS decrees action_button("collect", "Collect Tribute") action_button("end_collect", "End Collect Tribute") action_button("campaign", "Campaign") action_button("end_campaign", "End Campaign") action_button("demand", "Demand Obedience") action_button("end_demand", "End Demand Obedience") // MI buttons action_button("advance", "Advance") action_button("end_advance", "End Advance") action_button("amass", "Amass") action_button("mi_attack", "Attack & Plunder") action_button("end_gifts", "Refuse Compromising Gifts") // Event buttons action_button("obedient", "Obedient") action_button("rebelling", "Rebelling") action_button("remove_influence", "Remove Influence") action_button("add_influence", "Add Influence") // Other buttons action_button("roll", "Roll") action_button("end_cavalry", "End Cavalry") action_button("resume", "Resume") action_button("skip", "Skip") action_button("next", "Next") action_button("end_event", "End Event") action_button("end_return", "End Influence Shift") action_button("end_of_turn", "End Turn") action_button("deny", "Deny") action_button("done", "Done") action_button("undo", "Undo") } /* TOOLTIPS */ function register_card_tip(e, c) { e.onmouseenter = () => on_focus_card_tip(c) e.onmouseleave = on_blur_card_tip } function on_focus_this_event() { let c = view.deck[0] if (c > 0) ui.status.textContent = data.card_title[c] } function on_focus_unshaded_event() { let c = view.deck[0] if (c > 0) { let f = data.card_flavor[c] if (f) ui.status.textContent = data.card_title[c] + " - " + f else ui.status.textContent = data.card_title[c] } } function on_focus_gk_unshaded_event() { let c = view.deck[3][0] if (c > 0) { let f = data.card_flavor[c] if (f) ui.status.textContent = data.card_title[c] + " - " + f else ui.status.textContent = data.card_title[c] } } function on_focus_shaded_event() { let c = view.deck[0] if (c > 0) { ui.status.textContent = data.card_title[c] + " - " + data.card_flavor_shaded[c] } } function on_focus_gk_shaded_event() { let c = view.deck[3][0] if (c > 0) { ui.status.textContent = data.card_title[c] + " - " + data.card_flavor_shaded[c] } } function on_blur_event() { ui.status.textContent = "" } function on_focus_card_tip(c) { ui.card_tip.className = "card card_" + c } function on_blur_card_tip() { ui.card_tip.className = "hide" } function on_focus_space_tip(s) { ui.spaces[s].classList.add("tip") } function on_blur_space_tip(s) { ui.spaces[s].classList.remove("tip") } function on_click_space_tip(s) { scroll_into_view(ui.spaces[s]) } /* LOG */ function sub_card(match, p1) { let x = p1 | 0 let n = data.card_title[x] return `${n}` } function sub_space(match, p1) { let x = p1 | 0 let n = data.space_name[x] return `${n}` } const ICONS = { DDS: '', DBK: '', DVE: '', EDS: '', EBK: '', EVE: '', CDS: '', CMI: '', FDS: '', FBK: '', FVE: '', FMI: '', IBK: '', IVE: '', CAV: '', RES: '', A1: '\u2776', A2: '\u2777', A3: '\u2778', A4: '\u2779', A5: '\u277A', A6: '\u277B', D1: '\u2460', D2: '\u2461', D3: '\u2462', D4: '\u2463', D5: '\u2464', D6: '\u2465', } function sub_icon(match) { return ICONS[match] ?? match } function sub_repeat(match, n, sym) { var s = sym n = parseInt(n) while (--n > 0) s += "\u2009" + sym return s } function on_prompt(text) { return text.replaceAll("a Amir", "an Amir") } function on_log(text) { let p = document.createElement("div") let sub_text = "" let ix if (text.match(/^>/)) { text = text.substring(1) p.className = "indent" } text = text.replace(/->/g, "\u2794") text = text.replace(/&/g, "&") text = text.replace(//g, ">") text = text.replace(/([1-5]) (DDS|DBK|DVE|CDS|CMI|EDS|EBK|EVE)/g, sub_repeat) text = text.replace(/\bDDS\b/g, sub_icon) text = text.replace(/\bDBK\b/g, sub_icon) text = text.replace(/\bDVE\b/g, sub_icon) text = text.replace(/\bCDS\b/g, sub_icon) text = text.replace(/\bCMI\b/g, sub_icon) text = text.replace(/\bEDS\b/g, sub_icon) text = text.replace(/\bEBK\b/g, sub_icon) text = text.replace(/\bEVE\b/g, sub_icon) text = text.replace(/\bFDS\b/g, sub_icon) text = text.replace(/\bFBK\b/g, sub_icon) text = text.replace(/\bFVE\b/g, sub_icon) text = text.replace(/\bFMI\b/g, sub_icon) text = text.replace(/\bIBK\b/g, sub_icon) text = text.replace(/\bIVE\b/g, sub_icon) text = text.replace(/\bCAV\b/g, sub_icon) text = text.replace(/\bRES\b/g, sub_icon) text = text.replace(/\b[AD][1-6]\b/g, sub_icon) text = text.replace(/ - /g, " \u2013 ") if (text.match(/^\.h1 C/)) { text = text.substring(4) ix = parseInt(text.substring(1)) if (ix >= 37 && ix <= 44) p.className = "h1 mi" else if (ix === 45) p.className = "h1 succ1" else if (ix === 46) p.className = "h1 succ2" else if (ix === 47) p.className = "h1 succ3" else if (ix >= 48 && ix <= 49) p.className = "h1 mi" else p.className = "h1" } else if (text.match(/^\.h1 /)) { text = text.substring(4) p.className = "h1" } else if (text.match(/^\.h2 Sultanate/)) { text = text.substring(4) p.className = "h2 ds" } else if (text.match(/^\.h2 Bahmani/)) { text = text.substring(4) p.className = "h2 bk" } else if (text.match(/^\.h2 Vijayanagara/)) { text = text.substring(4) p.className = "h2 ve" } else if (text.match(/^\.h2 Mongol/)) { text = text.substring(4) p.className = "h2 mi" } else if (text.match(/^\.h3/)) { text = text.substring(4) p.className = "h3" } else if (text.match(/^\.i/)) { text = text.substring(3) p.className = "italic" } text = text.replace(/C(\d+)/g, sub_card) text = text.replace(/S(\d+)/g, sub_space) p.innerHTML = text return p }