This commit is contained in:
Azgaar 2020-03-22 21:50:38 +03:00
parent 8d7f95e2f3
commit cba011282d
11 changed files with 215 additions and 130 deletions

View file

@ -37,6 +37,10 @@ input {
text-indent: 1px; text-indent: 1px;
} }
input:read-only {
cursor: default;
}
textarea { textarea {
padding: 2px; padding: 2px;
text-indent: 1px; text-indent: 1px;
@ -121,6 +125,11 @@ button, select, a, .pointer {
pointer-events: none; pointer-events: none;
} }
#armies text {
pointer-events: none;
user-select: none;
}
#statesBody, #provincesBody, #relig, #biomes, #cults { #statesBody, #provincesBody, #relig, #biomes, #cults {
stroke-width: .6; stroke-width: .6;
fill-rule: evenodd; fill-rule: evenodd;
@ -209,29 +218,6 @@ i.icon-lock {
cursor: pointer; cursor: pointer;
} }
#armies rect {
stroke-width: .3;
stroke: #000;
}
#armies text {
pointer-events: none;
user-select: none;
stroke: none;
fill: #fff;
text-shadow: 0 0 4px #000;
font-size: 6px;
dominant-baseline: central;
text-anchor: middle;
font-family: Helvetica;
}
#regimentBase {
stroke-width: .3;
stroke: #000;
cursor: move;
}
.chartInfo { .chartInfo {
text-align: center; text-align: center;
font-family: sans-serif; font-family: sans-serif;

View file

@ -45,6 +45,20 @@
<body> <body>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="map" width="100%" height="100%"> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="map" width="100%" height="100%">
<defs> <defs>
<style type="text/css">
#armies rect {stroke-width: .3; stroke: #000;}
#armies text {
stroke: none;
fill: #fff;
text-shadow: 0 0 4px #000;
dominant-baseline: central;
text-anchor: middle;
font-family: Helvetica;
fill-opacity: 1;
}
#armies text.regimentIcon {font-size: .8em;}
</style>
<g id="filters"> <g id="filters">
<filter id="blurFilter" x="-1" y="-1" width="100" height="100"> <filter id="blurFilter" x="-1" y="-1" width="100" height="100">
<feGaussianBlur in="SourceGraphic" stdDeviation="0.2"/> <feGaussianBlur in="SourceGraphic" stdDeviation="0.2"/>
@ -890,7 +904,7 @@
<g id="viewbox"></g> <g id="viewbox"></g>
<g id="scaleBar" onclick="editUnits()"></g> <g id="scaleBar" onclick="editUnits()"></g>
<g id="initial" opacity=1> <g id="initial" opacity=1>
<rect x="-1%" y="-1%" width="102%" height="102%" fill="#53679f"></rect> <rect x="-1%" y="-1%" width="102%" height="102%" fill="#466eab"></rect>
<rect x="-1%" y="-1%" width="102%" height="102%" fill="url(#oceanic)"></rect> <rect x="-1%" y="-1%" width="102%" height="102%" fill="url(#oceanic)"></rect>
<use xlink:href="#rose" id="init-rose" x="50%" y="50%"></use> <use xlink:href="#rose" id="init-rose" x="50%" y="50%"></use>
</g> </g>
@ -1188,8 +1202,8 @@
<tr data-tip="Set foreground color. Visible if opacity > 0"> <tr data-tip="Set foreground color. Visible if opacity > 0">
<td>Foreground</td> <td>Foreground</td>
<td> <td>
<input id="styleOceanFore" type="color" value="#53679f"/> <input id="styleOceanFore" type="color" value="#466eab"/>
<output id="styleOceanForeOutput">#53679f</output> <output id="styleOceanForeOutput">#466eab</output>
</td> </td>
</tr> </tr>
@ -2388,6 +2402,12 @@
<i id="regimentNameRestore" data-tip="Click to restore regiment's default name" class="icon-ccw pointer"></i> <i id="regimentNameRestore" data-tip="Click to restore regiment's default name" class="icon-ccw pointer"></i>
</div> </div>
<div data-tip="Regiment emblem. Paste any Unicode symbol or select from the predefined list">
<div class="label italic">Emblem:</div>
<input id="regimentEmblem" style="width:5em">
<button id="regimentEmblemSelect" style="padding: 0; width: 4.5em">select</button>
</div>
<div id="regimentComposition" style="padding: .1em"></div> <div id="regimentComposition" style="padding: .1em"></div>
</div> </div>
@ -3270,7 +3290,6 @@
<button id="militaryOptionsButton" data-tip="Edit Military units" class="icon-cog"></button> <button id="militaryOptionsButton" data-tip="Edit Military units" class="icon-cog"></button>
<button id="militaryOverviewRecalculate" data-tip="Recalculate military forces based on current options" class="icon-retweet"></button> <button id="militaryOverviewRecalculate" data-tip="Recalculate military forces based on current options" class="icon-retweet"></button>
<button id="militaryExport" data-tip="Save military-related data as a text file (.csv)" class="icon-download"></button> <button id="militaryExport" data-tip="Save military-related data as a text file (.csv)" class="icon-download"></button>
<button id="militaryVisualize" data-tip="Show armies on the map" class="icon-user-shield"></button>
</div> </div>
</div> </div>
@ -3283,7 +3302,7 @@
<th data-tip="Conscription percentage for urban population">Urban %</th> <th data-tip="Conscription percentage for urban population">Urban %</th>
<th data-tip="Average number of people in crew">Crew</th> <th data-tip="Average number of people in crew">Crew</th>
<th data-tip="Unit type to apply special rules on forces recalculation">Type</th> <th data-tip="Unit type to apply special rules on forces recalculation">Type</th>
<th data-tip="Check if unit is separate and can be stacked only with units of the same type">S</th> <th data-tip="Check if unit is separate and can be stacked only with units of the same type">Sep.</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View file

@ -304,7 +304,7 @@ function findBurgForMFCG(params) {
// apply default biomes data // apply default biomes data
function applyDefaultBiomesSystem() { function applyDefaultBiomesSystem() {
const name = ["Marine","Hot desert","Cold desert","Savanna","Grassland","Tropical seasonal forest","Temperate deciduous forest","Tropical rainforest","Temperate rainforest","Taiga","Tundra","Glacier","Wetland"]; const name = ["Marine","Hot desert","Cold desert","Savanna","Grassland","Tropical seasonal forest","Temperate deciduous forest","Tropical rainforest","Temperate rainforest","Taiga","Tundra","Glacier","Wetland"];
const color = ["#53679f","#fbe79f","#b5b887","#d2d082","#c8d68f","#b6d95d","#29bc56","#7dcb35","#409c43","#4b6b32","#96784b","#d5e7eb","#0b9131"]; const color = ["#466eab","#fbe79f","#b5b887","#d2d082","#c8d68f","#b6d95d","#29bc56","#7dcb35","#409c43","#4b6b32","#96784b","#d5e7eb","#0b9131"];
const habitability = [0,4,10,22,30,50,100,80,90,12,4,0,12]; const habitability = [0,4,10,22,30,50,100,80,90,12,4,0,12];
const iconsDensity = [0,3,2,120,120,120,120,150,150,100,5,0,150]; const iconsDensity = [0,3,2,120,120,120,120,150,150,100,5,0,150];
const icons = [{},{dune:3, cactus:6, deadTree:1},{dune:9, deadTree:1},{acacia:1, grass:9},{grass:1},{acacia:8, palm:1},{deciduous:1},{acacia:5, palm:3, deciduous:1, swamp:1},{deciduous:6, swamp:1},{conifer:1},{grass:1},{},{swamp:1}]; const icons = [{},{dune:3, cactus:6, deadTree:1},{dune:9, deadTree:1},{acacia:1, grass:9},{grass:1},{acacia:8, palm:1},{deciduous:1},{acacia:5, palm:3, deciduous:1, swamp:1},{deciduous:6, swamp:1},{conifer:1},{grass:1},{},{swamp:1}];

View file

@ -595,13 +595,14 @@
console.timeEnd("assignColors"); console.timeEnd("assignColors");
} }
// generate historical wars // generate historical conflicts of each state
const generateCampaigns = function() { const generateCampaigns = function() {
const wars = {"War":4, "Conflict":2, "Campaign":4, "Invasion":2, "Rebellion":2, "Conquest":2, "Intervention":1, "Expedition":1, "Crusade":1}; const wars = {"War":4, "Conflict":2, "Campaign":4, "Invasion":2, "Rebellion":2, "Conquest":2, "Intervention":1, "Expedition":1, "Crusade":1};
pack.states.forEach(s => { pack.states.forEach(s => {
if (!s.i || s.removed) return; if (!s.i || s.removed) return;
s.campaigns = (s.neighbors||[0]).map(i => { const n = s.neighbors.length ? s.neighbors : [0];
s.campaigns = n.map(i => {
const name = i && P(.8) ? pack.states[i].name : Names.getCultureShort(s.culture); const name = i && P(.8) ? pack.states[i].name : Names.getCultureShort(s.culture);
const start = gauss(options.year-100, 150, 1, options.year-6), end = start + gauss(4, 5, 1, options.year - start - 1); const start = gauss(options.year-100, 150, 1, options.year-6), end = start + gauss(4, 5, 1, options.year - start - 1);
return {name:getAdjective(name) + " " + rw(wars), start, end}; return {name:getAdjective(name) + " " + rw(wars), start, end};

View file

@ -7,7 +7,7 @@
let cells, p, states; let cells, p, states;
const generate = function() { const generate = function() {
console.time("calculateMilitaryForces"); console.time("generateMilitaryForces");
cells = pack.cells, p = cells.p, states = pack.states; cells = pack.cells, p = cells.p, states = pack.states;
const valid = states.filter(s => s.i && !s.removed); // valid states const valid = states.filter(s => s.i && !s.removed); // valid states
@ -142,13 +142,14 @@
} }
} }
armies.selectAll("g").remove(); // clear armies layer
const expected = 3 * populationRate.value; // expected regiment size const expected = 3 * populationRate.value; // expected regiment size
const mergeable = (n, s) => (!n.s && !s.s) || n.u === s.u; const mergeable = (n, s) => (!n.s && !s.s) || n.u === s.u;
// get regiments for each state // get regiments for each state
valid.forEach(s => { valid.forEach(s => {
s.military = createRegiments(s.temp.platoons, s); s.military = createRegiments(s.temp.platoons, s);
delete s.temp; // do not store temp data delete s.temp; // do not store temp data
drawRegiments(s.military, s.i, s.color); drawRegiments(s.military, s.i);
}); });
function createRegiments(nodes, s) { function createRegiments(nodes, s) {
@ -184,35 +185,55 @@
// generate name for regiments // generate name for regiments
regiments.forEach(r => { regiments.forEach(r => {
r.name = getName(r, regiments); r.name = getName(r, regiments);
r.icon = getEmblem(r);
generateNote(r, s); generateNote(r, s);
}); });
return regiments; return regiments;
} }
console.timeEnd("calculateMilitaryForces"); console.timeEnd("generateMilitaryForces");
} }
function drawRegiments(regiments, s, color) { function drawRegiments(regiments, s) {
const size = 3; const size = +armies.attr("data-size");
const army = armies.append("g").attr("id", "army"+s).attr("fill", color); const w = d => d.n ? size * 4 : size * 6;
const g = army.selectAll("g").data(regiments).enter().append("g").attr("id", d => "regiment"+s+"-"+d.i); const h = size * 2;
g.append("rect").attr("data-name", d => d.name).attr("data-state", s).attr("data-id", d => d.i) const x = d => rn(d.x - w(d) / 2, 2);
.attr("x", d => d.n ? d.x-size*2 : d.x-size*3).attr("y", d => d.y-size) const y = d => rn(d.y - size, 2);
.attr("width", d => d.n ? size*4 : size*6).attr("height", size*2);
g.append("text").attr("x", d => d.x).attr("y", d => d.y).text(d => d.a); const baseColor = pack.states[s].color[0] === "#" ? pack.states[s].color : "#999";
const darkerColor = d3.color(baseColor).darker().hex();
const army = armies.append("g").attr("id", "army"+s).attr("fill", baseColor);
const g = army.selectAll("g").data(regiments).enter().append("g")
.attr("id", d => "regiment"+s+"-"+d.i).attr("data-name", d => d.name).attr("data-state", s).attr("data-id", d => d.i);
g.append("rect").attr("x", d => x(d)).attr("y", d => y(d)).attr("width", d => w(d)).attr("height", h);
g.append("text").attr("x", d => d.x).attr("y", d => d.y).text(d => getTotal(d));
g.append("rect").attr("fill", darkerColor).attr("x", d => x(d)-h).attr("y", d => y(d)).attr("width", h).attr("height", h);
g.append("text").attr("class", "regimentIcon").attr("x", d => x(d)-size).attr("y", d => d.y).text(d => d.icon);
} }
const drawRegiment = function(reg, s, x = reg.x, y = reg.y) { const drawRegiment = function(reg, s, x = reg.x, y = reg.y) {
const size = 3; const size = +armies.attr("data-size");
const w = reg.n ? size * 4 : size * 6;
const h = size * 2;
const x1 = rn(x - w / 2, 2);
const y1 = rn(y - size, 2);
const g = armies.select("g#army"+s).append("g").attr("id", "regiment"+s+"-"+reg.i); const army = armies.select("g#army"+s);
g.append("rect").attr("data-name", reg.name).attr("data-state", s).attr("data-id", reg.i) const darkerColor = d3.color(army.attr("fill")).darker().hex();
.attr("x", reg.n ? x-size*2 : x-size*3).attr("y", y-size)
.attr("width", reg.n ? size*4 : size*6).attr("height", size*2); const g = army.append("g").attr("id", "regiment"+s+"-"+reg.i).attr("data-name", reg.name).attr("data-state", s).attr("data-id", reg.i);
g.append("text").attr("x", x).attr("y", y).text(reg.a); g.append("rect").attr("x", x1).attr("y", y1).attr("width", w).attr("height", h);
g.append("text").attr("x", x).attr("y", y).text(getTotal(reg));
g.append("rect").attr("fill", darkerColor).attr("x", x1-h).attr("y", y1).attr("width", h).attr("height", h);
g.append("text").attr("class", "regimentIcon").attr("x", x1-size).attr("y", y).text(reg.icon);
} }
// utilize si function to make regiment total text fit regiment box
const getTotal = reg => reg.a > (reg.n ? 999 : 99999) ? si(reg.a) : reg.a;
const getName = function(r, regiments) { const getName = function(r, regiments) {
const proper = r.n ? null : const proper = r.n ? null :
cells.province[r.cell] ? pack.provinces[cells.province[r.cell]].name : cells.province[r.cell] ? pack.provinces[cells.province[r.cell]].name :
@ -222,6 +243,18 @@
return `${number}${proper?` (${proper}) `:` `}${form}`; return `${number}${proper?` (${proper}) `:` `}${form}`;
} }
// get default regiment emblem
const getEmblem = function(r) {
if (r.n) return "🌊";
if (!Object.values(r.u).length) return "🛡️";
const mainUnit = Object.entries(r.u).sort((a,b) => b[1]-a[1])[0][0];
const type = options.military.find(u => u.name === mainUnit).type;
if (type === "ranged") return "🏹";
if (type === "mounted") return "🐴";
if (type === "machinery") return "💣";
else return "⚔️";
}
const generateNote = function(r, s) { const generateNote = function(r, s) {
const base = cells.burg[r.cell] ? pack.burgs[cells.burg[r.cell]].name : const base = cells.burg[r.cell] ? pack.burgs[cells.burg[r.cell]].name :
cells.province[r.cell] ? pack.provinces[cells.province[r.cell]].fullName : null; cells.province[r.cell] ? pack.provinces[cells.province[r.cell]].fullName : null;
@ -231,11 +264,12 @@
const troops = `\r\n\r\nRegiment composition:\r\n${composition}.`; const troops = `\r\n\r\nRegiment composition:\r\n${composition}.`;
const campaign = ra(s.campaigns); const campaign = ra(s.campaigns);
const year = rand(campaign.start, campaign.end); const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year-100, 150, 1, options.year-6);
const legend = `Regiment was formed in ${year} ${options.era} during the ${campaign.name}. ${station}${troops}`; const conflict = campaign ? ` during the ${campaign.name}` : "";
notes.push({id:`regiment${s.i}-${r.i}`, name:r.name, legend}); const legend = `Regiment was formed in ${year} ${options.era}${conflict}. ${station}${troops}`;
notes.push({id:`regiment${s.i}-${r.i}`, name:`${r.icon} ${r.name}`, legend});
} }
return {generate, getName, generateNote, drawRegiment}; return {generate, getName, generateNote, drawRegiment, getTotal, getEmblem};
}))); })));

View file

@ -6,7 +6,7 @@
// set default options // set default options
const options = {scale: 50, lightness: .7, shadow: .5, sun: {x: 100, y: 600, z: 1000}, rotateMesh: 0, rotateGlobe: .5, const options = {scale: 50, lightness: .7, shadow: .5, sun: {x: 100, y: 600, z: 1000}, rotateMesh: 0, rotateGlobe: .5,
skyColor: "#9ecef5", waterColor: "#53679f", extendedWater: 0, resolution: 2}; skyColor: "#9ecef5", waterColor: "#466eab", extendedWater: 0, resolution: 2};
// set variables // set variables
let Renderer, scene, camera, controls, animationFrame, material, texture, let Renderer, scene, camera, controls, animationFrame, material, texture,

View file

@ -88,7 +88,7 @@ function showMapTooltip(point, e, i, g) {
const land = pack.cells.h[i] >= 20; const land = pack.cells.h[i] >= 20;
// specific elements // specific elements
if (group === "armies") {tip(e.target.dataset.name + ". Click to edit"); return;} if (group === "armies") {tip(e.target.parentNode.dataset.name + ". Click to edit"); return;}
if (group === "rivers") {tip(getRiverName(e.target.id) + "Click to edit"); return;} if (group === "rivers") {tip(getRiverName(e.target.id) + "Click to edit"); return;}
if (group === "routes") {tip("Click to edit the Route"); return;} if (group === "routes") {tip("Click to edit the Route"); return;}
if (group === "terrain") {tip("Click to edit the Relief Icon"); return;} if (group === "terrain") {tip("Click to edit the Relief Icon"); return;}

View file

@ -25,9 +25,8 @@ function overviewMilitary() {
document.getElementById("militaryExport").addEventListener("click", downloadMilitaryData); document.getElementById("militaryExport").addEventListener("click", downloadMilitaryData);
body.addEventListener("change", function(ev) { body.addEventListener("change", function(ev) {
const el = ev.target, line = el.parentNode, state = +line.dataset.id, type = el.dataset.type; const el = ev.target, line = el.parentNode, state = +line.dataset.id;
if (type && type !== "alert") changeForces(state, line, type, +el.value); else changeAlert(state, line, +el.value);
if (type === "alert") changeAlert(state, line, +el.value);
}); });
// update military types in header and tooltips // update military types in header and tooltips
@ -57,7 +56,7 @@ function overviewMilitary() {
const rate = total / population * 100; const rate = total / population * 100;
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" "); const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
const lineData = options.military.map(u => `<input data-type="${u.name}" data-tip="State ${u.name} units number" type="number" min=0 step=1 value="${getForces(u)}">`).join(" "); const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" ");
lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}"> lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
<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> <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>
@ -66,7 +65,7 @@ function overviewMilitary() {
<div data-type="total" data-tip="Total state military personnel (considering crew)"><b>${si(total)}</b></div> <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-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> <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. Modifier to military forces number, depends of political situation" type="number" min=0 step=.01 value="${rn(s.alert, 2)}"> <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)}">
</div>`; </div>`;
} }
body.insertAdjacentHTML("beforeend", lines); body.insertAdjacentHTML("beforeend", lines);
@ -78,30 +77,27 @@ function overviewMilitary() {
applySorting(militaryHeader); applySorting(militaryHeader);
} }
function changeForces(state, line, type, value) {
const s = pack.states[state];
if (!s.military.alert) {tip("Value won't be applied as War Alert is 0. Change Alert value to positive first", false, "error"); return;}
line.dataset[type] = value;
s.military[type] = value / populationRate.value / s.military.alert;
updateTotal(s.military, line);
updateFooter();
}
function changeAlert(state, line, alert) { function changeAlert(state, line, alert) {
const s = pack.states[state]; const s = pack.states[state];
s.military.alert = line.dataset.alert = alert; const dif = s.alert || alert ? alert / s.alert : 0; // modifier
const getForces = u => rn(s.military[u.name] * alert * populationRate.value)||0; s.alert = line.dataset.alert = alert;
options.military.forEach(u => line.dataset[u.name] = line.querySelector(`input[data-type='${u.name}']`).value = getForces(u));
updateTotal(s.military, line);
updateFooter();
}
function updateTotal(m, line) { s.military.forEach(r => {
line.dataset.total = rn(d3.sum(options.military.map(u => (m[u.name]||0) * u.crew)) * m.alert * populationRate.value); Object.keys(r.u).forEach(u => r.u[u] = rn(r.u[u] * dif)); // change units value
line.dataset.rate = line.dataset.total / line.dataset.population * 100; r.a = d3.sum(Object.values(r.u)); // change total
line.querySelector("div[data-type='total']>b").innerHTML = si(line.dataset.total); armies.select(`g>g#regiment${s.i}-${r.i}>text`).text(Military.getTotal(r)); // change icon text
line.querySelector("div[data-type='rate']").innerHTML = rn(line.dataset.rate, 2) + "%"; });
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() { function updateFooter() {
@ -138,7 +134,7 @@ function overviewMilitary() {
} }
function stateHighlightOff() { function stateHighlightOff() {
debug.selectAll(".highlight").each(function(el) { debug.selectAll(".highlight").each(function() {
d3.select(this).call(removePath); d3.select(this).call(removePath);
}); });
} }
@ -159,7 +155,7 @@ function overviewMilitary() {
Cancel: function() {$(this).dialog("close");} Cancel: function() {$(this).dialog("close");}
}, open: function() { }, open: function() {
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button"); 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[0].addEventListener("mousemove", () => tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>"));
buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table")); buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table"));
buttons[2].addEventListener("mousemove", () => tip("Restore default military units and settings")); buttons[2].addEventListener("mousemove", () => tip("Restore default military units and settings"));
buttons[3].addEventListener("mousemove", () => tip("Close the window without saving the changes")); buttons[3].addEventListener("mousemove", () => tip("Close the window without saving the changes"));
@ -201,7 +197,7 @@ function overviewMilitary() {
return {name:name.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, '_'), rural:+rural||0, urban:+urban||0, crew:+crew||0, type, separate:+separate||0}; 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)); localStorage.setItem("military", JSON.stringify(options.military));
calculateMilitaryForces(); Military.generate();
updateHeaders(); updateHeaders();
addLines(); addLines();
} }
@ -209,8 +205,17 @@ function overviewMilitary() {
} }
function militaryRecalculate() { function militaryRecalculate() {
calculateMilitaryForces(); 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(); addLines();
},
Cancel: function() {$(this).dialog("close");}
}
});
} }
function downloadMilitaryData() { function downloadMilitaryData() {
@ -223,7 +228,7 @@ function overviewMilitary() {
data += units.map(u => el.dataset[u]).join(",") + ","; data += units.map(u => el.dataset[u]).join(",") + ",";
data += el.dataset.total + ","; data += el.dataset.total + ",";
data += el.dataset.population + ","; data += el.dataset.population + ",";
data += el.dataset.rate + ","; data += rn(el.dataset.rate,2) + "%,";
data += el.dataset.alert + "\n"; data += el.dataset.alert + "\n";
}); });

View file

@ -6,7 +6,7 @@ function editRegiment() {
armies.selectAll(":scope > g").classed("draggable", true); armies.selectAll(":scope > g").classed("draggable", true);
armies.selectAll(":scope > g > g").call(d3.drag().on("drag", dragRegiment)); armies.selectAll(":scope > g > g").call(d3.drag().on("drag", dragRegiment));
elSelected = d3.event.target; elSelected = d3.event.target.parentElement; // select g element
if (!pack.states[elSelected.dataset.state]) return; if (!pack.states[elSelected.dataset.state]) return;
if (!regiment()) return; if (!regiment()) return;
updateRegimentData(regiment()); updateRegimentData(regiment());
@ -25,6 +25,8 @@ function editRegiment() {
document.getElementById("regimentNameRestore").addEventListener("click", restoreName); document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
document.getElementById("regimentType").addEventListener("click", changeType); document.getElementById("regimentType").addEventListener("click", changeType);
document.getElementById("regimentName").addEventListener("change", changeName); document.getElementById("regimentName").addEventListener("change", changeName);
document.getElementById("regimentEmblem").addEventListener("input", changeEmblem);
document.getElementById("regimentEmblemSelect").addEventListener("click", selectEmblem);
document.getElementById("regimentRegenerateLegend").addEventListener("click", regenerateLegend); document.getElementById("regimentRegenerateLegend").addEventListener("click", regenerateLegend);
document.getElementById("regimentLegend").addEventListener("click", editLegend); document.getElementById("regimentLegend").addEventListener("click", editLegend);
document.getElementById("regimentSplit").addEventListener("click", splitRegiment); document.getElementById("regimentSplit").addEventListener("click", splitRegiment);
@ -40,6 +42,7 @@ function editRegiment() {
function updateRegimentData(regiment) { function updateRegimentData(regiment) {
document.getElementById("regimentType").className = regiment.n ? "icon-anchor" :"icon-users"; document.getElementById("regimentType").className = regiment.n ? "icon-anchor" :"icon-users";
document.getElementById("regimentName").value = regiment.name; document.getElementById("regimentName").value = regiment.name;
document.getElementById("regimentEmblem").value = regiment.icon;
const composition = document.getElementById("regimentComposition"); const composition = document.getElementById("regimentComposition");
composition.innerHTML = options.military.map(u => { composition.innerHTML = options.military.map(u => {
@ -54,12 +57,13 @@ function editRegiment() {
function drawBase() { function drawBase() {
const reg = regiment(); const reg = regiment();
const tr = parseTransform(elSelected.parentNode.getAttribute("transform")); const tr = parseTransform(elSelected.getAttribute("transform"));
const tx = +tr[0], ty = +tr[1]; const tx = +tr[0], ty = +tr[1];
const x2 = +elSelected.nextSibling.getAttribute("x"), y2 = +elSelected.nextSibling.getAttribute("y"); const x2 = +elSelected.querySelector("text").getAttribute("x"), y2 = +elSelected.querySelector("text").getAttribute("y");
const clr = pack.states[elSelected.dataset.state].color; const clr = pack.states[elSelected.dataset.state].color;
const base = viewbox.insert("g", "g#armies").attr("id", "regimentBase"); const base = viewbox.insert("g", "g#armies").attr("id", "regimentBase")
.attr("stroke-width", .3).attr("stroke", "#000").attr("cursor", "move");
base.on("mouseenter", d => {tip("Regiment base. Drag to re-base the regiment", true);}).on("mouseleave", d => {tip('', true);}); base.on("mouseenter", d => {tip("Regiment base. Drag to re-base the regiment", true);}).on("mouseleave", d => {tip('', true);});
base.append("line").attr("x1", reg.x).attr("y1", reg.y).attr("x2", x2+tx).attr("y2", y2+ty).attr("class", "dragLine"); base.append("line").attr("x1", reg.x).attr("y1", reg.y).attr("x2", x2+tx).attr("y2", y2+ty).attr("class", "dragLine");
@ -71,9 +75,15 @@ function editRegiment() {
reg.n = +!reg.n; reg.n = +!reg.n;
document.getElementById("regimentType").className = reg.n ? "icon-anchor" :"icon-users"; document.getElementById("regimentType").className = reg.n ? "icon-anchor" :"icon-users";
const size = 3; const size = +armies.attr("data-size");
elSelected.setAttribute("x", reg.n ? reg.x-size*2 : reg.x-size*3); const baseRect = elSelected.querySelectorAll("rect")[0];
elSelected.setAttribute("width", reg.n ? size*4 : size*6); const iconRect = elSelected.querySelectorAll("rect")[1];
const icon = elSelected.querySelector(".regimentIcon");
const x = reg.n ? reg.x-size*2 : reg.x-size*3;
baseRect.setAttribute("x", x);
baseRect.setAttribute("width", reg.n ? size*4 : size*6);
iconRect.setAttribute("x", x - size*2);
icon.setAttribute("x", x - size);
} }
function changeName() { function changeName() {
@ -86,12 +96,50 @@ function editRegiment() {
elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name; elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name;
} }
function changeEmblem() {
const emblem = document.getElementById("regimentEmblem").value;
regiment().icon = elSelected.querySelector(".regimentIcon").innerHTML = emblem;
}
function selectEmblem() {
const emblems = ["⚔️","🏹","🐴","💣","🌊","🎯","⚓","🔮","📯","🛡️","👑",
"☠️","🎆","🗡️","⛏️","🔥","🐾","🎪","🏰","⚜️","⛓️","❤️","📜","🔱","🌈","🌠","💥","☀️","🍀",
"🔰","🕸️","⚗️","☣️","☢️","🎖️","⚕️","☸️","✡️","🚩","🏳️","🏴","🌈","💪","✊","👊","🤜","🤝","🙏","🧙","💂","🤴","🧛","🧟","🧞","🧝",
"🦄","🐲","🐉","🐎","🦓","🐺","🦊","🐱","🐈","🦁","🐯","🐅","🐆","🐕","🦌","🐵","🐒","🦍",
"🦅","🕊️","🐓","🦇","🐦","🦉","🐮","🐄","🐂","🐃","🐷","🐖","🐗","🐏","🐑","🐐","🐫","🦒","🐘","🦏",
"🐭","🐁","🐀","🐹","🐰","🐇","🦔","🐸","🐊","🐢","🦎","🐍","🐳","🐬","🦈","🐙","🦑","🐌","🦋","🐜","🐝","🐞","🦗","🕷️","🦂","🦀"];
alertMessage.innerHTML = "";
const container = document.createElement("div");
container.style.userSelect = "none";
container.style.cursor = "pointer";
container.style.fontSize = "2em";
container.style.width = "47vw";
container.innerHTML = emblems.map(i => `<span>${i}</span>`).join("");
container.addEventListener("mouseover", e => showTip(e), false);
container.addEventListener("click", e => clickEmblem(e), false);
alertMessage.appendChild(container);
$("#alert").dialog({resizable: false, width: fitContent(), title: "Select emblem"});
function showTip(e) {
if (e.target.tagName !== "SPAN") return;
tip(`Click to select ${e.target.innerHTML} emblem`);
}
function clickEmblem(e) {
if (e.target.tagName !== "SPAN") return;
document.getElementById("regimentEmblem").value = e.target.innerHTML;
changeEmblem();
}
}
function changeUnit() { function changeUnit() {
const u = this.dataset.u; const u = this.dataset.u;
const reg = regiment(); const reg = regiment();
reg.u[u] = (+this.value)||0; reg.u[u] = (+this.value)||0;
reg.a = d3.sum(Object.values(reg.u)); reg.a = d3.sum(Object.values(reg.u));
elSelected.nextSibling.innerHTML = reg.a; elSelected.querySelector("text").innerHTML = Military.getTotal(reg);
if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click(); if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click();
} }
@ -105,11 +153,11 @@ function editRegiment() {
reg.a = d3.sum(Object.values(u1)); // old reg total reg.a = d3.sum(Object.values(u1)); // old reg total
const a = d3.sum(Object.values(u2)); // new reg total const a = d3.sum(Object.values(u2)); // new reg total
const newReg = {a, cell:reg.cell, i, n:reg.n, u:u2, x:reg.x, y:reg.y}; const newReg = {a, cell:reg.cell, i, n:reg.n, u:u2, x:reg.x, y:reg.y, icon: reg.icon};
newReg.name = Military.getName(newReg, military); newReg.name = Military.getName(newReg, military);
military.push(newReg); military.push(newReg);
elSelected.parentNode.remove(); // undraw old reg elSelected.remove(); // undraw old reg
Military.drawRegiment(reg, state, reg.x, reg.y-6); // draw old reg above Military.drawRegiment(reg, state, reg.x, reg.y-6); // draw old reg above
Military.drawRegiment(newReg, state, reg.x, reg.y+6); // draw new reg below Military.drawRegiment(newReg, state, reg.x, reg.y+6); // draw new reg below
@ -134,7 +182,7 @@ function editRegiment() {
const state = elSelected.dataset.state, military = pack.states[state].military; const state = elSelected.dataset.state, military = pack.states[state].military;
const i = military.length ? last(military).i + 1 : 0; const i = military.length ? last(military).i + 1 : 0;
const n = +(pack.cells.h[cell] < 20); // naval or land const n = +(pack.cells.h[cell] < 20); // naval or land
const reg = {a:0, cell, i, n, u:{}, x, y}; const reg = {a:0, cell, i, n, u:{}, x, y, icon:"🛡️"};
reg.name = Military.getName(reg, military); reg.name = Military.getName(reg, military);
military.push(reg); military.push(reg);
Military.drawRegiment(reg, state); Military.drawRegiment(reg, state);
@ -155,45 +203,35 @@ function editRegiment() {
} }
function attachRegimentOnClick() { function attachRegimentOnClick() {
const target = d3.event.target, army = target.parentElement.parentElement; const target = d3.event.target, regSelected = target.parentElement, army = regSelected.parentElement;
const oldState = +elSelected.dataset.state, newState = +regSelected.dataset.state;
if (army.parentElement.id !== "armies") { if (army.parentElement.id !== "armies") {tip("Please click on a regiment", false, "error"); return;}
tip("Please click on a regiment", false, "error"); if (regSelected === elSelected) {tip("Cannot attach regiment to itself. Please click on another regiment", false, "error"); return;}
return; //if (army !== elSelected.parentElement) {tip("Cannot attach to a regiment of other state", false, "error"); return;};
}
if (target === elSelected) {
tip("Cannot attach regiment to itself. Please click on another regiment", false, "error");
return;
}
if (army !== elSelected.parentElement.parentElement) {
tip("Cannot attach this regiment to regiment of other state", false, "error");
return;
};
const reg = regiment(); // reg to be attached const reg = regiment(); // reg to be attached
const sel = pack.states[target.dataset.state].military.find(r => r.i == target.dataset.id); // reg to attach to const sel = pack.states[newState].military.find(r => r.i == regSelected.dataset.id); // reg to attach to
for (const unit of options.military) { for (const unit of options.military) {
const u = unit.name; const u = unit.name;
if (reg.u[u]) sel.u[u] ? sel.u[u] += reg.u[u] : sel.u[u] = reg.u[u]; if (reg.u[u]) sel.u[u] ? sel.u[u] += reg.u[u] : sel.u[u] = reg.u[u];
} }
sel.a = d3.sum(Object.values(sel.u)); // reg total sel.a = d3.sum(Object.values(sel.u)); // reg total
target.nextSibling.innerHTML = sel.a; // update selected reg total text regSelected.querySelector("text").innerHTML = Military.getTotal(sel); // update selected reg total text
// remove attached regiment // remove attached regiment
const military = pack.states[elSelected.dataset.state].military; const military = pack.states[oldState].military;
military.splice(military.indexOf(reg), 1); military.splice(military.indexOf(reg), 1);
const index = notes.findIndex(n => n.id === elSelected.parentNode.id); const index = notes.findIndex(n => n.id === elSelected.id);
if (index != -1) notes.splice(index, 1); if (index != -1) notes.splice(index, 1);
elSelected.parentNode.remove(); elSelected.remove();
$("#regimentEditor").dialog("close"); $("#regimentEditor").dialog("close");
} }
function regenerateLegend() { function regenerateLegend() {
const index = notes.findIndex(n => n.id === elSelected.parentNode.id); const index = notes.findIndex(n => n.id === elSelected.id);
if (index != -1) notes.splice(index, 1); if (index != -1) notes.splice(index, 1);
const s = pack.states[elSelected.dataset.state]; const s = pack.states[elSelected.dataset.state];
@ -201,7 +239,7 @@ function editRegiment() {
} }
function editLegend() { function editLegend() {
editNotes(elSelected.parentNode.id, regiment().name); editNotes(elSelected.id, regiment().name);
} }
function removeRegiment() { function removeRegiment() {
@ -215,9 +253,9 @@ function editRegiment() {
if (regIndex === -1) return; if (regIndex === -1) return;
military.splice(regIndex, 1); military.splice(regIndex, 1);
const index = notes.findIndex(n => n.id === elSelected.parentNode.id); const index = notes.findIndex(n => n.id === elSelected.id);
if (index != -1) notes.splice(index, 1); if (index != -1) notes.splice(index, 1);
elSelected.parentNode.remove(); elSelected.remove();
if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click(); if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click();
$("#regimentEditor").dialog("close"); $("#regimentEditor").dialog("close");
@ -233,10 +271,10 @@ function editRegiment() {
d3.select(this).raise(); d3.select(this).raise();
d3.select(this.parentNode).raise(); d3.select(this.parentNode).raise();
const self = elSelected.parentNode === this; const self = elSelected === this;
const baseLine = viewbox.select("g#regimentBase > line"); const baseLine = viewbox.select("g#regimentBase > line");
const x2 = +elSelected.nextSibling.getAttribute("x"); const text = elSelected.querySelector("text");
const y2 = +elSelected.nextSibling.getAttribute("y"); const x2 = +text.getAttribute("x"), y2 = +text.getAttribute("y");
d3.event.on("drag", function() { d3.event.on("drag", function() {
const x = dx + d3.event.x, y = dy + d3.event.y; const x = dx + d3.event.x, y = dy + d3.event.y;

View file

@ -693,6 +693,8 @@ function addDefaulsStyles() {
// set default style // set default style
function applyDefaultStyle() { function applyDefaultStyle() {
armies.attr("font-size", 6).attr("data-size", 3);
biomes.attr("opacity", null).attr("filter", null).attr("mask", null); biomes.attr("opacity", null).attr("filter", null).attr("mask", null);
stateBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt").attr("filter", null); stateBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt").attr("filter", null);
provinceBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .2).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt").attr("filter", null); provinceBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .2).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt").attr("filter", null);
@ -742,7 +744,7 @@ function applyDefaultStyle() {
// ocean and svg default style // ocean and svg default style
svg.attr("background-color", "#000000").attr("data-filter", null).attr("filter", null); svg.attr("background-color", "#000000").attr("data-filter", null).attr("filter", null);
ocean.attr("opacity", null); ocean.attr("opacity", null);
oceanLayers.select("rect").attr("fill", "#53679f"); oceanLayers.select("rect").attr("fill", "#466eab"); // old color #53679f
oceanLayers.attr("filter", null).attr("layers", "-6,-3,-1"); oceanLayers.attr("filter", null).attr("layers", "-6,-3,-1");
oceanPattern.attr("opacity", null); oceanPattern.attr("opacity", null);
svg.select("#oceanicPattern").attr("filter", "url(#pattern1)"); svg.select("#oceanicPattern").attr("filter", "url(#pattern1)");

View file

@ -250,11 +250,11 @@ function round(s, d = 1) {
// corvent number to short string with SI postfix // corvent number to short string with SI postfix
function si(n) { function si(n) {
if (n >= 1e9) {return rn(n / 1e9, 1) + "B";} if (n >= 1e9) return rn(n / 1e9, 1) + "B";
if (n >= 1e8) {return rn(n / 1e6) + "M";} if (n >= 1e8) return rn(n / 1e6) + "M";
if (n >= 1e6) {return rn(n / 1e6, 1) + "M";} if (n >= 1e6) return rn(n / 1e6, 1) + "M";
if (n >= 1e4) {return rn(n / 1e3) + "K";} if (n >= 1e4) return rn(n / 1e3) + "K";
if (n >= 1e3) {return rn(n / 1e3, 1) + "K";} if (n >= 1e3) return rn(n / 1e3, 1) + "K";
return rn(n); return rn(n);
} }