mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51:24 +01:00
Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into dev-economics
This commit is contained in:
commit
7dc71a5616
33 changed files with 5797 additions and 2941 deletions
|
|
@ -1,6 +1,5 @@
|
|||
"use strict";
|
||||
class Battle {
|
||||
|
||||
constructor(attacker, defender) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
|
|
@ -11,8 +10,8 @@ class Battle {
|
|||
this.x = defender.x;
|
||||
this.y = defender.y;
|
||||
this.cell = findCell(this.x, this.y);
|
||||
this.attackers = {regiments:[], distances:[], morale:100, casualties:0, power:0};
|
||||
this.defenders = {regiments:[], distances:[], morale:100, casualties:0, power:0};
|
||||
this.attackers = {regiments: [], distances: [], morale: 100, casualties: 0, power: 0};
|
||||
this.defenders = {regiments: [], distances: [], morale: 100, casualties: 0, power: 0};
|
||||
|
||||
this.addHeaders();
|
||||
this.addRegiment("attackers", attacker);
|
||||
|
|
@ -26,7 +25,9 @@ class Battle {
|
|||
this.getInitialMorale();
|
||||
|
||||
$("#battleScreen").dialog({
|
||||
title: this.name, resizable: false, width: fitContent(),
|
||||
title: this.name,
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: "center", at: "center", of: "#map"},
|
||||
close: () => Battle.prototype.context.cancelResults()
|
||||
});
|
||||
|
|
@ -38,7 +39,7 @@ class Battle {
|
|||
document.getElementById("battleType").addEventListener("click", ev => this.toggleChange(ev));
|
||||
document.getElementById("battleType").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changeType(ev));
|
||||
document.getElementById("battleNameShow").addEventListener("click", () => Battle.prototype.context.showNameSection());
|
||||
document.getElementById("battleNamePlace").addEventListener("change", ev => Battle.prototype.context.place = ev.target.value);
|
||||
document.getElementById("battleNamePlace").addEventListener("change", ev => (Battle.prototype.context.place = ev.target.value));
|
||||
document.getElementById("battleNameFull").addEventListener("change", ev => Battle.prototype.context.changeName(ev));
|
||||
document.getElementById("battleNameCulture").addEventListener("click", () => Battle.prototype.context.generateName("culture"));
|
||||
document.getElementById("battleNameRandom").addEventListener("click", () => Battle.prototype.context.generateName("random"));
|
||||
|
|
@ -69,9 +70,9 @@ class Battle {
|
|||
if (typesA.every(t => t === "aviation") && typesD.every(t => t === "aviation")) return "air"; // if attackers and defender have only aviation units
|
||||
if (attacker.n && !defender.n && typesA.some(t => t !== "naval")) return "landing"; // if attacked is naval with non-naval units and defender is not naval
|
||||
if (!defender.n && pack.burgs[pack.cells.burg[this.cell]].walls) return "siege"; // defender is in walled town
|
||||
if (P(.1) && [5,6,7,8,9,12].includes(pack.cells.biome[this.cell])) return "ambush"; // 20% if defenders are in forest or marshes
|
||||
if (P(0.1) && [5, 6, 7, 8, 9, 12].includes(pack.cells.biome[this.cell])) return "ambush"; // 20% if defenders are in forest or marshes
|
||||
return "field";
|
||||
}
|
||||
};
|
||||
|
||||
this.type = getType();
|
||||
this.setType();
|
||||
|
|
@ -80,9 +81,9 @@ class Battle {
|
|||
setType() {
|
||||
document.getElementById("battleType").className = "icon-button-" + this.type;
|
||||
|
||||
const sideSpecific = document.getElementById("battlePhases_"+this.type+"_attackers");
|
||||
const attackers = sideSpecific ? sideSpecific.content : document.getElementById("battlePhases_"+this.type).content;
|
||||
const defenders = sideSpecific ? document.getElementById("battlePhases_"+this.type+"_defenders").content : attackers;
|
||||
const sideSpecific = document.getElementById("battlePhases_" + this.type + "_attackers");
|
||||
const attackers = sideSpecific ? sideSpecific.content : document.getElementById("battlePhases_" + this.type).content;
|
||||
const defenders = sideSpecific ? document.getElementById("battlePhases_" + this.type + "_defenders").content : attackers;
|
||||
|
||||
document.getElementById("battlePhase_attackers").nextElementSibling.innerHTML = "";
|
||||
document.getElementById("battlePhase_defenders").nextElementSibling.innerHTML = "";
|
||||
|
|
@ -91,9 +92,13 @@ class Battle {
|
|||
}
|
||||
|
||||
definePlace() {
|
||||
const cells = pack.cells, i = this.cell;
|
||||
const cells = pack.cells,
|
||||
i = this.cell;
|
||||
const burg = cells.burg[i] ? pack.burgs[cells.burg[i]].name : null;
|
||||
const getRiver = i => {const river = pack.rivers.find(r => r.i === i); return river.name + " " + river.type};
|
||||
const getRiver = i => {
|
||||
const river = pack.rivers.find(r => r.i === i);
|
||||
return river.name + " " + river.type;
|
||||
};
|
||||
const river = !burg && cells.r[i] ? getRiver(cells.r[i]) : null;
|
||||
const proper = burg || river ? null : Names.getCulture(cells.culture[this.cell]);
|
||||
return burg ? burg : river ? river : proper;
|
||||
|
|
@ -102,10 +107,10 @@ class Battle {
|
|||
defineName() {
|
||||
if (this.type === "field") return "Battle of " + this.place;
|
||||
if (this.type === "naval") return "Naval Battle of " + this.place;
|
||||
if (this.type === "siege") return "Siege of "+ this.place;
|
||||
if (this.type === "siege") return "Siege of " + this.place;
|
||||
if (this.type === "ambush") return this.place + " Ambush";
|
||||
if (this.type === "landing") return this.place + " Landing";
|
||||
if (this.type === "air") return `${this.place} ${P(.8) ? "Air Battle" : "Dogfight"}`;
|
||||
if (this.type === "air") return `${this.place} ${P(0.8) ? "Air Battle" : "Dogfight"}`;
|
||||
}
|
||||
|
||||
getTypeName() {
|
||||
|
|
@ -121,7 +126,7 @@ class Battle {
|
|||
let headers = "<thead><tr><th></th><th></th>";
|
||||
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
const label = capitalize(u.name.replace(/_/g, " "));
|
||||
headers += `<th data-tip="${label}">${u.icon}</th>`;
|
||||
}
|
||||
|
||||
|
|
@ -130,11 +135,11 @@ class Battle {
|
|||
}
|
||||
|
||||
addRegiment(side, regiment) {
|
||||
regiment.casualties = Object.keys(regiment.u).reduce((a,b) => (a[b]=0,a), {});
|
||||
regiment.casualties = Object.keys(regiment.u).reduce((a, b) => ((a[b] = 0), a), {});
|
||||
regiment.survivors = Object.assign({}, regiment.u);
|
||||
|
||||
const state = pack.states[regiment.state];
|
||||
const distance = Math.hypot(this.y-regiment.by, this.x-regiment.bx) * distanceScaleInput.value | 0; // distance between regiment and its base
|
||||
const distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScaleInput.value) | 0; // distance between regiment and its base
|
||||
const color = state.color[0] === "#" ? state.color : "#999";
|
||||
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect"></rect>
|
||||
|
|
@ -146,14 +151,14 @@ class Battle {
|
|||
let survivors = `<tr class="battleSurvivors"><td></td><td data-tip="Supply line length, affects morale">Distance to base: ${distance} ${distanceUnitInput.value}</td>`;
|
||||
|
||||
for (const u of options.military) {
|
||||
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.u[u.name]||0}</td>`;
|
||||
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.u[u.name] || 0}</td>`;
|
||||
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td>`;
|
||||
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${regiment.u[u.name]||0}</td>`;
|
||||
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${regiment.u[u.name] || 0}</td>`;
|
||||
}
|
||||
|
||||
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.a||0}</td></tr>`;
|
||||
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.a || 0}</td></tr>`;
|
||||
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td></tr>`;
|
||||
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${regiment.a||0}</td></tr>`;
|
||||
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${regiment.a || 0}</td></tr>`;
|
||||
|
||||
const div = side === "attackers" ? battleAttackers : battleDefenders;
|
||||
div.innerHTML += body + initial + casualties + survivors + "</tbody>";
|
||||
|
|
@ -164,13 +169,19 @@ class Battle {
|
|||
addSide() {
|
||||
const body = document.getElementById("regimentSelectorBody");
|
||||
const context = Battle.prototype.context;
|
||||
const regiments = pack.states.filter(s => s.military && !s.removed).map(s => s.military).flat();
|
||||
const distance = reg => rn(Math.hypot(context.y-reg.y, context.x-reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const regiments = pack.states
|
||||
.filter(s => s.military && !s.removed)
|
||||
.map(s => s.military)
|
||||
.flat();
|
||||
const distance = reg => rn(Math.hypot(context.y - reg.y, context.x - reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const isAdded = reg => context.defenders.regiments.some(r => r === reg) || context.attackers.regiments.some(r => r === reg);
|
||||
|
||||
body.innerHTML = regiments.map(r => {
|
||||
const s = pack.states[r.state], added = isAdded(r), dist = added ? "0 " + distanceUnitInput.value : distance(r);
|
||||
return `<div ${added ? "class='inactive'" : ""} data-s=${s.i} data-i=${r.i} data-state=${s.name} data-regiment=${r.name}
|
||||
body.innerHTML = regiments
|
||||
.map(r => {
|
||||
const s = pack.states[r.state],
|
||||
added = isAdded(r),
|
||||
dist = added ? "0 " + distanceUnitInput.value : distance(r);
|
||||
return `<div ${added ? "class='inactive'" : ""} data-s=${s.i} data-i=${r.i} data-state=${s.name} data-regiment=${r.name}
|
||||
data-total=${r.a} data-distance=${dist} data-tip="Click to select regiment">
|
||||
<svg width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
|
||||
<div style="width:6em">${s.name.slice(0, 11)}</div>
|
||||
|
|
@ -179,11 +190,15 @@ class Battle {
|
|||
<div style="width:4em">${r.a}</div>
|
||||
<div style="width:4em">${dist}</div>
|
||||
</div>`;
|
||||
}).join("");
|
||||
})
|
||||
.join("");
|
||||
|
||||
$("#regimentSelectorScreen").dialog({
|
||||
resizable: false, width: fitContent(), title: "Add regiment to the battle",
|
||||
position: {my: "left center", at: "right+10 center", of: "#battleScreen"}, close: addSideClosed,
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
title: "Add regiment to the battle",
|
||||
position: {my: "left center", at: "right+10 center", of: "#battleScreen"},
|
||||
close: addSideClosed,
|
||||
buttons: {
|
||||
"Add to attackers": () => addSideClicked("attackers"),
|
||||
"Add to defenders": () => addSideClicked("defenders"),
|
||||
|
|
@ -195,13 +210,19 @@ class Battle {
|
|||
body.addEventListener("click", selectLine);
|
||||
|
||||
function selectLine(ev) {
|
||||
if (ev.target.className === "inactive") {tip("Regiment is already in the battle", false, "error"); return};
|
||||
if (ev.target.className === "inactive") {
|
||||
tip("Regiment is already in the battle", false, "error");
|
||||
return;
|
||||
}
|
||||
ev.target.classList.toggle("selected");
|
||||
}
|
||||
|
||||
function addSideClicked(side) {
|
||||
const selected = body.querySelectorAll(".selected");
|
||||
if (!selected.length) {tip("Please select a regiment first", false, "error"); return}
|
||||
if (!selected.length) {
|
||||
tip("Please select a regiment first", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
$("#regimentSelectorScreen").dialog("close");
|
||||
selected.forEach(line => {
|
||||
|
|
@ -212,8 +233,9 @@ class Battle {
|
|||
Battle.prototype.getInitialMorale.call(context);
|
||||
|
||||
// move regiment
|
||||
const defenders = context.defenders.regiments, attackers = context.attackers.regiments;
|
||||
const shift = side === "attackers" ? attackers.length * -8 : (defenders.length-1) * 8;
|
||||
const defenders = context.defenders.regiments,
|
||||
attackers = context.attackers.regiments;
|
||||
const shift = side === "attackers" ? attackers.length * -8 : (defenders.length - 1) * 8;
|
||||
regiment.px = regiment.x;
|
||||
regiment.py = regiment.y;
|
||||
Military.moveRegiment(regiment, defenders[0].x, defenders[0].y + shift);
|
||||
|
|
@ -227,7 +249,7 @@ class Battle {
|
|||
}
|
||||
|
||||
showNameSection() {
|
||||
document.querySelectorAll("#battleBottom > button").forEach(el => el.style.display = "none");
|
||||
document.querySelectorAll("#battleBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("battleNameSection").style.display = "inline-block";
|
||||
|
||||
document.getElementById("battleNamePlace").value = this.place;
|
||||
|
|
@ -235,22 +257,20 @@ class Battle {
|
|||
}
|
||||
|
||||
hideNameSection() {
|
||||
document.querySelectorAll("#battleBottom > button").forEach(el => el.style.display = "inline-block");
|
||||
document.querySelectorAll("#battleBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("battleNameSection").style.display = "none";
|
||||
}
|
||||
|
||||
changeName(ev) {
|
||||
this.name = ev.target.value;
|
||||
$("#battleScreen").dialog({"title":this.name});
|
||||
$("#battleScreen").dialog({title: this.name});
|
||||
}
|
||||
|
||||
generateName(type) {
|
||||
const place = type === "culture"
|
||||
? Names.getCulture(pack.cells.culture[this.cell], null, null, "")
|
||||
: Names.getBase(rand(nameBases.length-1));
|
||||
const place = type === "culture" ? Names.getCulture(pack.cells.culture[this.cell], null, null, "") : Names.getBase(rand(nameBases.length - 1));
|
||||
document.getElementById("battleNamePlace").value = this.place = place;
|
||||
document.getElementById("battleNameFull").value = this.name = this.defineName();
|
||||
$("#battleScreen").dialog({"title":this.name});
|
||||
$("#battleScreen").dialog({title: this.name});
|
||||
}
|
||||
|
||||
getJoinedForces(regiments) {
|
||||
|
|
@ -266,47 +286,47 @@ class Battle {
|
|||
calculateStrength(side) {
|
||||
const scheme = {
|
||||
// field battle phases
|
||||
"skirmish": {"melee":.2, "ranged":2.4, "mounted":.1, "machinery":3, "naval":1, "armored":.2, "aviation":1.8, "magical":1.8}, // ranged excel
|
||||
"melee": {"melee":2, "ranged":1.2, "mounted":1.5, "machinery":.5, "naval":.2, "armored":2, "aviation":.8, "magical":.8}, // melee excel
|
||||
"pursue": {"melee":1, "ranged":1, "mounted":4, "machinery":.05, "naval":1, "armored":1, "aviation":1.5, "magical":.6}, // mounted excel
|
||||
"retreat": {"melee":.1, "ranged":.01, "mounted":.5, "machinery":.01, "naval":.2, "armored":.1, "aviation":.8, "magical":.05}, // reduced
|
||||
skirmish: {melee: 0.2, ranged: 2.4, mounted: 0.1, machinery: 3, naval: 1, armored: 0.2, aviation: 1.8, magical: 1.8}, // ranged excel
|
||||
melee: {melee: 2, ranged: 1.2, mounted: 1.5, machinery: 0.5, naval: 0.2, armored: 2, aviation: 0.8, magical: 0.8}, // melee excel
|
||||
pursue: {melee: 1, ranged: 1, mounted: 4, machinery: 0.05, naval: 1, armored: 1, aviation: 1.5, magical: 0.6}, // mounted excel
|
||||
retreat: {melee: 0.1, ranged: 0.01, mounted: 0.5, machinery: 0.01, naval: 0.2, armored: 0.1, aviation: 0.8, magical: 0.05}, // reduced
|
||||
|
||||
// naval battle phases
|
||||
"shelling": {"melee":0, "ranged":.2, "mounted":0, "machinery":2, "naval":2, "armored":0, "aviation":.1, "magical":.5}, // naval and machinery excel
|
||||
"boarding": {"melee":1, "ranged":.5, "mounted":.5, "machinery":0, "naval":.5, "armored":.4, "aviation":0, "magical":.2}, // melee excel
|
||||
"chase": {"melee":0, "ranged":.15, "mounted":0, "machinery":1, "naval":1, "armored":0, "aviation":.15, "magical":.5}, // reduced
|
||||
"withdrawal": {"melee":0, "ranged":.02, "mounted":0, "machinery":.5, "naval":.1, "armored":0, "aviation":.1, "magical":.3}, // reduced
|
||||
shelling: {melee: 0, ranged: 0.2, mounted: 0, machinery: 2, naval: 2, armored: 0, aviation: 0.1, magical: 0.5}, // naval and machinery excel
|
||||
boarding: {melee: 1, ranged: 0.5, mounted: 0.5, machinery: 0, naval: 0.5, armored: 0.4, aviation: 0, magical: 0.2}, // melee excel
|
||||
chase: {melee: 0, ranged: 0.15, mounted: 0, machinery: 1, naval: 1, armored: 0, aviation: 0.15, magical: 0.5}, // reduced
|
||||
withdrawal: {melee: 0, ranged: 0.02, mounted: 0, machinery: 0.5, naval: 0.1, armored: 0, aviation: 0.1, magical: 0.3}, // reduced
|
||||
|
||||
// siege phases
|
||||
"blockade": {"melee":.25, "ranged":.25, "mounted":.2, "machinery":.5, "naval":.2, "armored":.1, "aviation":.25, "magical":.25}, // no active actions
|
||||
"sheltering": {"melee":.3, "ranged":.5, "mounted":.2, "machinery":.5, "naval":.2, "armored":.1, "aviation":.25, "magical":.25}, // no active actions
|
||||
"sortie": {"melee":2, "ranged":.5, "mounted":1.2, "machinery":.2, "naval":.1, "armored":.5, "aviation":1, "magical":1}, // melee excel
|
||||
"bombardment": {"melee":.2, "ranged":.5, "mounted":.2, "machinery":3, "naval":1, "armored":.5, "aviation":1, "magical":1}, // machinery excel
|
||||
"storming": {"melee":1, "ranged":.6, "mounted":.5, "machinery":1, "naval":.1, "armored":.1, "aviation":.5, "magical":.5}, // melee excel
|
||||
"defense": {"melee":2, "ranged":3, "mounted":1, "machinery":1, "naval":.1, "armored":1, "aviation":.5, "magical":1}, // ranged excel
|
||||
"looting": {"melee":1.6, "ranged":1.6, "mounted":.5, "machinery":.2, "naval":.02, "armored":.2, "aviation":.1, "magical":.3}, // melee excel
|
||||
"surrendering": {"melee":.1, "ranged":.1, "mounted":.05, "machinery":.01, "naval":.01, "armored":.02, "aviation":.01, "magical":.03}, // reduced
|
||||
blockade: {melee: 0.25, ranged: 0.25, mounted: 0.2, machinery: 0.5, naval: 0.2, armored: 0.1, aviation: 0.25, magical: 0.25}, // no active actions
|
||||
sheltering: {melee: 0.3, ranged: 0.5, mounted: 0.2, machinery: 0.5, naval: 0.2, armored: 0.1, aviation: 0.25, magical: 0.25}, // no active actions
|
||||
sortie: {melee: 2, ranged: 0.5, mounted: 1.2, machinery: 0.2, naval: 0.1, armored: 0.5, aviation: 1, magical: 1}, // melee excel
|
||||
bombardment: {melee: 0.2, ranged: 0.5, mounted: 0.2, machinery: 3, naval: 1, armored: 0.5, aviation: 1, magical: 1}, // machinery excel
|
||||
storming: {melee: 1, ranged: 0.6, mounted: 0.5, machinery: 1, naval: 0.1, armored: 0.1, aviation: 0.5, magical: 0.5}, // melee excel
|
||||
defense: {melee: 2, ranged: 3, mounted: 1, machinery: 1, naval: 0.1, armored: 1, aviation: 0.5, magical: 1}, // ranged excel
|
||||
looting: {melee: 1.6, ranged: 1.6, mounted: 0.5, machinery: 0.2, naval: 0.02, armored: 0.2, aviation: 0.1, magical: 0.3}, // melee excel
|
||||
surrendering: {melee: 0.1, ranged: 0.1, mounted: 0.05, machinery: 0.01, naval: 0.01, armored: 0.02, aviation: 0.01, magical: 0.03}, // reduced
|
||||
|
||||
// ambush phases
|
||||
"surprise": {"melee":2, "ranged":2.4, "mounted":1, "machinery":1, "naval":1, "armored":1, "aviation":.8, "magical":1.2}, // increased
|
||||
"shock": {"melee":.5, "ranged":.5, "mounted":.5, "machinery":.4, "naval":.3, "armored":.1, "aviation":.4, "magical":.5}, // reduced
|
||||
surprise: {melee: 2, ranged: 2.4, mounted: 1, machinery: 1, naval: 1, armored: 1, aviation: 0.8, magical: 1.2}, // increased
|
||||
shock: {melee: 0.5, ranged: 0.5, mounted: 0.5, machinery: 0.4, naval: 0.3, armored: 0.1, aviation: 0.4, magical: 0.5}, // reduced
|
||||
|
||||
// langing phases
|
||||
"landing": {"melee":.8, "ranged":.6, "mounted":.6, "machinery":.5, "naval":.5, "armored":.5, "aviation":.5, "magical":.6}, // reduced
|
||||
"flee": {"melee":.1, "ranged":.01, "mounted":.5, "machinery":.01, "naval":.5, "armored":.1, "aviation":.2, "magical":.05}, // reduced
|
||||
"waiting": {"melee":.05, "ranged":.5, "mounted":.05, "machinery":.5, "naval":2, "armored":.05, "aviation":.5, "magical":.5}, // reduced
|
||||
landing: {melee: 0.8, ranged: 0.6, mounted: 0.6, machinery: 0.5, naval: 0.5, armored: 0.5, aviation: 0.5, magical: 0.6}, // reduced
|
||||
flee: {melee: 0.1, ranged: 0.01, mounted: 0.5, machinery: 0.01, naval: 0.5, armored: 0.1, aviation: 0.2, magical: 0.05}, // reduced
|
||||
waiting: {melee: 0.05, ranged: 0.5, mounted: 0.05, machinery: 0.5, naval: 2, armored: 0.05, aviation: 0.5, magical: 0.5}, // reduced
|
||||
|
||||
// air battle phases
|
||||
"maneuvering": {"melee":0, "ranged":.1, "mounted":0, "machinery":.2, "naval":0, "armored":0, "aviation":1, "magical":.2}, // aviation
|
||||
"dogfight": {"melee":0, "ranged":.1, "mounted":0, "machinery":.1, "naval":0, "armored":0, "aviation":2, "magical":.1} // aviation
|
||||
maneuvering: {melee: 0, ranged: 0.1, mounted: 0, machinery: 0.2, naval: 0, armored: 0, aviation: 1, magical: 0.2}, // aviation
|
||||
dogfight: {melee: 0, ranged: 0.1, mounted: 0, machinery: 0.1, naval: 0, armored: 0, aviation: 2, magical: 0.1} // aviation
|
||||
};
|
||||
|
||||
const forces = this.getJoinedForces(this[side].regiments);
|
||||
const phase = this[side].phase;
|
||||
const adjuster = Math.max(populationRate.value / 10, 10); // population adjuster, by default 100
|
||||
const adjuster = Math.max(populationRate / 10, 10); // population adjuster, by default 100
|
||||
this[side].power = d3.sum(options.military.map(u => (forces[u.name] || 0) * u.power * scheme[phase][u.type])) / adjuster;
|
||||
const UIvalue = this[side].power ? Math.max(this[side].power|0, 1) : 0;
|
||||
document.getElementById("battlePower_"+side).innerHTML = UIvalue;
|
||||
const UIvalue = this[side].power ? Math.max(this[side].power | 0, 1) : 0;
|
||||
document.getElementById("battlePower_" + side).innerHTML = UIvalue;
|
||||
}
|
||||
|
||||
getInitialMorale() {
|
||||
|
|
@ -320,7 +340,7 @@ class Battle {
|
|||
}
|
||||
|
||||
updateMorale(side) {
|
||||
const morale = document.getElementById("battleMorale_"+side);
|
||||
const morale = document.getElementById("battleMorale_" + side);
|
||||
morale.dataset.tip = morale.dataset.tip.replace(morale.value, "");
|
||||
morale.value = this[side].morale | 0;
|
||||
morale.dataset.tip += morale.value;
|
||||
|
|
@ -335,9 +355,11 @@ class Battle {
|
|||
}
|
||||
|
||||
rollDie(side) {
|
||||
const el = document.getElementById("battleDie_"+side);
|
||||
const el = document.getElementById("battleDie_" + side);
|
||||
const prev = +el.innerHTML;
|
||||
do {el.innerHTML = rand(1, 6)} while (el.innerHTML == prev)
|
||||
do {
|
||||
el.innerHTML = rand(1, 6);
|
||||
} while (el.innerHTML == prev);
|
||||
this[side].die = +el.innerHTML;
|
||||
}
|
||||
|
||||
|
|
@ -357,12 +379,18 @@ class Battle {
|
|||
if (prev[0] === "skirmish" && prev[1] === "skirmish") {
|
||||
const forces = this.getJoinedForces(this.attackers.regiments.concat(this.defenders.regiments));
|
||||
const total = d3.sum(Object.values(forces)); // total forces
|
||||
const ranged = d3.sum(options.military.filter(u => u.type === "ranged").map(u => u.name).map(u => forces[u])) / total; // ranged units
|
||||
if (P(ranged) || P(.8-i/10)) return ["skirmish", "skirmish"];
|
||||
const ranged =
|
||||
d3.sum(
|
||||
options.military
|
||||
.filter(u => u.type === "ranged")
|
||||
.map(u => u.name)
|
||||
.map(u => forces[u])
|
||||
) / total; // ranged units
|
||||
if (P(ranged) || P(0.8 - i / 10)) return ["skirmish", "skirmish"];
|
||||
}
|
||||
|
||||
return ["melee", "melee"]; // default option
|
||||
}
|
||||
};
|
||||
|
||||
const getNavalBattlePhase = () => {
|
||||
const prev = [this.attackers.phase || "shelling", this.defenders.phase || "shelling"]; // previous phase
|
||||
|
|
@ -372,66 +400,66 @@ class Battle {
|
|||
|
||||
// withdrawal phase when power imbalanced
|
||||
if (!prev[0] === "boarding") {
|
||||
if (powerRatio < .5 || P(this.attackers.casualties) && powerRatio < 1) return ["withdrawal", "chase"];
|
||||
if (powerRatio > 2 || P(this.defenders.casualties) && powerRatio > 1) return ["chase", "withdrawal"];
|
||||
if (powerRatio < 0.5 || (P(this.attackers.casualties) && powerRatio < 1)) return ["withdrawal", "chase"];
|
||||
if (powerRatio > 2 || (P(this.defenders.casualties) && powerRatio > 1)) return ["chase", "withdrawal"];
|
||||
}
|
||||
|
||||
// boarding phase can start from 2nd iteration
|
||||
if (prev[0] === "boarding" || P(i/10 - .1)) return ["boarding", "boarding"];
|
||||
if (prev[0] === "boarding" || P(i / 10 - 0.1)) return ["boarding", "boarding"];
|
||||
|
||||
return ["shelling", "shelling"]; // default option
|
||||
}
|
||||
};
|
||||
|
||||
const getSiegePhase = () => {
|
||||
const prev = [this.attackers.phase || "blockade", this.defenders.phase || "sheltering"]; // previous phase
|
||||
let phase = ["blockade", "sheltering"] // default phase
|
||||
let phase = ["blockade", "sheltering"]; // default phase
|
||||
|
||||
if (prev[0] === "retreat" || prev[0] === "looting") return prev;
|
||||
|
||||
if (P(1 - morale[0] / 30) && powerRatio < 1) return ["retreat", "pursue"]; // attackers retreat chance if moral < 30
|
||||
if (P(1 - morale[1] / 15)) return ["looting", "surrendering"]; // defenders surrendering chance if moral < 15
|
||||
|
||||
if (P((powerRatio-1) / 2)) return ["storming", "defense"]; // start storm
|
||||
if (P((powerRatio - 1) / 2)) return ["storming", "defense"]; // start storm
|
||||
|
||||
if (prev[0] !== "storming") {
|
||||
const machinery = options.military.filter(u => u.type === "machinery").map(u => u.name); // machinery units
|
||||
|
||||
const attackers = this.getJoinedForces(this.attackers.regiments);
|
||||
const machineryA = d3.sum(machinery.map(u => attackers[u]));
|
||||
if (i && machineryA && P(.9)) phase[0] = "bombardment";
|
||||
if (i && machineryA && P(0.9)) phase[0] = "bombardment";
|
||||
|
||||
const defenders = this.getJoinedForces(this.defenders.regiments);
|
||||
const machineryD = d3.sum(machinery.map(u => defenders[u]));
|
||||
if (machineryD && P(.9)) phase[1] = "bombardment";
|
||||
if (machineryD && P(0.9)) phase[1] = "bombardment";
|
||||
|
||||
if (i && prev[1] !== "sortie" && machineryD < machineryA && P(.25) && P(morale[1]/70)) phase[1] = "sortie"; // defenders sortie
|
||||
if (i && prev[1] !== "sortie" && machineryD < machineryA && P(0.25) && P(morale[1] / 70)) phase[1] = "sortie"; // defenders sortie
|
||||
}
|
||||
|
||||
return phase;
|
||||
}
|
||||
};
|
||||
|
||||
const getAmbushPhase = () => {
|
||||
const prev = [this.attackers.phase || "shock", this.defenders.phase || "surprise"]; // previous phase
|
||||
|
||||
if (prev[1] === "surprise" && P(1-powerRatio*i/5)) return ["shock", "surprise"];
|
||||
if (prev[1] === "surprise" && P(1 - (powerRatio * i) / 5)) return ["shock", "surprise"];
|
||||
|
||||
// chance if moral < 25
|
||||
if (P(1 - morale[0] / 25)) return ["retreat", "pursue"];
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"];
|
||||
|
||||
return ["melee", "melee"]; // default option
|
||||
}
|
||||
};
|
||||
|
||||
const getLandingPhase = () => {
|
||||
const prev = [this.attackers.phase || "landing", this.defenders.phase || "defense"]; // previous phase
|
||||
|
||||
if (prev[1] === "waiting") return ["flee", "waiting"];
|
||||
if (prev[1] === "pursue") return ["flee", P(.3) ? "pursue" : "waiting"];
|
||||
if (prev[1] === "pursue") return ["flee", P(0.3) ? "pursue" : "waiting"];
|
||||
if (prev[1] === "retreat") return ["pursue", "retreat"];
|
||||
|
||||
if (prev[0] === "landing") {
|
||||
const attackers = P(i/2) ? "melee" : "landing";
|
||||
const defenders = i ? prev[1] : P(.5) ? "defense" : "shock";
|
||||
const attackers = P(i / 2) ? "melee" : "landing";
|
||||
const defenders = i ? prev[1] : P(0.5) ? "defense" : "shock";
|
||||
return [attackers, defenders];
|
||||
}
|
||||
|
||||
|
|
@ -439,7 +467,7 @@ class Battle {
|
|||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"]; // chance if moral < 25
|
||||
|
||||
return ["melee", "melee"]; // default option
|
||||
}
|
||||
};
|
||||
|
||||
const getAirBattlePhase = () => {
|
||||
const prev = [this.attackers.phase || "maneuvering", this.defenders.phase || "maneuvering"]; // previous phase
|
||||
|
|
@ -448,53 +476,87 @@ class Battle {
|
|||
if (P(1 - morale[0] / 25)) return ["retreat", "pursue"];
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"];
|
||||
|
||||
if (prev[0] === "maneuvering" && P(1-i/10)) return ["maneuvering", "maneuvering"];
|
||||
if (prev[0] === "maneuvering" && P(1 - i / 10)) return ["maneuvering", "maneuvering"];
|
||||
|
||||
return ["dogfight", "dogfight"]; // default option
|
||||
}
|
||||
};
|
||||
|
||||
const phase = function(type) {
|
||||
const phase = (function (type) {
|
||||
switch (type) {
|
||||
case "field": return getFieldBattlePhase();
|
||||
case "naval": return getNavalBattlePhase();
|
||||
case "siege": return getSiegePhase();
|
||||
case "ambush": return getAmbushPhase();
|
||||
case "landing": return getLandingPhase();
|
||||
case "air": return getAirBattlePhase();
|
||||
default: getFieldBattlePhase();
|
||||
case "field":
|
||||
return getFieldBattlePhase();
|
||||
case "naval":
|
||||
return getNavalBattlePhase();
|
||||
case "siege":
|
||||
return getSiegePhase();
|
||||
case "ambush":
|
||||
return getAmbushPhase();
|
||||
case "landing":
|
||||
return getLandingPhase();
|
||||
case "air":
|
||||
return getAirBattlePhase();
|
||||
default:
|
||||
getFieldBattlePhase();
|
||||
}
|
||||
}(this.type);
|
||||
})(this.type);
|
||||
|
||||
this.attackers.phase = phase[0];
|
||||
this.defenders.phase = phase[1];
|
||||
|
||||
const buttonA = document.getElementById("battlePhase_attackers");
|
||||
buttonA.className = "icon-button-" + this.attackers.phase;
|
||||
buttonA.dataset.tip = buttonA.nextElementSibling.querySelector("[data-phase='"+phase[0]+"']").dataset.tip;
|
||||
buttonA.dataset.tip = buttonA.nextElementSibling.querySelector("[data-phase='" + phase[0] + "']").dataset.tip;
|
||||
|
||||
const buttonD = document.getElementById("battlePhase_defenders");
|
||||
buttonD.className = "icon-button-" + this.defenders.phase;
|
||||
buttonD.dataset.tip = buttonD.nextElementSibling.querySelector("[data-phase='"+phase[1]+"']").dataset.tip;
|
||||
buttonD.dataset.tip = buttonD.nextElementSibling.querySelector("[data-phase='" + phase[1] + "']").dataset.tip;
|
||||
}
|
||||
|
||||
run() {
|
||||
// validations
|
||||
if (!this.attackers.power) {tip("Attackers army destroyed", false, "warn"); return}
|
||||
if (!this.defenders.power) {tip("Defenders army destroyed", false, "warn"); return}
|
||||
if (!this.attackers.power) {
|
||||
tip("Attackers army destroyed", false, "warn");
|
||||
return;
|
||||
}
|
||||
if (!this.defenders.power) {
|
||||
tip("Defenders army destroyed", false, "warn");
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate casualties
|
||||
const attack = this.attackers.power * (this.attackers.die / 10 + .4);
|
||||
const defense = this.defenders.power * (this.defenders.die / 10 + .4);
|
||||
const attack = this.attackers.power * (this.attackers.die / 10 + 0.4);
|
||||
const defense = this.defenders.power * (this.defenders.die / 10 + 0.4);
|
||||
|
||||
// casualties modifier for phase
|
||||
const phase = {
|
||||
"skirmish":.1, "melee":.2, "pursue":.3, "retreat":.3, "boarding":.2, "shelling":.1, "chase":.03, "withdrawal": .03,
|
||||
"blockade":0, "sheltering":0, "sortie":.1, "bombardment":.05, "storming":.2, "defense":.2, "looting":.5, "surrendering":.5,
|
||||
"surprise":.3, "shock":.3, "landing":.3, "flee":0, "waiting":0, "maneuvering":.1, "dogfight":.2};
|
||||
skirmish: 0.1,
|
||||
melee: 0.2,
|
||||
pursue: 0.3,
|
||||
retreat: 0.3,
|
||||
boarding: 0.2,
|
||||
shelling: 0.1,
|
||||
chase: 0.03,
|
||||
withdrawal: 0.03,
|
||||
blockade: 0,
|
||||
sheltering: 0,
|
||||
sortie: 0.1,
|
||||
bombardment: 0.05,
|
||||
storming: 0.2,
|
||||
defense: 0.2,
|
||||
looting: 0.5,
|
||||
surrendering: 0.5,
|
||||
surprise: 0.3,
|
||||
shock: 0.3,
|
||||
landing: 0.3,
|
||||
flee: 0,
|
||||
waiting: 0,
|
||||
maneuvering: 0.1,
|
||||
dogfight: 0.2
|
||||
};
|
||||
|
||||
const casualties = Math.random() * (Math.max(phase[this.attackers.phase], phase[this.defenders.phase])); // total casualties, ~10% per iteration
|
||||
const casualtiesA = casualties * defense / (attack + defense); // attackers casualties, ~5% per iteration
|
||||
const casualtiesD = casualties * attack / (attack + defense); // defenders casualties, ~5% per iteration
|
||||
const casualties = Math.random() * Math.max(phase[this.attackers.phase], phase[this.defenders.phase]); // total casualties, ~10% per iteration
|
||||
const casualtiesA = (casualties * defense) / (attack + defense); // attackers casualties, ~5% per iteration
|
||||
const casualtiesD = (casualties * attack) / (attack + defense); // defenders casualties, ~5% per iteration
|
||||
|
||||
this.calculateCasualties("attackers", casualtiesA);
|
||||
this.calculateCasualties("defenders", casualtiesD);
|
||||
|
|
@ -519,7 +581,7 @@ class Battle {
|
|||
calculateCasualties(side, casualties) {
|
||||
for (const r of this[side].regiments) {
|
||||
for (const unit in r.u) {
|
||||
const rand = .8 + Math.random() * .4;
|
||||
const rand = 0.8 + Math.random() * 0.4;
|
||||
const died = Math.min(Pint(r.u[unit] * casualties * rand), r.survivors[unit]);
|
||||
r.casualties[unit] -= died;
|
||||
r.survivors[unit] -= died;
|
||||
|
|
@ -551,10 +613,16 @@ class Battle {
|
|||
const button = ev.target;
|
||||
const div = button.nextElementSibling;
|
||||
|
||||
const hideSection = function() {button.style.opacity = 1; div.style.display = "none"}
|
||||
if (div.style.display === "block") {hideSection(); return}
|
||||
const hideSection = function () {
|
||||
button.style.opacity = 1;
|
||||
div.style.display = "none";
|
||||
};
|
||||
if (div.style.display === "block") {
|
||||
hideSection();
|
||||
return;
|
||||
}
|
||||
|
||||
button.style.opacity = .5;
|
||||
button.style.opacity = 0.5;
|
||||
div.style.display = "block";
|
||||
|
||||
document.getElementsByTagName("body")[0].addEventListener("click", hideSection, {once: true});
|
||||
|
|
@ -568,13 +636,13 @@ class Battle {
|
|||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
this.name = this.defineName();
|
||||
$("#battleScreen").dialog({"title":this.name});
|
||||
$("#battleScreen").dialog({title: this.name});
|
||||
}
|
||||
|
||||
changePhase(ev, side) {
|
||||
if (ev.target.tagName !== "BUTTON") return;
|
||||
const phase = this[side].phase = ev.target.dataset.phase;
|
||||
const button = document.getElementById("battlePhase_"+side);
|
||||
const phase = (this[side].phase = ev.target.dataset.phase);
|
||||
const button = document.getElementById("battlePhase_" + side);
|
||||
button.className = "icon-button-" + phase;
|
||||
button.dataset.tip = ev.target.dataset.tip;
|
||||
this.calculateStrength(side);
|
||||
|
|
@ -587,12 +655,12 @@ class Battle {
|
|||
const battleStatus = getBattleStatus(relativeCasualties, maxCasualties);
|
||||
function getBattleStatus(relative, max) {
|
||||
if (isNaN(relative)) return ["standoff", "standoff"]; // if no casualties at all
|
||||
if (max < .05) return ["minor skirmishes", "minor skirmishes"];
|
||||
if (max < 0.05) return ["minor skirmishes", "minor skirmishes"];
|
||||
if (relative > 95) return ["attackers flawless victory", "disorderly retreat of defenders"];
|
||||
if (relative > .7) return ["attackers decisive victory", "defenders disastrous defeat"];
|
||||
if (relative > .6) return ["attackers victory", "defenders defeat"];
|
||||
if (relative > .4) return ["stalemate", "stalemate"];
|
||||
if (relative > .3) return ["attackers defeat", "defenders victory"];
|
||||
if (relative > 0.7) return ["attackers decisive victory", "defenders disastrous defeat"];
|
||||
if (relative > 0.6) return ["attackers victory", "defenders defeat"];
|
||||
if (relative > 0.4) return ["stalemate", "stalemate"];
|
||||
if (relative > 0.3) return ["attackers defeat", "defenders victory"];
|
||||
if (relative > 0.5) return ["attackers disastrous defeat", "decisive victory of defenders"];
|
||||
if (relative >= 0) return ["attackers disorderly retreat", "flawless victory of defenders"];
|
||||
return ["stalemate", "stalemate"]; // exception
|
||||
|
|
@ -609,16 +677,10 @@ class Battle {
|
|||
if (note) {
|
||||
const status = side === "attackers" ? battleStatus[0] : battleStatus[1];
|
||||
const losses = r.a ? Math.abs(d3.sum(Object.values(r.casualties))) / r.a : 1;
|
||||
const regStatus =
|
||||
losses === 1 ? "is destroyed" :
|
||||
losses > .8 ? "is almost completely destroyed" :
|
||||
losses > .5 ? "suffered terrible losses" :
|
||||
losses > .3 ? "suffered severe losses" :
|
||||
losses > .2 ? "suffered heavy losses" :
|
||||
losses > .05 ? "suffered significant losses" :
|
||||
losses > 0 ? "suffered unsignificant losses" :
|
||||
"left the battle without loss";
|
||||
const casualties = Object.keys(r.casualties).map(t => r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null).filter(c => c);
|
||||
const regStatus = losses === 1 ? "is destroyed" : losses > 0.8 ? "is almost completely destroyed" : losses > 0.5 ? "suffered terrible losses" : losses > 0.3 ? "suffered severe losses" : losses > 0.2 ? "suffered heavy losses" : losses > 0.05 ? "suffered significant losses" : losses > 0 ? "suffered unsignificant losses" : "left the battle without loss";
|
||||
const casualties = Object.keys(r.casualties)
|
||||
.map(t => (r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null))
|
||||
.filter(c => c);
|
||||
const casualtiesText = casualties.length ? " Casualties: " + list(casualties) + "." : "";
|
||||
const legend = `\r\n\r\n${battleName} (${options.year} ${options.eraShort}): ${status}. The regiment ${regStatus}.${casualtiesText}`;
|
||||
note.legend += legend;
|
||||
|
|
@ -630,33 +692,38 @@ class Battle {
|
|||
}
|
||||
|
||||
// append battlefield marker
|
||||
void function addMarkerSymbol() {
|
||||
void (function addMarkerSymbol() {
|
||||
if (svg.select("#defs-markers").select("#marker_battlefield").size()) return;
|
||||
const symbol = svg.select("#defs-markers").append("symbol").attr("id", "marker_battlefield").attr("viewBox", "0 0 30 30");
|
||||
symbol.append("path").attr("d", "M6,19 l9,10 L24,19").attr("fill", "#000000").attr("stroke", "none");
|
||||
symbol.append("circle").attr("cx", 15).attr("cy", 15).attr("r", 10).attr("fill", "#ffffff").attr("stroke", "#000000").attr("stroke-width", 1);
|
||||
symbol.append("text").attr("x", "50%").attr("y", "52%").attr("fill", "#000000").attr("stroke", "#3200ff").attr("stroke-width", 0)
|
||||
.attr("font-size", "12px").attr("dominant-baseline", "central").text("⚔️");
|
||||
}()
|
||||
symbol.append("text").attr("x", "50%").attr("y", "52%").attr("fill", "#000000").attr("stroke", "#3200ff").attr("stroke-width", 0).attr("font-size", "12px").attr("dominant-baseline", "central").text("⚔️");
|
||||
})();
|
||||
|
||||
const getSide = (regs, n) => regs.length > 1
|
||||
? `${n ? "regiments" : "forces"} of ${list([... new Set(regs.map(r => pack.states[r.state].name))])}`
|
||||
: getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name;
|
||||
const getSide = (regs, n) => (regs.length > 1 ? `${n ? "regiments" : "forces"} of ${list([...new Set(regs.map(r => pack.states[r.state].name))])}` : getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name);
|
||||
const getLosses = casualties => Math.min(rn(casualties * 100), 100);
|
||||
|
||||
const status = battleStatus[+P(.7)];
|
||||
const status = battleStatus[+P(0.7)];
|
||||
const result = `The ${this.getTypeName(this.type)} ended in ${status}`;
|
||||
const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide(this.defenders.regiments, 0)}. ${result}.
|
||||
\r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(this.defenders.casualties)}%`;
|
||||
const id = getNextId("markerElement");
|
||||
notes.push({id, name:this.name, legend});
|
||||
notes.push({id, name: this.name, legend});
|
||||
|
||||
tip(`${this.name} is over. ${result}`, true, "success", 4000);
|
||||
|
||||
markers.append("use").attr("id", id)
|
||||
.attr("xlink:href", "#marker_battlefield").attr("data-id", "#marker_battlefield")
|
||||
.attr("data-x", this.x).attr("data-y", this.y).attr("x", this.x - 15).attr("y", this.y - 30)
|
||||
.attr("data-size", 1).attr("width", 30).attr("height", 30);
|
||||
markers
|
||||
.append("use")
|
||||
.attr("id", id)
|
||||
.attr("xlink:href", "#marker_battlefield")
|
||||
.attr("data-id", "#marker_battlefield")
|
||||
.attr("data-x", this.x)
|
||||
.attr("data-y", this.y)
|
||||
.attr("x", this.x - 15)
|
||||
.attr("y", this.y - 30)
|
||||
.attr("data-size", 1)
|
||||
.attr("width", 30)
|
||||
.attr("height", 30);
|
||||
|
||||
$("#battleScreen").dialog("destroy");
|
||||
this.cleanData();
|
||||
|
|
@ -682,5 +749,4 @@ class Battle {
|
|||
});
|
||||
delete Battle.prototype.context;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ function editBiomes() {
|
|||
modules.editBiomes = true;
|
||||
|
||||
$("#biomesEditor").dialog({
|
||||
title: "Biomes Editor", resizable: false, width: fitContent(), close: closeBiomesEditor,
|
||||
title: "Biomes Editor",
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: closeBiomesEditor,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg"}
|
||||
});
|
||||
|
||||
|
|
@ -33,18 +36,20 @@ function editBiomes() {
|
|||
document.getElementById("biomesRegenerateReliefIcons").addEventListener("click", regenerateIcons);
|
||||
document.getElementById("biomesExport").addEventListener("click", downloadBiomesData);
|
||||
|
||||
body.addEventListener("click", function(ev) {
|
||||
const el = ev.target, cl = el.classList;
|
||||
if (cl.contains("fillRect")) biomeChangeColor(el); else
|
||||
if (cl.contains("icon-info-circled")) openWiki(el); else
|
||||
if (cl.contains("icon-trash-empty")) removeCustomBiome(el);
|
||||
body.addEventListener("click", function (ev) {
|
||||
const el = ev.target,
|
||||
cl = el.classList;
|
||||
if (cl.contains("fillRect")) biomeChangeColor(el);
|
||||
else if (cl.contains("icon-info-circled")) openWiki(el);
|
||||
else if (cl.contains("icon-trash-empty")) removeCustomBiome(el);
|
||||
if (customization === 6) selectBiomeOnLineClick(el);
|
||||
});
|
||||
|
||||
body.addEventListener("change", function(ev) {
|
||||
const el = ev.target, cl = el.classList;
|
||||
if (cl.contains("biomeName")) biomeChangeName(el); else
|
||||
if (cl.contains("biomeHabitability")) biomeChangeHabitability(el);
|
||||
body.addEventListener("change", function (ev) {
|
||||
const el = ev.target,
|
||||
cl = el.classList;
|
||||
if (cl.contains("biomeName")) biomeChangeName(el);
|
||||
else if (cl.contains("biomeHabitability")) biomeChangeHabitability(el);
|
||||
});
|
||||
|
||||
function refreshBiomesEditor() {
|
||||
|
|
@ -73,13 +78,15 @@ function editBiomes() {
|
|||
function biomesEditorAddLines() {
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
const b = biomesData;
|
||||
let lines = "", totalArea = 0, totalPopulation = 0;;
|
||||
let lines = "",
|
||||
totalArea = 0,
|
||||
totalPopulation = 0;
|
||||
|
||||
for (const i of b.i) {
|
||||
if (!i || biomesData.name[i] === "removed") continue; // ignore water and removed biomes
|
||||
const area = b.area[i] * distanceScaleInput.value ** 2;
|
||||
const rural = b.rural[i] * populationRate.value;
|
||||
const urban = b.urban[i] * populationRate.value * urbanization.value;
|
||||
const rural = b.rural[i] * populationRate;
|
||||
const urban = b.urban[i] * populationRate * urbanization;
|
||||
const population = rn(rural + urban);
|
||||
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`;
|
||||
totalArea += area;
|
||||
|
|
@ -98,7 +105,7 @@ function editBiomes() {
|
|||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="biomePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Open Wikipedia article about the biome" class="icon-info-circled pointer hide"></span>
|
||||
${i>12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ''}
|
||||
${i > 12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ""}
|
||||
</div>`;
|
||||
}
|
||||
body.innerHTML = lines;
|
||||
|
|
@ -115,7 +122,10 @@ function editBiomes() {
|
|||
body.querySelectorAll("div.biomes").forEach(el => el.addEventListener("mouseenter", ev => biomeHighlightOn(ev)));
|
||||
body.querySelectorAll("div.biomes").forEach(el => el.addEventListener("mouseleave", ev => biomeHighlightOff(ev)));
|
||||
|
||||
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
|
||||
if (body.dataset.type === "percentage") {
|
||||
body.dataset.type = "absolute";
|
||||
togglePercentageMode();
|
||||
}
|
||||
applySorting(biomesHeader);
|
||||
$("#biomesEditor").dialog({width: fitContent()});
|
||||
}
|
||||
|
|
@ -123,25 +133,37 @@ function editBiomes() {
|
|||
function biomeHighlightOn(event) {
|
||||
if (customization === 6) return;
|
||||
const biome = +event.target.dataset.id;
|
||||
biomes.select("#biome"+biome).raise().transition(animate).attr("stroke-width", 2).attr("stroke", "#cd4c11");
|
||||
biomes
|
||||
.select("#biome" + biome)
|
||||
.raise()
|
||||
.transition(animate)
|
||||
.attr("stroke-width", 2)
|
||||
.attr("stroke", "#cd4c11");
|
||||
}
|
||||
|
||||
function biomeHighlightOff(event) {
|
||||
if (customization === 6) return;
|
||||
const biome = +event.target.dataset.id;
|
||||
const color = biomesData.color[biome];
|
||||
biomes.select("#biome"+biome).transition().attr("stroke-width", .7).attr("stroke", color);
|
||||
biomes
|
||||
.select("#biome" + biome)
|
||||
.transition()
|
||||
.attr("stroke-width", 0.7)
|
||||
.attr("stroke", color);
|
||||
}
|
||||
|
||||
function biomeChangeColor(el) {
|
||||
const currentFill = el.getAttribute("fill");
|
||||
const biome = +el.parentNode.parentNode.dataset.id;
|
||||
|
||||
const callback = function(fill) {
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
biomesData.color[biome] = fill;
|
||||
biomes.select("#biome"+biome).attr("fill", fill).attr("stroke", fill);
|
||||
}
|
||||
biomes
|
||||
.select("#biome" + biome)
|
||||
.attr("fill", fill)
|
||||
.attr("stroke", fill);
|
||||
};
|
||||
|
||||
openPicker(currentFill, callback);
|
||||
}
|
||||
|
|
@ -168,30 +190,52 @@ function editBiomes() {
|
|||
|
||||
function openWiki(el) {
|
||||
const name = el.parentNode.dataset.name;
|
||||
if (name === "Custom" || !name) {tip("Please provide a biome name", false, "error"); return;}
|
||||
if (name === "Custom" || !name) {
|
||||
tip("Please provide a biome name", false, "error");
|
||||
return;
|
||||
}
|
||||
const wiki = "https://en.wikipedia.org/wiki/";
|
||||
|
||||
switch (name) {
|
||||
case "Hot desert": openURL(wiki + "Desert_climate#Hot_desert_climates");
|
||||
case "Cold desert": openURL(wiki + "Desert_climate#Cold_desert_climates");
|
||||
case "Savanna": openURL(wiki + "Tropical_and_subtropical_grasslands,_savannas,_and_shrublands");
|
||||
case "Grassland": openURL(wiki + "Temperate_grasslands,_savannas,_and_shrublands");
|
||||
case "Tropical seasonal forest": openURL(wiki + "Seasonal_tropical_forest");
|
||||
case "Temperate deciduous forest": openURL(wiki + "Temperate_deciduous_forest");
|
||||
case "Tropical rainforest": openURL(wiki + "Tropical_rainforest");
|
||||
case "Temperate rainforest": openURL(wiki + "Temperate_rainforest");
|
||||
case "Taiga": openURL(wiki + "Taiga");
|
||||
case "Tundra": openURL(wiki + "Tundra");
|
||||
case "Glacier": openURL(wiki + "Glacier");
|
||||
case "Wetland": openURL(wiki + "Wetland");
|
||||
default: openURL(`https://en.wikipedia.org/w/index.php?search=${name}`);
|
||||
case "Hot desert":
|
||||
openURL(wiki + "Desert_climate#Hot_desert_climates");
|
||||
case "Cold desert":
|
||||
openURL(wiki + "Desert_climate#Cold_desert_climates");
|
||||
case "Savanna":
|
||||
openURL(wiki + "Tropical_and_subtropical_grasslands,_savannas,_and_shrublands");
|
||||
case "Grassland":
|
||||
openURL(wiki + "Temperate_grasslands,_savannas,_and_shrublands");
|
||||
case "Tropical seasonal forest":
|
||||
openURL(wiki + "Seasonal_tropical_forest");
|
||||
case "Temperate deciduous forest":
|
||||
openURL(wiki + "Temperate_deciduous_forest");
|
||||
case "Tropical rainforest":
|
||||
openURL(wiki + "Tropical_rainforest");
|
||||
case "Temperate rainforest":
|
||||
openURL(wiki + "Temperate_rainforest");
|
||||
case "Taiga":
|
||||
openURL(wiki + "Taiga");
|
||||
case "Tundra":
|
||||
openURL(wiki + "Tundra");
|
||||
case "Glacier":
|
||||
openURL(wiki + "Glacier");
|
||||
case "Wetland":
|
||||
openURL(wiki + "Wetland");
|
||||
default:
|
||||
openURL(`https://en.wikipedia.org/w/index.php?search=${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
if (legend.selectAll("*").size()) {clearLegend(); return;}; // hide legend
|
||||
if (legend.selectAll("*").size()) {
|
||||
clearLegend();
|
||||
return;
|
||||
} // hide legend
|
||||
const d = biomesData;
|
||||
const data = Array.from(d.i).filter(i => d.cells[i]).sort((a, b) => d.area[b] - d.area[a]).map(i => [i, d.color[i], d.name[i]]);
|
||||
const data = Array.from(d.i)
|
||||
.filter(i => d.cells[i])
|
||||
.sort((a, b) => d.area[b] - d.area[a])
|
||||
.map(i => [i, d.color[i], d.name[i]]);
|
||||
drawLegend("Biomes", data);
|
||||
}
|
||||
|
||||
|
|
@ -202,10 +246,10 @@ function editBiomes() {
|
|||
const totalArea = +biomesFooterArea.dataset.area;
|
||||
const totalPopulation = +biomesFooterPopulation.dataset.population;
|
||||
|
||||
body.querySelectorAll(":scope> div").forEach(function(el) {
|
||||
el.querySelector(".biomeCells").innerHTML = rn(+el.dataset.cells / totalCells * 100) + "%";
|
||||
el.querySelector(".biomeArea").innerHTML = rn(+el.dataset.area / totalArea * 100) + "%";
|
||||
el.querySelector(".biomePopulation").innerHTML = rn(+el.dataset.population / totalPopulation * 100) + "%";
|
||||
body.querySelectorAll(":scope> div").forEach(function (el) {
|
||||
el.querySelector(".biomeCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100) + "%";
|
||||
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%";
|
||||
el.querySelector(".biomePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%";
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
|
|
@ -214,8 +258,12 @@ function editBiomes() {
|
|||
}
|
||||
|
||||
function addCustomBiome() {
|
||||
const b = biomesData, i = biomesData.i.length;
|
||||
if (i > 254) {tip("Maximum number of biomes reached (255), data cleansing is required", false, "error"); return;}
|
||||
const b = biomesData,
|
||||
i = biomesData.i.length;
|
||||
if (i > 254) {
|
||||
tip("Maximum number of biomes reached (255), data cleansing is required", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
b.i.push(i);
|
||||
b.color.push(getRandomColor());
|
||||
|
|
@ -264,9 +312,9 @@ function editBiomes() {
|
|||
|
||||
function downloadBiomesData() {
|
||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||
let data = "Id,Biome,Color,Habitability,Cells,Area "+unit+",Population\n"; // headers
|
||||
let data = "Id,Biome,Color,Habitability,Cells,Area " + unit + ",Population\n"; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function(el) {
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
data += el.dataset.color + ",";
|
||||
|
|
@ -285,20 +333,17 @@ function editBiomes() {
|
|||
customization = 6;
|
||||
biomes.append("g").attr("id", "temp");
|
||||
|
||||
document.querySelectorAll("#biomesBottom > button").forEach(el => el.style.display = "none");
|
||||
document.querySelectorAll("#biomesBottom > div").forEach(el => el.style.display = "block");
|
||||
document.querySelectorAll("#biomesBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.querySelectorAll("#biomesBottom > div").forEach(el => (el.style.display = "block"));
|
||||
body.querySelector("div.biomes").classList.add("selected");
|
||||
|
||||
biomesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
|
||||
body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none");
|
||||
body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none"));
|
||||
biomesFooter.style.display = "none";
|
||||
$("#biomesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
|
||||
|
||||
tip("Click on biome to select, drag the circle to change biome", true);
|
||||
viewbox.style("cursor", "crosshair")
|
||||
.on("click", selectBiomeOnMapClick)
|
||||
.call(d3.drag().on("start", dragBiomeBrush))
|
||||
.on("touchmove mousemove", moveBiomeBrush);
|
||||
viewbox.style("cursor", "crosshair").on("click", selectBiomeOnMapClick).call(d3.drag().on("start", dragBiomeBrush)).on("touchmove mousemove", moveBiomeBrush);
|
||||
}
|
||||
|
||||
function selectBiomeOnLineClick(line) {
|
||||
|
|
@ -310,13 +355,16 @@ function editBiomes() {
|
|||
function selectBiomeOnMapClick() {
|
||||
const point = d3.mouse(this);
|
||||
const i = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[i] < 20) {tip("You cannot reassign water via biomes. Please edit the Heightmap to change water", false, "error"); return;}
|
||||
if (pack.cells.h[i] < 20) {
|
||||
tip("You cannot reassign water via biomes. Please edit the Heightmap to change water", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const assigned = biomes.select("#temp").select("polygon[data-cell='"+i+"']");
|
||||
const assigned = biomes.select("#temp").select("polygon[data-cell='" + i + "']");
|
||||
const biome = assigned.size() ? +assigned.attr("data-biome") : pack.cells.biome[i];
|
||||
|
||||
body.querySelector("div.selected").classList.remove("selected");
|
||||
body.querySelector("div[data-id='"+biome+"']").classList.add("selected");
|
||||
body.querySelector("div[data-id='" + biome + "']").classList.add("selected");
|
||||
}
|
||||
|
||||
function dragBiomeBrush() {
|
||||
|
|
@ -341,8 +389,8 @@ function editBiomes() {
|
|||
const biomeNew = selected.dataset.id;
|
||||
const color = biomesData.color[biomeNew];
|
||||
|
||||
selection.forEach(function(i) {
|
||||
const exists = temp.select("polygon[data-cell='"+i+"']");
|
||||
selection.forEach(function (i) {
|
||||
const exists = temp.select("polygon[data-cell='" + i + "']");
|
||||
const biomeOld = exists.size() ? +exists.attr("data-biome") : pack.cells.biome[i];
|
||||
if (biomeNew === biomeOld) return;
|
||||
|
||||
|
|
@ -361,7 +409,7 @@ function editBiomes() {
|
|||
|
||||
function applyBiomesChange() {
|
||||
const changed = biomes.select("#temp").selectAll("polygon");
|
||||
changed.each(function() {
|
||||
changed.each(function () {
|
||||
const i = +this.dataset.cell;
|
||||
const b = +this.dataset.biome;
|
||||
pack.cells.biome[i] = b;
|
||||
|
|
@ -379,10 +427,10 @@ function editBiomes() {
|
|||
biomes.select("#temp").remove();
|
||||
removeCircle();
|
||||
|
||||
document.querySelectorAll("#biomesBottom > button").forEach(el => el.style.display = "inline-block");
|
||||
document.querySelectorAll("#biomesBottom > div").forEach(el => el.style.display = "none");
|
||||
document.querySelectorAll("#biomesBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.querySelectorAll("#biomesBottom > div").forEach(el => (el.style.display = "none"));
|
||||
|
||||
body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all");
|
||||
body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all"));
|
||||
biomesEditor.querySelectorAll(".hide").forEach(el => el.classList.remove("hidden"));
|
||||
biomesFooter.style.display = "block";
|
||||
if (!close) $("#biomesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ function editBurg(id) {
|
|||
|
||||
document.getElementById('burgName').value = b.name;
|
||||
document.getElementById('burgType').value = b.type || 'Generic';
|
||||
document.getElementById('burgPopulation').value = rn(b.population * populationRate.value * urbanization.value);
|
||||
document.getElementById('burgPopulation').value = rn(b.population * populationRate * urbanization);
|
||||
document.getElementById('burgEditAnchorStyle').style.display = +b.port ? 'inline-block' : 'none';
|
||||
|
||||
// update list and select culture
|
||||
|
|
@ -123,7 +123,7 @@ function editBurg(id) {
|
|||
'Okhotsk (Russia)',
|
||||
'Fairbanks (Alaska)',
|
||||
'Nuuk (Greenland)',
|
||||
'Murmansk',
|
||||
'Murmansk', // -5 - 0
|
||||
'Arkhangelsk',
|
||||
'Anchorage',
|
||||
'Tromsø',
|
||||
|
|
@ -133,7 +133,7 @@ function editBurg(id) {
|
|||
'Halifax',
|
||||
'Prague',
|
||||
'Copenhagen',
|
||||
'London',
|
||||
'London', // 1 - 10
|
||||
'Antwerp',
|
||||
'Paris',
|
||||
'Milan',
|
||||
|
|
@ -143,7 +143,7 @@ function editBurg(id) {
|
|||
'Lisbon',
|
||||
'Barcelona',
|
||||
'Marrakesh',
|
||||
'Alexandria',
|
||||
'Alexandria', // 11 - 20
|
||||
'Tegucigalpa',
|
||||
'Guangzhou',
|
||||
'Rio de Janeiro',
|
||||
|
|
@ -153,11 +153,10 @@ function editBurg(id) {
|
|||
'Mogadishu',
|
||||
'Bangkok',
|
||||
'Aden',
|
||||
'Khartoum',
|
||||
'Mecca'
|
||||
];
|
||||
const city = cities[temperature + 5];
|
||||
return city ? 'in ' + city : null;
|
||||
'Khartoum'
|
||||
]; // 21 - 30
|
||||
if (temperature > 30) return 'Mecca';
|
||||
return cities[temperature + 5] || null;
|
||||
}
|
||||
|
||||
function dragBurgLabel() {
|
||||
|
|
@ -332,7 +331,7 @@ function editBurg(id) {
|
|||
|
||||
function changePopulation() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
pack.burgs[id].population = rn(burgPopulation.value / populationRate.value / urbanization.value, 4);
|
||||
pack.burgs[id].population = rn(burgPopulation.value / populationRate / urbanization, 4);
|
||||
}
|
||||
|
||||
function toggleFeature() {
|
||||
|
|
@ -423,7 +422,7 @@ function editBurg(id) {
|
|||
const cells = pack.cells;
|
||||
const name = elSelected.text();
|
||||
const size = Math.max(Math.min(rn(burg.population), 100), 6); // to be removed once change on MFDC is done
|
||||
const population = rn(burg.population * populationRate.value * urbanization.value);
|
||||
const population = rn(burg.population * populationRate * urbanization);
|
||||
|
||||
const s = burg.MFCG || defSeed;
|
||||
const cell = burg.cell;
|
||||
|
|
@ -547,7 +546,7 @@ function editBurg(id) {
|
|||
const id = +elSelected.attr('data-id');
|
||||
if (pack.burgs[id].capital) {
|
||||
alertMessage.innerHTML = `You cannot remove the burg as it is a state capital.<br><br>
|
||||
Please change state capital first. You can do it using Burgs Editor (shift + T)`;
|
||||
You can change the capital using Burgs Editor (shift + T)`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove burg',
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ function overviewBurgs() {
|
|||
totalPopulation = 0;
|
||||
|
||||
for (const b of filtered) {
|
||||
const population = b.population * populationRate.value * urbanization.value;
|
||||
const population = b.population * populationRate * urbanization;
|
||||
totalPopulation += population;
|
||||
const type = b.capital && b.port ? 'a-capital-port' : b.capital ? 'c-capital' : b.port ? 'p-port' : 'z-burg';
|
||||
const state = pack.states[b.state].name;
|
||||
|
|
@ -91,8 +91,8 @@ function overviewBurgs() {
|
|||
<span data-tip="${b.capital ? ' This burg is a state capital' : 'Click to assign a capital status'}" class="icon-star-empty${b.capital ? '' : ' inactive pointer'}"></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? '' : ' inactive'}" style="font-size:.9em"></span>
|
||||
</div>
|
||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||
<span class="locks pointer ${b.lock ? 'icon-lock' : 'icon-lock-open inactive'}"></span>
|
||||
<span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span>
|
||||
<span data-tip="Remove burg" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -163,10 +163,10 @@ function overviewBurgs() {
|
|||
const burg = +this.parentNode.dataset.id;
|
||||
if (this.value == '' || isNaN(+this.value)) {
|
||||
tip('Please provide an integer number (like 10000, not 10K)', false, 'error');
|
||||
this.value = si(pack.burgs[burg].population * populationRate.value * urbanization.value);
|
||||
this.value = si(pack.burgs[burg].population * populationRate * urbanization);
|
||||
return;
|
||||
}
|
||||
pack.burgs[burg].population = this.value / populationRate.value / urbanization.value;
|
||||
pack.burgs[burg].population = this.value / populationRate / urbanization;
|
||||
this.parentNode.dataset.population = this.value;
|
||||
this.value = si(this.value);
|
||||
|
||||
|
|
@ -255,14 +255,9 @@ function overviewBurgs() {
|
|||
function addBurgOnClick() {
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[cell] < 20) {
|
||||
tip('You cannot place state into the water. Please click on a land cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (pack.cells.burg[cell]) {
|
||||
tip('There is already a burg in this cell. Please select a free cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (pack.cells.h[cell] < 20) return tip('You cannot place state into the water. Please click on a land cell', false, 'error');
|
||||
if (pack.cells.burg[cell]) return tip('There is already a burg in this cell. Please select a free cell', false, 'error');
|
||||
|
||||
addBurg(point); // add new burg
|
||||
|
||||
if (d3.event.shiftKey === false) {
|
||||
|
|
@ -347,7 +342,7 @@ function overviewBurgs() {
|
|||
d3.select(ev.target).transition().duration(1500).attr('stroke', '#c13119');
|
||||
const name = d.data.name;
|
||||
const parent = d.parent.data.name;
|
||||
const population = si(d.value * populationRate.value * urbanization.value);
|
||||
const population = si(d.value * populationRate * urbanization);
|
||||
|
||||
burgsInfo.innerHTML = `${name}. ${parent}. Population: ${population}`;
|
||||
burgHighlightOn(ev);
|
||||
|
|
@ -449,7 +444,7 @@ function overviewBurgs() {
|
|||
data += b.state ? pack.states[b.state].fullName + ',' : pack.states[b.state].name + ',';
|
||||
data += pack.cultures[b.culture].name + ',';
|
||||
data += pack.religions[pack.cells.religion[b.cell]].name + ',';
|
||||
data += rn(b.population * populationRate.value * urbanization.value) + ',';
|
||||
data += rn(b.population * populationRate * urbanization) + ',';
|
||||
|
||||
// add geography data
|
||||
data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ',';
|
||||
|
|
@ -497,15 +492,9 @@ function overviewBurgs() {
|
|||
}
|
||||
|
||||
function importBurgNames(dataLoaded) {
|
||||
if (!dataLoaded) {
|
||||
tip('Cannot load the file, please check the format', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (!dataLoaded) return tip('Cannot load the file, please check the format', false, 'error');
|
||||
const data = dataLoaded.split('\r\n');
|
||||
if (!data.length) {
|
||||
tip('Cannot parse the list, please check the file format', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (!data.length) return tip('Cannot parse the list, please check the file format', false, 'error');
|
||||
|
||||
let change = [],
|
||||
message = `Burgs will be renamed as below. Please confirm`;
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ function editCultures() {
|
|||
for (const c of pack.cultures) {
|
||||
if (c.removed) continue;
|
||||
const area = c.area * distanceScaleInput.value ** 2;
|
||||
const rural = c.rural * populationRate.value;
|
||||
const urban = c.urban * populationRate.value * urbanization.value;
|
||||
const rural = c.rural * populationRate;
|
||||
const urban = c.urban * populationRate * urbanization;
|
||||
const population = rn(rural + urban);
|
||||
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to edit`;
|
||||
totalArea += area;
|
||||
|
|
@ -186,8 +186,8 @@ function editCultures() {
|
|||
.select("g[data-id='" + culture + "'] > path")
|
||||
.classed('selected', 1);
|
||||
const c = pack.cultures[culture];
|
||||
const rural = c.rural * populationRate.value;
|
||||
const urban = c.urban * populationRate.value * urbanization.value;
|
||||
const rural = c.rural * populationRate;
|
||||
const urban = c.urban * populationRate * urbanization;
|
||||
const population = rural + urban > 0 ? si(rn(rural + urban)) + ' people' : 'Extinct';
|
||||
info.innerHTML = `${c.name} culture. ${c.type}. ${population}`;
|
||||
tip('Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation');
|
||||
|
|
@ -323,8 +323,8 @@ function editCultures() {
|
|||
tip('Culture does not have any cells, cannot change population', false, 'error');
|
||||
return;
|
||||
}
|
||||
const rural = rn(c.rural * populationRate.value);
|
||||
const urban = rn(c.urban * populationRate.value * urbanization.value);
|
||||
const rural = rn(c.rural * populationRate);
|
||||
const urban = rn(c.urban * populationRate * urbanization);
|
||||
const total = rural + urban;
|
||||
const l = (n) => Number(n).toLocaleString();
|
||||
const burgs = pack.burgs.filter((b) => !b.removed && b.culture === culture);
|
||||
|
|
@ -367,7 +367,7 @@ function editCultures() {
|
|||
cells.forEach((i) => (pack.cells.pop[i] *= ruralChange));
|
||||
}
|
||||
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
|
||||
const points = ruralPop.value / populationRate.value;
|
||||
const points = ruralPop.value / populationRate;
|
||||
const cells = pack.cells.i.filter((i) => pack.cells.culture[i] === culture);
|
||||
const pop = rn(points / cells.length);
|
||||
cells.forEach((i) => (pack.cells.pop[i] = pop));
|
||||
|
|
@ -378,7 +378,7 @@ function editCultures() {
|
|||
burgs.forEach((b) => (b.population = rn(b.population * urbanChange, 4)));
|
||||
}
|
||||
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
|
||||
const points = urbanPop.value / populationRate.value / urbanization.value;
|
||||
const points = urbanPop.value / populationRate / urbanization;
|
||||
const population = rn(points / burgs.length, 4);
|
||||
burgs.forEach((b) => (b.population = population));
|
||||
}
|
||||
|
|
@ -402,27 +402,36 @@ function editCultures() {
|
|||
if (customization === 4) return;
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
|
||||
const message = 'Are you sure you want to remove the culture? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
cults.select('#culture' + culture).remove();
|
||||
debug.select('#cultureCenter' + culture).remove();
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the culture? <br>This action cannot be reverted';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove culture',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
cults.select('#culture' + culture).remove();
|
||||
debug.select('#cultureCenter' + culture).remove();
|
||||
|
||||
pack.burgs.filter((b) => b.culture == culture).forEach((b) => (b.culture = 0));
|
||||
pack.states.forEach((s, i) => {
|
||||
if (s.culture === culture) s.culture = 0;
|
||||
});
|
||||
pack.cells.culture.forEach((c, i) => {
|
||||
if (c === culture) pack.cells.culture[i] = 0;
|
||||
});
|
||||
pack.cultures[culture].removed = true;
|
||||
pack.burgs.filter((b) => b.culture == culture).forEach((b) => (b.culture = 0));
|
||||
pack.states.forEach((s, i) => {
|
||||
if (s.culture === culture) s.culture = 0;
|
||||
});
|
||||
pack.cells.culture.forEach((c, i) => {
|
||||
if (c === culture) pack.cells.culture[i] = 0;
|
||||
});
|
||||
pack.cultures[culture].removed = true;
|
||||
|
||||
const origin = pack.cultures[culture].origin;
|
||||
pack.cultures.forEach((c) => {
|
||||
if (c.origin === culture) c.origin = origin;
|
||||
});
|
||||
refreshCulturesEditor();
|
||||
};
|
||||
confirmationDialog({title: 'Remove culture', message, confirm: 'Remove', onConfirm});
|
||||
const origin = pack.cultures[culture].origin;
|
||||
pack.cultures.forEach((c) => {
|
||||
if (c.origin === culture) c.origin = origin;
|
||||
});
|
||||
refreshCulturesEditor();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function drawCultureCenters() {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
function showEPForRoute(node) {
|
||||
const points = [];
|
||||
debug.select("#controlPoints").selectAll("circle").each(function() {
|
||||
const i = findCell(this.getAttribute("cx"), this.getAttribute("cy"));
|
||||
points.push(i);
|
||||
});
|
||||
debug
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.each(function () {
|
||||
const i = findCell(this.getAttribute("cx"), this.getAttribute("cy"));
|
||||
points.push(i);
|
||||
});
|
||||
|
||||
const routeLen = node.getTotalLength() * distanceScaleInput.value;
|
||||
showElevationProfile(points, routeLen, false);
|
||||
|
|
@ -13,10 +16,13 @@ function showEPForRoute(node) {
|
|||
|
||||
function showEPForRiver(node) {
|
||||
const points = [];
|
||||
debug.select("#controlPoints").selectAll("circle").each(function() {
|
||||
const i = findCell(this.getAttribute("cx"), this.getAttribute("cy"));
|
||||
points.push(i);
|
||||
});
|
||||
debug
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.each(function () {
|
||||
const i = findCell(this.getAttribute("cx"), this.getAttribute("cy"));
|
||||
points.push(i);
|
||||
});
|
||||
|
||||
const riverLen = (node.getTotalLength() / 2) * distanceScaleInput.value;
|
||||
showElevationProfile(points, riverLen, true);
|
||||
|
|
@ -29,7 +35,9 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
document.getElementById("epSave").addEventListener("click", downloadCSV);
|
||||
|
||||
$("#elevationProfile").dialog({
|
||||
title: "Elevation profile", resizable: false, width: window.width,
|
||||
title: "Elevation profile",
|
||||
resizable: false,
|
||||
width: window.width,
|
||||
close: closeElevationProfile,
|
||||
position: {my: "left top", at: "left+20 bottom-500", of: window, collision: "fit"}
|
||||
});
|
||||
|
|
@ -37,27 +45,30 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
// prevent river graphs from showing rivers as flowing uphill - remember the general slope
|
||||
let slope = 0;
|
||||
if (isRiver) {
|
||||
if (pack.cells.h[data[0]] < pack.cells.h[data[data.length-1]]) {
|
||||
if (pack.cells.h[data[0]] < pack.cells.h[data[data.length - 1]]) {
|
||||
slope = 1; // up-hill
|
||||
} else if (pack.cells.h[data[0]] > pack.cells.h[data[data.length-1]]) {
|
||||
} else if (pack.cells.h[data[0]] > pack.cells.h[data[data.length - 1]]) {
|
||||
slope = -1; // down-hill
|
||||
}
|
||||
}
|
||||
|
||||
const chartWidth = window.innerWidth-180, chartHeight = 300; // height of our land/sea profile, excluding the biomes data below
|
||||
const xOffset = 80, yOffset = 80; // this is our drawing starting point from top-left (y = 0) of SVG
|
||||
const chartWidth = window.innerWidth - 180,
|
||||
chartHeight = 300; // height of our land/sea profile, excluding the biomes data below
|
||||
const xOffset = 80,
|
||||
yOffset = 80; // this is our drawing starting point from top-left (y = 0) of SVG
|
||||
const biomesHeight = 40;
|
||||
|
||||
let lastBurgIndex = 0;
|
||||
let lastBurgCell = 0;
|
||||
let burgCount = 0;
|
||||
let chartData = {biome:[], burg:[], cell:[], height:[], mi:1000000, ma:0, mih: 100, mah: 0, points:[]};
|
||||
let chartData = {biome: [], burg: [], cell: [], height: [], mi: 1000000, ma: 0, mih: 100, mah: 0, points: []};
|
||||
for (let i = 0, prevB = 0, prevH = -1; i < data.length; i++) {
|
||||
let cell = data[i];
|
||||
let h = pack.cells.h[cell];
|
||||
if (h < 20) {
|
||||
const f = pack.features[pack.cells.f[cell]];
|
||||
if (f.type === "lake") h = f.height; else h = 20;
|
||||
if (f.type === "lake") h = f.height;
|
||||
else h = 20;
|
||||
}
|
||||
|
||||
// check for river up-hill
|
||||
|
|
@ -73,21 +84,25 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
let b = pack.cells.burg[cell];
|
||||
if (b == prevB) b = 0;
|
||||
else prevB = b;
|
||||
if (b) { burgCount++; lastBurgIndex = i; lastBurgCell = cell; }
|
||||
if (b) {
|
||||
burgCount++;
|
||||
lastBurgIndex = i;
|
||||
lastBurgCell = cell;
|
||||
}
|
||||
|
||||
chartData.biome[i] = pack.cells.biome[cell];
|
||||
chartData.burg[i] = b;
|
||||
chartData.cell[i] = cell;
|
||||
let sh = getHeight(h);
|
||||
chartData.height[i] = parseInt(sh.substr(0, sh.indexOf(' ')));
|
||||
chartData.height[i] = parseInt(sh.substr(0, sh.indexOf(" ")));
|
||||
chartData.mih = Math.min(chartData.mih, h);
|
||||
chartData.mah = Math.max(chartData.mah, h);
|
||||
chartData.mi = Math.min(chartData.mi, chartData.height[i]);
|
||||
chartData.ma = Math.max(chartData.ma, chartData.height[i]);
|
||||
}
|
||||
|
||||
if (lastBurgIndex != 0 && lastBurgCell == chartData.cell[data.length-1] && lastBurgIndex < data.length-1) {
|
||||
chartData.burg[data.length-1] = chartData.burg[lastBurgIndex];
|
||||
if (lastBurgIndex != 0 && lastBurgCell == chartData.cell[data.length - 1] && lastBurgIndex < data.length - 1) {
|
||||
chartData.burg[data.length - 1] = chartData.burg[lastBurgIndex];
|
||||
chartData.burg[lastBurgIndex] = 0;
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +111,7 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
function downloadCSV() {
|
||||
let data = "Point,X,Y,Cell,Height,Height value,Population,Burg,Burg population,Biome,Biome color,Culture,Culture color,Religion,Religion color,Province,Province color,State,State color\n"; // headers
|
||||
|
||||
for (let k=0; k < chartData.points.length; k++) {
|
||||
for (let k = 0; k < chartData.points.length; k++) {
|
||||
let cell = chartData.cell[k];
|
||||
let burg = pack.cells.burg[cell];
|
||||
let biome = pack.cells.biome[cell];
|
||||
|
|
@ -107,16 +122,16 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
let pop = pack.cells.pop[cell];
|
||||
let h = pack.cells.h[cell];
|
||||
|
||||
data += k+1 + ",";
|
||||
data += k + 1 + ",";
|
||||
data += chartData.points[k][0] + ",";
|
||||
data += chartData.points[k][1] + ",";
|
||||
data += cell + ",";
|
||||
data += getHeight(h) + ",";
|
||||
data += h + ",";
|
||||
data += rn(pop * populationRate.value) + ",";
|
||||
data += rn(pop * populationRate) + ",";
|
||||
if (burg) {
|
||||
data += pack.burgs[burg].name + ",";
|
||||
data += (pack.burgs[burg].population * populationRate.value * urbanization.value) + ",";
|
||||
data += pack.burgs[burg].population * populationRate * urbanization + ",";
|
||||
} else {
|
||||
data += ",0,";
|
||||
}
|
||||
|
|
@ -142,18 +157,27 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
chartData.points = [];
|
||||
let heightScale = 100 / parseInt(epScaleRange.value);
|
||||
|
||||
heightScale *= .9; // curves cause the heights to go slightly higher, adjust here
|
||||
heightScale *= 0.9; // curves cause the heights to go slightly higher, adjust here
|
||||
|
||||
const xscale = d3.scaleLinear().domain([0, data.length]).range([0, chartWidth]);
|
||||
const yscale = d3.scaleLinear().domain([0, chartData.ma * heightScale]).range([chartHeight, 0]);
|
||||
const yscale = d3
|
||||
.scaleLinear()
|
||||
.domain([0, chartData.ma * heightScale])
|
||||
.range([chartHeight, 0]);
|
||||
|
||||
for (let i=0; i<data.length; i++) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
chartData.points.push([xscale(i) + xOffset, yscale(chartData.height[i]) + yOffset]);
|
||||
}
|
||||
|
||||
document.getElementById("elevationGraph").innerHTML = "";
|
||||
|
||||
const chart = d3.select("#elevationGraph").append("svg").attr("width", chartWidth+120).attr("height", chartHeight+yOffset+biomesHeight).attr("id", "elevationSVG").attr("class", "epbackground");
|
||||
const chart = d3
|
||||
.select("#elevationGraph")
|
||||
.append("svg")
|
||||
.attr("width", chartWidth + 120)
|
||||
.attr("height", chartHeight + yOffset + biomesHeight)
|
||||
.attr("id", "elevationSVG")
|
||||
.attr("class", "epbackground");
|
||||
// arrow-head definition
|
||||
chart.append("defs").append("marker").attr("id", "arrowhead").attr("orient", "auto").attr("markerWidth", "2").attr("markerHeight", "4").attr("refX", "0.1").attr("refY", "2").append("path").attr("d", "M0,0 V4 L2,2 Z").attr("fill", "darkgray");
|
||||
|
||||
|
|
@ -161,41 +185,62 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
const landdef = chart.select("defs").append("linearGradient").attr("id", "landdef").attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%");
|
||||
|
||||
if (chartData.mah == chartData.mih) {
|
||||
landdef.append("stop").attr("offset", "0%").attr("style", "stop-color:" + getColor(chartData.mih, colors) + ";stop-opacity:1");
|
||||
landdef.append("stop").attr("offset", "100%").attr("style", "stop-color:" + getColor(chartData.mah, colors) + ";stop-opacity:1");
|
||||
landdef
|
||||
.append("stop")
|
||||
.attr("offset", "0%")
|
||||
.attr("style", "stop-color:" + getColor(chartData.mih, colors) + ";stop-opacity:1");
|
||||
landdef
|
||||
.append("stop")
|
||||
.attr("offset", "100%")
|
||||
.attr("style", "stop-color:" + getColor(chartData.mah, colors) + ";stop-opacity:1");
|
||||
} else {
|
||||
for (let k=chartData.mah; k >= chartData.mih; k--) {
|
||||
let perc = 1 - (k - chartData.mih) / (chartData.mah - chartData.mih);
|
||||
landdef.append("stop").attr("offset", perc*100 + "%").attr("style", "stop-color:" + getColor(k, colors) + ";stop-opacity:1");
|
||||
for (let k = chartData.mah; k >= chartData.mih; k--) {
|
||||
let perc = 1 - (k - chartData.mih) / (chartData.mah - chartData.mih);
|
||||
landdef
|
||||
.append("stop")
|
||||
.attr("offset", perc * 100 + "%")
|
||||
.attr("style", "stop-color:" + getColor(k, colors) + ";stop-opacity:1");
|
||||
}
|
||||
}
|
||||
|
||||
// land
|
||||
let curve = d3.line().curve(d3.curveBasis); // see https://github.com/d3/d3-shape#curves
|
||||
let epCurveIndex = parseInt(epCurve.selectedIndex);
|
||||
switch(epCurveIndex) {
|
||||
case 0 : curve = d3.line().curve(d3.curveLinear); break;
|
||||
case 1 : curve = d3.line().curve(d3.curveBasis); break;
|
||||
case 2 : curve = d3.line().curve(d3.curveBundle.beta(1)); break;
|
||||
case 3 : curve = d3.line().curve(d3.curveCatmullRom.alpha(0.5)); break;
|
||||
case 4 : curve = d3.line().curve(d3.curveMonotoneX); break;
|
||||
case 5 : curve = d3.line().curve(d3.curveNatural); break;
|
||||
switch (epCurveIndex) {
|
||||
case 0:
|
||||
curve = d3.line().curve(d3.curveLinear);
|
||||
break;
|
||||
case 1:
|
||||
curve = d3.line().curve(d3.curveBasis);
|
||||
break;
|
||||
case 2:
|
||||
curve = d3.line().curve(d3.curveBundle.beta(1));
|
||||
break;
|
||||
case 3:
|
||||
curve = d3.line().curve(d3.curveCatmullRom.alpha(0.5));
|
||||
break;
|
||||
case 4:
|
||||
curve = d3.line().curve(d3.curveMonotoneX);
|
||||
break;
|
||||
case 5:
|
||||
curve = d3.line().curve(d3.curveNatural);
|
||||
break;
|
||||
}
|
||||
|
||||
// copy the points so that we can add extra straight pieces, else we get curves at the ends of the chart
|
||||
let extra = chartData.points.slice();
|
||||
let path = curve(extra);
|
||||
// this completes the right-hand side and bottom of our land "polygon"
|
||||
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(extra[extra.length-1][1]);
|
||||
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||
path += " L" + parseInt(xscale(0) + +xOffset) +"," + parseInt(yscale(0) + +yOffset);
|
||||
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(extra[extra.length - 1][1]);
|
||||
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||
path += " L" + parseInt(xscale(0) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||
path += "Z";
|
||||
chart.append("g").attr("id", "epland").append("path").attr("d", path).attr("stroke", "purple").attr("stroke-width", "0").attr("fill", "url(#landdef)");
|
||||
|
||||
// biome / heights
|
||||
let g = chart.append("g").attr("id", "epbiomes");
|
||||
const hu = heightUnit.value;
|
||||
for(let k=0; k < chartData.points.length; k++) {
|
||||
for (let k = 0; k < chartData.points.length; k++) {
|
||||
const x = chartData.points[k][0];
|
||||
const y = yOffset + chartHeight;
|
||||
const c = biomesData.color[chartData.biome[k]];
|
||||
|
|
@ -207,45 +252,53 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
const state = pack.cells.state[cell];
|
||||
let pop = pack.cells.pop[cell];
|
||||
if (chartData.burg[k]) {
|
||||
pop += pack.burgs[chartData.burg[k]].population * urbanization.value;
|
||||
pop += pack.burgs[chartData.burg[k]].population * urbanization;
|
||||
}
|
||||
|
||||
const populationDesc = rn(pop * populationRate.value);
|
||||
const populationDesc = rn(pop * populationRate);
|
||||
|
||||
const provinceDesc = province ? ", " + pack.provinces[province].name : "";
|
||||
const dataTip = biomesData.name[chartData.biome[k]] +
|
||||
provinceDesc +
|
||||
", " + pack.states[state].name +
|
||||
", " + pack.religions[religion].name +
|
||||
", " + pack.cultures[culture].name +
|
||||
" (height: " + chartData.height[k] + " " + hu + ", population " + populationDesc + ", cell " + chartData.cell[k] + ")";
|
||||
const dataTip = biomesData.name[chartData.biome[k]] + provinceDesc + ", " + pack.states[state].name + ", " + pack.religions[religion].name + ", " + pack.cultures[culture].name + " (height: " + chartData.height[k] + " " + hu + ", population " + populationDesc + ", cell " + chartData.cell[k] + ")";
|
||||
|
||||
g.append("rect").attr("stroke", c).attr("fill", c).attr("x", x).attr("y", y).attr("width", xscale(1)).attr("height", 15).attr("data-tip", dataTip);
|
||||
}
|
||||
|
||||
const xAxis = d3.axisBottom(xscale).ticks(10).tickFormat(function(d){ return (rn(d / chartData.points.length * routeLen) + " " + distanceUnitInput.value);});
|
||||
const yAxis = d3.axisLeft(yscale).ticks(5).tickFormat(function(d) { return d + " " + hu; });
|
||||
const xAxis = d3
|
||||
.axisBottom(xscale)
|
||||
.ticks(10)
|
||||
.tickFormat(function (d) {
|
||||
return rn((d / chartData.points.length) * routeLen) + " " + distanceUnitInput.value;
|
||||
});
|
||||
const yAxis = d3
|
||||
.axisLeft(yscale)
|
||||
.ticks(5)
|
||||
.tickFormat(function (d) {
|
||||
return d + " " + hu;
|
||||
});
|
||||
|
||||
const xGrid = d3.axisBottom(xscale).ticks(10).tickSize(-chartHeight).tickFormat("");
|
||||
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth).tickFormat("");
|
||||
|
||||
chart.append("g")
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epxaxis")
|
||||
.attr("transform", "translate(" + xOffset + "," + parseInt(chartHeight + +yOffset + 20) + ")")
|
||||
.call(xAxis)
|
||||
.selectAll("text")
|
||||
.style("text-anchor", "center")
|
||||
.attr("transform", function(d) {
|
||||
return "rotate(0)" // used to rotate labels, - anti-clockwise, + clockwise
|
||||
.attr("transform", function (d) {
|
||||
return "rotate(0)"; // used to rotate labels, - anti-clockwise, + clockwise
|
||||
});
|
||||
|
||||
chart.append("g")
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epyaxis")
|
||||
.attr("transform", "translate(" + parseInt(+xOffset-10) + "," + parseInt(+yOffset) + ")")
|
||||
.attr("transform", "translate(" + parseInt(+xOffset - 10) + "," + parseInt(+yOffset) + ")")
|
||||
.call(yAxis);
|
||||
|
||||
// add the X gridlines
|
||||
chart.append("g")
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epxgrid")
|
||||
.attr("class", "epgrid")
|
||||
.attr("stroke-dasharray", "4 1")
|
||||
|
|
@ -253,7 +306,8 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
.call(xGrid);
|
||||
|
||||
// add the Y gridlines
|
||||
chart.append("g")
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epygrid")
|
||||
.attr("class", "epgrid")
|
||||
.attr("stroke-dasharray", "4 1")
|
||||
|
|
@ -266,22 +320,33 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
const add = 15;
|
||||
|
||||
let xwidth = chartData.points[1][0] - chartData.points[0][0];
|
||||
for (let k=0; k<chartData.points.length; k++) {
|
||||
for (let k = 0; k < chartData.points.length; k++) {
|
||||
if (chartData.burg[k] > 0) {
|
||||
let b = chartData.burg[k];
|
||||
|
||||
let x1 = chartData.points[k][0]; // left side of graph by default
|
||||
if (k > 0) x1 += xwidth/2; // center it if not first
|
||||
if (k == chartData.points.length-1) x1 = chartWidth + xOffset; // right part of graph
|
||||
y1+=add;
|
||||
if (k > 0) x1 += xwidth / 2; // center it if not first
|
||||
if (k == chartData.points.length - 1) x1 = chartWidth + xOffset; // right part of graph
|
||||
y1 += add;
|
||||
if (y1 >= yOffset) y1 = add;
|
||||
|
||||
// burg name
|
||||
g.append("text").attr("id", "ep" + b).attr("class", "epburglabel").attr("x", x1).attr("y", y1).attr("text-anchor", "middle");
|
||||
g.append("text")
|
||||
.attr("id", "ep" + b)
|
||||
.attr("class", "epburglabel")
|
||||
.attr("x", x1)
|
||||
.attr("y", y1)
|
||||
.attr("text-anchor", "middle");
|
||||
document.getElementById("ep" + b).innerHTML = pack.burgs[b].name;
|
||||
|
||||
// arrow from burg name to graph line
|
||||
g.append("path").attr("id", "eparrow" + b).attr("d", "M" + x1.toString() + "," + (y1+3).toString() + "L" + x1.toString() + "," + parseInt(chartData.points[k][1]-3).toString()).attr("stroke", "darkgray").attr("fill", "lightgray").attr("stroke-width", "1").attr("marker-end", "url(#arrowhead)");
|
||||
g.append("path")
|
||||
.attr("id", "eparrow" + b)
|
||||
.attr("d", "M" + x1.toString() + "," + (y1 + 3).toString() + "L" + x1.toString() + "," + parseInt(chartData.points[k][1] - 3).toString())
|
||||
.attr("stroke", "darkgray")
|
||||
.attr("fill", "lightgray")
|
||||
.attr("stroke-width", "1")
|
||||
.attr("marker-end", "url(#arrowhead)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
// Module to store general UI functions
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
// fit full-screen map if window is resized
|
||||
$(window).resize(function(e) {
|
||||
if (localStorage.getItem("mapWidth") && localStorage.getItem("mapHeight")) return;
|
||||
$(window).resize(function (e) {
|
||||
if (localStorage.getItem('mapWidth') && localStorage.getItem('mapHeight')) return;
|
||||
mapWidthInput.value = window.innerWidth;
|
||||
mapHeightInput.value = window.innerHeight;
|
||||
changeMapSize();
|
||||
});
|
||||
|
||||
window.onbeforeunload = () => "Are you sure you want to navigate away?";
|
||||
window.onbeforeunload = () => 'Are you sure you want to navigate away?';
|
||||
|
||||
// Tooltips
|
||||
const tooltip = document.getElementById("tooltip");
|
||||
const tooltip = document.getElementById('tooltip');
|
||||
|
||||
// show tip for non-svg elemets with data-tip
|
||||
document.getElementById("dialogs").addEventListener("mousemove", showDataTip);
|
||||
document.getElementById("optionsContainer").addEventListener("mousemove", showDataTip);
|
||||
document.getElementById("exitCustomization").addEventListener("mousemove", showDataTip);
|
||||
document.getElementById('dialogs').addEventListener('mousemove', showDataTip);
|
||||
document.getElementById('optionsContainer').addEventListener('mousemove', showDataTip);
|
||||
document.getElementById('exitCustomization').addEventListener('mousemove', showDataTip);
|
||||
|
||||
/**
|
||||
* @param {string} tip Tooltip text
|
||||
|
|
@ -25,15 +25,15 @@ document.getElementById("exitCustomization").addEventListener("mousemove", showD
|
|||
* @param {string} type Message type (color): error, warn, success
|
||||
* @param {number} time Timeout to auto hide, ms
|
||||
*/
|
||||
function tip(tip = "Tip is undefined", main, type, time) {
|
||||
function tip(tip = 'Tip is undefined', main, type, time) {
|
||||
tooltip.innerHTML = tip;
|
||||
tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)";
|
||||
if (type === "error") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)"; else
|
||||
if (type === "warn") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)"; else
|
||||
if (type === "success") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)";
|
||||
tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)';
|
||||
if (type === 'error') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)';
|
||||
else if (type === 'warn') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)';
|
||||
else if (type === 'success') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)';
|
||||
|
||||
if (main) tooltip.dataset.main = tip; // set main tip
|
||||
if (time) setTimeout(() => tooltip.dataset.main = "", time); // clear main in some time
|
||||
if (time) setTimeout(() => (tooltip.dataset.main = ''), time); // clear main in some time
|
||||
}
|
||||
|
||||
function showMainTip() {
|
||||
|
|
@ -41,8 +41,8 @@ function showMainTip() {
|
|||
}
|
||||
|
||||
function clearMainTip() {
|
||||
tooltip.dataset.main = "";
|
||||
tooltip.innerHTML = "";
|
||||
tooltip.dataset.main = '';
|
||||
tooltip.innerHTML = '';
|
||||
}
|
||||
|
||||
// show tip at the bottom of the screen, consider possible translation
|
||||
|
|
@ -62,7 +62,8 @@ function mouseMove() {
|
|||
if (i === undefined) return;
|
||||
showNotes(d3.event, i);
|
||||
const g = findGridCell(point[0], point[1]); // grid cell id
|
||||
if (tooltip.dataset.main) showMainTip(); else showMapTooltip(point, d3.event, i, g);
|
||||
if (tooltip.dataset.main) showMainTip();
|
||||
else showMapTooltip(point, d3.event, i, g);
|
||||
if (cellInfo.offsetParent) updateCellInfo(point, i, g);
|
||||
}
|
||||
|
||||
|
|
@ -70,24 +71,24 @@ function mouseMove() {
|
|||
function showNotes(e, i) {
|
||||
if (notesEditor.offsetParent) return;
|
||||
let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id;
|
||||
if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id; else
|
||||
if (e.target.parentNode.parentNode.id === "burgIcons") id = "burg" + e.target.dataset.id;
|
||||
if (e.target.parentNode.parentNode.id === 'burgLabels') id = 'burg' + e.target.dataset.id;
|
||||
else if (e.target.parentNode.parentNode.id === 'burgIcons') id = 'burg' + e.target.dataset.id;
|
||||
|
||||
const note = notes.find(note => note.id === id);
|
||||
if (note !== undefined && note.legend !== "") {
|
||||
document.getElementById("notes").style.display = "block";
|
||||
document.getElementById("notesHeader").innerHTML = note.name;
|
||||
document.getElementById("notesBody").innerHTML = note.legend;
|
||||
const note = notes.find((note) => note.id === id);
|
||||
if (note !== undefined && note.legend !== '') {
|
||||
document.getElementById('notes').style.display = 'block';
|
||||
document.getElementById('notesHeader').innerHTML = note.name;
|
||||
document.getElementById('notesBody').innerHTML = note.legend;
|
||||
} else if (!options.pinNotes) {
|
||||
document.getElementById("notes").style.display = "none";
|
||||
document.getElementById("notesHeader").innerHTML = "";
|
||||
document.getElementById("notesBody").innerHTML = "";
|
||||
document.getElementById('notes').style.display = 'none';
|
||||
document.getElementById('notesHeader').innerHTML = '';
|
||||
document.getElementById('notesBody').innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
// show viewbox tooltip if main tooltip is blank
|
||||
function showMapTooltip(point, e, i, g) {
|
||||
tip(""); // clear tip
|
||||
tip(''); // clear tip
|
||||
const path = e.composedPath ? e.composedPath() : getComposedPath(e.target); // apply polyfill
|
||||
if (!path[path.length - 8]) return;
|
||||
const group = path[path.length - 7].id;
|
||||
|
|
@ -95,16 +96,14 @@ function showMapTooltip(point, e, i, g) {
|
|||
const land = pack.cells.h[i] >= 20;
|
||||
|
||||
// specific elements
|
||||
if (group === "armies") {
|
||||
tip(e.target.parentNode.dataset.name + ". Click to edit");
|
||||
if (group === 'armies') {
|
||||
tip(e.target.parentNode.dataset.name + '. Click to edit');
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === "emblems" && e.target.tagName === "use") {
|
||||
if (group === 'emblems' && e.target.tagName === 'use') {
|
||||
const parent = e.target.parentNode;
|
||||
const [g, type] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] :
|
||||
parent.id === "provinceEmblems" ? [pack.provinces, "province"] :
|
||||
[pack.states, "state"];
|
||||
const [g, type] = parent.id === 'burgEmblems' ? [pack.burgs, 'burg'] : parent.id === 'provinceEmblems' ? [pack.provinces, 'province'] : [pack.states, 'state'];
|
||||
const i = +e.target.dataset.i;
|
||||
if (event.shiftKey) highlightEmblemElement(type, g[i]);
|
||||
|
||||
|
|
@ -116,126 +115,168 @@ function showMapTooltip(point, e, i, g) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (group === "goods") {
|
||||
if (group === 'goods') {
|
||||
const id = +e.target.dataset.i;
|
||||
const resource = pack.resources.find(resource => resource.i === id);
|
||||
tip("Resource: " + resource.name);
|
||||
const resource = pack.resources.find((resource) => resource.i === id);
|
||||
tip('Resource: ' + resource.name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === "rivers") {
|
||||
if (group === 'rivers') {
|
||||
const river = +e.target.id.slice(5);
|
||||
const r = pack.rivers.find(r => r.i === river);
|
||||
const name = r ? r.name + " " + r.type : "";
|
||||
tip(name + ". Click to edit");
|
||||
const r = pack.rivers.find((r) => r.i === river);
|
||||
const name = r ? r.name + ' ' + r.type : '';
|
||||
tip(name + '. Click to edit');
|
||||
if (riversOverview.offsetParent) highlightEditorLine(riversOverview, river, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === "routes") {tip("Click to edit the Route"); return;}
|
||||
if (group === "terrain") {tip("Click to edit the Relief Icon"); return;}
|
||||
if (subgroup === "burgLabels" || subgroup === "burgIcons") {
|
||||
if (group === 'routes') {
|
||||
tip('Click to edit the Route');
|
||||
return;
|
||||
}
|
||||
if (group === 'terrain') {
|
||||
tip('Click to edit the Relief Icon');
|
||||
return;
|
||||
}
|
||||
if (subgroup === 'burgLabels' || subgroup === 'burgIcons') {
|
||||
const burg = +path[path.length - 10].dataset.id;
|
||||
const b = pack.burgs[burg];
|
||||
const population = si(b.population * populationRate.value * urbanization.value);
|
||||
const population = si(b.population * populationRate * urbanization);
|
||||
tip(`${b.name}. Population: ${population}. Click to edit`);
|
||||
if (burgsOverview.offsetParent) highlightEditorLine(burgsOverview, burg, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === "labels") {tip("Click to edit the Label"); return;}
|
||||
if (group === "markers") {tip("Click to edit the Marker"); return;}
|
||||
if (group === "ruler") {
|
||||
const tag = e.target.tagName;
|
||||
const className = e.target.getAttribute("class");
|
||||
if (tag === "circle" && className === "edge") {tip("Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point"); return;}
|
||||
if (tag === "circle" && className === "control") {tip("Drag to adjust. Hold Shifta and drag to keep axial direction. Click to remove the point"); return;}
|
||||
if (tag === "circle") {tip("Drag to adjust the measurer"); return;}
|
||||
if (tag === "polyline") {tip("Click on drag to add a control point"); return;}
|
||||
if (tag === "path") {tip("Drag to move the measurer"); return;}
|
||||
if (tag === "text") {tip("Drag to move, click to remove the measurer"); return;}
|
||||
if (group === 'labels') {
|
||||
tip('Click to edit the Label');
|
||||
return;
|
||||
}
|
||||
if (subgroup === "burgIcons") {tip("Click to edit the Burg"); return;}
|
||||
if (subgroup === "burgLabels") {tip("Click to edit the Burg"); return;}
|
||||
if (group === "lakes" && !land) {
|
||||
if (group === 'markers') {
|
||||
tip('Click to edit the Marker');
|
||||
return;
|
||||
}
|
||||
if (group === 'ruler') {
|
||||
const tag = e.target.tagName;
|
||||
const className = e.target.getAttribute('class');
|
||||
if (tag === 'circle' && className === 'edge') {
|
||||
tip('Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point');
|
||||
return;
|
||||
}
|
||||
if (tag === 'circle' && className === 'control') {
|
||||
tip('Drag to adjust. Hold Shifta and drag to keep axial direction. Click to remove the point');
|
||||
return;
|
||||
}
|
||||
if (tag === 'circle') {
|
||||
tip('Drag to adjust the measurer');
|
||||
return;
|
||||
}
|
||||
if (tag === 'polyline') {
|
||||
tip('Click on drag to add a control point');
|
||||
return;
|
||||
}
|
||||
if (tag === 'path') {
|
||||
tip('Drag to move the measurer');
|
||||
return;
|
||||
}
|
||||
if (tag === 'text') {
|
||||
tip('Drag to move, click to remove the measurer');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (subgroup === 'burgIcons') {
|
||||
tip('Click to edit the Burg');
|
||||
return;
|
||||
}
|
||||
if (subgroup === 'burgLabels') {
|
||||
tip('Click to edit the Burg');
|
||||
return;
|
||||
}
|
||||
if (group === 'lakes' && !land) {
|
||||
const lakeId = +e.target.dataset.f;
|
||||
const name = pack.features[lakeId]?.name;
|
||||
const fullName = subgroup === "freshwater" ? name : name + " " + subgroup;
|
||||
tip(`${fullName} lake. Click to edit`); return;
|
||||
const fullName = subgroup === 'freshwater' ? name : name + ' ' + subgroup;
|
||||
tip(`${fullName} lake. Click to edit`);
|
||||
return;
|
||||
}
|
||||
if (group === "coastline") {tip("Click to edit the coastline"); return;}
|
||||
if (group === "zones") {
|
||||
const zone = path[path.length-8];
|
||||
if (group === 'coastline') {
|
||||
tip('Click to edit the coastline');
|
||||
return;
|
||||
}
|
||||
if (group === 'zones') {
|
||||
const zone = path[path.length - 8];
|
||||
tip(zone.dataset.description);
|
||||
if (zonesEditor.offsetParent) highlightEditorLine(zonesEditor, zone.id, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === "ice") {tip("Click to edit the Ice"); return;}
|
||||
if (group === 'ice') {
|
||||
tip('Click to edit the Ice');
|
||||
return;
|
||||
}
|
||||
|
||||
// covering elements
|
||||
if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: "+ getFriendlyPrecipitation(i)); else
|
||||
if (layerIsOn("togglePopulation")) tip(getPopulationTip(i)); else
|
||||
if (layerIsOn("toggleTemp")) tip("Temperature: " + convertTemperature(grid.cells.temp[g])); else
|
||||
if (layerIsOn("toggleBiomes") && pack.cells.biome[i]) {
|
||||
const biome = pack.cells.biome[i]
|
||||
tip("Biome: " + biomesData.name[biome]);
|
||||
if (layerIsOn('togglePrec') && land) tip('Annual Precipitation: ' + getFriendlyPrecipitation(i));
|
||||
else if (layerIsOn('togglePopulation')) tip(getPopulationTip(i));
|
||||
else if (layerIsOn('toggleTemp')) tip('Temperature: ' + convertTemperature(grid.cells.temp[g]));
|
||||
else if (layerIsOn('toggleBiomes') && pack.cells.biome[i]) {
|
||||
const biome = pack.cells.biome[i];
|
||||
tip('Biome: ' + biomesData.name[biome]);
|
||||
if (biomesEditor.offsetParent) highlightEditorLine(biomesEditor, biome);
|
||||
} else
|
||||
if (layerIsOn("toggleReligions") && pack.cells.religion[i]) {
|
||||
} else if (layerIsOn('toggleReligions') && pack.cells.religion[i]) {
|
||||
const religion = pack.cells.religion[i];
|
||||
const r = pack.religions[religion];
|
||||
const type = r.type === "Cult" || r.type == "Heresy" ? r.type : r.type + " religion";
|
||||
tip(type + ": " + r.name);
|
||||
const type = r.type === 'Cult' || r.type == 'Heresy' ? r.type : r.type + ' religion';
|
||||
tip(type + ': ' + r.name);
|
||||
if (religionsEditor.offsetParent) highlightEditorLine(religionsEditor, religion);
|
||||
} else
|
||||
if (pack.cells.state[i] && (layerIsOn("toggleProvinces") || layerIsOn("toggleStates"))) {
|
||||
} else if (pack.cells.state[i] && (layerIsOn('toggleProvinces') || layerIsOn('toggleStates'))) {
|
||||
const state = pack.cells.state[i];
|
||||
const stateName = pack.states[state].fullName;
|
||||
const province = pack.cells.province[i];
|
||||
const prov = province ? pack.provinces[province].fullName + ", " : "";
|
||||
const prov = province ? pack.provinces[province].fullName + ', ' : '';
|
||||
tip(prov + stateName);
|
||||
if (statesEditor.offsetParent) highlightEditorLine(statesEditor, state);
|
||||
if (diplomacyEditor.offsetParent) highlightEditorLine(diplomacyEditor, state);
|
||||
if (militaryOverview.offsetParent) highlightEditorLine(militaryOverview, state);
|
||||
if (provincesEditor.offsetParent) highlightEditorLine(provincesEditor, province);
|
||||
} else
|
||||
if (layerIsOn("toggleCultures") && pack.cells.culture[i]) {
|
||||
} else if (layerIsOn('toggleCultures') && pack.cells.culture[i]) {
|
||||
const culture = pack.cells.culture[i];
|
||||
tip("Culture: " + pack.cultures[culture].name);
|
||||
tip('Culture: ' + pack.cultures[culture].name);
|
||||
if (culturesEditor.offsetParent) highlightEditorLine(culturesEditor, culture);
|
||||
} else
|
||||
if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point));
|
||||
} else if (layerIsOn('toggleHeight')) tip('Height: ' + getFriendlyHeight(point));
|
||||
}
|
||||
|
||||
function highlightEditorLine(editor, id, timeout = 15000) {
|
||||
Array.from(editor.getElementsByClassName("states hovered")).forEach(el => el.classList.remove("hovered")); // clear all hovered
|
||||
const hovered = Array.from(editor.querySelectorAll("div")).find(el => el.dataset.id == id);
|
||||
if (hovered) hovered.classList.add("hovered"); // add hovered class
|
||||
if (timeout) setTimeout(() => {hovered && hovered.classList.remove("hovered")}, timeout);
|
||||
Array.from(editor.getElementsByClassName('states hovered')).forEach((el) => el.classList.remove('hovered')); // clear all hovered
|
||||
const hovered = Array.from(editor.querySelectorAll('div')).find((el) => el.dataset.id == id);
|
||||
if (hovered) hovered.classList.add('hovered'); // add hovered class
|
||||
if (timeout)
|
||||
setTimeout(() => {
|
||||
hovered && hovered.classList.remove('hovered');
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
// get cell info on mouse move
|
||||
function updateCellInfo(point, i, g) {
|
||||
const cells = pack.cells;
|
||||
const x = infoX.innerHTML = rn(point[0]);
|
||||
const y = infoY.innerHTML = rn(point[1]);
|
||||
const x = (infoX.innerHTML = rn(point[0]));
|
||||
const y = (infoY.innerHTML = rn(point[1]));
|
||||
const f = cells.f[i];
|
||||
infoLat.innerHTML = toDMS(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT, "lat");
|
||||
infoLon.innerHTML = toDMS(mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT, "lon");
|
||||
infoLat.innerHTML = toDMS(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT, 'lat');
|
||||
infoLon.innerHTML = toDMS(mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT, 'lon');
|
||||
|
||||
infoCell.innerHTML = i;
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
infoArea.innerHTML = cells.area[i] ? si(cells.area[i] * distanceScaleInput.value ** 2) + unit : "n/a";
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
infoArea.innerHTML = cells.area[i] ? si(cells.area[i] * distanceScaleInput.value ** 2) + unit : 'n/a';
|
||||
infoEvelation.innerHTML = getElevation(pack.features[f], pack.cells.h[i]);
|
||||
infoDepth.innerHTML = getDepth(pack.features[f], pack.cells.h[i], point);
|
||||
infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]);
|
||||
infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : "n/a";
|
||||
infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : "no";
|
||||
infoState.innerHTML = cells.h[i] >= 20 ? cells.state[i] ? `${pack.states[cells.state[i]].fullName} (${cells.state[i]})` : "neutral lands (0)" : "no";
|
||||
infoProvince.innerHTML = cells.province[i] ? `${pack.provinces[cells.province[i]].fullName} (${cells.province[i]})` : "no";
|
||||
infoCulture.innerHTML = cells.culture[i] ? `${pack.cultures[cells.culture[i]].name} (${cells.culture[i]})` : "no";
|
||||
infoReligion.innerHTML = cells.religion[i] ? `${pack.religions[cells.religion[i]].name} (${cells.religion[i]})` : "no";
|
||||
infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : 'n/a';
|
||||
infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : 'no';
|
||||
infoState.innerHTML = cells.h[i] >= 20 ? (cells.state[i] ? `${pack.states[cells.state[i]].fullName} (${cells.state[i]})` : 'neutral lands (0)') : 'no';
|
||||
infoProvince.innerHTML = cells.province[i] ? `${pack.provinces[cells.province[i]].fullName} (${cells.province[i]})` : 'no';
|
||||
infoCulture.innerHTML = cells.culture[i] ? `${pack.cultures[cells.culture[i]].name} (${cells.culture[i]})` : 'no';
|
||||
infoReligion.innerHTML = cells.religion[i] ? `${pack.religions[cells.religion[i]].name} (${cells.religion[i]})` : 'no';
|
||||
infoPopulation.innerHTML = getFriendlyPopulation(i);
|
||||
infoBurg.innerHTML = cells.burg[i] ? pack.burgs[cells.burg[i]].name + " (" + cells.burg[i] + ")" : "no";
|
||||
infoFeature.innerHTML = f ? pack.features[f].group + " (" + f + ")" : "n/a";
|
||||
infoBurg.innerHTML = cells.burg[i] ? pack.burgs[cells.burg[i]].name + ' (' + cells.burg[i] + ')' : 'no';
|
||||
infoFeature.innerHTML = f ? pack.features[f].group + ' (' + f + ')' : 'n/a';
|
||||
infoBiome.innerHTML = biomesData.name[cells.biome[i]];
|
||||
}
|
||||
|
||||
|
|
@ -245,23 +286,29 @@ function toDMS(coord, c) {
|
|||
const minutesNotTruncated = (Math.abs(coord) - degrees) * 60;
|
||||
const minutes = Math.floor(minutesNotTruncated);
|
||||
const seconds = Math.floor((minutesNotTruncated - minutes) * 60);
|
||||
const cardinal = c === "lat" ? coord >= 0 ? "N" : "S" : coord >= 0 ? "E" : "W";
|
||||
return degrees + "° " + minutes + "′ " + seconds + "″ " + cardinal;
|
||||
const cardinal = c === 'lat' ? (coord >= 0 ? 'N' : 'S') : coord >= 0 ? 'E' : 'W';
|
||||
return degrees + '° ' + minutes + '′ ' + seconds + '″ ' + cardinal;
|
||||
}
|
||||
|
||||
// get surface elevation
|
||||
function getElevation(f, h) {
|
||||
if (f.land) return getHeight(h) + " (" + h + ")"; // land: usual height
|
||||
if (f.border) return "0 " + heightUnit.value; // ocean: 0
|
||||
if (f.type === "lake") return getHeight(f.height) + " (" + f.height + ")"; // lake: defined on river generation
|
||||
if (f.land) return getHeight(h) + ' (' + h + ')'; // land: usual height
|
||||
if (f.border) return '0 ' + heightUnit.value; // ocean: 0
|
||||
if (f.type === 'lake') return getHeight(f.height) + ' (' + f.height + ')'; // lake: defined on river generation
|
||||
}
|
||||
|
||||
// get water depth
|
||||
function getDepth(f, h, p) {
|
||||
if (f.land) return "0 " + heightUnit.value; // land: 0
|
||||
if (!f.border) return getHeight(h, "abs"); // lake: pack abs height
|
||||
if (f.land) return '0 ' + heightUnit.value; // land: 0
|
||||
|
||||
// lake: difference between surface and bottom
|
||||
const gridH = grid.cells.h[findGridCell(p[0], p[1])];
|
||||
return getHeight(gridH, "abs"); // ocean: grig height
|
||||
if (f.type === 'lake') {
|
||||
const depth = gridH === 19 ? f.height / 2 : gridH;
|
||||
return getHeight(depth, 'abs');
|
||||
}
|
||||
|
||||
return getHeight(gridH, 'abs'); // ocean: grid height
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) height value from map data
|
||||
|
|
@ -275,107 +322,139 @@ function getFriendlyHeight(p) {
|
|||
function getHeight(h, abs) {
|
||||
const unit = heightUnit.value;
|
||||
let unitRatio = 3.281; // default calculations are in feet
|
||||
if (unit === "m") unitRatio = 1; // if meter
|
||||
else if (unit === "f") unitRatio = 0.5468; // if fathom
|
||||
if (unit === 'm') unitRatio = 1;
|
||||
// if meter
|
||||
else if (unit === 'f') unitRatio = 0.5468; // if fathom
|
||||
|
||||
let height = -990;
|
||||
if (h >= 20) height = Math.pow(h - 18, +heightExponentInput.value);
|
||||
else if (h < 20 && h > 0) height = (h - 20) / h * 50;
|
||||
else if (h < 20 && h > 0) height = ((h - 20) / h) * 50;
|
||||
|
||||
if (abs) height = Math.abs(height);
|
||||
return rn(height * unitRatio) + " " + unit;
|
||||
return rn(height * unitRatio) + ' ' + unit;
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) precipitation value from map data
|
||||
function getFriendlyPrecipitation(i) {
|
||||
const prec = grid.cells.prec[pack.cells.g[i]];
|
||||
return prec * 100 + " mm";
|
||||
return prec * 100 + ' mm';
|
||||
}
|
||||
|
||||
function getRiverInfo(id) {
|
||||
const r = pack.rivers.find(r => r.i == id);
|
||||
return r ? `${r.name} ${r.type} (${id})` : "n/a";
|
||||
const r = pack.rivers.find((r) => r.i == id);
|
||||
return r ? `${r.name} ${r.type} (${id})` : 'n/a';
|
||||
}
|
||||
|
||||
function getCellPopulation(i) {
|
||||
const rural = pack.cells.pop[i] * populationRate.value;
|
||||
const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate.value * urbanization.value : 0;
|
||||
const rural = pack.cells.pop[i] * populationRate;
|
||||
const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate * urbanization : 0;
|
||||
return [rural, urban];
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) population value from map data
|
||||
function getFriendlyPopulation(i) {
|
||||
const [rural, urban] = getCellPopulation(i);
|
||||
return `${si(rural+urban)} (${si(rural)} rural, urban ${si(urban)})`;
|
||||
return `${si(rural + urban)} (${si(rural)} rural, urban ${si(urban)})`;
|
||||
}
|
||||
|
||||
function getPopulationTip(i) {
|
||||
const [rural, urban] = getCellPopulation(i);
|
||||
return `Cell population: ${si(rural+urban)}; Rural: ${si(rural)}; Urban: ${si(urban)}`;
|
||||
return `Cell population: ${si(rural + urban)}; Rural: ${si(rural)}; Urban: ${si(urban)}`;
|
||||
}
|
||||
|
||||
function highlightEmblemElement(type, el) {
|
||||
const i = el.i, cells = pack.cells;
|
||||
const animation = d3.transition().duration(1000).ease(d3.easeSinIn);
|
||||
const i = el.i,
|
||||
cells = pack.cells;
|
||||
const animation = d3.transition().duration(1000).ease(d3.easeSinIn);
|
||||
|
||||
if (type === "burg") {
|
||||
const {x, y} = el;
|
||||
debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 0)
|
||||
.attr("fill", "none").attr("stroke", "#d0240f").attr("stroke-width", 1).attr("opacity", 1)
|
||||
.transition(animation).attr("r", 20).attr("opacity", .1).attr("stroke-width", 0).remove();
|
||||
return;
|
||||
}
|
||||
if (type === 'burg') {
|
||||
const {x, y} = el;
|
||||
debug
|
||||
.append('circle')
|
||||
.attr('cx', x)
|
||||
.attr('cy', y)
|
||||
.attr('r', 0)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#d0240f')
|
||||
.attr('stroke-width', 1)
|
||||
.attr('opacity', 1)
|
||||
.transition(animation)
|
||||
.attr('r', 20)
|
||||
.attr('opacity', 0.1)
|
||||
.attr('stroke-width', 0)
|
||||
.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const [x, y] = el.pole || pack.cells.p[el.center];
|
||||
const obj = type === "state" ? cells.state : cells.province;
|
||||
const borderCells = cells.i.filter(id => obj[id] === i && cells.c[id].some(n => obj[n] !== i));
|
||||
const data = Array.from(borderCells).filter((c, i) => !(i%2)).map(i => cells.p[i]).map(i => [i[0], i[1], Math.hypot(i[0]-x, i[1]-y)]);
|
||||
const [x, y] = el.pole || pack.cells.p[el.center];
|
||||
const obj = type === 'state' ? cells.state : cells.province;
|
||||
const borderCells = cells.i.filter((id) => obj[id] === i && cells.c[id].some((n) => obj[n] !== i));
|
||||
const data = Array.from(borderCells)
|
||||
.filter((c, i) => !(i % 2))
|
||||
.map((i) => cells.p[i])
|
||||
.map((i) => [i[0], i[1], Math.hypot(i[0] - x, i[1] - y)]);
|
||||
|
||||
debug.selectAll("line").data(data).enter().append("line")
|
||||
.attr("x1", x).attr("y1", y).attr("x2", d => d[0]).attr("y2", d => d[1])
|
||||
.attr("stroke", "#d0240f").attr("stroke-width", .5).attr("opacity", .2)
|
||||
.attr("stroke-dashoffset", d => d[2]).attr("stroke-dasharray", d => d[2])
|
||||
.transition(animation).attr("stroke-dashoffset", 0).attr("opacity", 1)
|
||||
.transition(animation).delay(1000).attr("stroke-dashoffset", d => d[2]).attr("opacity", 0).remove();
|
||||
debug
|
||||
.selectAll('line')
|
||||
.data(data)
|
||||
.enter()
|
||||
.append('line')
|
||||
.attr('x1', x)
|
||||
.attr('y1', y)
|
||||
.attr('x2', (d) => d[0])
|
||||
.attr('y2', (d) => d[1])
|
||||
.attr('stroke', '#d0240f')
|
||||
.attr('stroke-width', 0.5)
|
||||
.attr('opacity', 0.2)
|
||||
.attr('stroke-dashoffset', (d) => d[2])
|
||||
.attr('stroke-dasharray', (d) => d[2])
|
||||
.transition(animation)
|
||||
.attr('stroke-dashoffset', 0)
|
||||
.attr('opacity', 1)
|
||||
.transition(animation)
|
||||
.delay(1000)
|
||||
.attr('stroke-dashoffset', (d) => d[2])
|
||||
.attr('opacity', 0)
|
||||
.remove();
|
||||
}
|
||||
|
||||
// assign lock behavior
|
||||
document.querySelectorAll("[data-locked]").forEach(function(e) {
|
||||
e.addEventListener("mouseover", function(event) {
|
||||
if (this.className === "icon-lock") tip("Click to unlock the option and allow it to be randomized on new map generation");
|
||||
else tip("Click to lock the option and always use the current value on new map generation");
|
||||
document.querySelectorAll('[data-locked]').forEach(function (e) {
|
||||
e.addEventListener('mouseover', function (event) {
|
||||
if (this.className === 'icon-lock') tip('Click to unlock the option and allow it to be randomized on new map generation');
|
||||
else tip('Click to lock the option and always use the current value on new map generation');
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
e.addEventListener("click", function() {
|
||||
const id = (this.id).slice(5);
|
||||
if (this.className === "icon-lock") unlock(id);
|
||||
e.addEventListener('click', function () {
|
||||
const id = this.id.slice(5);
|
||||
if (this.className === 'icon-lock') unlock(id);
|
||||
else lock(id);
|
||||
});
|
||||
});
|
||||
|
||||
// lock option
|
||||
function lock(id) {
|
||||
const input = document.querySelector("[data-stored='"+id+"']");
|
||||
const input = document.querySelector("[data-stored='" + id + "']");
|
||||
if (input) localStorage.setItem(id, input.value);
|
||||
const el = document.getElementById("lock_" + id);
|
||||
if(!el) return;
|
||||
const el = document.getElementById('lock_' + id);
|
||||
if (!el) return;
|
||||
el.dataset.locked = 1;
|
||||
el.className = "icon-lock";
|
||||
el.className = 'icon-lock';
|
||||
}
|
||||
|
||||
// unlock option
|
||||
function unlock(id) {
|
||||
localStorage.removeItem(id);
|
||||
const el = document.getElementById("lock_" + id);
|
||||
if(!el) return;
|
||||
const el = document.getElementById('lock_' + id);
|
||||
if (!el) return;
|
||||
el.dataset.locked = 0;
|
||||
el.className = "icon-lock-open";
|
||||
el.className = 'icon-lock-open';
|
||||
}
|
||||
|
||||
// check if option is locked
|
||||
function locked(id) {
|
||||
const lockEl = document.getElementById("lock_" + id);
|
||||
const lockEl = document.getElementById('lock_' + id);
|
||||
return lockEl.dataset.locked == 1;
|
||||
}
|
||||
|
||||
|
|
@ -385,16 +464,16 @@ function stored(option) {
|
|||
}
|
||||
|
||||
// assign skeaker behaviour
|
||||
Array.from(document.getElementsByClassName("speaker")).forEach(el => {
|
||||
Array.from(document.getElementsByClassName('speaker')).forEach((el) => {
|
||||
const input = el.previousElementSibling;
|
||||
el.addEventListener("click", () => speak(input.value));
|
||||
el.addEventListener('click', () => speak(input.value));
|
||||
});
|
||||
|
||||
function speak(text) {
|
||||
const speaker = new SpeechSynthesisUtterance(text);
|
||||
const voices = speechSynthesis.getVoices();
|
||||
if (voices.length) {
|
||||
const voiceId = +document.getElementById("speakerVoice").value;
|
||||
const voiceId = +document.getElementById('speakerVoice').value;
|
||||
speaker.voice = voices[voiceId];
|
||||
}
|
||||
speechSynthesis.speak(speaker);
|
||||
|
|
@ -402,21 +481,21 @@ function speak(text) {
|
|||
|
||||
// apply drop-down menu option. If the value is not in options, add it
|
||||
function applyOption(select, id, name = id) {
|
||||
const custom = !Array.from(select.options).some(o => o.value == id);
|
||||
const custom = !Array.from(select.options).some((o) => o.value == id);
|
||||
if (custom) select.options.add(new Option(name, id));
|
||||
select.value = id;
|
||||
}
|
||||
|
||||
// show info about the generator in a popup
|
||||
function showInfo() {
|
||||
const Discord = link("https://discordapp.com/invite/X7E84HU", "Discord");
|
||||
const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit")
|
||||
const Patreon = link("https://www.patreon.com/azgaar", "Patreon");
|
||||
const Trello = link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Trello");
|
||||
const Armoria = link("https://azgaar.github.io/Armoria", "Armoria");
|
||||
const Discord = link('https://discordapp.com/invite/X7E84HU', 'Discord');
|
||||
const Reddit = link('https://www.reddit.com/r/FantasyMapGenerator', 'Reddit');
|
||||
const Patreon = link('https://www.patreon.com/azgaar', 'Patreon');
|
||||
const Trello = link('https://trello.com/b/7x832DG4/fantasy-map-generator', 'Trello');
|
||||
const Armoria = link('https://azgaar.github.io/Armoria', 'Armoria');
|
||||
|
||||
const QuickStart = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial", "Quick start tutorial");
|
||||
const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page");
|
||||
const QuickStart = link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial', 'Quick start tutorial');
|
||||
const QAA = link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A', 'Q&A page');
|
||||
|
||||
alertMessage.innerHTML = `
|
||||
<b>Fantasy Map Generator</b> (FMG) is an open-source application, it means the code is published an anyone can use it.
|
||||
|
|
@ -434,32 +513,39 @@ function showInfo() {
|
|||
|
||||
<b>Links:</b>
|
||||
<ul style="columns:2">
|
||||
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator", "GitHub repository")}</li>
|
||||
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE", "License")}</li>
|
||||
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "Changelog")}</li>
|
||||
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys", "Hotkeys")}</li>
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator', 'GitHub repository')}</li>
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE', 'License')}</li>
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog', 'Changelog')}</li>
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys', 'Hotkeys')}</li>
|
||||
</ul>`;
|
||||
|
||||
$("#alert").dialog({resizable: false, title: document.title, width: "28em",
|
||||
buttons: {OK: function() {$(this).dialog("close");}},
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: document.title,
|
||||
width: '28em',
|
||||
buttons: {
|
||||
OK: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
position: {my: 'center', at: 'center', of: 'svg'}
|
||||
});
|
||||
}
|
||||
|
||||
// prevent default browser behavior for FMG-used hotkeys
|
||||
document.addEventListener("keydown", event => {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.altKey && event.keyCode !== 18) event.preventDefault(); // disallow alt key combinations
|
||||
if (event.ctrlKey && event.code === "KeyS") event.preventDefault(); // disallow CTRL + C
|
||||
if (event.ctrlKey && event.code === 'KeyS') event.preventDefault(); // disallow CTRL + C
|
||||
if ([112, 113, 117, 120, 9].includes(event.keyCode)) event.preventDefault(); // F1, F2, F6, F9, Tab
|
||||
});
|
||||
|
||||
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
||||
document.addEventListener("keyup", event => {
|
||||
document.addEventListener('keyup', (event) => {
|
||||
if (!window.closeDialogs) return; // not all modules are loaded
|
||||
const canvas3d = document.getElementById("canvas3d"); // check if 3d mode is active
|
||||
const canvas3d = document.getElementById('canvas3d'); // check if 3d mode is active
|
||||
const active = document.activeElement.tagName;
|
||||
if (active === "INPUT" || active === "SELECT" || active === "TEXTAREA") return; // don't trigger if user inputs a text
|
||||
if (active === "DIV" && document.activeElement.contentEditable === "true") return; // don't trigger if user inputs a text
|
||||
if (active === 'INPUT' || active === 'SELECT' || active === 'TEXTAREA') return; // don't trigger if user inputs a text
|
||||
if (active === 'DIV' && document.activeElement.contentEditable === 'true') return; // don't trigger if user inputs a text
|
||||
event.stopPropagation();
|
||||
|
||||
const key = event.keyCode;
|
||||
|
|
@ -467,95 +553,174 @@ document.addEventListener("keyup", event => {
|
|||
const shift = event.shiftKey || key === 16;
|
||||
const alt = event.altKey || key === 18;
|
||||
|
||||
if (key === 112) showInfo(); // "F1" to show info
|
||||
else if (key === 113) regeneratePrompt(); // "F2" for new map
|
||||
else if (key === 113) regeneratePrompt(); // "F2" for a new map
|
||||
else if (key === 117) quickSave(); // "F6" for quick save
|
||||
else if (key === 120) quickLoad(); // "F9" for quick load
|
||||
else if (key === 9) toggleOptions(event); // Tab to toggle options
|
||||
else if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
|
||||
else if (key === 46) removeElementOnKey(); // "Delete" to remove the selected element
|
||||
else if (key === 79 && canvas3d) toggle3dOptions(); // "O" to toggle 3d options
|
||||
|
||||
else if (ctrl && key === 81) toggleSaveReminder(); // Ctrl + "Q" to toggle save reminder
|
||||
else if (ctrl && key === 83) saveMap(); // Ctrl + "S" to save .map file
|
||||
else if (undo.offsetParent && ctrl && key === 90) undo.click(); // Ctrl + "Z" to undo
|
||||
else if (redo.offsetParent && ctrl && key === 89) redo.click(); // Ctrl + "Y" to redo
|
||||
|
||||
else if (shift && key === 72) editHeightmap(); // Shift + "H" to edit Heightmap
|
||||
else if (shift && key === 66) editBiomes(); // Shift + "B" to edit Biomes
|
||||
else if (shift && key === 83) editStates(); // Shift + "S" to edit States
|
||||
else if (shift && key === 80) editProvinces(); // Shift + "P" to edit Provinces
|
||||
else if (shift && key === 68) editDiplomacy(); // Shift + "D" to edit Diplomacy
|
||||
else if (shift && key === 67) editCultures(); // Shift + "C" to edit Cultures
|
||||
else if (shift && key === 78) editNamesbase(); // Shift + "N" to edit Namesbase
|
||||
else if (shift && key === 90) editZones(); // Shift + "Z" to edit Zones
|
||||
else if (shift && key === 82) editReligions(); // Shift + "R" to edit Religions
|
||||
else if (shift && key === 81) editResources(); // Shift + "Q" to edit Resources
|
||||
else if (shift && key === 89) openEmblemEditor(); // Shift + "Y" to edit Emblems
|
||||
else if (shift && key === 87) editUnits(); // Shift + "W" to edit Units
|
||||
else if (shift && key === 79) editNotes(); // Shift + "O" to edit Notes
|
||||
else if (shift && key === 84) overviewBurgs(); // Shift + "T" to open Burgs overview
|
||||
else if (shift && key === 86) overviewRivers(); // Shift + "V" to open Rivers overview
|
||||
else if (shift && key === 77) overviewMilitary(); // Shift + "M" to open Military overview
|
||||
else if (shift && key === 69) viewCellDetails(); // Shift + "E" to open Cell Details
|
||||
|
||||
else if (shift && key === 49) toggleAddBurg(); // Shift + "1" to click to add Burg
|
||||
else if (shift && key === 50) toggleAddLabel(); // Shift + "2" to click to add Label
|
||||
else if (shift && key === 51) toggleAddRiver(); // Shift + "3" to click to add River
|
||||
else if (shift && key === 52) toggleAddRoute(); // Shift + "4" to click to add Route
|
||||
else if (shift && key === 53) toggleAddMarker(); // Shift + "5" to click to add Marker
|
||||
|
||||
else if (alt && key === 66) console.table(pack.burgs); // Alt + "B" to log burgs data
|
||||
else if (alt && key === 83) console.table(pack.states); // Alt + "S" to log states data
|
||||
else if (alt && key === 67) console.table(pack.cultures); // Alt + "C" to log cultures data
|
||||
else if (alt && key === 82) console.table(pack.religions); // Alt + "R" to log religions data
|
||||
else if (alt && key === 70) console.table(pack.features); // Alt + "F" to log features data
|
||||
|
||||
else if (key === 88) toggleTexture(); // "X" to toggle Texture layer
|
||||
else if (key === 72) toggleHeight(); // "H" to toggle Heightmap layer
|
||||
else if (key === 66) toggleBiomes(); // "B" to toggle Biomes layer
|
||||
else if (key === 69) toggleCells(); // "E" to toggle Cells layer
|
||||
else if (key === 71) toggleGrid(); // "G" to toggle Grid layer
|
||||
else if (key === 79) toggleCoordinates(); // "O" to toggle Coordinates layer
|
||||
else if (key === 87) toggleCompass(); // "W" to toggle Compass Rose layer
|
||||
else if (key === 86) toggleRivers(); // "V" to toggle Rivers layer
|
||||
else if (key === 70) toggleRelief(); // "F" to toggle Relief icons layer
|
||||
else if (key === 67) toggleCultures(); // "C" to toggle Cultures layer
|
||||
else if (key === 83) toggleStates(); // "S" to toggle States layer
|
||||
else if (key === 80) toggleProvinces(); // "P" to toggle Provinces layer
|
||||
else if (key === 90) toggleZones(); // "Z" to toggle Zones
|
||||
else if (key === 68) toggleBorders(); // "D" to toggle Borders layer
|
||||
else if (key === 82) toggleReligions(); // "R" to toggle Religions layer
|
||||
else if (key === 85) toggleRoutes(); // "U" to toggle Routes layer
|
||||
else if (key === 84) toggleTemp(); // "T" to toggle Temperature layer
|
||||
else if (key === 78) togglePopulation(); // "N" to toggle Population layer
|
||||
else if (key === 74) toggleIce(); // "J" to toggle Ice layer
|
||||
else if (key === 65) togglePrec(); // "A" to toggle Precipitation layer
|
||||
else if (key === 81) toggleResources(); // "Q" to toggle Resources layer
|
||||
else if (key === 89) toggleEmblems(); // "Y" to toggle Emblems layer
|
||||
else if (key === 76) toggleLabels(); // "L" to toggle Labels layer
|
||||
else if (key === 73) toggleIcons(); // "I" to toggle Icons layer
|
||||
else if (key === 77) toggleMilitary(); // "M" to toggle Military layer
|
||||
else if (key === 75) toggleMarkers(); // "K" to toggle Markers layer
|
||||
else if (key === 187) toggleRulers(); // Equal (=) to toggle Rulers
|
||||
else if (key === 189) toggleScaleBar(); // Minus (-) to toggle Scale bar
|
||||
|
||||
else if (key === 37) zoom.translateBy(svg, 10, 0); // Left to scroll map left
|
||||
else if (key === 39) zoom.translateBy(svg, -10, 0); // Right to scroll map right
|
||||
else if (key === 38) zoom.translateBy(svg, 0, 10); // Up to scroll map up
|
||||
else if (key === 40) zoom.translateBy(svg, 0, -10); // Up to scroll map up
|
||||
else if (key === 107 || key === 109) pressNumpadSign(key); // Numpad Plus/Minus to zoom map or change brush size
|
||||
else if (key === 48 || key === 96) resetZoom(1000); // 0 to reset zoom
|
||||
else if (key === 49 || key === 97) zoom.scaleTo(svg, 1); // 1 to zoom to 1
|
||||
else if (key === 50 || key === 98) zoom.scaleTo(svg, 2); // 2 to zoom to 2
|
||||
else if (key === 51 || key === 99) zoom.scaleTo(svg, 3); // 3 to zoom to 3
|
||||
else if (key === 52 || key === 100) zoom.scaleTo(svg, 4); // 4 to zoom to 4
|
||||
else if (key === 53 || key === 101) zoom.scaleTo(svg, 5); // 5 to zoom to 5
|
||||
else if (key === 54 || key === 102) zoom.scaleTo(svg, 6); // 6 to zoom to 6
|
||||
else if (key === 55 || key === 103) zoom.scaleTo(svg, 7); // 7 to zoom to 7
|
||||
else if (key === 56 || key === 104) zoom.scaleTo(svg, 8); // 8 to zoom to 8
|
||||
else if (key === 57 || key === 105) zoom.scaleTo(svg, 9); // 9 to zoom to 9
|
||||
if (key === 112) showInfo();
|
||||
// "F1" to show info
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for new map
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for a new map
|
||||
else if (key === 117) quickSave();
|
||||
// "F6" for quick save
|
||||
else if (key === 120) quickLoad();
|
||||
// "F9" for quick load
|
||||
else if (key === 9) toggleOptions(event);
|
||||
// Tab to toggle options
|
||||
else if (key === 27) {
|
||||
closeDialogs();
|
||||
hideOptions();
|
||||
} // Escape to close all dialogs
|
||||
else if (key === 46) removeElementOnKey();
|
||||
// "Delete" to remove the selected element
|
||||
else if (key === 79 && canvas3d) toggle3dOptions();
|
||||
// "O" to toggle 3d options
|
||||
else if (ctrl && key === 81) toggleSaveReminder();
|
||||
// Ctrl + "Q" to toggle save reminder
|
||||
else if (ctrl && key === 83) saveMap();
|
||||
// Ctrl + "S" to save .map file
|
||||
else if (undo.offsetParent && ctrl && key === 90) undo.click();
|
||||
// Ctrl + "Z" to undo
|
||||
else if (redo.offsetParent && ctrl && key === 89) redo.click();
|
||||
// Ctrl + "Y" to redo
|
||||
else if (shift && key === 72) editHeightmap();
|
||||
// Shift + "H" to edit Heightmap
|
||||
else if (shift && key === 66) editBiomes();
|
||||
// Shift + "B" to edit Biomes
|
||||
else if (shift && key === 83) editStates();
|
||||
// Shift + "S" to edit States
|
||||
else if (shift && key === 80) editProvinces();
|
||||
// Shift + "P" to edit Provinces
|
||||
else if (shift && key === 68) editDiplomacy();
|
||||
// Shift + "D" to edit Diplomacy
|
||||
else if (shift && key === 67) editCultures();
|
||||
// Shift + "C" to edit Cultures
|
||||
else if (shift && key === 78) editNamesbase();
|
||||
// Shift + "N" to edit Namesbase
|
||||
else if (shift && key === 90) editZones();
|
||||
// Shift + "Z" to edit Zones
|
||||
else if (shift && key === 82) editReligions();
|
||||
// Shift + "R" to edit Religions
|
||||
else if (shift && key === 81) editResources();
|
||||
// Shift + "Q" to edit Resources
|
||||
else if (shift && key === 89) openEmblemEditor();
|
||||
// Shift + "Y" to edit Emblems
|
||||
else if (shift && key === 87) editUnits();
|
||||
// Shift + "W" to edit Units
|
||||
else if (shift && key === 79) editNotes();
|
||||
// Shift + "O" to edit Notes
|
||||
else if (shift && key === 84) overviewBurgs();
|
||||
// Shift + "T" to open Burgs overview
|
||||
else if (shift && key === 86) overviewRivers();
|
||||
// Shift + "V" to open Rivers overview
|
||||
else if (shift && key === 77) overviewMilitary();
|
||||
// Shift + "M" to open Military overview
|
||||
else if (shift && key === 69) viewCellDetails();
|
||||
// Shift + "E" to open Cell Details
|
||||
else if (shift && key === 49) toggleAddBurg();
|
||||
// Shift + "1" to click to add Burg
|
||||
else if (shift && key === 50) toggleAddLabel();
|
||||
// Shift + "2" to click to add Label
|
||||
else if (shift && key === 51) toggleAddRiver();
|
||||
// Shift + "3" to click to add River
|
||||
else if (shift && key === 52) toggleAddRoute();
|
||||
// Shift + "4" to click to add Route
|
||||
else if (shift && key === 53) toggleAddMarker();
|
||||
// Shift + "5" to click to add Marker
|
||||
else if (alt && key === 66) console.table(pack.burgs);
|
||||
// Alt + "B" to log burgs data
|
||||
else if (alt && key === 83) console.table(pack.states);
|
||||
// Alt + "S" to log states data
|
||||
else if (alt && key === 67) console.table(pack.cultures);
|
||||
// Alt + "C" to log cultures data
|
||||
else if (alt && key === 82) console.table(pack.religions);
|
||||
// Alt + "R" to log religions data
|
||||
else if (alt && key === 70) console.table(pack.features);
|
||||
// Alt + "F" to log features data
|
||||
else if (key === 88) toggleTexture();
|
||||
// "X" to toggle Texture layer
|
||||
else if (key === 72) toggleHeight();
|
||||
// "H" to toggle Heightmap layer
|
||||
else if (key === 66) toggleBiomes();
|
||||
// "B" to toggle Biomes layer
|
||||
else if (key === 69) toggleCells();
|
||||
// "E" to toggle Cells layer
|
||||
else if (key === 71) toggleGrid();
|
||||
// "G" to toggle Grid layer
|
||||
else if (key === 79) toggleCoordinates();
|
||||
// "O" to toggle Coordinates layer
|
||||
else if (key === 87) toggleCompass();
|
||||
// "W" to toggle Compass Rose layer
|
||||
else if (key === 86) toggleRivers();
|
||||
// "V" to toggle Rivers layer
|
||||
else if (key === 70) toggleRelief();
|
||||
// "F" to toggle Relief icons layer
|
||||
else if (key === 67) toggleCultures();
|
||||
// "C" to toggle Cultures layer
|
||||
else if (key === 83) toggleStates();
|
||||
// "S" to toggle States layer
|
||||
else if (key === 80) toggleProvinces();
|
||||
// "P" to toggle Provinces layer
|
||||
else if (key === 90) toggleZones();
|
||||
// "Z" to toggle Zones
|
||||
else if (key === 68) toggleBorders();
|
||||
// "D" to toggle Borders layer
|
||||
else if (key === 82) toggleReligions();
|
||||
// "R" to toggle Religions layer
|
||||
else if (key === 85) toggleRoutes();
|
||||
// "U" to toggle Routes layer
|
||||
else if (key === 84) toggleTemp();
|
||||
// "T" to toggle Temperature layer
|
||||
else if (key === 78) togglePopulation();
|
||||
// "N" to toggle Population layer
|
||||
else if (key === 74) toggleIce();
|
||||
// "J" to toggle Ice layer
|
||||
else if (key === 65) togglePrec();
|
||||
// "A" to toggle Precipitation layer
|
||||
else if (key === 81) toggleResources();
|
||||
// "Q" to toggle Resources layer
|
||||
else if (key === 89) toggleEmblems();
|
||||
// "Y" to toggle Emblems layer
|
||||
else if (key === 76) toggleLabels();
|
||||
// "L" to toggle Labels layer
|
||||
else if (key === 73) toggleIcons();
|
||||
// "I" to toggle Icons layer
|
||||
else if (key === 77) toggleMilitary();
|
||||
// "M" to toggle Military layer
|
||||
else if (key === 75) toggleMarkers();
|
||||
// "K" to toggle Markers layer
|
||||
else if (key === 187) toggleRulers();
|
||||
// Equal (=) to toggle Rulers
|
||||
else if (key === 189) toggleScaleBar();
|
||||
// Minus (-) to toggle Scale bar
|
||||
else if (key === 37) zoom.translateBy(svg, 10, 0);
|
||||
// Left to scroll map left
|
||||
else if (key === 39) zoom.translateBy(svg, -10, 0);
|
||||
// Right to scroll map right
|
||||
else if (key === 38) zoom.translateBy(svg, 0, 10);
|
||||
// Up to scroll map up
|
||||
else if (key === 40) zoom.translateBy(svg, 0, -10);
|
||||
// Up to scroll map up
|
||||
else if (key === 107 || key === 109) pressNumpadSign(key);
|
||||
// Numpad Plus/Minus to zoom map or change brush size
|
||||
else if (key === 48 || key === 96) resetZoom(1000);
|
||||
// 0 to reset zoom
|
||||
else if (key === 49 || key === 97) zoom.scaleTo(svg, 1);
|
||||
// 1 to zoom to 1
|
||||
else if (key === 50 || key === 98) zoom.scaleTo(svg, 2);
|
||||
// 2 to zoom to 2
|
||||
else if (key === 51 || key === 99) zoom.scaleTo(svg, 3);
|
||||
// 3 to zoom to 3
|
||||
else if (key === 52 || key === 100) zoom.scaleTo(svg, 4);
|
||||
// 4 to zoom to 4
|
||||
else if (key === 53 || key === 101) zoom.scaleTo(svg, 5);
|
||||
// 5 to zoom to 5
|
||||
else if (key === 54 || key === 102) zoom.scaleTo(svg, 6);
|
||||
// 6 to zoom to 6
|
||||
else if (key === 55 || key === 103) zoom.scaleTo(svg, 7);
|
||||
// 7 to zoom to 7
|
||||
else if (key === 56 || key === 104) zoom.scaleTo(svg, 8);
|
||||
// 8 to zoom to 8
|
||||
else if (key === 57 || key === 105) zoom.scaleTo(svg, 9);
|
||||
// 9 to zoom to 9
|
||||
else if (ctrl) pressControl(); // Control to toggle mode
|
||||
});
|
||||
|
||||
|
|
@ -564,32 +729,32 @@ function pressNumpadSign(key) {
|
|||
let brush = null;
|
||||
const d = key === 107 ? 1 : -1;
|
||||
|
||||
if (brushRadius.offsetParent) brush = document.getElementById("brushRadius"); else
|
||||
if (biomesManuallyBrush.offsetParent) brush = document.getElementById("biomesManuallyBrush"); else
|
||||
if (statesManuallyBrush.offsetParent) brush = document.getElementById("statesManuallyBrush"); else
|
||||
if (provincesManuallyBrush.offsetParent) brush = document.getElementById("provincesManuallyBrush"); else
|
||||
if (culturesManuallyBrush.offsetParent) brush = document.getElementById("culturesManuallyBrush"); else
|
||||
if (zonesBrush.offsetParent) brush = document.getElementById("zonesBrush"); else
|
||||
if (religionsManuallyBrush.offsetParent) brush = document.getElementById("religionsManuallyBrush");
|
||||
if (brushRadius.offsetParent) brush = document.getElementById('brushRadius');
|
||||
else if (biomesManuallyBrush.offsetParent) brush = document.getElementById('biomesManuallyBrush');
|
||||
else if (statesManuallyBrush.offsetParent) brush = document.getElementById('statesManuallyBrush');
|
||||
else if (provincesManuallyBrush.offsetParent) brush = document.getElementById('provincesManuallyBrush');
|
||||
else if (culturesManuallyBrush.offsetParent) brush = document.getElementById('culturesManuallyBrush');
|
||||
else if (zonesBrush.offsetParent) brush = document.getElementById('zonesBrush');
|
||||
else if (religionsManuallyBrush.offsetParent) brush = document.getElementById('religionsManuallyBrush');
|
||||
|
||||
if (brush) {
|
||||
const value = Math.max(Math.min(+brush.value + d, +brush.max), +brush.min);
|
||||
brush.value = document.getElementById(brush.id+"Number").value = value;
|
||||
brush.value = document.getElementById(brush.id + 'Number').value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleBy = key === 107 ? 1.2 : .8;
|
||||
const scaleBy = key === 107 ? 1.2 : 0.8;
|
||||
zoom.scaleBy(svg, scaleBy); // if no, zoom map
|
||||
}
|
||||
|
||||
function pressControl() {
|
||||
if (zonesRemove.offsetParent) {
|
||||
zonesRemove.classList.contains("pressed") ? zonesRemove.classList.remove("pressed") : zonesRemove.classList.add("pressed");
|
||||
zonesRemove.classList.contains('pressed') ? zonesRemove.classList.remove('pressed') : zonesRemove.classList.add('pressed');
|
||||
}
|
||||
}
|
||||
|
||||
// trigger trash button click on "Delete" keypress
|
||||
function removeElementOnKey() {
|
||||
$(".dialog:visible .fastDelete").click();
|
||||
$('.dialog:visible .fastDelete').click();
|
||||
$("button:visible:contains('Remove')").click();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,9 @@ function editHeightmap() {
|
|||
title: 'Edit Heightmap',
|
||||
width: '28em',
|
||||
buttons: {
|
||||
Erase: function () {
|
||||
enterHeightmapEditMode('erase');
|
||||
},
|
||||
Keep: function () {
|
||||
enterHeightmapEditMode('keep');
|
||||
},
|
||||
Risk: function () {
|
||||
enterHeightmapEditMode('risk');
|
||||
},
|
||||
Erase: () => enterHeightmapEditMode('erase'),
|
||||
Keep: () => enterHeightmapEditMode('keep'),
|
||||
Risk: () => enterHeightmapEditMode('risk'),
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
|
|
@ -77,7 +71,7 @@ function editHeightmap() {
|
|||
convertImage.style.display = type === 'erase' ? 'inline-block' : 'none';
|
||||
|
||||
// hide erosion checkbox if mode is Keep
|
||||
changeHeightsBox.style.display = type === 'keep' ? 'none' : 'inline-block';
|
||||
allowErosionBox.style.display = type === 'keep' ? 'none' : 'inline-block';
|
||||
|
||||
// show finalize button
|
||||
if (!sessionStorage.getItem('noExitButtonAnimation')) {
|
||||
|
|
@ -191,19 +185,22 @@ function editHeightmap() {
|
|||
INFO && console.group('Edit Heightmap');
|
||||
TIME && console.time('regenerateErasedData');
|
||||
|
||||
const change = changeHeights.checked;
|
||||
const erosionAllowed = allowErosion.checked;
|
||||
markFeatures();
|
||||
getSignedDistanceField();
|
||||
if (change) openNearSeaLakes();
|
||||
markupGridOcean();
|
||||
if (erosionAllowed) {
|
||||
addLakesInDeepDepressions();
|
||||
openNearSeaLakes();
|
||||
}
|
||||
OceanLayers();
|
||||
calculateTemperatures();
|
||||
generatePrecipitation();
|
||||
reGraph();
|
||||
drawCoastline();
|
||||
|
||||
Rivers.generate(change);
|
||||
Rivers.generate(erosionAllowed);
|
||||
|
||||
if (!change) {
|
||||
if (!erosionAllowed) {
|
||||
for (const i of pack.cells.i) {
|
||||
const g = pack.cells.g[i];
|
||||
if (pack.cells.h[i] !== grid.cells.h[g] && pack.cells.h[i] >= 20 === grid.cells.h[g] >= 20) pack.cells.h[i] = grid.cells.h[g];
|
||||
|
|
@ -248,6 +245,7 @@ function editHeightmap() {
|
|||
function restoreRiskedData() {
|
||||
INFO && console.group('Edit Heightmap');
|
||||
TIME && console.time('restoreRiskedData');
|
||||
const erosionAllowed = allowErosion.checked;
|
||||
|
||||
// assign pack data to grid cells
|
||||
const l = grid.cells.i.length;
|
||||
|
|
@ -262,7 +260,7 @@ function editHeightmap() {
|
|||
const culture = new Uint16Array(l);
|
||||
const religion = new Uint16Array(l);
|
||||
|
||||
// rivers data, stored only if changeHeights is unchecked
|
||||
// rivers data, stored only if allowErosion is unchecked
|
||||
const fl = new Uint16Array(l);
|
||||
const r = new Uint16Array(l);
|
||||
const conf = new Uint8Array(l);
|
||||
|
|
@ -280,7 +278,7 @@ function editHeightmap() {
|
|||
burg[g] = pack.cells.burg[i];
|
||||
religion[g] = pack.cells.religion[i];
|
||||
|
||||
if (!changeHeights.checked) {
|
||||
if (!erosionAllowed) {
|
||||
fl[g] = pack.cells.fl[i];
|
||||
r[g] = pack.cells.r[i];
|
||||
conf[g] = pack.cells.conf[i];
|
||||
|
|
@ -312,14 +310,15 @@ function editHeightmap() {
|
|||
});
|
||||
|
||||
markFeatures();
|
||||
getSignedDistanceField();
|
||||
markupGridOcean();
|
||||
if (erosionAllowed) addLakesInDeepDepressions();
|
||||
OceanLayers();
|
||||
calculateTemperatures();
|
||||
generatePrecipitation();
|
||||
reGraph();
|
||||
drawCoastline();
|
||||
|
||||
if (changeHeights.checked) Rivers.generate(changeHeights.checked);
|
||||
if (erosionAllowed) Rivers.generate(true);
|
||||
|
||||
// assign saved pack data from grid back to pack
|
||||
const n = pack.cells.i.length;
|
||||
|
|
@ -334,7 +333,7 @@ function editHeightmap() {
|
|||
pack.cells.religion = new Uint16Array(n);
|
||||
pack.cells.biome = new Uint8Array(n);
|
||||
|
||||
if (!changeHeights.checked) {
|
||||
if (!erosionAllowed) {
|
||||
pack.cells.r = new Uint16Array(n);
|
||||
pack.cells.conf = new Uint8Array(n);
|
||||
pack.cells.fl = new Uint16Array(n);
|
||||
|
|
@ -348,7 +347,7 @@ function editHeightmap() {
|
|||
pack.cells.biome[i] = land && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], pack.cells.h[i]);
|
||||
|
||||
// rivers data
|
||||
if (!changeHeights.checked) {
|
||||
if (!erosionAllowed) {
|
||||
pack.cells.r[i] = r[g];
|
||||
pack.cells.conf[i] = conf[g];
|
||||
pack.cells.fl[i] = fl[g];
|
||||
|
|
@ -412,7 +411,7 @@ function editHeightmap() {
|
|||
drawStates();
|
||||
drawBorders();
|
||||
|
||||
if (changeHeights.checked) {
|
||||
if (erosionAllowed) {
|
||||
Rivers.specify();
|
||||
Lakes.generateName();
|
||||
}
|
||||
|
|
@ -817,10 +816,25 @@ function editHeightmap() {
|
|||
const steps = body.querySelectorAll('div').length;
|
||||
const changed = +body.getAttribute('data-changed');
|
||||
const template = e.target.value;
|
||||
if (!steps || !changed) return changeTemplate(template);
|
||||
if (!steps || !changed) {
|
||||
changeTemplate(template);
|
||||
return;
|
||||
}
|
||||
|
||||
const message = 'Are you sure you want to select a different template? <br>All changes will be lost';
|
||||
confirmationDialog({title: 'Change template', message, confirm: 'Change', onConfirm: () => changeTemplate(template)});
|
||||
alertMessage.innerHTML = 'Are you sure you want to select a different template? All changes will be lost.';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Change Template',
|
||||
buttons: {
|
||||
Change: function () {
|
||||
changeTemplate(template);
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function changeTemplate(template) {
|
||||
|
|
@ -952,7 +966,8 @@ function editHeightmap() {
|
|||
|
||||
for (const s of steps) {
|
||||
if (s.style.opacity == 0.5) continue;
|
||||
const type = s.getAttribute('data-type');
|
||||
const type = s.dataset.type;
|
||||
|
||||
const elCount = s.querySelector('.templateCount') || '';
|
||||
const elHeight = s.querySelector('.templateHeight') || '';
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ function getDefaultPresets() {
|
|||
heightmap: ['toggleHeight', 'toggleRivers'],
|
||||
physical: ['toggleCoordinates', 'toggleHeight', 'toggleIce', 'toggleRivers', 'toggleScaleBar'],
|
||||
poi: ['toggleBorders', 'toggleHeight', 'toggleIce', 'toggleIcons', 'toggleMarkers', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar'],
|
||||
economical: ['toggleResources', 'toggleBiomes', 'toggleBorders', 'toggleIcons', 'toggleIce', 'toggleLabels', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar'],
|
||||
military: ['toggleBorders', 'toggleIcons', 'toggleLabels', 'toggleMilitary', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar', 'toggleStates'],
|
||||
emblems: ['toggleBorders', 'toggleIcons', 'toggleIce', 'toggleEmblems', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar', 'toggleStates'],
|
||||
landmass: ['toggleScaleBar']
|
||||
|
|
@ -547,7 +546,7 @@ function drawPopulation(event) {
|
|||
.transition(show)
|
||||
.attr('y2', (d) => d[2]);
|
||||
|
||||
const urban = burgs.filter((b) => b.i && !b.removed).map((b) => [b.x, b.y, b.y - (b.population / 8) * urbanization.value]);
|
||||
const urban = burgs.filter((b) => b.i && !b.removed).map((b) => [b.x, b.y, b.y - (b.population / 8) * urbanization]);
|
||||
population
|
||||
.select('#urban')
|
||||
.selectAll('line')
|
||||
|
|
@ -919,7 +918,9 @@ function drawStates() {
|
|||
const bodyString = bodyData.map((d) => `<path id="state${d[1]}" d="${d[0]}" fill="${d[2]}" stroke="none"/>`).join('');
|
||||
const gapString = gapData.map((d) => `<path id="state-gap${d[1]}" d="${d[0]}" fill="none" stroke="${d[2]}"/>`).join('');
|
||||
const clipString = bodyData.map((d) => `<clipPath id="state-clip${d[1]}"><use href="#state${d[1]}"/></clipPath>`).join('');
|
||||
const haloString = bodyData.map((d) => `<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${d3.color(d[2]) ? d3.color(d[2]).darker().hex() : '#666666'}"/>`).join('');
|
||||
const haloString = bodyData
|
||||
.map((d) => `<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${d3.color(d[2]) ? d3.color(d[2]).darker().hex() : '#666666'}"/>`)
|
||||
.join('');
|
||||
|
||||
statesBody.html(bodyString + gapString);
|
||||
defs.select('#statePaths').html(clipString);
|
||||
|
|
|
|||
|
|
@ -20,10 +20,7 @@ class Rulers {
|
|||
for (const rulerString of rulers) {
|
||||
const [type, pointsString] = rulerString.split(": ");
|
||||
const points = pointsString.split(" ").map(el => el.split(",").map(n => +n));
|
||||
const Type = type === "Ruler" ? Ruler :
|
||||
type === "Opisometer" ? Opisometer :
|
||||
type === "RouteOpisometer" ? RouteOpisometer :
|
||||
type === "Planimeter" ? Planimeter : null;
|
||||
const Type = type === "Ruler" ? Ruler : type === "Opisometer" ? Opisometer : type === "RouteOpisometer" ? RouteOpisometer : type === "Planimeter" ? Planimeter : null;
|
||||
this.create(Type, points);
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +54,7 @@ class Measurer {
|
|||
}
|
||||
|
||||
getSize() {
|
||||
return rn(1 / scale ** .3 * 2, 2);
|
||||
return rn((1 / scale ** 0.3) * 2, 2);
|
||||
}
|
||||
|
||||
getDash() {
|
||||
|
|
@ -66,10 +63,11 @@ class Measurer {
|
|||
|
||||
drag() {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y;
|
||||
const x = +tr[0] - d3.event.x,
|
||||
y = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const transform = `translate(${(x + d3.event.x)},${(y + d3.event.y)})`;
|
||||
d3.event.on("drag", function () {
|
||||
const transform = `translate(${x + d3.event.x},${y + d3.event.y})`;
|
||||
this.setAttribute("transform", transform);
|
||||
});
|
||||
}
|
||||
|
|
@ -89,9 +87,9 @@ class Measurer {
|
|||
const MIN_DIST2 = 900;
|
||||
const optimized = [];
|
||||
|
||||
for (let i=0, p1 = this.points[0]; i < this.points.length; i++) {
|
||||
for (let i = 0, p1 = this.points[0]; i < this.points.length; i++) {
|
||||
const p2 = this.points[i];
|
||||
const dist2 = !i || i === this.points.length-1 ? Infinity : (p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2;
|
||||
const dist2 = !i || i === this.points.length - 1 ? Infinity : (p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2;
|
||||
if (dist2 < MIN_DIST2) continue;
|
||||
optimized.push(p2);
|
||||
p1 = p2;
|
||||
|
|
@ -105,7 +103,6 @@ class Measurer {
|
|||
undraw() {
|
||||
this.el?.remove();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Ruler extends Measurer {
|
||||
|
|
@ -136,12 +133,29 @@ class Ruler extends Measurer {
|
|||
const size = this.getSize();
|
||||
const dash = this.getDash();
|
||||
|
||||
const el = this.el = ruler.append("g").attr("class", "ruler").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size);
|
||||
el.append("polyline").attr("points", points).attr("class", "white").attr("stroke-width", size)
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "ruler")
|
||||
.call(d3.drag().on("start", this.drag))
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("polyline")
|
||||
.attr("points", points)
|
||||
.attr("class", "white")
|
||||
.attr("stroke-width", size)
|
||||
.call(d3.drag().on("start", () => this.addControl(this)));
|
||||
el.append("polyline").attr("points", points).attr("class", "gray").attr("stroke-width", rn(size * 1.2, 2)).attr("stroke-dasharray", dash);
|
||||
el.append("g").attr("class", "rulerPoints").attr("stroke-width", .5 * size).attr("font-size", 2 * size);
|
||||
el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", () => rulers.remove(this.id));
|
||||
el.append("polyline")
|
||||
.attr("points", points)
|
||||
.attr("class", "gray")
|
||||
.attr("stroke-width", rn(size * 1.2, 2))
|
||||
.attr("stroke-dasharray", dash);
|
||||
el.append("g")
|
||||
.attr("class", "rulerPoints")
|
||||
.attr("stroke-width", 0.5 * size)
|
||||
.attr("font-size", 2 * size);
|
||||
el.append("text")
|
||||
.attr("dx", ".35em")
|
||||
.attr("dy", "-.45em")
|
||||
.on("click", () => rulers.remove(this.id));
|
||||
this.drawPoints(el);
|
||||
this.updateLabel();
|
||||
return this;
|
||||
|
|
@ -151,7 +165,7 @@ class Ruler extends Measurer {
|
|||
const g = el.select(".rulerPoints");
|
||||
g.selectAll("circle").remove();
|
||||
|
||||
for (let i=0; i < this.points.length; i++) {
|
||||
for (let i = 0; i < this.points.length; i++) {
|
||||
const [x, y] = this.points[i];
|
||||
this.drawPoint(g, x, y, i);
|
||||
}
|
||||
|
|
@ -160,14 +174,25 @@ class Ruler extends Measurer {
|
|||
drawPoint(el, x, y, i) {
|
||||
const context = this;
|
||||
el.append("circle")
|
||||
.attr("r", "1em").attr("cx", x).attr("cy", y)
|
||||
.attr("r", "1em")
|
||||
.attr("cx", x)
|
||||
.attr("cy", y)
|
||||
.attr("class", this.isEdge(i) ? "edge" : "control")
|
||||
.on("click", function() {context.removePoint(context, i)})
|
||||
.call(d3.drag().clickDistance(3).on("start", function() {context.dragControl(context, i)}));
|
||||
.on("click", function () {
|
||||
context.removePoint(context, i);
|
||||
})
|
||||
.call(
|
||||
d3
|
||||
.drag()
|
||||
.clickDistance(3)
|
||||
.on("start", function () {
|
||||
context.dragControl(context, i);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
isEdge(i) {
|
||||
return i === 0 || i === this.points.length-1;
|
||||
return i === 0 || i === this.points.length - 1;
|
||||
}
|
||||
|
||||
updateLabel() {
|
||||
|
|
@ -179,9 +204,9 @@ class Ruler extends Measurer {
|
|||
|
||||
getLength() {
|
||||
let length = 0;
|
||||
for (let i=0; i < this.points.length - 1; i++) {
|
||||
for (let i = 0; i < this.points.length - 1; i++) {
|
||||
const [x1, y1] = this.points[i];
|
||||
const [x2, y2] = this.points[i+1];
|
||||
const [x2, y2] = this.points[i + 1];
|
||||
length += Math.hypot(x1 - x2, y1 - y2);
|
||||
}
|
||||
return length;
|
||||
|
|
@ -189,20 +214,20 @@ class Ruler extends Measurer {
|
|||
|
||||
dragControl(context, pointId) {
|
||||
let addPoint = context.isEdge(pointId) && d3.event.sourceEvent.ctrlKey;
|
||||
let circle = context.el.select(`circle:nth-child(${pointId+1})`);
|
||||
let circle = context.el.select(`circle:nth-child(${pointId + 1})`);
|
||||
const line = context.el.selectAll("polyline");
|
||||
|
||||
let x0 = rn(d3.event.x, 1);
|
||||
let y0 = rn(d3.event.y, 1);
|
||||
let axis;
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
d3.event.on("drag", function () {
|
||||
if (addPoint) {
|
||||
if (d3.event.dx < .1 && d3.event.dy < .1) return;
|
||||
if (d3.event.dx < 0.1 && d3.event.dy < 0.1) return;
|
||||
context.pushPoint(pointId);
|
||||
context.drawPoints(context.el);
|
||||
if (pointId) pointId++;
|
||||
circle = context.el.select(`circle:nth-child(${pointId+1})`);
|
||||
circle = context.el.select(`circle:nth-child(${pointId + 1})`);
|
||||
addPoint = false;
|
||||
}
|
||||
|
||||
|
|
@ -253,13 +278,38 @@ class Opisometer extends Measurer {
|
|||
const dash = this.getDash();
|
||||
const context = this;
|
||||
|
||||
const el = this.el = ruler.append("g").attr("class", "opisometer").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size);
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "opisometer")
|
||||
.call(d3.drag().on("start", this.drag))
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("path").attr("class", "white").attr("stroke-width", size);
|
||||
el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
|
||||
const rulerPoints = el.append("g").attr("class", "rulerPoints").attr("stroke-width", .5 * size).attr("font-size", 2 * size);
|
||||
rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 0)}));
|
||||
rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 1)}));
|
||||
el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", () => rulers.remove(this.id));
|
||||
const rulerPoints = el
|
||||
.append("g")
|
||||
.attr("class", "rulerPoints")
|
||||
.attr("stroke-width", 0.5 * size)
|
||||
.attr("font-size", 2 * size);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
context.dragControl(context, 0);
|
||||
})
|
||||
);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
context.dragControl(context, 1);
|
||||
})
|
||||
);
|
||||
el.append("text")
|
||||
.attr("dx", ".35em")
|
||||
.attr("dy", "-.45em")
|
||||
.on("click", () => rulers.remove(this.id));
|
||||
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
|
|
@ -267,7 +317,7 @@ class Opisometer extends Measurer {
|
|||
}
|
||||
|
||||
updateCurve() {
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(.5));
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.5));
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
|
||||
|
|
@ -288,7 +338,7 @@ class Opisometer extends Measurer {
|
|||
const MIN_DIST = d3.event.sourceEvent.shiftKey ? 9 : 100;
|
||||
let prev = rigth ? last(context.points) : context.points[0];
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
d3.event.on("drag", function () {
|
||||
const point = [d3.event.x | 0, d3.event.y | 0];
|
||||
|
||||
const dist2 = (prev[0] - point[0]) ** 2 + (prev[1] - point[1]) ** 2;
|
||||
|
|
@ -301,7 +351,7 @@ class Opisometer extends Measurer {
|
|||
context.updateLabel();
|
||||
});
|
||||
|
||||
d3.event.on("end", function() {
|
||||
d3.event.on("end", function () {
|
||||
if (!d3.event.sourceEvent.shiftKey) context.optimize();
|
||||
});
|
||||
}
|
||||
|
|
@ -367,13 +417,37 @@ class RouteOpisometer extends Measurer {
|
|||
const dash = this.getDash();
|
||||
const context = this;
|
||||
|
||||
const el = this.el = ruler.append("g").attr("class", "opisometer").attr("font-size", 10 * size);
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "opisometer")
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("path").attr("class", "white").attr("stroke-width", size);
|
||||
el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
|
||||
const rulerPoints = el.append("g").attr("class", "rulerPoints").attr("stroke-width", .5 * size).attr("font-size", 2 * size);
|
||||
rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 0)}));
|
||||
rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 1)}));
|
||||
el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", () => rulers.remove(this.id));
|
||||
const rulerPoints = el
|
||||
.append("g")
|
||||
.attr("class", "rulerPoints")
|
||||
.attr("stroke-width", 0.5 * size)
|
||||
.attr("font-size", 2 * size);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
context.dragControl(context, 0);
|
||||
})
|
||||
);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
context.dragControl(context, 1);
|
||||
})
|
||||
);
|
||||
el.append("text")
|
||||
.attr("dx", ".35em")
|
||||
.attr("dy", "-.45em")
|
||||
.on("click", () => rulers.remove(this.id));
|
||||
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
|
|
@ -381,7 +455,7 @@ class RouteOpisometer extends Measurer {
|
|||
}
|
||||
|
||||
updateCurve() {
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(.5));
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.5));
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
|
||||
|
|
@ -399,7 +473,7 @@ class RouteOpisometer extends Measurer {
|
|||
}
|
||||
|
||||
dragControl(context, rigth) {
|
||||
d3.event.on("drag", function() {
|
||||
d3.event.on("drag", function () {
|
||||
const mousePoint = [d3.event.x | 0, d3.event.y | 0];
|
||||
const cells = pack.cells;
|
||||
|
||||
|
|
@ -422,7 +496,11 @@ class Planimeter extends Measurer {
|
|||
if (this.el) this.el.selectAll("*").remove();
|
||||
const size = this.getSize();
|
||||
|
||||
const el = this.el = ruler.append("g").attr("class", "planimeter").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size);
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "planimeter")
|
||||
.call(d3.drag().on("start", this.drag))
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("path").attr("class", "planimeter").attr("stroke-width", size);
|
||||
el.append("text").on("click", () => rulers.remove(this.id));
|
||||
|
||||
|
|
@ -432,7 +510,7 @@ class Planimeter extends Measurer {
|
|||
}
|
||||
|
||||
updateCurve() {
|
||||
lineGen.curve(d3.curveCatmullRomClosed.alpha(.5));
|
||||
lineGen.curve(d3.curveCatmullRomClosed.alpha(0.5));
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
}
|
||||
|
|
@ -458,36 +536,79 @@ function drawScaleBar() {
|
|||
|
||||
// calculate size
|
||||
const init = 100; // actual length in pixels if scale, dScale and size = 1;
|
||||
const size = +barSize.value;
|
||||
let val = init * size * dScale / scale; // bar length in distance unit
|
||||
if (val > 900) val = rn(val, -3); // round to 1000
|
||||
else if (val > 90) val = rn(val, -2); // round to 100
|
||||
else if (val > 9) val = rn(val, -1); // round to 10
|
||||
else val = rn(val) // round to 1
|
||||
const l = val * scale / dScale; // actual length in pixels on this scale
|
||||
const size = +barSizeInput.value;
|
||||
let val = (init * size * dScale) / scale; // bar length in distance unit
|
||||
if (val > 900) val = rn(val, -3);
|
||||
// round to 1000
|
||||
else if (val > 90) val = rn(val, -2);
|
||||
// round to 100
|
||||
else if (val > 9) val = rn(val, -1);
|
||||
// round to 10
|
||||
else val = rn(val); // round to 1
|
||||
const l = (val * scale) / dScale; // actual length in pixels on this scale
|
||||
|
||||
scaleBar.append("line").attr("x1", 0.5).attr("y1", 0).attr("x2", l+size-0.5).attr("y2", 0).attr("stroke-width", size).attr("stroke", "white");
|
||||
scaleBar.append("line").attr("x1", 0).attr("y1", size).attr("x2", l+size).attr("y2", size).attr("stroke-width", size).attr("stroke", "#3d3d3d");
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0.5)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", l + size - 0.5)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "white");
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", size)
|
||||
.attr("x2", l + size)
|
||||
.attr("y2", size)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "#3d3d3d");
|
||||
const dash = size + " " + rn(l / 5 - size, 2);
|
||||
scaleBar.append("line").attr("x1", 0).attr("y1", 0).attr("x2", l+size).attr("y2", 0)
|
||||
.attr("stroke-width", rn(size * 3, 2)).attr("stroke-dasharray", dash).attr("stroke", "#3d3d3d");
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", l + size)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", rn(size * 3, 2))
|
||||
.attr("stroke-dasharray", dash)
|
||||
.attr("stroke", "#3d3d3d");
|
||||
|
||||
const fontSize = rn(5 * size, 1);
|
||||
scaleBar.selectAll("text").data(d3.range(0,6)).enter().append("text")
|
||||
.attr("x", d => rn(d * l/5, 2)).attr("y", 0).attr("dy", "-.5em")
|
||||
.attr("font-size", fontSize).text(d => rn(d * l/5 * dScale / scale) + (d<5 ? "" : " " + unit));
|
||||
scaleBar
|
||||
.selectAll("text")
|
||||
.data(d3.range(0, 6))
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("x", d => rn((d * l) / 5, 2))
|
||||
.attr("y", 0)
|
||||
.attr("dy", "-.5em")
|
||||
.attr("font-size", fontSize)
|
||||
.text(d => rn((((d * l) / 5) * dScale) / scale) + (d < 5 ? "" : " " + unit));
|
||||
|
||||
if (barLabel.value !== "") {
|
||||
scaleBar.append("text").attr("x", (l+1) / 2).attr("y", 2 * size)
|
||||
scaleBar
|
||||
.append("text")
|
||||
.attr("x", (l + 1) / 2)
|
||||
.attr("y", 2 * size)
|
||||
.attr("dominant-baseline", "text-before-edge")
|
||||
.attr("font-size", fontSize).text(barLabel.value);
|
||||
.attr("font-size", fontSize)
|
||||
.text(barLabel.value);
|
||||
}
|
||||
|
||||
const bbox = scaleBar.node().getBBox();
|
||||
// append backbround rectangle
|
||||
scaleBar.insert("rect", ":first-child").attr("x", -10).attr("y", -20).attr("width", bbox.width + 10).attr("height", bbox.height + 15)
|
||||
.attr("stroke-width", size).attr("stroke", "none").attr("filter", "url(#blur5)")
|
||||
.attr("fill", barBackColor.value).attr("opacity", +barBackOpacity.value);
|
||||
scaleBar
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", -10)
|
||||
.attr("y", -20)
|
||||
.attr("width", bbox.width + 10)
|
||||
.attr("height", bbox.height + 15)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "none")
|
||||
.attr("filter", "url(#blur5)")
|
||||
.attr("fill", barBackColor.value)
|
||||
.attr("opacity", +barBackOpacity.value);
|
||||
|
||||
fitScaleBar();
|
||||
}
|
||||
|
|
@ -495,9 +616,10 @@ function drawScaleBar() {
|
|||
// fit ScaleBar to canvas size
|
||||
function fitScaleBar() {
|
||||
if (!scaleBar.select("rect").size() || scaleBar.style("display") === "none") return;
|
||||
const px = isNaN(+barPosX.value) ? .99 : barPosX.value / 100;
|
||||
const py = isNaN(+barPosY.value) ? .99 : barPosY.value / 100;
|
||||
const px = isNaN(+barPosX.value) ? 0.99 : barPosX.value / 100;
|
||||
const py = isNaN(+barPosY.value) ? 0.99 : barPosY.value / 100;
|
||||
const bbox = scaleBar.select("rect").node().getBBox();
|
||||
const x = rn(svgWidth * px - bbox.width + 10), y = rn(svgHeight * py - bbox.height + 20);
|
||||
const x = rn(svgWidth * px - bbox.width + 10),
|
||||
y = rn(svgHeight * py - bbox.height + 20);
|
||||
scaleBar.attr("transform", `translate(${x},${y})`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ function overviewMilitary() {
|
|||
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 population = rn((s.rural + s.urban * urbanization) * populationRate);
|
||||
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;
|
||||
|
|
@ -114,7 +114,7 @@ function overviewMilitary() {
|
|||
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 population = rn((s.rural + s.urban * urbanization) * populationRate);
|
||||
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']").innerHTML = si(total);
|
||||
|
|
@ -282,12 +282,21 @@ function overviewMilitary() {
|
|||
}
|
||||
|
||||
function militaryRecalculate() {
|
||||
const message = 'Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated';
|
||||
const onConfirm = () => {
|
||||
Military.generate();
|
||||
addLines();
|
||||
};
|
||||
confirmationDialog({title: 'Remove regiment', message, confirm: 'Remove', onConfirm});
|
||||
alertMessage.innerHTML = 'Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove regiment',
|
||||
buttons: {
|
||||
Recalculate: function () {
|
||||
$(this).dialog('close');
|
||||
Military.generate();
|
||||
addLines();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function downloadMilitaryData() {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ function editNotes(id, name) {
|
|||
document.getElementById('legendsToLoad').addEventListener('change', function () {
|
||||
uploadFile(this, uploadLegends);
|
||||
});
|
||||
document.getElementById('notesClearStyle').addEventListener('click', clearStyle);
|
||||
document.getElementById('notesRemove').addEventListener('click', triggerNotesRemove);
|
||||
|
||||
function showNote(note) {
|
||||
|
|
@ -89,8 +90,20 @@ function editNotes(id, name) {
|
|||
const element = document.getElementById(select.value);
|
||||
|
||||
if (element === null) {
|
||||
const message = 'Related element is not found. Would you like to remove the note?';
|
||||
confirmationDialog({title: 'Element not found', message, confirm: 'Remove', onConfirm: removeLegend});
|
||||
alertMessage.innerHTML = 'Related element is not found. Would you like to remove the note?';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Element not found',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
removeLegend();
|
||||
},
|
||||
Keep: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -104,15 +117,34 @@ function editNotes(id, name) {
|
|||
}
|
||||
|
||||
function uploadLegends(dataLoaded) {
|
||||
if (!dataLoaded) return tip('Cannot load the file. Please check the data format', false, 'error');
|
||||
if (!dataLoaded) {
|
||||
tip('Cannot load the file. Please check the data format', false, 'error');
|
||||
return;
|
||||
}
|
||||
notes = JSON.parse(dataLoaded);
|
||||
document.getElementById('notesSelect').options.length = 0;
|
||||
editNotes(notes[0].id, notes[0].name);
|
||||
}
|
||||
|
||||
function clearStyle() {
|
||||
editor.content.innerHTML = editor.content.textContent;
|
||||
}
|
||||
|
||||
function triggerNotesRemove() {
|
||||
const message = 'Are you sure you want to remove the selected note? <br>This action cannot be reverted';
|
||||
confirmationDialog({title: 'Remove note', message, confirm: 'Remove', onConfirm: removeLegend});
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the selected note?';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove note',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
removeLegend();
|
||||
},
|
||||
Keep: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeLegend() {
|
||||
|
|
@ -120,7 +152,10 @@ function editNotes(id, name) {
|
|||
const index = notes.findIndex((n) => n.id === select.value);
|
||||
notes.splice(index, 1);
|
||||
select.options.length = 0;
|
||||
if (!notes.length) return $('#notesEditor').dialog('close');
|
||||
if (!notes.length) {
|
||||
$('#notesEditor').dialog('close');
|
||||
return;
|
||||
}
|
||||
notesText.innerHTML = '';
|
||||
editNotes(notes[0].id, notes[0].name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,10 @@ function showSupporters() {
|
|||
Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,Radovan Zapletal,Jmmat6,
|
||||
Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,Guilherme Aguiar,Jarno Hallikainen,
|
||||
Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,Cooper Counts,Patrick Jones,Clonetone,
|
||||
PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram`;
|
||||
PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram,
|
||||
Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,
|
||||
Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,
|
||||
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray`;
|
||||
|
||||
const array = supporters
|
||||
.replace(/(?:\r\n|\r|\n)/g, '')
|
||||
|
|
@ -106,35 +109,51 @@ function showSupporters() {
|
|||
$('#alert').dialog({resizable: false, title: 'Patreon Supporters', width: '54vw', position: {my: 'center', at: 'center', of: 'svg'}});
|
||||
}
|
||||
|
||||
// on any option or dialog change
|
||||
document.getElementById('options').addEventListener('change', checkIfStored);
|
||||
document.getElementById('dialogs').addEventListener('change', checkIfStored);
|
||||
document.getElementById('options').addEventListener('input', updateOutputToFollowInput);
|
||||
document.getElementById('dialogs').addEventListener('input', updateOutputToFollowInput);
|
||||
|
||||
function checkIfStored(ev) {
|
||||
if (ev.target.dataset.stored) lock(ev.target.dataset.stored);
|
||||
}
|
||||
|
||||
function updateOutputToFollowInput(ev) {
|
||||
const id = ev.target.id;
|
||||
const value = ev.target.value;
|
||||
|
||||
// specific cases
|
||||
if (id === 'manorsInput') return (manorsOutput.value = value == 1000 ? 'auto' : value);
|
||||
|
||||
// generic case
|
||||
if (id.slice(-5) === 'Input') {
|
||||
const output = document.getElementById(id.slice(0, -5) + 'Output');
|
||||
if (output) output.value = value;
|
||||
} else if (id.slice(-6) === 'Output') {
|
||||
const input = document.getElementById(id.slice(0, -6) + 'Input');
|
||||
if (input) input.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Option listeners
|
||||
const optionsContent = document.getElementById('optionsContent');
|
||||
optionsContent.addEventListener('input', function (event) {
|
||||
const id = event.target.id,
|
||||
value = event.target.value;
|
||||
const id = event.target.id;
|
||||
const value = event.target.value;
|
||||
if (id === 'mapWidthInput' || id === 'mapHeightInput') mapSizeInputChange();
|
||||
else if (id === 'pointsInput') changeCellsDensity(+value);
|
||||
else if (id === 'culturesInput') culturesOutput.value = value;
|
||||
else if (id === 'culturesOutput') culturesInput.value = value;
|
||||
else if (id === 'culturesSet') changeCultureSet();
|
||||
else if (id === 'regionsInput' || id === 'regionsOutput') changeStatesNumber(value);
|
||||
else if (id === 'provincesInput') provincesOutput.value = value;
|
||||
else if (id === 'provincesOutput') provincesOutput.value = value;
|
||||
else if (id === 'provincesOutput') powerOutput.value = value;
|
||||
else if (id === 'powerInput') powerOutput.value = value;
|
||||
else if (id === 'powerOutput') powerInput.value = value;
|
||||
else if (id === 'neutralInput') neutralOutput.value = value;
|
||||
else if (id === 'neutralOutput') neutralInput.value = value;
|
||||
else if (id === 'manorsInput') changeBurgsNumberSlider(value);
|
||||
else if (id === 'religionsInput') religionsOutput.value = value;
|
||||
else if (id === 'emblemShape') changeEmblemShape(value);
|
||||
else if (id === 'tooltipSizeInput' || id === 'tooltipSizeOutput') changeTooltipSize(value);
|
||||
else if (id === 'transparencyInput') changeDialogsTransparency(value);
|
||||
});
|
||||
|
||||
optionsContent.addEventListener('change', function (event) {
|
||||
if (event.target.dataset.stored) lock(event.target.dataset.stored);
|
||||
const id = event.target.id,
|
||||
value = event.target.value;
|
||||
const id = event.target.id;
|
||||
const value = event.target.value;
|
||||
|
||||
if (id === 'zoomExtentMin' || id === 'zoomExtentMax') changeZoomExtent(value);
|
||||
else if (id === 'optionsSeed') generateMapWithSeed();
|
||||
else if (id === 'uiSizeInput' || id === 'uiSizeOutput') changeUIsize(value);
|
||||
|
|
@ -330,8 +349,8 @@ function changeCellsDensity(value) {
|
|||
const cells = convert(value);
|
||||
|
||||
pointsInput.setAttribute('data-cells', cells);
|
||||
pointsOutput.value = cells / 1000 + 'K';
|
||||
pointsOutput.style.color = cells > 50000 ? '#b12117' : cells !== 10000 ? '#dfdf12' : '#053305';
|
||||
pointsOutput_formatted.value = cells / 1000 + 'K';
|
||||
pointsOutput_formatted.style.color = cells > 50000 ? '#b12117' : cells !== 10000 ? '#dfdf12' : '#053305';
|
||||
}
|
||||
|
||||
function changeCultureSet() {
|
||||
|
|
@ -382,16 +401,11 @@ function changeEmblemShape(emblemShape) {
|
|||
}
|
||||
|
||||
function changeStatesNumber(value) {
|
||||
regionsInput.value = regionsOutput.value = value;
|
||||
regionsOutput.style.color = +value ? null : '#b12117';
|
||||
burgLabels.select('#capitals').attr('data-size', Math.max(rn(6 - value / 20), 3));
|
||||
labels.select('#countries').attr('data-size', Math.max(rn(18 - value / 6), 4));
|
||||
}
|
||||
|
||||
function changeBurgsNumberSlider(value) {
|
||||
manorsOutput.value = value == 1000 ? 'auto' : value;
|
||||
}
|
||||
|
||||
function changeUIsize(value) {
|
||||
if (isNaN(+value) || +value < 0.5) return;
|
||||
|
||||
|
|
@ -408,7 +422,6 @@ function getUImaxSize() {
|
|||
}
|
||||
|
||||
function changeTooltipSize(value) {
|
||||
tooltipSizeInput.value = tooltipSizeOutput.value = value;
|
||||
tooltip.style.fontSize = `calc(${value}px + 0.5vw)`;
|
||||
}
|
||||
|
||||
|
|
@ -446,8 +459,9 @@ function applyStoredOptions() {
|
|||
if (localStorage.getItem('heightUnit')) applyOption(heightUnit, localStorage.getItem('heightUnit'));
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const stored = localStorage.key(i),
|
||||
value = localStorage.getItem(stored);
|
||||
const stored = localStorage.key(i);
|
||||
const value = localStorage.getItem(stored);
|
||||
|
||||
if (stored === 'speakerVoice') continue;
|
||||
const input = document.getElementById(stored + 'Input') || document.getElementById(stored);
|
||||
const output = document.getElementById(stored + 'Output');
|
||||
|
|
@ -606,23 +620,40 @@ document.getElementById('sticked').addEventListener('click', function (event) {
|
|||
});
|
||||
|
||||
function regeneratePrompt() {
|
||||
if (customization) return tip('New map cannot be generated when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
const workingMinutes = (Date.now() - last(mapHistory).created) / 60000;
|
||||
if (workingMinutes < 5) return regenerateMap();
|
||||
|
||||
const message = 'Are you sure you want to generate a new map? <br>All unsaved changes made to the current map will be lost';
|
||||
const onConfirm = () => {
|
||||
closeDialogs();
|
||||
if (customization) {
|
||||
tip('New map cannot be generated when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
return;
|
||||
}
|
||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||
if (workingTime < 5) {
|
||||
regenerateMap();
|
||||
};
|
||||
confirmationDialog({title: 'Generate new map', message, confirm: 'Generate', onConfirm});
|
||||
return;
|
||||
}
|
||||
|
||||
alertMessage.innerHTML = `Are you sure you want to generate a new map?<br>
|
||||
All unsaved changes made to the current map will be lost`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Generate new map',
|
||||
buttons: {
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Generate: function () {
|
||||
closeDialogs();
|
||||
regenerateMap();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showSavePane() {
|
||||
document.getElementById('showLabels').checked = !hideLabels.checked;
|
||||
|
||||
$('#saveMapData').dialog({
|
||||
title: 'Save map',
|
||||
resizable: false,
|
||||
width: '27em',
|
||||
width: '30em',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Close: function () {
|
||||
|
|
@ -703,6 +734,74 @@ document.getElementById('mapToLoad').addEventListener('change', function () {
|
|||
uploadMap(fileToLoad);
|
||||
});
|
||||
|
||||
function openSaveTiles() {
|
||||
closeDialogs();
|
||||
updateTilesOptions();
|
||||
const status = document.getElementById('tileStatus');
|
||||
status.innerHTML = '';
|
||||
let loading = null;
|
||||
|
||||
$('#saveTilesScreen').dialog({
|
||||
resizable: false,
|
||||
title: 'Download tiles',
|
||||
width: '23em',
|
||||
buttons: {
|
||||
Download: function () {
|
||||
status.innerHTML = 'Preparing for download...';
|
||||
setTimeout(() => (status.innerHTML = 'Downloading. It may take some time.'), 1000);
|
||||
loading = setInterval(() => (status.innerHTML += '.'), 1000);
|
||||
saveTiles().then(() => {
|
||||
clearInterval(loading);
|
||||
status.innerHTML = `Done. Check file in "Downloads" (crtl + J)`;
|
||||
setTimeout(() => (status.innerHTML = ''), 8000);
|
||||
});
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
close: () => {
|
||||
debug.selectAll('*').remove();
|
||||
clearInterval(loading);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById('saveTilesScreen')
|
||||
.querySelectorAll('input')
|
||||
.forEach((el) => el.addEventListener('input', updateTilesOptions));
|
||||
|
||||
function updateTilesOptions() {
|
||||
const tileSize = document.getElementById('tileSize');
|
||||
const tilesX = +document.getElementById('tileColsOutput').value;
|
||||
const tilesY = +document.getElementById('tileRowsOutput').value;
|
||||
const scale = +document.getElementById('tileScaleOutput').value;
|
||||
|
||||
// calculate size
|
||||
const sizeX = graphWidth * scale * tilesX;
|
||||
const sizeY = graphHeight * scale * tilesY;
|
||||
const totalSize = sizeX * sizeY;
|
||||
|
||||
tileSize.innerHTML = `${sizeX} x ${sizeY} px`;
|
||||
tileSize.style.color = totalSize > 1e9 ? '#d00b0b' : totalSize > 1e8 ? '#9e6409' : '#1a941a';
|
||||
|
||||
// draw tiles
|
||||
const rects = [];
|
||||
const labels = [];
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
||||
rects.push(`<rect x=${x} y=${y} width=${tileW} height=${tileH} />`);
|
||||
labels.push(`<text x=${x + tileW / 2} y=${y + tileH / 2}>${i}</text>`);
|
||||
}
|
||||
}
|
||||
const rectsG = "<g fill='none' stroke='#000'>" + rects.join('') + '</g>';
|
||||
const labelsG = "<g fill='#000' stroke='none' text-anchor='middle' dominant-baseline='central' font-size='24px'>" + labels.join('') + '</g>';
|
||||
debug.html(rectsG + labelsG);
|
||||
}
|
||||
|
||||
// View mode
|
||||
viewMode.addEventListener('click', changeViewMode);
|
||||
function changeViewMode(event) {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ function editProvinces() {
|
|||
else if (cl.contains('name')) editProvinceName(p);
|
||||
else if (cl.contains('coaIcon')) editEmblem('province', 'provinceCOA' + p, pack.provinces[p]);
|
||||
else if (cl.contains('icon-star-empty')) capitalZoomIn(p);
|
||||
else if (cl.contains('icon-flag-empty')) triggerIndependence(p);
|
||||
else if (cl.contains('icon-flag-empty')) triggerIndependencePromps(p);
|
||||
else if (cl.contains('culturePopulation')) changePopulation(p);
|
||||
else if (cl.contains('icon-pin')) toggleFog(p, cl);
|
||||
else if (cl.contains('icon-trash-empty')) removeProvince(p);
|
||||
|
|
@ -118,8 +118,8 @@ function editProvinces() {
|
|||
for (const p of filtered) {
|
||||
const area = p.area * distanceScaleInput.value ** 2;
|
||||
totalArea += area;
|
||||
const rural = p.rural * populationRate.value;
|
||||
const urban = p.urban * populationRate.value * urbanization.value;
|
||||
const rural = p.rural * populationRate;
|
||||
const urban = p.urban * populationRate * urbanization;
|
||||
const population = rn(rural + urban);
|
||||
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`;
|
||||
totalPopulation += population;
|
||||
|
|
@ -233,9 +233,21 @@ function editProvinces() {
|
|||
zoomTo(x, y, 8, 2000);
|
||||
}
|
||||
|
||||
function triggerIndependence(p) {
|
||||
const message = 'Are you sure you want to declare province independence? <br>It will turn province into a new state';
|
||||
confirmationDialog({title: 'Declare independence', message, confirm: 'Declare', onConfirm: () => declareProvinceIndependence(p)});
|
||||
function triggerIndependencePromps(p) {
|
||||
alertMessage.innerHTML = 'Are you sure you want to declare province independence? <br>It will turn province into a new state';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Declare independence',
|
||||
buttons: {
|
||||
Declare: function () {
|
||||
declareProvinceIndependence(p);
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function declareProvinceIndependence(p) {
|
||||
|
|
@ -328,8 +340,8 @@ function editProvinces() {
|
|||
tip('Province does not have any cells, cannot change population', false, 'error');
|
||||
return;
|
||||
}
|
||||
const rural = rn(p.rural * populationRate.value);
|
||||
const urban = rn(p.urban * populationRate.value * urbanization.value);
|
||||
const rural = rn(p.rural * populationRate);
|
||||
const urban = rn(p.urban * populationRate * urbanization);
|
||||
const total = rural + urban;
|
||||
const l = (n) => Number(n).toLocaleString();
|
||||
|
||||
|
|
@ -370,7 +382,7 @@ function editProvinces() {
|
|||
cells.forEach((i) => (pack.cells.pop[i] *= ruralChange));
|
||||
}
|
||||
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
|
||||
const points = ruralPop.value / populationRate.value;
|
||||
const points = ruralPop.value / populationRate;
|
||||
const pop = rn(points / cells.length);
|
||||
cells.forEach((i) => (pack.cells.pop[i] = pop));
|
||||
}
|
||||
|
|
@ -380,7 +392,7 @@ function editProvinces() {
|
|||
p.burgs.forEach((b) => (pack.burgs[b].population = rn(pack.burgs[b].population * urbanChange, 4)));
|
||||
}
|
||||
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
|
||||
const points = urbanPop.value / populationRate.value / urbanization.value;
|
||||
const points = urbanPop.value / populationRate / urbanization;
|
||||
const population = rn(points / burgs.length, 4);
|
||||
p.burgs.forEach((b) => (pack.burgs[b].population = population));
|
||||
}
|
||||
|
|
@ -397,31 +409,40 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function removeProvince(p) {
|
||||
const message = 'Are you sure you want to remove the province? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
pack.cells.province.forEach((province, i) => {
|
||||
if (province === p) pack.cells.province[i] = 0;
|
||||
});
|
||||
const s = pack.provinces[p].state;
|
||||
const state = pack.states[s];
|
||||
if (state.provinces.includes(p)) state.provinces.splice(state.provinces.indexOf(p), 1);
|
||||
alertMessage.innerHTML = `Are you sure you want to remove the province? <br>This action cannot be reverted`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove province',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
pack.cells.province.forEach((province, i) => {
|
||||
if (province === p) pack.cells.province[i] = 0;
|
||||
});
|
||||
const s = pack.provinces[p].state,
|
||||
state = pack.states[s];
|
||||
if (state.provinces.includes(p)) state.provinces.splice(state.provinces.indexOf(p), 1);
|
||||
|
||||
unfog('focusProvince' + p);
|
||||
unfog('focusProvince' + p);
|
||||
|
||||
const coaId = 'provinceCOA' + p;
|
||||
if (document.getElementById(coaId)) document.getElementById(coaId).remove();
|
||||
emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove();
|
||||
const coaId = 'provinceCOA' + p;
|
||||
if (document.getElementById(coaId)) document.getElementById(coaId).remove();
|
||||
emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove();
|
||||
|
||||
pack.provinces[p] = {i: p, removed: true};
|
||||
pack.provinces[p] = {i: p, removed: true};
|
||||
|
||||
const g = provs.select('#provincesBody');
|
||||
g.select('#province' + p).remove();
|
||||
g.select('#province-gap' + p).remove();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
else drawBorders();
|
||||
refreshProvincesEditor();
|
||||
};
|
||||
confirmationDialog({title: 'Remove province', message, confirm: 'Remove', onConfirm});
|
||||
const g = provs.select('#provincesBody');
|
||||
g.select('#province' + p).remove();
|
||||
g.select('#province-gap' + p).remove();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
else drawBorders();
|
||||
refreshProvincesEditor();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editProvinceName(province) {
|
||||
|
|
@ -571,8 +592,8 @@ function editProvinces() {
|
|||
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
const area = d.data.area * distanceScaleInput.value ** 2 + unit;
|
||||
const rural = rn(d.data.rural * populationRate.value);
|
||||
const urban = rn(d.data.urban * populationRate.value * urbanization.value);
|
||||
const rural = rn(d.data.rural * populationRate);
|
||||
const urban = rn(d.data.urban * populationRate * urbanization);
|
||||
|
||||
const value =
|
||||
provincesTreeType.value === 'area'
|
||||
|
|
@ -938,8 +959,8 @@ function editProvinces() {
|
|||
data += el.dataset.capital + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + ',';
|
||||
data += `${Math.round(pack.provinces[key].rural * populationRate.value)},`;
|
||||
data += `${Math.round(pack.provinces[key].urban * populationRate.value * urbanization.value)}\n`;
|
||||
data += `${Math.round(pack.provinces[key].rural * populationRate)},`;
|
||||
data += `${Math.round(pack.provinces[key].urban * populationRate * urbanization)}\n`;
|
||||
});
|
||||
|
||||
const name = getFileName('Provinces') + '.csv';
|
||||
|
|
@ -947,26 +968,36 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function removeAllProvinces() {
|
||||
const message = `Are you sure you want to remove all provinces? <br>This action cannot be reverted`;
|
||||
const onConfirm = () => {
|
||||
// remove emblems
|
||||
document.querySelectorAll("[id^='provinceCOA']").forEach((el) => el.remove());
|
||||
emblems.select('#provinceEmblems').selectAll('*').remove();
|
||||
alertMessage.innerHTML = `Are you sure you want to remove all provinces? <br>This action cannot be reverted`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove all provinces',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
|
||||
// remove data
|
||||
pack.provinces = [0];
|
||||
pack.cells.province = new Uint16Array(pack.cells.i.length);
|
||||
pack.states.forEach((s) => (s.provinces = []));
|
||||
// remove emblems
|
||||
document.querySelectorAll("[id^='provinceCOA']").forEach((el) => el.remove());
|
||||
emblems.select('#provinceEmblems').selectAll('*').remove();
|
||||
|
||||
unfog();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
else drawBorders();
|
||||
provs.select('#provincesBody').remove();
|
||||
turnButtonOff('toggleProvinces');
|
||||
// remove data
|
||||
pack.provinces = [0];
|
||||
pack.cells.province = new Uint16Array(pack.cells.i.length);
|
||||
pack.states.forEach((s) => (s.provinces = []));
|
||||
|
||||
provincesEditorAddLines();
|
||||
};
|
||||
confirmationDialog({title: 'Remove all provinces', message, confirm: 'Remove', onConfirm});
|
||||
unfog();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
else drawBorders();
|
||||
provs.select('#provincesBody').remove();
|
||||
turnButtonOff('toggleProvinces');
|
||||
|
||||
provincesEditorAddLines();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function dragLabel() {
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ function editReligions() {
|
|||
if (r.removed) continue;
|
||||
|
||||
const area = r.area * distanceScaleInput.value ** 2;
|
||||
const rural = r.rural * populationRate.value;
|
||||
const urban = r.urban * populationRate.value * urbanization.value;
|
||||
const rural = r.rural * populationRate;
|
||||
const urban = r.urban * populationRate * urbanization;
|
||||
const population = rn(rural + urban);
|
||||
if (r.i && !r.cells && body.dataset.extinct !== 'show') continue; // hide extinct religions
|
||||
const populationTip = `Believers: ${si(population)}; Rural areas: ${si(rural)}; Urban areas: ${si(urban)}. Click to change`;
|
||||
|
|
@ -160,8 +160,8 @@ function editReligions() {
|
|||
const r = pack.religions[religion];
|
||||
const type = r.name.includes(r.type) ? '' : r.type === 'Folk' || r.type === 'Organized' ? '. ' + r.type + ' religion' : '. ' + r.type;
|
||||
const form = r.form === r.type || r.name.includes(r.form) ? '' : '. ' + r.form;
|
||||
const rural = r.rural * populationRate.value;
|
||||
const urban = r.urban * populationRate.value * urbanization.value;
|
||||
const rural = r.rural * populationRate;
|
||||
const urban = r.urban * populationRate * urbanization;
|
||||
const population = rural + urban > 0 ? '. ' + si(rn(rural + urban)) + ' believers' : '. Extinct';
|
||||
info.innerHTML = `${r.name}${type}${form}${population}`;
|
||||
tip('Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation');
|
||||
|
|
@ -273,8 +273,8 @@ function editReligions() {
|
|||
tip('Religion does not have any cells, cannot change population', false, 'error');
|
||||
return;
|
||||
}
|
||||
const rural = rn(r.rural * populationRate.value);
|
||||
const urban = rn(r.urban * populationRate.value * urbanization.value);
|
||||
const rural = rn(r.rural * populationRate);
|
||||
const urban = rn(r.urban * populationRate * urbanization);
|
||||
const total = rural + urban;
|
||||
const l = (n) => Number(n).toLocaleString();
|
||||
const burgs = pack.burgs.filter((b) => !b.removed && pack.cells.religion[b.cell] === religion);
|
||||
|
|
@ -318,7 +318,7 @@ function editReligions() {
|
|||
cells.forEach((i) => (pack.cells.pop[i] *= ruralChange));
|
||||
}
|
||||
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
|
||||
const points = ruralPop.value / populationRate.value;
|
||||
const points = ruralPop.value / populationRate;
|
||||
const cells = pack.cells.i.filter((i) => pack.cells.religion[i] === religion);
|
||||
const pop = rn(points / cells.length);
|
||||
cells.forEach((i) => (pack.cells.pop[i] = pop));
|
||||
|
|
@ -329,7 +329,7 @@ function editReligions() {
|
|||
burgs.forEach((b) => (b.population = rn(b.population * urbanChange, 4)));
|
||||
}
|
||||
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
|
||||
const points = urbanPop.value / populationRate.value / urbanization.value;
|
||||
const points = urbanPop.value / populationRate / urbanization;
|
||||
const population = rn(points / burgs.length, 4);
|
||||
burgs.forEach((b) => (b.population = population));
|
||||
}
|
||||
|
|
@ -342,24 +342,33 @@ function editReligions() {
|
|||
if (customization) return;
|
||||
const religion = +this.parentNode.dataset.id;
|
||||
|
||||
const message = 'Are you sure you want to remove the religion? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
relig.select('#religion' + religion).remove();
|
||||
relig.select('#religion-gap' + religion).remove();
|
||||
debug.select('#religionsCenter' + religion).remove();
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the religion? <br>This action cannot be reverted';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove religion',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
relig.select('#religion' + religion).remove();
|
||||
relig.select('#religion-gap' + religion).remove();
|
||||
debug.select('#religionsCenter' + religion).remove();
|
||||
|
||||
pack.cells.religion.forEach((r, i) => {
|
||||
if (r === religion) pack.cells.religion[i] = 0;
|
||||
});
|
||||
pack.religions[religion].removed = true;
|
||||
const origin = pack.religions[religion].origin;
|
||||
pack.religions.forEach((r) => {
|
||||
if (r.origin === religion) r.origin = origin;
|
||||
});
|
||||
pack.cells.religion.forEach((r, i) => {
|
||||
if (r === religion) pack.cells.religion[i] = 0;
|
||||
});
|
||||
pack.religions[religion].removed = true;
|
||||
const origin = pack.religions[religion].origin;
|
||||
pack.religions.forEach((r) => {
|
||||
if (r.origin === religion) r.origin = origin;
|
||||
});
|
||||
|
||||
refreshReligionsEditor();
|
||||
};
|
||||
confirmationDialog({title: 'Remove religion', message, confirm: 'Remove', onConfirm});
|
||||
refreshReligionsEditor();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function drawReligionCenters() {
|
||||
|
|
|
|||
|
|
@ -89,8 +89,8 @@ function editStates() {
|
|||
for (const s of pack.states) {
|
||||
if (s.removed) continue;
|
||||
const area = s.area * distanceScaleInput.value ** 2;
|
||||
const rural = s.rural * populationRate.value;
|
||||
const urban = s.urban * populationRate.value * urbanization.value;
|
||||
const rural = s.rural * populationRate;
|
||||
const urban = s.urban * populationRate * urbanization;
|
||||
const population = rn(rural + urban);
|
||||
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`;
|
||||
totalArea += area;
|
||||
|
|
@ -362,8 +362,8 @@ function editStates() {
|
|||
tip('State does not have any cells, cannot change population', false, 'error');
|
||||
return;
|
||||
}
|
||||
const rural = rn(s.rural * populationRate.value);
|
||||
const urban = rn(s.urban * populationRate.value * urbanization.value);
|
||||
const rural = rn(s.rural * populationRate);
|
||||
const urban = rn(s.urban * populationRate * urbanization);
|
||||
const total = rural + urban;
|
||||
const l = (n) => Number(n).toLocaleString();
|
||||
|
||||
|
|
@ -405,7 +405,7 @@ function editStates() {
|
|||
cells.forEach((i) => (pack.cells.pop[i] *= ruralChange));
|
||||
}
|
||||
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
|
||||
const points = ruralPop.value / populationRate.value;
|
||||
const points = ruralPop.value / populationRate;
|
||||
const cells = pack.cells.i.filter((i) => pack.cells.state[i] === state);
|
||||
const pop = points / cells.length;
|
||||
cells.forEach((i) => (pack.cells.pop[i] = pop));
|
||||
|
|
@ -417,7 +417,7 @@ function editStates() {
|
|||
burgs.forEach((b) => (b.population = rn(b.population * urbanChange, 4)));
|
||||
}
|
||||
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
|
||||
const points = urbanPop.value / populationRate.value / urbanization.value;
|
||||
const points = urbanPop.value / populationRate / urbanization;
|
||||
const burgs = pack.burgs.filter((b) => !b.removed && b.state === state);
|
||||
const population = rn(points / burgs.length, 4);
|
||||
burgs.forEach((b) => (b.population = population));
|
||||
|
|
@ -459,8 +459,21 @@ function editStates() {
|
|||
|
||||
function stateRemovePrompt(state) {
|
||||
if (customization) return;
|
||||
const message = 'Are you sure you want to remove the state? <br>This action cannot be reverted';
|
||||
confirmationDialog({title: 'Remove state', message, confirm: 'Remove', onConfirm: () => stateRemove(state)});
|
||||
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the state? <br>This action cannot be reverted';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove state',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
stateRemove(state);
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function stateRemove(state) {
|
||||
|
|
@ -627,8 +640,8 @@ function editStates() {
|
|||
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
const area = d.data.area * distanceScaleInput.value ** 2 + unit;
|
||||
const rural = rn(d.data.rural * populationRate.value);
|
||||
const urban = rn(d.data.urban * populationRate.value * urbanization.value);
|
||||
const rural = rn(d.data.rural * populationRate);
|
||||
const urban = rn(d.data.urban * populationRate * urbanization);
|
||||
|
||||
const option = statesTreeType.value;
|
||||
const value =
|
||||
|
|
@ -1056,8 +1069,8 @@ function editStates() {
|
|||
data += el.dataset.burgs + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + ',';
|
||||
data += `${Math.round(pack.states[key].rural * populationRate.value)},`;
|
||||
data += `${Math.round(pack.states[key].urban * populationRate.value * urbanization.value)}\n`;
|
||||
data += `${Math.round(pack.states[key].rural * populationRate)},`;
|
||||
data += `${Math.round(pack.states[key].urban * populationRate * urbanization)}\n`;
|
||||
});
|
||||
|
||||
const name = getFileName('States') + '.csv';
|
||||
|
|
|
|||
1011
modules/ui/style.js
1011
modules/ui/style.js
File diff suppressed because one or more lines are too long
|
|
@ -1,93 +1,110 @@
|
|||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
toolsContent.addEventListener("click", function(event) {
|
||||
if (customization) {tip("Please exit the customization mode first", false, "warning"); return;}
|
||||
if (event.target.tagName !== "BUTTON") return;
|
||||
toolsContent.addEventListener('click', function (event) {
|
||||
if (customization) {
|
||||
tip('Please exit the customization mode first', false, 'warning');
|
||||
return;
|
||||
}
|
||||
if (event.target.tagName !== 'BUTTON') return;
|
||||
const button = event.target.id;
|
||||
|
||||
// Click to open Editor buttons
|
||||
if (button === "editHeightmapButton") editHeightmap(); else
|
||||
if (button === "editBiomesButton") editBiomes(); else
|
||||
if (button === "editStatesButton") editStates(); else
|
||||
if (button === "editProvincesButton") editProvinces(); else
|
||||
if (button === "editDiplomacyButton") editDiplomacy(); else
|
||||
if (button === "editCulturesButton") editCultures(); else
|
||||
if (button === "editReligions") editReligions(); else
|
||||
if (button === "editResources") editResources(); else
|
||||
if (button === "editEmblemButton") openEmblemEditor(); else
|
||||
if (button === "editNamesBaseButton") editNamesbase(); else
|
||||
if (button === "editUnitsButton") editUnits(); else
|
||||
if (button === "editNotesButton") editNotes(); else
|
||||
if (button === "editZonesButton") editZones(); else
|
||||
if (button === "overviewBurgsButton") overviewBurgs(); else
|
||||
if (button === "overviewRiversButton") overviewRivers(); else
|
||||
if (button === "overviewMilitaryButton") overviewMilitary(); else
|
||||
if (button === "overviewCellsButton") viewCellDetails();
|
||||
if (button === 'editHeightmapButton') editHeightmap();
|
||||
else if (button === 'editBiomesButton') editBiomes();
|
||||
else if (button === 'editStatesButton') editStates();
|
||||
else if (button === 'editProvincesButton') editProvinces();
|
||||
else if (button === 'editDiplomacyButton') editDiplomacy();
|
||||
else if (button === 'editCulturesButton') editCultures();
|
||||
else if (button === 'editReligions') editReligions();
|
||||
else if (button === 'editEmblemButton') openEmblemEditor();
|
||||
else if (button === 'editNamesBaseButton') editNamesbase();
|
||||
else if (button === 'editUnitsButton') editUnits();
|
||||
else if (button === 'editNotesButton') editNotes();
|
||||
else if (button === 'editZonesButton') editZones();
|
||||
else if (button === 'overviewBurgsButton') overviewBurgs();
|
||||
else if (button === 'overviewRiversButton') overviewRivers();
|
||||
else if (button === 'overviewMilitaryButton') overviewMilitary();
|
||||
else if (button === 'overviewCellsButton') viewCellDetails();
|
||||
|
||||
// Click to Regenerate buttons
|
||||
if (event.target.parentNode.id === "regenerateFeature") {
|
||||
if (sessionStorage.getItem("regenerateFeatureDontAsk")) {processFeatureRegeneration(event, button); return;}
|
||||
if (event.target.parentNode.id === 'regenerateFeature') {
|
||||
if (sessionStorage.getItem('regenerateFeatureDontAsk')) {
|
||||
processFeatureRegeneration(event, button);
|
||||
return;
|
||||
}
|
||||
|
||||
alertMessage.innerHTML = `Regeneration will remove all the custom changes for the element.<br><br>Are you sure you want to proceed?`
|
||||
$("#alert").dialog({resizable: false, title: "Regenerate element",
|
||||
alertMessage.innerHTML = `Regeneration will remove all the custom changes for the element.<br><br>Are you sure you want to proceed?`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Regenerate element',
|
||||
buttons: {
|
||||
Proceed: function() {processFeatureRegeneration(event, button); $(this).dialog("close");},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
Proceed: function () {
|
||||
processFeatureRegeneration(event, button);
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
open: function() {
|
||||
const pane = $(this).dialog("widget").find(".ui-dialog-buttonpane");
|
||||
open: function () {
|
||||
const pane = $(this).dialog('widget').find('.ui-dialog-buttonpane');
|
||||
$('<span><input id="dontAsk" class="checkbox" type="checkbox"><label for="dontAsk" class="checkbox-label dontAsk"><i>do not ask again</i></label><span>').prependTo(pane);
|
||||
},
|
||||
close: function() {
|
||||
const box = $(this).dialog("widget").find(".checkbox")[0];
|
||||
close: function () {
|
||||
const box = $(this).dialog('widget').find('.checkbox')[0];
|
||||
if (!box) return;
|
||||
if (box.checked) sessionStorage.setItem("regenerateFeatureDontAsk", true);
|
||||
$(this).dialog("destroy");
|
||||
if (box.checked) sessionStorage.setItem('regenerateFeatureDontAsk', true);
|
||||
$(this).dialog('destroy');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Click to Add buttons
|
||||
if (button === "addLabel") toggleAddLabel(); else
|
||||
if (button === "addBurgTool") toggleAddBurg(); else
|
||||
if (button === "addRiver") toggleAddRiver(); else
|
||||
if (button === "addRoute") toggleAddRoute(); else
|
||||
if (button === "addMarker") toggleAddMarker();
|
||||
if (button === 'addLabel') toggleAddLabel();
|
||||
else if (button === 'addBurgTool') toggleAddBurg();
|
||||
else if (button === 'addRiver') toggleAddRiver();
|
||||
else if (button === 'addRoute') toggleAddRoute();
|
||||
else if (button === 'addMarker') toggleAddMarker();
|
||||
});
|
||||
|
||||
function processFeatureRegeneration(event, button) {
|
||||
if (button === "regenerateStateLabels") {BurgsAndStates.drawStateLabels(); if (!layerIsOn("toggleLabels")) toggleLabels();} else
|
||||
if (button === "regenerateReliefIcons") {ReliefIcons(); if (!layerIsOn("toggleRelief")) toggleRelief();} else
|
||||
if (button === "regenerateRoutes") {Routes.regenerate(); if (!layerIsOn("toggleRoutes")) toggleRoutes();} else
|
||||
if (button === "regenerateRivers") regenerateRivers(); else
|
||||
if (button === "regeneratePopulation") recalculatePopulation(); else
|
||||
if (button === "regenerateStates") regenerateStates(); else
|
||||
if (button === "regenerateProvinces") regenerateProvinces(); else
|
||||
if (button === "regenerateBurgs") regenerateBurgs(); else
|
||||
if (button === "regenerateResources") regenerateResources(); else
|
||||
if (button === "regenerateEmblems") regenerateEmblems(); else
|
||||
if (button === "regenerateReligions") regenerateReligions(); else
|
||||
if (button === "regenerateCultures") regenerateCultures(); else
|
||||
if (button === "regenerateMilitary") regenerateMilitary(); else
|
||||
if (button === "regenerateIce") regenerateIce(); else
|
||||
if (button === "regenerateMarkers") regenerateMarkers(event); else
|
||||
if (button === "regenerateZones") regenerateZones(event);
|
||||
if (button === 'regenerateStateLabels') {
|
||||
BurgsAndStates.drawStateLabels();
|
||||
if (!layerIsOn('toggleLabels')) toggleLabels();
|
||||
} else if (button === 'regenerateReliefIcons') {
|
||||
ReliefIcons();
|
||||
if (!layerIsOn('toggleRelief')) toggleRelief();
|
||||
} else if (button === 'regenerateRoutes') {
|
||||
Routes.regenerate();
|
||||
if (!layerIsOn('toggleRoutes')) toggleRoutes();
|
||||
} else if (button === 'regenerateRivers') regenerateRivers();
|
||||
else if (button === 'regeneratePopulation') recalculatePopulation();
|
||||
else if (button === 'regenerateStates') regenerateStates();
|
||||
else if (button === 'regenerateProvinces') regenerateProvinces();
|
||||
else if (button === 'regenerateBurgs') regenerateBurgs();
|
||||
else if (button === 'regenerateEmblems') regenerateEmblems();
|
||||
else if (button === 'regenerateReligions') regenerateReligions();
|
||||
else if (button === 'regenerateCultures') regenerateCultures();
|
||||
else if (button === 'regenerateMilitary') regenerateMilitary();
|
||||
else if (button === 'regenerateIce') regenerateIce();
|
||||
else if (button === 'regenerateMarkers') regenerateMarkers(event);
|
||||
else if (button === 'regenerateZones') regenerateZones(event);
|
||||
}
|
||||
|
||||
async function openEmblemEditor() {
|
||||
let type, id, el;
|
||||
|
||||
if (pack.states[1]?.coa) {
|
||||
type = "state";
|
||||
id = "stateCOA1";
|
||||
type = 'state';
|
||||
id = 'stateCOA1';
|
||||
el = pack.states[1];
|
||||
} else if (pack.burgs[1]?.coa) {
|
||||
type = "burg";
|
||||
id = "burgCOA1";
|
||||
type = 'burg';
|
||||
id = 'burgCOA1';
|
||||
el = pack.burgs[1];
|
||||
} else {
|
||||
tip("No emblems to edit, please generate states and burgs first", false, "error");
|
||||
tip('No emblems to edit, please generate states and burgs first', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -99,98 +116,105 @@ function regenerateRivers() {
|
|||
Rivers.generate();
|
||||
Lakes.defineGroup();
|
||||
Rivers.specify();
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
}
|
||||
|
||||
function recalculatePopulation() {
|
||||
rankCells();
|
||||
pack.burgs.forEach(b => {
|
||||
pack.burgs.forEach((b) => {
|
||||
if (!b.i || b.removed || b.lock) return;
|
||||
const i = b.cell;
|
||||
|
||||
b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i] / 2) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
|
||||
b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i] / 2) / 8 + b.i / 1000 + (i % 100) / 1000, 0.1), 3);
|
||||
if (b.capital) b.population = b.population * 1.3; // increase capital population
|
||||
if (b.port) b.population = b.population * 1.3; // increase port population
|
||||
b.population = rn(b.population * gauss(2,3,.6,20,3), 3);
|
||||
b.population = rn(b.population * gauss(2, 3, 0.6, 20, 3), 3);
|
||||
});
|
||||
}
|
||||
|
||||
function regenerateStates() {
|
||||
const localSeed = Math.floor(Math.random() * 1e9); // new random seed
|
||||
Math.random = aleaPRNG(localSeed);
|
||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
const burgs = pack.burgs.filter((b) => b.i && !b.removed);
|
||||
if (!burgs.length) {
|
||||
tip("No burgs to generate states. Please create burgs first", false, "error");
|
||||
tip('No burgs to generate states. Please create burgs first', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (burgs.length < +regionsInput.value) {
|
||||
tip(`Not enough burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, "warn");
|
||||
tip(`Not enough burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, 'warn');
|
||||
}
|
||||
|
||||
// burg local ids sorted by a bit randomized population:
|
||||
const sorted = burgs.map((b, i) => [i, b.population * Math.random()]).sort((a, b) => b[1] - a[1]).map(b => b[0]);
|
||||
const sorted = burgs
|
||||
.map((b, i) => [i, b.population * Math.random()])
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map((b) => b[0]);
|
||||
const capitalsTree = d3.quadtree();
|
||||
|
||||
// turn all old capitals into towns
|
||||
burgs.filter(b => b.capital).forEach(b => {
|
||||
moveBurgToGroup(b.i, "towns");
|
||||
b.capital = 0;
|
||||
});
|
||||
burgs
|
||||
.filter((b) => b.capital)
|
||||
.forEach((b) => {
|
||||
moveBurgToGroup(b.i, 'towns');
|
||||
b.capital = 0;
|
||||
});
|
||||
|
||||
// remove emblems
|
||||
document.querySelectorAll("[id^=stateCOA]").forEach(el => el.remove());
|
||||
document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove());
|
||||
emblems.selectAll("use").remove();
|
||||
document.querySelectorAll('[id^=stateCOA]').forEach((el) => el.remove());
|
||||
document.querySelectorAll('[id^=provinceCOA]').forEach((el) => el.remove());
|
||||
emblems.selectAll('use').remove();
|
||||
|
||||
unfog();
|
||||
|
||||
// if desired states number is 0
|
||||
if (regionsInput.value == 0) {
|
||||
tip(`Cannot generate zero states. Please check the <i>States Number</i> option`, false, "warn");
|
||||
pack.states = pack.states.slice(0,1); // remove all except of neutrals
|
||||
tip(`Cannot generate zero states. Please check the <i>States Number</i> option`, false, 'warn');
|
||||
pack.states = pack.states.slice(0, 1); // remove all except of neutrals
|
||||
pack.states[0].diplomacy = []; // clear diplomacy
|
||||
pack.provinces = [0]; // remove all provinces
|
||||
pack.cells.state = new Uint16Array(pack.cells.i.length); // reset cells data
|
||||
borders.selectAll("path").remove(); // remove borders
|
||||
regions.selectAll("path").remove(); // remove states fill
|
||||
labels.select("#states").selectAll("text"); // remove state labels
|
||||
defs.select("#textPaths").selectAll("path[id*='stateLabel']").remove(); // remove state labels paths
|
||||
borders.selectAll('path').remove(); // remove borders
|
||||
regions.selectAll('path').remove(); // remove states fill
|
||||
labels.select('#states').selectAll('text'); // remove state labels
|
||||
defs.select('#textPaths').selectAll("path[id*='stateLabel']").remove(); // remove state labels paths
|
||||
|
||||
if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click();
|
||||
if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click();
|
||||
if (document.getElementById('burgsOverviewRefresh').offsetParent) burgsOverviewRefresh.click();
|
||||
if (document.getElementById('statesEditorRefresh').offsetParent) statesEditorRefresh.click();
|
||||
return;
|
||||
}
|
||||
|
||||
const neutral = pack.states[0].name;
|
||||
const count = Math.min(+regionsInput.value, burgs.length);
|
||||
let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals
|
||||
pack.states = d3.range(count).map(i => {
|
||||
pack.states = d3.range(count).map((i) => {
|
||||
if (!i) return {i, name: neutral};
|
||||
|
||||
let capital = null, x = 0, y = 0;
|
||||
let capital = null,
|
||||
x = 0,
|
||||
y = 0;
|
||||
for (const i of sorted) {
|
||||
capital = burgs[i];
|
||||
x = capital.x, y = capital.y;
|
||||
(x = capital.x), (y = capital.y);
|
||||
if (capitalsTree.find(x, y, spacing) === undefined) break;
|
||||
spacing = Math.max(spacing - 1, 1);
|
||||
}
|
||||
|
||||
capitalsTree.add([x, y]);
|
||||
capital.capital = 1;
|
||||
moveBurgToGroup(capital.i, "cities");
|
||||
moveBurgToGroup(capital.i, 'cities');
|
||||
|
||||
const culture = capital.culture;
|
||||
const basename = capital.name.length < 9 && capital.cell%5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0);
|
||||
const basename = capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, '', 0);
|
||||
const name = Names.getState(basename, culture);
|
||||
const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]);
|
||||
const type = nomadic ? "Nomadic" : pack.cultures[culture].type === "Nomadic" ? "Generic" : pack.cultures[culture].type;
|
||||
const type = nomadic ? 'Nomadic' : pack.cultures[culture].type === 'Nomadic' ? 'Generic' : pack.cultures[culture].type;
|
||||
const expansionism = rn(Math.random() * powerInput.value + 1, 1);
|
||||
|
||||
const cultureType = pack.cultures[culture].type;
|
||||
const coa = COA.generate(capital.coa, .3, null, cultureType);
|
||||
const coa = COA.generate(capital.coa, 0.3, null, cultureType);
|
||||
coa.shield = capital.coa.shield;
|
||||
|
||||
return {i, name, type, capital:capital.i, center:capital.cell, culture, expansionism, coa};
|
||||
return {i, name, type, capital: capital.i, center: capital.cell, culture, expansionism, coa};
|
||||
});
|
||||
|
||||
BurgsAndStates.expandStates();
|
||||
|
|
@ -201,15 +225,17 @@ function regenerateStates() {
|
|||
BurgsAndStates.generateDiplomacy();
|
||||
BurgsAndStates.defineStateForms();
|
||||
BurgsAndStates.generateProvinces(true);
|
||||
if (!layerIsOn("toggleStates")) toggleStates(); else drawStates();
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders();
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
else drawStates();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
else drawBorders();
|
||||
BurgsAndStates.drawStateLabels();
|
||||
Military.generate();
|
||||
if (layerIsOn("toggleEmblems")) drawEmblems(); // redrawEmblems
|
||||
if (layerIsOn('toggleEmblems')) drawEmblems(); // redrawEmblems
|
||||
|
||||
if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click();
|
||||
if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click();
|
||||
if (document.getElementById("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click();
|
||||
if (document.getElementById('burgsOverviewRefresh').offsetParent) burgsOverviewRefresh.click();
|
||||
if (document.getElementById('statesEditorRefresh').offsetParent) statesEditorRefresh.click();
|
||||
if (document.getElementById('militaryOverviewRefresh').offsetParent) militaryOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateProvinces() {
|
||||
|
|
@ -217,49 +243,61 @@ function regenerateProvinces() {
|
|||
|
||||
BurgsAndStates.generateProvinces(true);
|
||||
drawBorders();
|
||||
if (layerIsOn("toggleProvinces")) drawProvinces();
|
||||
if (layerIsOn('toggleProvinces')) drawProvinces();
|
||||
|
||||
// remove emblems
|
||||
document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove());
|
||||
emblems.selectAll("use").remove();
|
||||
if (layerIsOn("toggleEmblems")) drawEmblems();
|
||||
document.querySelectorAll('[id^=provinceCOA]').forEach((el) => el.remove());
|
||||
emblems.selectAll('use').remove();
|
||||
if (layerIsOn('toggleEmblems')) drawEmblems();
|
||||
}
|
||||
|
||||
function regenerateBurgs() {
|
||||
const cells = pack.cells, states = pack.states, Lockedburgs = pack.burgs.filter(b =>b.lock);
|
||||
const cells = pack.cells,
|
||||
states = pack.states,
|
||||
Lockedburgs = pack.burgs.filter((b) => b.lock);
|
||||
rankCells();
|
||||
cells.burg = new Uint16Array(cells.i.length);
|
||||
const burgs = pack.burgs = [0]; // clear burgs array
|
||||
states.filter(s => s.i).forEach(s => s.capital = 0); // clear state capitals
|
||||
pack.provinces.filter(p => p.i).forEach(p => p.burg = 0); // clear province capitals
|
||||
const burgs = (pack.burgs = [0]); // clear burgs array
|
||||
states.filter((s) => s.i).forEach((s) => (s.capital = 0)); // clear state capitals
|
||||
pack.provinces.filter((p) => p.i).forEach((p) => (p.burg = 0)); // clear province capitals
|
||||
const burgsTree = d3.quadtree();
|
||||
|
||||
const score = new Int16Array(cells.s.map(s => s * Math.random())); // cell score for capitals placement
|
||||
const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
||||
const burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** .8) + states.length : +manorsInput.value + states.length;
|
||||
const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** .7 / 66); // base min distance between towns
|
||||
const score = new Int16Array(cells.s.map((s) => s * Math.random())); // cell score for capitals placement
|
||||
const sorted = cells.i.filter((i) => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
||||
const burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** 0.8) + states.length : +manorsInput.value + states.length;
|
||||
const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** 0.7 / 66); // base min distance between towns
|
||||
|
||||
for (let j=0; j < Lockedburgs.length; j++) {
|
||||
//clear locked list since ids will change
|
||||
//burglock.selectAll("text").remove();
|
||||
for (let j = 0; j < Lockedburgs.length; j++) {
|
||||
const id = burgs.length;
|
||||
const oldBurg = Lockedburgs[j];
|
||||
oldBurg.i = id;
|
||||
burgs.push(oldBurg);
|
||||
burgsTree.add([oldBurg.x, oldBurg.y]);
|
||||
cells.burg[oldBurg.cell] = id;
|
||||
if (oldBurg.capital) {states[oldBurg.state].capital = id; states[oldBurg.state].center = oldBurg.cell;}
|
||||
if (oldBurg.capital) {
|
||||
states[oldBurg.state].capital = id;
|
||||
states[oldBurg.state].center = oldBurg.cell;
|
||||
}
|
||||
//burglock.append("text").attr("data-id", id);
|
||||
}
|
||||
|
||||
for (let i=0; i < sorted.length && burgs.length < burgsCount; i++) {
|
||||
for (let i = 0; i < sorted.length && burgs.length < burgsCount; i++) {
|
||||
const id = burgs.length;
|
||||
const cell = sorted[i];
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
const x = cells.p[cell][0],
|
||||
y = cells.p[cell][1];
|
||||
|
||||
const s = spacing * gauss(1, .3, .2, 2, 2); // randomize to make the placement not uniform
|
||||
const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make the placement not uniform
|
||||
if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
|
||||
|
||||
const state = cells.state[cell];
|
||||
const capital = state && !states[state].capital; // if state doesn't have capital, make this burg a capital, no capital for neutral lands
|
||||
if (capital) {states[state].capital = id; states[state].center = cell;}
|
||||
if (capital) {
|
||||
states[state].capital = id;
|
||||
states[state].center = cell;
|
||||
}
|
||||
|
||||
const culture = cells.culture[cell];
|
||||
const name = Names.getCulture(culture);
|
||||
|
|
@ -269,92 +307,97 @@ function regenerateBurgs() {
|
|||
}
|
||||
|
||||
// add a capital at former place for states without added capitals
|
||||
states.filter(s => s.i && !s.removed && !s.capital).forEach(s => {
|
||||
const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg
|
||||
s.capital = burg;
|
||||
s.center = pack.burgs[burg].cell;
|
||||
pack.burgs[burg].capital = 1;
|
||||
pack.burgs[burg].state = s.i;
|
||||
moveBurgToGroup(burg, "cities");
|
||||
});
|
||||
states
|
||||
.filter((s) => s.i && !s.removed && !s.capital)
|
||||
.forEach((s) => {
|
||||
const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg
|
||||
s.capital = burg;
|
||||
s.center = pack.burgs[burg].cell;
|
||||
pack.burgs[burg].capital = 1;
|
||||
pack.burgs[burg].state = s.i;
|
||||
moveBurgToGroup(burg, 'cities');
|
||||
});
|
||||
|
||||
pack.features.forEach(f => {if (f.port) f.port = 0}); // reset features ports counter
|
||||
pack.features.forEach((f) => {
|
||||
if (f.port) f.port = 0;
|
||||
}); // reset features ports counter
|
||||
BurgsAndStates.specifyBurgs();
|
||||
BurgsAndStates.defineBurgFeatures();
|
||||
BurgsAndStates.drawBurgs();
|
||||
Routes.regenerate();
|
||||
|
||||
// remove emblems
|
||||
document.querySelectorAll("[id^=burgCOA]").forEach(el => el.remove());
|
||||
emblems.selectAll("use").remove();
|
||||
if (layerIsOn("toggleEmblems")) drawEmblems();
|
||||
document.querySelectorAll('[id^=burgCOA]').forEach((el) => el.remove());
|
||||
emblems.selectAll('use').remove();
|
||||
if (layerIsOn('toggleEmblems')) drawEmblems();
|
||||
|
||||
if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click();
|
||||
if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click();
|
||||
if (document.getElementById('burgsOverviewRefresh').offsetParent) burgsOverviewRefresh.click();
|
||||
if (document.getElementById('statesEditorRefresh').offsetParent) statesEditorRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateResources() {
|
||||
Resources.generate();
|
||||
goods.selectAll("*").remove();
|
||||
if (layerIsOn("toggleResources")) drawResources();
|
||||
goods.selectAll('*').remove();
|
||||
if (layerIsOn('toggleResources')) drawResources();
|
||||
refreshAllEditors();
|
||||
}
|
||||
|
||||
function regenerateEmblems() {
|
||||
// remove old emblems
|
||||
document.querySelectorAll("[id^=stateCOA]").forEach(el => el.remove());
|
||||
document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove());
|
||||
document.querySelectorAll("[id^=burgCOA]").forEach(el => el.remove());
|
||||
emblems.selectAll("use").remove();
|
||||
document.querySelectorAll('[id^=stateCOA]').forEach((el) => el.remove());
|
||||
document.querySelectorAll('[id^=provinceCOA]').forEach((el) => el.remove());
|
||||
document.querySelectorAll('[id^=burgCOA]').forEach((el) => el.remove());
|
||||
emblems.selectAll('use').remove();
|
||||
|
||||
// generate new emblems
|
||||
pack.states.forEach(state => {
|
||||
pack.states.forEach((state) => {
|
||||
if (!state.i || state.removed) return;
|
||||
const cultureType = pack.cultures[state.culture].type;
|
||||
state.coa = COA.generate(null, null, null, cultureType);
|
||||
state.coa.shield = COA.getShield(state.culture, null);
|
||||
});
|
||||
|
||||
pack.burgs.forEach(burg => {
|
||||
pack.burgs.forEach((burg) => {
|
||||
if (!burg.i || burg.removed) return;
|
||||
const state = pack.states[burg.state];
|
||||
|
||||
let kinship = state ? .25 : 0;
|
||||
if (burg.capital) kinship += .1;
|
||||
else if (burg.port) kinship -= .1;
|
||||
if (state && burg.culture !== state.culture) kinship -= .25;
|
||||
let kinship = state ? 0.25 : 0;
|
||||
if (burg.capital) kinship += 0.1;
|
||||
else if (burg.port) kinship -= 0.1;
|
||||
if (state && burg.culture !== state.culture) kinship -= 0.25;
|
||||
burg.coa = COA.generate(state ? state.coa : null, kinship, null, burg.type);
|
||||
burg.coa.shield = COA.getShield(burg.culture, state ? burg.state : 0);
|
||||
});
|
||||
|
||||
pack.provinces.forEach(province => {
|
||||
pack.provinces.forEach((province) => {
|
||||
if (!province.i || province.removed) return;
|
||||
const parent = province.burg ? pack.burgs[province.burg] : pack.states[province.state];
|
||||
|
||||
let dominion = false;
|
||||
if (!province.burg) {
|
||||
dominion = P(.2);
|
||||
if (province.formName === "Colony") dominion = P(.95); else
|
||||
if (province.formName === "Island") dominion = P(.6); else
|
||||
if (province.formName === "Islands") dominion = P(.5); else
|
||||
if (province.formName === "Territory") dominion = P(.4); else
|
||||
if (province.formName === "Land") dominion = P(.3);
|
||||
dominion = P(0.2);
|
||||
if (province.formName === 'Colony') dominion = P(0.95);
|
||||
else if (province.formName === 'Island') dominion = P(0.6);
|
||||
else if (province.formName === 'Islands') dominion = P(0.5);
|
||||
else if (province.formName === 'Territory') dominion = P(0.4);
|
||||
else if (province.formName === 'Land') dominion = P(0.3);
|
||||
}
|
||||
|
||||
const nameByBurg = province.burg && province.name.slice(0, 3) === parent.name.slice(0, 3);
|
||||
const kinship = dominion ? 0 : nameByBurg ? .8 : .4;
|
||||
const kinship = dominion ? 0 : nameByBurg ? 0.8 : 0.4;
|
||||
const culture = pack.cells.culture[province.center];
|
||||
const type = BurgsAndStates.getType(province.center, parent.port);
|
||||
province.coa = COA.generate(parent.coa, kinship, dominion, type);
|
||||
province.coa.shield = COA.getShield(culture, province.state);
|
||||
});
|
||||
|
||||
if (layerIsOn("toggleEmblems")) drawEmblems(); // redrawEmblems
|
||||
if (layerIsOn('toggleEmblems')) drawEmblems(); // redrawEmblems
|
||||
}
|
||||
|
||||
function regenerateReligions() {
|
||||
Religions.generate();
|
||||
if (!layerIsOn("toggleReligions")) toggleReligions(); else drawReligions();
|
||||
if (!layerIsOn('toggleReligions')) toggleReligions();
|
||||
else drawReligions();
|
||||
}
|
||||
|
||||
function regenerateCultures() {
|
||||
|
|
@ -362,66 +405,73 @@ function regenerateCultures() {
|
|||
Cultures.expand();
|
||||
BurgsAndStates.updateCultures();
|
||||
Religions.updateCultures();
|
||||
if (!layerIsOn("toggleCultures")) toggleCultures(); else drawCultures();
|
||||
if (!layerIsOn('toggleCultures')) toggleCultures();
|
||||
else drawCultures();
|
||||
refreshAllEditors();
|
||||
}
|
||||
|
||||
function regenerateMilitary() {
|
||||
Military.generate();
|
||||
if (!layerIsOn("toggleMilitary")) toggleMilitary();
|
||||
if (document.getElementById("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click();
|
||||
if (!layerIsOn('toggleMilitary')) toggleMilitary();
|
||||
if (document.getElementById('militaryOverviewRefresh').offsetParent) militaryOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateIce() {
|
||||
if (!layerIsOn("toggleIce")) toggleIce();
|
||||
ice.selectAll("*").remove();
|
||||
if (!layerIsOn('toggleIce')) toggleIce();
|
||||
ice.selectAll('*').remove();
|
||||
drawIce();
|
||||
}
|
||||
|
||||
function regenerateMarkers(event) {
|
||||
if (isCtrlClick(event)) prompt("Please provide markers number multiplier", {default:1, step:.01, min:0, max:100}, v => addNumberOfMarkers(v));
|
||||
else addNumberOfMarkers(gauss(1, .5, .3, 5, 2));
|
||||
if (isCtrlClick(event)) prompt('Please provide markers number multiplier', {default: 1, step: 0.01, min: 0, max: 100}, (v) => addNumberOfMarkers(v));
|
||||
else addNumberOfMarkers(gauss(1, 0.5, 0.3, 5, 2));
|
||||
|
||||
function addNumberOfMarkers(number) {
|
||||
// remove existing markers and assigned notes
|
||||
markers.selectAll("use").each(function() {
|
||||
const index = notes.findIndex(n => n.id === this.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
}).remove();
|
||||
markers
|
||||
.selectAll('use')
|
||||
.each(function () {
|
||||
const index = notes.findIndex((n) => n.id === this.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
})
|
||||
.remove();
|
||||
|
||||
addMarkers(number);
|
||||
if (!layerIsOn("toggleMarkers")) toggleMarkers();
|
||||
if (!layerIsOn('toggleMarkers')) toggleMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
function regenerateZones(event) {
|
||||
if (isCtrlClick(event)) prompt("Please provide zones number multiplier", {default:1, step:.01, min:0, max:100}, v => addNumberOfZones(v));
|
||||
else addNumberOfZones(gauss(1, .5, .6, 5, 2));
|
||||
if (isCtrlClick(event)) prompt('Please provide zones number multiplier', {default: 1, step: 0.01, min: 0, max: 100}, (v) => addNumberOfZones(v));
|
||||
else addNumberOfZones(gauss(1, 0.5, 0.6, 5, 2));
|
||||
|
||||
function addNumberOfZones(number) {
|
||||
zones.selectAll("g").remove(); // remove existing zones
|
||||
zones.selectAll('g').remove(); // remove existing zones
|
||||
addZones(number);
|
||||
if (document.getElementById("zonesEditorRefresh").offsetParent) zonesEditorRefresh.click();
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
if (document.getElementById('zonesEditorRefresh').offsetParent) zonesEditorRefresh.click();
|
||||
if (!layerIsOn('toggleZones')) toggleZones();
|
||||
}
|
||||
}
|
||||
|
||||
function unpressClickToAddButton() {
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
addFeature.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed'));
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
}
|
||||
|
||||
function toggleAddLabel() {
|
||||
const pressed = document.getElementById("addLabel").classList.contains("pressed");
|
||||
if (pressed) {unpressClickToAddButton(); return;}
|
||||
const pressed = document.getElementById('addLabel').classList.contains('pressed');
|
||||
if (pressed) {
|
||||
unpressClickToAddButton();
|
||||
return;
|
||||
}
|
||||
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
addFeature.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed'));
|
||||
addLabel.classList.add('pressed');
|
||||
closeDialogs(".stable");
|
||||
viewbox.style("cursor", "crosshair").on("click", addLabelOnClick);
|
||||
tip("Click on map to place label. Hold Shift to add multiple", true);
|
||||
if (!layerIsOn("toggleLabels")) toggleLabels();
|
||||
closeDialogs('.stable');
|
||||
viewbox.style('cursor', 'crosshair').on('click', addLabelOnClick);
|
||||
tip('Click on map to place label. Hold Shift to add multiple', true);
|
||||
if (!layerIsOn('toggleLabels')) toggleLabels();
|
||||
}
|
||||
|
||||
function addLabelOnClick() {
|
||||
|
|
@ -431,53 +481,71 @@ function addLabelOnClick() {
|
|||
const cell = findCell(point[0], point[1]);
|
||||
const culture = pack.cells.culture[cell];
|
||||
const name = Names.getCulture(culture);
|
||||
const id = getNextId("label");
|
||||
const id = getNextId('label');
|
||||
|
||||
let group = labels.select("#addedLabels");
|
||||
if (!group.size()) group = labels.append("g").attr("id", "addedLabels")
|
||||
.attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a")
|
||||
.attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC")
|
||||
.attr("font-size", 18).attr("data-size", 18).attr("filter", null);
|
||||
let group = labels.select('#addedLabels');
|
||||
if (!group.size())
|
||||
group = labels
|
||||
.append('g')
|
||||
.attr('id', 'addedLabels')
|
||||
.attr('fill', '#3e3e4b')
|
||||
.attr('opacity', 1)
|
||||
.attr('stroke', '#3a3a3a')
|
||||
.attr('stroke-width', 0)
|
||||
.attr('font-family', 'Almendra SC')
|
||||
.attr('data-font', 'Almendra+SC')
|
||||
.attr('font-size', 18)
|
||||
.attr('data-size', 18)
|
||||
.attr('filter', null);
|
||||
|
||||
const example = group.append("text").attr("x", 0).attr("x", 0).text(name);
|
||||
const example = group.append('text').attr('x', 0).attr('x', 0).text(name);
|
||||
const width = example.node().getBBox().width;
|
||||
const x = width / -2; // x offset;
|
||||
example.remove();
|
||||
|
||||
group.classed("hidden", false);
|
||||
group.append("text").attr("id", id)
|
||||
.append("textPath").attr("xlink:href", "#textPath_"+id).attr("startOffset", "50%").attr("font-size", "100%")
|
||||
.append("tspan").attr("x", x).text(name);
|
||||
group.classed('hidden', false);
|
||||
group
|
||||
.append('text')
|
||||
.attr('id', id)
|
||||
.append('textPath')
|
||||
.attr('xlink:href', '#textPath_' + id)
|
||||
.attr('startOffset', '50%')
|
||||
.attr('font-size', '100%')
|
||||
.append('tspan')
|
||||
.attr('x', x)
|
||||
.text(name);
|
||||
|
||||
defs.select("#textPaths")
|
||||
.append("path").attr("id", "textPath_"+id)
|
||||
.attr("d", `M${point[0]-width},${point[1]} h${width*2}`);
|
||||
defs
|
||||
.select('#textPaths')
|
||||
.append('path')
|
||||
.attr('id', 'textPath_' + id)
|
||||
.attr('d', `M${point[0] - width},${point[1]} h${width * 2}`);
|
||||
|
||||
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
||||
}
|
||||
|
||||
function toggleAddBurg() {
|
||||
unpressClickToAddButton();
|
||||
document.getElementById("addBurgTool").classList.add("pressed");
|
||||
document.getElementById('addBurgTool').classList.add('pressed');
|
||||
overviewBurgs();
|
||||
document.getElementById("addNewBurg").click();
|
||||
document.getElementById('addNewBurg').click();
|
||||
}
|
||||
|
||||
function toggleAddRiver() {
|
||||
const pressed = document.getElementById("addRiver").classList.contains("pressed");
|
||||
const pressed = document.getElementById('addRiver').classList.contains('pressed');
|
||||
if (pressed) {
|
||||
unpressClickToAddButton();
|
||||
document.getElementById("addNewRiver").classList.remove("pressed");
|
||||
document.getElementById('addNewRiver').classList.remove('pressed');
|
||||
return;
|
||||
}
|
||||
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
addFeature.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed'));
|
||||
addRiver.classList.add('pressed');
|
||||
document.getElementById("addNewRiver").classList.add("pressed");
|
||||
closeDialogs(".stable");
|
||||
viewbox.style("cursor", "crosshair").on("click", addRiverOnClick);
|
||||
tip("Click on map to place new river or extend an existing one. Hold Shift to place multiple rivers", true, "warn");
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
document.getElementById('addNewRiver').classList.add('pressed');
|
||||
closeDialogs('.stable');
|
||||
viewbox.style('cursor', 'crosshair').on('click', addRiverOnClick);
|
||||
tip('Click on map to place new river or extend an existing one. Hold Shift to place multiple rivers', true, 'warn');
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
}
|
||||
|
||||
function addRiverOnClick() {
|
||||
|
|
@ -487,27 +555,26 @@ function addRiverOnClick() {
|
|||
if (cells.r[i] || cells.h[i] < 20 || cells.b[i]) return;
|
||||
|
||||
const dataRiver = []; // to store river points
|
||||
let river = +getNextId("river").slice(5); // river id
|
||||
let river = +getNextId('river').slice(5); // river id
|
||||
cells.fl[i] = grid.cells.prec[cells.g[i]]; // initial flux
|
||||
|
||||
// height with added t value to make map less depressed
|
||||
const h = Array.from(cells.h)
|
||||
.map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100)
|
||||
.map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000);
|
||||
const h = Rivers.alterHeights();
|
||||
Lakes.prepareLakeData(h);
|
||||
Rivers.resolveDepressions(h);
|
||||
|
||||
while (i) {
|
||||
cells.r[i] = river;
|
||||
const x = cells.p[i][0], y = cells.p[i][1];
|
||||
dataRiver.push({x, y, cell:i});
|
||||
const [x, y] = cells.p[i];
|
||||
dataRiver.push({x, y, cell: i});
|
||||
|
||||
const min = cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell
|
||||
if (h[i] <= h[min]) {tip(`Cell ${i} is depressed, river cannot flow further`, false, "error"); return;}
|
||||
const tx = cells.p[min][0], ty = cells.p[min][1];
|
||||
const min = cells.c[i].sort((a, b) => h[a] - h[b])[0]; // downhill cell
|
||||
if (h[i] <= h[min]) return tip(`Cell ${i} is depressed, river cannot flow further`, false, 'error');
|
||||
|
||||
const [tx, ty] = cells.p[min];
|
||||
|
||||
if (h[min] < 20) {
|
||||
// pour to water body
|
||||
dataRiver.push({x: tx, y: ty, cell:i});
|
||||
dataRiver.push({x: tx, y: ty, cell: i});
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -518,10 +585,10 @@ function addRiverOnClick() {
|
|||
continue;
|
||||
}
|
||||
|
||||
// hadnle case when lowest cell already has a river
|
||||
// handle case when lowest cell already has a river
|
||||
const r = cells.r[min];
|
||||
const riverCells = cells.i.filter(i => cells.r[i] === r);
|
||||
const riverCellsUpper = riverCells.filter(i => h[i] > h[min]);
|
||||
const riverCells = cells.i.filter((i) => cells.r[i] === r);
|
||||
const riverCellsUpper = riverCells.filter((i) => h[i] > h[min]);
|
||||
|
||||
// finish new river if old river is longer
|
||||
if (dataRiver.length <= riverCellsUpper.length) {
|
||||
|
|
@ -532,22 +599,25 @@ function addRiverOnClick() {
|
|||
}
|
||||
|
||||
// extend old river
|
||||
rivers.select("#river"+r).remove();
|
||||
cells.i.filter(i => cells.r[i] === river).forEach(i => cells.r[i] = r);
|
||||
riverCells.forEach(i => cells.r[i] = 0);
|
||||
rivers.select('#river' + r).remove();
|
||||
cells.i.filter((i) => cells.r[i] === river).forEach((i) => (cells.r[i] = r));
|
||||
riverCells.forEach((i) => (cells.r[i] = 0));
|
||||
river = r;
|
||||
cells.fl[min] = cells.fl[i] + grid.cells.prec[cells.g[min]];
|
||||
i = min;
|
||||
}
|
||||
|
||||
const points = Rivers.addMeandering(dataRiver, 1, .5);
|
||||
const widthFactor = rn(.8 + Math.random() * .4, 1); // river width modifier [.8, 1.2]
|
||||
const sourceWidth = .1;
|
||||
const points = Rivers.addMeandering(dataRiver, 1, 0.5);
|
||||
const widthFactor = rn(0.8 + Math.random() * 0.4, 1); // river width modifier [.8, 1.2]
|
||||
const sourceWidth = 0.1;
|
||||
const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth);
|
||||
rivers.append("path").attr("d", path).attr("id", "river"+river);
|
||||
rivers
|
||||
.append('path')
|
||||
.attr('d', path)
|
||||
.attr('id', 'river' + river);
|
||||
|
||||
// add new river to data or change extended river attributes
|
||||
const r = pack.rivers.find(r => r.i === river);
|
||||
const r = pack.rivers.find((r) => r.i === river);
|
||||
const mouth = last(dataRiver).cell;
|
||||
const discharge = cells.fl[mouth]; // in m3/s
|
||||
|
||||
|
|
@ -561,74 +631,98 @@ function addRiverOnClick() {
|
|||
const source = dataRiver[0].cell;
|
||||
const width = rn(offset ** 2, 2); // mounth width in km
|
||||
const name = Rivers.getName(mouth);
|
||||
const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)];
|
||||
const type = length < smallLength ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River";
|
||||
const smallLength = pack.rivers.map((r) => r.length || 0).sort((a, b) => a - b)[Math.ceil(pack.rivers.length * 0.15)];
|
||||
const type = length < smallLength ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : 'River';
|
||||
|
||||
pack.rivers.push({i:river, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, basin, name, type});
|
||||
pack.rivers.push({i: river, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, basin, name, type});
|
||||
}
|
||||
|
||||
if (d3.event.shiftKey === false) {
|
||||
Lakes.cleanupLakeData();
|
||||
unpressClickToAddButton();
|
||||
document.getElementById("addNewRiver").classList.remove("pressed");
|
||||
document.getElementById('addNewRiver').classList.remove('pressed');
|
||||
if (addNewRiver.offsetParent) riversOverviewRefresh.click();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAddRoute() {
|
||||
const pressed = document.getElementById("addRoute").classList.contains("pressed");
|
||||
if (pressed) {unpressClickToAddButton(); return;}
|
||||
const pressed = document.getElementById('addRoute').classList.contains('pressed');
|
||||
if (pressed) {
|
||||
unpressClickToAddButton();
|
||||
return;
|
||||
}
|
||||
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
addFeature.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed'));
|
||||
addRoute.classList.add('pressed');
|
||||
closeDialogs(".stable");
|
||||
viewbox.style("cursor", "crosshair").on("click", addRouteOnClick);
|
||||
tip("Click on map to add a first control point", true);
|
||||
if (!layerIsOn("toggleRoutes")) toggleRoutes();
|
||||
closeDialogs('.stable');
|
||||
viewbox.style('cursor', 'crosshair').on('click', addRouteOnClick);
|
||||
tip('Click on map to add a first control point', true);
|
||||
if (!layerIsOn('toggleRoutes')) toggleRoutes();
|
||||
}
|
||||
|
||||
function addRouteOnClick() {
|
||||
unpressClickToAddButton();
|
||||
const point = d3.mouse(this);
|
||||
const id = getNextId("route");
|
||||
elSelected = routes.select("g").append("path").attr("id", id).attr("data-new", 1).attr("d", `M${point[0]},${point[1]}`);
|
||||
const id = getNextId('route');
|
||||
elSelected = routes.select('g').append('path').attr('id', id).attr('data-new', 1).attr('d', `M${point[0]},${point[1]}`);
|
||||
editRoute(true);
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
const pressed = document.getElementById("addMarker").classList.contains("pressed");
|
||||
if (pressed) {unpressClickToAddButton(); return;}
|
||||
const pressed = document.getElementById('addMarker').classList.contains('pressed');
|
||||
if (pressed) {
|
||||
unpressClickToAddButton();
|
||||
return;
|
||||
}
|
||||
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
addFeature.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed'));
|
||||
addMarker.classList.add('pressed');
|
||||
closeDialogs(".stable");
|
||||
viewbox.style("cursor", "crosshair").on("click", addMarkerOnClick);
|
||||
tip("Click on map to add a marker. Hold Shift to add multiple", true);
|
||||
if (!layerIsOn("toggleMarkers")) toggleMarkers();
|
||||
closeDialogs('.stable');
|
||||
viewbox.style('cursor', 'crosshair').on('click', addMarkerOnClick);
|
||||
tip('Click on map to add a marker. Hold Shift to add multiple', true);
|
||||
if (!layerIsOn('toggleMarkers')) toggleMarkers();
|
||||
}
|
||||
|
||||
function addMarkerOnClick() {
|
||||
const point = d3.mouse(this);
|
||||
const x = rn(point[0], 2), y = rn(point[1], 2);
|
||||
const id = getNextId("markerElement");
|
||||
const x = rn(point[0], 2),
|
||||
y = rn(point[1], 2);
|
||||
const id = getNextId('markerElement');
|
||||
|
||||
const selected = markerSelectGroup.value;
|
||||
const valid = selected && d3.select("#defs-markers").select("#"+selected).size();
|
||||
const symbol = valid ? "#"+selected : "#marker0";
|
||||
const valid =
|
||||
selected &&
|
||||
d3
|
||||
.select('#defs-markers')
|
||||
.select('#' + selected)
|
||||
.size();
|
||||
const symbol = valid ? '#' + selected : '#marker0';
|
||||
const added = markers.select("[data-id='" + symbol + "']").size();
|
||||
let desired = valid && added ? markers.select("[data-id='" + symbol + "']").attr("data-size") : 1;
|
||||
let desired = valid && added ? markers.select("[data-id='" + symbol + "']").attr('data-size') : 1;
|
||||
if (isNaN(desired)) desired = 1;
|
||||
const size = desired * 5 + 25 / scale;
|
||||
|
||||
markers.append("use").attr("id", id).attr("xlink:href", symbol).attr("data-id", symbol)
|
||||
.attr("data-x", x).attr("data-y", y).attr("x", x - size / 2).attr("y", y - size)
|
||||
.attr("data-size", desired).attr("width", size).attr("height", size);
|
||||
markers
|
||||
.append('use')
|
||||
.attr('id', id)
|
||||
.attr('xlink:href', symbol)
|
||||
.attr('data-id', symbol)
|
||||
.attr('data-x', x)
|
||||
.attr('data-y', y)
|
||||
.attr('x', x - size / 2)
|
||||
.attr('y', y - size)
|
||||
.attr('data-size', desired)
|
||||
.attr('width', size)
|
||||
.attr('height', size);
|
||||
|
||||
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
||||
}
|
||||
|
||||
function viewCellDetails() {
|
||||
$("#cellInfo").dialog({
|
||||
resizable: false, width: "22em", title: "Cell Details",
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
$('#cellInfo').dialog({
|
||||
resizable: false,
|
||||
width: '22em',
|
||||
title: 'Cell Details',
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,23 +15,22 @@ function editUnits() {
|
|||
document.getElementById('distanceUnitInput').addEventListener('change', changeDistanceUnit);
|
||||
document.getElementById('distanceScaleOutput').addEventListener('input', changeDistanceScale);
|
||||
document.getElementById('distanceScaleInput').addEventListener('change', changeDistanceScale);
|
||||
document.getElementById('areaUnit').addEventListener('change', () => lock('areaUnit'));
|
||||
document.getElementById('heightUnit').addEventListener('change', changeHeightUnit);
|
||||
document.getElementById('heightExponentInput').addEventListener('input', changeHeightExponent);
|
||||
document.getElementById('heightExponentOutput').addEventListener('input', changeHeightExponent);
|
||||
document.getElementById('temperatureScale').addEventListener('change', changeTemperatureScale);
|
||||
document.getElementById('barSizeOutput').addEventListener('input', changeScaleBarSize);
|
||||
document.getElementById('barSize').addEventListener('input', changeScaleBarSize);
|
||||
document.getElementById('barLabel').addEventListener('input', changeScaleBarLabel);
|
||||
document.getElementById('barPosX').addEventListener('input', changeScaleBarPosition);
|
||||
document.getElementById('barPosY').addEventListener('input', changeScaleBarPosition);
|
||||
document.getElementById('barSizeOutput').addEventListener('input', drawScaleBar);
|
||||
document.getElementById('barSizeInput').addEventListener('input', drawScaleBar);
|
||||
document.getElementById('barLabel').addEventListener('input', drawScaleBar);
|
||||
document.getElementById('barPosX').addEventListener('input', fitScaleBar);
|
||||
document.getElementById('barPosY').addEventListener('input', fitScaleBar);
|
||||
document.getElementById('barBackOpacity').addEventListener('input', changeScaleBarOpacity);
|
||||
document.getElementById('barBackColor').addEventListener('input', changeScaleBarColor);
|
||||
|
||||
document.getElementById('populationRateOutput').addEventListener('input', changePopulationRate);
|
||||
document.getElementById('populationRate').addEventListener('change', changePopulationRate);
|
||||
document.getElementById('populationRateInput').addEventListener('change', changePopulationRate);
|
||||
document.getElementById('urbanizationOutput').addEventListener('input', changeUrbanizationRate);
|
||||
document.getElementById('urbanization').addEventListener('change', changeUrbanizationRate);
|
||||
document.getElementById('urbanizationInput').addEventListener('change', changeUrbanizationRate);
|
||||
|
||||
document.getElementById('addLinearRuler').addEventListener('click', addRuler);
|
||||
document.getElementById('addOpisometer').addEventListener('click', toggleOpisometerMode);
|
||||
|
|
@ -51,114 +50,53 @@ function editUnits() {
|
|||
return;
|
||||
}
|
||||
|
||||
lock('distanceUnit');
|
||||
drawScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
function changeDistanceScale() {
|
||||
const scale = +this.value;
|
||||
if (!scale || isNaN(scale) || scale < 0) {
|
||||
tip('Distance scale should be a positive number', false, 'error');
|
||||
this.value = document.getElementById('distanceScaleInput').dataset.value;
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('distanceScaleOutput').value = scale;
|
||||
document.getElementById('distanceScaleInput').value = scale;
|
||||
document.getElementById('distanceScaleInput').dataset.value = scale;
|
||||
lock('distanceScale');
|
||||
|
||||
drawScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
function changeHeightUnit() {
|
||||
if (this.value === 'custom_name') {
|
||||
prompt('Provide a custom name for a height unit', {default: ''}, (custom) => {
|
||||
this.options.add(new Option(custom, custom, false, true));
|
||||
lock('heightUnit');
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.value !== 'custom_name') return;
|
||||
|
||||
lock('heightUnit');
|
||||
prompt('Provide a custom name for a height unit', {default: ''}, (custom) => {
|
||||
this.options.add(new Option(custom, custom, false, true));
|
||||
lock('heightUnit');
|
||||
});
|
||||
}
|
||||
|
||||
function changeHeightExponent() {
|
||||
document.getElementById('heightExponentInput').value = this.value;
|
||||
document.getElementById('heightExponentOutput').value = this.value;
|
||||
calculateTemperatures();
|
||||
if (layerIsOn('toggleTemp')) drawTemp();
|
||||
lock('heightExponent');
|
||||
}
|
||||
|
||||
function changeTemperatureScale() {
|
||||
lock('temperatureScale');
|
||||
if (layerIsOn('toggleTemp')) drawTemp();
|
||||
}
|
||||
|
||||
function changeScaleBarSize() {
|
||||
document.getElementById('barSize').value = this.value;
|
||||
document.getElementById('barSizeOutput').value = this.value;
|
||||
drawScaleBar();
|
||||
lock('barSize');
|
||||
}
|
||||
|
||||
function changeScaleBarPosition() {
|
||||
lock('barPosX');
|
||||
lock('barPosY');
|
||||
fitScaleBar();
|
||||
}
|
||||
|
||||
function changeScaleBarLabel() {
|
||||
lock('barLabel');
|
||||
drawScaleBar();
|
||||
}
|
||||
|
||||
function changeScaleBarOpacity() {
|
||||
scaleBar.select('rect').attr('opacity', this.value);
|
||||
lock('barBackOpacity');
|
||||
}
|
||||
|
||||
function changeScaleBarColor() {
|
||||
scaleBar.select('rect').attr('fill', this.value);
|
||||
lock('barBackColor');
|
||||
}
|
||||
|
||||
function changePopulationRate() {
|
||||
const rate = +this.value;
|
||||
if (!rate || isNaN(rate) || rate <= 0) {
|
||||
tip('Population rate should be a positive number', false, 'error');
|
||||
this.value = document.getElementById('populationRate').dataset.value;
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('populationRateOutput').value = rate;
|
||||
document.getElementById('populationRate').value = rate;
|
||||
document.getElementById('populationRate').dataset.value = rate;
|
||||
lock('populationRate');
|
||||
populationRate = +this.value;
|
||||
}
|
||||
|
||||
function changeUrbanizationRate() {
|
||||
const rate = +this.value;
|
||||
if (!rate || isNaN(rate) || rate < 0) {
|
||||
tip('Urbanization rate should be a number', false, 'error');
|
||||
this.value = document.getElementById('urbanization').dataset.value;
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('urbanizationOutput').value = rate;
|
||||
document.getElementById('urbanization').value = rate;
|
||||
document.getElementById('urbanization').dataset.value = rate;
|
||||
lock('urbanization');
|
||||
urbanization = +this.value;
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
// distanceScale
|
||||
document.getElementById('distanceScaleOutput').value = 3;
|
||||
document.getElementById('distanceScaleInput').value = 3;
|
||||
document.getElementById('distanceScaleInput').dataset.value = 3;
|
||||
unlock('distanceScale');
|
||||
|
||||
// units
|
||||
|
|
@ -180,7 +118,7 @@ function editUnits() {
|
|||
calculateTemperatures();
|
||||
|
||||
// scale bar
|
||||
barSizeOutput.value = barSize.value = 2;
|
||||
barSizeOutput.value = barSizeInput.value = 2;
|
||||
barLabel.value = '';
|
||||
barBackOpacity.value = 0.2;
|
||||
barBackColor.value = '#ffffff';
|
||||
|
|
@ -195,8 +133,8 @@ function editUnits() {
|
|||
drawScaleBar();
|
||||
|
||||
// population
|
||||
populationRateOutput.value = populationRate.value = 1000;
|
||||
urbanizationOutput.value = urbanization.value = 1;
|
||||
populationRate = populationRateOutput.value = populationRateInput.value = 1000;
|
||||
urbanization = urbanizationOutput.value = urbanizationInput.value = 1;
|
||||
localStorage.removeItem('populationRate');
|
||||
localStorage.removeItem('urbanization');
|
||||
}
|
||||
|
|
@ -328,12 +266,22 @@ function editUnits() {
|
|||
|
||||
function removeAllRulers() {
|
||||
if (!rulers.data.length) return;
|
||||
|
||||
const message = 'Are you sure you want to remove all placed rulers?<br>If you just want to hide rulers, toggle the Rulers layer off in Menu';
|
||||
const onConfirm = () => {
|
||||
rulers.undraw();
|
||||
rulers = new Rulers();
|
||||
};
|
||||
confirmationDialog({title: 'Remove all rulers', message, confirm: 'Remove', onConfirm});
|
||||
alertMessage.innerHTML = `
|
||||
Are you sure you want to remove all placed rulers?
|
||||
<br>If you just want to hide rulers, toggle the Rulers layer off in Menu`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove all rulers',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
rulers.undraw();
|
||||
rulers = new Rulers();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ function editZones() {
|
|||
modules.editZones = true;
|
||||
|
||||
$("#zonesEditor").dialog({
|
||||
title: "Zones Editor", resizable: false, width: fitContent(), close: () => exitZonesManualAssignment("close"),
|
||||
title: "Zones Editor",
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: () => exitZonesManualAssignment("close"),
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
|
|
@ -25,19 +28,37 @@ function editZones() {
|
|||
document.getElementById("zonesExport").addEventListener("click", downloadZonesData);
|
||||
document.getElementById("zonesRemove").addEventListener("click", toggleEraseMode);
|
||||
|
||||
body.addEventListener("click", function(ev) {
|
||||
const el = ev.target, cl = el.classList, zone = el.parentNode.dataset.id;
|
||||
if (cl.contains("culturePopulation")) {changePopulation(zone); return;}
|
||||
if (cl.contains("icon-trash-empty")) {zoneRemove(zone); return;}
|
||||
if (cl.contains("icon-eye")) {toggleVisibility(el); return;}
|
||||
if (cl.contains("icon-pin")) {toggleFog(zone, cl); return;}
|
||||
if (cl.contains("fillRect")) {changeFill(el); return;}
|
||||
body.addEventListener("click", function (ev) {
|
||||
const el = ev.target,
|
||||
cl = el.classList,
|
||||
zone = el.parentNode.dataset.id;
|
||||
if (cl.contains("culturePopulation")) {
|
||||
changePopulation(zone);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("icon-trash-empty")) {
|
||||
zoneRemove(zone);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("icon-eye")) {
|
||||
toggleVisibility(el);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("icon-pin")) {
|
||||
toggleFog(zone, cl);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("fillRect")) {
|
||||
changeFill(el);
|
||||
return;
|
||||
}
|
||||
if (customization) selectZone(el);
|
||||
});
|
||||
|
||||
body.addEventListener("input", function(ev) {
|
||||
const el = ev.target, zone = el.parentNode.dataset.id;
|
||||
if (el.classList.contains("religionName")) zones.select("#"+zone).attr("data-description", el.value);
|
||||
body.addEventListener("input", function (ev) {
|
||||
const el = ev.target,
|
||||
zone = el.parentNode.dataset.id;
|
||||
if (el.classList.contains("religionName")) zones.select("#" + zone).attr("data-description", el.value);
|
||||
});
|
||||
|
||||
// add line for each zone
|
||||
|
|
@ -45,17 +66,17 @@ function editZones() {
|
|||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
let lines = "";
|
||||
|
||||
zones.selectAll("g").each(function() {
|
||||
zones.selectAll("g").each(function () {
|
||||
const c = this.dataset.cells ? this.dataset.cells.split(",").map(c => +c) : [];
|
||||
const description = this.dataset.description;
|
||||
const fill = this.getAttribute("fill");
|
||||
const area = d3.sum(c.map(i => pack.cells.area[i])) * (distanceScaleInput.value ** 2);
|
||||
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate.value;
|
||||
const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate.value * urbanization.value;
|
||||
const area = d3.sum(c.map(i => pack.cells.area[i])) * distanceScaleInput.value ** 2;
|
||||
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate;
|
||||
const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization;
|
||||
const population = rural + urban;
|
||||
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`;
|
||||
const inactive = this.style.display === "none";
|
||||
const focused = defs.select("#fog #focus"+this.id).size();
|
||||
const focused = defs.select("#fog #focus" + this.id).size();
|
||||
|
||||
lines += `<div class="states" data-id="${this.id}" data-fill="${fill}" data-description="${description}" data-cells=${c.length} data-area=${area} data-population=${population}>
|
||||
<svg data-tip="Zone fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${fill}" class="fillRect pointer"></svg>
|
||||
|
|
@ -67,8 +88,8 @@ function editZones() {
|
|||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Drag to raise or lower the zone" class="icon-resize-vertical hide"></span>
|
||||
<span data-tip="Toggle zone focus" class="icon-pin ${focused?'':' inactive'} hide ${c.length?'':' placeholder'}"></span>
|
||||
<span data-tip="Toggle zone visibility" class="icon-eye ${inactive?' inactive':''} hide ${c.length?'':' placeholder'}"></span>
|
||||
<span data-tip="Toggle zone focus" class="icon-pin ${focused ? "" : " inactive"} hide ${c.length ? "" : " placeholder"}"></span>
|
||||
<span data-tip="Toggle zone visibility" class="icon-eye ${inactive ? " inactive" : ""} hide ${c.length ? "" : " placeholder"}"></span>
|
||||
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
});
|
||||
|
|
@ -76,8 +97,8 @@ function editZones() {
|
|||
body.innerHTML = lines;
|
||||
|
||||
// update footer
|
||||
const totalArea = zonesFooterArea.dataset.area = graphWidth * graphHeight * (distanceScaleInput.value ** 2);
|
||||
const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization.value) * populationRate.value;
|
||||
const totalArea = (zonesFooterArea.dataset.area = graphWidth * graphHeight * distanceScaleInput.value ** 2);
|
||||
const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization) * populationRate;
|
||||
zonesFooterPopulation.dataset.population = totalPop;
|
||||
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
|
||||
zonesFooterCells.innerHTML = pack.cells.i.length;
|
||||
|
|
@ -88,48 +109,53 @@ function editZones() {
|
|||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => zoneHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => zoneHighlightOff(ev)));
|
||||
|
||||
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
|
||||
if (body.dataset.type === "percentage") {
|
||||
body.dataset.type = "absolute";
|
||||
togglePercentageMode();
|
||||
}
|
||||
$("#zonesEditor").dialog({width: fitContent()});
|
||||
}
|
||||
|
||||
function zoneHighlightOn(event) {
|
||||
const zone = event.target.dataset.id;
|
||||
zones.select("#"+zone).style("outline", "1px solid red");
|
||||
zones.select("#" + zone).style("outline", "1px solid red");
|
||||
}
|
||||
|
||||
function zoneHighlightOff(event) {
|
||||
const zone = event.target.dataset.id;
|
||||
zones.select("#"+zone).style("outline", null);
|
||||
zones.select("#" + zone).style("outline", null);
|
||||
}
|
||||
|
||||
$(body).sortable({items: "div.states", handle: ".icon-resize-vertical", containment: "parent", axis: "y", update: movezone});
|
||||
function movezone(ev, ui) {
|
||||
const zone = $("#"+ui.item.attr("data-id"));
|
||||
const prev = $("#"+ui.item.prev().attr("data-id"));
|
||||
if (prev) {zone.insertAfter(prev); return;}
|
||||
const next = $("#"+ui.item.next().attr("data-id"));
|
||||
const zone = $("#" + ui.item.attr("data-id"));
|
||||
const prev = $("#" + ui.item.prev().attr("data-id"));
|
||||
if (prev) {
|
||||
zone.insertAfter(prev);
|
||||
return;
|
||||
}
|
||||
const next = $("#" + ui.item.next().attr("data-id"));
|
||||
if (next) zone.insertBefore(next);
|
||||
}
|
||||
|
||||
function enterZonesManualAssignent() {
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
customization = 10;
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => el.style.display = "none");
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("zonesManuallyButtons").style.display = "inline-block";
|
||||
|
||||
zonesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
|
||||
zonesFooter.style.display = "none";
|
||||
body.querySelectorAll("div > input, select, svg").forEach(e => e.style.pointerEvents = "none");
|
||||
body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "none"));
|
||||
$("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
|
||||
|
||||
tip("Click to select a zone, drag to paint a zone", true);
|
||||
viewbox.style("cursor", "crosshair")
|
||||
.on("click", selectZoneOnMapClick)
|
||||
.call(d3.drag().on("start", dragZoneBrush))
|
||||
.on("touchmove mousemove", moveZoneBrush);
|
||||
viewbox.style("cursor", "crosshair").on("click", selectZoneOnMapClick).call(d3.drag().on("start", dragZoneBrush)).on("touchmove mousemove", moveZoneBrush);
|
||||
|
||||
body.querySelector("div").classList.add("selected");
|
||||
zones.selectAll("g").each(function() {this.setAttribute("data-init", this.getAttribute("data-cells"));});
|
||||
zones.selectAll("g").each(function () {
|
||||
this.setAttribute("data-init", this.getAttribute("data-cells"));
|
||||
});
|
||||
}
|
||||
|
||||
function selectZone(el) {
|
||||
|
|
@ -154,9 +180,9 @@ function editZones() {
|
|||
|
||||
const selection = r > 5 ? findAll(p[0], p[1], r) : [findCell(p[0], p[1], r)];
|
||||
if (!selection) return;
|
||||
|
||||
|
||||
const selected = body.querySelector("div.selected");
|
||||
const zone = zones.select("#"+selected.dataset.id);
|
||||
const zone = zones.select("#" + selected.dataset.id);
|
||||
const base = zone.attr("id") + "_"; // id generic part
|
||||
const dataCells = zone.attr("data-cells");
|
||||
let cells = dataCells ? dataCells.split(",").map(i => +i) : [];
|
||||
|
|
@ -175,7 +201,10 @@ function editZones() {
|
|||
selection.forEach(i => {
|
||||
if (cells.includes(i)) return;
|
||||
cells.push(i);
|
||||
zone.append("polygon").attr("points", getPackPolygon(i)).attr("id", base + i);
|
||||
zone
|
||||
.append("polygon")
|
||||
.attr("points", getPackPolygon(i))
|
||||
.attr("id", base + i);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -191,10 +220,10 @@ function editZones() {
|
|||
}
|
||||
|
||||
function applyZonesManualAssignent() {
|
||||
zones.selectAll("g").each(function() {
|
||||
zones.selectAll("g").each(function () {
|
||||
if (this.dataset.cells) return;
|
||||
// all zone cells are removed
|
||||
unfog("focusZone"+this.id);
|
||||
unfog("focusZone" + this.id);
|
||||
this.style.display = "block";
|
||||
});
|
||||
|
||||
|
|
@ -204,15 +233,20 @@ function editZones() {
|
|||
|
||||
// restore initial zone cells
|
||||
function cancelZonesManualAssignent() {
|
||||
zones.selectAll("g").each(function() {
|
||||
zones.selectAll("g").each(function () {
|
||||
const zone = d3.select(this);
|
||||
const dataCells = zone.attr("data-init");
|
||||
const cells = dataCells ? dataCells.split(",").map(i => +i) : [];
|
||||
zone.attr("data-cells", cells);
|
||||
zone.selectAll("*").remove();
|
||||
const base = zone.attr("id") + "_"; // id generic part
|
||||
zone.selectAll("*").data(cells).enter().append("polygon")
|
||||
.attr("points", d => getPackPolygon(d)).attr("id", d => base + d);
|
||||
zone
|
||||
.selectAll("*")
|
||||
.data(cells)
|
||||
.enter()
|
||||
.append("polygon")
|
||||
.attr("points", d => getPackPolygon(d))
|
||||
.attr("id", d => base + d);
|
||||
});
|
||||
|
||||
exitZonesManualAssignment();
|
||||
|
|
@ -221,56 +255,68 @@ function editZones() {
|
|||
function exitZonesManualAssignment(close) {
|
||||
customization = 0;
|
||||
removeCircle();
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => el.style.display = "inline-block");
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("zonesManuallyButtons").style.display = "none";
|
||||
|
||||
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
|
||||
zonesFooter.style.display = "block";
|
||||
body.querySelectorAll("div > input, select, svg").forEach(e => e.style.pointerEvents = "all");
|
||||
if(!close) $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
|
||||
body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "all"));
|
||||
if (!close) $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
|
||||
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
zones.selectAll("g").each(function() {this.removeAttribute("data-init");});
|
||||
zones.selectAll("g").each(function () {
|
||||
this.removeAttribute("data-init");
|
||||
});
|
||||
const selected = body.querySelector("div.selected");
|
||||
if (selected) selected.classList.remove("selected");
|
||||
}
|
||||
|
||||
function changeFill(el) {
|
||||
const fill = el.getAttribute("fill");
|
||||
const callback = function(fill) {
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute("fill", fill);
|
||||
}
|
||||
};
|
||||
|
||||
openPicker(fill, callback);
|
||||
}
|
||||
|
||||
function toggleVisibility(el) {
|
||||
const zone = zones.select("#"+el.parentNode.dataset.id);
|
||||
const zone = zones.select("#" + el.parentNode.dataset.id);
|
||||
const inactive = zone.style("display") === "none";
|
||||
inactive ? zone.style("display", "block") : zone.style("display", "none");
|
||||
el.classList.toggle("inactive");
|
||||
}
|
||||
|
||||
function toggleFog(z, cl) {
|
||||
const dataCells = zones.select("#"+z).attr("data-cells");
|
||||
const dataCells = zones.select("#" + z).attr("data-cells");
|
||||
if (!dataCells) return;
|
||||
|
||||
const path = "M" + dataCells.split(",").map(c => getPackPolygon(+c)).join("M") + "Z", id = "focusZone"+z;
|
||||
const path =
|
||||
"M" +
|
||||
dataCells
|
||||
.split(",")
|
||||
.map(c => getPackPolygon(+c))
|
||||
.join("M") +
|
||||
"Z",
|
||||
id = "focusZone" + z;
|
||||
cl.contains("inactive") ? fog(id, path) : unfog(id);
|
||||
cl.toggle("inactive");
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
if (legend.selectAll("*").size()) {clearLegend(); return;}; // hide legend
|
||||
if (legend.selectAll("*").size()) {
|
||||
clearLegend();
|
||||
return;
|
||||
} // hide legend
|
||||
const data = [];
|
||||
|
||||
zones.selectAll("g").each(function() {
|
||||
zones.selectAll("g").each(function () {
|
||||
const id = this.dataset.id;
|
||||
const description = this.dataset.description;
|
||||
const fill = this.getAttribute("fill");
|
||||
data.push([id, fill, description])
|
||||
data.push([id, fill, description]);
|
||||
});
|
||||
|
||||
drawLegend("Zones", data);
|
||||
|
|
@ -283,12 +329,11 @@ function editZones() {
|
|||
const totalArea = +zonesFooterArea.dataset.area;
|
||||
const totalPopulation = +zonesFooterPopulation.dataset.population;
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function(el) {
|
||||
el.querySelector(".stateCells").innerHTML = rn(+el.dataset.cells / totalCells * 100, 2) + "%";
|
||||
el.querySelector(".biomeArea").innerHTML = rn(+el.dataset.area / totalArea * 100, 2) + "%";
|
||||
el.querySelector(".culturePopulation").innerHTML = rn(+el.dataset.population / totalPopulation * 100, 2) + "%";
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
el.querySelector(".stateCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100, 2) + "%";
|
||||
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100, 2) + "%";
|
||||
el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100, 2) + "%";
|
||||
});
|
||||
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
zonesEditorAddLines();
|
||||
|
|
@ -298,7 +343,7 @@ function editZones() {
|
|||
function addZonesLayer() {
|
||||
const id = getNextId("zone");
|
||||
const description = "Unknown zone";
|
||||
const fill = "url(#hatch" + id.slice(4)%14 + ")";
|
||||
const fill = "url(#hatch" + (id.slice(4) % 14) + ")";
|
||||
zones.append("g").attr("id", id).attr("data-description", description).attr("data-cells", "").attr("fill", fill);
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
|
||||
|
|
@ -323,9 +368,9 @@ function editZones() {
|
|||
|
||||
function downloadZonesData() {
|
||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||
let data = "Id,Fill,Description,Cells,Area "+unit+",Population\n"; // headers
|
||||
let data = "Id,Fill,Description,Cells,Area " + unit + ",Population\n"; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function(el) {
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.fill + ",";
|
||||
data += el.dataset.description + ",";
|
||||
|
|
@ -343,68 +388,83 @@ function editZones() {
|
|||
}
|
||||
|
||||
function changePopulation(zone) {
|
||||
const dataCells = zones.select("#"+zone).attr("data-cells");
|
||||
const cells = dataCells ? dataCells.split(",").map(i => +i).filter(i => pack.cells.h[i] >= 20) : [];
|
||||
if (!cells.length) {tip("Zone does not have any land cells, cannot change population", false, "error"); return;}
|
||||
const dataCells = zones.select("#" + zone).attr("data-cells");
|
||||
const cells = dataCells
|
||||
? dataCells
|
||||
.split(",")
|
||||
.map(i => +i)
|
||||
.filter(i => pack.cells.h[i] >= 20)
|
||||
: [];
|
||||
if (!cells.length) {
|
||||
tip("Zone does not have any land cells, cannot change population", false, "error");
|
||||
return;
|
||||
}
|
||||
const burgs = pack.burgs.filter(b => !b.removed && cells.includes(b.cell));
|
||||
|
||||
const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate.value);
|
||||
const urban = rn(d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate.value * urbanization.value);
|
||||
const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate);
|
||||
const urban = rn(d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization);
|
||||
const total = rural + urban;
|
||||
const l = n => Number(n).toLocaleString();
|
||||
|
||||
alertMessage.innerHTML = `
|
||||
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
|
||||
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length?'':"disabled"}>
|
||||
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"}>
|
||||
<p>Total population: ${l(total)} ⇒ <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
|
||||
|
||||
const update = function() {
|
||||
const update = function () {
|
||||
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
|
||||
if (isNaN(totalNew)) return;
|
||||
totalPop.innerHTML = l(totalNew);
|
||||
totalPopPerc.innerHTML = rn(totalNew / total * 100);
|
||||
}
|
||||
totalPopPerc.innerHTML = rn((totalNew / total) * 100);
|
||||
};
|
||||
|
||||
ruralPop.oninput = () => update();
|
||||
urbanPop.oninput = () => update();
|
||||
|
||||
$("#alert").dialog({
|
||||
resizable: false, title: "Change zone population", width: "24em", buttons: {
|
||||
Apply: function() {applyPopulationChange(); $(this).dialog("close");},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}, position: {my: "center", at: "center", of: "svg"}
|
||||
resizable: false,
|
||||
title: "Change zone population",
|
||||
width: "24em",
|
||||
buttons: {
|
||||
Apply: function () {
|
||||
applyPopulationChange();
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
},
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
});
|
||||
|
||||
function applyPopulationChange() {
|
||||
const ruralChange = ruralPop.value / rural;
|
||||
if (isFinite(ruralChange) && ruralChange !== 1) {
|
||||
cells.forEach(i => pack.cells.pop[i] *= ruralChange);
|
||||
cells.forEach(i => (pack.cells.pop[i] *= ruralChange));
|
||||
}
|
||||
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
|
||||
const points = ruralPop.value / populationRate.value;
|
||||
const points = ruralPop.value / populationRate;
|
||||
const pop = rn(points / cells.length);
|
||||
cells.forEach(i => pack.cells.pop[i] = pop);
|
||||
cells.forEach(i => (pack.cells.pop[i] = pop));
|
||||
}
|
||||
|
||||
const urbanChange = urbanPop.value / urban;
|
||||
if (isFinite(urbanChange) && urbanChange !== 1) {
|
||||
burgs.forEach(b => b.population = rn(b.population * urbanChange, 4));
|
||||
burgs.forEach(b => (b.population = rn(b.population * urbanChange, 4)));
|
||||
}
|
||||
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
|
||||
const points = urbanPop.value / populationRate.value / urbanization.value;
|
||||
const points = urbanPop.value / populationRate / urbanization;
|
||||
const population = rn(points / burgs.length, 4);
|
||||
burgs.forEach(b => b.population = population);
|
||||
burgs.forEach(b => (b.population = population));
|
||||
}
|
||||
|
||||
zonesEditorAddLines();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function zoneRemove(zone) {
|
||||
zones.select("#"+zone).remove();
|
||||
unfog("focusZone"+zone);
|
||||
zones.select("#" + zone).remove();
|
||||
unfog("focusZone" + zone);
|
||||
zonesEditorAddLines();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue