"use strict"; function overviewMilitary() { if (customization) return; closeDialogs("#militaryOverview, .stable"); if (!layerIsOn("toggleStates")) toggleStates(); if (!layerIsOn("toggleBorders")) toggleBorders(); const body = document.getElementById("militaryBody"); addLines(); $("#militaryOverview").dialog(); if (modules.overviewMilitary) return; modules.overviewMilitary = true; updateHeaders(); $("#militaryOverview").dialog({ title: "Military Overview", resizable: false, width: fitContent(), position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); // add listeners document.getElementById("militaryOverviewRefresh").addEventListener("click", addLines); document.getElementById("militaryOptionsButton").addEventListener("click", militaryCustomize); document.getElementById("militaryOverviewRecalculate").addEventListener("click", militaryRecalculate); document.getElementById("militaryExport").addEventListener("click", downloadMilitaryData); body.addEventListener("change", function(ev) { const el = ev.target, line = el.parentNode, state = +line.dataset.id; changeAlert(state, line, +el.value); }); // update military types in header and tooltips function updateHeaders() { const header = document.getElementById("militaryHeader"); header.querySelectorAll(".removable").forEach(el => el.remove()); const insert = html => document.getElementById("militaryTotal").insertAdjacentHTML("beforebegin", html); for (const u of options.military) { const label = capitalize(u.name.replace(/_/g, ' ')); insert(`
${label} 
`); } header.querySelectorAll(".removable").forEach(function(e) { e.addEventListener("click", function() {sortLines(this);}); }); } // add line for each state function addLines() { body.innerHTML = ""; let lines = ""; const states = pack.states.filter(s => s.i && !s.removed); for (const s of states) { const population = rn((s.rural + s.urban * urbanization.value) * populationRate.value); const getForces = u => s.military.reduce((s, r) => s+(r.u[u.name]||0), 0); const total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0); const rate = total / population * 100; const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" "); const lineData = options.military.map(u => `
${getForces(u)}
`).join(" "); lines += `
${lineData}
${si(total)}
${si(population)}
${rn(rate, 2)}%
`; } body.insertAdjacentHTML("beforeend", lines); updateFooter(); // add listeners body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev))); body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev))); applySorting(militaryHeader); } function changeAlert(state, line, alert) { const s = pack.states[state]; const dif = s.alert || alert ? alert / s.alert : 0; // modifier s.alert = line.dataset.alert = alert; s.military.forEach(r => { Object.keys(r.u).forEach(u => r.u[u] = rn(r.u[u] * dif)); // change units value r.a = d3.sum(Object.values(r.u)); // change total armies.select(`g>g#regiment${s.i}-${r.i}>text`).text(Military.getTotal(r)); // change icon text }); const getForces = u => s.military.reduce((s, r) => s+(r.u[u.name]||0), 0); options.military.forEach(u => line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u)); const population = rn((s.rural + s.urban * urbanization.value) * populationRate.value); const total = line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0); const rate = line.dataset.rate = total / population * 100; line.querySelector("div[data-type='total']>b").innerHTML = si(total); line.querySelector("div[data-type='rate']").innerHTML = rn(rate, 2) + "%"; updateFooter(); } function updateFooter() { const lines = Array.from(body.querySelectorAll(":scope > div")); const statesNumber = militaryFooterStates.innerHTML = pack.states.filter(s => s.i && !s.removed).length; militaryFooterForces.innerHTML = si(d3.sum(lines.map(el => el.dataset.total)) / statesNumber); militaryFooterRate.innerHTML = rn(d3.sum(lines.map(el => el.dataset.rate)) / statesNumber, 2) + "%"; militaryFooterAlert.innerHTML = rn(d3.sum(lines.map(el => el.dataset.alert)) / statesNumber, 2); } function stateHighlightOn(event) { if (!layerIsOn("toggleStates")) return; const state = +event.target.dataset.id; if (customization || !state) return; const path = regions.select("#state"+state).attr("d"); debug.append("path").attr("class", "highlight").attr("d", path) .attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1) .attr("filter", "url(#blur1)").call(transition); } function transition(path) { const duration = (path.node().getTotalLength() + 5000) / 2; path.transition().duration(duration).attrTween("stroke-dasharray", tweenDash); } function tweenDash() { const l = this.getTotalLength(); const i = d3.interpolateString("0," + l, l + "," + l); return t => i(t); } function removePath(path) { path.transition().duration(1000).attr("opacity", 0).remove(); } function stateHighlightOff() { debug.selectAll(".highlight").each(function() { d3.select(this).call(removePath); }); } function militaryCustomize() { const types = ["default", "melee", "ranged", "mounted", "machinery", "naval"]; const table = document.getElementById("militaryOptions").querySelector("tbody"); removeUnitLines(); options.military.map(u => addUnitLine(u)); $("#militaryOptions").dialog({ title: "Edit Military Units", resizable: false, width: fitContent(), position: {my: "center", at: "center", of: "svg"}, buttons: { Apply: function() {applyMilitaryOptions(); $(this).dialog("close");}, Add: () => addUnitLine({name: "custom", rural: 0.2, urban: 0.5, crew: 1, type: "default"}), Restore: restoreDefaultUnits, Cancel: function() {$(this).dialog("close");} }, open: function() { const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button"); buttons[0].addEventListener("mousemove", () => tip("Apply military units settings. All forces will be recalculated!")); buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table")); buttons[2].addEventListener("mousemove", () => tip("Restore default military units and settings")); buttons[3].addEventListener("mousemove", () => tip("Close the window without saving the changes")); } }); function removeUnitLines() { table.querySelectorAll("tr").forEach(el => el.remove()); } function addUnitLine(u) { const row = ` `; table.insertAdjacentHTML("beforeend", row); } function restoreDefaultUnits() { removeUnitLines(); [{name:"infantry", rural:.25, urban:.2, crew:1, type:"melee", separate:0}, {name:"archers", rural:.12, urban:.2, crew:1, type:"ranged", separate:0}, {name:"cavalry", rural:.12, urban:.03, crew:3, type:"mounted", separate:0}, {name:"artillery", rural:0, urban:.03, crew:8, type:"machinery", separate:0}, {name:"fleet", rural:0, urban:.015, crew:100, type:"naval", separate:1}].map(u => addUnitLine(u)); } function applyMilitaryOptions() { options.military = Array.from(table.querySelectorAll("tr")).map(r => { const [name, rural, urban, crew, type, separate] = Array.from(r.querySelectorAll("input, select")).map(d => d.value||d.checked); return {name:name.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, '_'), rural:+rural||0, urban:+urban||0, crew:+crew||0, type, separate:+separate||0}; }); localStorage.setItem("military", JSON.stringify(options.military)); Military.generate(); updateHeaders(); addLines(); } } function militaryRecalculate() { alertMessage.innerHTML = "Are you sure you want to recalculate military forces for all states?"; $("#alert").dialog({resizable: false, title: "Remove regiment", buttons: { Recalculate: function() { $(this).dialog("close"); Military.generate(); addLines(); }, Cancel: function() {$(this).dialog("close");} } }); } function downloadMilitaryData() { const units = options.military.map(u => u.name); let data = "Id,State,"+units.map(u => capitalize(u)).join(",")+",Total,Population,Rate,War Alert\n"; // headers body.querySelectorAll(":scope > div").forEach(function(el) { data += el.dataset.id + ","; data += el.dataset.state + ","; data += units.map(u => el.dataset[u]).join(",") + ","; data += el.dataset.total + ","; data += el.dataset.population + ","; data += rn(el.dataset.rate,2) + "%,"; data += el.dataset.alert + "\n"; }); const name = getFileName("Military") + ".csv"; downloadFile(data, name); } }