Merge branch 'Azgaar:master' into master

This commit is contained in:
Aitor 2022-05-02 15:34:01 +02:00 committed by GitHub
commit df627d3d75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
199 changed files with 7379 additions and 8490 deletions

View file

@ -266,14 +266,7 @@ function editBurg(id) {
toggleNewGroupInput();
document.getElementById("burgInputGroup").value = "";
const newLabelG = document.querySelector("#burgLabels").appendChild(labelG.cloneNode(false));
newLabelG.id = group;
const newIconG = document.querySelector("#burgIcons").appendChild(iconG.cloneNode(false));
newIconG.id = group;
if (anchor) {
const newAnchorG = document.querySelector("#anchors").appendChild(anchorG.cloneNode(false));
newAnchorG.id = group;
}
addBurgsGroup(group);
moveBurgToGroup(id, group);
}

View file

@ -1,5 +1,6 @@
"use strict";
function editCultures() {
const cultureTypes = ["Generic", "River", "Lake", "Naval", "Nomadic", "Hunting", "Highland"];
if (customization) return;
closeDialogs("#culturesEditor, .stable");
if (!layerIsOn("toggleCultures")) toggleCultures();
@ -37,6 +38,8 @@ function editCultures() {
document.getElementById("culturesEditNamesBase").addEventListener("click", editNamesbase);
document.getElementById("culturesAdd").addEventListener("click", enterAddCulturesMode);
document.getElementById("culturesExport").addEventListener("click", downloadCulturesData);
document.getElementById("culturesImport").addEventListener("click", () => document.getElementById("culturesCSVToLoad").click());
document.getElementById("culturesCSVToLoad").addEventListener("change", uploadCulturesData);
function refreshCulturesEditor() {
culturesCollectStatistics();
@ -169,8 +172,7 @@ function editCultures() {
function getTypeOptions(type) {
let options = "";
const types = ["Generic", "River", "Lake", "Naval", "Nomadic", "Hunting", "Highland"];
types.forEach(t => (options += `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`));
cultureTypes.forEach(t => (options += `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`));
return options;
}
@ -366,7 +368,7 @@ function editCultures() {
width: "24em",
buttons: {
Apply: function () {
applyPopulationChange();
applyPopulationChange(rural, urban, ruralPop.value, urbanPop.value, culture);
$(this).dialog("close");
},
Cancel: function () {
@ -375,32 +377,33 @@ function editCultures() {
},
position: {my: "center", at: "center", of: "svg"}
});
}
function applyPopulationChange() {
const ruralChange = ruralPop.value / rural;
if (isFinite(ruralChange) && ruralChange !== 1) {
const cells = pack.cells.i.filter(i => pack.cells.culture[i] === culture);
cells.forEach(i => (pack.cells.pop[i] *= ruralChange));
}
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
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));
}
const urbanChange = urbanPop.value / urban;
if (isFinite(urbanChange) && urbanChange !== 1) {
burgs.forEach(b => (b.population = rn(b.population * urbanChange, 4)));
}
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
const points = urbanPop.value / populationRate / urbanization;
const population = rn(points / burgs.length, 4);
burgs.forEach(b => (b.population = population));
}
refreshCulturesEditor();
function applyPopulationChange(oldRural, oldUrban, newRural, newUrban, culture) {
const ruralChange = newRural / oldRural;
if (isFinite(ruralChange) && ruralChange !== 1) {
const cells = pack.cells.i.filter(i => pack.cells.culture[i] === culture);
cells.forEach(i => (pack.cells.pop[i] *= ruralChange));
}
if (!isFinite(ruralChange) && +newRural > 0) {
const points = newRural / 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));
}
const burgs = pack.burgs.filter(b => !b.removed && b.culture === culture);
const urbanChange = newUrban / oldUrban;
if (isFinite(urbanChange) && urbanChange !== 1) {
burgs.forEach(b => (b.population = rn(b.population * urbanChange, 4)));
}
if (!isFinite(urbanChange) && +newUrban > 0) {
const points = newUrban / populationRate / urbanization;
const population = rn(points / burgs.length, 4);
burgs.forEach(b => (b.population = population));
}
refreshCulturesEditor();
}
function cultureRegenerateBurgs() {
@ -414,6 +417,26 @@ function editCultures() {
tip(`Names for ${cBurgs.length} burgs are regenerated`, false, "success");
}
function removeCultureModel(culture) {
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;
const origin = pack.cultures[culture].origin;
pack.cultures.forEach(c => {
if (c.origin === culture) c.origin = origin;
});
refreshCulturesEditor();
}
function cultureRemove() {
if (customization === 4) return;
const culture = +this.parentNode.dataset.id;
@ -424,23 +447,7 @@ function editCultures() {
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;
const origin = pack.cultures[culture].origin;
pack.cultures.forEach(c => {
if (c.origin === culture) c.origin = origin;
});
refreshCulturesEditor();
removeCultureModel(culture);
$(this).dialog("close");
},
Cancel: function () {
@ -856,7 +863,7 @@ function editCultures() {
function downloadCulturesData() {
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
let data = "Id,Culture,Color,Cells,Expansionism,Type,Area " + unit + ",Population,Namesbase,Emblems Shape\n"; // headers
let data = "Id,Culture,Color,Cells,Expansionism,Type,Area " + unit + ",Population,Namesbase,Emblems Shape,Origin\n"; // headers
body.querySelectorAll(":scope > div").forEach(function (el) {
data += el.dataset.id + ",";
@ -869,7 +876,8 @@ function editCultures() {
data += el.dataset.population + ",";
const base = +el.dataset.base;
data += nameBases[base].name + ",";
data += el.dataset.emblems + "\n";
data += el.dataset.emblems + ",";
data += pack.cultures[+el.dataset.id].origin + "\n";
});
const name = getFileName("Cultures") + ".csv";
@ -881,4 +889,54 @@ function editCultures() {
exitCulturesManualAssignment("close");
exitAddCultureMode();
}
async function uploadCulturesData() {
const csv = await Formats.csvParser(this.files[0]);
this.value = "";
const cultures = pack.cultures;
const shapes = Object.keys(COA.shields.types)
.map(type => Object.keys(COA.shields[type]))
.flat();
const populated = pack.cells.pop.map((c, i) => c? i: null).filter(c => c);
cultures.forEach((item) => {if (item.i) item.removed = true});
for (const c of csv.iterator((a, b) => +a[0] > +b[0])) {
let current;
if (+c.id < cultures.length) {
current = cultures[c.id];
const ratio = current.urban / (current.rural + current.urban);
applyPopulationChange(current.rural, current.urban, c.population * (1 - ratio), c.population * ratio, +c.id);
} else {
current = {i: cultures.length, center: ra(populated), area: 0, cells: 0, origin: 0, rural: 0, urban: 0};
cultures.push(current);
}
current.removed = false;
current.name = c.culture;
current.code = abbreviate(
current.name,
cultures.map(c => c.code)
);
current.color = c.color;
current.expansionism = +c.expansionism;
current.origin = +c.origin;
if (cultureTypes.includes(c.type)) current.type = c.type;
else current.type = "Generic";
const shieldShape = c["emblems shape"].toLowerCase();
if (shapes.includes(shieldShape)) current.shield = shieldShape;
else current.shield = "heater";
const nameBaseIndex = nameBases.findIndex(n => n.name == c.namesbase);
current.base = nameBaseIndex === -1 ? 0 : nameBaseIndex;
}
cultures.filter(c => c.removed).forEach(c => removeCultureModel(c.i))
drawCultures();
refreshCulturesEditor();
}
}

View file

@ -169,7 +169,7 @@ function moveBurgToGroup(id, g) {
const icon = document.querySelector("#burgIcons [data-id='" + id + "']");
const anchor = document.querySelector("#anchors [data-id='" + id + "']");
if (!label || !icon) {
ERROR && console.error("Cannot find label or icon elements");
ERROR && console.error(`Cannot find label or icon elements for id ${id}`);
return;
}
@ -190,6 +190,25 @@ function moveBurgToGroup(id, g) {
}
}
function moveAllBurgsToGroup(fromGroup, toGroup) {
const groupToMove = document.querySelector(`#burgIcons #${fromGroup}`);
const burgsToMove = Array.from(groupToMove.children).map(x=>x.dataset.id);
addBurgsGroup(toGroup)
burgsToMove.forEach(x=>moveBurgToGroup(x, toGroup));
}
function addBurgsGroup(group) {
if (document.querySelector(`#burgLabels > #${group}`)) return;
const labelCopy = document.querySelector("#burgLabels > #towns").cloneNode(false);
const iconCopy = document.querySelector("#burgIcons > #towns").cloneNode(false);
const anchorCopy = document.querySelector("#anchors > #towns").cloneNode(false);
// FIXME: using the same id is against the spec!
document.querySelector("#burgLabels").appendChild(labelCopy).id = group;
document.querySelector("#burgIcons").appendChild(iconCopy).id = group;
document.querySelector("#anchors").appendChild(anchorCopy).id = group;
}
function removeBurg(id) {
const label = document.querySelector("#burgLabels [data-id='" + id + "']");
const icon = document.querySelector("#burgIcons [data-id='" + id + "']");

View file

@ -405,7 +405,7 @@ document.querySelectorAll("[data-locked]").forEach(function (e) {
// 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;

View file

@ -1823,6 +1823,7 @@ function getLayer(id) {
if (id === "toggleCompass") return $("#compass");
if (id === "toggleRivers") return $("#rivers");
if (id === "toggleRelief") return $("#terrain");
if (id === "toggleReligions") return $("#relig");
if (id === "toggleCultures") return $("#cults");
if (id === "toggleStates") return $("#regions");
if (id === "toggleProvinces") return $("#provs");

View file

@ -241,8 +241,7 @@ function editMarker(markerI) {
}
function deleteMarker() {
notes = notes.filter(note => note.id !== element.id);
pack.markers = pack.markers.filter(m => m.i !== marker.i);
Markers.deleteMarker(marker.i)
element.remove();
$("#markerEditor").dialog("close");
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();

View file

@ -105,7 +105,8 @@ function showSupporters() {
Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya,www15o,Jan Bundesmann,Angelique Badger,Joshua Xiong,Moist mongol,
Frank Fewkes,jason baldrick,Game Master Pro,Andrew Kircher,Preston Mitchell,Chris Kohut,Emarandzeb,Trentin Bergeron,Damon Gallaty,Pleaseworkforonce,
Jordan,William Markus,Sidr Dim,Alexander Whittaker,The Next Level,Patrick Valverde,Markus Peham,Daniel Cooper,the Beagles of Neorbus,Marley Moule,
Maximilian Schielke,Johnathan Xavier Hutchinson,Ele,Rita,Randy Ross,John Wick,RedSpaz,cameron cannon,Ian Grau-Fay,Kyle Barrett,Charlotte Wiland`;
Maximilian Schielke,Johnathan Xavier Hutchinson,Ele,Rita,Randy Ross,John Wick,RedSpaz,cameron cannon,Ian Grau-Fay,Kyle Barrett,Charlotte Wiland,
David Kaul,E. Jason Davis,Cyberate,Atenfox,Sea Wolf,Holly Loveless,Roekai,Alden Z,angel carrillo,Sam Spoerle`;
const array = supporters
.replace(/(?:\r\n|\r|\n)/g, "")
@ -334,27 +335,35 @@ function copyMapURL() {
.catch(err => tip("Could not copy URL: " + err, false, "error", 5000));
}
function changeCellsDensity(value) {
const convert = v => {
if (v == 1) return 1000;
if (v == 2) return 2000;
if (v == 3) return 5000;
if (v == 4) return 10000;
if (v == 5) return 20000;
if (v == 6) return 30000;
if (v == 7) return 40000;
if (v == 8) return 50000;
if (v == 9) return 60000;
if (v == 10) return 70000;
if (v == 11) return 80000;
if (v == 12) return 90000;
if (v == 13) return 100000;
};
const cells = convert(value);
const cellsDensityMap = {
1: 1000,
2: 2000,
3: 5000,
4: 10000,
5: 20000,
6: 30000,
7: 40000,
8: 50000,
9: 60000,
10: 70000,
11: 80000,
12: 90000,
13: 100000
};
pointsInput.setAttribute("data-cells", cells);
pointsOutput_formatted.value = cells / 1000 + "K";
pointsOutput_formatted.style.color = cells > 50000 ? "#b12117" : cells !== 10000 ? "#dfdf12" : "#053305";
function changeCellsDensity(value) {
const cells = cellsDensityMap[value] || 1000;
pointsInput.dataset.cells = cells;
pointsOutputFormatted.value = getCellsDensityValue(cells);
pointsOutputFormatted.style.color = getCellsDensityColor(cells);
}
function getCellsDensityValue(cells) {
return cells / 1000 + "K";
}
function getCellsDensityColor(cells) {
return cells > 50000 ? "#b12117" : cells !== 10000 ? "#dfdf12" : "#053305";
}
function changeCultureSet() {

View file

@ -574,20 +574,20 @@ addFontMethod.addEventListener("change", function () {
});
styleFontSize.addEventListener("change", function () {
changeFontSize(+this.value);
changeFontSize(getEl(), +this.value);
});
styleFontPlus.addEventListener("click", function () {
const size = +getEl().attr("data-size") + 1;
changeFontSize(Math.min(size, 999));
changeFontSize(getEl(), Math.min(size, 999));
});
styleFontMinus.addEventListener("click", function () {
const size = +getEl().attr("data-size") - 1;
changeFontSize(Math.max(size, 1));
changeFontSize(getEl(), Math.max(size, 1));
});
function changeFontSize(size) {
function changeFontSize(el, size) {
styleFontSize.value = size;
const getSizeOnScale = element => {
@ -600,7 +600,7 @@ function changeFontSize(size) {
};
const scaleSize = getSizeOnScale(styleElementSelect.value);
getEl().attr("data-size", size).attr("font-size", scaleSize);
el.attr("data-size", size).attr("font-size", scaleSize);
if (styleElementSelect.value === "legend") redrawLegend();
}

223
modules/ui/submap.js Normal file
View file

@ -0,0 +1,223 @@
"use strict";
// UI elements for submap generation
window.UISubmap = (function () {
document.getElementById("submapPointsInput").addEventListener("input", function () {
const output = document.getElementById("submapPointsOutputFormatted");
const cells = cellsDensityMap[+this.value] || 1000;
this.dataset.cells = cells;
output.value = getCellsDensityValue(cells);
output.style.color = getCellsDensityColor(cells);
});
function openSubmapMenu() {
$("#submapOptionsDialog").dialog({
title: "Create a submap",
width: "30em",
resizable: false,
position: {my: "center", at: "center", of: "svg"},
buttons: {
Submap: function () {
$(this).dialog("close");
generateSubmap();
},
Cancel: function () {
$(this).dialog("close");
}
}
});
}
function openResampleMenu() {
resetZoom(0);
document.getElementById("submapAngleInput").value = 0;
document.getElementById("submapAngleOutput").value = "0°";
document.getElementById("submapShiftX").value = 0;
document.getElementById("submapShiftY").value = 0;
document.getElementById("submapMirrorH").checked = false;
document.getElementById("submapMirrorV").checked = false;
$("#resampleDialog").dialog({
title: "Resample map",
width: "30em",
resizable: false,
position: {my: "center", at: "center", of: "svg"},
buttons: {
Resample: function () {
$(this).dialog("close");
resampleCurrentMap();
},
Cancel: function () {
$(this).dialog("close");
}
}
});
}
// Resample the whole map to different cell resolution or shape
const resampleCurrentMap = debounce(function () {
WARN && console.warn("Resampling current map");
const cellNumId = +document.getElementById("submapPointsInput").value;
if (!cellsDensityMap[cellNumId]) return console.error("Unknown cell number!");
const angle = (+document.getElementById("submapAngleInput").value / 180) * Math.PI;
const shiftX = +document.getElementById("submapShiftX").value;
const shiftY = +document.getElementById("submapShiftY").value;
const mirrorH = document.getElementById("submapMirrorH").checked;
const mirrorV = document.getElementById("submapMirrorV").checked;
const [cx, cy] = [graphWidth / 2, graphHeight / 2];
const rot = alfa => (x, y) => [(x - cx) * Math.cos(alfa) - (y - cy) * Math.sin(alfa) + cx, (y - cy) * Math.cos(alfa) + (x - cx) * Math.sin(alfa) + cy];
const shift = (dx, dy) => (x, y) => [x + dx, y + dy];
const flipH = (x, y) => [-x + 2 * cx, y];
const flipV = (x, y) => [x, -y + 2 * cy];
const app = (f, g) => (x, y) => f(...g(x, y));
const id = (x, y) => [x, y];
let projection = id;
let inverse = id;
if (angle) [projection, inverse] = [rot(angle), rot(-angle)];
if (shiftX || shiftY) {
projection = app(shift(shiftX, shiftY), projection);
inverse = app(inverse, shift(-shiftX, -shiftY));
}
if (mirrorH) [projection, inverse] = [app(flipH, projection), app(inverse, flipH)];
if (mirrorV) [projection, inverse] = [app(flipV, projection), app(inverse, flipV)];
changeCellsDensity(cellNumId);
startResample({
lockMarkers: false,
lockBurgs: false,
depressRivers: false,
addLakesInDepressions: false,
promoteTowns: false,
smoothHeightMap: false,
rescaleStyles: false,
projection,
inverse
});
}, 1000);
// Create submap from the current map. Submap limits defined by the current window size (canvas viewport)
const generateSubmap = debounce(function () {
WARN && console.warn("Resampling current map");
closeDialogs("#worldConfigurator, #options3d");
const checked = id => Boolean(document.getElementById(id).checked);
// Create projection func from current zoom extents
const [[x0, y0], [x1, y1]] = getViewBoxExtent();
const origScale = scale;
const options = {
lockMarkers: checked("submapLockMarkers"),
lockBurgs: checked("submapLockBurgs"),
depressRivers: checked("submapDepressRivers"),
addLakesInDepressions: checked("submapAddLakeInDepression"),
promoteTowns: checked("submapPromoteTowns"),
rescaleStyles: checked("submapRescaleStyles"),
smoothHeightMap: scale > 2,
inverse: (x, y) => [x / origScale + x0, y / origScale + y0],
projection: (x, y) => [(x - x0) * origScale, (y - y0) * origScale]
};
// converting map position on the planet
const mapSizeOutput = document.getElementById("mapSizeOutput");
const latitudeOutput = document.getElementById("latitudeOutput");
const latN = 90 - ((180 - (mapSizeInput.value / 100) * 180) * latitudeOutput.value) / 100;
const newLatN = latN - ((y0 / graphHeight) * mapSizeOutput.value * 180) / 100;
mapSizeOutput.value /= scale;
latitudeOutput.value = ((90 - newLatN) / (180 - (mapSizeOutput.value / 100) * 180)) * 100;
document.getElementById("mapSizeInput").value = mapSizeOutput.value;
document.getElementById("latitudeInput").value = latitudeOutput.value;
// fix scale
distanceScaleInput.value = distanceScaleOutput.value = rn((distanceScale = distanceScaleOutput.value / scale), 2);
populationRateInput.value = populationRateOutput.value = rn((populationRate = populationRateOutput.value / scale), 2);
customization = 0;
startResample(options);
}, 1000);
async function startResample(options) {
// Do model changes with Submap.resample then do view changes if needed
resetZoom(0);
let oldstate = {
grid: deepCopy(grid),
pack: deepCopy(pack),
notes: deepCopy(notes),
seed,
graphWidth,
graphHeight
};
undraw();
try {
const oldScale = scale;
await Submap.resample(oldstate, options);
if (options.promoteTowns) {
const groupName = "largetowns";
moveAllBurgsToGroup("towns", groupName);
changeRadius(rn(oldScale * 0.8, 2), groupName);
changeFontSize(svg.select(`#labels #${groupName}`), rn(oldScale * 2, 2));
invokeActiveZooming();
}
if (options.rescaleStyles) changeStyles(oldScale);
} catch (error) {
showSubmapErrorHandler(error);
}
oldstate = null; // destroy old state to free memory
restoreLayers();
if (ThreeD.options.isOn) ThreeD.redraw();
if ($("#worldConfigurator").is(":visible")) editWorld();
}
function changeStyles(scale) {
// resize burgIcons
const burgIcons = [...document.getElementById("burgIcons").querySelectorAll("g")];
for (const bi of burgIcons) {
const newRadius = rn(minmax(bi.getAttribute("size") * scale, 0.2, 10), 2);
changeRadius(newRadius, bi.id);
const swAttr = bi.attributes["stroke-width"];
swAttr.value = +swAttr.value * scale;
}
// burglabels
const burgLabels = [...document.getElementById("burgLabels").querySelectorAll("g")];
for (const bl of burgLabels) {
const size = +bl.dataset["size"];
bl.dataset["size"] = Math.max(rn((size + size / scale) / 2, 2), 1) * scale;
}
// emblems
const emblemMod = minmax((scale - 1) * 0.3 + 1, 0.5, 5);
emblemsStateSizeInput.value = minmax(+emblemsStateSizeInput.value * emblemMod, 0.5, 5);
emblemsProvinceSizeInput.value = minmax(+emblemsProvinceSizeInput.value * emblemMod, 0.5, 5);
emblemsBurgSizeInput.value = minmax(+emblemsBurgSizeInput.value * emblemMod, 0.5, 5);
drawEmblems();
}
function showSubmapErrorHandler(error) {
ERROR && console.error(error);
clearMainTip();
alertMessage.innerHTML = `Map resampling failed:
<br>You may retry after clearing stored data or contact us at discord.
<p id="errorBox">${parseError(error)}</p>`;
$("#alert").dialog({
resizable: false,
title: "Resampling error",
width: "32em",
buttons: {
Ok: function () {
$(this).dialog("close");
}
},
position: {my: "center", at: "center", of: "svg"}
});
}
return {openSubmapMenu, openResampleMenu};
})();

View file

@ -72,6 +72,9 @@ toolsContent.addEventListener("click", function (event) {
else if (button === "addRiver") toggleAddRiver();
else if (button === "addRoute") toggleAddRoute();
else if (button === "addMarker") toggleAddMarker();
// click to create a new map buttons
else if (button === "openSubmapMenu") UISubmap.openSubmapMenu();
else if (button === "openResampleMenu") UISubmap.openResampleMenu();
});
function processFeatureRegeneration(event, button) {

View file

@ -103,6 +103,7 @@ function editUnits() {
function restoreDefaultUnits() {
// distanceScale
distanceScale = 3;
document.getElementById("distanceScaleOutput").value = 3;
document.getElementById("distanceScaleInput").value = 3;
unlock("distanceScale");