mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 01:41:22 +01:00
commit
45f6b1827c
4 changed files with 94 additions and 64 deletions
17
index.html
17
index.html
|
|
@ -177,7 +177,7 @@
|
|||
<div id="loading">
|
||||
<div id="titleName"><t data-t="titleName">Azgaar's</t></div>
|
||||
<div id="title"><t data-t="title">Fantasy Map Generator</t></div>
|
||||
<div id="version"><t data-t="version">v. </t>1.72</div>
|
||||
<div id="version"><t data-t="version">v. </t>1.73</div>
|
||||
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
|
||||
</div>
|
||||
|
||||
|
|
@ -2871,9 +2871,10 @@
|
|||
<div id="zonesEditor" class="dialog stable" style="display: none">
|
||||
<div id="customHeader" class="header">
|
||||
<div style="left:1.8em" data-tip="Zone description">Description </div>
|
||||
<div style="left:13em" data-tip="Zone cells count" class="hide">Cells </div>
|
||||
<div style="left:19em" data-tip="Zone area" class="hide">Area </div>
|
||||
<div style="left:24em" data-tip="Zone population" class="hide">Population </div>
|
||||
<div style="left:13em" data-tip="Zone type">Type </div>
|
||||
<div style="left:19em" data-tip="Zone cells count" class="hide">Cells </div>
|
||||
<div style="left:23.6em" data-tip="Zone area" class="hide">Area </div>
|
||||
<div style="left:30.6em" data-tip="Zone population" class="hide">Population </div>
|
||||
</div>
|
||||
|
||||
<div id="zonesBodySection" class="table" data-type="absolute"></div>
|
||||
|
|
@ -2894,16 +2895,18 @@
|
|||
<button id="zonesManually" data-tip="Re-assign zones" class="icon-brush"></button>
|
||||
<div id="zonesManuallyButtons" style="display: none">
|
||||
<label data-tip="Change brush size. Shortcut: + (increase), – (decrease)" class="italic">Brush:
|
||||
<input id="zonesBrush" oninput="tip('Brush size: '+this.value); zonesBrushNumber.value = this.value" type="range" min=5 max=25 value=7 style="width:7em">
|
||||
<input id="zonesBrushNumber" oninput="tip('Brush size: '+this.value); zonesBrush.value = this.value" type="number" min=5 max=25 value=7>
|
||||
<input id="zonesBrush" oninput="tip('Brush size: '+this.value); zonesBrushNumber.value = this.value" type="range" min=5 max=50 value=7 style="width:9em">
|
||||
<input id="zonesBrushNumber" oninput="tip('Brush size: '+this.value); zonesBrush.value = this.value" type="number" min=5 max=50 value=7>
|
||||
</label><br>
|
||||
<button id="zonesManuallyApply" data-tip="Apply assignment" class="icon-check"></button>
|
||||
<button id="zonesManuallyCancel" data-tip="Cancel assignment" class="icon-cancel"></button>
|
||||
<button id="zonesRemove" data-tip="Click to toggle the removal mode on brush dragging. Shortcut: ctrl" class="icon-eraser"></button>
|
||||
</div>
|
||||
|
||||
<button id="zonesAdd" data-tip="Add a new zone layer" class="icon-plus"></button>
|
||||
<button id="zonesAdd" data-tip="Add new zone layer" class="icon-plus"></button>
|
||||
<button id="zonesExport" data-tip="Download zones-related data" class="icon-download"></button>
|
||||
|
||||
<div id="zonesFilters" data-tip="Show only zones of selected type" style="display: inline-block">Type: <select id="zonesFilterType"></select></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
39
main.js
39
main.js
|
|
@ -435,6 +435,7 @@ function showWelcomeMessage() {
|
|||
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
|
||||
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
|
||||
<ul><b>Latest changes:</b>
|
||||
<li>Zones editor: fitler by type</li>
|
||||
<li>Color picker: new hatchings</li>
|
||||
<li>New style presets: Cyberpunk and Atlas</li>
|
||||
<li>Burg temperature graph</li>
|
||||
|
|
@ -1504,14 +1505,12 @@ function rankCells() {
|
|||
TIME && console.timeEnd("rankCells");
|
||||
}
|
||||
|
||||
// regenerate some zones
|
||||
// generate zones
|
||||
function addZones(number = 1) {
|
||||
TIME && console.time("addZones");
|
||||
const data = [],
|
||||
cells = pack.cells,
|
||||
states = pack.states,
|
||||
burgs = pack.burgs;
|
||||
const {cells, states, burgs} = pack;
|
||||
const used = new Uint8Array(cells.i.length); // to store used cells
|
||||
const zonesData = [];
|
||||
|
||||
for (let i = 0; i < rn(Math.random() * 1.8 * number); i++) addInvasion(); // invasion of enemy lands
|
||||
for (let i = 0; i < rn(Math.random() * 1.6 * number); i++) addRebels(); // rebels along a state border
|
||||
|
|
@ -1525,6 +1524,8 @@ function addZones(number = 1) {
|
|||
for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addFlood(); // flood on river banks
|
||||
for (let i = 0; i < rn(Math.random() * 1.2 * number); i++) addTsunami(); // tsunami starting near coast
|
||||
|
||||
drawZones();
|
||||
|
||||
function addInvasion() {
|
||||
const atWar = states.filter(s => s.diplomacy && s.diplomacy.some(d => d === "Enemy"));
|
||||
if (!atWar.length) return;
|
||||
|
|
@ -1565,7 +1566,7 @@ function addZones(number = 1) {
|
|||
Intervention: 1
|
||||
});
|
||||
const name = getAdjective(invader.name) + " " + invasion;
|
||||
data.push({name, type: "Invasion", cells: cellsArray, fill: "url(#hatch1)"});
|
||||
zonesData.push({name, type: "Invasion", cells: cellsArray, fill: "url(#hatch1)"});
|
||||
}
|
||||
|
||||
function addRebels() {
|
||||
|
|
@ -1594,7 +1595,7 @@ function addZones(number = 1) {
|
|||
|
||||
const rebels = rw({Rebels: 5, Insurgents: 2, Mutineers: 1, Rioters: 1, Separatists: 1, Secessionists: 1, Insurrection: 2, Rebellion: 1, Conspiracy: 2});
|
||||
const name = getAdjective(states[neib].name) + " " + rebels;
|
||||
data.push({name, type: "Rebels", cells: cellsArray, fill: "url(#hatch3)"});
|
||||
zonesData.push({name, type: "Rebels", cells: cellsArray, fill: "url(#hatch3)"});
|
||||
}
|
||||
|
||||
function addProselytism() {
|
||||
|
|
@ -1624,7 +1625,7 @@ function addZones(number = 1) {
|
|||
}
|
||||
|
||||
const name = getAdjective(organized.name.split(" ")[0]) + " Proselytism";
|
||||
data.push({name, type: "Proselytism", cells: cellsArray, fill: "url(#hatch6)"});
|
||||
zonesData.push({name, type: "Proselytism", cells: cellsArray, fill: "url(#hatch6)"});
|
||||
}
|
||||
|
||||
function addCrusade() {
|
||||
|
|
@ -1636,7 +1637,7 @@ function addZones(number = 1) {
|
|||
cellsArray.forEach(i => (used[i] = 1));
|
||||
|
||||
const name = getAdjective(heresy.name.split(" ")[0]) + " Crusade";
|
||||
data.push({name, type: "Crusade", cells: cellsArray, fill: "url(#hatch6)"});
|
||||
zonesData.push({name, type: "Crusade", cells: cellsArray, fill: "url(#hatch6)"});
|
||||
}
|
||||
|
||||
function addDisease() {
|
||||
|
|
@ -1673,7 +1674,7 @@ function addZones(number = 1) {
|
|||
|
||||
const type = rw({Fever: 5, Pestilence: 2, Flu: 2, Pox: 2, Smallpox: 2, Plague: 4, Cholera: 2, Dropsy: 1, Leprosy: 2});
|
||||
const name = rw({[color()]: 4, [animal()]: 2, [adjective()]: 1}) + " " + type;
|
||||
data.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"});
|
||||
zonesData.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"});
|
||||
}
|
||||
|
||||
function addDisaster() {
|
||||
|
|
@ -1705,7 +1706,7 @@ function addZones(number = 1) {
|
|||
|
||||
const type = rw({Famine: 5, Dearth: 1, Drought: 3, Earthquake: 3, Tornadoes: 1, Wildfires: 1});
|
||||
const name = getAdjective(burg.name) + " " + type;
|
||||
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
|
||||
zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
|
||||
}
|
||||
|
||||
function addEruption() {
|
||||
|
|
@ -1736,7 +1737,7 @@ function addZones(number = 1) {
|
|||
});
|
||||
}
|
||||
|
||||
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch7)"});
|
||||
zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch7)"});
|
||||
}
|
||||
|
||||
function addAvalanche() {
|
||||
|
|
@ -1761,7 +1762,7 @@ function addZones(number = 1) {
|
|||
|
||||
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
|
||||
const name = proper + " Avalanche";
|
||||
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
|
||||
zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
|
||||
}
|
||||
|
||||
function addFault() {
|
||||
|
|
@ -1786,7 +1787,7 @@ function addZones(number = 1) {
|
|||
|
||||
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
|
||||
const name = proper + " Fault";
|
||||
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch2)"});
|
||||
zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch2)"});
|
||||
}
|
||||
|
||||
function addFlood() {
|
||||
|
|
@ -1816,7 +1817,7 @@ function addZones(number = 1) {
|
|||
}
|
||||
|
||||
const name = getAdjective(burgs[cells.burg[cell]].name) + " Flood";
|
||||
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
|
||||
zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
|
||||
}
|
||||
|
||||
function addTsunami() {
|
||||
|
|
@ -1844,13 +1845,13 @@ function addZones(number = 1) {
|
|||
|
||||
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
|
||||
const name = proper + " Tsunami";
|
||||
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
|
||||
zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
|
||||
}
|
||||
|
||||
void (function drawZones() {
|
||||
function drawZones() {
|
||||
zones
|
||||
.selectAll("g")
|
||||
.data(data)
|
||||
.data(zonesData)
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("id", (d, i) => "zone" + i)
|
||||
|
|
@ -1866,7 +1867,7 @@ function addZones(number = 1) {
|
|||
.attr("id", function (d) {
|
||||
return this.parentNode.id + "_" + d;
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("addZones");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -940,6 +940,12 @@ function parseLoadedData(data) {
|
|||
if (version < 1.73) {
|
||||
// v1.73 moved the hatching patterns out of the user's SVG
|
||||
document.getElementById("hatching")?.remove();
|
||||
|
||||
// v1.73 added zone type to UI, ensure type is populated
|
||||
const zones = Array.from(document.querySelectorAll("#zones > g"));
|
||||
zones.forEach(zone => {
|
||||
if (!zone.dataset.type) zone.dataset.type = "Unknown";
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ function editZones() {
|
|||
closeDialogs();
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
const body = document.getElementById("zonesBodySection");
|
||||
|
||||
updateFilters();
|
||||
zonesEditorAddLines();
|
||||
|
||||
if (modules.editZones) return;
|
||||
|
|
@ -18,6 +20,8 @@ function editZones() {
|
|||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("zonesFilterType").addEventListener("click", updateFilters);
|
||||
document.getElementById("zonesFilterType").addEventListener("change", filterZonesByType);
|
||||
document.getElementById("zonesEditorRefresh").addEventListener("click", zonesEditorAddLines);
|
||||
document.getElementById("zonesEditStyle").addEventListener("click", () => editStyle("zones"));
|
||||
document.getElementById("zonesLegend").addEventListener("click", toggleLegend);
|
||||
|
|
@ -38,37 +42,55 @@ function editZones() {
|
|||
else if (cl.contains("icon-trash-empty")) zoneRemove(zone);
|
||||
else if (cl.contains("icon-eye")) toggleVisibility(el);
|
||||
else if (cl.contains("icon-pin")) toggleFog(zone, cl);
|
||||
|
||||
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);
|
||||
const el = ev.target;
|
||||
const zone = zones.select("#" + el.parentNode.dataset.id);
|
||||
|
||||
if (el.classList.contains("zoneName")) zone.attr("data-description", el.value);
|
||||
else if (el.classList.contains("zoneType")) zone.attr("data-type", el.value);
|
||||
});
|
||||
|
||||
// update type filter with a list of used types
|
||||
function updateFilters() {
|
||||
const zones = Array.from(document.querySelectorAll("#zones > g"));
|
||||
const types = unique(zones.map(zone => zone.dataset.type));
|
||||
|
||||
const filterSelect = document.getElementById("zonesFilterType");
|
||||
const typeToFilterBy = types.includes(zonesFilterType.value) ? zonesFilterType.value : "all";
|
||||
|
||||
filterSelect.innerHTML = "<option value='all'>all</option>" + types.map(type => `<option value="${type}">${type}</option>`).join("");
|
||||
filterSelect.value = typeToFilterBy;
|
||||
}
|
||||
|
||||
// add line for each zone
|
||||
function zonesEditorAddLines() {
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
let lines = "";
|
||||
|
||||
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 typeToFilterBy = document.getElementById("zonesFilterType").value;
|
||||
const zones = Array.from(document.querySelectorAll("#zones > g"));
|
||||
const filteredZones = typeToFilterBy === "all" ? zones : zones.filter(zone => zone.dataset.type === typeToFilterBy);
|
||||
|
||||
const lines = filteredZones.map(zoneEl => {
|
||||
const c = zoneEl.dataset.cells ? zoneEl.dataset.cells.split(",").map(c => +c) : [];
|
||||
const description = zoneEl.dataset.description;
|
||||
const type = zoneEl.dataset.type;
|
||||
const fill = zoneEl.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;
|
||||
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 inactive = zoneEl.style.display === "none";
|
||||
const focused = defs.select("#fog #focus" + zoneEl.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}>
|
||||
return `<div class="states" data-id="${zoneEl.id}" data-fill="${fill}" data-description="${description}"
|
||||
data-type="${type}" data-cells=${c.length} data-area=${area} data-population=${population}>
|
||||
<fill-box fill="${fill}"></fill-box>
|
||||
<input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false">
|
||||
<input data-tip="Zone description. Click and type to change" style="width: 11em" class="zoneName" value="${description}" autocorrect="off" spellcheck="false">
|
||||
<input data-tip="Zone type. Click and type to change" class="zoneType" value="${type}">
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.length}</div>
|
||||
<span data-tip="Zone area" style="padding-right:4px" class="icon-map-o hide"></span>
|
||||
|
|
@ -82,13 +104,13 @@ function editZones() {
|
|||
</div>`;
|
||||
});
|
||||
|
||||
body.innerHTML = lines;
|
||||
body.innerHTML = lines.join("");
|
||||
|
||||
// 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) * populationRate;
|
||||
zonesFooterPopulation.dataset.population = totalPop;
|
||||
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
|
||||
zonesFooterNumber.innerHTML = `${filteredZones.length} of ${zones.length}`;
|
||||
zonesFooterCells.innerHTML = pack.cells.i.length;
|
||||
zonesFooterArea.innerHTML = si(totalArea) + unit;
|
||||
zonesFooterPopulation.innerHTML = si(totalPop);
|
||||
|
|
@ -114,6 +136,19 @@ function editZones() {
|
|||
zones.select("#" + zone).style("outline", null);
|
||||
}
|
||||
|
||||
function filterZonesByType() {
|
||||
const typeToFilterBy = this.value;
|
||||
const zones = Array.from(document.querySelectorAll("#zones > g"));
|
||||
|
||||
for (const zone of zones) {
|
||||
const type = zone.dataset.type;
|
||||
const visible = typeToFilterBy === "all" || type === typeToFilterBy;
|
||||
zone.style.display = visible ? "block" : "none";
|
||||
}
|
||||
|
||||
zonesEditorAddLines();
|
||||
}
|
||||
|
||||
$(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"));
|
||||
|
|
@ -129,7 +164,7 @@ function editZones() {
|
|||
function enterZonesManualAssignent() {
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
customization = 10;
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.querySelectorAll("#zonesBottom > *").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("zonesManuallyButtons").style.display = "inline-block";
|
||||
|
||||
zonesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
|
||||
|
|
@ -243,7 +278,7 @@ function editZones() {
|
|||
function exitZonesManualAssignment(close) {
|
||||
customization = 0;
|
||||
removeCircle();
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.querySelectorAll("#zonesBottom > *").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("zonesManuallyButtons").style.display = "none";
|
||||
|
||||
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
|
||||
|
|
@ -331,37 +366,22 @@ function editZones() {
|
|||
function addZonesLayer() {
|
||||
const id = getNextId("zone");
|
||||
const description = "Unknown zone";
|
||||
const type = "Unknown";
|
||||
const fill = "url(#hatch" + (id.slice(4) % 42) + ")";
|
||||
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;
|
||||
zones.append("g").attr("id", id).attr("data-description", description).attr("data-type", type).attr("data-cells", "").attr("fill", fill);
|
||||
|
||||
const line = `<div class="states" data-id="${id}" data-fill="${fill}" data-description="${description}" data-cells=0 data-area=0 data-population=0>
|
||||
<fill-box fill="${fill}"></fill-box>
|
||||
<input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false">
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells hide">0</div>
|
||||
<span data-tip="Zone area" style="padding-right:4px" class="icon-map-o hide"></span>
|
||||
<div data-tip="Zone area" class="biomeArea hide">0 ${unit}</div>
|
||||
<span class="icon-male hide"></span>
|
||||
<div class="culturePopulation hide">0</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 inactive hide placeholder"></span>
|
||||
<span data-tip="Toggle zone visibility" class="icon-eye hide placeholder"></span>
|
||||
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
|
||||
body.insertAdjacentHTML("beforeend", line);
|
||||
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
|
||||
zonesEditorAddLines();
|
||||
}
|
||||
|
||||
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,Type,Cells,Area " + unit + ",Population\n"; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.fill + ",";
|
||||
data += el.dataset.description + ",";
|
||||
data += el.dataset.type + ",";
|
||||
data += el.dataset.cells + ",";
|
||||
data += el.dataset.area + ",";
|
||||
data += el.dataset.population + "\n";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue