mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 01:41:22 +01:00
Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into burg-groups
This commit is contained in:
commit
95b7ed9ea4
33 changed files with 572 additions and 378 deletions
|
|
@ -172,6 +172,7 @@ t,
|
|||
#texture,
|
||||
#landmass,
|
||||
#vignette,
|
||||
#gridOverlay,
|
||||
#fogging {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
|||
95
index.html
95
index.html
|
|
@ -11,8 +11,8 @@
|
|||
name="description"
|
||||
content="Free web app that helps fantasy writers, game masters, and cartographers create and edit fantasy maps"
|
||||
/>
|
||||
|
||||
<meta property="og:url" content="https://azgaar.github.io/Fantasy-Map-Generator" />
|
||||
|
||||
<meta property="og:title" content="Azgaar's Fantasy Map Generator" />
|
||||
<meta
|
||||
property="og:description"
|
||||
|
|
@ -138,7 +138,7 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<link rel="preload" href="index.css?v=1.106.3" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||
<link rel="preload" href="index.css?v=1.108.6" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||
<link rel="preload" href="icons.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||
<link
|
||||
rel="preload"
|
||||
|
|
@ -354,9 +354,7 @@
|
|||
<g id="statePaths"></g>
|
||||
<g id="defs-emblems"></g>
|
||||
<mask id="land"></mask>
|
||||
<mask id="water">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="white" />
|
||||
</mask>
|
||||
<mask id="water"></mask>
|
||||
<mask id="fog" style="stroke-width: 10; stroke: black; stroke-linejoin: round; stroke-opacity: 0.1">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="white" stroke="none" />
|
||||
</mask>
|
||||
|
|
@ -3575,9 +3573,9 @@
|
|||
<input id="markerType" style="width: 10.3em" />
|
||||
</div>
|
||||
|
||||
<div data-tip="Marker icon. Paste any Unicode symbol or select from the predefined list">
|
||||
<div data-tip="Marker icon" style="display: flex; align-items: center">
|
||||
<div class="label">Icon:</div>
|
||||
<input id="markerIcon" style="width: 5em" />
|
||||
<div id="markerIcon" style="font-size: 1.5em; width: 3.7em">👑</div>
|
||||
<button id="markerIconSelect" style="width: 5em">select</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -3666,10 +3664,10 @@
|
|||
></i>
|
||||
</div>
|
||||
|
||||
<div data-tip="Regiment emblem. Paste any Unicode symbol or select from the predefined list">
|
||||
<div class="label italic">Emblem:</div>
|
||||
<input id="regimentEmblem" style="width: 5em" />
|
||||
<button id="regimentEmblemSelect" style="padding: 0; width: 4.5em">select</button>
|
||||
<div data-tip="Regiment emblem" style="display: flex; align-items: center">
|
||||
<div class="label">Emblem:</div>
|
||||
<div id="regimentEmblem" style="font-size: 1.5em; width: 3.7em"></div>
|
||||
<button id="regimentEmblemChange" style="padding: 0; width: 4.5em">change</button>
|
||||
</div>
|
||||
|
||||
<div id="regimentComposition" class="table"></div>
|
||||
|
|
@ -5823,11 +5821,24 @@
|
|||
</div>
|
||||
|
||||
<div id="iconSelector" style="display: none" class="dialog">
|
||||
<table id="iconTable" class="table pointer" style="font-size: 2em; text-align: center; width: 100%"></table>
|
||||
<div style="font-style: italic; font-size: 1.2em; margin: 0.4em 0 0 0.4em">
|
||||
<span>Select from the list or paste a Unicode character here: </span>
|
||||
<input id="iconInput" style="width: 2.5em" />
|
||||
<span>. See <a href="https://emojipedia.org" target="_blank">Emojipedia</a> for reference</span>
|
||||
<div>
|
||||
<b>Unicode emojis</b>
|
||||
<div style="font-style: italic">
|
||||
<span>Select from the list or paste a Unicode character here: </span>
|
||||
<input id="iconInput" style="width: 2.5em" />
|
||||
<span>. See <a href="https://emojidb.org" target="_blank">EmojiDB</a> to search for emojis</span>
|
||||
</div>
|
||||
<table id="iconTable" class="table pointer" style="font-size: 2em; text-align: center; width: 100%"></table>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 0.5em">
|
||||
<b>External images</b>
|
||||
<div style="font-style: italic">
|
||||
<span>Paste link to the image here: </span>
|
||||
<input id="imageInput" style="width: 20em" />
|
||||
<button id="addImage" type="button">Add</button>
|
||||
</div>
|
||||
<div id="addedIcons" class="pointer" style="display: flex; flex-wrap: wrap; max-width: 420px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -8312,7 +8323,7 @@
|
|||
<script src="config/precreated-heightmaps.js"></script>
|
||||
<script src="modules/heightmap-generator.js?v=1.99.00"></script>
|
||||
<script src="modules/features.js?v=1.104.0"></script>
|
||||
<script src="modules/ocean-layers.js?v=1.104.8"></script>
|
||||
<script src="modules/ocean-layers.js?v=1.108.4"></script>
|
||||
<script src="modules/river-generator.js?v=1.106.7"></script>
|
||||
<script src="modules/lakes.js?v=1.99.00"></script>
|
||||
<script src="modules/biomes.js?v=1.99.00"></script>
|
||||
|
|
@ -8323,8 +8334,8 @@
|
|||
<script src="modules/provinces-generator.js?v=1.106.0"></script>
|
||||
<script src="modules/routes-generator.js?v=1.106.0"></script>
|
||||
<script src="modules/religions-generator.js?v=1.106.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/military-generator.js?v=1.107.0"></script>
|
||||
<script src="modules/markers-generator.js?v=1.107.0"></script>
|
||||
<script src="modules/zones-generator.js?v=1.106.0"></script>
|
||||
<script src="modules/coa-generator.js?v=1.99.00"></script>
|
||||
<script src="modules/resample.js?v=1.106.4"></script>
|
||||
|
|
@ -8333,21 +8344,20 @@
|
|||
<script src="libs/lineclip.min.js?v1.105.0"></script>
|
||||
<script src="libs/simplify.js?v1.105.6"></script>
|
||||
<script src="modules/fonts.js?v=1.99.03"></script>
|
||||
<script src="modules/ui/layers.js?v=1.106.0"></script>
|
||||
<script src="modules/ui/layers.js?v=1.108.4"></script>
|
||||
<script src="modules/ui/measurers.js?v=1.99.00"></script>
|
||||
<script src="modules/ui/style-presets.js?v=1.100.00"></script>
|
||||
<script src="modules/ui/general.js?v=1.100.00"></script>
|
||||
<script src="modules/ui/options.js?v=1.106.2"></script>
|
||||
<script src="main.js?v=1.106.0"></script>
|
||||
<script src="main.js?v=1.108.1"></script>
|
||||
|
||||
<script defer src="modules/relief-icons.js?v=1.99.05"></script>
|
||||
<script defer src="modules/ui/style.js?v=1.104.0"></script>
|
||||
<script defer src="modules/ui/editors.js?v=1.106.1"></script>
|
||||
<script defer src="modules/ui/tools.js?v=1.106.0"></script>
|
||||
<script defer src="modules/ui/style.js?v=1.108.4"></script>
|
||||
<script defer src="modules/ui/editors.js?v=1.108.5"></script>
|
||||
<script defer src="modules/ui/tools.js?v=1.108.5"></script>
|
||||
<script defer src="modules/ui/world-configurator.js?v=1.105.4"></script>
|
||||
<script defer src="modules/ui/heightmap-editor.js?v=1.105.2"></script>
|
||||
<script defer src="modules/ui/provinces-editor.js?v=1.106.1"></script>
|
||||
<script defer src="modules/ui/biomes-editor.js?v=1.99.05"></script>
|
||||
<script defer src="modules/ui/provinces-editor.js?v=1.108.1"></script>
|
||||
<script defer src="modules/ui/biomes-editor.js?v=1.108.4"></script>
|
||||
<script defer src="modules/ui/namesbase-editor.js?v=1.105.11"></script>
|
||||
<script defer src="modules/ui/elevation-profile.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/temperature-graph.js?v=1.106.6"></script>
|
||||
|
|
@ -8364,20 +8374,20 @@
|
|||
<script defer src="modules/ui/burg-group-editor.js?v=1.106.0"></script>
|
||||
<script defer src="modules/ui/burg-editor.js?v=1.106.6"></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.107.3"></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/zones-editor.js?v=1.105.20"></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/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/regiments-overview.js?v=1.104.0"></script>
|
||||
<script defer src="modules/ui/markers-overview.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/regiment-editor.js?v=1.104.14"></script>
|
||||
<script defer src="modules/ui/battle-screen.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/military-overview.js?v=1.108.5"></script>
|
||||
<script defer src="modules/ui/regiments-overview.js?v=1.108.5"></script>
|
||||
<script defer src="modules/ui/markers-overview.js?v=1.108.5"></script>
|
||||
<script defer src="modules/ui/regiment-editor.js?v=1.108.5"></script>
|
||||
<script defer src="modules/ui/battle-screen.js?v=1.108.5"></script>
|
||||
<script defer src="modules/ui/emblems-editor.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/markers-editor.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/markers-editor.js?v=1.108.5"></script>
|
||||
<script defer src="modules/ui/3d.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/submap-tool.js?v=1.106.2"></script>
|
||||
<script defer src="modules/ui/transform-tool.js?v=1.106.2"></script>
|
||||
|
|
@ -8385,21 +8395,22 @@
|
|||
<script defer src="modules/coa-renderer.js?v=1.99.00"></script>
|
||||
<script defer src="libs/rgbquant.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/load.js?v=1.105.24"></script>
|
||||
<script defer src="modules/io/save.js?v=1.107.4"></script>
|
||||
<script defer src="modules/io/load.js?v=1.108.0"></script>
|
||||
<script defer src="modules/io/cloud.js?v=1.106.0"></script>
|
||||
<script defer src="modules/io/export.js?v=1.100.00"></script>
|
||||
|
||||
<script defer src="modules/renderers/draw-features.js?v=1.106.0"></script>
|
||||
<script defer src="modules/renderers/draw-features.js?v=1.108.2"></script>
|
||||
<script defer src="modules/renderers/draw-borders.js?v=1.104.0"></script>
|
||||
<script defer src="modules/renderers/draw-heightmap.js?v=1.104.0"></script>
|
||||
<script defer src="modules/renderers/draw-markers.js?v=1.104.0"></script>
|
||||
<script defer src="modules/renderers/draw-scalebar.js?v=1.104.0"></script>
|
||||
<script defer src="modules/renderers/draw-markers.js?v=1.108.5"></script>
|
||||
<script defer src="modules/renderers/draw-scalebar.js?v=1.108.1"></script>
|
||||
<script defer src="modules/renderers/draw-temperature.js?v=1.104.0"></script>
|
||||
<script defer src="modules/renderers/draw-emblems.js?v=1.104.0"></script>
|
||||
<script defer src="modules/renderers/draw-military.js?v=1.104.13"></script>
|
||||
<script defer src="modules/renderers/draw-state-labels.js?v=1.106.0"></script>
|
||||
<script defer src="modules/renderers/draw-burg-labels.js?v=1.104.0"></script>
|
||||
<script defer src="modules/renderers/draw-military.js?v=1.108.5"></script>
|
||||
<script defer src="modules/renderers/draw-state-labels.js?v=1.108.1"></script>
|
||||
<script defer src="modules/renderers/draw-burg-labels.js?v=1.108.1"></script>
|
||||
<script defer src="modules/renderers/draw-burg-icons.js?v=1.104.0"></script>
|
||||
<script defer src="modules/renderers/draw-relief-icons.js?v=1.108.4"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
5
main.js
5
main.js
|
|
@ -1071,7 +1071,7 @@ function generatePrecipitation() {
|
|||
const from = west[0][0],
|
||||
to = west[west.length - 1][0];
|
||||
const y = (grid.points[from][1] + grid.points[to][1]) / 2;
|
||||
wind.append("text").attr("x", 20).attr("y", y).text("\u21C9");
|
||||
wind.append("text").attr("text-rendering", "optimizeSpeed").attr("x", 20).attr("y", y).text("\u21C9");
|
||||
}
|
||||
}
|
||||
if (easterly.length > 1) {
|
||||
|
|
@ -1082,6 +1082,7 @@ function generatePrecipitation() {
|
|||
const y = (grid.points[from][1] + grid.points[to][1]) / 2;
|
||||
wind
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("x", graphWidth - 52)
|
||||
.attr("y", y)
|
||||
.text("\u21C7");
|
||||
|
|
@ -1092,12 +1093,14 @@ function generatePrecipitation() {
|
|||
if (northerly)
|
||||
wind
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("x", graphWidth / 2)
|
||||
.attr("y", 42)
|
||||
.text("\u21CA");
|
||||
if (southerly)
|
||||
wind
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("x", graphWidth / 2)
|
||||
.attr("y", graphHeight - 20)
|
||||
.text("\u21C8");
|
||||
|
|
|
|||
|
|
@ -941,7 +941,6 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
defs.select("#land").selectAll("path, use").remove();
|
||||
defs.select("#water").selectAll("path, use").remove();
|
||||
viewbox.select("#coastline").selectAll("path, use").remove();
|
||||
drawFeatures();
|
||||
|
||||
// v1.104.0 introduced bugs with state borders
|
||||
regions
|
||||
|
|
@ -957,6 +956,24 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
}
|
||||
|
||||
if (isOlderThan("1.107.0")) {
|
||||
// v1.107.0 allowed custom images for markers and regiments
|
||||
if (layerIsOn("toggleMarkers")) drawMarkers();
|
||||
if (layerIsOn("toggleMilitary")) drawMilitary();
|
||||
}
|
||||
|
||||
if (isOlderThan("1.108.0")) {
|
||||
// v1.108.0 changed features rendering method
|
||||
pack.features.forEach(f => {
|
||||
// fix lakes with missing group
|
||||
if (f?.type === "lake" && !f.group) f.group = "freshwater";
|
||||
});
|
||||
drawFeatures();
|
||||
|
||||
// some old maps has incorrect "heights" groups
|
||||
viewbox.selectAll("#heights").remove();
|
||||
}
|
||||
|
||||
if (isOlderThan("1.109.0")) {
|
||||
// v1.107.0 changeв burg groups and added customizable icons
|
||||
icons.selectAll("circle, use").remove();
|
||||
|
||||
|
|
|
|||
|
|
@ -743,6 +743,7 @@ function showStatesChart() {
|
|||
|
||||
node
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.style("font-size", d => rn((d.r ** 0.97 * 4) / lp(d.data.name), 2) + "px")
|
||||
.selectAll("tspan")
|
||||
.data(d => d.data.name.split(exp))
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ async function parseLoadedData(data, mapVersion) {
|
|||
if (settings[23]) rescaleLabels.checked = +settings[23];
|
||||
if (settings[24]) urbanDensity = urbanDensityInput.value = +settings[24];
|
||||
if (settings[25]) longitudeInput.value = longitudeOutput.value = minmax(settings[25] || 50, 0, 100);
|
||||
if (settings[26]) growthRate.value = settings[26];
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -471,7 +472,7 @@ async function parseLoadedData(data, mapVersion) {
|
|||
|
||||
{
|
||||
// dynamically import and run auto-update script
|
||||
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.105.24");
|
||||
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.108.0");
|
||||
resolveVersionConflicts(mapVersion);
|
||||
}
|
||||
|
||||
|
|
@ -489,12 +490,16 @@ async function parseLoadedData(data, mapVersion) {
|
|||
if (textureHref) updateTextureSelectValue(textureHref);
|
||||
}
|
||||
|
||||
// data integrity checks
|
||||
{
|
||||
const cells = pack.cells;
|
||||
const {cells, vertices} = pack;
|
||||
|
||||
if (pack.cells.i.length !== pack.cells.state.length) {
|
||||
const message = "[Data integrity] Striping issue detected. To fix edit the heightmap in ERASE mode";
|
||||
ERROR && console.error(message);
|
||||
const cellsMismatch = cells.i.length !== cells.state.length;
|
||||
const featureVerticesMismatch = pack.features.some(f => f?.vertices?.some(vertex => !vertices.p[vertex]));
|
||||
|
||||
if (cellsMismatch || featureVerticesMismatch) {
|
||||
const message = "[Data integrity] Striping issue detected. To fix try to edit the heightmap in ERASE mode";
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const invalidStates = [...new Set(cells.state)].filter(s => !pack.states[s] || pack.states[s].removed);
|
||||
|
|
@ -745,7 +750,7 @@ async function parseLoadedData(data, mapVersion) {
|
|||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Loading error",
|
||||
maxWidth: "50em",
|
||||
maxWidth: "40em",
|
||||
buttons: {
|
||||
"Clear cache": () => cleanupData(),
|
||||
"Select file": function () {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ function prepareMapData() {
|
|||
stylePreset.value,
|
||||
+rescaleLabels.checked,
|
||||
urbanDensity,
|
||||
longitudeOutput.value
|
||||
longitudeOutput.value,
|
||||
growthRate.value
|
||||
].join("|");
|
||||
const coords = JSON.stringify(mapCoordinates);
|
||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ window.Markers = (function () {
|
|||
/*
|
||||
Default markers config:
|
||||
type - short description (snake-case)
|
||||
icon - unicode character, make sure it's supported by most of the browsers. Source: emojipedia.org
|
||||
icon - unicode character or url to image
|
||||
dx: icon offset in x direction, in pixels
|
||||
dy: icon offset in y direction, in pixels
|
||||
min: minimum number of candidates to add at least 1 marker
|
||||
|
|
|
|||
|
|
@ -380,7 +380,7 @@ window.Military = (function () {
|
|||
: gauss(options.year - 100, 150, 1, options.year - 6);
|
||||
const conflict = campaign ? ` during the ${campaign.name}` : "";
|
||||
const legend = `Regiment was formed in ${year} ${options.era}${conflict}. ${station}${troops}`;
|
||||
notes.push({id: `regiment${s.i}-${r.i}`, name: `${r.icon} ${r.name}`, legend});
|
||||
notes.push({id: `regiment${s.i}-${r.i}`, name: r.name, legend});
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
window.ReliefIcons = (function () {
|
||||
const draw = function () {
|
||||
TIME && console.time("drawRelief");
|
||||
terrain.selectAll("*").remove();
|
||||
|
||||
const cells = pack.cells;
|
||||
const density = terrain.attr("density") || 0.4;
|
||||
const size = 2 * (terrain.attr("size") || 1);
|
||||
const mod = 0.2 * size; // size modifier
|
||||
const relief = [];
|
||||
|
||||
for (const i of cells.i) {
|
||||
const height = cells.h[i];
|
||||
if (height < 20) continue; // no icons on water
|
||||
if (cells.r[i]) continue; // no icons on rivers
|
||||
const biome = cells.biome[i];
|
||||
if (height < 50 && biomesData.iconsDensity[biome] === 0) continue; // no icons for this biome
|
||||
|
||||
const polygon = getPackPolygon(i);
|
||||
const [minX, maxX] = d3.extent(polygon, p => p[0]);
|
||||
const [minY, maxY] = d3.extent(polygon, p => p[1]);
|
||||
|
||||
if (height < 50) placeBiomeIcons(i, biome);
|
||||
else placeReliefIcons(i);
|
||||
|
||||
function placeBiomeIcons() {
|
||||
const iconsDensity = biomesData.iconsDensity[biome] / 100;
|
||||
const radius = 2 / iconsDensity / density;
|
||||
if (Math.random() > iconsDensity * 10) return;
|
||||
|
||||
for (const [cx, cy] of poissonDiscSampler(minX, minY, maxX, maxY, radius)) {
|
||||
if (!d3.polygonContains(polygon, [cx, cy])) continue;
|
||||
let h = (4 + Math.random()) * size;
|
||||
const icon = getBiomeIcon(i, biomesData.icons[biome]);
|
||||
if (icon === "#relief-grass-1") h *= 1.2;
|
||||
relief.push({i: icon, x: rn(cx - h, 2), y: rn(cy - h, 2), s: rn(h * 2, 2)});
|
||||
}
|
||||
}
|
||||
|
||||
function placeReliefIcons(i) {
|
||||
const radius = 2 / density;
|
||||
const [icon, h] = getReliefIcon(i, height);
|
||||
|
||||
for (const [cx, cy] of poissonDiscSampler(minX, minY, maxX, maxY, radius)) {
|
||||
if (!d3.polygonContains(polygon, [cx, cy])) continue;
|
||||
relief.push({i: icon, x: rn(cx - h, 2), y: rn(cy - h, 2), s: rn(h * 2, 2)});
|
||||
}
|
||||
}
|
||||
|
||||
function getReliefIcon(i, h) {
|
||||
const temp = grid.cells.temp[pack.cells.g[i]];
|
||||
const type = h > 70 && temp < 0 ? "mountSnow" : h > 70 ? "mount" : "hill";
|
||||
const size = h > 70 ? (h - 45) * mod : minmax((h - 40) * mod, 3, 6);
|
||||
return [getIcon(type), size];
|
||||
}
|
||||
}
|
||||
|
||||
// sort relief icons by y+size
|
||||
relief.sort((a, b) => a.y + a.s - (b.y + b.s));
|
||||
|
||||
let reliefHTML = "";
|
||||
for (const r of relief) {
|
||||
reliefHTML += `<use href="${r.i}" x="${r.x}" y="${r.y}" width="${r.s}" height="${r.s}"/>`;
|
||||
}
|
||||
terrain.html(reliefHTML);
|
||||
|
||||
TIME && console.timeEnd("drawRelief");
|
||||
};
|
||||
|
||||
function getBiomeIcon(i, b) {
|
||||
let type = b[Math.floor(Math.random() * b.length)];
|
||||
const temp = grid.cells.temp[pack.cells.g[i]];
|
||||
if (type === "conifer" && temp < 0) type = "coniferSnow";
|
||||
return getIcon(type);
|
||||
}
|
||||
|
||||
function getVariant(type) {
|
||||
switch (type) {
|
||||
case "mount":
|
||||
return rand(2, 7);
|
||||
case "mountSnow":
|
||||
return rand(1, 6);
|
||||
case "hill":
|
||||
return rand(2, 5);
|
||||
case "conifer":
|
||||
return 2;
|
||||
case "coniferSnow":
|
||||
return 1;
|
||||
case "swamp":
|
||||
return rand(2, 3);
|
||||
case "cactus":
|
||||
return rand(1, 3);
|
||||
case "deadTree":
|
||||
return rand(1, 2);
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
function getOldIcon(type) {
|
||||
switch (type) {
|
||||
case "mountSnow":
|
||||
return "mount";
|
||||
case "vulcan":
|
||||
return "mount";
|
||||
case "coniferSnow":
|
||||
return "conifer";
|
||||
case "cactus":
|
||||
return "dune";
|
||||
case "deadTree":
|
||||
return "dune";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
function getIcon(type) {
|
||||
const set = terrain.attr("set") || "simple";
|
||||
if (set === "simple") return "#relief-" + getOldIcon(type) + "-1";
|
||||
if (set === "colored") return "#relief-" + type + "-" + getVariant(type);
|
||||
if (set === "gray") return "#relief-" + type + "-" + getVariant(type) + "-bw";
|
||||
return "#relief-" + getOldIcon(type) + "-1"; // simple
|
||||
}
|
||||
|
||||
return {draw};
|
||||
})();
|
||||
|
|
@ -19,6 +19,7 @@ function drawBurgLabels() {
|
|||
.data(burgsInGroup)
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("id", d => "burgLabel" + d.i)
|
||||
.attr("data-id", d => d.i)
|
||||
.attr("x", d => d.x)
|
||||
|
|
@ -38,6 +39,7 @@ function drawBurgLabel(burg) {
|
|||
|
||||
group
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("id", "burgLabel" + burg.i)
|
||||
.attr("data-id", burg.i)
|
||||
.attr("x", burg.x)
|
||||
|
|
|
|||
|
|
@ -2,55 +2,60 @@
|
|||
|
||||
function drawFeatures() {
|
||||
TIME && console.time("drawFeatures");
|
||||
const featurePaths = defs.select("#featurePaths");
|
||||
const landMask = defs.select("#land");
|
||||
const waterMask = defs.select("#water");
|
||||
|
||||
const html = {
|
||||
paths: [],
|
||||
landMask: [],
|
||||
waterMask: ['<rect x="0" y="0" width="100%" height="100%" fill="white" />'],
|
||||
coastline: {},
|
||||
lakes: {}
|
||||
};
|
||||
|
||||
for (const feature of pack.features) {
|
||||
if (!feature || feature.type === "ocean") continue;
|
||||
|
||||
featurePaths
|
||||
.append("path")
|
||||
.attr("d", getFeaturePath(feature))
|
||||
.attr("id", "feature_" + feature.i)
|
||||
.attr("data-f", feature.i);
|
||||
html.paths.push(`<path d="${getFeaturePath(feature)}" id="feature_${feature.i}" data-f="${feature.i}"></path>`);
|
||||
|
||||
if (feature.type === "lake") {
|
||||
landMask
|
||||
.append("use")
|
||||
.attr("href", "#feature_" + feature.i)
|
||||
.attr("data-f", feature.i)
|
||||
.attr("fill", "black");
|
||||
lakes
|
||||
.select(`#${feature.group}`)
|
||||
.append("use")
|
||||
.attr("href", "#feature_" + feature.i)
|
||||
.attr("data-f", feature.i);
|
||||
html.landMask.push(`<use href="#feature_${feature.i}" data-f="${feature.i}" fill="black"></use>`);
|
||||
|
||||
const lakeGroup = feature.group || "freshwater";
|
||||
if (!html.lakes[lakeGroup]) html.lakes[lakeGroup] = [];
|
||||
html.lakes[lakeGroup].push(`<use href="#feature_${feature.i}" data-f="${feature.i}"></use>`);
|
||||
} else {
|
||||
landMask
|
||||
.append("use")
|
||||
.attr("href", "#feature_" + feature.i)
|
||||
.attr("data-f", feature.i)
|
||||
.attr("fill", "white");
|
||||
waterMask
|
||||
.append("use")
|
||||
.attr("href", "#feature_" + feature.i)
|
||||
.attr("data-f", feature.i)
|
||||
.attr("fill", "black");
|
||||
const coastlineGroup = feature.group === "lake_island" ? "#lake_island" : "#sea_island";
|
||||
coastline
|
||||
.select(coastlineGroup)
|
||||
.append("use")
|
||||
.attr("href", "#feature_" + feature.i)
|
||||
.attr("data-f", feature.i);
|
||||
html.landMask.push(`<use href="#feature_${feature.i}" data-f="${feature.i}" fill="white"></use>`);
|
||||
html.waterMask.push(`<use href="#feature_${feature.i}" data-f="${feature.i}" fill="black"></use>`);
|
||||
|
||||
const coastlineGroup = feature.group === "lake_island" ? "lake_island" : "sea_island";
|
||||
if (!html.coastline[coastlineGroup]) html.coastline[coastlineGroup] = [];
|
||||
html.coastline[coastlineGroup].push(`<use href="#feature_${feature.i}" data-f="${feature.i}"></use>`);
|
||||
}
|
||||
}
|
||||
|
||||
defs.select("#featurePaths").html(html.paths.join(""));
|
||||
defs.select("#land").html(html.landMask.join(""));
|
||||
defs.select("#water").html(html.waterMask.join(""));
|
||||
|
||||
coastline.selectAll("g").each(function () {
|
||||
const paths = html.coastline[this.id] || [];
|
||||
d3.select(this).html(paths.join(""));
|
||||
});
|
||||
|
||||
lakes.selectAll("g").each(function () {
|
||||
const paths = html.lakes[this.id] || [];
|
||||
d3.select(this).html(paths.join(""));
|
||||
});
|
||||
|
||||
TIME && console.timeEnd("drawFeatures");
|
||||
}
|
||||
|
||||
function getFeaturePath(feature) {
|
||||
const points = feature.vertices.map(vertex => pack.vertices.p[vertex]);
|
||||
if (points.some(point => point === undefined)) {
|
||||
ERROR && console.error("Undefined point in getFeaturePath");
|
||||
return "";
|
||||
}
|
||||
|
||||
const simplifiedPoints = simplify(points, 0.3);
|
||||
const clippedPoints = clipPoly(simplifiedPoints, 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -42,9 +42,12 @@ function drawMarker(marker, rescale = 1) {
|
|||
const viewX = rn(x - zoomSize / 2, 1);
|
||||
const viewY = rn(y - zoomSize, 1);
|
||||
|
||||
const isExternal = icon.startsWith("http") || icon.startsWith("data:image");
|
||||
|
||||
return /* html */ `
|
||||
<svg id="${id}" viewbox="0 0 30 30" width="${zoomSize}" height="${zoomSize}" x="${viewX}" y="${viewY}">
|
||||
<g>${getPin(pin, fill, stroke)}</g>
|
||||
<text x="${dx}%" y="${dy}%" font-size="${px}px" >${icon}</text>
|
||||
<text x="${dx}%" y="${dy}%" font-size="${px}px" >${isExternal ? "" : icon}</text>
|
||||
<image x="${dx / 2}%" y="${dy / 2}%" width="${px}px" height="${px}px" href="${isExternal ? icon : ""}" />
|
||||
</svg>`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ const drawRegiments = function (regiments, s) {
|
|||
g.append("text")
|
||||
.attr("x", d => d.x)
|
||||
.attr("y", d => d.y)
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.text(d => Military.getTotal(d));
|
||||
g.append("rect")
|
||||
.attr("fill", "currentColor")
|
||||
|
|
@ -52,9 +53,17 @@ const drawRegiments = function (regiments, s) {
|
|||
.attr("height", h);
|
||||
g.append("text")
|
||||
.attr("class", "regimentIcon")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("x", d => x(d) - size)
|
||||
.attr("y", d => d.y)
|
||||
.text(d => d.icon);
|
||||
.text(d => (d.icon.startsWith("http") || d.icon.startsWith("data:image") ? "" : d.icon));
|
||||
g.append("image")
|
||||
.attr("class", "regimentImage")
|
||||
.attr("x", d => x(d) - h)
|
||||
.attr("y", d => y(d))
|
||||
.attr("height", h)
|
||||
.attr("width", h)
|
||||
.attr("href", d => (d.icon.startsWith("http") || d.icon.startsWith("data:image") ? d.icon : ""));
|
||||
};
|
||||
|
||||
const drawRegiment = function (reg, stateId) {
|
||||
|
|
@ -84,7 +93,11 @@ const drawRegiment = function (reg, stateId) {
|
|||
.attr("transform", `rotate(${reg.angle || 0})`)
|
||||
.attr("transform-origin", `${reg.x}px ${reg.y}px`);
|
||||
g.append("rect").attr("x", x1).attr("y", y1).attr("width", w).attr("height", h);
|
||||
g.append("text").attr("x", reg.x).attr("y", reg.y).text(Military.getTotal(reg));
|
||||
g.append("text")
|
||||
.attr("x", reg.x)
|
||||
.attr("y", reg.y)
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.text(Military.getTotal(reg));
|
||||
g.append("rect")
|
||||
.attr("fill", "currentColor")
|
||||
.attr("x", x1 - h)
|
||||
|
|
@ -93,9 +106,17 @@ const drawRegiment = function (reg, stateId) {
|
|||
.attr("height", h);
|
||||
g.append("text")
|
||||
.attr("class", "regimentIcon")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("x", x1 - size)
|
||||
.attr("y", reg.y)
|
||||
.text(reg.icon);
|
||||
.text(reg.icon.startsWith("http") || reg.icon.startsWith("data:image") ? "" : reg.icon);
|
||||
g.append("image")
|
||||
.attr("class", "regimentImage")
|
||||
.attr("x", x1 - h)
|
||||
.attr("y", y1)
|
||||
.attr("height", h)
|
||||
.attr("width", h)
|
||||
.attr("href", reg.icon.startsWith("http") || reg.icon.startsWith("data:image") ? reg.icon : "");
|
||||
};
|
||||
|
||||
// move one regiment to another
|
||||
|
|
@ -122,5 +143,13 @@ const moveRegiment = function (reg, x, y) {
|
|||
el.select(".regimentIcon")
|
||||
.transition(move)
|
||||
.attr("x", x1(x) - size)
|
||||
.attr("y", y);
|
||||
.attr("y", y)
|
||||
.attr("height", "6")
|
||||
.attr("width", "6");
|
||||
el.select(".regimentImage")
|
||||
.transition(move)
|
||||
.attr("x", x1(x) - h)
|
||||
.attr("y", y1(y))
|
||||
.attr("height", "6")
|
||||
.attr("width", "6");
|
||||
};
|
||||
|
|
|
|||
124
modules/renderers/draw-relief-icons.js
Normal file
124
modules/renderers/draw-relief-icons.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"use strict";
|
||||
|
||||
function drawReliefIcons() {
|
||||
TIME && console.time("drawRelief");
|
||||
terrain.selectAll("*").remove();
|
||||
|
||||
const cells = pack.cells;
|
||||
const density = terrain.attr("density") || 0.4;
|
||||
const size = 2 * (terrain.attr("size") || 1);
|
||||
const mod = 0.2 * size; // size modifier
|
||||
const relief = [];
|
||||
|
||||
for (const i of cells.i) {
|
||||
const height = cells.h[i];
|
||||
if (height < 20) continue; // no icons on water
|
||||
if (cells.r[i]) continue; // no icons on rivers
|
||||
const biome = cells.biome[i];
|
||||
if (height < 50 && biomesData.iconsDensity[biome] === 0) continue; // no icons for this biome
|
||||
|
||||
const polygon = getPackPolygon(i);
|
||||
const [minX, maxX] = d3.extent(polygon, p => p[0]);
|
||||
const [minY, maxY] = d3.extent(polygon, p => p[1]);
|
||||
|
||||
if (height < 50) placeBiomeIcons(i, biome);
|
||||
else placeReliefIcons(i);
|
||||
|
||||
function placeBiomeIcons() {
|
||||
const iconsDensity = biomesData.iconsDensity[biome] / 100;
|
||||
const radius = 2 / iconsDensity / density;
|
||||
if (Math.random() > iconsDensity * 10) return;
|
||||
|
||||
for (const [cx, cy] of poissonDiscSampler(minX, minY, maxX, maxY, radius)) {
|
||||
if (!d3.polygonContains(polygon, [cx, cy])) continue;
|
||||
let h = (4 + Math.random()) * size;
|
||||
const icon = getBiomeIcon(i, biomesData.icons[biome]);
|
||||
if (icon === "#relief-grass-1") h *= 1.2;
|
||||
relief.push({i: icon, x: rn(cx - h, 2), y: rn(cy - h, 2), s: rn(h * 2, 2)});
|
||||
}
|
||||
}
|
||||
|
||||
function placeReliefIcons(i) {
|
||||
const radius = 2 / density;
|
||||
const [icon, h] = getReliefIcon(i, height);
|
||||
|
||||
for (const [cx, cy] of poissonDiscSampler(minX, minY, maxX, maxY, radius)) {
|
||||
if (!d3.polygonContains(polygon, [cx, cy])) continue;
|
||||
relief.push({i: icon, x: rn(cx - h, 2), y: rn(cy - h, 2), s: rn(h * 2, 2)});
|
||||
}
|
||||
}
|
||||
|
||||
function getReliefIcon(i, h) {
|
||||
const temp = grid.cells.temp[pack.cells.g[i]];
|
||||
const type = h > 70 && temp < 0 ? "mountSnow" : h > 70 ? "mount" : "hill";
|
||||
const size = h > 70 ? (h - 45) * mod : minmax((h - 40) * mod, 3, 6);
|
||||
return [getIcon(type), size];
|
||||
}
|
||||
}
|
||||
|
||||
// sort relief icons by y+size
|
||||
relief.sort((a, b) => a.y + a.s - (b.y + b.s));
|
||||
|
||||
const reliefHTML = new Array(relief.length);
|
||||
for (const r of relief) {
|
||||
reliefHTML.push(`<use href="${r.i}" x="${r.x}" y="${r.y}" width="${r.s}" height="${r.s}"/>`);
|
||||
}
|
||||
terrain.html(reliefHTML.join(""));
|
||||
|
||||
TIME && console.timeEnd("drawRelief");
|
||||
|
||||
function getBiomeIcon(i, b) {
|
||||
let type = b[Math.floor(Math.random() * b.length)];
|
||||
const temp = grid.cells.temp[pack.cells.g[i]];
|
||||
if (type === "conifer" && temp < 0) type = "coniferSnow";
|
||||
return getIcon(type);
|
||||
}
|
||||
|
||||
function getVariant(type) {
|
||||
switch (type) {
|
||||
case "mount":
|
||||
return rand(2, 7);
|
||||
case "mountSnow":
|
||||
return rand(1, 6);
|
||||
case "hill":
|
||||
return rand(2, 5);
|
||||
case "conifer":
|
||||
return 2;
|
||||
case "coniferSnow":
|
||||
return 1;
|
||||
case "swamp":
|
||||
return rand(2, 3);
|
||||
case "cactus":
|
||||
return rand(1, 3);
|
||||
case "deadTree":
|
||||
return rand(1, 2);
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
function getOldIcon(type) {
|
||||
switch (type) {
|
||||
case "mountSnow":
|
||||
return "mount";
|
||||
case "vulcan":
|
||||
return "mount";
|
||||
case "coniferSnow":
|
||||
return "conifer";
|
||||
case "cactus":
|
||||
return "dune";
|
||||
case "deadTree":
|
||||
return "dune";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
function getIcon(type) {
|
||||
const set = terrain.attr("set") || "simple";
|
||||
if (set === "simple") return "#relief-" + getOldIcon(type) + "-1";
|
||||
if (set === "colored") return "#relief-" + type + "-" + getVariant(type);
|
||||
if (set === "gray") return "#relief-" + type + "-" + getVariant(type) + "-bw";
|
||||
return "#relief-" + getOldIcon(type) + "-1"; // simple
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ function drawScaleBar(scaleBar, scaleLevel) {
|
|||
.data(d3.range(0, 6))
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("x", d => rn((d * length) / 5, 2))
|
||||
.attr("y", 0)
|
||||
.attr("dy", "-.6em")
|
||||
|
|
@ -52,6 +53,7 @@ function drawScaleBar(scaleBar, scaleLevel) {
|
|||
if (label) {
|
||||
texts
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("x", (length + 1) / 2)
|
||||
.attr("dy", ".6em")
|
||||
.attr("dominant-baseline", "text-before-edge")
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ function drawStateLabels(list) {
|
|||
|
||||
const textElement = textGroup
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("id", "stateLabel" + stateId)
|
||||
.append("textPath")
|
||||
.attr("startOffset", "50%")
|
||||
|
|
|
|||
|
|
@ -131,7 +131,9 @@ class Battle {
|
|||
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, " "));
|
||||
headers += `<th data-tip="${label}">${u.icon}</th>`;
|
||||
const isExternal = u.icon.startsWith("http") || u.icon.startsWith("data:image");
|
||||
const iconHTML = isExternal ? `<img src="${u.icon}" width="15" height="15">` : u.icon;
|
||||
headers += `<th data-tip="${label}">${iconHTML}</th>`;
|
||||
}
|
||||
|
||||
headers += "<th data-tip='Total military''>Total</th></tr></thead>";
|
||||
|
|
@ -145,9 +147,13 @@ class Battle {
|
|||
const state = pack.states[regiment.state];
|
||||
const distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScale) | 0; // distance between regiment and its base
|
||||
const color = state.color[0] === "#" ? state.color : "#999";
|
||||
|
||||
const isExternal = regiment.icon.startsWith("http") || regiment.icon.startsWith("data:image");
|
||||
const iconHtml = isExternal
|
||||
? `<image href="${regiment.icon}" x="0.1em" y="0.1em" width="1.2em" height="1.2em"></image>`
|
||||
: `<text x="50%" y="1em" style="text-anchor: middle">${regiment.icon}</text>`;
|
||||
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em; stroke: #333">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}"></rect>
|
||||
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}"></rect>${iconHtml}</svg>`;
|
||||
const body = `<tbody id="battle${state.i}-${regiment.i}">`;
|
||||
|
||||
let initial = `<tr class="battleInitial"><td>${icon}</td><td class="regiment" data-tip="${
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ function editBiomes() {
|
|||
}
|
||||
|
||||
function regenerateIcons() {
|
||||
ReliefIcons.draw();
|
||||
drawReliefIcons();
|
||||
if (!layerIsOn("toggleRelief")) toggleRelief();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -275,6 +275,7 @@ function drawLegend(name, data) {
|
|||
|
||||
labels
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.text(data[i][2])
|
||||
.attr("x", offset + colorBoxSize * 1.6)
|
||||
.attr("y", fontSize / 1.6 + lineHeight + l * lineHeight + vOffset);
|
||||
|
|
@ -285,6 +286,7 @@ function drawLegend(name, data) {
|
|||
const offset = colOffset + legend.node().getBBox().width / 2;
|
||||
labels
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("font-weight", "bold")
|
||||
.attr("font-size", "1.2em")
|
||||
|
|
@ -976,25 +978,66 @@ function selectIcon(initial, callback) {
|
|||
const cell = row.insertCell(i % 17);
|
||||
cell.innerHTML = icons[i];
|
||||
}
|
||||
|
||||
// find external images used as icons and show them
|
||||
const externalResources = new Set();
|
||||
const isExternal = url => url.startsWith("http") || url.startsWith("data:image");
|
||||
|
||||
options.military.forEach(unit => {
|
||||
if (isExternal(unit.icon)) externalResources.add(unit.icon);
|
||||
});
|
||||
|
||||
pack.states.forEach(state => {
|
||||
state?.military?.forEach(regiment => {
|
||||
if (isExternal(regiment.icon)) externalResources.add(regiment.icon);
|
||||
});
|
||||
});
|
||||
|
||||
externalResources.forEach(addExternalImage);
|
||||
}
|
||||
|
||||
input.oninput = e => callback(input.value);
|
||||
input.oninput = () => callback(input.value);
|
||||
|
||||
table.onclick = e => {
|
||||
if (e.target.tagName === "TD") {
|
||||
input.value = e.target.textContent;
|
||||
callback(input.value);
|
||||
}
|
||||
};
|
||||
|
||||
table.onmouseover = e => {
|
||||
if (e.target.tagName === "TD") tip(`Click to select ${e.target.textContent} icon`);
|
||||
};
|
||||
|
||||
function addExternalImage(url) {
|
||||
const addedIcons = byId("addedIcons");
|
||||
const image = document.createElement("div");
|
||||
image.style.cssText = `width: 2.2em; height: 2.2em; background-size: cover; background-image: url(${url})`;
|
||||
addedIcons.appendChild(image);
|
||||
image.onclick = () => callback(url);
|
||||
}
|
||||
|
||||
byId("addImage").onclick = function () {
|
||||
const input = this.previousElementSibling;
|
||||
const ulr = input.value;
|
||||
if (!ulr) return tip("Enter image URL to add", false, "error", 4000);
|
||||
if (!ulr.match(/^((http|https):\/\/)|data\:image\//)) return tip("Enter valid URL", false, "error", 4000);
|
||||
addExternalImage(ulr);
|
||||
callback(ulr);
|
||||
input.value = "";
|
||||
};
|
||||
|
||||
byId("addedIcons")
|
||||
.querySelectorAll("div")
|
||||
.forEach(div => {
|
||||
div.onclick = () => callback(div.style.backgroundImage.slice(5, -2));
|
||||
});
|
||||
|
||||
$("#iconSelector").dialog({
|
||||
width: fitContent(),
|
||||
title: "Select Icon",
|
||||
buttons: {
|
||||
Apply: function () {
|
||||
callback(input.value || "⠀");
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Close: function () {
|
||||
|
|
@ -1060,7 +1103,7 @@ function refreshAllEditors() {
|
|||
// dynamically loaded editors
|
||||
async function editStates() {
|
||||
if (customization) return;
|
||||
const Editor = await import("../dynamic/editors/states-editor.js?v=1.106.1");
|
||||
const Editor = await import("../dynamic/editors/states-editor.js?v=1.108.1");
|
||||
Editor.open();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ function drawLayers() {
|
|||
if (layerIsOn("toggleCoordinates")) drawCoordinates();
|
||||
if (layerIsOn("toggleCompass")) compass.style("display", "block");
|
||||
if (layerIsOn("toggleRivers")) drawRivers();
|
||||
if (layerIsOn("toggleRelief")) ReliefIcons.draw();
|
||||
if (layerIsOn("toggleRelief")) drawReliefIcons();
|
||||
if (layerIsOn("toggleReligions")) drawReligions();
|
||||
if (layerIsOn("toggleCultures")) drawCultures();
|
||||
if (layerIsOn("toggleStates")) drawStates();
|
||||
|
|
@ -424,13 +424,14 @@ function drawIce() {
|
|||
const {temp, h} = cells;
|
||||
Math.random = aleaPRNG(seed);
|
||||
|
||||
const ICEBERG_MAX_TEMP = 1;
|
||||
const ICE_SHIELD_MAX_TEMP = -8;
|
||||
const ICEBERG_MAX_TEMP = 0;
|
||||
const GLACIER_MAX_TEMP = -8;
|
||||
const minMaxTemp = d3.min(temp);
|
||||
|
||||
// very cold: draw ice shields
|
||||
// cold land: draw glaciers
|
||||
{
|
||||
const type = "iceShield";
|
||||
const getType = cellId => (temp[cellId] <= ICE_SHIELD_MAX_TEMP ? type : null);
|
||||
const getType = cellId => (h[cellId] >= 20 && temp[cellId] <= GLACIER_MAX_TEMP ? type : null);
|
||||
const isolines = getIsolines(grid, getType, {polygons: true});
|
||||
isolines[type]?.polygons?.forEach(points => {
|
||||
const clipped = clipPoly(points);
|
||||
|
|
@ -438,21 +439,18 @@ function drawIce() {
|
|||
});
|
||||
}
|
||||
|
||||
// mildly cold: draw icebergs
|
||||
// cold water: draw icebergs
|
||||
for (const cellId of grid.cells.i) {
|
||||
const t = temp[cellId];
|
||||
if (t > ICEBERG_MAX_TEMP) continue; // too warm: no icebergs
|
||||
if (t <= ICE_SHIELD_MAX_TEMP) continue; // already drawn as ice shield
|
||||
if (h[cellId] >= 20) continue; // no icebergs on land
|
||||
if (t > ICEBERG_MAX_TEMP) continue; // too warm: no icebergs
|
||||
if (features[cells.f[cellId]].type === "lake") continue; // no icebers on lakes
|
||||
if (P(0.8)) continue; // skip most of eligible cells
|
||||
|
||||
const tNormalized = normalize(t, -8, 2);
|
||||
const randomFactor = t > -5 ? 0.4 + rand() * 1.2 : 1;
|
||||
if (P(tNormalized ** 0.5 * randomFactor)) continue; // cold: skip some cells
|
||||
|
||||
let defaultSize = 1 - tNormalized; // iceberg size: 0 = zero size, 1 = full size
|
||||
if (cells.t[cellId] === -1) defaultSize /= 1.3; // coasline: smaller icebergs
|
||||
const size = minmax(rn(defaultSize * randomFactor, 2), 0.08, 1);
|
||||
const randomFactor = 0.8 + rand() * 0.4; // random size factor
|
||||
let baseSize = (1 - normalize(t, minMaxTemp, 1)) * 0.8; // size: 0 = zero size, 1 = full size
|
||||
if (cells.t[cellId] === -1) baseSize /= 1.3; // coasline: smaller icebergs
|
||||
const size = minmax(rn(baseSize * randomFactor, 2), 0.1, 1);
|
||||
|
||||
const [cx, cy] = grid.points[cellId];
|
||||
const points = getGridPolygon(cellId).map(([x, y]) => [rn(lerp(cx, x, size), 2), rn(lerp(cy, y, size), 2)]);
|
||||
|
|
@ -723,6 +721,7 @@ function drawCoordinates() {
|
|||
.data(data)
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("x", d => d.x)
|
||||
.attr("y", d => d.y)
|
||||
.text(d => d.text);
|
||||
|
|
@ -743,7 +742,7 @@ function toggleCompass(event) {
|
|||
function toggleRelief(event) {
|
||||
if (!layerIsOn("toggleRelief")) {
|
||||
turnButtonOn("toggleRelief");
|
||||
if (!terrain.selectAll("*").size()) ReliefIcons.draw();
|
||||
if (!terrain.selectAll("*").size()) drawReliefIcons();
|
||||
$("#terrain").fadeIn();
|
||||
if (event && isCtrlClick(event)) editStyle("terrain");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,25 +8,24 @@ function editMarker(markerI) {
|
|||
|
||||
elSelected = d3.select(element).raise().call(d3.drag().on("start", dragMarker)).classed("draggable", true);
|
||||
|
||||
if (document.getElementById("notesEditor").offsetParent) editNotes(element.id, element.id);
|
||||
if (byId("notesEditor").offsetParent) editNotes(element.id, element.id);
|
||||
|
||||
// dom elements
|
||||
const markerType = document.getElementById("markerType");
|
||||
const markerIcon = document.getElementById("markerIcon");
|
||||
const markerIconSelect = document.getElementById("markerIconSelect");
|
||||
const markerIconSize = document.getElementById("markerIconSize");
|
||||
const markerIconShiftX = document.getElementById("markerIconShiftX");
|
||||
const markerIconShiftY = document.getElementById("markerIconShiftY");
|
||||
const markerSize = document.getElementById("markerSize");
|
||||
const markerPin = document.getElementById("markerPin");
|
||||
const markerFill = document.getElementById("markerFill");
|
||||
const markerStroke = document.getElementById("markerStroke");
|
||||
const markerType = byId("markerType");
|
||||
const markerIconSelect = byId("markerIconSelect");
|
||||
const markerIconSize = byId("markerIconSize");
|
||||
const markerIconShiftX = byId("markerIconShiftX");
|
||||
const markerIconShiftY = byId("markerIconShiftY");
|
||||
const markerSize = byId("markerSize");
|
||||
const markerPin = byId("markerPin");
|
||||
const markerFill = byId("markerFill");
|
||||
const markerStroke = byId("markerStroke");
|
||||
|
||||
const markerNotes = document.getElementById("markerNotes");
|
||||
const markerLock = document.getElementById("markerLock");
|
||||
const addMarker = document.getElementById("addMarker");
|
||||
const markerAdd = document.getElementById("markerAdd");
|
||||
const markerRemove = document.getElementById("markerRemove");
|
||||
const markerNotes = byId("markerNotes");
|
||||
const markerLock = byId("markerLock");
|
||||
const addMarker = byId("addMarker");
|
||||
const markerAdd = byId("markerAdd");
|
||||
const markerRemove = byId("markerRemove");
|
||||
|
||||
updateInputs();
|
||||
|
||||
|
|
@ -39,8 +38,7 @@ function editMarker(markerI) {
|
|||
|
||||
const listeners = [
|
||||
listen(markerType, "change", changeMarkerType),
|
||||
listen(markerIcon, "input", changeMarkerIcon),
|
||||
listen(markerIconSelect, "click", selectMarkerIcon),
|
||||
listen(markerIconSelect, "click", changeMarkerIcon),
|
||||
listen(markerIconSize, "input", changeIconSize),
|
||||
listen(markerIconShiftX, "input", changeIconShiftX),
|
||||
listen(markerIconShiftY, "input", changeIconShiftY),
|
||||
|
|
@ -61,7 +59,7 @@ function editMarker(markerI) {
|
|||
return [element, marker];
|
||||
}
|
||||
|
||||
const element = document.getElementById(`marker${markerI}`);
|
||||
const element = byId(`marker${markerI}`);
|
||||
const marker = pack.markers.find(({i}) => i === markerI);
|
||||
return [element, marker];
|
||||
}
|
||||
|
|
@ -97,19 +95,20 @@ function editMarker(markerI) {
|
|||
}
|
||||
|
||||
function updateInputs() {
|
||||
const {icon, type = "", size = 30, dx = 50, dy = 50, px = 12, stroke = "#000000", fill = "#ffffff", pin = "bubble", lock} = marker;
|
||||
byId("markerIcon").innerHTML = marker.icon.startsWith("http") || marker.icon.startsWith("data:image")
|
||||
? `<img src="${marker.icon}" style="width: 1em; height: 1em;">`
|
||||
: marker.icon;
|
||||
|
||||
markerType.value = type;
|
||||
markerIcon.value = icon;
|
||||
markerIconSize.value = px;
|
||||
markerIconShiftX.value = dx;
|
||||
markerIconShiftY.value = dy;
|
||||
markerSize.value = size;
|
||||
markerPin.value = pin;
|
||||
markerFill.value = fill;
|
||||
markerStroke.value = stroke;
|
||||
markerType.value = marker.type || "";
|
||||
markerIconSize.value = marker.px || 12;
|
||||
markerIconShiftX.value = marker.dx || 50;
|
||||
markerIconShiftY.value = marker.dy || 50;
|
||||
markerSize.value = marker.size || 30;
|
||||
markerPin.value = marker.pin || "bubble";
|
||||
markerFill.value = marker.fill || "#ffffff";
|
||||
markerStroke.value = marker.stroke || "#000000";
|
||||
|
||||
markerLock.className = lock ? "icon-lock" : "icon-lock-open";
|
||||
markerLock.className = marker.lock ? "icon-lock" : "icon-lock-open";
|
||||
}
|
||||
|
||||
function changeMarkerType() {
|
||||
|
|
@ -117,18 +116,12 @@ function editMarker(markerI) {
|
|||
}
|
||||
|
||||
function changeMarkerIcon() {
|
||||
const icon = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
selectIcon(marker.icon, value => {
|
||||
const isExternal = value.startsWith("http") || value.startsWith("data:image");
|
||||
byId("markerIcon").innerHTML = isExternal ? `<img src="${value}" style="width: 1em; height: 1em;">` : value;
|
||||
|
||||
function selectMarkerIcon() {
|
||||
selectIcon(marker.icon, icon => {
|
||||
markerIcon.value = icon;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
marker.icon = value;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
});
|
||||
|
|
@ -165,7 +158,7 @@ function editMarker(markerI) {
|
|||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.size = size;
|
||||
const {i, x, y, hidden} = marker;
|
||||
const el = !hidden && document.getElementById(`marker${i}`);
|
||||
const el = !hidden && byId(`marker${i}`);
|
||||
if (!el) return;
|
||||
|
||||
const zoomedSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
|
||||
|
|
@ -201,12 +194,23 @@ function editMarker(markerI) {
|
|||
}
|
||||
|
||||
function redrawIcon({i, hidden, icon, dx = 50, dy = 50, px = 12}) {
|
||||
const iconElement = !hidden && document.querySelector(`#marker${i} > text`);
|
||||
if (iconElement) {
|
||||
iconElement.innerHTML = icon;
|
||||
iconElement.setAttribute("x", dx + "%");
|
||||
iconElement.setAttribute("y", dy + "%");
|
||||
iconElement.setAttribute("font-size", px + "px");
|
||||
const isExternal = icon.startsWith("http") || icon.startsWith("data:image");
|
||||
|
||||
const iconText = !hidden && document.querySelector(`#marker${i} > text`);
|
||||
if (iconText) {
|
||||
iconText.innerHTML = isExternal ? "" : icon;
|
||||
iconText.setAttribute("x", dx + "%");
|
||||
iconText.setAttribute("y", dy + "%");
|
||||
iconText.setAttribute("font-size", px + "px");
|
||||
}
|
||||
|
||||
const iconImage = !hidden && document.querySelector(`#marker${i} > image`);
|
||||
if (iconImage) {
|
||||
iconImage.setAttribute("x", dx / 2 + "%");
|
||||
iconImage.setAttribute("y", dy / 2 + "%");
|
||||
iconImage.setAttribute("width", px + "px");
|
||||
iconImage.setAttribute("height", px + "px");
|
||||
iconImage.setAttribute("href", isExternal ? icon : "");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -241,10 +245,10 @@ function editMarker(markerI) {
|
|||
}
|
||||
|
||||
function deleteMarker() {
|
||||
Markers.deleteMarker(marker.i)
|
||||
Markers.deleteMarker(marker.i);
|
||||
element.remove();
|
||||
$("#markerEditor").dialog("close");
|
||||
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
|
||||
if (byId("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function closeMarkerEditor() {
|
||||
|
|
|
|||
|
|
@ -69,18 +69,24 @@ function overviewMarkers() {
|
|||
function addLines() {
|
||||
const lines = pack.markers
|
||||
.map(({i, type, icon, pinned, lock}) => {
|
||||
return `<div class="states" data-i=${i} data-type="${type}">
|
||||
<div data-tip="Marker icon and type" style="width:12em">${icon} ${type}</div>
|
||||
<span style="padding-right:.1em" data-tip="Edit marker" class="icon-pencil"></span>
|
||||
<span style="padding-right:.1em" data-tip="Focus on marker position" class="icon-dot-circled pointer"></span>
|
||||
<span style="padding-right:.1em" data-tip="Pin marker (display only pinned markers)" class="icon-pin ${
|
||||
pinned ? "" : "inactive"
|
||||
}" pointer"></span>
|
||||
<span style="padding-right:.1em" class="locks pointer ${
|
||||
lock ? "icon-lock" : "icon-lock-open inactive"
|
||||
}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span data-tip="Remove marker" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
return /* html */ `
|
||||
<div class="states" data-i=${i} data-type="${type}">
|
||||
${
|
||||
icon.startsWith("http") || icon.startsWith("data:image")
|
||||
? `<img src="${icon}" data-tip="Marker icon" style="width:1.2em; height:1.2em; vertical-align: middle;">`
|
||||
: `<span data-tip="Marker icon" style="width:1.2em">${icon}</span>`
|
||||
}
|
||||
<div data-tip="Marker type" style="width:10em">${type}</div>
|
||||
<span style="padding-right:.1em" data-tip="Edit marker" class="icon-pencil"></span>
|
||||
<span style="padding-right:.1em" data-tip="Focus on marker position" class="icon-dot-circled pointer"></span>
|
||||
<span style="padding-right:.1em" data-tip="Pin marker (display only pinned markers)" class="icon-pin ${
|
||||
pinned ? "" : "inactive"
|
||||
}" pointer"></span>
|
||||
<span style="padding-right:.1em" class="locks pointer ${
|
||||
lock ? "icon-lock" : "icon-lock-open inactive"
|
||||
}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span data-tip="Remove marker" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
|
|
|
|||
|
|
@ -284,7 +284,14 @@ function overviewMilitary() {
|
|||
if (el.tagName !== "BUTTON") return;
|
||||
const type = el.dataset.type;
|
||||
|
||||
if (type === "icon") return selectIcon(el.textContent, v => (el.textContent = v));
|
||||
if (type === "icon") {
|
||||
return selectIcon(el.textContent, function (value) {
|
||||
el.innerHTML = value.startsWith("http") || value.startsWith("data:image")
|
||||
? `<img src="${value}" style="width:1.2em;height:1.2em;pointer-events:none;">`
|
||||
: value;
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "biomes") {
|
||||
const {i, name, color} = biomesData;
|
||||
const biomesArray = Array(i.length).fill(null);
|
||||
|
|
@ -329,9 +336,15 @@ function overviewMilitary() {
|
|||
${getLimitText(unit[attr])}
|
||||
</button>`;
|
||||
|
||||
row.innerHTML = /* html */ `<td><button data-type="icon" data-tip="Click to select unit icon">${
|
||||
icon || " "
|
||||
}</button></td>
|
||||
row.innerHTML = /* html */ `<td>
|
||||
<button data-type="icon" data-tip="Click to select unit icon">
|
||||
${
|
||||
icon.startsWith("http") || icon.startsWith("data:image")
|
||||
? `<img src="${icon}" style="width:1.2em;height:1.2em;pointer-events:none;">`
|
||||
: icon || ""
|
||||
}
|
||||
</button>
|
||||
</td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${name}" /></td>
|
||||
<td>${getLimitButton("biomes")}</td>
|
||||
<td>${getLimitButton("states")}</td>
|
||||
|
|
@ -427,7 +440,11 @@ function overviewMilitary() {
|
|||
const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] =
|
||||
elements.map(el => {
|
||||
const {type, value} = el.dataset || {};
|
||||
if (type === "icon") return el.textContent || "⠀";
|
||||
if (type === "icon") {
|
||||
const value = el.innerHTML.trim();
|
||||
const isImage = value.startsWith("<img");
|
||||
return isImage ? value.match(/src="([^"]*)"/)[1] : value || "⠀";
|
||||
}
|
||||
if (type) return value ? value.split(",").map(v => parseInt(v)) : null;
|
||||
if (el.type === "number") return +el.value || 0;
|
||||
if (el.type === "checkbox") return +el.checked || 0;
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ function editNotes(id, name) {
|
|||
|
||||
$("#notesEditor").dialog({
|
||||
title: "Notes Editor",
|
||||
width: window.innerWidth * 0.8,
|
||||
height: window.innerHeight * 0.75,
|
||||
width: svgWidth * 0.8,
|
||||
height: svgHeight * 0.75,
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
close: removeEditor
|
||||
});
|
||||
|
|
|
|||
|
|
@ -711,6 +711,7 @@ function editProvinces() {
|
|||
|
||||
node
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("dx", ".2em")
|
||||
.attr("dy", "1em")
|
||||
.attr("x", d => d.x0)
|
||||
|
|
|
|||
|
|
@ -24,18 +24,17 @@ function editRegiment(selector) {
|
|||
modules.editRegiment = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
|
||||
document.getElementById("regimentType").addEventListener("click", changeType);
|
||||
document.getElementById("regimentName").addEventListener("change", changeName);
|
||||
document.getElementById("regimentEmblem").addEventListener("input", changeEmblem);
|
||||
document.getElementById("regimentEmblemSelect").addEventListener("click", selectEmblem);
|
||||
document.getElementById("regimentAttack").addEventListener("click", toggleAttack);
|
||||
document.getElementById("regimentRegenerateLegend").addEventListener("click", regenerateLegend);
|
||||
document.getElementById("regimentLegend").addEventListener("click", editLegend);
|
||||
document.getElementById("regimentSplit").addEventListener("click", splitRegiment);
|
||||
document.getElementById("regimentAdd").addEventListener("click", toggleAdd);
|
||||
document.getElementById("regimentAttach").addEventListener("click", toggleAttach);
|
||||
document.getElementById("regimentRemove").addEventListener("click", removeRegiment);
|
||||
byId("regimentNameRestore").addEventListener("click", restoreName);
|
||||
byId("regimentType").addEventListener("click", changeType);
|
||||
byId("regimentName").addEventListener("change", changeName);
|
||||
byId("regimentEmblemChange").addEventListener("click", changeEmblem);
|
||||
byId("regimentAttack").addEventListener("click", toggleAttack);
|
||||
byId("regimentRegenerateLegend").addEventListener("click", regenerateLegend);
|
||||
byId("regimentLegend").addEventListener("click", editLegend);
|
||||
byId("regimentSplit").addEventListener("click", splitRegiment);
|
||||
byId("regimentAdd").addEventListener("click", toggleAdd);
|
||||
byId("regimentAttach").addEventListener("click", toggleAttach);
|
||||
byId("regimentRemove").addEventListener("click", removeRegiment);
|
||||
|
||||
// get regiment data element
|
||||
function getRegiment() {
|
||||
|
|
@ -43,11 +42,13 @@ function editRegiment(selector) {
|
|||
}
|
||||
|
||||
function updateRegimentData(regiment) {
|
||||
document.getElementById("regimentType").className = regiment.n ? "icon-anchor" : "icon-users";
|
||||
document.getElementById("regimentName").value = regiment.name;
|
||||
document.getElementById("regimentEmblem").value = regiment.icon;
|
||||
const composition = document.getElementById("regimentComposition");
|
||||
byId("regimentType").className = regiment.n ? "icon-anchor" : "icon-users";
|
||||
byId("regimentName").value = regiment.name;
|
||||
byId("regimentEmblem").innerHTML = regiment.icon.startsWith("http") || regiment.icon.startsWith("data:image")
|
||||
? `<img src="${regiment.icon}" style="width: 1em; height: 1em;">`
|
||||
: regiment.icon;
|
||||
|
||||
const composition = byId("regimentComposition");
|
||||
composition.innerHTML = options.military
|
||||
.map(u => {
|
||||
return `<div data-tip="${capitalize(u.name)} number. Input to change">
|
||||
|
|
@ -126,12 +127,13 @@ function editRegiment(selector) {
|
|||
function changeType() {
|
||||
const reg = getRegiment();
|
||||
reg.n = +!reg.n;
|
||||
document.getElementById("regimentType").className = reg.n ? "icon-anchor" : "icon-users";
|
||||
byId("regimentType").className = reg.n ? "icon-anchor" : "icon-users";
|
||||
|
||||
const size = +armies.attr("box-size");
|
||||
const baseRect = elSelected.querySelectorAll("rect")[0];
|
||||
const iconRect = elSelected.querySelectorAll("rect")[1];
|
||||
const icon = elSelected.querySelector(".regimentIcon");
|
||||
const image = elSelected.querySelector(".regimentIcon");
|
||||
const x = reg.n ? reg.x - size * 2 : reg.x - size * 3;
|
||||
baseRect.setAttribute("x", x);
|
||||
baseRect.setAttribute("width", reg.n ? size * 4 : size * 6);
|
||||
|
|
@ -148,19 +150,19 @@ function editRegiment(selector) {
|
|||
const reg = getRegiment(),
|
||||
regs = pack.states[elSelected.dataset.state].military;
|
||||
const name = Military.getName(reg, regs);
|
||||
elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name;
|
||||
}
|
||||
|
||||
function selectEmblem() {
|
||||
selectIcon(regimentEmblem.value, v => {
|
||||
regimentEmblem.value = v;
|
||||
changeEmblem();
|
||||
});
|
||||
elSelected.dataset.name = reg.name = byId("regimentName").value = name;
|
||||
}
|
||||
|
||||
function changeEmblem() {
|
||||
const emblem = document.getElementById("regimentEmblem").value;
|
||||
getRegiment().icon = elSelected.querySelector(".regimentIcon").innerHTML = emblem;
|
||||
const regiment = getRegiment();
|
||||
|
||||
selectIcon(regiment.icon, value => {
|
||||
regiment.icon = value;
|
||||
const isExternal = value.startsWith("http") || value.startsWith("data:image");
|
||||
byId("regimentEmblem").innerHTML = isExternal ? `<img src="${value}" style="width: 1em; height: 1em;">` : value;
|
||||
elSelected.querySelector(".regimentIcon").innerHTML = isExternal ? "" : value;
|
||||
elSelected.querySelector(".regimentImage").setAttribute("href", isExternal ? value : "");
|
||||
});
|
||||
}
|
||||
|
||||
function changeUnit() {
|
||||
|
|
@ -224,8 +226,8 @@ function editRegiment(selector) {
|
|||
}
|
||||
|
||||
function toggleAdd() {
|
||||
document.getElementById("regimentAdd").classList.toggle("pressed");
|
||||
if (document.getElementById("regimentAdd").classList.contains("pressed")) {
|
||||
byId("regimentAdd").classList.toggle("pressed");
|
||||
if (byId("regimentAdd").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", addRegimentOnClick);
|
||||
tip("Click on map to create new regiment or fleet", true);
|
||||
} else {
|
||||
|
|
@ -252,8 +254,8 @@ function editRegiment(selector) {
|
|||
}
|
||||
|
||||
function toggleAttack() {
|
||||
document.getElementById("regimentAttack").classList.toggle("pressed");
|
||||
if (document.getElementById("regimentAttack").classList.contains("pressed")) {
|
||||
byId("regimentAttack").classList.toggle("pressed");
|
||||
if (byId("regimentAttack").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", attackRegimentOnClick);
|
||||
tip("Click on another regiment to initiate battle", true);
|
||||
armies.selectAll(":scope > g").classed("draggable", false);
|
||||
|
|
@ -307,6 +309,7 @@ function editRegiment(selector) {
|
|||
.on("end", () => new Battle(attacker, defender));
|
||||
svg
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("x", window.innerWidth / 2)
|
||||
.attr("y", window.innerHeight / 2)
|
||||
.text("⚔️")
|
||||
|
|
@ -324,8 +327,8 @@ function editRegiment(selector) {
|
|||
}
|
||||
|
||||
function toggleAttach() {
|
||||
document.getElementById("regimentAttach").classList.toggle("pressed");
|
||||
if (document.getElementById("regimentAttach").classList.contains("pressed")) {
|
||||
byId("regimentAttach").classList.toggle("pressed");
|
||||
if (byId("regimentAttach").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", attachRegimentOnClick);
|
||||
tip("Click on another regiment to unite both regiments. The current regiment will be removed", true);
|
||||
armies.selectAll(":scope > g").classed("draggable", false);
|
||||
|
|
@ -427,6 +430,7 @@ function editRegiment(selector) {
|
|||
const text = this.querySelector("text");
|
||||
const iconRect = this.querySelectorAll("rect")[1];
|
||||
const icon = this.querySelector(".regimentIcon");
|
||||
const image = this.querySelector(".regimentImage");
|
||||
|
||||
const self = elSelected === this;
|
||||
const baseLine = viewbox.select("g#regimentBase > line");
|
||||
|
|
@ -448,6 +452,8 @@ function editRegiment(selector) {
|
|||
iconRect.setAttribute("y", y1);
|
||||
icon.setAttribute("x", x1 - size);
|
||||
icon.setAttribute("y", y);
|
||||
image.setAttribute("x", x1 - h);
|
||||
image.setAttribute("y", y1);
|
||||
if (self) {
|
||||
baseLine.attr("x2", x).attr("y2", y);
|
||||
rotationControl
|
||||
|
|
@ -479,9 +485,9 @@ function editRegiment(selector) {
|
|||
viewbox.selectAll("g#regimentBase").remove();
|
||||
armies.selectAll(":scope > g").classed("draggable", false);
|
||||
armies.selectAll("g>g").call(d3.drag().on("drag", null));
|
||||
document.getElementById("regimentAdd").classList.remove("pressed");
|
||||
document.getElementById("regimentAttack").classList.remove("pressed");
|
||||
document.getElementById("regimentAttach").classList.remove("pressed");
|
||||
byId("regimentAdd").classList.remove("pressed");
|
||||
byId("regimentAttack").classList.remove("pressed");
|
||||
byId("regimentAttach").classList.remove("pressed");
|
||||
restoreDefaultEvents();
|
||||
elSelected = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,14 +67,24 @@ function overviewRegiments(state) {
|
|||
)
|
||||
.join(" ");
|
||||
|
||||
lines += /* html */ `<div class="states" data-id="${r.i}" data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
|
||||
lines += /* html */ `<div class="states" data-id="${r.i}" data-s="${s.i}" data-state="${s.name}" data-name="${
|
||||
r.name
|
||||
}" ${sortData} data-total="${r.a}">
|
||||
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
|
||||
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly />
|
||||
<span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span>
|
||||
${
|
||||
r.icon.startsWith("http") || r.icon.startsWith("data:image")
|
||||
? `<img src="${r.icon}" data-tip="Regiment's emblem" style="width:1.2em; height:1.2em; vertical-align: middle;">`
|
||||
: `<span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span>`
|
||||
}
|
||||
<input data-tip="Regiment's name" style="width:13em" value="${r.name}" readonly />
|
||||
${lineData}
|
||||
<div data-type="total" data-tip="Total military personnel (not considering crew)" style="font-weight: bold">${r.a}</div>
|
||||
<span data-tip="Edit regiment" onclick="editRegiment('#regiment${s.i}-${r.i}')" class="icon-pencil pointer"></span>
|
||||
<div data-type="total" data-tip="Total military personnel (not considering crew)" style="font-weight: bold">${
|
||||
r.a
|
||||
}</div>
|
||||
<span data-tip="Edit regiment" onclick="editRegiment('#regiment${s.i}-${
|
||||
r.i
|
||||
}')" class="icon-pencil pointer"></span>
|
||||
</div>`;
|
||||
|
||||
regiments.push(r);
|
||||
|
|
|
|||
|
|
@ -727,19 +727,19 @@ styleHeightmapCurve.on("change", e => {
|
|||
|
||||
styleReliefSet.on("change", e => {
|
||||
terrain.attr("set", e.target.value);
|
||||
ReliefIcons.draw();
|
||||
drawReliefIcons();
|
||||
if (!layerIsOn("toggleRelief")) toggleRelief();
|
||||
});
|
||||
|
||||
styleReliefSize.on("change", e => {
|
||||
terrain.attr("size", e.target.value);
|
||||
ReliefIcons.draw();
|
||||
drawReliefIcons();
|
||||
if (!layerIsOn("toggleRelief")) toggleRelief();
|
||||
});
|
||||
|
||||
styleReliefDensity.on("change", e => {
|
||||
terrain.attr("density", e.target.value);
|
||||
ReliefIcons.draw();
|
||||
drawReliefIcons();
|
||||
if (!layerIsOn("toggleRelief")) toggleRelief();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ function processFeatureRegeneration(event, button) {
|
|||
$("#labels").fadeIn();
|
||||
drawStateLabels();
|
||||
} else if (button === "regenerateReliefIcons") {
|
||||
ReliefIcons.draw();
|
||||
drawReliefIcons();
|
||||
if (!layerIsOn("toggleRelief")) toggleRelief();
|
||||
} else if (button === "regenerateRoutes") {
|
||||
regenerateRoutes();
|
||||
|
|
@ -635,8 +635,10 @@ function addLabelOnClick() {
|
|||
group.classed("hidden", false);
|
||||
group
|
||||
.append("text")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("id", id)
|
||||
.append("textPath")
|
||||
.attr("text-rendering", "optimizeSpeed")
|
||||
.attr("xlink:href", "#textPath_" + id)
|
||||
.attr("startOffset", "50%")
|
||||
.attr("font-size", "100%")
|
||||
|
|
@ -877,33 +879,47 @@ function configMarkersGeneration() {
|
|||
drawConfigTable();
|
||||
|
||||
function drawConfigTable() {
|
||||
const {markers} = pack;
|
||||
const config = Markers.getConfig();
|
||||
const headers = `<thead style='font-weight:bold'><tr>
|
||||
|
||||
const headers = /* html */ `<thead style='font-weight:bold'><tr>
|
||||
<td data-tip="Marker type name">Type</td>
|
||||
<td data-tip="Marker icon">Icon</td>
|
||||
<td data-tip="Marker number multiplier">Multiplier</td>
|
||||
<td data-tip="Number of markers of that type on the current map">Number</td>
|
||||
</tr></thead>`;
|
||||
const lines = config.map(({type, icon, multiplier}, index) => {
|
||||
const inputId = `markerIconInput${index}`;
|
||||
return `<tr>
|
||||
<td><input value="${type}" /></td>
|
||||
|
||||
const lines = config.map(({type, icon, multiplier}) => {
|
||||
const isExternal = icon.startsWith("http") || icon.startsWith("data:image");
|
||||
|
||||
return /* html */ `<tr>
|
||||
<td><input class="type" value="${type}" /></td>
|
||||
<td style="position: relative">
|
||||
<input id="${inputId}" style="width: 5em" value="${icon}" />
|
||||
<i class="icon-edit pointer" style="position: absolute; margin:.4em 0 0 -1.4em; font-size:.85em"></i>
|
||||
<img class="image" src="${isExternal ? icon : ""}" ${
|
||||
isExternal ? "" : "hidden"
|
||||
} style="width:1.2em; height:1.2em; vertical-align: middle;">
|
||||
<span class="emoji" style="font-size:1.2em">${isExternal ? "" : icon}</span>
|
||||
<button class="changeIcon icon-pencil"></button>
|
||||
</td>
|
||||
<td><input type="number" min="0" max="100" step="0.1" value="${multiplier}" /></td>
|
||||
<td style="text-align:center">${markers.filter(marker => marker.type === type).length}</td>
|
||||
<td><input class="multiplier" type="number" min="0" max="100" step="0.1" value="${multiplier}" /></td>
|
||||
<td style="text-align:center">${pack.markers.filter(marker => marker.type === type).length}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
const table = `<table class="table">${headers}<tbody>${lines.join("")}</tbody></table>`;
|
||||
alertMessage.innerHTML = table;
|
||||
|
||||
alertMessage.querySelectorAll("i").forEach(selectIconButton => {
|
||||
alertMessage.querySelectorAll("button.changeIcon").forEach(selectIconButton => {
|
||||
selectIconButton.addEventListener("click", function () {
|
||||
const input = this.previousElementSibling;
|
||||
selectIcon(input.value, icon => (input.value = icon));
|
||||
const image = this.parentElement.querySelector(".image");
|
||||
const emoji = this.parentElement.querySelector(".emoji");
|
||||
const icon = image.getAttribute("src") || emoji.textContent;
|
||||
|
||||
selectIcon(icon, value => {
|
||||
const isExternal = value.startsWith("http") || value.startsWith("data:image");
|
||||
image.setAttribute("src", isExternal ? value : "");
|
||||
image.hidden = !isExternal;
|
||||
emoji.textContent = isExternal ? "" : value;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -911,12 +927,14 @@ function configMarkersGeneration() {
|
|||
const applyChanges = () => {
|
||||
const rows = alertMessage.querySelectorAll("tbody > tr");
|
||||
const rowsData = Array.from(rows).map(row => {
|
||||
const inputs = row.querySelectorAll("input");
|
||||
return {
|
||||
type: inputs[0].value,
|
||||
icon: inputs[1].value,
|
||||
multiplier: parseFloat(inputs[2].value)
|
||||
};
|
||||
const type = row.querySelector(".type").value;
|
||||
|
||||
const image = row.querySelector(".image");
|
||||
const emoji = row.querySelector(".emoji");
|
||||
const icon = image.getAttribute("src") || emoji.textContent;
|
||||
|
||||
const multiplier = parseFloat(row.querySelector(".multiplier").value);
|
||||
return {type, icon, multiplier};
|
||||
});
|
||||
|
||||
const config = Markers.getConfig();
|
||||
|
|
|
|||
|
|
@ -248,10 +248,10 @@
|
|||
},
|
||||
"#ice": {
|
||||
"opacity": 0.9,
|
||||
"fill": "#e8f0f6",
|
||||
"fill": "#f1f8fe",
|
||||
"stroke": "#e8f0f6",
|
||||
"stroke-width": 1,
|
||||
"filter": "url(#dropShadow05)"
|
||||
"stroke-width": 0.5,
|
||||
"filter": "url(#dropShadow01)"
|
||||
},
|
||||
"#emblems": {
|
||||
"opacity": 0.9,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@
|
|||
// clip polygon by graph bbox
|
||||
function clipPoly(points, secure = 0) {
|
||||
if (points.length < 2) return points;
|
||||
if (points.some(point => point === undefined)) {
|
||||
ERROR && console.error("Undefined point in clipPoly", points);
|
||||
return points;
|
||||
}
|
||||
|
||||
return polygonclip(points, [0, 0, graphWidth, graphHeight], secure);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
*
|
||||
* Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2
|
||||
*/
|
||||
const VERSION = "1.107.0";
|
||||
|
||||
const VERSION = "1.109.0";
|
||||
if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function");
|
||||
|
||||
{
|
||||
|
|
@ -36,6 +37,7 @@ if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format o
|
|||
|
||||
<ul>
|
||||
<strong>Latest changes:</strong>
|
||||
<li>Ability to set custom image as Marker or Regiment icon</li>
|
||||
<li>Submap and Transform tools rework</li>
|
||||
<li>Azgaar Bot to answer questions and provide help</li>
|
||||
<li>Labels: ability to set letter spacing</li>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue