diff --git a/index.html b/index.html index 322c806b..2403f908 100644 --- a/index.html +++ b/index.html @@ -5348,16 +5348,21 @@ - - - - - + + + + + + + + + + Order Name Population Percentile diff --git a/main.js b/main.js index 0bb23cef..af49ecd3 100644 --- a/main.js +++ b/main.js @@ -171,7 +171,8 @@ let options = { // create groups for each burg type { - for (const {name} of options.burgs.groups) { + const sortedGroups = [...options.burgs.groups].sort((a, b) => a.order - b.order); + for (const {name} of sortedGroups) { burgIcons.append("g").attr("id", name); burgLabels.append("g").attr("id", name); } @@ -661,14 +662,18 @@ async function generate(options) { rankCells(); Cultures.generate(); Cultures.expand(); + Burgs.generate(); States.generate(); Routes.generate(); Religions.generate(); + + Burgs.specify(); + States.collectStatistics(); States.defineStateForms(); + Provinces.generate(); Provinces.getPoles(); - Burgs.specify(); Rivers.specify(); Features.specify(); @@ -695,7 +700,7 @@ async function generate(options) { title: "Generation error", width: "32em", buttons: { - "Cleanup data": cleanupData, + "Cleanup data": () => cleanupData(), Regenerate: function () { regenerateMap("generation error"); $(this).dialog("close"); diff --git a/modules/burgs-generator.js b/modules/burgs-generator.js index 6de59243..f27fe286 100644 --- a/modules/burgs-generator.js +++ b/modules/burgs-generator.js @@ -261,12 +261,13 @@ window.Burgs = (() => { } const getDefaultGroups = () => [ - {name: "capitals", active: true, features: {capital: true}, preview: "watabou-city-generator"}, - {name: "cities", active: true, percentile: 90, min: 5, preview: "watabou-city-generator"}, + {name: "capitals", active: true, order: 9, features: {capital: true}, preview: "watabou-city-generator"}, + {name: "cities", active: true, order: 8, percentile: 90, min: 5, preview: "watabou-city-generator"}, { name: "forts", active: true, features: {citadel: true, walls: false, plaza: false, port: false}, + order: 6, max: 1, preview: null }, @@ -274,6 +275,7 @@ window.Burgs = (() => { name: "monasteries", active: true, features: {temple: true, walls: false, plaza: false, port: false}, + order: 5, max: 0.8, preview: null }, @@ -281,6 +283,7 @@ window.Burgs = (() => { name: "caravanserais", active: true, features: {port: false, plaza: true}, + order: 4, max: 0.8, biomes: [1, 2, 3], preview: null @@ -288,6 +291,7 @@ window.Burgs = (() => { { name: "trading_posts", active: true, + order: 3, features: {plaza: true}, max: 0.8, biomes: [5, 6, 7, 8, 9, 10, 11, 12], @@ -296,6 +300,7 @@ window.Burgs = (() => { { name: "villages", active: true, + order: 2, min: 0.1, max: 2, features: {walls: false}, @@ -304,11 +309,12 @@ window.Burgs = (() => { { name: "hamlets", active: true, + order: 1, features: {walls: false, plaza: false}, max: 0.1, preview: "watabou-village-generator" }, - {name: "towns", active: true, isDefault: true, preview: "watabou-city-generator"} + {name: "towns", active: true, order: 7, isDefault: true, preview: "watabou-city-generator"} ]; function defineGroup(burg, populations) { diff --git a/modules/dynamic/editors/states-editor.js b/modules/dynamic/editors/states-editor.js index 8118fc1b..91845a68 100644 --- a/modules/dynamic/editors/states-editor.js +++ b/modules/dynamic/editors/states-editor.js @@ -1253,6 +1253,8 @@ function addState() { coa, pole }); + + States.findNeighbors(); States.collectStatistics(); States.defineStateForms([newState]); adjustProvinces([cells.province[center]]); diff --git a/modules/states-generator.js b/modules/states-generator.js index 7811cdf0..6facc274 100644 --- a/modules/states-generator.js +++ b/modules/states-generator.js @@ -4,14 +4,11 @@ window.States = (() => { const generate = () => { TIME && console.time("generateStates"); pack.states = createStates(); - expandStates(); - normalizeStates(); + normalize(); getPoles(); - - collectStatistics(); + findNeighbors(); assignColors(); - generateCampaigns(); generateDiplomacy(); @@ -144,7 +141,7 @@ window.States = (() => { TIME && console.timeEnd("expandStates"); }; - const normalizeStates = () => { + const normalize = () => { TIME && console.time("normalizeStates"); const {cells, burgs} = pack; @@ -174,14 +171,11 @@ window.States = (() => { }); }; - // calculate states data like area, population etc. - const collectStatistics = () => { - TIME && console.time("collectStatistics"); + const findNeighbors = () => { const {cells, states} = pack; states.forEach(s => { if (s.removed) return; - s.cells = s.area = s.burgs = s.rural = s.urban = 0; s.neighbors = new Set(); }); @@ -189,28 +183,16 @@ window.States = (() => { if (cells.h[i] < 20) continue; const s = cells.state[i]; - // check for neighboring states cells.c[i] .filter(c => cells.h[c] >= 20 && cells.state[c] !== s) .forEach(c => states[s].neighbors.add(cells.state[c])); - - // collect stats - states[s].cells += 1; - states[s].area += cells.area[i]; - states[s].rural += cells.pop[i]; - if (cells.burg[i]) { - states[s].urban += pack.burgs[cells.burg[i]].population; - states[s].burgs++; - } } // convert neighbors Set object into array states.forEach(s => { - if (!s.neighbors) return; + if (!s.neighbors || s.removed) return; s.neighbors = Array.from(s.neighbors); }); - - TIME && console.timeEnd("collectStatistics"); }; const assignColors = () => { @@ -238,6 +220,33 @@ window.States = (() => { TIME && console.timeEnd("assignColors"); }; + // calculate states data like area, population etc. + const collectStatistics = () => { + TIME && console.time("collectStatistics"); + const {cells, states} = pack; + + states.forEach(s => { + if (s.removed) return; + s.cells = s.area = s.burgs = s.rural = s.urban = 0; + }); + + for (const i of cells.i) { + if (cells.h[i] < 20) continue; + const s = cells.state[i]; + + // collect stats + states[s].cells += 1; + states[s].area += cells.area[i]; + states[s].rural += cells.pop[i]; + if (cells.burg[i]) { + states[s].urban += pack.burgs[cells.burg[i]].population; + states[s].burgs++; + } + } + + TIME && console.timeEnd("collectStatistics"); + }; + const wars = { War: 6, Conflict: 2, @@ -614,8 +623,9 @@ window.States = (() => { return { generate, expandStates, - normalizeStates, + normalize, getPoles, + findNeighbors, assignColors, collectStatistics, generateCampaign, diff --git a/modules/ui/burg-group-editor.js b/modules/ui/burg-group-editor.js index 15222fa9..01252256 100644 --- a/modules/ui/burg-group-editor.js +++ b/modules/ui/burg-group-editor.js @@ -31,22 +31,23 @@ function editBurgGroups() { // add listeners byId("burgGroupsForm").on("change", validateForm).on("submit", submitForm); byId("burgGroupsBody").on("click", ev => { - const line = ev.target.closest("tr"); - if (line && ev.target.classList.contains("removeGroup")) { - const lines = byId("burgGroupsBody").children; - if (lines.length < 2) return tip("At least one group should be defined", false, "error"); + const el = ev.target; + const line = el.closest("tr"); + if (!line) return; - confirmationDialog({ - title: this.dataset.tip, - message: - "Are you sure you want to remove the group?
This WON'T change the burgs unless the changes are applied", - confirm: "Remove", - onConfirm: () => { - line.remove(); - validateForm(); - } - }); + if (el.name === "biomes") { + const biomes = Array(biomesData.i.length) + .fill(null) + .map((_, i) => ({i, name: biomesData.name[i], color: biomesData.color[i]})); + return selectLimitation(el, biomes); } + if (el.name === "states") return selectLimitation(el, pack.states); + if (el.name === "cultures") return selectLimitation(el, pack.cultures); + if (el.name === "religions") return selectLimitation(el, pack.religions); + if (el.name === "features") return selectFeaturesLimitation(el); + if (el.name === "up") return line.parentNode.insertBefore(line, line.previousElementSibling); + if (el.name === "down") return line.parentNode.insertBefore(line.nextElementSibling, line); + if (el.name === "remove") return removeLine(line); }); function addLines() { @@ -58,22 +59,183 @@ function editBurgGroups() { const count = pack.burgs.filter(burg => !burg.removed && burg.group === group.name).length; // prettier-ignore return /* html */ ` + - - - - - + + + + + + + + + + + + + + + + + + + + ${count} - + + + `; } + function selectLimitation(el, data) { + const value = el.previousElementSibling.value; + const initial = value ? value.split(",").map(v => +v) : []; + + const filtered = data.filter(datum => datum.i && !datum.removed); + const lines = filtered.map( + ({i, name, fullName, color}) => /* html */ ` + + + + + + + + + ` + ); + + alertMessage.innerHTML = /* html */ `Limit group by ${el.name}: + + + ${lines.join("")} + +
`; + + $("#alert").dialog({ + width: fitContent(), + title: "Limit group", + buttons: { + Invert: function () { + alertMessage.querySelectorAll("input").forEach(el => (el.checked = !el.checked)); + }, + Apply: function () { + const inputs = Array.from(alertMessage.querySelectorAll("input")); + const selected = inputs.reduce((acc, input) => { + if (input.checked) acc.push(input.dataset.i); + return acc; + }, []); + + if (!selected.length) return tip("Select at least one element", false, "error"); + + const allAreSelected = selected.length === inputs.length; + el.previousElementSibling.value = allAreSelected ? "" : selected.join(","); + el.innerHTML = allAreSelected ? "all" : "some"; + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } + } + }); + } + + function selectFeaturesLimitation(el) { + const value = el.previousElementSibling.value; + const initial = value ? JSON.parse(value) : {}; + + const features = [ + {name: "capital", icon: "icon-star"}, + {name: "port", icon: "icon-anchor"}, + {name: "citadel", icon: "icon-chess-rook"}, + {name: "walls", icon: "icon-fort-awesome"}, + {name: "plaza", icon: "icon-store"}, + {name: "temple", icon: "icon-chess-bishop"}, + {name: "shanty", icon: "icon-campground"} + ]; + + const lines = features.map( + // prettier-ignore + ({name, icon}) => /* html */ ` + + + + ${name} + + + + + + + + + + + ` + ); + + alertMessage.innerHTML = /* html */ ` +
+ + + + + + + + + ${lines.join("")} + +
FeaturesTrueFalseAny
+
`; + + $("#alert").dialog({ + width: fitContent(), + title: "Limit group by features", + buttons: { + Apply: function () { + const form = byId("featuresLimitationForm"); + const values = features.reduce((acc, {name}) => { + const value = form[name].value; + if (value !== "undefined") acc[name] = value === "true"; + return acc; + }, {}); + + el.previousElementSibling.value = JSON.stringify(values); + el.innerHTML = Object.keys(values).length ? "some" : "any"; + $(this).dialog("close"); + }, + Cancel: function () { + $(this).dialog("close"); + } + } + }); + } + + function removeLine(line) { + const lines = byId("burgGroupsBody").children; + if (lines.length < 2) return tip("At least one group should be defined", false, "error"); + + confirmationDialog({ + title: this.dataset.tip, + message: + "Are you sure you want to remove the group?
This WON'T change the burgs unless the changes are applied", + confirm: "Remove", + onConfirm: () => { + line.remove(); + validateForm(); + } + }); + } + function validateForm() { const form = byId("burgGroupsForm"); @@ -116,6 +278,13 @@ function editBurgGroups() { function parseInput(input) { if (input.name === "name") return sanitizeId(input.value); + if (input.name === "features") { + const isValid = JSON.isValid(input.value); + const parsed = isValid ? JSON.parse(input.value) : {}; + if (Object.keys(parsed).length) return parsed; + return null; + } + if (input.type === "hidden") return input.value || null; if (input.type === "radio") return input.checked; if (input.type === "checkbox") return input.checked; if (input.type === "number") { @@ -123,7 +292,7 @@ function editBurgGroups() { if (value === 0 || isNaN(value)) return null; return value; } - return input.value; + return input.value || null; } options.burgs.groups = lines.map(line => { diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index 2c3689cb..192873ea 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -238,8 +238,8 @@ function editHeightmap(options) { } Biomes.define(); - rankCells(); + rankCells(); Cultures.generate(); Cultures.expand(); @@ -247,10 +247,13 @@ function editHeightmap(options) { States.generate(); Routes.generate(); Religions.generate(); + + Burgs.specify(); + States.collectStatistics(); States.defineStateForms(); + Provinces.generate(); Provinces.getPoles(); - Burgs.specify(); Rivers.specify(); Features.specify(); diff --git a/modules/ui/military-overview.js b/modules/ui/military-overview.js index e300ed86..dd83e066 100644 --- a/modules/ui/military-overview.js +++ b/modules/ui/military-overview.js @@ -368,14 +368,17 @@ function overviewMilitary() { const filtered = data.filter(datum => datum.i && !datum.removed); const lines = filtered.map( - ({i, name, fullName, color}) => - ` - - - ` + ({i, name, fullName, color}) => /* html */ ` + + + + + + + ` ); + alertMessage.innerHTML = /* html */ `Limit unit by ${type}: @@ -385,7 +388,7 @@ function overviewMilitary() { $("#alert").dialog({ width: fitContent(), - title: `Limit unit`, + title: "Limit unit", buttons: { Invert: function () { alertMessage.querySelectorAll("input").forEach(el => (el.checked = !el.checked)); diff --git a/modules/ui/provinces-editor.js b/modules/ui/provinces-editor.js index 531d6728..76e103cd 100644 --- a/modules/ui/provinces-editor.js +++ b/modules/ui/provinces-editor.js @@ -371,6 +371,7 @@ function editProvinces() { layerIsOn("toggleStates") ? drawStates() : toggleStates(); layerIsOn("toggleBorders") ? drawBorders() : toggleBorders(); + States.findNeighbors(); States.collectStatistics(); States.defineStateForms(newStates); drawStateLabels(allStates); diff --git a/modules/ui/tools.js b/modules/ui/tools.js index d7ee828e..d77be8ec 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -155,13 +155,15 @@ function regenerateStates() { pack.states = newStates; States.expandStates(); - States.normalizeStates(); + States.normalize(); States.getPoles(); + States.findNeighbors(); States.collectStatistics(); States.assignColors(); States.generateCampaigns(); States.generateDiplomacy(); States.defineStateForms(); + Provinces.generate(true); Provinces.getPoles(); diff --git a/utils/stringUtils.js b/utils/stringUtils.js index e9c4ef7a..ed1bd7fa 100644 --- a/utils/stringUtils.js +++ b/utils/stringUtils.js @@ -51,10 +51,10 @@ function parseTransform(string) { JSON.isValid = str => { try { JSON.parse(str); + return true; } catch (e) { return false; } - return true; }; function sanitizeId(string) {