This commit is contained in:
Azgaar 2020-03-27 17:52:23 +03:00
parent cba011282d
commit c8f758ab3c
15 changed files with 397 additions and 135 deletions

View file

@ -68,6 +68,7 @@ function editDiplomacy() {
const tipChange = `${tip}. Click to change relations to ${selName}`;
lines += `<div class="states" data-id=${s.i} data-name="${s.fullName}" data-relations="${relation}">
<span data-tip="${tipSelect}" class="icon-right-open"></span>
<div data-tip="${tipSelect}" style="width:12em">${s.fullName}</div>
<svg data-tip="${tipChange}" width=".9em" height=".9em" style="margin-bottom:-1px" class="changeRelations">
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect pointer" style="pointer-events: none"></rect>
@ -91,30 +92,20 @@ function editDiplomacy() {
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)
const d = regions.select("#state"+state).attr("d");
const path = debug.append("path").attr("class", "highlight").attr("d", d)
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
.attr("filter", "url(#blur1)").call(transition);
}
.attr("filter", "url(#blur1)");
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 l = path.node().getTotalLength(), dur = (l + 5000) / 2;
const i = d3.interpolateString("0," + l, l + "," + l);
return t => i(t);
}
function removePath(path) {
path.transition().duration(1000).attr("opacity", 0).remove();
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)});
}
function stateHighlightOff() {
debug.selectAll(".highlight").each(function(el) {
d3.select(this).call(removePath);
function stateHighlightOff(event) {
debug.selectAll(".highlight").each(function() {
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
});
}

View file

@ -311,7 +311,7 @@ function editHeightmap() {
const land = pack.cells.h[i] >= 20;
// check biome
if (land && !biome[g]) pack.cells.biome[i] = getBiomeId(grid.cells.prec[g], grid.cells.temp[g]);
if (!biome[g]) pack.cells.biome[i] = getBiomeId(grid.cells.prec[g], grid.cells.temp[g]);
else if (!land && biome[g]) pack.cells.biome[i] = 0;
else pack.cells.biome[i] = biome[g];

View file

@ -334,9 +334,9 @@ function drawBiomes() {
const cells = pack.cells, vertices = pack.vertices, n = cells.i.length;
const used = new Uint8Array(cells.i.length);
const paths = new Array(biomesData.i.length).fill("");
for (const i of cells.i) {
if (!cells.biome[i]) continue; // no need to mark water
if (!cells.biome[i]) continue; // no need to mark marine biome (liquid water)
if (used[i]) continue; // already marked
const b = cells.biome[i];
const onborder = cells.c[i].some(n => cells.biome[n] !== b);

View file

@ -20,6 +20,7 @@ function overviewMilitary() {
// add listeners
document.getElementById("militaryOverviewRefresh").addEventListener("click", addLines);
document.getElementById("militaryPercentage").addEventListener("click", togglePercentageMode);
document.getElementById("militaryOptionsButton").addEventListener("click", militaryCustomize);
document.getElementById("militaryOverviewRecalculate").addEventListener("click", militaryRecalculate);
document.getElementById("militaryExport").addEventListener("click", downloadMilitaryData);
@ -29,6 +30,11 @@ function overviewMilitary() {
changeAlert(state, line, +el.value);
});
body.addEventListener("click", function(ev) {
const el = ev.target, line = el.parentNode, state = +line.dataset.id;
if (el.tagName === "SPAN") overviewRegiments(state);
});
// update military types in header and tooltips
function updateHeaders() {
const header = document.getElementById("militaryHeader");
@ -62,10 +68,11 @@ function overviewMilitary() {
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
${lineData}
<div data-type="total" data-tip="Total state military personnel (considering crew)"><b>${si(total)}</b></div>
<div data-tip="State population">${si(population)}</div>
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>
<div data-type="population" data-tip="State population">${si(population)}</div>
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div>
<input data-type="alert" data-tip="War Alert. Editable modifier to military forces number, depends of political situation" type="number" min=0 step=.01 value="${rn(s.alert, 2)}">
<input data-tip="War Alert. Editable modifier to military forces number, depends of political situation" style="width:4.1em" type="number" min=0 step=.01 value="${rn(s.alert, 2)}">
<span data-tip="Show regiments list" class="icon-list-bullet pointer"></span>
</div>`;
}
body.insertAdjacentHTML("beforeend", lines);
@ -74,6 +81,8 @@ function overviewMilitary() {
// 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)));
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
applySorting(militaryHeader);
}
@ -103,7 +112,9 @@ function overviewMilitary() {
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);
const total = d3.sum(lines.map(el => el.dataset.total));
militaryFooterForcesTotal.innerHTML = si(total);
militaryFooterForces.innerHTML = si(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);
}
@ -112,31 +123,51 @@ function overviewMilitary() {
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)
const d = regions.select("#state"+state).attr("d");
const path = debug.append("path").attr("class", "highlight").attr("d", d)
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
.attr("filter", "url(#blur1)").call(transition);
}
.attr("filter", "url(#blur1)");
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 l = path.node().getTotalLength(), dur = (l + 5000) / 2;
const i = d3.interpolateString("0," + l, l + "," + l);
return t => i(t);
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)});
armies.select("#army"+state).transition().duration(dur).style("fill", "#ff0000");
}
function removePath(path) {
path.transition().duration(1000).attr("opacity", 0).remove();
}
function stateHighlightOff() {
function stateHighlightOff(event) {
debug.selectAll(".highlight").each(function() {
d3.select(this).call(removePath);
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
});
const state = +event.target.dataset.id;
armies.select("#army"+state).transition().duration(1000).style("fill", null);
}
function togglePercentageMode() {
if (body.dataset.type === "absolute") {
body.dataset.type = "percentage";
const lines = body.querySelectorAll(":scope > div");
const array = Array.from(lines), cache = [];
const total = function(type) {
if (cache[type]) cache[type];
cache[type] = d3.sum(array.map(el => +el.dataset[type]));
return cache[type];
}
lines.forEach(function(el) {
el.querySelectorAll("div").forEach(function(div) {
const type = div.dataset.type;
if (type === "rate") return;
div.textContent = rn(+el.dataset[type] / total(type) * 100) + "%";
});
});
} else {
body.dataset.type = "absolute";
addLines();
}
}
function militaryCustomize() {

View file

@ -336,10 +336,10 @@ function randomizeOptions() {
// 'Options' settings
if (randomize || !locked("template")) randomizeHeightmapTemplate();
if (randomize || !locked("regions")) regionsInput.value = regionsOutput.value = gauss(15, 3, 2, 30);
if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(40, 20, 20, 100);
if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(20, 10, 20, 100);
if (randomize || !locked("manors")) {manorsInput.value = 1000; manorsOutput.value = "auto";}
if (randomize || !locked("religions")) religionsInput.value = religionsOutput.value = gauss(5, 2, 2, 10);
if (randomize || !locked("power")) powerInput.value = powerOutput.value = gauss(3, 2, 0, 10);
if (randomize || !locked("power")) powerInput.value = powerOutput.value = gauss(4, 2, 0, 10, 2);
if (randomize || !locked("neutral")) neutralInput.value = neutralOutput.value = rn(1 + Math.random(), 1);
if (randomize || !locked("cultures")) culturesInput.value = culturesOutput.value = gauss(12, 3, 5, 30);
if (randomize || !locked("culturesSet")) randomizeCultureSet();

View file

@ -0,0 +1,160 @@
"use strict";
function overviewRegiments(state) {
if (customization) return;
closeDialogs(".stable");
const body = document.getElementById("regimentsBody");
updateFilter();
addLines();
$("#regimentsOverview").dialog();
if (modules.overviewRegiments) return;
modules.overviewRegiments = true;
updateHeaders();
$("#regimentsOverview").dialog({
title: "Regiments Overview", resizable: false, width: fitContent(),
position: {my: "center", at: "center", of: "svg"}
});
// add listeners
document.getElementById("regimentsOverviewRefresh").addEventListener("click", addLines);
document.getElementById("regimentsPercentage").addEventListener("click", togglePercentageMode);
document.getElementById("regimentsAddNew").addEventListener("click", toggleAddRegiment);
document.getElementById("regimentsExport").addEventListener("click", downloadRegimentsData);
document.getElementById("regimentsFilter").addEventListener("change", filterRegiments);
body.addEventListener("click", function(ev) {
const el = ev.target, line = el.parentNode, state = +line.dataset.id;
//if (el.tagName === "SPAN") showRegimentList(state);
});
// update military types in header and tooltips
function updateHeaders() {
const header = document.getElementById("regimentsHeader");
header.querySelectorAll(".removable").forEach(el => el.remove());
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
for (const u of options.military) {
const label = capitalize(u.name.replace(/_/g, ' '));
insert(`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label}&nbsp;</div>`);
}
header.querySelectorAll(".removable").forEach(function(e) {
e.addEventListener("click", function() {sortLines(this);});
});
}
// add line for each state
function addLines() {
body.innerHTML = "";
let lines = "";
const regiments = [];
for (const s of pack.states) {
if (!s.i || s.removed || !s.military.length) continue;
if (state !== -1 && s.i !== state) continue; // specific state is selected
for (const r of s.military) {
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name]||0}`).join(" ");
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name]||0}</div>`).join(" ");
lines += `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
<span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span>
<input data-tip="Regiment's name" style="width:13em" value="${r.name}" readonly>
${lineData}
<div data-type="total" data-tip="Total military personnel (not considering crew)" style="font-weight: bold">${r.a}</div>
<span data-tip="Edit regiment" class="icon-pencil pointer"></span>
</div>`;
regiments.push(r);
}
}
lines += `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div>
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name]||0)))}</div>`).join(" ")}
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
</div>`;
body.insertAdjacentHTML("beforeend", lines);
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
applySorting(regimentsHeader);
// add listeners
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => regimentHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => regimentHighlightOff(ev)));
}
function updateFilter() {
const filter = document.getElementById("regimentsFilter");
filter.options.length = 0; // remove all options
filter.options.add(new Option(`all`, -1, false, state === -1));
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name) ? 1 : -1);
statesSorted.forEach(s => filter.options.add(new Option(s.name, s.i, false, s.i == state)));
}
function filterRegiments() {
state = +this.value;
addLines();
}
function regimentHighlightOn(event) {
const state = +event.target.dataset.s;
const id = +event.target.dataset.id;
if (customization || !state) return;
armies.select(`g > g#regiment${state}-${id}`).transition().duration(2000).style("fill", "#ff0000");
}
function regimentHighlightOff(event) {
const state = +event.target.dataset.s;
const id = +event.target.dataset.id;
armies.select(`g > g#regiment${state}-${id}`).transition().duration(1000).style("fill", null);
}
function togglePercentageMode() {
if (body.dataset.type === "absolute") {
body.dataset.type = "percentage";
const lines = body.querySelectorAll(":scope > div:not(.totalLine)");
const array = Array.from(lines), cache = [];
const total = function(type) {
if (cache[type]) cache[type];
cache[type] = d3.sum(array.map(el => +el.dataset[type]));
return cache[type];
}
lines.forEach(function(el) {
el.querySelectorAll("div").forEach(function(div) {
const type = div.dataset.type;
if (type === "rate") return;
div.textContent = rn(+el.dataset[type] / total(type) * 100) + "%";
});
});
} else {
body.dataset.type = "absolute";
addLines();
}
}
function toggleAddRegiment() {
}
function downloadRegimentsData() {
const units = options.military.map(u => u.name);
let data = "State,Id,Name,"+units.map(u => capitalize(u)).join(",")+",Total\n"; // headers
body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function(el) {
data += el.dataset.state + ",";
data += el.dataset.id + ",";
data += el.dataset.name + ",";
data += units.map(u => el.dataset[u]).join(",") + ",";
data += el.dataset.total + "\n";
});
const name = getFileName("Regiments") + ".csv";
downloadFile(data, name);
}
}

View file

@ -173,30 +173,20 @@ function editStates() {
if (!layerIsOn("toggleStates")) return;
const state = +event.target.dataset.id;
if (customization || !state) return;
const path = statesBody.select("#state"+state).attr("d");
debug.append("path").attr("class", "highlight").attr("d", path)
const d = regions.select("#state"+state).attr("d");
const path = debug.append("path").attr("class", "highlight").attr("d", d)
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
.attr("filter", "url(#blur1)").call(transition);
}
.attr("filter", "url(#blur1)");
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 l = path.node().getTotalLength(), dur = (l + 5000) / 2;
const i = d3.interpolateString("0," + l, l + "," + l);
return t => i(t);
}
function removePath(path) {
path.transition().duration(1000).attr("opacity", 0).remove();
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)});
}
function stateHighlightOff() {
debug.selectAll(".highlight").each(function(el) {
d3.select(this).call(removePath);
function stateHighlightOff(event) {
debug.selectAll(".highlight").each(function() {
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
});
}