"use strict"
/* global view, data, send_action, action_button, scroll_with_middle_mouse */
const SCALE = 1.8033333333333332
const FLN = 0
const GOV = 1
const DEPLOY = 1
const ELIMINATED = 2
// const UG = 0
// const OPS = 1
// const PTL = 2
// const OC = 3
// const BOXES = [UG, OPS, PTL, OC]
const area_count = 31
const unit_count = 120
function is_gov_unit(u) { return (u >= 0 && u <= 39) }
function is_fln_unit(u) { return (u >= 40 && u <= 119) }
function set_has(set, item) {
if (!set)
return false
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
}
let ui = {
board: document.getElementById("map"),
map: document.getElementById("map"),
favicon: document.getElementById("favicon"),
header: document.querySelector("header"),
status: document.getElementById("status"),
player: [
document.getElementById("role_FLN"),
document.getElementById("role_Government"),
],
ap: document.querySelector("#role_FLN .role_ap"),
psl: [
document.querySelector("#role_FLN .role_psl"),
document.querySelector("#role_Government .role_psl"),
],
markers: {
turn: document.getElementById("turn_now"),
fln_psl: document.getElementById("fln_psl"),
fln_ap: document.getElementById("fln_ap"),
gov_psl: document.getElementById("gov_psl"),
air_avail: document.getElementById("air_avail"),
air_max: document.getElementById("air_max"),
helo_avail: document.getElementById("helo_avail"),
helo_max: document.getElementById("helo_max"),
naval: document.getElementById("naval"),
border_zone: document.getElementById("border_zone"),
},
tracker: [],
drm: [],
areas: [],
area_markers: [],
boxes: [],
locations: [],
zones: [],
units: [],
units_holder: document.getElementById("units"),
fln_supply_panel: document.getElementById("fln_supply_panel"),
gov_supply_panel: document.getElementById("gov_supply_panel"),
eliminated_panel: document.getElementById("eliminated_panel"),
fln_supply: document.getElementById("fln_supply"),
gov_supply: document.getElementById("gov_supply"),
eliminated: document.getElementById("eliminated"),
}
// remote (1 bit), terrorized (1 bit), gov control (1 bit), fln control (1 bit)
const AREA_FLN_CONTROL_SHIFT = 0
const AREA_FLN_CONTROL_MASK = 1 << AREA_FLN_CONTROL_SHIFT
const AREA_GOV_CONTROL_SHIFT = 1
const AREA_GOV_CONTROL_MASK = 1 << AREA_GOV_CONTROL_SHIFT
const AREA_TERRORIZED_SHIFT = 2
const AREA_TERRORIZED_MASK = 1 << AREA_TERRORIZED_SHIFT
const AREA_REMOTE_SHIFT = 3
const AREA_REMOTE_MASK = 1 << AREA_REMOTE_SHIFT
// area control
function is_area_fln_control(l) {
return (view.areas[l] & AREA_FLN_CONTROL_MASK) === AREA_FLN_CONTROL_MASK
}
function is_area_gov_control(l) {
return (view.areas[l] & AREA_GOV_CONTROL_MASK) === AREA_GOV_CONTROL_MASK
}
// terrorized
function is_area_terrorized(l) {
return (view.areas[l] & AREA_TERRORIZED_MASK) === AREA_TERRORIZED_MASK
}
// remote
function is_area_remote(l) {
return (view.areas[l] & AREA_REMOTE_MASK) === AREA_REMOTE_MASK
}
function is_area_country(l) {
return data.areas[l].type === COUNTRY
}
function is_area_oas_active(l) {
return view.oas === l
}
// === UNIT STATE ===
// location (8 bits), op box (2 bits), dispersed (1 bit), airmobile (1 bit), neutralized (1 bit)
const UNIT_NEUTRALIZED_SHIFT = 0
const UNIT_NEUTRALIZED_MASK = 1 << UNIT_NEUTRALIZED_SHIFT
const UNIT_AIRMOBILE_SHIFT = 1
const UNIT_AIRMOBILE_MASK = 1 << UNIT_AIRMOBILE_SHIFT
const UNIT_DISPERSED_SHIFT = 2
const UNIT_DISPERSED_MASK = 1 << UNIT_DISPERSED_SHIFT
const UNIT_BOX_SHIFT = 3
const UNIT_BOX_MASK = 3 << UNIT_BOX_SHIFT
const UNIT_LOC_SHIFT = 5
const UNIT_LOC_MASK = 255 << UNIT_LOC_SHIFT
function is_unit_neutralized(u) {
return (view.units[u] & UNIT_NEUTRALIZED_MASK) === UNIT_NEUTRALIZED_MASK
}
function unit_loc(u) {
return (view.units[u] & UNIT_LOC_MASK) >> UNIT_LOC_SHIFT
}
function unit_box(u) {
return (view.units[u] & UNIT_BOX_MASK) >> UNIT_BOX_SHIFT
}
function is_unit_airmobile(u) {
return (view.units[u] & UNIT_AIRMOBILE_MASK) === UNIT_AIRMOBILE_MASK
}
function is_unit_dispersed(u) {
return (view.units[u] & UNIT_DISPERSED_MASK) === UNIT_DISPERSED_MASK
}
function is_unit_contacted(u) {
return set_has(view.contacted, u)
}
function is_unit_eliminated(u) {
return unit_loc(u) === ELIMINATED
}
function is_unit_action(unit) {
return !!(view.actions && view.actions.unit && view.actions.unit.includes(unit))
}
function is_unit_selected(unit) {
if (Array.isArray(view.selected))
return view.selected.includes(unit)
return view.selected === unit
}
function is_loc_selected(loc) {
if (Array.isArray(view.selected_loc))
return view.selected_loc.includes(loc)
return view.selected_loc === loc
}
function is_loc_action(x) {
return !!(view.actions && view.actions.loc && view.actions.loc.includes(x))
}
let action_register = []
function register_action(e, action, id) {
e.my_action = action
e.my_id = id
e.onmousedown = on_click_action
action_register.push(e)
}
function on_click_action(evt) {
if (evt.button === 0)
if (send_action(evt.target.my_action, evt.target.my_id))
evt.stopPropagation()
}
function is_action(action, arg) {
if (arg === undefined)
return !!(view.actions && view.actions[action] === 1)
return !!(view.actions && view.actions[action] && view.actions[action].includes(arg))
}
let on_init_once = false
function on_focus_unit(evt) {
document.getElementById("status").textContent = data.units[evt.target.unit].name
}
function on_blur(_evt) {
document.getElementById("status").textContent = ""
}
function build_units() {
function build_unit(u) {
let side = is_gov_unit(u) ? "gov" : "fln"
let elt = ui.units[u] = document.createElement("div")
let klass = data.units[u].class
elt.className = `counter unit ${side} u${u} ${klass}`
elt.addEventListener("mousedown", on_click_unit)
elt.addEventListener("mouseenter", on_focus_unit)
elt.addEventListener("mouseleave", on_blur)
elt.unit = u
}
for (let u = 0; u < unit_count; ++u) {
build_unit(u)
}
}
function on_click_loc(evt) {
if (evt.button === 0) {
let loc = parseInt(evt.target.dataset.loc)
console.log('loc', loc)
if (send_action('loc', loc))
evt.stopPropagation()
}
}
function on_click_unit(evt) {
if (evt.button === 0) {
console.log('unit', evt.target.unit, data.units[evt.target.unit])
send_action('unit', evt.target.unit)
}
}
function create_tracker(i, x, y) {
let e = ui.tracker[i] = document.createElement("div")
e.dataset.id = i
e.className = "space stack m"
e.style.left = x / SCALE + "px"
e.style.top = y / SCALE + "px"
e.style.width = 85 / SCALE + "px"
e.style.height = 85 / SCALE + "px"
document.getElementById("tracker").appendChild(e)
}
function create_border_zone(i) {
let e = ui.drm[i] = document.createElement("div")
e.dataset.id = i
e.className = "space"
e.style.left = (288.2 + (i * 99)) / SCALE + "px"
e.style.top = 396 / SCALE + "px"
e.style.width = 94 / SCALE + "px"
e.style.height = 94 / SCALE + "px"
document.getElementById("drm").appendChild(e)
}
const COUNTRY = 4
function create_area(i, area_id, type) {
let e = ui.areas[i] = document.createElement("div")
e.id = `area-${area_id}`
e.dataset.loc = data.areas[i].loc
e.className = "space"
e.addEventListener("mousedown", on_click_loc)
e.style.left = data.areas[i].x / SCALE + "px"
e.style.top = data.areas[i].y / SCALE + "px"
if (type !== COUNTRY) {
e.style.width = 193 / SCALE + "px"
e.style.height = 193 / SCALE + "px"
} else {
e.style.width = data.areas[i].w / SCALE + "px"
e.style.height = data.areas[i].h / SCALE + "px"
}
document.getElementById("areas").appendChild(e)
}
function create_area_markers(i, area_id) {
let e = document.createElement("div")
e.id = `area-marker-${area_id}`
e.dataset.loc = data.areas[i].loc
e.className = "space stack s"
e.style.left = (data.areas[i].x + 175) / SCALE + "px"
e.style.top = (data.areas[i].y - 65) / SCALE + "px"
e.style.width = 65 / SCALE + "px"
e.style.height = 65 / SCALE + "px"
document.getElementById("area_markers").appendChild(e)
ui.area_markers[i] = {}
for (let marker of ['remote', 'fln_control', 'gov_control', 'terror', 'oas_active']) {
let em = ui.area_markers[i][marker] = document.createElement("div")
em.id = `area-marker-${i}-${marker}`
em.className = `counter ${marker} s`
e.appendChild(em)
}
}
function create_box(i, area_id, box_id) {
let e = ui.boxes[i * 4 + box_id] = document.createElement("div")
e.id = `ops-${area_id}-${box_id}`
e.dataset.loc = data.areas[i].loc
e.className = "space stack"
e.addEventListener("mousedown", on_click_loc)
e.style.left = (data.areas[i].x + (box_id % 2) * 99) / SCALE + "px"
e.style.top = (data.areas[i].y + Math.floor(box_id / 2) * 99) / SCALE + "px"
e.style.width = 94 / SCALE + "px"
e.style.height = 94 / SCALE + "px"
document.getElementById("boxes").appendChild(e)
return e
}
function on_init() {
if (on_init_once)
return
on_init_once = true
// Tracker
let x = 5
let y = 5
for (let i = 0; i < 100; ++i) {
create_tracker(i, x, y)
if (i < 29) {
x += 90
} else if (i < 50) {
y += 90
} else if (i < 79) {
x -= 90
} else {
y -= 90
}
}
// Border Zone DRM
for (let i = 0; i < 4; ++i) {
create_border_zone(i)
}
// Areas
for (let i = 0; i < data.areas.length; ++i) {
let area_id = data.areas[i].id
let type = data.areas[i].type
if (type) {
create_area(i, area_id, type)
if (type !== COUNTRY) {
// Area markers
create_area_markers(i, area_id)
// Unit Boxes
for (let box_id = 0; box_id < 4; ++box_id) {
create_box(i, area_id, box_id)
}
} else {
let e = create_box(i, area_id, 0)
if (area_id === "FRANCE") {
ui.area_markers[i] = {}
let marker = 'oas_active'
let em = ui.area_markers[i][marker] = document.createElement("div")
em.id = `area-marker-${i}-${marker}`
em.className = `counter ${marker}`
e.appendChild(em)
}
}
}
}
build_units()
}
function update_unit(e, u) {
e.classList.toggle("neutralized", is_unit_neutralized(u))
e.classList.toggle("airmobile", is_unit_airmobile(u))
e.classList.toggle("fr_xx_dispersed", is_unit_dispersed(u))
e.classList.toggle("action", is_unit_action(u))
e.classList.toggle("selected", is_unit_selected(u))
e.classList.toggle("contacted", is_unit_contacted(u))
e.classList.toggle("eliminated", is_unit_eliminated(u))
}
Node.prototype.appendChildAnimated = function(e) {
const { left: x0, top: y0 } = e.getBoundingClientRect()
this.appendChild(e)
if (!x0)
return
const { left: x1, top: y1 } = e.getBoundingClientRect()
const dx = x0 - x1
const dy = y0 - y1
if (!dx && !dy)
return
const transformFrom = `translate3d(${dx}px, ${dy}px, 0)`
const transformTo = `translate3d(0, 0, 0)`
e.animate([
{ transform: transformFrom },
{ transform: transformTo },
], {
duration: 1000,
easing: 'ease',
})
}
function update_map() {
console.log("VIEW", view)
ui.player[FLN].classList.toggle("active", view.active === "FLN")
ui.player[GOV].classList.toggle("active", view.active === "Government")
ui.ap.textContent = view.fln_ap
ui.psl[FLN].textContent = view.fln_psl
ui.psl[GOV].textContent = view.gov_psl
ui.tracker[view.turn % 100].appendChildAnimated(ui.markers.turn)
ui.tracker[view.fln_ap].appendChildAnimated(ui.markers.fln_ap)
ui.tracker[view.fln_psl].appendChildAnimated(ui.markers.fln_psl)
ui.tracker[view.gov_psl].appendChildAnimated(ui.markers.gov_psl)
ui.tracker[view.air_avail].appendChildAnimated(ui.markers.air_avail)
ui.tracker[view.air_max].appendChildAnimated(ui.markers.air_max)
ui.tracker[view.helo_avail].appendChildAnimated(ui.markers.helo_avail)
ui.tracker[view.helo_max].appendChildAnimated(ui.markers.helo_max)
ui.tracker[view.naval].appendChildAnimated(ui.markers.naval)
// Hide avail markers when no Air / Helo at all
ui.markers.air_avail.classList.toggle("hide", !view.air_max)
ui.markers.helo_avail.classList.toggle("hide", !view.helo_max)
ui.drm[-view.border_zone_drm].appendChildAnimated(ui.markers.border_zone)
ui.markers.border_zone.classList.toggle("hide", view.border_zone_drm === null)
ui.markers.border_zone.classList.toggle("neutralized", !view.border_zone_active)
for (let u = 0; u < unit_count; ++u) {
let e = ui.units[u]
let loc = unit_loc(u)
e.dataset.loc = loc
if (loc) {
if (loc === DEPLOY) {
if (is_gov_unit(u) && !ui.gov_supply.contains(e))
ui.gov_supply.appendChildAnimated(e)
if (is_fln_unit(u) && !ui.fln_supply.contains(e))
ui.fln_supply.appendChildAnimated(e)
} else if (loc === ELIMINATED) {
if (!ui.eliminated.contains(e))
ui.eliminated.appendChildAnimated(e)
} else {
let box_id = unit_box(u)
if (is_area_country(loc)) {
// only single box in France, Morocco and Tunisia
box_id = 0
}
if (!ui.boxes[loc * 4 + box_id].contains(e)) {
ui.boxes[loc * 4 + box_id].appendChildAnimated(e)
}
}
update_unit(e, u)
} else {
e.remove()
}
}
// Hide supply panels when empty
ui.fln_supply_panel.classList.toggle("hide", !(ui.fln_supply.childNodes.length - 1))
ui.gov_supply_panel.classList.toggle("hide", !(ui.gov_supply.childNodes.length - 1))
ui.eliminated_panel.classList.toggle("hide", !(ui.eliminated.childNodes.length - 1))
for (let i = 0; i < ui.areas.length; ++i) {
let e = ui.areas[i]
if (e) {
let loc = parseInt(e.dataset.loc)
e.classList.toggle("action", is_loc_action(loc))
e.classList.toggle("target", is_loc_selected(loc))
let em = ui.area_markers[i]
if (em) {
if (!is_area_country(loc)) {
em.fln_control.classList.toggle("hide", !is_area_fln_control(loc))
em.gov_control.classList.toggle("hide", !is_area_gov_control(loc))
em.remote.classList.toggle("hide", !is_area_remote(loc))
em.terror.classList.toggle("hide", !is_area_terrorized(loc))
}
em.oas_active.classList.toggle("hide", !is_area_oas_active(loc))
}
}
}
}
function on_update() { // eslint-disable-line no-unused-vars
on_init()
update_map()
for (let e of action_register)
e.classList.toggle("action", is_action(e.my_action, e.my_id))
action_button("quick_setup", "Quick Setup")
action_button("end_deployment", "End deployment")
action_button("roll", "Roll")
action_button("raise_fln_psl_1d6", "+1d6 FLN PSL")
action_button("lower_gov_psl_1d6", "-1d6 Government PSL")
action_button("zone_I", "I")
action_button("zone_II", "II")
action_button("zone_III", "III")
action_button("zone_IV", "IV")
action_button("zone_V", "V")
action_button("zone_VI", "VI")
// gov reinforcement
action_button("mobilization", "Mobilization")
action_button("activation", "Activation")
action_button("acquire_assets", "Acquire assets")
action_button("select_all_inactive", "Select All")
action_button("acquire_air_point", "+1 Air")
action_button("acquire_helo_point", "+1 Helo")
action_button("acquire_naval_point", "+1 Naval")
action_button("mobilize_border_zone", "Mobilize Border")
action_button("improve_border_zone", "Improve Border")
action_button("activate_border_zone", "Activate Border")
action_button("remove", "Remove")
action_button("activate", "Activate")
action_button("build_cadre", "Build Cadre")
action_button("build_band", "Build Band")
action_button("convert_cadre_to_front", "Convert to Front")
action_button("convert_cadre_to_band", "Convert to Band")
action_button("convert_band_to_failek", "Convert to Failek")
action_button("convert_front_to_cadre", "Convert to Cadre")
action_button("end_reinforcement", "End reinforcement")
action_button("change_division_mode", "Change Division Mode")
action_button("end_deployment", "End deployment")
action_button("propaganda", "Propaganda")
action_button("strike", "Strike")
action_button("move", "Move")
action_button("raid", "Raid")
action_button("harass", "Harass")
action_button("flush", "Flush")
action_button("intelligence", "Intelligence")
action_button("civil_affairs", "Civil Affairs")
action_button("suppression", "Suppression")
action_button("population_resettlement", "Population Resettlement")
action_button("gov_mission", "Government Mission")
action_button("use_air_point", "Air Point")
action_button("airmobilize", "Airmobilize")
action_button("no_react", "No React")
action_button("auto_pass", "Auto-Pass")
action_button("pass", "Pass")
action_button("add_fln_psl", "+1 FLN PSL")
action_button("add_5_fln_psl", "+5 FLN PSL")
action_button("remove_fln_psl", "-1 FLN PSL")
action_button("remove_5_fln_psl", "-5 FLN PSL")
action_button("add_gov_psl", "+1 Government PSL")
action_button("add_5_gov_psl", "+5 Government PSL")
action_button("remove_gov_psl", "-1 Government PSL")
action_button("remove_5_gov_psl", "-5 Government PSL")
action_button("eliminate_cadre", "Eliminate Cadre")
action_button("eliminate_band", "Eliminate Band")
action_button("reduce_front", "Reduce Front")
action_button("reduce_failek", "Reduce Failek")
action_button("end_turn", "End Turn")
action_button("done", "Done")
action_button("undo", "Undo")
// XXX debug
action_button("restart", "Restart")
action_button("reset", "Reset")
}
const ICONS = {
B0: '',
B1: '',
B2: '',
B3: '',
B4: '',
B5: '',
B6: '',
W0: '',
W1: '',
W2: '',
W3: '',
W4: '',
W5: '',
W6: '',
}
function sub_icon(match) {
return ICONS[match]
}
function on_focus_area_tip(x) { // eslint-disable-line no-unused-vars
ui.areas[x].classList.add("tip")
}
function on_blur_area_tip(x) { // eslint-disable-line no-unused-vars
ui.areas[x].classList.remove("tip")
}
function on_click_area_tip(x) { // eslint-disable-line no-unused-vars
ui.areas[x].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" })
}
function sub_area_name(_match, p1, _offset, _string) {
let x = p1 | 0
let n = data.areas[x].name
return `${n}`
}
function on_focus_unit_tip(x) { // eslint-disable-line no-unused-vars
ui.units[x].classList.add("tip")
}
function on_blur_unit_tip(x) { // eslint-disable-line no-unused-vars
ui.units[x].classList.remove("tip")
}
function on_click_unit_tip(x) { // eslint-disable-line no-unused-vars
ui.units[x].scrollIntoView({ block:"center", inline:"center", behavior:"smooth" })
}
function sub_unit_name(_match, p1, _offset, _string) {
let x = p1 | 0
let n = data.units[x].name
return `${n}`
}
function on_log(text) { // eslint-disable-line no-unused-vars
let p = document.createElement("div")
if (text.match(/^>/)) {
text = text.substring(1)
p.className = "i"
}
text = text.replace(/&/g, "&")
text = text.replace(//g, ">")
text = text.replace(/U(\d+)/g, sub_unit_name)
text = text.replace(/A(\d+)/g, sub_area_name)
text = text.replace(/\b[BW]\d\b/g, sub_icon)
if (text.match(/^\.h1/)) {
text = text.substring(4)
p.className = 'h1'
}
else if (text.match(/^\.h2/)) {
text = text.substring(4)
p.className = 'h2'
if (text.match(/^FLN /)) {
p.classList.add("fln")
} else if (text.match(/^Government /)) {
p.classList.add("gov")
} else if (text.match(/^OAS /)) {
p.classList.add("oas")
} else {
p.classList.add("both")
}
}
else if (text.match(/^\.h3/)) {
text = text.substring(4)
p.className = 'h3'
}
p.innerHTML = text
return p
}
scroll_with_middle_mouse("main")