diff --git a/index.html b/index.html index e05c060ef..b852ad3c7 100644 --- a/index.html +++ b/index.html @@ -2017,11 +2017,14 @@ States - + Provinces Emblems - Religions - Cultures + Religions + Cultures b.i && !b.removed); - if (!burgs.length) return tip("There are no any burgs to generate states. Please create burgs first", false, "error"); - if (burgs.length < statesCount) - tip(`Not enough burgs to generate ${statesCount} states. Will generate only ${burgs.length} states`, false, "warn"); - - // turn all old capitals into towns, except for the capitals of locked states - burgs - .filter(b => b.capital && pack.states.find(s => s.lock && s.capital === b.i) === undefined) - .forEach(b => { - moveBurgToGroup(b.i, "towns"); - b.capital = 0; - }); - - // remove emblems - document.querySelectorAll("[id^=stateCOA]").forEach(el => el.remove()); - document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove()); - emblems.selectAll("use").remove(); - - unfog(); - - if (!statesCount) { - tip(`Cannot generate zero states. Please check the States Number option`, false, "warn"); - pack.states = pack.states.slice(0, 1); // remove all except of neutrals - pack.states[0].diplomacy = []; // clear diplomacy - pack.provinces = [0]; // remove all provinces - pack.cells.state = new Uint16Array(pack.cells.i.length); // reset cells data - borders.selectAll("path").remove(); // remove borders - regions.selectAll("path").remove(); // remove states fill - labels.select("#states").selectAll("text"); // remove state labels - defs.select("#textPaths").selectAll("path[id*='stateLabel']").remove(); // remove state labels paths - - if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click(); - if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click(); - return; - } - - // burg local ids sorted by a bit randomized population. Also ignore burgs of a locked state. - const sortedBurgs = burgs - .filter(b => !pack.states[b.state] || !pack.states[b.state].lock) - .map((b, i) => [b, b.population * Math.random()]) - .sort((a, b) => b[1] - a[1]) - .map(b => b[0]); - const capitalsTree = d3.quadtree(); - - const neutral = pack.states[0].name; // neutrals name - const count = Math.min(statesCount, burgs.length) + 1; // +1 for neutral - let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals - - const states = []; - // Get all the states to restore - let statesToRestore = []; - if (pack.states) { - pack.states.forEach(state => { - if (!state.lock) return; - - statesToRestore.push(state) - }); - } - - d3.range(count).forEach(i => { - if (!i) { - states.push({i, name: neutral}); - return; - } - - // If we still have states to restore from the locks, restore those first and assign them the right ids. - if (statesToRestore.length) { - const [toRestore, ...rest] = statesToRestore; - - toRestore.old_i = toRestore.i; - toRestore.i = i; - states.push(toRestore); - - // Also reassign the state id of all provinces of this state for locked provinces - toRestore.provinces.forEach(id => { - if (!pack.provinces[id]) return; - - pack.provinces[id].state = toRestore.i; - pack.provinces[id].should_restore = true; - }); - - statesToRestore = rest; - - const {x, y} = burgs[toRestore.capital]; - capitalsTree.add([x, y]); - return; - } - - let capital = null; - for (const burg of sortedBurgs) { - const {x, y} = burg; - if (capitalsTree.find(x, y, spacing) === undefined) { - burg.capital = 1; - capital = burg; - capitalsTree.add([x, y]); - moveBurgToGroup(burg.i, "cities"); - break; - } - - spacing = Math.max(spacing - 1, 1); - } - - const culture = capital.culture; - const basename = - capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0); - const name = Names.getState(basename, culture); - const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]); - const type = nomadic - ? "Nomadic" - : pack.cultures[culture].type === "Nomadic" - ? "Generic" - : pack.cultures[culture].type; - const expansionism = rn(Math.random() * powerInput.value + 1, 1); - - const cultureType = pack.cultures[culture].type; - const coa = COA.generate(capital.coa, 0.3, null, cultureType); - coa.shield = capital.coa.shield; - - states.push({ - i, - name, - type, - capital: capital.i, - center: capital.cell, - culture, - expansionism, - coa - }); - }); - - pack.states = states; - } - // define burg coordinates, coa, port status and define details const specifyBurgs = function () { TIME && console.time("specifyBurgs"); @@ -502,12 +364,6 @@ window.BurgsAndStates = (function () { TIME && console.time("expandStates"); const {cells, states, cultures, burgs} = pack; - const prevStates = {}; - if (cells.state) { - cells.state.forEach(function (i, index) { - prevStates[index] = i; - }) - } cells.state = new Uint16Array(cells.i.length); const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p}); const cost = []; @@ -516,17 +372,6 @@ window.BurgsAndStates = (function () { states .filter(s => s.i && !s.removed) .forEach(s => { - if (s.lock) { - Object.entries(prevStates).forEach(function ([index, stateId]) { - if (stateId === s.old_i) { - cells.state[index] = s.i; - cost[index] = neutral; - } - }); - - return; - } - const capitalCell = burgs[s.capital].cell; cells.state[capitalCell] = s.i; const cultureCenter = cultures[s.culture].center; @@ -541,9 +386,9 @@ window.BurgsAndStates = (function () { const {type, culture} = states[s]; cells.c[e].forEach(e => { - if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells - // Do not overwrite cells from a locked state. - if (states[cells.state[e]].lock) return; + const state = states[cells.state[e]]; + if (state.lock) return; // do not overwrite cell of locked states + if (cells.state[e] && e === state.center) return; // do not overwrite capital cells const cultureCost = culture === cells.culture[e] ? -9 : 100; const populationCost = cells.h[e] < 20 ? 0 : cells.s[e] ? Math.max(20 - cells.s[e], 0) : 5000; @@ -608,16 +453,12 @@ window.BurgsAndStates = (function () { for (const i of cells.i) { if (cells.h[i] < 20 || cells.burg[i]) continue; // do not overwrite burgs + if (pack.states[cells.state[i]]?.lock) continue; // do not overwrite cells of locks states if (cells.c[i].some(c => burgs[cells.burg[c]].capital)) continue; // do not overwrite near capital - if (pack.states[cells.state[i]]?.lock) continue; // Do not overwrite cells of locks states const neibs = cells.c[i].filter(c => cells.h[c] >= 20); - const adversaries = neibs.filter( - c => !pack.states[cells.state[c]]?.lock && cells.state[c] !== cells.state[i] - ); + const adversaries = neibs.filter(c => !pack.states[cells.state[c]]?.lock && cells.state[c] !== cells.state[i]); if (adversaries.length < 2) continue; - const buddies = neibs.filter( - c => !pack.states[cells.state[c]]?.lock && cells.state[c] === cells.state[i] - ); + const buddies = neibs.filter(c => !pack.states[cells.state[c]]?.lock && cells.state[c] === cells.state[i]); if (buddies.length > 2) continue; if (adversaries.length <= buddies.length) continue; cells.state[i] = cells.state[adversaries[0]]; @@ -763,18 +604,22 @@ window.BurgsAndStates = (function () { if (!list) { // remove all labels and textpaths - g.selectAll("text").filter((_, i) => { - const id = g.select(`:nth-child(${i + 1})`).node()?.id; - if (!id) return true; - - return !pack.states.some(s => s.lock && `${s.old_i}` === id.substring(10)); - }).remove(); - t.selectAll("path[id*='stateLabel']").filter((_, i) => { - const id = t.select(`:nth-child(${i + 1})`).node()?.id; - if (!id) return true; - - return !pack.states.some(s => s.lock && `${s.old_i}` === id.substring(19)); - }).remove(); + g.selectAll("text") + .filter((_, i) => { + const id = g.select(`:nth-child(${i + 1})`).node()?.id; + if (!id) return true; + + return !pack.states.some(s => s.lock && `${s.old_i}` === id.substring(10)); + }) + .remove(); + t.selectAll("path[id*='stateLabel']") + .filter((_, i) => { + const id = t.select(`:nth-child(${i + 1})`).node()?.id; + if (!id) return true; + + return !pack.states.some(s => s.lock && `${s.old_i}` === id.substring(19)); + }) + .remove(); } pack.states.forEach(s => { @@ -787,11 +632,8 @@ window.BurgsAndStates = (function () { const labelNode = g.select(`#stateLabel${s.old_i}`); const textNode = t.select(`#textPath_stateLabel${s.old_i}`); - labelNode - .attr('id', `stateLabel${s.i}`) - .select("textPath") - .attr("xlink:href", `#textPath_stateLabel${s.i}`); - textNode.attr('id', `textPath_stateLabel${s.i}`); + labelNode.attr("id", `stateLabel${s.i}`).select("textPath").attr("xlink:href", `#textPath_stateLabel${s.i}`); + textNode.attr("id", `textPath_stateLabel${s.i}`); }); const example = g.append("text").attr("x", 0).attr("x", 0).text("Average"); @@ -1332,25 +1174,20 @@ window.BurgsAndStates = (function () { return adjName ? `${getAdjective(s.name)} ${s.formName}` : `${s.formName} of ${s.name}`; }; - const generateProvinces = function ( - regenerate = false, - ignoreLockedStates = false - ) { + const generateProvinces = function (regenerate = false, ignoreLockedStates = false) { TIME && console.time("generateProvinces"); const localSeed = regenerate ? generateSeed() : seed; Math.random = aleaPRNG(localSeed); const {cells, states, burgs} = pack; - const provincesToRestore = pack.provinces ? - pack.provinces.filter(p => p.lock || p.should_restore) - : []; + const provincesToRestore = pack.provinces ? pack.provinces.filter(p => p.lock || p.should_restore) : []; const provinces = (pack.provinces = [0].concat(...provincesToRestore)); const prevProvinces = {}; if (cells.province) { cells.province.forEach((i, index) => { prevProvinces[index] = i; - }) + }); } cells.province = new Uint16Array(cells.i.length); // cell state const percentage = +provincesInput.value; @@ -1380,8 +1217,9 @@ window.BurgsAndStates = (function () { if (!s.i || s.removed) return; const stateBurgs = burgs // Filter for burgs of this state that haven't been removed and that are not in a locked province. - .filter(b => - b.state === s.i && !b.removed && provincesToRestore.find(p => prevProvinces[b.cell] === p.i) === undefined + .filter( + b => + b.state === s.i && !b.removed && provincesToRestore.find(p => prevProvinces[b.cell] === p.i) === undefined ) .sort((a, b) => b.population * gauss(1, 0.2, 0.5, 1.5, 3) - a.population) .sort((a, b) => b.capital - a.capital); @@ -1450,16 +1288,15 @@ window.BurgsAndStates = (function () { // Do not overwrite cells from a locked state or province. if ( (provinces[cells.province[e]] && provinces[cells.province[e]].lock) || - ( - // For finding if the state is locked, first make sure we care about that - // then find the province, the state for the province, and if both are defined, - // check the lock. - !ignoreLockedStates && + // For finding if the state is locked, first make sure we care about that + // then find the province, the state for the province, and if both are defined, + // check the lock. + (!ignoreLockedStates && provinces[cells.province[e]] && states[provinces[cells.province[e]].state] && - states[provinces[cells.province[e]].state].lock - ) - ) return; + states[provinces[cells.province[e]].state].lock) + ) + return; const land = cells.h[e] >= 20; if (!land && !cells.t[e]) return; // cannot pass deep ocean @@ -1480,17 +1317,16 @@ window.BurgsAndStates = (function () { for (const i of cells.i) { if (cells.burg[i]) continue; // do not overwrite burgs // Do not process any locked provinces or states, if we care about the latter - if ( - pack.provinces[cells.province[i]].lock || - (!ignoreLockedStates && pack.states[cells.state[i]].lock) - ) continue; + if (pack.provinces[cells.province[i]].lock || (!ignoreLockedStates && pack.states[cells.state[i]].lock)) continue; // Find neighbors, but ignore any cells from locked states or provinces - const neibs = cells.c[i].filter( - c => - (ignoreLockedStates || !pack.states[cells.state[c]].lock) && - !pack.provinces[cells.province[c]].lock && - cells.state[c] === cells.state[i] - ).map(c => cells.province[c]); + const neibs = cells.c[i] + .filter( + c => + (ignoreLockedStates || !pack.states[cells.state[c]].lock) && + !pack.provinces[cells.province[c]].lock && + cells.state[c] === cells.state[i] + ) + .map(c => cells.province[c]); const adversaries = neibs.filter(c => c !== cells.province[i]); if (adversaries.length < 2) continue; const buddies = neibs.filter(c => c === cells.province[i]).length; @@ -1613,7 +1449,6 @@ window.BurgsAndStates = (function () { return { generate, - regenerateStates, expandStates, normalizeStates, assignColors, diff --git a/modules/dynamic/editors/states-editor.js b/modules/dynamic/editors/states-editor.js index 85705b9d5..773d421dc 100644 --- a/modules/dynamic/editors/states-editor.js +++ b/modules/dynamic/editors/states-editor.js @@ -153,7 +153,8 @@ function addListeners() { else if (classList.contains("statePopulation")) changePopulation(stateId); else if (classList.contains("icon-pin")) toggleFog(stateId, classList); else if (classList.contains("icon-trash-empty")) stateRemovePrompt(stateId); - else if (classList.contains("icon-lock") || classList.contains("icon-lock-open")) updateLockStatus(stateId, classList); + else if (classList.contains("icon-lock") || classList.contains("icon-lock-open")) + updateLockStatus(stateId, classList); }); $body.on("input", function (ev) { @@ -289,7 +290,9 @@ function statesEditorAddLines() { ${s.cells} - + `; } diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 46e3195f3..532c0da2b 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -1,4 +1,5 @@ "use strict"; + // module to control the Tools options (click to edit, to re-geenerate, tp add) toolsContent.addEventListener("click", function (event) { @@ -138,7 +139,7 @@ function recalculatePopulation() { } function regenerateStates() { - BurgsAndStates.regenerateStates(); + recreateStates(); BurgsAndStates.expandStates(); BurgsAndStates.normalizeStates(); BurgsAndStates.collectStatistics(); @@ -160,6 +161,137 @@ function regenerateStates() { if (document.getElementById("militaryOverviewRefresh")?.offsetParent) militaryOverviewRefresh.click(); } +function recreateStates() { + const localSeed = generateSeed(); + Math.random = aleaPRNG(localSeed); + + const statesCount = +regionsOutput.value; + const validBurgs = pack.burgs.filter(b => b.i && !b.removed); + if (!validBurgs.length) + return tip("There are no any burgs to generate states. Please create burgs first", false, "error"); + if (validBurgs.length < statesCount) + tip( + `Not enough burgs to generate ${statesCount} states. Will generate only ${validBurgs.length} states`, + false, + "warn" + ); + + const lockedStates = pack.states.filter(s => s.i && !s.removed && s.lock); + const lockedStatesIds = lockedStates.map(s => s.i); + const lockedStatesCapitals = lockedStates.map(s => s.capital); + + // turn all old capitals into towns, except for the capitals of locked states + for (const burg of validBurgs) { + if (!burg.capital) continue; + if (lockedStatesCapitals.includes(burg.i)) continue; + + moveBurgToGroup(burg.i, "towns"); + burg.capital = 0; + } + + // remove emblems + document.querySelectorAll("[id^=stateCOA]").forEach(el => el.remove()); + document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove()); + emblems.selectAll("use").remove(); + + unfog(); + + if (!statesCount) { + tip(`Cannot generate zero states. Please check the States Number option`, false, "warn"); + pack.states = pack.states.slice(0, 1); // remove all except of neutrals + pack.states[0].diplomacy = []; // clear diplomacy + pack.provinces = [0]; // remove all provinces + pack.cells.state = new Uint16Array(pack.cells.i.length); // reset cells data + + borders.selectAll("path").remove(); // remove borders + regions.selectAll("path").remove(); // remove states fill + labels.select("#states").selectAll("text"); // remove state labels + defs.select("#textPaths").selectAll("path[id*='stateLabel']").remove(); // remove state labels paths + + if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click(); + if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click(); + return; + } + + // burg local ids sorted by a bit randomized population. Also ignore burgs of a locked state + const sortedBurgs = validBurgs + .filter(b => !lockedStatesIds.includes(b.state)) + .map(b => [b, b.population * Math.random()]) + .sort((a, b) => b[1] - a[1]) + .map(b => b[0]); + + const count = Math.min(statesCount, validBurgs.length) + 1; // +1 for neutral + let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals + + const capitalsTree = d3.quadtree(); + const isTooClose = (x, y, spacing) => Boolean(capitalsTree.find(x, y, spacing)); + + const newStates = [{i: 0, name: pack.states[0].name}]; + + // restore locked states + lockedStates.forEach(s => { + const newId = newStates.length; + + s.provinces.forEach(id => { + if (!pack.provinces[id] || !pack.provinces[id].removed) return; + pack.provinces[id].state = newId; + // pack.provinces[id].should_restore = true; + }); + + for (const i of pack.cells.i) { + const stateId = pack.cells.state[i]; + if (stateId === s.i) pack.cells.state[i] = newId; + } + + const {x, y} = validBurgs[s.capital]; + capitalsTree.add([x, y]); + + s.i = newId; + newStates.push(s); + }); + + for (let i = newStates.length; i < count; i++) { + let capital = null; + + for (const burg of sortedBurgs) { + const {x, y} = burg; + if (!isTooClose(x, y, spacing)) { + burg.capital = 1; + capital = burg; + capitalsTree.add([x, y]); + moveBurgToGroup(burg.i, "cities"); + break; + } + + spacing = Math.max(spacing - 1, 1); + } + + // all burgs are too close, should not happen in normal conditions + if (!capital) break; + + // create new state + const culture = capital.culture; + const basename = + capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0); + const name = Names.getState(basename, culture); + const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]); + const type = nomadic + ? "Nomadic" + : pack.cultures[culture].type === "Nomadic" + ? "Generic" + : pack.cultures[culture].type; + const expansionism = rn(Math.random() * powerInput.value + 1, 1); + + const cultureType = pack.cultures[culture].type; + const coa = COA.generate(capital.coa, 0.3, null, cultureType); + coa.shield = capital.coa.shield; + + newStates.push({i, name, type, capital: capital.i, center: capital.cell, culture, expansionism, coa}); + } + + pack.states = newStates; +} + function regenerateProvinces() { unfog();