mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-18 10:01:23 +01:00
Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into submap-refactoring
This commit is contained in:
commit
fb48f5d57a
18 changed files with 511 additions and 73 deletions
36
index.html
36
index.html
|
|
@ -4964,7 +4964,18 @@
|
||||||
>Model:
|
>Model:
|
||||||
<select id="aiGeneratorModel"></select>
|
<select id="aiGeneratorModel"></select>
|
||||||
</label>
|
</label>
|
||||||
|
<label for="aiGeneratorTemperature"
|
||||||
|
>Temperature:
|
||||||
|
<input id="aiGeneratorTemperature" type="number" min="0" max="2" placeholder="1.2" class="icon-key" />
|
||||||
|
<a
|
||||||
|
href="https://platform.openai.com/docs/api-reference/chat/create#chat-create-temperature"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
class="icon-help-circled"
|
||||||
|
style="text-decoration: none"
|
||||||
|
data-tip="Between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic."
|
||||||
|
></a>
|
||||||
|
</label>
|
||||||
<label for="aiGeneratorKey"
|
<label for="aiGeneratorKey"
|
||||||
>Key:
|
>Key:
|
||||||
<input id="aiGeneratorKey" placeholder="Enter OpenAI API key" class="icon-key" />
|
<input id="aiGeneratorKey" placeholder="Enter OpenAI API key" class="icon-key" />
|
||||||
|
|
@ -8033,7 +8044,6 @@
|
||||||
<script src="libs/jquery-ui.min.js"></script>
|
<script src="libs/jquery-ui.min.js"></script>
|
||||||
<script src="versioning.js"></script>
|
<script src="versioning.js"></script>
|
||||||
<script src="libs/d3.min.js"></script>
|
<script src="libs/d3.min.js"></script>
|
||||||
<script src="libs/priority-queue.min.js"></script>
|
|
||||||
<script src="libs/flatqueue.js"></script>
|
<script src="libs/flatqueue.js"></script>
|
||||||
<script src="libs/delaunator.min.js"></script>
|
<script src="libs/delaunator.min.js"></script>
|
||||||
<script src="libs/indexedDB.js?v=1.99.00"></script>
|
<script src="libs/indexedDB.js?v=1.99.00"></script>
|
||||||
|
|
@ -8048,7 +8058,7 @@
|
||||||
<script src="utils/numberUtils.js?v=1.99.00"></script>
|
<script src="utils/numberUtils.js?v=1.99.00"></script>
|
||||||
<script src="utils/polyfills.js?v=1.99.00"></script>
|
<script src="utils/polyfills.js?v=1.99.00"></script>
|
||||||
<script src="utils/probabilityUtils.js?v=1.99.05"></script>
|
<script src="utils/probabilityUtils.js?v=1.99.05"></script>
|
||||||
<script src="utils/stringUtils.js?v=1.99.00"></script>
|
<script src="utils/stringUtils.js?v=1.105.19"></script>
|
||||||
<script src="utils/languageUtils.js?v=1.99.00"></script>
|
<script src="utils/languageUtils.js?v=1.99.00"></script>
|
||||||
<script src="utils/unitUtils.js?v=1.99.00"></script>
|
<script src="utils/unitUtils.js?v=1.99.00"></script>
|
||||||
<script src="utils/pathUtils.js?v=1.105.8"></script>
|
<script src="utils/pathUtils.js?v=1.105.8"></script>
|
||||||
|
|
@ -8064,14 +8074,14 @@
|
||||||
<script src="modules/lakes.js?v=1.99.00"></script>
|
<script src="modules/lakes.js?v=1.99.00"></script>
|
||||||
<script src="modules/biomes.js?v=1.99.00"></script>
|
<script src="modules/biomes.js?v=1.99.00"></script>
|
||||||
<script src="modules/names-generator.js?v=1.105.11"></script>
|
<script src="modules/names-generator.js?v=1.105.11"></script>
|
||||||
<script src="modules/cultures-generator.js?v=1.105.19"></script>
|
<script src="modules/cultures-generator.js?v=1.105.21"></script>
|
||||||
<script src="modules/burgs-and-states.js?v=1.105.7"></script>
|
<script src="modules/burgs-and-states.js?v=1.105.21"></script>
|
||||||
<script src="modules/provinces-generator.js?v=1.104.0"></script>
|
<script src="modules/provinces-generator.js?v=1.105.21"></script>
|
||||||
<script src="modules/routes-generator.js?v=1.104.10"></script>
|
<script src="modules/routes-generator.js?v=1.104.10"></script>
|
||||||
<script src="modules/religions-generator.js?v=1.99.05"></script>
|
<script src="modules/religions-generator.js?v=1.105.21"></script>
|
||||||
<script src="modules/military-generator.js?v=1.104.0"></script>
|
<script src="modules/military-generator.js?v=1.104.0"></script>
|
||||||
<script src="modules/markers-generator.js?v=1.104.0"></script>
|
<script src="modules/markers-generator.js?v=1.104.0"></script>
|
||||||
<script src="modules/zones-generator.js?v=1.104.0"></script>
|
<script src="modules/zones-generator.js?v=1.105.21"></script>
|
||||||
<script src="modules/coa-generator.js?v=1.99.00"></script>
|
<script src="modules/coa-generator.js?v=1.99.00"></script>
|
||||||
<script src="modules/resample.js?v=1.105.13"></script>
|
<script src="modules/resample.js?v=1.105.13"></script>
|
||||||
<script src="libs/alea.min.js?v1.105.0"></script>
|
<script src="libs/alea.min.js?v1.105.0"></script>
|
||||||
|
|
@ -8110,10 +8120,10 @@
|
||||||
<script defer src="modules/ui/burg-editor.js?v=1.102.00"></script>
|
<script defer src="modules/ui/burg-editor.js?v=1.102.00"></script>
|
||||||
<script defer src="modules/ui/units-editor.js?v=1.104.0"></script>
|
<script defer src="modules/ui/units-editor.js?v=1.104.0"></script>
|
||||||
<script defer src="modules/ui/notes-editor.js?v=1.99.06"></script>
|
<script defer src="modules/ui/notes-editor.js?v=1.99.06"></script>
|
||||||
<script defer src="modules/ui/ai-generator.js?v=1.99.09"></script>
|
<script defer src="modules/ui/ai-generator.js?v=1.105.22"></script>
|
||||||
<script defer src="modules/ui/diplomacy-editor.js?v=1.99.00"></script>
|
<script defer src="modules/ui/diplomacy-editor.js?v=1.99.00"></script>
|
||||||
<script defer src="modules/ui/zones-editor.js?v=1.105.18"></script>
|
<script defer src="modules/ui/zones-editor.js?v=1.105.20"></script>
|
||||||
<script defer src="modules/ui/burgs-overview.js?v=1.105.19"></script>
|
<script defer src="modules/ui/burgs-overview.js?v=1.105.15"></script>
|
||||||
<script defer src="modules/ui/routes-overview.js?v=1.104.3"></script>
|
<script defer src="modules/ui/routes-overview.js?v=1.104.3"></script>
|
||||||
<script defer src="modules/ui/rivers-overview.js?v=1.99.00"></script>
|
<script defer src="modules/ui/rivers-overview.js?v=1.99.00"></script>
|
||||||
<script defer src="modules/ui/military-overview.js?v=1.99.00"></script>
|
<script defer src="modules/ui/military-overview.js?v=1.99.00"></script>
|
||||||
|
|
@ -8131,8 +8141,8 @@
|
||||||
<script defer src="libs/rgbquant.min.js"></script>
|
<script defer src="libs/rgbquant.min.js"></script>
|
||||||
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
||||||
<script defer src="modules/io/save.js?v=1.100.00"></script>
|
<script defer src="modules/io/save.js?v=1.100.00"></script>
|
||||||
<script defer src="modules/io/load.js?v=1.105.19"></script>
|
<script defer src="modules/io/load.js?v=1.105.17"></script>
|
||||||
<script defer src="modules/io/cloud.js?v=1.99.00"></script>
|
<script defer src="modules/io/cloud.js?v=1.105.19"></script>
|
||||||
<script defer src="modules/io/export.js?v=1.100.00"></script>
|
<script defer src="modules/io/export.js?v=1.100.00"></script>
|
||||||
|
|
||||||
<script defer src="modules/renderers/draw-features.js?v=1.105.19"></script>
|
<script defer src="modules/renderers/draw-features.js?v=1.105.19"></script>
|
||||||
|
|
|
||||||
1
libs/priority-queue.min.js
vendored
1
libs/priority-queue.min.js
vendored
File diff suppressed because one or more lines are too long
4
main.js
4
main.js
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
// set debug options
|
// set debug options
|
||||||
const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
|
const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
|
||||||
const DEBUG = localStorage.getItem("debug");
|
const DEBUG = JSON.safeParse(localStorage.getItem("debug")) || {};
|
||||||
const INFO = true;
|
const INFO = true;
|
||||||
const TIME = true;
|
const TIME = true;
|
||||||
const WARN = true;
|
const WARN = true;
|
||||||
|
|
@ -915,7 +915,7 @@ function calculateTemperatures() {
|
||||||
const [, y] = grid.points[rowCellId];
|
const [, y] = grid.points[rowCellId];
|
||||||
const rowLatitude = mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT; // [90; -90]
|
const rowLatitude = mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT; // [90; -90]
|
||||||
const tempSeaLevel = calculateSeaLevelTemp(rowLatitude);
|
const tempSeaLevel = calculateSeaLevelTemp(rowLatitude);
|
||||||
DEBUG && console.info(`${rn(rowLatitude)}° sea temperature: ${rn(tempSeaLevel)}°C`);
|
DEBUG.temperature && console.info(`${rn(rowLatitude)}° sea temperature: ${rn(tempSeaLevel)}°C`);
|
||||||
|
|
||||||
for (let cellId = rowCellId; cellId < rowCellId + grid.cellsX; cellId++) {
|
for (let cellId = rowCellId; cellId < rowCellId + grid.cellsX; cellId++) {
|
||||||
const tempAltitudeDrop = getAltitudeTemperatureDrop(cells.h[cellId]);
|
const tempAltitudeDrop = getAltitudeTemperatureDrop(cells.h[cellId]);
|
||||||
|
|
|
||||||
|
|
@ -286,7 +286,8 @@ window.BurgsAndStates = (() => {
|
||||||
const {cells, states, cultures, burgs} = pack;
|
const {cells, states, cultures, burgs} = pack;
|
||||||
|
|
||||||
cells.state = cells.state || new Uint16Array(cells.i.length);
|
cells.state = cells.state || new Uint16Array(cells.i.length);
|
||||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
|
||||||
|
const queue = new FlatQueue();
|
||||||
const cost = [];
|
const cost = [];
|
||||||
|
|
||||||
const globalGrowthRate = byId("growthRate").valueAsNumber || 1;
|
const globalGrowthRate = byId("growthRate").valueAsNumber || 1;
|
||||||
|
|
@ -307,12 +308,13 @@ window.BurgsAndStates = (() => {
|
||||||
cells.state[capitalCell] = state.i;
|
cells.state[capitalCell] = state.i;
|
||||||
const cultureCenter = cultures[state.culture].center;
|
const cultureCenter = cultures[state.culture].center;
|
||||||
const b = cells.biome[cultureCenter]; // state native biome
|
const b = cells.biome[cultureCenter]; // state native biome
|
||||||
queue.queue({e: state.center, p: 0, s: state.i, b});
|
queue.push({e: state.center, p: 0, s: state.i, b}, 0);
|
||||||
cost[state.center] = 1;
|
cost[state.center] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const next = queue.dequeue();
|
const next = queue.pop();
|
||||||
|
|
||||||
const {e, p, s, b} = next;
|
const {e, p, s, b} = next;
|
||||||
const {type, culture} = states[s];
|
const {type, culture} = states[s];
|
||||||
|
|
||||||
|
|
@ -335,7 +337,7 @@ window.BurgsAndStates = (() => {
|
||||||
if (!cost[e] || totalCost < cost[e]) {
|
if (!cost[e] || totalCost < cost[e]) {
|
||||||
if (cells.h[e] >= 20) cells.state[e] = s; // assign state to cell
|
if (cells.h[e] >= 20) cells.state[e] = s; // assign state to cell
|
||||||
cost[e] = totalCost;
|
cost[e] = totalCost;
|
||||||
queue.queue({e, p: totalCost, s, b});
|
queue.push({e, p: totalCost, s, b}, totalCost);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -518,7 +518,7 @@ window.Cultures = (function () {
|
||||||
TIME && console.time("expandCultures");
|
TIME && console.time("expandCultures");
|
||||||
const {cells, cultures} = pack;
|
const {cells, cultures} = pack;
|
||||||
|
|
||||||
const queue = new PriorityQueue({comparator: (a, b) => a.priority - b.priority});
|
const queue = new FlatQueue();
|
||||||
const cost = [];
|
const cost = [];
|
||||||
|
|
||||||
const neutralRate = byId("neutralRate")?.valueAsNumber || 1;
|
const neutralRate = byId("neutralRate")?.valueAsNumber || 1;
|
||||||
|
|
@ -538,11 +538,11 @@ window.Cultures = (function () {
|
||||||
|
|
||||||
for (const culture of cultures) {
|
for (const culture of cultures) {
|
||||||
if (!culture.i || culture.removed || culture.lock) continue;
|
if (!culture.i || culture.removed || culture.lock) continue;
|
||||||
queue.queue({cellId: culture.center, cultureId: culture.i, priority: 0});
|
queue.push({cellId: culture.center, cultureId: culture.i, priority: 0}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const {cellId, priority, cultureId} = queue.dequeue();
|
const {cellId, priority, cultureId} = queue.pop();
|
||||||
const {type, expansionism} = cultures[cultureId];
|
const {type, expansionism} = cultures[cultureId];
|
||||||
|
|
||||||
cells.c[cellId].forEach(neibCellId => {
|
cells.c[cellId].forEach(neibCellId => {
|
||||||
|
|
@ -566,7 +566,7 @@ window.Cultures = (function () {
|
||||||
if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
|
if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
|
||||||
if (cells.pop[neibCellId] > 0) cells.culture[neibCellId] = cultureId; // assign culture to populated cell
|
if (cells.pop[neibCellId] > 0) cells.culture[neibCellId] = cultureId; // assign culture to populated cell
|
||||||
cost[neibCellId] = totalCost;
|
cost[neibCellId] = totalCost;
|
||||||
queue.queue({cellId: neibCellId, cultureId, priority: totalCost});
|
queue.push({cellId: neibCellId, cultureId, priority: totalCost}, totalCost);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ window.Cloud = (function () {
|
||||||
|
|
||||||
async save(fileName, contents) {
|
async save(fileName, contents) {
|
||||||
const resp = await this.call("filesUpload", {path: "/" + fileName, contents});
|
const resp = await this.call("filesUpload", {path: "/" + fileName, contents});
|
||||||
DEBUG && console.info("Dropbox response:", resp);
|
DEBUG.cloud && console.info("Dropbox response:", resp);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -104,7 +104,7 @@ window.Cloud = (function () {
|
||||||
|
|
||||||
// Callback function for auth window
|
// Callback function for auth window
|
||||||
async setDropBoxToken(token) {
|
async setDropBoxToken(token) {
|
||||||
DEBUG && console.info("Access token:", token);
|
DEBUG.cloud && console.info("Access token:", token);
|
||||||
setToken(this.name, token);
|
setToken(this.name, token);
|
||||||
await this.connect(token);
|
await this.connect(token);
|
||||||
this.authWindow.close();
|
this.authWindow.close();
|
||||||
|
|
@ -131,7 +131,7 @@ window.Cloud = (function () {
|
||||||
allow_download: true
|
allow_download: true
|
||||||
};
|
};
|
||||||
const resp = await this.call("sharingCreateSharedLinkWithSettings", {path, settings});
|
const resp = await this.call("sharingCreateSharedLinkWithSettings", {path, settings});
|
||||||
DEBUG && console.info("Dropbox link object:", resp.result);
|
DEBUG.cloud && console.info("Dropbox link object:", resp.result);
|
||||||
return resp.result.url;
|
return resp.result.url;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ async function quickLoad() {
|
||||||
async function loadFromDropbox() {
|
async function loadFromDropbox() {
|
||||||
const mapPath = byId("loadFromDropboxSelect")?.value;
|
const mapPath = byId("loadFromDropboxSelect")?.value;
|
||||||
|
|
||||||
DEBUG && console.info("Loading map from Dropbox:", mapPath);
|
console.info("Loading map from Dropbox:", mapPath);
|
||||||
const blob = await Cloud.providers.dropbox.load(mapPath);
|
const blob = await Cloud.providers.dropbox.load(mapPath);
|
||||||
uploadMap(blob);
|
uploadMap(blob);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,18 +77,18 @@ window.Provinces = (function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
// expand generated provinces
|
// expand generated provinces
|
||||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
const queue = new FlatQueue();
|
||||||
const cost = [];
|
const cost = [];
|
||||||
|
|
||||||
provinces.forEach(p => {
|
provinces.forEach(p => {
|
||||||
if (!p.i || p.removed || isProvinceLocked(p)) return;
|
if (!p.i || p.removed || isProvinceLocked(p)) return;
|
||||||
provinceIds[p.center] = p.i;
|
provinceIds[p.center] = p.i;
|
||||||
queue.queue({e: p.center, p: 0, province: p.i, state: p.state});
|
queue.push({e: p.center, province: p.i, state: p.state, p: 0}, 0);
|
||||||
cost[p.center] = 1;
|
cost[p.center] = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const {e, p, province, state} = queue.dequeue();
|
const {e, p, province, state} = queue.pop();
|
||||||
|
|
||||||
cells.c[e].forEach(e => {
|
cells.c[e].forEach(e => {
|
||||||
if (isProvinceCellLocked(e)) return; // do not overwrite cell of locked provinces
|
if (isProvinceCellLocked(e)) return; // do not overwrite cell of locked provinces
|
||||||
|
|
@ -103,7 +103,7 @@ window.Provinces = (function () {
|
||||||
if (!cost[e] || totalCost < cost[e]) {
|
if (!cost[e] || totalCost < cost[e]) {
|
||||||
if (land) provinceIds[e] = province; // assign province to a cell
|
if (land) provinceIds[e] = province; // assign province to a cell
|
||||||
cost[e] = totalCost;
|
cost[e] = totalCost;
|
||||||
queue.queue({e, p: totalCost, province, state});
|
queue.push({e, province, state, p: totalCost}, totalCost);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -158,9 +158,9 @@ window.Provinces = (function () {
|
||||||
// expand province
|
// expand province
|
||||||
const cost = [];
|
const cost = [];
|
||||||
cost[center] = 1;
|
cost[center] = 1;
|
||||||
queue.queue({e: center, p: 0});
|
queue.push({e: center, p: 0}, 0);
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const {e, p} = queue.dequeue();
|
const {e, p} = queue.pop();
|
||||||
|
|
||||||
cells.c[e].forEach(nextCellId => {
|
cells.c[e].forEach(nextCellId => {
|
||||||
if (provinceIds[nextCellId]) return;
|
if (provinceIds[nextCellId]) return;
|
||||||
|
|
@ -173,7 +173,7 @@ window.Provinces = (function () {
|
||||||
if (!cost[nextCellId] || totalCost < cost[nextCellId]) {
|
if (!cost[nextCellId] || totalCost < cost[nextCellId]) {
|
||||||
if (land && cells.state[nextCellId] === s.i) provinceIds[nextCellId] = provinceId; // assign province to a cell
|
if (land && cells.state[nextCellId] === s.i) provinceIds[nextCellId] = provinceId; // assign province to a cell
|
||||||
cost[nextCellId] = totalCost;
|
cost[nextCellId] = totalCost;
|
||||||
queue.queue({e: nextCellId, p: totalCost});
|
queue.push({e: nextCellId, p: totalCost}, totalCost);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -216,15 +216,15 @@ window.Provinces = (function () {
|
||||||
// check if there is a land way within the same state between two cells
|
// check if there is a land way within the same state between two cells
|
||||||
function isPassable(from, to) {
|
function isPassable(from, to) {
|
||||||
if (cells.f[from] !== cells.f[to]) return false; // on different islands
|
if (cells.f[from] !== cells.f[to]) return false; // on different islands
|
||||||
const queue = [from],
|
const passableQueue = [from],
|
||||||
used = new Uint8Array(cells.i.length),
|
used = new Uint8Array(cells.i.length),
|
||||||
state = cells.state[from];
|
state = cells.state[from];
|
||||||
while (queue.length) {
|
while (passableQueue.length) {
|
||||||
const current = queue.pop();
|
const current = passableQueue.pop();
|
||||||
if (current === to) return true; // way is found
|
if (current === to) return true; // way is found
|
||||||
cells.c[current].forEach(c => {
|
cells.c[current].forEach(c => {
|
||||||
if (used[c] || cells.h[c] < 20 || cells.state[c] !== state) return;
|
if (used[c] || cells.h[c] < 20 || cells.state[c] !== state) return;
|
||||||
queue.push(c);
|
passableQueue.push(c);
|
||||||
used[c] = 1;
|
used[c] = 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -695,7 +695,7 @@ window.Religions = (function () {
|
||||||
const {cells, routes} = pack;
|
const {cells, routes} = pack;
|
||||||
const religionIds = spreadFolkReligions(religions);
|
const religionIds = spreadFolkReligions(religions);
|
||||||
|
|
||||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
const queue = new FlatQueue();
|
||||||
const cost = [];
|
const cost = [];
|
||||||
|
|
||||||
// limit cost for organized religions growth
|
// limit cost for organized religions growth
|
||||||
|
|
@ -705,14 +705,14 @@ window.Religions = (function () {
|
||||||
.filter(r => r.i && !r.lock && r.type !== "Folk" && !r.removed)
|
.filter(r => r.i && !r.lock && r.type !== "Folk" && !r.removed)
|
||||||
.forEach(r => {
|
.forEach(r => {
|
||||||
religionIds[r.center] = r.i;
|
religionIds[r.center] = r.i;
|
||||||
queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center]});
|
queue.push({e: r.center, p: 0, r: r.i, s: cells.state[r.center]}, 0);
|
||||||
cost[r.center] = 1;
|
cost[r.center] = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
const religionsMap = new Map(religions.map(r => [r.i, r]));
|
const religionsMap = new Map(religions.map(r => [r.i, r]));
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const {e: cellId, p, r, s: state} = queue.dequeue();
|
const {e: cellId, p, r, s: state} = queue.pop();
|
||||||
const {culture, expansion, expansionism} = religionsMap.get(r);
|
const {culture, expansion, expansionism} = religionsMap.get(r);
|
||||||
|
|
||||||
cells.c[cellId].forEach(nextCell => {
|
cells.c[cellId].forEach(nextCell => {
|
||||||
|
|
@ -732,7 +732,7 @@ window.Religions = (function () {
|
||||||
if (cells.culture[nextCell]) religionIds[nextCell] = r; // assign religion to cell
|
if (cells.culture[nextCell]) religionIds[nextCell] = r; // assign religion to cell
|
||||||
cost[nextCell] = totalCost;
|
cost[nextCell] = totalCost;
|
||||||
|
|
||||||
queue.queue({e: nextCell, p: totalCost, r, s: state});
|
queue.push({e: nextCell, p: totalCost, r, s: state}, totalCost);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,10 @@ function drawStateLabels(list) {
|
||||||
const pathPoints = [[ray1.x, ray1.y], state.pole, [ray2.x, ray2.y]];
|
const pathPoints = [[ray1.x, ray1.y], state.pole, [ray2.x, ray2.y]];
|
||||||
if (ray1.x > ray2.x) pathPoints.reverse();
|
if (ray1.x > ray2.x) pathPoints.reverse();
|
||||||
|
|
||||||
DEBUG && drawPoint(state.pole, {color: "black", radius: 1});
|
if (DEBUG.stateLabels) {
|
||||||
DEBUG && drawPath(pathPoints, {color: "black", width: 0.2});
|
drawPoint(state.pole, {color: "black", radius: 1});
|
||||||
|
drawPath(pathPoints, {color: "black", width: 0.2});
|
||||||
|
}
|
||||||
|
|
||||||
labelPaths.push([state.i, pathPoints]);
|
labelPaths.push([state.i, pathPoints]);
|
||||||
}
|
}
|
||||||
|
|
@ -163,9 +165,11 @@ function drawStateLabels(list) {
|
||||||
const offset1 = [x + -dy * offset, y + dx * offset];
|
const offset1 = [x + -dy * offset, y + dx * offset];
|
||||||
const offset2 = [x + dy * offset, y + -dx * offset];
|
const offset2 = [x + dy * offset, y + -dx * offset];
|
||||||
|
|
||||||
DEBUG && drawPoint([x, y], {color: isInsideState(x, y) ? "blue" : "red", radius: 0.8});
|
if (DEBUG.stateLabels) {
|
||||||
DEBUG && drawPoint(offset1, {color: isInsideState(...offset1) ? "blue" : "red", radius: 0.4});
|
drawPoint([x, y], {color: isInsideState(x, y) ? "blue" : "red", radius: 0.8});
|
||||||
DEBUG && drawPoint(offset2, {color: isInsideState(...offset2) ? "blue" : "red", radius: 0.4});
|
drawPoint(offset1, {color: isInsideState(...offset1) ? "blue" : "red", radius: 0.4});
|
||||||
|
drawPoint(offset2, {color: isInsideState(...offset2) ? "blue" : "red", radius: 0.4});
|
||||||
|
}
|
||||||
|
|
||||||
const inState = isInsideState(x, y) && isInsideState(...offset1) && isInsideState(...offset2);
|
const inState = isInsideState(x, y) && isInsideState(...offset1) && isInsideState(...offset2);
|
||||||
if (!inState) break;
|
if (!inState) break;
|
||||||
|
|
|
||||||
407
modules/submap.js
Normal file
407
modules/submap.js
Normal file
|
|
@ -0,0 +1,407 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
window.Submap = (function () {
|
||||||
|
const isWater = (pack, id) => pack.cells.h[id] < 20;
|
||||||
|
const inMap = (x, y) => x > 0 && x < graphWidth && y > 0 && y < graphHeight;
|
||||||
|
|
||||||
|
/*
|
||||||
|
generate new map based on an existing one (resampling parentMap)
|
||||||
|
parentMap: {seed, grid, pack} from original map
|
||||||
|
options = {
|
||||||
|
projection: f(Number,Number)->[Number, Number]
|
||||||
|
function to calculate new coordinates
|
||||||
|
inverse: g(Number,Number)->[Number, Number]
|
||||||
|
inverse of f
|
||||||
|
depressRivers: Bool carve out riverbeds?
|
||||||
|
smoothHeightMap: Bool run smooth filter on heights
|
||||||
|
addLakesInDepressions: call FMG original funtion on heightmap
|
||||||
|
|
||||||
|
lockMarkers: Bool Auto lock all copied markers
|
||||||
|
lockBurgs: Bool Auto lock all copied burgs
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
function resample(parentMap, options) {
|
||||||
|
const projection = options.projection;
|
||||||
|
const inverse = options.inverse;
|
||||||
|
const stage = s => INFO && console.info("SUBMAP:", s);
|
||||||
|
const timeStart = performance.now();
|
||||||
|
invokeActiveZooming();
|
||||||
|
|
||||||
|
// copy seed
|
||||||
|
seed = parentMap.seed;
|
||||||
|
Math.random = aleaPRNG(seed);
|
||||||
|
INFO && console.group("SubMap with seed: " + seed);
|
||||||
|
|
||||||
|
applyGraphSize();
|
||||||
|
grid = generateGrid();
|
||||||
|
|
||||||
|
drawScaleBar(scaleBar, scale);
|
||||||
|
fitScaleBar(scaleBar, svgWidth, svgHeight);
|
||||||
|
|
||||||
|
const resampler = (points, qtree, f) => {
|
||||||
|
for (const [i, [x, y]] of points.entries()) {
|
||||||
|
const [tx, ty] = inverse(x, y);
|
||||||
|
const oldid = qtree.find(tx, ty, Infinity)[2];
|
||||||
|
f(i, oldid);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
stage("Resampling heightmap, temperature and precipitation");
|
||||||
|
// resample heightmap from old WorldState
|
||||||
|
const n = grid.points.length;
|
||||||
|
grid.cells.h = new Uint8Array(n); // heightmap
|
||||||
|
grid.cells.temp = new Int8Array(n); // temperature
|
||||||
|
grid.cells.prec = new Uint8Array(n); // precipitation
|
||||||
|
const reverseGridMap = new Uint32Array(n); // cellmap from new -> oldcell
|
||||||
|
|
||||||
|
const oldGrid = parentMap.grid;
|
||||||
|
// build cache old -> [newcelllist]
|
||||||
|
const forwardGridMap = parentMap.grid.points.map(_ => []);
|
||||||
|
resampler(grid.points, parentMap.pack.cells.q, (id, oldid) => {
|
||||||
|
const cid = parentMap.pack.cells.g[oldid];
|
||||||
|
grid.cells.h[id] = oldGrid.cells.h[cid];
|
||||||
|
grid.cells.temp[id] = oldGrid.cells.temp[cid];
|
||||||
|
grid.cells.prec[id] = oldGrid.cells.prec[cid];
|
||||||
|
if (options.depressRivers) forwardGridMap[cid].push(id);
|
||||||
|
reverseGridMap[id] = cid;
|
||||||
|
});
|
||||||
|
// TODO: add smooth/noise function for h, temp, prec n times
|
||||||
|
|
||||||
|
// smooth heightmap
|
||||||
|
// smoothing should never change cell type (land->water or water->land)
|
||||||
|
|
||||||
|
if (options.smoothHeightMap) {
|
||||||
|
const gcells = grid.cells;
|
||||||
|
gcells.h.forEach((h, i) => {
|
||||||
|
const hs = gcells.c[i].map(c => gcells.h[c]);
|
||||||
|
hs.push(h);
|
||||||
|
gcells.h[i] = h >= 20 ? Math.max(d3.mean(hs), 20) : Math.min(d3.mean(hs), 19);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.depressRivers) {
|
||||||
|
stage("Generating riverbeds");
|
||||||
|
const rbeds = new Uint16Array(grid.cells.i.length);
|
||||||
|
|
||||||
|
// and erode riverbeds
|
||||||
|
parentMap.pack.rivers.forEach(r =>
|
||||||
|
r.cells.forEach(oldpc => {
|
||||||
|
if (oldpc < 0) return; // ignore out-of-map marker (-1)
|
||||||
|
const oldc = parentMap.pack.cells.g[oldpc];
|
||||||
|
const targetCells = forwardGridMap[oldc];
|
||||||
|
if (!targetCells) throw "TargetCell shouldn't be empty";
|
||||||
|
targetCells.forEach(c => {
|
||||||
|
if (grid.cells.h[c] < 20) return;
|
||||||
|
rbeds[c] = 1;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// raise every land cell a bit except riverbeds
|
||||||
|
grid.cells.h.forEach((h, i) => {
|
||||||
|
if (rbeds[i] || h < 20) return;
|
||||||
|
grid.cells.h[i] = Math.min(h + 2, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stage("Detect features, ocean and generating lakes");
|
||||||
|
Features.markupGrid();
|
||||||
|
|
||||||
|
addLakesInDeepDepressions();
|
||||||
|
openNearSeaLakes();
|
||||||
|
|
||||||
|
OceanLayers();
|
||||||
|
|
||||||
|
calculateMapCoordinates();
|
||||||
|
calculateTemperatures();
|
||||||
|
generatePrecipitation();
|
||||||
|
stage("Cell cleanup");
|
||||||
|
reGraph();
|
||||||
|
|
||||||
|
// remove misclassified cells
|
||||||
|
stage("Define coastline");
|
||||||
|
Features.markupPack();
|
||||||
|
createDefaultRuler();
|
||||||
|
|
||||||
|
// Packed Graph
|
||||||
|
const oldCells = parentMap.pack.cells;
|
||||||
|
const forwardMap = parentMap.pack.cells.p.map(_ => []); // old -> [newcelllist]
|
||||||
|
|
||||||
|
const pn = pack.cells.i.length;
|
||||||
|
const cells = pack.cells;
|
||||||
|
cells.culture = new Uint16Array(pn);
|
||||||
|
cells.state = new Uint16Array(pn);
|
||||||
|
cells.burg = new Uint16Array(pn);
|
||||||
|
cells.religion = new Uint16Array(pn);
|
||||||
|
cells.province = new Uint16Array(pn);
|
||||||
|
|
||||||
|
stage("Resampling culture, state and religion map");
|
||||||
|
for (const [id, gridCellId] of cells.g.entries()) {
|
||||||
|
const oldGridId = reverseGridMap[gridCellId];
|
||||||
|
if (oldGridId === undefined) {
|
||||||
|
console.error("Can not find old cell id", reverseGridMap, "in", gridCellId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// find old parent's children
|
||||||
|
const oldChildren = oldCells.i.filter(oid => oldCells.g[oid] == oldGridId);
|
||||||
|
let oldid; // matching cell on the original map
|
||||||
|
|
||||||
|
if (!oldChildren.length) {
|
||||||
|
// it *must* be a (deleted) deep ocean cell
|
||||||
|
if (!oldGrid.cells.h[oldGridId] < 20) {
|
||||||
|
console.error(`Warning, ${gridCellId} should be water cell, not ${oldGrid.cells.h[oldGridId]}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// find replacement: closest water cell
|
||||||
|
const [ox, oy] = cells.p[id];
|
||||||
|
const [tx, ty] = inverse(x, y);
|
||||||
|
oldid = oldCells.q.find(tx, ty, Infinity)[2];
|
||||||
|
if (!oldid) {
|
||||||
|
console.warn("Warning, no id found in quad", id, "parent", gridCellId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// find closest children (packcell) on the parent map
|
||||||
|
const distance = x => (x[0] - cells.p[id][0]) ** 2 + (x[1] - cells.p[id][1]) ** 2;
|
||||||
|
let d = Infinity;
|
||||||
|
oldChildren.forEach(oid => {
|
||||||
|
// this should be always true, unless some algo modded the height!
|
||||||
|
if (isWater(parentMap.pack, oid) !== isWater(pack, id)) {
|
||||||
|
console.warn(`cell sank because of addLakesInDepressions: ${oid}`);
|
||||||
|
}
|
||||||
|
const [oldpx, oldpy] = oldCells.p[oid];
|
||||||
|
const nd = distance(projection(oldpx, oldpy));
|
||||||
|
if (isNaN(nd)) {
|
||||||
|
console.error("Distance is not a number!", "Old point:", oldpx, oldpy);
|
||||||
|
}
|
||||||
|
if (nd < d) [d, oldid] = [nd, oid];
|
||||||
|
});
|
||||||
|
if (oldid === undefined) {
|
||||||
|
console.warn("Warning, no match for", id, "(parent:", gridCellId, ")");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWater(pack, id) !== isWater(parentMap.pack, oldid)) {
|
||||||
|
WARN && console.warn("Type discrepancy detected:", id, oldid, `${pack.cells.t[id]} != ${oldCells.t[oldid]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
cells.culture[id] = oldCells.culture[oldid];
|
||||||
|
cells.state[id] = oldCells.state[oldid];
|
||||||
|
cells.religion[id] = oldCells.religion[oldid];
|
||||||
|
cells.province[id] = oldCells.province[oldid];
|
||||||
|
// reverseMap.set(id, oldid)
|
||||||
|
forwardMap[oldid].push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
stage("Regenerating river network");
|
||||||
|
Rivers.generate();
|
||||||
|
|
||||||
|
// biome calculation based on (resampled) grid.cells.temp and prec
|
||||||
|
// it's safe to recalculate.
|
||||||
|
stage("Regenerating Biome");
|
||||||
|
Biomes.define();
|
||||||
|
// recalculate suitability and population
|
||||||
|
// TODO: normalize according to the base-map
|
||||||
|
rankCells();
|
||||||
|
|
||||||
|
stage("Porting Cultures");
|
||||||
|
pack.cultures = parentMap.pack.cultures;
|
||||||
|
// fix culture centers
|
||||||
|
const validCultures = new Set(pack.cells.culture);
|
||||||
|
pack.cultures.forEach((c, i) => {
|
||||||
|
if (!i) return; // ignore wildlands
|
||||||
|
if (!validCultures.has(i)) {
|
||||||
|
c.removed = true;
|
||||||
|
c.center = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newCenters = forwardMap[c.center];
|
||||||
|
c.center = newCenters.length ? newCenters[0] : pack.cells.culture.findIndex(x => x === i);
|
||||||
|
});
|
||||||
|
|
||||||
|
stage("Porting and locking burgs");
|
||||||
|
copyBurgs(parentMap, projection, options);
|
||||||
|
|
||||||
|
// transfer states, mark states without land as removed.
|
||||||
|
stage("Porting states");
|
||||||
|
const validStates = new Set(pack.cells.state);
|
||||||
|
pack.states = parentMap.pack.states;
|
||||||
|
// keep valid states and neighbors only
|
||||||
|
pack.states.forEach((s, i) => {
|
||||||
|
if (!s.i || s.removed) return; // ignore removed and neutrals
|
||||||
|
if (!validStates.has(i)) s.removed = true;
|
||||||
|
s.neighbors = s.neighbors.filter(n => validStates.has(n));
|
||||||
|
|
||||||
|
// find center
|
||||||
|
s.center = pack.burgs[s.capital].cell
|
||||||
|
? pack.burgs[s.capital].cell // capital is the best bet
|
||||||
|
: pack.cells.state.findIndex(x => x === i); // otherwise use the first valid cell
|
||||||
|
});
|
||||||
|
BurgsAndStates.getPoles();
|
||||||
|
|
||||||
|
// transfer provinces, mark provinces without land as removed.
|
||||||
|
stage("Porting provinces");
|
||||||
|
const validProvinces = new Set(pack.cells.province);
|
||||||
|
pack.provinces = parentMap.pack.provinces;
|
||||||
|
// mark uneccesary provinces
|
||||||
|
pack.provinces.forEach((p, i) => {
|
||||||
|
if (!p || p.removed) return;
|
||||||
|
if (!validProvinces.has(i)) {
|
||||||
|
p.removed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newCenters = forwardMap[p.center];
|
||||||
|
p.center = newCenters.length ? newCenters[0] : pack.cells.province.findIndex(x => x === i);
|
||||||
|
});
|
||||||
|
Provinces.getPoles();
|
||||||
|
|
||||||
|
stage("Regenerating routes network");
|
||||||
|
regenerateRoutes();
|
||||||
|
|
||||||
|
Rivers.specify();
|
||||||
|
Features.specify();
|
||||||
|
|
||||||
|
stage("Porting military");
|
||||||
|
for (const s of pack.states) {
|
||||||
|
if (!s.military) continue;
|
||||||
|
for (const m of s.military) {
|
||||||
|
[m.x, m.y] = projection(m.x, m.y);
|
||||||
|
[m.bx, m.by] = projection(m.bx, m.by);
|
||||||
|
const cc = forwardMap[m.cell];
|
||||||
|
m.cell = cc && cc.length ? cc[0] : null;
|
||||||
|
}
|
||||||
|
s.military = s.military.filter(m => m.cell).map((m, i) => ({...m, i}));
|
||||||
|
}
|
||||||
|
|
||||||
|
stage("Copying markers");
|
||||||
|
for (const m of pack.markers) {
|
||||||
|
const [x, y] = projection(m.x, m.y);
|
||||||
|
if (!inMap(x, y)) {
|
||||||
|
Markers.deleteMarker(m.i);
|
||||||
|
} else {
|
||||||
|
m.x = x;
|
||||||
|
m.y = y;
|
||||||
|
m.cell = findCell(x, y);
|
||||||
|
if (options.lockMarkers) m.lock = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (layerIsOn("toggleMarkers")) drawMarkers();
|
||||||
|
|
||||||
|
stage("Regenerating Zones");
|
||||||
|
Zones.generate();
|
||||||
|
Names.getMapName();
|
||||||
|
stage("Restoring Notes");
|
||||||
|
notes = parentMap.notes;
|
||||||
|
stage("Submap done");
|
||||||
|
|
||||||
|
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
|
||||||
|
showStatistics();
|
||||||
|
INFO && console.groupEnd("Generated Map " + seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* find the nearest cell accepted by filter f *and* having at
|
||||||
|
* least one *neighbor* fulfilling filter g, up to cell-distance `max`
|
||||||
|
* returns [cellid, neighbor] tuple or undefined if no such cell.
|
||||||
|
* accepts coordinates (x, y)
|
||||||
|
*/
|
||||||
|
const findNearest =
|
||||||
|
(f, g, max = 3) =>
|
||||||
|
(px, py) => {
|
||||||
|
const d2 = c => (px - pack.cells.p[c][0]) ** 2 + (py - pack.cells.p[c][0]) ** 2;
|
||||||
|
const startCell = findCell(px, py);
|
||||||
|
const tested = new Set([startCell]); // ignore analyzed cells
|
||||||
|
const kernel = (cs, level) => {
|
||||||
|
const [bestf, bestg] = cs.filter(f).reduce(
|
||||||
|
([cf, cg], c) => {
|
||||||
|
const neighbors = pack.cells.c[c];
|
||||||
|
const betterg = neighbors.filter(g).reduce((u, x) => (d2(x) < d2(u) ? x : u));
|
||||||
|
if (cf === undefined) return [c, betterg];
|
||||||
|
return betterg && d2(cf) < d2(c) ? [c, betterg] : [cf, cg];
|
||||||
|
},
|
||||||
|
[undefined, undefined]
|
||||||
|
);
|
||||||
|
if (bestf && bestg) return [bestf, bestg];
|
||||||
|
|
||||||
|
// no suitable pair found, retry with next ring
|
||||||
|
const targets = new Set(cs.map(c => pack.cells.c[c]).flat());
|
||||||
|
const ring = Array.from(targets).filter(nc => !tested.has(nc));
|
||||||
|
if (level >= max || !ring.length) return [undefined, undefined];
|
||||||
|
ring.forEach(c => tested.add(c));
|
||||||
|
return kernel(ring, level + 1);
|
||||||
|
};
|
||||||
|
const pair = kernel([startCell], 1);
|
||||||
|
return pair;
|
||||||
|
};
|
||||||
|
|
||||||
|
function copyBurgs(parentMap, projection, options) {
|
||||||
|
const cells = pack.cells;
|
||||||
|
pack.burgs = parentMap.pack.burgs;
|
||||||
|
|
||||||
|
// remap burgs to the best new cell
|
||||||
|
pack.burgs.forEach((b, id) => {
|
||||||
|
if (id == 0) return; // skip empty city of neturals
|
||||||
|
[b.x, b.y] = projection(b.x, b.y);
|
||||||
|
b.population = b.population * options.scale; // adjust for populationRate change
|
||||||
|
|
||||||
|
// disable out-of-map (removed) burgs
|
||||||
|
if (!inMap(b.x, b.y)) {
|
||||||
|
b.removed = true;
|
||||||
|
b.cell = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cityCell = findCell(b.x, b.y);
|
||||||
|
let searchFunc;
|
||||||
|
const isFreeLand = c => cells.t[c] === 1 && !cells.burg[c];
|
||||||
|
const nearCoast = c => cells.t[c] === -1;
|
||||||
|
|
||||||
|
// check if we need to relocate the burg
|
||||||
|
if (cells.burg[cityCell])
|
||||||
|
// already occupied
|
||||||
|
searchFunc = findNearest(isFreeLand, _ => true, 3);
|
||||||
|
|
||||||
|
if (isWater(pack, cityCell) || b.port)
|
||||||
|
// burg is in water or port
|
||||||
|
searchFunc = findNearest(isFreeLand, nearCoast, 6);
|
||||||
|
|
||||||
|
if (searchFunc) {
|
||||||
|
const [newCell, neighbor] = searchFunc(b.x, b.y);
|
||||||
|
if (!newCell) {
|
||||||
|
WARN && console.warn(`Can not relocate Burg: ${b.name} sunk and destroyed. :-(`);
|
||||||
|
b.cell = null;
|
||||||
|
b.removed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[b.x, b.y] = b.port ? getCloseToEdgePoint(newCell, neighbor) : cells.p[newCell];
|
||||||
|
if (b.port) b.port = cells.f[neighbor]; // copy feature number
|
||||||
|
b.cell = newCell;
|
||||||
|
if (b.port && !isWater(pack, neighbor)) console.error("betrayal! negihbor must be water!", b);
|
||||||
|
} else {
|
||||||
|
b.cell = cityCell;
|
||||||
|
}
|
||||||
|
if (b.i && !b.lock) b.lock = options.lockBurgs;
|
||||||
|
cells.burg[b.cell] = id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCloseToEdgePoint(cell1, cell2) {
|
||||||
|
const {cells, vertices} = pack;
|
||||||
|
|
||||||
|
const [x0, y0] = cells.p[cell1];
|
||||||
|
|
||||||
|
const commonVertices = cells.v[cell1].filter(vertex => vertices.c[vertex].some(cell => cell === cell2));
|
||||||
|
const [x1, y1] = vertices.p[commonVertices[0]];
|
||||||
|
const [x2, y2] = vertices.p[commonVertices[1]];
|
||||||
|
const xEdge = (x1 + x2) / 2;
|
||||||
|
const yEdge = (y1 + y2) / 2;
|
||||||
|
|
||||||
|
const x = rn(x0 + 0.95 * (xEdge - x0), 2);
|
||||||
|
const y = rn(y0 + 0.95 * (yEdge - y0), 2);
|
||||||
|
|
||||||
|
return [x, y];
|
||||||
|
}
|
||||||
|
|
||||||
|
// export
|
||||||
|
return {resample, findNearest};
|
||||||
|
})();
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
const GPT_MODELS = ["gpt-4o-mini", "chatgpt-4o-latest", "gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"];
|
const GPT_MODELS = ["gpt-4o-mini", "chatgpt-4o-latest", "gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"];
|
||||||
const SYSTEM_MESSAGE = "I'm working on my fantasy map.";
|
const SYSTEM_MESSAGE = "I'm working on my fantasy map.";
|
||||||
|
|
||||||
function geneateWithAi(defaultPrompt, onApply) {
|
function generateWithAi(defaultPrompt, onApply) {
|
||||||
updateValues();
|
updateValues();
|
||||||
|
|
||||||
$("#aiGenerator").dialog({
|
$("#aiGenerator").dialog({
|
||||||
|
|
@ -26,13 +26,14 @@ function geneateWithAi(defaultPrompt, onApply) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modules.geneateWithAi) return;
|
if (modules.generateWithAi) return;
|
||||||
modules.geneateWithAi = true;
|
modules.generateWithAi = true;
|
||||||
|
|
||||||
function updateValues() {
|
function updateValues() {
|
||||||
byId("aiGeneratorResult").value = "";
|
byId("aiGeneratorResult").value = "";
|
||||||
byId("aiGeneratorPrompt").value = defaultPrompt;
|
byId("aiGeneratorPrompt").value = defaultPrompt;
|
||||||
byId("aiGeneratorKey").value = localStorage.getItem("fmg-ai-kl") || "";
|
byId("aiGeneratorKey").value = localStorage.getItem("fmg-ai-kl") || "";
|
||||||
|
byId("aiGeneratorTemperature").value = localStorage.getItem("fmg-ai-temperature") || "1.2";
|
||||||
|
|
||||||
const select = byId("aiGeneratorModel");
|
const select = byId("aiGeneratorModel");
|
||||||
select.options.length = 0;
|
select.options.length = 0;
|
||||||
|
|
@ -52,6 +53,12 @@ function geneateWithAi(defaultPrompt, onApply) {
|
||||||
const prompt = byId("aiGeneratorPrompt").value;
|
const prompt = byId("aiGeneratorPrompt").value;
|
||||||
if (!prompt) return tip("Please enter a prompt", true, "error", 4000);
|
if (!prompt) return tip("Please enter a prompt", true, "error", 4000);
|
||||||
|
|
||||||
|
const temperature = parseFloat(byId("aiGeneratorTemperature").value);
|
||||||
|
if (isNaN(temperature) || temperature < 0 || temperature > 2) {
|
||||||
|
return tip("Temperature must be a number between 0 and 2", true, "error", 4000);
|
||||||
|
}
|
||||||
|
localStorage.setItem("fmg-ai-temperature", temperature.toString());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
const resultArea = byId("aiGeneratorResult");
|
const resultArea = byId("aiGeneratorResult");
|
||||||
|
|
@ -70,7 +77,7 @@ function geneateWithAi(defaultPrompt, onApply) {
|
||||||
{role: "system", content: SYSTEM_MESSAGE},
|
{role: "system", content: SYSTEM_MESSAGE},
|
||||||
{role: "user", content: prompt}
|
{role: "user", content: prompt}
|
||||||
],
|
],
|
||||||
temperature: 1.2,
|
temperature: temperature,
|
||||||
stream: true // Enable streaming
|
stream: true // Enable streaming
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ function editNotes(id, name) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
geneateWithAi(prompt, onApply);
|
generateWithAi(prompt, onApply);
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadLegends() {
|
function downloadLegends() {
|
||||||
|
|
|
||||||
|
|
@ -116,20 +116,20 @@ function selectStyleElement() {
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
"armies",
|
"armies",
|
||||||
"routes",
|
|
||||||
"lakes",
|
|
||||||
"biomes",
|
"biomes",
|
||||||
"borders",
|
"borders",
|
||||||
"cults",
|
|
||||||
"relig",
|
|
||||||
"cells",
|
"cells",
|
||||||
"coastline",
|
"coastline",
|
||||||
"prec",
|
"coordinates",
|
||||||
|
"cults",
|
||||||
|
"gridOverlay",
|
||||||
"ice",
|
"ice",
|
||||||
"icons",
|
"icons",
|
||||||
"coordinates",
|
"lakes",
|
||||||
"zones",
|
"prec",
|
||||||
"gridOverlay"
|
"relig",
|
||||||
|
"routes",
|
||||||
|
"zones"
|
||||||
].includes(styleElement)
|
].includes(styleElement)
|
||||||
) {
|
) {
|
||||||
styleStroke.style.display = "block";
|
styleStroke.style.display = "block";
|
||||||
|
|
@ -140,7 +140,7 @@ function selectStyleElement() {
|
||||||
|
|
||||||
// stroke dash
|
// stroke dash
|
||||||
if (
|
if (
|
||||||
["routes", "borders", "temperature", "legend", "population", "coordinates", "zones", "gridOverlay"].includes(
|
["borders", "cells", "coordinates", "gridOverlay", "legend", "population", "routes", "temperature", "zones"].includes(
|
||||||
styleElement
|
styleElement
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
@ -788,7 +788,7 @@ styleShadowInput.on("input", function () {
|
||||||
styleFontAdd.on("click", function () {
|
styleFontAdd.on("click", function () {
|
||||||
addFontNameInput.value = "";
|
addFontNameInput.value = "";
|
||||||
addFontURLInput.value = "";
|
addFontURLInput.value = "";
|
||||||
|
|
||||||
$("#addFontDialog").dialog({
|
$("#addFontDialog").dialog({
|
||||||
title: "Add custom font",
|
title: "Add custom font",
|
||||||
width: "26em",
|
width: "26em",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function editZones() {
|
function editZones() {
|
||||||
closeDialogs();
|
closeDialogs("#zonesEditor, .stable");
|
||||||
if (!layerIsOn("toggleZones")) toggleZones();
|
if (!layerIsOn("toggleZones")) toggleZones();
|
||||||
const body = byId("zonesBodySection");
|
const body = byId("zonesBodySection");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,11 +209,11 @@ window.Zones = (function () {
|
||||||
const cost = [];
|
const cost = [];
|
||||||
const maxCells = rand(20, 40);
|
const maxCells = rand(20, 40);
|
||||||
|
|
||||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
const queue = new FlatQueue();
|
||||||
queue.queue({e: burg.cell, p: 0});
|
queue.push({e: burg.cell, p: 0}, 0);
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const next = queue.dequeue();
|
const next = queue.pop();
|
||||||
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
|
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
|
||||||
usedCells[next.e] = 1;
|
usedCells[next.e] = 1;
|
||||||
|
|
||||||
|
|
@ -224,7 +224,7 @@ window.Zones = (function () {
|
||||||
|
|
||||||
if (!cost[nextCellId] || p < cost[nextCellId]) {
|
if (!cost[nextCellId] || p < cost[nextCellId]) {
|
||||||
cost[nextCellId] = p;
|
cost[nextCellId] = p;
|
||||||
queue.queue({e: nextCellId, p});
|
queue.push({e: nextCellId, p}, p);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -251,11 +251,11 @@ window.Zones = (function () {
|
||||||
const cost = [];
|
const cost = [];
|
||||||
const maxCells = rand(5, 25);
|
const maxCells = rand(5, 25);
|
||||||
|
|
||||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
const queue = new FlatQueue();
|
||||||
queue.queue({e: burg.cell, p: 0});
|
queue.push({e: burg.cell, p: 0}, 0);
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const next = queue.dequeue();
|
const next = queue.pop();
|
||||||
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
|
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
|
||||||
usedCells[next.e] = 1;
|
usedCells[next.e] = 1;
|
||||||
|
|
||||||
|
|
@ -266,7 +266,7 @@ window.Zones = (function () {
|
||||||
|
|
||||||
if (!cost[e] || p < cost[e]) {
|
if (!cost[e] || p < cost[e]) {
|
||||||
cost[e] = p;
|
cost[e] = p;
|
||||||
queue.queue({e, p});
|
queue.push({e, p}, p);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,3 +56,11 @@ JSON.isValid = str => {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
JSON.safeParse = str => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@
|
||||||
*
|
*
|
||||||
* Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2
|
* Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2
|
||||||
*/
|
*/
|
||||||
const VERSION = "1.105.19";
|
|
||||||
|
const VERSION = "1.105.22";
|
||||||
if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function");
|
if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function");
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue