mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-18 10:01:23 +01:00
refactor: dynamically load modules
This commit is contained in:
parent
347083291f
commit
0c6eadaed3
21 changed files with 124 additions and 105 deletions
|
|
@ -6,7 +6,7 @@ import {findCell} from "utils/graphUtils";
|
|||
import {getSegmentId} from "utils/lineUtils";
|
||||
import {rn} from "utils/numberUtils";
|
||||
import {parseTransform, round} from "utils/stringUtils";
|
||||
import {si} from "utils/unitUtils";
|
||||
import {getArea, getAreaUnit, si} from "utils/unitUtils";
|
||||
|
||||
export class Rulers {
|
||||
constructor() {
|
||||
|
|
|
|||
|
|
@ -1,631 +0,0 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {restoreDefaultEvents} from "scripts/events";
|
||||
import {findCell} from "utils/graphUtils";
|
||||
import {tip, clearMainTip} from "scripts/tooltips";
|
||||
import {getCoordinates} from "utils/coordinateUtils";
|
||||
import {rn} from "utils/numberUtils";
|
||||
import {si, siToInteger} from "utils/unitUtils";
|
||||
import {getHeight} from "utils/unitUtils";
|
||||
import {closeDialogs} from "dialogs/utils";
|
||||
import {openDialog} from "dialogs";
|
||||
|
||||
export function overviewBurgs() {
|
||||
if (customization) return;
|
||||
closeDialogs("#burgsOverview, .stable");
|
||||
if (!layerIsOn("toggleIcons")) toggleIcons();
|
||||
if (!layerIsOn("toggleLabels")) toggleLabels();
|
||||
|
||||
const body = document.getElementById("burgsBody");
|
||||
updateFilter();
|
||||
updateLockAllIcon();
|
||||
burgsOverviewAddLines();
|
||||
$("#burgsOverview").dialog();
|
||||
|
||||
if (fmg.modules.overviewBurgs) return;
|
||||
fmg.modules.overviewBurgs = true;
|
||||
|
||||
$("#burgsOverview").dialog({
|
||||
title: "Burgs Overview",
|
||||
resizable: false,
|
||||
width: "fit-content",
|
||||
close: exitAddBurgMode,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("burgsOverviewRefresh").addEventListener("click", refreshBurgsEditor);
|
||||
document.getElementById("burgsChart").addEventListener("click", showBurgsChart);
|
||||
document.getElementById("burgsFilterState").addEventListener("change", burgsOverviewAddLines);
|
||||
document.getElementById("burgsFilterCulture").addEventListener("change", burgsOverviewAddLines);
|
||||
document.getElementById("regenerateBurgNames").addEventListener("click", regenerateNames);
|
||||
document.getElementById("addNewBurg").addEventListener("click", enterAddBurgMode);
|
||||
document.getElementById("burgsExport").addEventListener("click", downloadBurgsData);
|
||||
document.getElementById("burgNamesImport").addEventListener("click", renameBurgsInBulk);
|
||||
document.getElementById("burgsListToLoad").addEventListener("change", function () {
|
||||
uploadFile(this, importBurgNames);
|
||||
});
|
||||
document.getElementById("burgsLockAll").addEventListener("click", toggleLockAll);
|
||||
document.getElementById("burgsRemoveAll").addEventListener("click", triggerAllBurgsRemove);
|
||||
document.getElementById("burgsInvertLock").addEventListener("click", invertLock);
|
||||
|
||||
function refreshBurgsEditor() {
|
||||
updateFilter();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function updateFilter() {
|
||||
const stateFilter = document.getElementById("burgsFilterState");
|
||||
const selectedState = stateFilter.value || 1;
|
||||
stateFilter.options.length = 0; // remove all options
|
||||
stateFilter.options.add(new Option(`all`, -1, false, selectedState == -1));
|
||||
stateFilter.options.add(new Option(pack.states[0].name, 0, false, !selectedState));
|
||||
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState)));
|
||||
|
||||
const cultureFilter = document.getElementById("burgsFilterCulture");
|
||||
const selectedCulture = cultureFilter.value || -1;
|
||||
cultureFilter.options.length = 0; // remove all options
|
||||
cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture == -1));
|
||||
cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, !selectedCulture));
|
||||
const culturesSorted = pack.cultures.filter(c => c.i && !c.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
culturesSorted.forEach(c => cultureFilter.options.add(new Option(c.name, c.i, false, c.i == selectedCulture)));
|
||||
}
|
||||
|
||||
// add line for each burg
|
||||
function burgsOverviewAddLines() {
|
||||
const selectedState = +document.getElementById("burgsFilterState").value;
|
||||
const selectedCulture = +document.getElementById("burgsFilterCulture").value;
|
||||
let filtered = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||
if (selectedState != -1) filtered = filtered.filter(b => b.state === selectedState); // filtered by state
|
||||
if (selectedCulture != -1) filtered = filtered.filter(b => b.culture === selectedCulture); // filtered by culture
|
||||
|
||||
body.innerHTML = "";
|
||||
let lines = "",
|
||||
totalPopulation = 0;
|
||||
|
||||
for (const b of filtered) {
|
||||
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;
|
||||
const prov = pack.cells.province[b.cell];
|
||||
const province = prov ? pack.provinces[prov].name : "";
|
||||
const culture = pack.cultures[b.culture].name;
|
||||
|
||||
lines += /* html */ `<div
|
||||
class="states"
|
||||
data-id=${b.i}
|
||||
data-name="${b.name}"
|
||||
data-state="${state}"
|
||||
data-province="${province}"
|
||||
data-culture="${culture}"
|
||||
data-population=${population}
|
||||
data-type="${type}"
|
||||
>
|
||||
<span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span>
|
||||
<input data-tip="Burg name. Click and type to change" class="burgName" value="${
|
||||
b.name
|
||||
}" autocorrect="off" spellcheck="false" />
|
||||
<input data-tip="Burg province" class="burgState" value="${province}" disabled />
|
||||
<input data-tip="Burg state" class="burgState" value="${state}" disabled />
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell culture use Cultures Editor)" class="stateCulture">
|
||||
${getCultureOptions(b.culture)}
|
||||
</select>
|
||||
<span data-tip="Burg population" class="icon-male"></span>
|
||||
<input data-tip="Burg population. Type to change" class="burgPopulation" value=${si(population)} />
|
||||
<div class="burgType">
|
||||
<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 data-tip="Toggle element lock. Lock will prevent it from regeneration"
|
||||
class="locks pointer ${b.lock ? "icon-lock" : "icon-lock-open inactive"}"></span>
|
||||
<span data-tip="Remove burg" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.insertAdjacentHTML("beforeend", lines);
|
||||
|
||||
// update footer
|
||||
burgsFooterBurgs.innerHTML = filtered.length;
|
||||
burgsFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0;
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => burgHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => burgHighlightOff(ev)));
|
||||
body.querySelectorAll("div > input.burgName").forEach(el => el.addEventListener("input", changeBurgName));
|
||||
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomIntoBurg));
|
||||
body.querySelectorAll("div > select.stateCulture").forEach(el => el.addEventListener("change", changeBurgCulture));
|
||||
body
|
||||
.querySelectorAll("div > input.burgPopulation")
|
||||
.forEach(el => el.addEventListener("change", changeBurgPopulation));
|
||||
body
|
||||
.querySelectorAll("div > span.icon-star-empty")
|
||||
.forEach(el => el.addEventListener("click", toggleCapitalStatus));
|
||||
body.querySelectorAll("div > span.icon-anchor").forEach(el => el.addEventListener("click", togglePortStatus));
|
||||
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleBurgLockStatus));
|
||||
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openBurgEditor));
|
||||
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerBurgRemove));
|
||||
|
||||
applySorting(burgsHeader);
|
||||
}
|
||||
|
||||
function getCultureOptions(culture) {
|
||||
let options = "";
|
||||
pack.cultures
|
||||
.filter(c => !c.removed)
|
||||
.forEach(c => (options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`));
|
||||
return options;
|
||||
}
|
||||
|
||||
function burgHighlightOn(event) {
|
||||
if (!layerIsOn("toggleLabels")) toggleLabels();
|
||||
const burg = +event.target.dataset.id;
|
||||
burgLabels.select("[data-id='" + burg + "']").classed("drag", true);
|
||||
}
|
||||
|
||||
function burgHighlightOff() {
|
||||
burgLabels.selectAll("text.drag").classed("drag", false);
|
||||
}
|
||||
|
||||
function changeBurgName() {
|
||||
if (this.value == "") tip("Please provide a name", false, "error");
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
pack.burgs[burg].name = this.value;
|
||||
this.parentNode.dataset.name = this.value;
|
||||
const label = document.querySelector("#burgLabels [data-id='" + burg + "']");
|
||||
if (label) label.innerHTML = this.value;
|
||||
}
|
||||
|
||||
function zoomIntoBurg() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
const label = document.querySelector("#burgLabels [data-id='" + burg + "']");
|
||||
const x = +label.getAttribute("x");
|
||||
const y = +label.getAttribute("y");
|
||||
zoomTo(x, y, 8, 2000);
|
||||
}
|
||||
|
||||
function changeBurgCulture() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
const v = +this.value;
|
||||
pack.burgs[burg].culture = v;
|
||||
this.parentNode.dataset.culture = pack.cultures[v].name;
|
||||
}
|
||||
|
||||
function changeBurgPopulation() {
|
||||
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 * urbanization);
|
||||
return;
|
||||
}
|
||||
pack.burgs[burg].population = this.value / populationRate / urbanization;
|
||||
this.parentNode.dataset.population = this.value;
|
||||
this.value = si(this.value);
|
||||
|
||||
const population = [];
|
||||
body.querySelectorAll(":scope > div").forEach(el => population.push(siToInteger(el.dataset.population)));
|
||||
burgsFooterPopulation.innerHTML = si(d3.mean(population));
|
||||
}
|
||||
|
||||
function toggleCapitalStatus() {
|
||||
const burg = +this.parentNode.parentNode.dataset.id;
|
||||
toggleCapital(burg);
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function togglePortStatus() {
|
||||
const burg = +this.parentNode.parentNode.dataset.id;
|
||||
togglePort(burg);
|
||||
if (this.classList.contains("inactive")) this.classList.remove("inactive");
|
||||
else this.classList.add("inactive");
|
||||
}
|
||||
|
||||
function toggleBurgLockStatus() {
|
||||
const burgId = +this.parentNode.dataset.id;
|
||||
|
||||
const burg = pack.burgs[burgId];
|
||||
burg.lock = !burg.lock;
|
||||
|
||||
if (this.classList.contains("icon-lock")) {
|
||||
this.classList.remove("icon-lock");
|
||||
this.classList.add("icon-lock-open");
|
||||
this.classList.add("inactive");
|
||||
} else {
|
||||
this.classList.remove("icon-lock-open");
|
||||
this.classList.add("icon-lock");
|
||||
this.classList.remove("inactive");
|
||||
}
|
||||
}
|
||||
|
||||
function openBurgEditor() {
|
||||
openDialog("burgEditor", null, {id: +this.parentNode.dataset.id});
|
||||
}
|
||||
|
||||
function triggerBurgRemove() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
if (pack.burgs[burg].capital)
|
||||
return tip("You cannot remove the capital. Please change the capital first", false, "error");
|
||||
|
||||
confirmationDialog({
|
||||
title: "Remove burg",
|
||||
message: "Are you sure you want to remove the burg? This actiove cannot be reverted",
|
||||
confirm: "Remove",
|
||||
onConfirm: () => {
|
||||
removeBurg(burg);
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function regenerateNames() {
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
const burg = +el.dataset.id;
|
||||
if (pack.burgs[burg].lock) return;
|
||||
|
||||
const culture = pack.burgs[burg].culture;
|
||||
const name = Names.getCulture(culture);
|
||||
|
||||
el.querySelector(".burgName").value = name;
|
||||
pack.burgs[burg].name = el.dataset.name = name;
|
||||
burgLabels.select("[data-id='" + burg + "']").text(name);
|
||||
});
|
||||
}
|
||||
|
||||
function enterAddBurgMode() {
|
||||
if (this.classList.contains("pressed")) return exitAddBurgMode();
|
||||
customization = 3;
|
||||
this.classList.add("pressed");
|
||||
tip("Click on the map to create a new burg. Hold Shift to add multiple", true, "warn");
|
||||
viewbox.style("cursor", "crosshair").on("click", addBurgOnClick);
|
||||
}
|
||||
|
||||
function addBurgOnClick() {
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
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) {
|
||||
exitAddBurgMode();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
}
|
||||
|
||||
function exitAddBurgMode() {
|
||||
customization = 0;
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
if (addBurgTool.classList.contains("pressed")) addBurgTool.classList.remove("pressed");
|
||||
if (addNewBurg.classList.contains("pressed")) addNewBurg.classList.remove("pressed");
|
||||
}
|
||||
|
||||
function showBurgsChart() {
|
||||
// build hierarchy tree
|
||||
const states = pack.states.map(s => {
|
||||
const color = s.color ? s.color : "#ccc";
|
||||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, state: s.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const burgs = pack.burgs
|
||||
.filter(b => b.i && !b.removed)
|
||||
.map(b => {
|
||||
const id = b.i + states.length - 1;
|
||||
const population = b.population;
|
||||
const capital = b.capital;
|
||||
const province = pack.cells.province[b.cell];
|
||||
const parent = province ? province + states.length - 1 : b.state;
|
||||
return {
|
||||
id,
|
||||
i: b.i,
|
||||
state: b.state,
|
||||
culture: b.culture,
|
||||
province,
|
||||
parent,
|
||||
name: b.name,
|
||||
population,
|
||||
capital,
|
||||
x: b.x,
|
||||
y: b.y
|
||||
};
|
||||
});
|
||||
const data = states.concat(burgs);
|
||||
if (data.length < 2) return tip("No burgs to show", false, "error");
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
.parentId(d => d.state)(data)
|
||||
.sum(d => d.population)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
const width = 150 + 200 * uiSizeOutput.value;
|
||||
const height = 150 + 200 * uiSizeOutput.value;
|
||||
const margin = {top: 0, right: -50, bottom: -10, left: -50};
|
||||
const w = width - margin.left - margin.right;
|
||||
const h = height - margin.top - margin.bottom;
|
||||
const treeLayout = d3.pack().size([w, h]).padding(3);
|
||||
|
||||
// prepare svg
|
||||
alertMessage.innerHTML = /* html */ `<select id="burgsTreeType" style="display:block; margin-left:13px; font-size:11px">
|
||||
<option value="states" selected>Group by state</option>
|
||||
<option value="cultures">Group by culture</option>
|
||||
<option value="parent">Group by province and state</option>
|
||||
<option value="provinces">Group by province</option>
|
||||
</select>`;
|
||||
alertMessage.innerHTML += `<div id='burgsInfo' class='chartInfo'>‍</div>`;
|
||||
const svg = d3
|
||||
.select("#alertMessage")
|
||||
.insert("svg", "#burgsInfo")
|
||||
.attr("id", "burgsTree")
|
||||
.attr("width", width)
|
||||
.attr("height", height - 10)
|
||||
.attr("stroke-width", 2);
|
||||
const graph = svg.append("g").attr("transform", `translate(-50, -10)`);
|
||||
document.getElementById("burgsTreeType").addEventListener("change", updateChart);
|
||||
|
||||
treeLayout(root);
|
||||
|
||||
const node = graph
|
||||
.selectAll("circle")
|
||||
.data(root.leaves())
|
||||
.join("circle")
|
||||
.attr("data-id", d => d.data.i)
|
||||
.attr("r", d => d.r)
|
||||
.attr("fill", d => d.parent.data.color)
|
||||
.attr("cx", d => d.x)
|
||||
.attr("cy", d => d.y)
|
||||
.on("mouseenter", d => showInfo(event, d))
|
||||
.on("mouseleave", d => hideInfo(event, d))
|
||||
.on("click", d => zoomTo(d.data.x, d.data.y, 8, 2000));
|
||||
|
||||
function showInfo(ev, d) {
|
||||
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 * urbanization);
|
||||
|
||||
burgsInfo.innerHTML = /* html */ `${name}. ${parent}. Population: ${population}`;
|
||||
burgHighlightOn(ev);
|
||||
tip("Click to zoom into view");
|
||||
}
|
||||
|
||||
function hideInfo(ev) {
|
||||
burgHighlightOff(ev);
|
||||
if (!document.getElementById("burgsInfo")) return;
|
||||
burgsInfo.innerHTML = "‍";
|
||||
d3.select(ev.target).transition().attr("stroke", null);
|
||||
tip("");
|
||||
}
|
||||
|
||||
function updateChart() {
|
||||
const getStatesData = () =>
|
||||
pack.states.map(s => {
|
||||
const color = s.color ? s.color : "#ccc";
|
||||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, state: s.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const getCulturesData = () =>
|
||||
pack.cultures.map(c => {
|
||||
const color = c.color ? c.color : "#ccc";
|
||||
return {id: c.i, culture: c.i ? 0 : null, color, name: c.name};
|
||||
});
|
||||
|
||||
const getParentData = () => {
|
||||
const states = pack.states.map(s => {
|
||||
const color = s.color ? s.color : "#ccc";
|
||||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, parent: s.i ? 0 : null, color, name};
|
||||
});
|
||||
const provinces = pack.provinces
|
||||
.filter(p => p.i && !p.removed)
|
||||
.map(p => {
|
||||
return {id: p.i + states.length - 1, parent: p.state, color: p.color, name: p.fullName};
|
||||
});
|
||||
return states.concat(provinces);
|
||||
};
|
||||
|
||||
const getProvincesData = () =>
|
||||
pack.provinces.map(p => {
|
||||
const color = p.color ? p.color : "#ccc";
|
||||
const name = p.fullName ? p.fullName : p.name;
|
||||
return {id: p.i ? p.i : 0, province: p.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const value = d => {
|
||||
if (this.value === "states") return d.state;
|
||||
if (this.value === "cultures") return d.culture;
|
||||
if (this.value === "parent") return d.parent;
|
||||
if (this.value === "provinces") return d.province;
|
||||
};
|
||||
|
||||
const mapping = {
|
||||
states: getStatesData,
|
||||
cultures: getCulturesData,
|
||||
parent: getParentData,
|
||||
provinces: getProvincesData
|
||||
};
|
||||
|
||||
const base = mapping[this.value]();
|
||||
burgs.forEach(b => (b.id = b.i + base.length - 1));
|
||||
|
||||
const data = base.concat(burgs);
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
.parentId(d => value(d))(data)
|
||||
.sum(d => d.population)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
node
|
||||
.data(treeLayout(root).leaves())
|
||||
.transition()
|
||||
.duration(2000)
|
||||
.attr("data-id", d => d.data.i)
|
||||
.attr("fill", d => d.parent.data.color)
|
||||
.attr("cx", d => d.x)
|
||||
.attr("cy", d => d.y)
|
||||
.attr("r", d => d.r);
|
||||
}
|
||||
|
||||
$("#alert").dialog({
|
||||
title: "Burgs bubble chart",
|
||||
width: "fit-content",
|
||||
position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"},
|
||||
buttons: {},
|
||||
close: () => (alertMessage.innerHTML = "")
|
||||
});
|
||||
}
|
||||
|
||||
function downloadBurgsData() {
|
||||
let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,Latitude,Longitude,Elevation (${heightUnit.value}),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town`; // headers
|
||||
if (options.showMFCGMap) data += `,City Generator Link`;
|
||||
data += "\n";
|
||||
|
||||
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||
|
||||
valid.forEach(b => {
|
||||
data += b.i + ",";
|
||||
data += b.name + ",";
|
||||
const province = pack.cells.province[b.cell];
|
||||
data += province ? pack.provinces[province].name + "," : ",";
|
||||
data += province ? pack.provinces[province].fullName + "," : ",";
|
||||
data += pack.states[b.state].name + ",";
|
||||
data += pack.states[b.state].fullName + ",";
|
||||
data += pack.cultures[b.culture].name + ",";
|
||||
data += pack.religions[pack.cells.religion[b.cell]].name + ",";
|
||||
data += rn(b.population * populationRate * urbanization) + ",";
|
||||
|
||||
// add geography data
|
||||
const [lon, lat] = getCoordinates(b.x, b.y, 2);
|
||||
data += lat + ",";
|
||||
data += lon + ",";
|
||||
data += parseInt(getHeight(pack.cells.h[b.cell])) + ",";
|
||||
|
||||
// add status data
|
||||
data += b.capital ? "capital," : ",";
|
||||
data += b.port ? "port," : ",";
|
||||
data += b.citadel ? "citadel," : ",";
|
||||
data += b.walls ? "walls," : ",";
|
||||
data += b.plaza ? "plaza," : ",";
|
||||
data += b.temple ? "temple," : ",";
|
||||
data += b.shanty ? "shanty town," : ",";
|
||||
if (options.showMFCGMap) data += getMFCGlink(b);
|
||||
data += "\n";
|
||||
});
|
||||
|
||||
const name = getFileName("Burgs") + ".csv";
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function renameBurgsInBulk() {
|
||||
alertMessage.innerHTML = /* html */ `Download burgs list as a text file, make changes and re-upload the file. Make sure the file is a plain text document with each
|
||||
name on its own line (the dilimiter is CRLF). If you do not want to change the name, just leave it as is`;
|
||||
|
||||
$("#alert").dialog({
|
||||
title: "Burgs bulk renaming",
|
||||
width: "22em",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Download: function () {
|
||||
const data = pack.burgs
|
||||
.filter(b => b.i && !b.removed)
|
||||
.map(b => b.name)
|
||||
.join("\r\n");
|
||||
const name = getFileName("Burg names") + ".txt";
|
||||
downloadFile(data, name);
|
||||
},
|
||||
Upload: () => burgsListToLoad.click(),
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function importBurgNames(dataLoaded) {
|
||||
if (!dataLoaded) return tip("Cannot load the file, please check the format", false, "error");
|
||||
const data = dataLoaded.split("\r\n");
|
||||
if (!data.length) return tip("Cannot parse the list, please check the file format", false, "error");
|
||||
|
||||
let change = [];
|
||||
let message = `Burgs to be renamed as below:`;
|
||||
message += `<table class="overflow-table"><tr><th>Id</th><th>Current name</th><th>New Name</th></tr>`;
|
||||
|
||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
for (let i = 0; i < data.length && i <= burgs.length; i++) {
|
||||
const v = data[i];
|
||||
if (!v || !burgs[i] || v == burgs[i].name) continue;
|
||||
change.push({id: burgs[i].i, name: v});
|
||||
message += `<tr><td style="width:20%">${burgs[i].i}</td><td style="width:40%">${burgs[i].name}</td><td style="width:40%">${v}</td></tr>`;
|
||||
}
|
||||
message += `</tr></table>`;
|
||||
|
||||
if (!change.length) message = "No changes found in the file. Please change some names to get a result";
|
||||
alertMessage.innerHTML = message;
|
||||
|
||||
const onConfirm = () => {
|
||||
for (let i = 0; i < change.length; i++) {
|
||||
const id = change[i].id;
|
||||
pack.burgs[id].name = change[i].name;
|
||||
burgLabels.select("[data-id='" + id + "']").text(change[i].name);
|
||||
}
|
||||
burgsOverviewAddLines();
|
||||
};
|
||||
|
||||
confirmationDialog({
|
||||
title: "Burgs bulk renaming",
|
||||
message,
|
||||
confirm: "Rename",
|
||||
onConfirm
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAllBurgsRemove() {
|
||||
const number = pack.burgs.filter(b => b.i && !b.removed && !b.capital && !b.lock).length;
|
||||
confirmationDialog({
|
||||
title: `Remove ${number} burgs`,
|
||||
message: `
|
||||
Are you sure you want to remove all <i>unlocked</i> burgs except for capitals?
|
||||
<br><i>To remove a capital you have to remove a state first</i>`,
|
||||
confirm: "Remove",
|
||||
onConfirm: removeAllBurgs
|
||||
});
|
||||
}
|
||||
|
||||
function removeAllBurgs() {
|
||||
pack.burgs.filter(b => b.i && !(b.capital || b.lock)).forEach(b => removeBurg(b.i));
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function invertLock() {
|
||||
pack.burgs = pack.burgs.map(burg => ({...burg, lock: !burg.lock}));
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function toggleLockAll() {
|
||||
const activeBurgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
const allLocked = activeBurgs.every(burg => burg.lock);
|
||||
|
||||
pack.burgs.forEach(burg => {
|
||||
burg.lock = !allLocked;
|
||||
});
|
||||
|
||||
burgsOverviewAddLines();
|
||||
document.getElementById("burgsLockAll").className = allLocked ? "icon-lock" : "icon-lock-open";
|
||||
}
|
||||
|
||||
function updateLockAllIcon() {
|
||||
const allLocked = pack.burgs.every(({lock, i, removed}) => lock || !i || removed);
|
||||
document.getElementById("burgsLockAll").className = allLocked ? "icon-lock-open" : "icon-lock";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import {getHeight, getCellIdPrecipitation, getFriendlyPopulation} from "utils/unitUtils.ts";
|
||||
import {getArea, getAreaUnit} from "utils/unitUtils";
|
||||
import {getCellIdPrecipitation, getFriendlyPopulation, getHeight} from "utils/unitUtils.ts";
|
||||
|
||||
// get cell info on mouse move
|
||||
export function updateCellInfo(point, i, g) {
|
||||
|
|
|
|||
|
|
@ -1,227 +0,0 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {getPackPolygon} from "utils/graphUtils";
|
||||
import {tip} from "scripts/tooltips";
|
||||
import {clipPoly} from "utils/lineUtils";
|
||||
import {rn} from "utils/numberUtils";
|
||||
import {round} from "utils/stringUtils";
|
||||
import {si} from "utils/unitUtils";
|
||||
import {closeDialogs} from "dialogs/utils";
|
||||
|
||||
export function editCoastline(node = d3.event.target) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
if (layerIsOn("toggleCells")) toggleCells();
|
||||
|
||||
$("#coastlineEditor").dialog({
|
||||
title: "Edit Coastline",
|
||||
resizable: false,
|
||||
position: {my: "center top+20", at: "top", of: d3.event, collision: "fit"},
|
||||
close: closeCoastlineEditor
|
||||
});
|
||||
|
||||
debug.append("g").attr("id", "vertices");
|
||||
elSelected = d3.select(node);
|
||||
selectCoastlineGroup(node);
|
||||
drawCoastlineVertices();
|
||||
viewbox.on("touchmove mousemove", null);
|
||||
|
||||
if (fmg.modules.editCoastline) return;
|
||||
fmg.modules.editCoastline = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("coastlineGroupsShow").addEventListener("click", showGroupSection);
|
||||
document.getElementById("coastlineGroup").addEventListener("change", changeCoastlineGroup);
|
||||
document.getElementById("coastlineGroupAdd").addEventListener("click", toggleNewGroupInput);
|
||||
document.getElementById("coastlineGroupName").addEventListener("change", createNewGroup);
|
||||
document.getElementById("coastlineGroupRemove").addEventListener("click", removeCoastlineGroup);
|
||||
document.getElementById("coastlineGroupsHide").addEventListener("click", hideGroupSection);
|
||||
document.getElementById("coastlineEditStyle").addEventListener("click", editGroupStyle);
|
||||
|
||||
function drawCoastlineVertices() {
|
||||
const f = +elSelected.attr("data-f"); // feature id
|
||||
const v = pack.features[f].vertices; // coastline outer vertices
|
||||
|
||||
const l = pack.cells.i.length;
|
||||
const c = [...new Set(v.map(v => pack.vertices.c[v]).flat())].filter(c => c < l);
|
||||
debug
|
||||
.select("#vertices")
|
||||
.selectAll("polygon")
|
||||
.data(c)
|
||||
.enter()
|
||||
.append("polygon")
|
||||
.attr("points", d => getPackPolygon(d))
|
||||
.attr("data-c", d => d);
|
||||
|
||||
debug
|
||||
.select("#vertices")
|
||||
.selectAll("circle")
|
||||
.data(v)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("cx", d => pack.vertices.p[d][0])
|
||||
.attr("cy", d => pack.vertices.p[d][1])
|
||||
.attr("r", 0.4)
|
||||
.attr("data-v", d => d)
|
||||
.call(d3.drag().on("drag", dragVertex))
|
||||
.on("mousemove", () =>
|
||||
tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights")
|
||||
);
|
||||
|
||||
const area = pack.features[f].area;
|
||||
coastlineArea.innerHTML = si(getArea(area)) + " " + getAreaUnit();
|
||||
}
|
||||
|
||||
function dragVertex() {
|
||||
const x = rn(d3.event.x, 2),
|
||||
y = rn(d3.event.y, 2);
|
||||
this.setAttribute("cx", x);
|
||||
this.setAttribute("cy", y);
|
||||
const v = +this.dataset.v;
|
||||
pack.vertices.p[v] = [x, y];
|
||||
debug
|
||||
.select("#vertices")
|
||||
.selectAll("polygon")
|
||||
.attr("points", d => getPackPolygon(d));
|
||||
redrawCoastline();
|
||||
}
|
||||
|
||||
const lineGen = d3.line().curve(d3.curveBasisClosed);
|
||||
|
||||
function redrawCoastline() {
|
||||
const f = +elSelected.attr("data-f");
|
||||
const vertices = pack.features[f].vertices;
|
||||
const points = clipPoly(vertices.map(v => pack.vertices.p[v]));
|
||||
const d = round(lineGen(points));
|
||||
elSelected.attr("d", d);
|
||||
defs.select("mask#land > path#land_" + f).attr("d", d); // update land mask
|
||||
defs.select("mask#water > path#water_" + f).attr("d", d); // update water mask
|
||||
|
||||
const area = Math.abs(d3.polygonArea(points));
|
||||
coastlineArea.innerHTML = si(getArea(area)) + " " + getAreaUnit();
|
||||
}
|
||||
|
||||
function showGroupSection() {
|
||||
document.querySelectorAll("#coastlineEditor > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("coastlineGroupsSelection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideGroupSection() {
|
||||
document.querySelectorAll("#coastlineEditor > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("coastlineGroupsSelection").style.display = "none";
|
||||
document.getElementById("coastlineGroupName").style.display = "none";
|
||||
document.getElementById("coastlineGroupName").value = "";
|
||||
document.getElementById("coastlineGroup").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function selectCoastlineGroup(node) {
|
||||
const group = node.parentNode.id;
|
||||
const select = document.getElementById("coastlineGroup");
|
||||
select.options.length = 0; // remove all options
|
||||
|
||||
coastline.selectAll("g").each(function () {
|
||||
select.options.add(new Option(this.id, this.id, false, this.id === group));
|
||||
});
|
||||
}
|
||||
|
||||
function changeCoastlineGroup() {
|
||||
document.getElementById(this.value).appendChild(elSelected.node());
|
||||
}
|
||||
|
||||
function toggleNewGroupInput() {
|
||||
if (coastlineGroupName.style.display === "none") {
|
||||
coastlineGroupName.style.display = "inline-block";
|
||||
coastlineGroupName.focus();
|
||||
coastlineGroup.style.display = "none";
|
||||
} else {
|
||||
coastlineGroupName.style.display = "none";
|
||||
coastlineGroup.style.display = "inline-block";
|
||||
}
|
||||
}
|
||||
|
||||
function createNewGroup() {
|
||||
if (!this.value) {
|
||||
tip("Please provide a valid group name");
|
||||
return;
|
||||
}
|
||||
const group = this.value
|
||||
.toLowerCase()
|
||||
.replace(/ /g, "_")
|
||||
.replace(/[^\w\s]/gi, "");
|
||||
|
||||
if (document.getElementById(group)) {
|
||||
tip("Element with this id already exists. Please provide a unique name", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number.isFinite(+group.charAt(0))) {
|
||||
tip("Group name should start with a letter", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// just rename if only 1 element left
|
||||
const oldGroup = elSelected.node().parentNode;
|
||||
const basic = ["sea_island", "lake_island"].includes(oldGroup.id);
|
||||
if (!basic && oldGroup.childElementCount === 1) {
|
||||
document.getElementById("coastlineGroup").selectedOptions[0].remove();
|
||||
document.getElementById("coastlineGroup").options.add(new Option(group, group, false, true));
|
||||
oldGroup.id = group;
|
||||
toggleNewGroupInput();
|
||||
document.getElementById("coastlineGroupName").value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// create a new group
|
||||
const newGroup = elSelected.node().parentNode.cloneNode(false);
|
||||
document.getElementById("coastline").appendChild(newGroup);
|
||||
newGroup.id = group;
|
||||
document.getElementById("coastlineGroup").options.add(new Option(group, group, false, true));
|
||||
document.getElementById(group).appendChild(elSelected.node());
|
||||
|
||||
toggleNewGroupInput();
|
||||
document.getElementById("coastlineGroupName").value = "";
|
||||
}
|
||||
|
||||
function removeCoastlineGroup() {
|
||||
const group = elSelected.node().parentNode.id;
|
||||
if (["sea_island", "lake_island"].includes(group)) {
|
||||
tip("This is one of the default groups, it cannot be removed", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const count = elSelected.node().parentNode.childElementCount;
|
||||
alertMessage.innerHTML = /* html */ `Are you sure you want to remove the group? All coastline elements of the group (${count}) will be moved under
|
||||
<i>sea_island</i> group`;
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove coastline group",
|
||||
width: "26em",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
const sea = document.getElementById("sea_island");
|
||||
const groupEl = document.getElementById(group);
|
||||
while (groupEl.childNodes.length) {
|
||||
sea.appendChild(groupEl.childNodes[0]);
|
||||
}
|
||||
groupEl.remove();
|
||||
document.getElementById("coastlineGroup").selectedOptions[0].remove();
|
||||
document.getElementById("coastlineGroup").value = "sea_island";
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editGroupStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle("coastline", g);
|
||||
}
|
||||
|
||||
function closeCoastlineEditor() {
|
||||
debug.select("#vertices").remove();
|
||||
unselect();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,462 +0,0 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {restoreDefaultEvents} from "scripts/events";
|
||||
import {findCell} from "utils/graphUtils";
|
||||
import {tip, clearMainTip} from "scripts/tooltips";
|
||||
import {closeDialogs} from "dialogs/utils";
|
||||
|
||||
export function editDiplomacy() {
|
||||
if (customization) return;
|
||||
if (pack.states.filter(s => s.i && !s.removed).length < 2)
|
||||
return tip("There should be at least 2 states to edit the diplomacy", false, "error");
|
||||
|
||||
const body = document.getElementById("diplomacyBodySection");
|
||||
|
||||
closeDialogs("#diplomacyEditor, .stable");
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders();
|
||||
if (layerIsOn("toggleProvinces")) toggleProvinces();
|
||||
if (layerIsOn("toggleCultures")) toggleCultures();
|
||||
if (layerIsOn("toggleBiomes")) toggleBiomes();
|
||||
if (layerIsOn("toggleReligions")) toggleReligions();
|
||||
|
||||
const relations = {
|
||||
Ally: {
|
||||
inText: "is an ally of",
|
||||
color: "#00b300",
|
||||
tip: "Allies formed a defensive pact and protect each other in case of third party aggression"
|
||||
},
|
||||
Friendly: {
|
||||
inText: "is friendly to",
|
||||
color: "#d4f8aa",
|
||||
tip: "State is friendly to anouther state when they share some common interests"
|
||||
},
|
||||
Neutral: {
|
||||
inText: "is neutral to",
|
||||
color: "#edeee8",
|
||||
tip: "Neutral means states relations are neither positive nor negative"
|
||||
},
|
||||
Suspicion: {
|
||||
inText: "is suspicious of",
|
||||
color: "#eeafaa",
|
||||
tip: "Suspicion means state has a cautious distrust of another state"
|
||||
},
|
||||
Enemy: {inText: "is at war with", color: "#e64b40", tip: "Enemies are states at war with each other"},
|
||||
Unknown: {
|
||||
inText: "does not know about",
|
||||
color: "#a9a9a9",
|
||||
tip: "Relations are unknown if states do not have enough information about each other"
|
||||
},
|
||||
Rival: {
|
||||
inText: "is a rival of",
|
||||
color: "#ad5a1f",
|
||||
tip: "Rivalry is a state of competing for dominance in the region"
|
||||
},
|
||||
Vassal: {inText: "is a vassal of", color: "#87CEFA", tip: "Vassal is a state having obligation to its suzerain"},
|
||||
Suzerain: {
|
||||
inText: "is suzerain to",
|
||||
color: "#00008B",
|
||||
tip: "Suzerain is a state having some control over its vassals"
|
||||
}
|
||||
};
|
||||
|
||||
refreshDiplomacyEditor();
|
||||
viewbox.style("cursor", "crosshair").on("click", selectStateOnMapClick);
|
||||
|
||||
if (fmg.modules.editDiplomacy) return;
|
||||
fmg.modules.editDiplomacy = true;
|
||||
|
||||
$("#diplomacyEditor").dialog({
|
||||
title: "Diplomacy Editor",
|
||||
resizable: false,
|
||||
width: "fit-content",
|
||||
close: closeDiplomacyEditor,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("diplomacyEditorRefresh").addEventListener("click", refreshDiplomacyEditor);
|
||||
document.getElementById("diplomacyEditStyle").addEventListener("click", () => editStyle("regions"));
|
||||
document.getElementById("diplomacyRegenerate").addEventListener("click", regenerateRelations);
|
||||
document.getElementById("diplomacyReset").addEventListener("click", resetRelations);
|
||||
document.getElementById("diplomacyShowMatrix").addEventListener("click", showRelationsMatrix);
|
||||
document.getElementById("diplomacyHistory").addEventListener("click", showRelationsHistory);
|
||||
document.getElementById("diplomacyExport").addEventListener("click", downloadDiplomacyData);
|
||||
|
||||
body.addEventListener("click", function (ev) {
|
||||
const el = ev.target;
|
||||
if (el.parentElement.classList.contains("Self")) return;
|
||||
|
||||
if (el.classList.contains("changeRelations")) {
|
||||
const line = el.parentElement;
|
||||
const subjectId = +line.dataset.id;
|
||||
const objectId = +body.querySelector("div.Self").dataset.id;
|
||||
const currentRelation = line.dataset.relations;
|
||||
|
||||
selectRelation(subjectId, objectId, currentRelation);
|
||||
return;
|
||||
}
|
||||
|
||||
// select state of clicked line
|
||||
body.querySelector("div.Self").classList.remove("Self");
|
||||
el.parentElement.classList.add("Self");
|
||||
refreshDiplomacyEditor();
|
||||
});
|
||||
|
||||
function refreshDiplomacyEditor() {
|
||||
diplomacyEditorAddLines();
|
||||
showStateRelations();
|
||||
}
|
||||
|
||||
// add line for each state
|
||||
function diplomacyEditorAddLines() {
|
||||
const states = pack.states;
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const selectedId = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
|
||||
const selectedName = states[selectedId].name;
|
||||
|
||||
COArenderer.trigger("stateCOA" + selectedId, states[selectedId].coa);
|
||||
let lines = /* html */ `<div class="states Self" data-id=${selectedId} data-tip="List below shows relations to ${selectedName}">
|
||||
<div style="width: max-content">${states[selectedId].fullName}</div>
|
||||
<svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${selectedId}"></use></svg>
|
||||
</div>`;
|
||||
|
||||
for (const state of states) {
|
||||
if (!state.i || state.removed || state.i === selectedId) continue;
|
||||
const relation = state.diplomacy[selectedId];
|
||||
const {color, inText} = relations[relation];
|
||||
|
||||
const tip = `${state.name} ${inText} ${selectedName}`;
|
||||
const tipSelect = `${tip}. Click to see relations to ${state.name}`;
|
||||
const tipChange = `Click to change relations. ${tip}`;
|
||||
|
||||
const name = state.fullName.length < 23 ? state.fullName : state.name;
|
||||
COArenderer.trigger("stateCOA" + state.i, state.coa);
|
||||
|
||||
lines += /* html */ `<div class="states" data-id=${state.i} data-name="${name}" data-relations="${relation}">
|
||||
<svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${state.i}"></use></svg>
|
||||
<div data-tip="${tipSelect}" style="width: 12em">${name}</div>
|
||||
<div data-tip="${tipChange}" class="changeRelations" style="width: 6em">
|
||||
<fill-box fill="${color}" size=".9em"></fill-box>
|
||||
${relation}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
body.innerHTML = lines;
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev)));
|
||||
|
||||
applySorting(diplomacyHeader);
|
||||
$("#diplomacyEditor").dialog();
|
||||
}
|
||||
|
||||
function stateHighlightOn(event) {
|
||||
if (!layerIsOn("toggleStates")) return;
|
||||
const state = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
const d = regions.select("#state" + state).attr("d");
|
||||
|
||||
const path = debug
|
||||
.append("path")
|
||||
.attr("class", "highlight")
|
||||
.attr("d", d)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)");
|
||||
|
||||
const l = path.node().getTotalLength(),
|
||||
dur = (l + 5000) / 2;
|
||||
const i = d3.interpolateString("0," + l, l + "," + l);
|
||||
path
|
||||
.transition()
|
||||
.duration(dur)
|
||||
.attrTween("stroke-dasharray", function () {
|
||||
return t => i(t);
|
||||
});
|
||||
}
|
||||
|
||||
function stateHighlightOff(event) {
|
||||
debug.selectAll(".highlight").each(function () {
|
||||
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
|
||||
});
|
||||
}
|
||||
|
||||
function showStateRelations() {
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const sel = selectedLine ? +selectedLine.dataset.id : pack.states.find(s => s.i && !s.removed).i;
|
||||
if (!sel) return;
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
|
||||
statesBody.selectAll("path").each(function () {
|
||||
if (this.id.slice(0, 9) === "state-gap") return; // exclude state gap element
|
||||
const id = +this.id.slice(5); // state id
|
||||
|
||||
const relation = pack.states[id].diplomacy[sel];
|
||||
const color = relations[relation]?.color || "#4682b4";
|
||||
|
||||
this.setAttribute("fill", color);
|
||||
statesBody.select("#state-gap" + id).attr("stroke", color);
|
||||
statesHalo.select("#state-border" + id).attr("stroke", d3.color(color).darker().hex());
|
||||
});
|
||||
}
|
||||
|
||||
function selectStateOnMapClick() {
|
||||
const point = d3.mouse(this);
|
||||
const i = findCell(point[0], point[1]);
|
||||
const state = pack.cells.state[i];
|
||||
if (!state) return;
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
if (+selectedLine.dataset.id === state) return;
|
||||
|
||||
selectedLine.classList.remove("Self");
|
||||
body.querySelector("div[data-id='" + state + "']").classList.add("Self");
|
||||
refreshDiplomacyEditor();
|
||||
}
|
||||
|
||||
function selectRelation(subjectId, objectId, currentRelation) {
|
||||
const states = pack.states;
|
||||
|
||||
const subject = states[subjectId];
|
||||
const header = `<div style="margin-bottom: 0.3em"><svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${subject.i}"></use></svg> <b>${subject.fullName}</b></div>`;
|
||||
|
||||
const options = Object.entries(relations)
|
||||
.map(
|
||||
([relation, {color, inText, tip}]) =>
|
||||
`<div style="margin-block: 0.2em" data-tip="${tip}"><label class="pointer">
|
||||
<input type="radio" name="relationSelect" value="${relation}" ${currentRelation === relation && "checked"} >
|
||||
<fill-box fill="${color}" size=".8em"></fill-box>
|
||||
${inText}
|
||||
</label></div>`
|
||||
)
|
||||
.join("");
|
||||
|
||||
const object = states[objectId];
|
||||
const footer = `<div style="margin-top: 0.7em"><svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${object.i}"></use></svg> <b>${object.fullName}</b></div>`;
|
||||
|
||||
alertMessage.innerHTML = /* html */ `<div style="overflow: hidden">${header} ${options} ${footer}</div>`;
|
||||
|
||||
$("#alert").dialog({
|
||||
width: "fit-content",
|
||||
title: `Change relations`,
|
||||
buttons: {
|
||||
Apply: function () {
|
||||
const newRelation = document.querySelector('input[name="relationSelect"]:checked')?.value;
|
||||
changeRelation(subjectId, objectId, currentRelation, newRelation);
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function changeRelation(subjectId, objectId, oldRelation, newRelation) {
|
||||
if (newRelation === oldRelation) return;
|
||||
const states = pack.states;
|
||||
const chronicle = states[0].diplomacy;
|
||||
|
||||
const subjectName = states[subjectId].name;
|
||||
const objectName = states[objectId].name;
|
||||
|
||||
states[subjectId].diplomacy[objectId] = newRelation;
|
||||
states[objectId].diplomacy[subjectId] =
|
||||
newRelation === "Vassal" ? "Suzerain" : newRelation === "Suzerain" ? "Vassal" : newRelation;
|
||||
|
||||
// update relation history
|
||||
const change = () => [
|
||||
`Relations change`,
|
||||
`${subjectName}-${getAdjective(objectName)} relations changed to ${newRelation.toLowerCase()}`
|
||||
];
|
||||
const ally = () => [`Defence pact`, `${subjectName} entered into defensive pact with ${objectName}`];
|
||||
const vassal = () => [`Vassalization`, `${subjectName} became a vassal of ${objectName}`];
|
||||
const suzerain = () => [`Vassalization`, `${subjectName} vassalized ${objectName}`];
|
||||
const rival = () => [`Rivalization`, `${subjectName} and ${objectName} became rivals`];
|
||||
const unknown = () => [
|
||||
`Relations severance`,
|
||||
`${subjectName} recalled their ambassadors and wiped all the records about ${objectName}`
|
||||
];
|
||||
const war = () => [`War declaration`, `${subjectName} declared a war on its enemy ${objectName}`];
|
||||
const peace = () => {
|
||||
const treaty = `${subjectName} and ${objectName} agreed to cease fire and signed a peace treaty`;
|
||||
const changed =
|
||||
newRelation === "Ally"
|
||||
? ally()
|
||||
: newRelation === "Vassal"
|
||||
? vassal()
|
||||
: newRelation === "Suzerain"
|
||||
? suzerain()
|
||||
: newRelation === "Unknown"
|
||||
? unknown()
|
||||
: change();
|
||||
return [`War termination`, treaty, changed[1]];
|
||||
};
|
||||
|
||||
if (oldRelation === "Enemy") chronicle.push(peace());
|
||||
else if (newRelation === "Enemy") chronicle.push(war());
|
||||
else if (newRelation === "Vassal") chronicle.push(vassal());
|
||||
else if (newRelation === "Suzerain") chronicle.push(suzerain());
|
||||
else if (newRelation === "Ally") chronicle.push(ally());
|
||||
else if (newRelation === "Unknown") chronicle.push(unknown());
|
||||
else if (newRelation === "Rival") chronicle.push(rival());
|
||||
else chronicle.push(change());
|
||||
|
||||
refreshDiplomacyEditor();
|
||||
if (diplomacyMatrix.offsetParent) {
|
||||
document.getElementById("diplomacyMatrixBody").innerHTML = "";
|
||||
showRelationsMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
function regenerateRelations() {
|
||||
BurgsAndStates.generateDiplomacy();
|
||||
refreshDiplomacyEditor();
|
||||
}
|
||||
|
||||
function resetRelations() {
|
||||
const selectedId = +body.querySelector("div.Self")?.dataset?.id;
|
||||
if (!selectedId) return;
|
||||
const states = pack.states;
|
||||
|
||||
states[selectedId].diplomacy.forEach((relations, index) => {
|
||||
if (relations !== "x") {
|
||||
states[selectedId].diplomacy[index] = "Neutral";
|
||||
states[index].diplomacy[selectedId] = "Neutral";
|
||||
}
|
||||
});
|
||||
|
||||
refreshDiplomacyEditor();
|
||||
}
|
||||
|
||||
function showRelationsHistory() {
|
||||
const chronicle = pack.states[0].diplomacy;
|
||||
|
||||
let message = /* html */ `<div autocorrect="off" spellcheck="false">`;
|
||||
chronicle.forEach((entry, index) => {
|
||||
message += `<div>`;
|
||||
entry.forEach((l, entryIndex) => {
|
||||
message += /* html */ `<div contenteditable="true" data-id="${index}-${entryIndex}"
|
||||
${entryIndex ? "" : "style='font-weight:bold'"}>${l}</div>`;
|
||||
});
|
||||
message += `‍</div>`;
|
||||
});
|
||||
|
||||
if (!chronicle.length) {
|
||||
pack.states[0].diplomacy = [[]];
|
||||
message += /* html */ `<div><div contenteditable="true" data-id="0-0">No historical records</div>‍</div>`;
|
||||
}
|
||||
|
||||
alertMessage.innerHTML =
|
||||
message +
|
||||
`</div><div class="info-line">Type to edit. Press Enter to add a new line, empty the element to remove it</div>`;
|
||||
alertMessage
|
||||
.querySelectorAll("div[contenteditable='true']")
|
||||
.forEach(el => el.addEventListener("input", changeReliationsHistory));
|
||||
|
||||
$("#alert").dialog({
|
||||
title: "Relations history",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Save: function () {
|
||||
const data = this.querySelector("div").innerText.split("\n").join("\r\n");
|
||||
const name = getFileName("Relations history") + ".txt";
|
||||
downloadFile(data, name);
|
||||
},
|
||||
Clear: function () {
|
||||
pack.states[0].diplomacy = [];
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Close: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function changeReliationsHistory() {
|
||||
const i = this.dataset.id.split("-");
|
||||
const group = pack.states[0].diplomacy[i[0]];
|
||||
if (this.innerHTML === "") {
|
||||
group.splice(i[1], 1);
|
||||
this.remove();
|
||||
} else group[i[1]] = this.innerHTML;
|
||||
}
|
||||
|
||||
function showRelationsMatrix() {
|
||||
const states = pack.states.filter(s => s.i && !s.removed);
|
||||
const valid = states.map(state => state.i);
|
||||
const diplomacyMatrixBody = document.getElementById("diplomacyMatrixBody");
|
||||
|
||||
let table = `<table><thead><tr><th data-tip='‍'></th>`;
|
||||
table += states.map(state => `<th data-tip='Relations to ${state.fullName}'>${state.name}</th>`).join("") + `</tr>`;
|
||||
table += `<tbody>`;
|
||||
|
||||
states.forEach(state => {
|
||||
table +=
|
||||
`<tr data-id=${state.i}><th data-tip='Relations of ${state.fullName}'>${state.name}</th>` +
|
||||
state.diplomacy
|
||||
.filter((v, i) => valid.includes(i))
|
||||
.map((relation, index) => {
|
||||
const relationObj = relations[relation];
|
||||
if (!relationObj) return `<td class='${relation}'>${relation}</td>`;
|
||||
|
||||
const objectState = pack.states[valid[index]];
|
||||
const tip = `${state.fullName} ${relationObj.inText} ${objectState.fullName}`;
|
||||
return `<td data-id=${objectState.i} data-tip='${tip}' class='${relation}'>${relation}</td>`;
|
||||
})
|
||||
.join("") +
|
||||
"</tr>";
|
||||
});
|
||||
|
||||
table += `</tbody></table>`;
|
||||
diplomacyMatrixBody.innerHTML = table;
|
||||
|
||||
const tableEl = diplomacyMatrixBody.querySelector("table");
|
||||
tableEl.addEventListener("click", function (event) {
|
||||
const el = event.target;
|
||||
if (el.tagName !== "TD") return;
|
||||
|
||||
const currentRelation = el.innerText;
|
||||
if (!relations[currentRelation]) return;
|
||||
|
||||
const subjectId = +el.closest("tr")?.dataset?.id;
|
||||
const objectId = +el?.dataset?.id;
|
||||
|
||||
selectRelation(subjectId, objectId, currentRelation);
|
||||
});
|
||||
|
||||
$("#diplomacyMatrix").dialog({
|
||||
title: "Relations matrix",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {}
|
||||
});
|
||||
}
|
||||
|
||||
function downloadDiplomacyData() {
|
||||
const states = pack.states.filter(s => s.i && !s.removed);
|
||||
const valid = states.map(s => s.i);
|
||||
|
||||
let data = "," + states.map(s => s.name).join(",") + "\n"; // headers
|
||||
states.forEach(s => {
|
||||
const rels = s.diplomacy.filter((v, i) => valid.includes(i));
|
||||
data += s.name + "," + rels.join(",") + "\n";
|
||||
});
|
||||
|
||||
const name = getFileName("Relations") + ".csv";
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function closeDiplomacyEditor() {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
const selected = body.querySelector("div.Self");
|
||||
if (selected) selected.classList.remove("Self");
|
||||
if (layerIsOn("toggleStates")) drawStates();
|
||||
else toggleStates();
|
||||
debug.selectAll(".highlight").remove();
|
||||
}
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ function sortLines(headerElement) {
|
|||
applySorting(headers);
|
||||
}
|
||||
|
||||
function applySorting(headers) {
|
||||
export function applySorting(headers) {
|
||||
const header = headers.querySelector("div[class*='icon-sort']");
|
||||
if (!header) return;
|
||||
const sortby = header.dataset.sortby;
|
||||
|
|
@ -963,15 +963,6 @@ function selectIcon(initial, callback) {
|
|||
});
|
||||
}
|
||||
|
||||
function getAreaUnit(squareMark = "²") {
|
||||
return byId("areaUnit").value === "square" ? byId("distanceUnitInput").value + squareMark : byId("areaUnit").value;
|
||||
}
|
||||
|
||||
function getArea(rawArea) {
|
||||
const distanceScale = byId("distanceScaleInput")?.value;
|
||||
return rawArea * distanceScale ** 2;
|
||||
}
|
||||
|
||||
function confirmationDialog(options) {
|
||||
const {
|
||||
title = "Confirm action",
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ function handleKeyup(event) {
|
|||
else if (shift && code === "KeyB") editBiomes();
|
||||
else if (shift && code === "KeyS") openDialog("statesEditor");
|
||||
else if (shift && code === "KeyP") editProvinces();
|
||||
else if (shift && code === "KeyD") editDiplomacy();
|
||||
else if (shift && code === "KeyD") openDialog("diplomacyEditor");
|
||||
else if (shift && code === "KeyC") openDialog("culturesEditor");
|
||||
else if (shift && code === "KeyN") editNamesbase();
|
||||
else if (shift && code === "KeyZ") editZones();
|
||||
|
|
@ -53,7 +53,7 @@ function handleKeyup(event) {
|
|||
else if (shift && code === "KeyQ") openDialog("unitsEditor");
|
||||
else if (shift && code === "KeyO") editNotes();
|
||||
else if (shift && code === "KeyA") openDialog("chartsOverview");
|
||||
else if (shift && code === "KeyT") overviewBurgs();
|
||||
else if (shift && code === "KeyT") openDialog("burgsOverview");
|
||||
else if (shift && code === "KeyV") overviewRivers();
|
||||
else if (shift && code === "KeyM") overviewMilitary();
|
||||
else if (shift && code === "KeyK") overviewMarkers();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {getPackPolygon} from "utils/graphUtils";
|
||||
import {closeDialogs} from "dialogs/utils";
|
||||
import {tip} from "scripts/tooltips";
|
||||
import {getPackPolygon} from "utils/graphUtils";
|
||||
import {rn} from "utils/numberUtils";
|
||||
import {rand} from "utils/probabilityUtils";
|
||||
import {round} from "utils/stringUtils";
|
||||
import {si, getHeight} from "utils/unitUtils";
|
||||
import {closeDialogs} from "dialogs/utils";
|
||||
import {getArea, getAreaUnit, getHeight, si} from "utils/unitUtils";
|
||||
|
||||
export function editLake() {
|
||||
if (customization) return;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {restoreDefaultEvents} from "scripts/events";
|
||||
import {findAll, findCell, getPackPolygon, isLand} from "utils/graphUtils";
|
||||
import {unique} from "utils/arrayUtils";
|
||||
import {tip, showMainTip, clearMainTip} from "scripts/tooltips";
|
||||
import {getRandomColor} from "utils/colorUtils";
|
||||
import {rn} from "utils/numberUtils";
|
||||
import {rand, P} from "utils/probabilityUtils";
|
||||
import {parseTransform} from "utils/stringUtils";
|
||||
import {si} from "utils/unitUtils";
|
||||
import {turnLayerButtonOff} from "layers";
|
||||
import {byId} from "utils/shorthands";
|
||||
import {closeDialogs} from "dialogs/utils";
|
||||
import {turnLayerButtonOff} from "layers";
|
||||
import {restoreDefaultEvents} from "scripts/events";
|
||||
import {clearMainTip, showMainTip, tip} from "scripts/tooltips";
|
||||
import {unique} from "utils/arrayUtils";
|
||||
import {getRandomColor} from "utils/colorUtils";
|
||||
import {findAll, findCell, getPackPolygon, isLand} from "utils/graphUtils";
|
||||
import {rn} from "utils/numberUtils";
|
||||
import {P, rand} from "utils/probabilityUtils";
|
||||
import {byId} from "utils/shorthands";
|
||||
import {parseTransform} from "utils/stringUtils";
|
||||
import {getArea, getAreaUnit, si} from "utils/unitUtils";
|
||||
|
||||
export function editProvinces() {
|
||||
if (customization) return;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ toolsContent.addEventListener("click", function (event) {
|
|||
else if (button === "editBiomesButton") editBiomes();
|
||||
else if (button === "editStatesButton") openDialog("statesEditor");
|
||||
else if (button === "editProvincesButton") editProvinces();
|
||||
else if (button === "editDiplomacyButton") editDiplomacy();
|
||||
else if (button === "editDiplomacyButton") openDialog("diplomacyEditor");
|
||||
else if (button === "editCulturesButton") openDialog("culturesEditor");
|
||||
else if (button === "editReligions") openDialog("religionsEditor");
|
||||
else if (button === "editEmblemButton") openEmblemEditor();
|
||||
|
|
@ -33,7 +33,7 @@ toolsContent.addEventListener("click", function (event) {
|
|||
else if (button === "editNotesButton") editNotes();
|
||||
else if (button === "editZonesButton") editZones();
|
||||
else if (button === "overviewChartsButton") openDialog("chartsOverview");
|
||||
else if (button === "overviewBurgsButton") overviewBurgs();
|
||||
else if (button === "overviewBurgsButton") openDialog("burgsOverview");
|
||||
else if (button === "overviewRiversButton") overviewRivers();
|
||||
else if (button === "overviewMilitaryButton") overviewMilitary();
|
||||
else if (button === "overviewMarkersButton") overviewMarkers();
|
||||
|
|
@ -539,7 +539,7 @@ function addLabelOnClick() {
|
|||
function toggleAddBurg() {
|
||||
unpressClickToAddButton();
|
||||
document.getElementById("addBurgTool").classList.add("pressed");
|
||||
overviewBurgs();
|
||||
openDialog("burgsOverview");
|
||||
document.getElementById("addNewBurg").click();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {restoreDefaultEvents} from "scripts/events";
|
||||
import {findAll, findCell, getPackPolygon} from "utils/graphUtils";
|
||||
import {unique} from "utils/arrayUtils";
|
||||
import {tip, showMainTip, clearMainTip} from "scripts/tooltips";
|
||||
import {rn} from "utils/numberUtils";
|
||||
import {getNextId} from "utils/nodeUtils";
|
||||
import {si} from "utils/unitUtils";
|
||||
import {closeDialogs} from "dialogs/utils";
|
||||
import {restoreDefaultEvents} from "scripts/events";
|
||||
import {clearMainTip, showMainTip, tip} from "scripts/tooltips";
|
||||
import {unique} from "utils/arrayUtils";
|
||||
import {findAll, findCell, getPackPolygon} from "utils/graphUtils";
|
||||
import {getNextId} from "utils/nodeUtils";
|
||||
import {rn} from "utils/numberUtils";
|
||||
import {getArea, getAreaUnit, si} from "utils/unitUtils";
|
||||
|
||||
export function editZones() {
|
||||
closeDialogs();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue