Merge branch 'Azgaar:master' into Province-legend-box

This commit is contained in:
Ángel Montero Lamas 2024-04-09 20:21:00 +02:00 committed by GitHub
commit 366b12ece6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 1935 additions and 1057 deletions

View file

@ -1,5 +1,3 @@
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)
# Fantasy Map Generator # Fantasy Map Generator
Azgaar's _Fantasy Map Generator_ is a free web application that helps fantasy writers, game masters, and cartographers create and edit fantasy maps. Azgaar's _Fantasy Map Generator_ is a free web application that helps fantasy writers, game masters, and cartographers create and edit fantasy maps.
@ -8,19 +6,17 @@ Link: [azgaar.github.io/Fantasy-Map-Generator](https://azgaar.github.io/Fantasy-
Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for guidance. The current progress is tracked in [Trello](https://trello.com/b/7x832DG4/fantasy-map-generator). Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com). Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for guidance. The current progress is tracked in [Trello](https://trello.com/b/7x832DG4/fantasy-map-generator). Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com).
[![preview](https://cdn.discordapp.com/attachments/587406457725779968/594840629213659136/preview1.png)](https://i.redd.it/8bf81ir2cy631.png) [![preview](https://github.com/Azgaar/Fantasy-Map-Generator/assets/26469650/9502eae9-92e0-4d0d-9f17-a2ba4a565c01)](https://github.com/Azgaar/Fantasy-Map-Generator/assets/26469650/11a42446-4bd5-4526-9cb1-3ef97c868992)
[![preview](https://cdn.discordapp.com/attachments/587406457725779968/594840633911279636/preview2.png)](https://cdn.discordapp.com/attachments/515359185664344071/593888810782162964/The_Wichin_Island_sepia.png) [![preview](https://github.com/Azgaar/Fantasy-Map-Generator/assets/26469650/e751a9e5-7986-4638-b8a9-362395ef7583)](https://github.com/Azgaar/Fantasy-Map-Generator/assets/26469650/e751a9e5-7986-4638-b8a9-362395ef7583)
[![preview](https://cdn.discordapp.com/attachments/587406457725779968/594840632296734720/preview3.png)](https://cdn.discordapp.com/attachments/515359096925454350/593891237984206848/The_Wichin_Island_-_diplomacy.png) [![preview](https://github.com/Azgaar/Fantasy-Map-Generator/assets/26469650/b0d0efde-a0d1-4e80-8818-ea3dd83c2323)](https://github.com/Azgaar/Fantasy-Map-Generator/assets/26469650/b0d0efde-a0d1-4e80-8818-ea3dd83c2323)
Join our [Discord server](https://discordapp.com/invite/X7E84HU) and [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) to share your creations, discuss the Generator, suggest ideas and get the most recent updates. Join our [Discord server](https://discordapp.com/invite/X7E84HU) and [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) to share your creations, discuss the Generator, suggest ideas and get the most recent updates.
Contact me via [email](mailto:azgaar.fmg@yandex.by) if you have non-public suggestions. For bug reports please use [GitHub issues](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or _#bugs_ channel on Discord. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips). Contact me via [email](mailto:azgaar.fmg@yandex.com) if you have non-public suggestions. For bug reports please use [GitHub issues](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or _#fmg-bugs_ channel on Discord. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips).
Electron desktop application is available in [releases](https://github.com/Azgaar/Fantasy-Map-Generator/releases). Download archive for your architecture, unzip and run. Pull requests are highly welcomed. The codebase is messy and requires re-design. I will appreciate if you start with minor changes. Check out the [data model](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Data-model) before contributing.
Pull requests are highly welcomed. The codebase is messy and requires re-design, but I will appreciate if you start with minor changes. Check out the [data model](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Data-model) before contributing.
You can support the project on [Patreon](https://www.patreon.com/azgaar). You can support the project on [Patreon](https://www.patreon.com/azgaar).

View file

@ -1876,12 +1876,6 @@ div.editorLine {
margin: 0.4em 0 0 -0.9em; margin: 0.4em 0 0 -0.9em;
} }
#barBackColor {
width: 3.5em;
padding: 0px;
height: 1.2em;
}
#ruler { #ruler {
cursor: move; cursor: move;
fill: none; fill: none;
@ -1921,18 +1915,6 @@ div.editorLine {
stroke: #737373; stroke: #737373;
} }
#scaleBar {
stroke: none;
fill: none;
cursor: pointer;
}
#scaleBar text {
fill: #353540;
text-anchor: middle;
font-family: var(--serif);
}
#militaryOptionsTable select { #militaryOptionsTable select {
border: 1px solid #d4d4d4; border: 1px solid #d4d4d4;
} }

View file

@ -138,7 +138,7 @@
} }
</style> </style>
<link rel="preload" href="index.css?v=1.95.00" as="style" onload="this.onload=null; this.rel='stylesheet'" /> <link rel="preload" href="index.css?v=1.96.00" 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" href="icons.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
<link rel="preload" href="libs/jquery-ui.css" as="style" onload="this.onload=null; this.rel='stylesheet'" /> <link rel="preload" href="libs/jquery-ui.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
</head> </head>
@ -366,7 +366,9 @@
</mask> </mask>
</defs> </defs>
<g id="viewbox"></g> <g id="viewbox"></g>
<g id="scaleBar"></g> <g id="scaleBar">
<rect id="scaleBarBack"></rect>
</g>
<g id="vignette" mask="url(#vignette-mask)"> <g id="vignette" mask="url(#vignette-mask)">
<rect x="0" y="0" width="100%" height="100%" /> <rect x="0" y="0" width="100%" height="100%" />
</g> </g>
@ -799,6 +801,7 @@
<option value="rivers">Rivers</option> <option value="rivers">Rivers</option>
<option value="routes">Routes</option> <option value="routes">Routes</option>
<option value="ruler">Rulers</option> <option value="ruler">Rulers</option>
<option value="scaleBar">Scale Bar</option>
<option value="regions" selected>States</option> <option value="regions" selected>States</option>
<option value="temperature">Temperature</option> <option value="temperature">Temperature</option>
<option value="texture">Texture</option> <option value="texture">Texture</option>
@ -824,6 +827,63 @@
</tr> </tr>
</tbody> </tbody>
<tbody id="styleHeightmap">
<tr id="styleHeightmapRenderOceanOption" data-tip="Check to render ocean heights">
<td colspan="2">
<input id="styleHeightmapRenderOcean" class="checkbox" type="checkbox" />
<label for="styleHeightmapRenderOcean" class="checkbox-label">Render ocean heights</label>
</td>
</tr>
<tr data-tip="Terracing rate. Set to 0 (toggle off) to improve performance">
<td>Terracing</td>
<td>
<input id="styleHeightmapTerracingInput" type="range" min="0" max="20" step="1" />
<output id="styleHeightmapTerracingOutput">0</output>
</td>
</tr>
<tr data-tip="Layers reduction rate. Increase to improve performance">
<td>Reduce layers</td>
<td>
<input id="styleHeightmapSkipInput" type="range" min="0" max="10" step="1" value="5" />
<output id="styleHeightmapSkipOutput">5</output>
</td>
</tr>
<tr data-tip="Line simplification rate. Increase to slightly improve performance">
<td>Simplify line</td>
<td>
<input id="styleHeightmapSimplificationInput" type="range" min="0" max="10" step="1" value="0" />
<output id="styleHeightmapSimplificationOutput">0</output>
</td>
</tr>
<tr data-tip="Select line interpolation type">
<td>Line style</td>
<td>
<select id="styleHeightmapCurve">
<option value="curveBasisClosed" selected>Curved</option>
<option value="curveLinear">Linear</option>
<option value="curveStep">Rectangular</option>
</select>
</td>
</tr>
<tr data-tip="Select color scheme for the element">
<td>Color scheme</td>
<td>
<select id="styleHeightmapScheme"></select>
<button
id="openCreateHeightmapSchemeButton"
data-tip="Click to add a custom heightmap color scheme"
data-stops="#ffffff,#EEEECC,#D2B48C,#008000,#008080"
class="icon-plus sideButton"
></button>
</td>
</tr>
</tbody>
<tbody id="styleOpacity" style="display: none"> <tbody id="styleOpacity" style="display: none">
<tr data-tip="Set opacity. 0: transparent, 1: solid"> <tr data-tip="Set opacity. 0: transparent, 1: solid">
<td>Opacity</td> <td>Opacity</td>
@ -1281,56 +1341,6 @@
</tr> </tr>
</tbody> </tbody>
<tbody id="styleHeightmap">
<tr data-tip="Terracing rate. Set to 0 (toggle off) to improve performance">
<td>Terracing</td>
<td>
<input id="styleHeightmapTerracingInput" type="range" min="0" max="20" step="1" />
<output id="styleHeightmapTerracingOutput">0</output>
</td>
</tr>
<tr data-tip="Layers reduction rate. Increase to improve performance">
<td>Reduce layers</td>
<td>
<input id="styleHeightmapSkipInput" type="range" min="0" max="10" step="1" value="5" />
<output id="styleHeightmapSkipOutput">5</output>
</td>
</tr>
<tr data-tip="Line simplification rate. Increase to slightly improve performance">
<td>Simplify line</td>
<td>
<input id="styleHeightmapSimplificationInput" type="range" min="0" max="10" step="1" value="0" />
<output id="styleHeightmapSimplificationOutput">0</output>
</td>
</tr>
<tr data-tip="Select line interpolation type">
<td>Line style</td>
<td>
<select id="styleHeightmapCurve">
<option value="0" selected>Curved</option>
<option value="1">Linear</option>
<option value="2">Rectangular</option>
</select>
</td>
</tr>
<tr data-tip="Select color scheme for the element">
<td>Color scheme</td>
<td>
<select id="styleHeightmapScheme"></select>
<button
id="openCreateHeightmapSchemeButton"
data-tip="Click to add a custom heightmap color scheme"
data-stops="#ffffff,#EEEECC,#D2B48C,#008000,#008080"
class="icon-plus sideButton"
></button>
</td>
</tr>
</tbody>
<tbody id="styleArmies"> <tbody id="styleArmies">
<tr data-tip="Set fill transparency. Set to 0 to make it fully transparent"> <tr data-tip="Set fill transparency. Set to 0 to make it fully transparent">
<td>Fill opacity</td> <td>Fill opacity</td>
@ -1446,6 +1456,84 @@
</td> </td>
</tr> </tr>
</tbody> </tbody>
<tbody id="styleScaleBar">
<tr data-tip="Set bar and font size">
<td>Size</td>
<td>
<span>Bar </span>
<input id="styleScaleBarSize" type="number" min=".5" max="5" step=".1" />
<span>Font </span>
<input id="styleScaleBarFontSize" type="number" min="1" max="100" step=".1" />
</td>
</tr>
<tr data-tip="Set position of the Scale bar bottom right corner (in percents)">
<td>Position</td>
<td>
<span>x </span>
<input id="styleScaleBarPositionX" type="number" min="0" max="100" step="0.1" style="width: 5em" />
<span>y </span>
<input id="styleScaleBarPositionY" type="number" min="0" max="100" step="0.1" style="width: 5em" />
</td>
</tr>
<tr data-tip="Type scale bar label, leave blank to hide label">
<td>Label</td>
<td>
<input id="styleScaleBarLabel" type="text" />
</td>
</tr>
<tr data-tip="Set background opacity. 0: transparent, 1: solid">
<td>Back opacity</td>
<td>
<input id="styleScaleBarBackgroundOpacityInput" type="range" min="0" max="1" step="0.01" />
<output id="styleScaleBarBackgroundOpacityOutput"></output>
</td>
</tr>
<tr data-tip="Set background fill color">
<td>Back fill</td>
<td>
<input id="styleScaleBarBackgroundFillInput" type="color" />
<output id="styleScaleBarBackgroundFillOutput"></output>
</td>
</tr>
<tr data-tip="Set background stroke color and width">
<td>Back stroke</td>
<td>
<input id="styleScaleBarBackgroundStrokeInput" type="color" />
<output id="styleScaleBarBackgroundStrokeOutput"></output>
<span>Width </span>
<input
id="styleScaleBarBackgroundStrokeWidth"
type="number"
min="0"
max="10"
step="0.1"
style="width: 5em"
/>
</td>
</tr>
<tr data-tip="Set background element padding: top, right, bottom, left (in pixels)">
<td>Back padding</td>
<td style="display: flex; gap: 4px">
<input id="styleScaleBarBackgroundPaddingTop" type="number" min="0" max="100" style="width: 5em" />
<input id="styleScaleBarBackgroundPaddingRight" type="number" min="0" max="100" style="width: 5em" />
<input id="styleScaleBarBackgroundPaddingBottom" type="number" min="0" max="100" style="width: 5em" />
<input id="styleScaleBarBackgroundPaddingLeft" type="number" min="0" max="100" style="width: 5em" />
</td>
</tr>
<tr data-tip="Select background filter">
<td>Back filter</td>
<td><select id="styleScaleBarBackgroundFilter" /></td>
</tr>
</tbody>
</table> </table>
<div id="mapFilters" data-tip="Set a filter to be applied to the map in general"> <div id="mapFilters" data-tip="Set a filter to be applied to the map in general">
@ -3241,6 +3329,22 @@
<input id="burgPopulation" type="number" min="0" step="1" style="width: 8em" /> <input id="burgPopulation" type="number" min="0" step="1" style="width: 8em" />
</div> </div>
<div data-tip="Burg mean annual temperature and real-world city for comparison">
<div class="label">Temperature:</div>
<span id="burgTemperature"></span>, like in
<span id="burgTemperatureLikeIn"></span>
<i
id="burgTemperatureGraph"
data-tip="Show temperature graph for the burg"
class="icon-chart-area pointer"
></i>
</div>
<div data-tip="Burg height above mean sea level">
<div class="label">Elevation:</div>
<span id="burgElevation"></span> above sea level
</div>
<div> <div>
<div class="label">Features:</div> <div class="label">Features:</div>
<span <span
@ -3290,49 +3394,23 @@
style="font-size: 1em" style="font-size: 1em"
></span> ></span>
</div> </div>
<div data-tip="Burg mean annual temperature and real-world city for comparison">
<div class="label">Temperature:</div>
<span id="burgTemperature"></span>, like in
<span id="burgTemperatureLikeIn"></span>
<i
id="burgTemperatureGraph"
data-tip="Show temperature graph for the burg"
class="icon-chart-area pointer"
></i>
</div>
<div data-tip="Burg height above mean sea level">
<div class="label">Elevation:</div>
<span id="burgElevation"></span> above sea level
</div>
</div> </div>
</div> </div>
<div <div id="burgPreviewSection" data-tip="Burg map preview" style="display: flex; flex-direction: column">
id="mfcgPreviewSection" <div style="display: flex; justify-content: space-between">
data-tip="Burg preview in the Medieval Fantasy City Generator. Default seed is a combination of map seed and burg id" <span>Burg preview:</span>
style="display: flex; flex-direction: column" <div style="display: flex; gap: 0.5em">
>
<div>
See in <a id="mfcgLink" target="_blank">City Generator by Watabou</a>.
<div id="mfcgBurgSeedSection">
Seed: <input id="mfcgBurgSeed" style="width: 10em" type="number" min="1" max="1e13" step="1" />
<i <i
id="regenerateMFCGBurgSeed" id="burgLinkEdit"
data-tip="Randomize Medieval Fantasy City Generator burg seed" data-tip="Provide custom link to the burg map"
class="icon-arrows-cw pointer" class="icon-pencil pointer"
style="margin-left: 0.1em" style="margin-top: -0.1em"
></i> ></i>
<i id="burgLinkOpen" data-tip="Open burg map in a new tab" class="icon-link-ext pointer"></i>
</div> </div>
<i
id="addCustomMFCGBurgLink"
data-tip="Provide custom link to the burg map"
class="icon-pencil pointer"
style="margin-left: 0.1em"
></i>
</div> </div>
<iframe id="mfcgPreview" sandbox="allow-scripts"></iframe> <div id="burgPreviewObject" style="max-width: 30em; pointer-events: none"></div>
</div> </div>
</div> </div>
@ -3372,7 +3450,7 @@
</div> </div>
<button id="burgEditEmblem" data-tip="Edit emblem" class="icon-shield-alt"></button> <button id="burgEditEmblem" data-tip="Edit emblem" class="icon-shield-alt"></button>
<button id="burgToggleMFCGMap" data-tip="Toggle MFCG map" class="icon-map"></button> <button id="burgTogglePreview" data-tip="Toggle preview" class="icon-map"></button>
<button id="burgRelocate" data-tip="Relocate burg" class="icon-target"></button> <button id="burgRelocate" data-tip="Relocate burg" class="icon-target"></button>
<button id="burglLegend" data-tip="Edit free text notes (legend) for this burg" class="icon-edit"></button> <button id="burglLegend" data-tip="Edit free text notes (legend) for this burg" class="icon-edit"></button>
<button id="burgLock" class="icon-lock-open" onmouseover="showElementLockTip(event)"></button> <button id="burgLock" class="icon-lock-open" onmouseover="showElementLockTip(event)"></button>
@ -4450,7 +4528,7 @@
</div> </div>
<div id="provincesEditor" class="dialog stable" style="display: none"> <div id="provincesEditor" class="dialog stable" style="display: none">
<div id="provincesHeader" class="header" style="grid-template-columns: 11em 8em 8em 8em 5em 8em"> <div id="provincesHeader" class="header" style="grid-template-columns: 11em 8em 8em 6em 6em 6em 8em">
<div data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="name"> <div data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="name">
Province&nbsp; Province&nbsp;
</div> </div>
@ -4463,6 +4541,9 @@
<div data-tip="Click to sort by province owner" class="sortable alphabetically" data-sortby="state"> <div data-tip="Click to sort by province owner" class="sortable alphabetically" data-sortby="state">
State&nbsp; State&nbsp;
</div> </div>
<div data-tip="Click to sort by province burgs count" class="sortable hide" data-sortby="burgs">
Burgs&nbsp;
</div>
<div data-tip="Click to sort by province area" class="sortable hide" data-sortby="area">Area&nbsp;</div> <div data-tip="Click to sort by province area" class="sortable hide" data-sortby="area">Area&nbsp;</div>
<div data-tip="Click to sort by province population" class="sortable hide" data-sortby="population"> <div data-tip="Click to sort by province population" class="sortable hide" data-sortby="population">
Population&nbsp; Population&nbsp;
@ -4475,6 +4556,9 @@
<div data-tip="Provinces displayed" style="margin-left: 4px"> <div data-tip="Provinces displayed" style="margin-left: 4px">
Provinces:&nbsp;<span id="provincesFooterNumber">0</span> Provinces:&nbsp;<span id="provincesFooterNumber">0</span>
</div> </div>
<div data-tip="Total burgs number" style="margin-left: 12px">
Burgs:&nbsp;<span id="provincesFooterBurgs">0</span>
</div>
<div data-tip="Average area" style="margin-left: 14px"> <div data-tip="Average area" style="margin-left: 14px">
Mean area:&nbsp;<span id="provincesFooterArea">0</span> Mean area:&nbsp;<span id="provincesFooterArea">0</span>
</div> </div>
@ -4613,7 +4697,7 @@
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span> <span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
<span <span
id="provinceNameEditorShortCulture" id="provinceNameEditorShortCulture"
data-tip="Generate culture-specific name" data-tip="Generate culture-specific name for the province"
class="icon-book pointer" class="icon-book pointer"
></span> ></span>
<span id="provinceNameEditorShortRandom" data-tip="Generate random name" class="icon-globe pointer"></span> <span id="provinceNameEditorShortRandom" data-tip="Generate random name" class="icon-globe pointer"></span>
@ -4689,6 +4773,14 @@
class="icon-arrows-cw pointer" class="icon-arrows-cw pointer"
></span> ></span>
</div> </div>
<div
id="provinceCultureName"
data-tip="Dominant culture in the province. This defines culture-based naming. Can be changed via the Cultures Editor"
style="margin-top: 0.2em"
>
Dominant culture:&nbsp;<span id="provinceCultureDisplay"></span>
</div>
</div> </div>
<div id="namesbaseEditor" class="dialog stable textual" style="display: none"> <div id="namesbaseEditor" class="dialog stable textual" style="display: none">
@ -5058,6 +5150,8 @@
<option value="km">Kilometer (km)</option> <option value="km">Kilometer (km)</option>
<option value="lg">League (lg)</option> <option value="lg">League (lg)</option>
<option value="vr">Versta (vr)</option> <option value="vr">Versta (vr)</option>
<option value="nmi">Nautical mile (nmi)</option>
<option value="nlg">Nautical league (nlg)</option>
<option value="custom_name">Custom name</option> <option value="custom_name">Custom name</option>
</select> </select>
</div> </div>
@ -5133,49 +5227,6 @@
</select> </select>
</div> </div>
<div class="unitsHeader">
<span class="icon-minus"></span>
<div>Scale bar:</div>
</div>
<div data-tip="Set scale bar size">
<div>Bar size:</div>
<input id="barSizeOutput" data-stored="barSize" type="range" min=".5" max="5" value="2" step=".1" />
<input id="barSizeInput" data-stored="barSize" type="number" min=".5" max="5" value="2" step=".1" />
</div>
<div data-tip="Type scale bar label, leave blank to hide label">
<div>Bar label:</div>
<input id="barLabel" data-stored="barLabel" type="text" placeholder="hidden" value="" />
</div>
<div data-tip="Set background for Scale bar">
<div>Bar background:</div>
<input
id="barBackOpacity"
data-stored="barBackOpacity"
type="range"
min="0"
max="1"
value=".2"
step=".01"
/>
<input id="barBackColor" data-stored="barBackColor" type="color" value="#ffffff" />
</div>
<div data-tip="Set position of the Scale bar bottom right corner in percents">
<div>Bar position:</div>
x:<input id="barPosX" data-stored="barPosX" type="number" min="0" max="100" step=".1" value="99" /> y:<input
id="barPosY"
data-stored="barPosY"
type="number"
min="0"
max="100"
step=".1"
value="99"
/>
</div>
<div class="unitsHeader"> <div class="unitsHeader">
<span class="icon-male"></span> <span class="icon-male"></span>
<div>Population:</div> <div>Population:</div>
@ -5539,7 +5590,9 @@
<th data-tip="Average number of people in crew (used for total personnel calculation)">Crew</th> <th data-tip="Average number of people in crew (used for total personnel calculation)">Crew</th>
<th data-tip="Unit military power (used for battle simulation)">Power</th> <th data-tip="Unit military power (used for battle simulation)">Power</th>
<th data-tip="Unit type to apply special rules on forces recalculation">Type</th> <th data-tip="Unit type to apply special rules on forces recalculation">Type</th>
<th data-tip="Check if unit is separate and can be stacked only with units of the same type">Sep.</th> <th data-tip="Check if unit is separate and can be stacked only with units of the same type">
Separate
</th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>
@ -5705,7 +5758,7 @@
<table id="iconTable" class="table pointer" style="font-size: 2em; text-align: center; width: 100%"></table> <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"> <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> <span>Select from the list or paste a Unicode character here: </span>
<input id="iconInput" style="width: 2em" /> <input id="iconInput" style="width: 2.5em" />
<span>. See <a href="https://emojipedia.org" target="_blank">Emojipedia</a> for reference</span> <span>. See <a href="https://emojipedia.org" target="_blank">Emojipedia</a> for reference</span>
</div> </div>
</div> </div>
@ -5982,7 +6035,7 @@
</div> </div>
<div id="exportToPngTilesScreen" style="display: none" class="dialog"> <div id="exportToPngTilesScreen" style="display: none" class="dialog">
<p>Map will be split into tiles and downloaded as a single zip file. Avoid saving to big images</p> <p>Map will be split into tiles and downloaded as a single zip file. Avoid saving too large images</p>
<div data-tip="Number of columns" style="margin-bottom: 0.3em"> <div data-tip="Number of columns" style="margin-bottom: 0.3em">
<div class="label">Columns:</div> <div class="label">Columns:</div>
<input <input
@ -5990,7 +6043,7 @@
data-stored="tileCols" data-stored="tileCols"
type="range" type="range"
min="2" min="2"
max="20" max="26"
value="8" value="8"
style="width: 10em" style="width: 10em"
/> />
@ -6003,7 +6056,7 @@
data-stored="tileRows" data-stored="tileRows"
type="range" type="range"
min="2" min="2"
max="20" max="26"
value="8" value="8"
style="width: 10em" style="width: 10em"
/> />
@ -6026,7 +6079,7 @@
<div class="label">Total size:</div> <div class="label">Total size:</div>
<div id="tileSize" style="display: inline-block">1000 x 1000 px</div> <div id="tileSize" style="display: inline-block">1000 x 1000 px</div>
</div> </div>
<div id="tileStatus" style="background-color: #33333310; font-style: italic"></div> <div id="tileStatus" style="font-style: italic"></div>
</div> </div>
<div id="resampleDialog" style="display: none" class="dialog"> <div id="resampleDialog" style="display: none" class="dialog">
@ -7964,7 +8017,7 @@
<script src="utils/commonUtils.js?v=1.89.29"></script> <script src="utils/commonUtils.js?v=1.89.29"></script>
<script src="utils/arrayUtils.js"></script> <script src="utils/arrayUtils.js"></script>
<script src="utils/colorUtils.js"></script> <script src="utils/colorUtils.js"></script>
<script src="utils/graphUtils.js?v=1.93.12"></script> <script src="utils/graphUtils.js?v=1.96.00"></script>
<script src="utils/nodeUtils.js"></script> <script src="utils/nodeUtils.js"></script>
<script src="utils/numberUtils.js?v=1.89.08"></script> <script src="utils/numberUtils.js?v=1.89.08"></script>
<script src="utils/polyfills.js?v=1.95.03"></script> <script src="utils/polyfills.js?v=1.95.03"></script>
@ -7977,39 +8030,39 @@
<script src="config/heightmap-templates.js"></script> <script src="config/heightmap-templates.js"></script>
<script src="config/precreated-heightmaps.js"></script> <script src="config/precreated-heightmaps.js"></script>
<script src="modules/heightmap-generator.js?v=1.88.00"></script> <script src="modules/heightmap-generator.js?v=1.88.00"></script>
<script src="modules/ocean-layers.js?v=1.95.00"></script> <script src="modules/ocean-layers.js?v=1.96.00"></script>
<script src="modules/river-generator.js?v=1.89.13"></script> <script src="modules/river-generator.js?v=1.89.13"></script>
<script src="modules/lakes.js"></script> <script src="modules/lakes.js"></script>
<script src="modules/biomes.js"></script> <script src="modules/biomes.js"></script>
<script src="modules/names-generator.js?v=1.87.14"></script> <script src="modules/names-generator.js?v=1.87.14"></script>
<script src="modules/cultures-generator.js?v=1.89.10"></script> <script src="modules/cultures-generator.js?v=1.96.05"></script>
<script src="modules/renderers/state-labels.js"></script> <script src="modules/renderers/state-labels.js?v=1.96.04"></script>
<script src="modules/burgs-and-states.js?v=1.92.00"></script> <script src="modules/burgs-and-states.js?v=1.97.00"></script>
<script src="modules/routes-generator.js"></script> <script src="modules/routes-generator.js"></script>
<script src="modules/religions-generator.js?v=1.93.08"></script> <script src="modules/religions-generator.js?v=1.93.08"></script>
<script src="modules/military-generator.js"></script> <script src="modules/military-generator.js?v=1.96.01"></script>
<script src="modules/markers-generator.js?v=1.93.04"></script> <script src="modules/markers-generator.js?v=1.93.04"></script>
<script src="modules/coa-generator.js?v=1.91.05"></script> <script src="modules/coa-generator.js?v=1.91.05"></script>
<script src="modules/submap.js?v=1.94.01"></script> <script src="modules/submap.js?v=1.96.00"></script>
<script src="libs/polylabel.min.js"></script> <script src="libs/polylabel.min.js"></script>
<script src="libs/lineclip.min.js"></script> <script src="libs/lineclip.min.js"></script>
<script src="libs/alea.min.js"></script> <script src="libs/alea.min.js"></script>
<script src="modules/fonts.js?v=1.89.18"></script> <script src="modules/fonts.js?v=1.89.18"></script>
<script src="modules/ui/layers.js?v=1.94.00"></script> <script src="modules/ui/layers.js?v=1.96.00"></script>
<script src="modules/ui/measurers.js?v=1.94.03"></script> <script src="modules/ui/measurers.js?v=1.96.00"></script>
<script src="modules/ui/stylePresets.js?v=1.95.00"></script> <script src="modules/ui/stylePresets.js?v=1.96.00"></script>
<script src="modules/ui/general.js?v=1.94.01"></script> <script src="modules/ui/general.js?v=1.96.00"></script>
<script src="modules/ui/options.js?v=1.94.06"></script> <script src="modules/ui/options.js?v=1.96.00"></script>
<script src="main.js?v=1.94.05"></script> <script src="main.js?v=1.97.00"></script>
<script defer src="modules/relief-icons.js"></script> <script defer src="modules/relief-icons.js"></script>
<script defer src="modules/ui/style.js?v=1.95.00"></script> <script defer src="modules/ui/style.js?v=1.96.00"></script>
<script defer src="modules/ui/editors.js?v=1.95.04"></script> <script defer src="modules/ui/editors.js?v=1.97.00"></script>
<script defer src="modules/ui/tools.js?v=1.95.01"></script> <script defer src="modules/ui/tools.js?v=1.96.03"></script>
<script defer src="modules/ui/world-configurator.js?v=1.91.05"></script> <script defer src="modules/ui/world-configurator.js?v=1.98.00"></script>
<script defer src="modules/ui/heightmap-editor.js?v=1.93.00"></script> <script defer src="modules/ui/heightmap-editor.js?v=1.96.00"></script>
<script defer src="modules/ui/provinces-editor.js?v=1.92.00"></script> <script defer src="modules/ui/provinces-editor.js?v=1.96.00"></script>
<script defer src="modules/ui/biomes-editor.js?v=1.91.05"></script> <script defer src="modules/ui/biomes-editor.js?v=1.91.05"></script>
<script defer src="modules/ui/namesbase-editor.js?v=1.95.02"></script> <script defer src="modules/ui/namesbase-editor.js?v=1.95.02"></script>
<script defer src="modules/ui/elevation-profile.js"></script> <script defer src="modules/ui/elevation-profile.js"></script>
@ -8022,14 +8075,14 @@
<script defer src="modules/ui/rivers-editor.js"></script> <script defer src="modules/ui/rivers-editor.js"></script>
<script defer src="modules/ui/rivers-creator.js?v=1.89.13"></script> <script defer src="modules/ui/rivers-creator.js?v=1.89.13"></script>
<script defer src="modules/ui/relief-editor.js"></script> <script defer src="modules/ui/relief-editor.js"></script>
<script defer src="modules/ui/burg-editor.js"></script> <script defer src="modules/ui/burg-editor.js?v=1.97.00"></script>
<script defer src="modules/ui/units-editor.js?v=1.94.02"></script> <script defer src="modules/ui/units-editor.js?v=1.96.00"></script>
<script defer src="modules/ui/notes-editor.js?v=1.93.09"></script> <script defer src="modules/ui/notes-editor.js?v=1.93.09"></script>
<script defer src="modules/ui/diplomacy-editor.js?v=1.88.04"></script> <script defer src="modules/ui/diplomacy-editor.js?v=1.88.04"></script>
<script defer src="modules/ui/zones-editor.js"></script> <script defer src="modules/ui/zones-editor.js"></script>
<script defer src="modules/ui/burgs-overview.js?v=1.93.10"></script> <script defer src="modules/ui/burgs-overview.js?v=1.97.00"></script>
<script defer src="modules/ui/rivers-overview.js"></script> <script defer src="modules/ui/rivers-overview.js"></script>
<script defer src="modules/ui/military-overview.js"></script> <script defer src="modules/ui/military-overview.js?v=1.96.07"></script>
<script defer src="modules/ui/regiments-overview.js?v=1.89.20"></script> <script defer src="modules/ui/regiments-overview.js?v=1.89.20"></script>
<script defer src="modules/ui/markers-overview.js?v=1.89.38"></script> <script defer src="modules/ui/markers-overview.js?v=1.89.38"></script>
<script defer src="modules/ui/regiment-editor.js"></script> <script defer src="modules/ui/regiment-editor.js"></script>
@ -8037,16 +8090,15 @@
<script defer src="modules/ui/emblems-editor.js?v=1.91.00"></script> <script defer src="modules/ui/emblems-editor.js?v=1.91.00"></script>
<script defer src="modules/ui/markers-editor.js"></script> <script defer src="modules/ui/markers-editor.js"></script>
<script defer src="modules/ui/3d.js?v=1.94.03"></script> <script defer src="modules/ui/3d.js?v=1.94.03"></script>
<script defer src="modules/ui/submap.js?v=1.94.03"></script> <script defer src="modules/ui/submap.js?v=1.96.00"></script>
<script defer src="modules/ui/hotkeys.js?v=1.95.00"></script> <script defer src="modules/ui/hotkeys.js?v=1.95.00"></script>
<script defer src="modules/coa-renderer.js?v=1.94.00"></script> <script defer src="modules/coa-renderer.js?v=1.94.00"></script>
<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.93.02"></script> <script defer src="modules/io/save.js?v=1.96.00"></script>
<script defer src="modules/io/load.js?v=1.95.00"></script> <script defer src="modules/io/load.js?v=1.97.04"></script>
<script defer src="modules/io/cloud.js?v=1.94.04"></script> <script defer src="modules/io/cloud.js?v=1.96.00"></script>
<script defer src="modules/io/export.js?v=1.95.05"></script> <script defer src="modules/io/export.js?v=1.97.03"></script>
<script defer src="modules/io/formats.js"></script>
<!-- Web Components --> <!-- Web Components -->
<script defer src="components/fill-box.js"></script> <script defer src="components/fill-box.js"></script>

16
libs/jquery-ui.css vendored
View file

@ -313,8 +313,9 @@ body .ui-dialog {
background-color: inherit; background-color: inherit;
} }
.ui-dialog .ui-dialog-titlebar { .ui-dialog .ui-dialog-titlebar {
padding: 0.4em 1em; display: flex;
position: relative; padding: 0.4em 0.3em;
justify-content: space-evenly;
font-size: 1.2em; font-size: 1.2em;
min-width: 150px; min-width: 150px;
} }
@ -328,9 +329,6 @@ body .ui-dialog {
} }
.ui-dialog .ui-dialog-titlebar button { .ui-dialog .ui-dialog-titlebar button {
position: absolute;
right: 0.5em;
top: 53%;
padding: 0; padding: 0;
width: 1.8em; width: 1.8em;
height: 1.8em; height: 1.8em;
@ -340,14 +338,6 @@ body .ui-dialog {
border: 1px solid #c5c5c5; border: 1px solid #c5c5c5;
} }
.ui-dialog .ui-dialog-titlebar button.ui-dialog-titlebar-collapse {
margin: -1em 2.2em 0 0;
}
.ui-dialog .ui-dialog-titlebar button.ui-dialog-titlebar-close {
margin: -1em 0 0;
}
.ui-dialog .ui-dialog-titlebar button:active { .ui-dialog .ui-dialog-titlebar button:active {
border: 1px solid #5d4651; border: 1px solid #5d4651;
color: #5d4651; color: #5d4651;

View file

@ -1 +1 @@
!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).polylabel=t()}}(function(){return function t(n,e,r){function o(a,u){if(!e[a]){if(!n[a]){var f="function"==typeof require&&require;if(!u&&f)return f(a,!0);if(i)return i(a,!0);var h=new Error("Cannot find module '"+a+"'");throw h.code="MODULE_NOT_FOUND",h}var s=e[a]={exports:{}};n[a][0].call(s.exports,function(t){var e=n[a][1][t];return o(e||t)},s,s.exports,t,n,e,r)}return e[a].exports}for(var i="function"==typeof require&&require,a=0;a<r.length;a++)o(r[a]);return o}({1:[function(t,n,e){"use strict";var r=t("tinyqueue");function o(t,n){return n.max-t.max}function i(t,n,e,r){this.x=t,this.y=n,this.h=e,this.d=function(t,n,e){for(var r=!1,o=1/0,i=0;i<e.length;i++)for(var u=e[i],f=0,h=u.length,s=h-1;f<h;s=f++){var d=u[f],l=u[s];d[1]>n!=l[1]>n&&t<(l[0]-d[0])*(n-d[1])/(l[1]-d[1])+d[0]&&(r=!r),o=Math.min(o,a(t,n,d,l))}return(r?1:-1)*Math.sqrt(o)}(t,n,r),this.max=this.d+this.h*Math.SQRT2}function a(t,n,e,r){var o=e[0],i=e[1],a=r[0]-o,u=r[1]-i;if(0!==a||0!==u){var f=((t-o)*a+(n-i)*u)/(a*a+u*u);f>1?(o=r[0],i=r[1]):f>0&&(o+=a*f,i+=u*f)}return(a=t-o)*a+(u=n-i)*u}n.exports=function(t,n,e){var a,u,f,h;n=n||1;for(var s=0;s<t[0].length;s++){var d=t[0][s];(!s||d[0]<a)&&(a=d[0]),(!s||d[1]<u)&&(u=d[1]),(!s||d[0]>f)&&(f=d[0]),(!s||d[1]>h)&&(h=d[1])}for(var l=f-a,p=h-u,c=Math.min(l,p),v=c/2,g=new r(null,o),x=a;x<f;x+=c)for(var y=u;y<h;y+=c)g.push(new i(x+v,y+v,v,t));var w=function(t){for(var n=0,e=0,r=0,o=t[0],a=0,u=o.length,f=u-1;a<u;f=a++){var h=o[a],s=o[f],d=h[0]*s[1]-s[0]*h[1];e+=(h[0]+s[0])*d,r+=(h[1]+s[1])*d,n+=3*d}return new i(e/n,r/n,0,t)}(t),m=g.length;for(;g.length;){var b=g.pop();b.d>w.d&&(w=b,e&&console.log("found best %d after %d probes",Math.round(1e4*b.d)/1e4,m)),b.max-w.d<=n||(v=b.h/2,g.push(new i(b.x-v,b.y-v,v,t)),g.push(new i(b.x+v,b.y-v,v,t)),g.push(new i(b.x-v,b.y+v,v,t)),g.push(new i(b.x+v,b.y+v,v,t)),m+=4)}e&&(console.log("num probes: "+m),console.log("best distance: "+w.d));return[w.x,w.y]}},{tinyqueue:2}],2:[function(t,n,e){"use strict";function r(t,n){if(!(this instanceof r))return new r(t,n);if(this.data=t||[],this.length=this.data.length,this.compare=n||o,t)for(var e=Math.floor(this.length/2);e>=0;e--)this._down(e)}function o(t,n){return t<n?-1:t>n?1:0}function i(t,n,e){var r=t[n];t[n]=t[e],t[e]=r}n.exports=r,r.prototype={push:function(t){this.data.push(t),this.length++,this._up(this.length-1)},pop:function(){var t=this.data[0];return this.data[0]=this.data[this.length-1],this.length--,this.data.pop(),this._down(0),t},peek:function(){return this.data[0]},_up:function(t){for(var n=this.data,e=this.compare;t>0;){var r=Math.floor((t-1)/2);if(!(e(n[t],n[r])<0))break;i(n,r,t),t=r}},_down:function(t){for(var n=this.data,e=this.compare,r=this.length;;){var o=2*t+1,a=o+1,u=t;if(o<r&&e(n[o],n[u])<0&&(u=o),a<r&&e(n[a],n[u])<0&&(u=a),u===t)return;i(n,u,t),t=u}}}},{}]},{},[1])(1)}); !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).polylabel=t()}}(function(){return function t(n,e,r){function o(a,u){if(!e[a]){if(!n[a]){var f="function"==typeof require&&require;if(!u&&f)return f(a,!0);if(i)return i(a,!0);var h=new Error("Cannot find module '"+a+"'");throw h.code="MODULE_NOT_FOUND",h}var s=e[a]={exports:{}};n[a][0].call(s.exports,function(t){var e=n[a][1][t];return o(e||t)},s,s.exports,t,n,e,r)}return e[a].exports}for(var i="function"==typeof require&&require,a=0;a<r.length;a++)o(r[a]);return o}({1:[function(t,n,e){"use strict";var r=t("tinyqueue");function o(t,n){return n.max-t.max}function i(t,n,e,r){this.x=t,this.y=n,this.h=e,this.d=function(t,n,e){for(var r=!1,o=1/0,i=0;i<e.length;i++)for(var u=e[i],f=0,h=u.length,s=h-1;f<h;s=f++){var d=u[f],l=u[s];d[1]>n!=l[1]>n&&t<(l[0]-d[0])*(n-d[1])/(l[1]-d[1])+d[0]&&(r=!r),o=Math.min(o,a(t,n,d,l))}return(r?1:-1)*Math.sqrt(o)}(t,n,r),this.max=this.d+this.h*Math.SQRT2}function a(t,n,e,r){var o=e[0],i=e[1],a=r[0]-o,u=r[1]-i;if(0!==a||0!==u){var f=((t-o)*a+(n-i)*u)/(a*a+u*u);f>1?(o=r[0],i=r[1]):f>0&&(o+=a*f,i+=u*f)}return(a=t-o)*a+(u=n-i)*u}n.exports=function(t,n,e){var a,u,f,h;n=n||1;for(var s=0;s<t[0].length;s++){var d=t[0][s];(!s||d[0]<a)&&(a=d[0]),(!s||d[1]<u)&&(u=d[1]),(!s||d[0]>f)&&(f=d[0]),(!s||d[1]>h)&&(h=d[1])}for(var l=f-a,p=h-u,c=Math.min(l,p),v=c/2,g=new r(null,o),x=a;x<f;x+=c)for(var y=u;y<h;y+=c)g.push(new i(x+v,y+v,v,t));var w=function(t){for(var n=0,e=0,r=0,o=t[0],a=0,u=o.length,f=u-1;a<u;f=a++){var h=o[a],s=o[f],d=h[0]*s[1]-s[0]*h[1];e+=(h[0]+s[0])*d,r+=(h[1]+s[1])*d,n+=3*d}return new i(e/n,r/n,0,t)}(t),m=g.length;for(;g.length;){var b=g.pop();b.d>w.d&&(w=b,e&&console.info("found best %d after %d probes",Math.round(1e4*b.d)/1e4,m)),b.max-w.d<=n||(v=b.h/2,g.push(new i(b.x-v,b.y-v,v,t)),g.push(new i(b.x+v,b.y-v,v,t)),g.push(new i(b.x-v,b.y+v,v,t)),g.push(new i(b.x+v,b.y+v,v,t)),m+=4)}e&&(console.info("num probes: "+m),console.info("best distance: "+w.d));return[w.x,w.y]}},{tinyqueue:2}],2:[function(t,n,e){"use strict";function r(t,n){if(!(this instanceof r))return new r(t,n);if(this.data=t||[],this.length=this.data.length,this.compare=n||o,t)for(var e=Math.floor(this.length/2);e>=0;e--)this._down(e)}function o(t,n){return t<n?-1:t>n?1:0}function i(t,n,e){var r=t[n];t[n]=t[e],t[e]=r}n.exports=r,r.prototype={push:function(t){this.data.push(t),this.length++,this._up(this.length-1)},pop:function(){var t=this.data[0];return this.data[0]=this.data[this.length-1],this.length--,this.data.pop(),this._down(0),t},peek:function(){return this.data[0]},_up:function(t){for(var n=this.data,e=this.compare;t>0;){var r=Math.floor((t-1)/2);if(!(e(n[t],n[r])<0))break;i(n,r,t),t=r}},_down:function(t){for(var n=this.data,e=this.compare,r=this.length;;){var o=2*t+1,a=o+1,u=t;if(o<r&&e(n[o],n[u])<0&&(u=o),a<r&&e(n[a],n[u])<0&&(u=a),u===t)return;i(n,u,t),t=u}}}},{}]},{},[1])(1)});

2
libs/three.min.js vendored

File diff suppressed because one or more lines are too long

58
main.js
View file

@ -92,16 +92,19 @@ let fogging = viewbox
let ruler = viewbox.append("g").attr("id", "ruler").style("display", "none"); let ruler = viewbox.append("g").attr("id", "ruler").style("display", "none");
let debug = viewbox.append("g").attr("id", "debug"); let debug = viewbox.append("g").attr("id", "debug");
// lake and coast groups
lakes.append("g").attr("id", "freshwater"); lakes.append("g").attr("id", "freshwater");
lakes.append("g").attr("id", "salt"); lakes.append("g").attr("id", "salt");
lakes.append("g").attr("id", "sinkhole"); lakes.append("g").attr("id", "sinkhole");
lakes.append("g").attr("id", "frozen"); lakes.append("g").attr("id", "frozen");
lakes.append("g").attr("id", "lava"); lakes.append("g").attr("id", "lava");
lakes.append("g").attr("id", "dry"); lakes.append("g").attr("id", "dry");
coastline.append("g").attr("id", "sea_island"); coastline.append("g").attr("id", "sea_island");
coastline.append("g").attr("id", "lake_island"); coastline.append("g").attr("id", "lake_island");
terrs.append("g").attr("id", "oceanHeights");
terrs.append("g").attr("id", "landHeights");
labels.append("g").attr("id", "states"); labels.append("g").attr("id", "states");
labels.append("g").attr("id", "addedLabels"); labels.append("g").attr("id", "addedLabels");
@ -182,12 +185,13 @@ const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", onZoomDebouced);
// default options, based on Earth data // default options, based on Earth data
let options = { let options = {
pinNotes: false, pinNotes: false,
showMFCGMap: true,
winds: [225, 45, 225, 315, 135, 315], winds: [225, 45, 225, 315, 135, 315],
temperatureEquator: 27, temperatureEquator: 27,
temperatureNorthPole: -30, temperatureNorthPole: -30,
temperatureSouthPole: -15, temperatureSouthPole: -15,
stateLabelsMode: "auto" stateLabelsMode: "auto",
showBurgPreview: true,
villageMaxPopulation: 2000
}; };
let mapCoordinates = {}; // map coordinates on globe let mapCoordinates = {}; // map coordinates on globe
@ -841,8 +845,8 @@ function openNearSeaLakes() {
const LIMIT = 22; // max height that can be breached by water const LIMIT = 22; // max height that can be breached by water
for (const i of cells.i) { for (const i of cells.i) {
const lake = cells.f[i]; const lakeFeatureId = cells.f[i];
if (features[lake].type !== "lake") continue; // not a lake cell if (features[lakeFeatureId].type !== "lake") continue; // not a lake
check_neighbours: for (const c of cells.c[i]) { check_neighbours: for (const c of cells.c[i]) {
if (cells.t[c] !== 1 || cells.h[c] > LIMIT) continue; // water cannot break this if (cells.t[c] !== 1 || cells.h[c] > LIMIT) continue; // water cannot break this
@ -850,20 +854,24 @@ function openNearSeaLakes() {
for (const n of cells.c[c]) { for (const n of cells.c[c]) {
const ocean = cells.f[n]; const ocean = cells.f[n];
if (features[ocean].type !== "ocean") continue; // not an ocean if (features[ocean].type !== "ocean") continue; // not an ocean
removeLake(c, lake, ocean); removeLake(c, lakeFeatureId, ocean);
break check_neighbours; break check_neighbours;
} }
} }
} }
function removeLake(threshold, lake, ocean) { function removeLake(thresholdCellId, lakeFeatureId, oceanFeatureId) {
cells.h[threshold] = 19; cells.h[thresholdCellId] = 19;
cells.t[threshold] = -1; cells.t[thresholdCellId] = -1;
cells.f[threshold] = ocean; cells.f[thresholdCellId] = oceanFeatureId;
cells.c[threshold].forEach(function (c) { cells.c[thresholdCellId].forEach(function (c) {
if (cells.h[c] >= 20) cells.t[c] = 1; // mark as coastline if (cells.h[c] >= 20) cells.t[c] = 1; // mark as coastline
}); });
features[lake].type = "ocean"; // mark former lake as ocean
cells.i.forEach(i => {
if (cells.f[i] === lakeFeatureId) cells.f[i] = oceanFeatureId;
});
features[lakeFeatureId].type = "ocean"; // mark former lake as ocean
} }
TIME && console.timeEnd("openLakes"); TIME && console.timeEnd("openLakes");
@ -1250,6 +1258,7 @@ function drawCoastline() {
features[f].vertices = vchain; features[f].vertices = vchain;
const path = round(lineGen(points)); const path = round(lineGen(points));
if (features[f].type === "lake") { if (features[f].type === "lake") {
landMask landMask
.append("path") .append("path")
@ -1347,22 +1356,14 @@ function drawCoastline() {
// Re-mark features (ocean, lakes, islands) // Re-mark features (ocean, lakes, islands)
function reMarkFeatures() { function reMarkFeatures() {
TIME && console.time("reMarkFeatures"); TIME && console.time("reMarkFeatures");
const cells = pack.cells, const cells = pack.cells;
features = (pack.features = [0]); const features = (pack.features = [0]);
cells.f = new Uint16Array(cells.i.length); // cell feature number cells.f = new Uint16Array(cells.i.length); // cell feature number
cells.t = new Int8Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast; cells.t = new Int8Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast;
cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); // cell haven (opposite water cell); cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); // cell haven (opposite water cell);
cells.harbor = new Uint8Array(cells.i.length); // cell harbor (number of adjacent water cells); cells.harbor = new Uint8Array(cells.i.length); // cell harbor (number of adjacent water cells);
const defineHaven = i => {
const water = cells.c[i].filter(c => cells.h[c] < 20);
const dist2 = water.map(c => (cells.p[i][0] - cells.p[c][0]) ** 2 + (cells.p[i][1] - cells.p[c][1]) ** 2);
const closest = water[dist2.indexOf(Math.min.apply(Math, dist2))];
cells.haven[i] = closest;
cells.harbor[i] = water.length;
};
if (!cells.i.length) return; // no cells -> there is nothing to do if (!cells.i.length) return; // no cells -> there is nothing to do
for (let i = 1, queue = [0]; queue[0] !== -1; i++) { for (let i = 1, queue = [0]; queue[0] !== -1; i++) {
const start = queue[0]; // first cell const start = queue[0]; // first cell
@ -1403,6 +1404,15 @@ function reMarkFeatures() {
// markupPackLand // markupPackLand
markup(pack.cells, 3, 1, 0); markup(pack.cells, 3, 1, 0);
function defineHaven(i) {
const water = cells.c[i].filter(c => cells.h[c] < 20);
const dist2 = water.map(c => (cells.p[i][0] - cells.p[c][0]) ** 2 + (cells.p[i][1] - cells.p[c][1]) ** 2);
const closest = water[dist2.indexOf(Math.min.apply(Math, dist2))];
cells.haven[i] = closest;
cells.harbor[i] = water.length;
}
function defineOceanGroup(number) { function defineOceanGroup(number) {
if (number > grid.cells.i.length / 25) return "ocean"; if (number > grid.cells.i.length / 25) return "ocean";
if (number > grid.cells.i.length / 100) return "sea"; if (number > grid.cells.i.length / 100) return "sea";
@ -1925,7 +1935,7 @@ function showStatistics() {
mapId = Date.now(); // unique map id is it's creation date number mapId = Date.now(); // unique map id is it's creation date number
mapHistory.push({seed, width: graphWidth, height: graphHeight, template: heightmap, created: mapId}); mapHistory.push({seed, width: graphWidth, height: graphHeight, template: heightmap, created: mapId});
INFO && console.log(stats); INFO && console.info(stats);
} }
const regenerateMap = debounce(async function (options) { const regenerateMap = debounce(async function (options) {

View file

@ -252,13 +252,15 @@ window.BurgsAndStates = (function () {
.filter(b => (newburg ? b.i == newburg.i : b.i && !b.removed)) .filter(b => (newburg ? b.i == newburg.i : b.i && !b.removed))
.forEach(b => { .forEach(b => {
const pop = b.population; const pop = b.population;
b.citadel = b.capital || (pop > 50 && P(0.75)) || P(0.5) ? 1 : 0; b.citadel = Number(b.capital || (pop > 50 && P(0.75)) || (pop > 15 && P(0.5)) || P(0.1));
b.plaza = pop > 50 || (pop > 30 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.25) ? 1 : 0; b.plaza = Number(pop > 20 || (pop > 10 && P(0.8)) || (pop > 4 && P(0.7)) || P(0.6));
b.walls = b.capital || pop > 30 || (pop > 20 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.2) ? 1 : 0; b.walls = Number(b.capital || pop > 30 || (pop > 20 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.1));
b.shanty = pop > 60 || (pop > 40 && P(0.75)) || (pop > 20 && b.walls && P(0.4)) ? 1 : 0; b.shanty = Number(pop > 60 || (pop > 40 && P(0.75)) || (pop > 20 && b.walls && P(0.4)));
const religion = cells.religion[b.cell]; const religion = cells.religion[b.cell];
const theocracy = pack.states[b.state].form === "Theocracy"; const theocracy = pack.states[b.state].form === "Theocracy";
b.temple = (religion && theocracy) || pop > 50 || (pop > 35 && P(0.75)) || (pop > 20 && P(0.5)) ? 1 : 0; b.temple = Number(
(religion && theocracy && P(0.5)) || pop > 50 || (pop > 35 && P(0.75)) || (pop > 20 && P(0.5))
);
}); });
}; };
@ -859,9 +861,10 @@ window.BurgsAndStates = (function () {
if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals
} }
if (base === 16 && (form === "Empire" || form === "Kingdom")) return "Khaganate"; // Turkic if (base === 31 && (form === "Empire" || form === "Kingdom")) return "Khanate"; // Mongolian
if (base === 16 && form === "Principality") return "Beylik"; // Turkic
if (base === 5 && (form === "Empire" || form === "Kingdom")) return "Tsardom"; // Ruthenian if (base === 5 && (form === "Empire" || form === "Kingdom")) return "Tsardom"; // Ruthenian
if ([16, 31].includes(base) && (form === "Empire" || form === "Kingdom")) return "Khaganate"; // Turkic, Mongolian if (base === 16 && (form === "Empire" || form === "Kingdom")) return "Khaganate"; // Turkic
if (base === 12 && (form === "Kingdom" || form === "Grand Duchy")) return "Shogunate"; // Japanese if (base === 12 && (form === "Kingdom" || form === "Grand Duchy")) return "Shogunate"; // Japanese
if ([18, 17].includes(base) && form === "Empire") return "Caliphate"; // Arabic, Berber if ([18, 17].includes(base) && form === "Empire") return "Caliphate"; // Arabic, Berber
if (base === 18 && (form === "Grand Duchy" || form === "Duchy")) return "Emirate"; // Arabic if (base === 18 && (form === "Grand Duchy" || form === "Duchy")) return "Emirate"; // Arabic

View file

@ -71,28 +71,31 @@ window.Cultures = (function () {
return; return;
} }
const cell = (c.center = placeCenter(c.sort ? c.sort : i => cells.s[i])); const sortingFn = c.sort ? c.sort : i => cells.s[i];
centers.add(cells.p[cell]); const center = placeCenter(sortingFn);
centers.add(cells.p[center]);
c.center = center;
c.i = newId; c.i = newId;
delete c.odd; delete c.odd;
delete c.sort; delete c.sort;
c.color = colors[i]; c.color = colors[i];
c.type = defineCultureType(cell); c.type = defineCultureType(center);
c.expansionism = defineCultureExpansionism(c.type); c.expansionism = defineCultureExpansionism(c.type);
c.origins = [0]; c.origins = [0];
c.code = abbreviate(c.name, codes); c.code = abbreviate(c.name, codes);
codes.push(c.code); codes.push(c.code);
cultureIds[cell] = newId; cultureIds[center] = newId;
if (emblemShape === "random") c.shield = getRandomShield(); if (emblemShape === "random") c.shield = getRandomShield();
}); });
cells.culture = cultureIds; cells.culture = cultureIds;
function placeCenter(v) { function placeCenter(sortingFn) {
let spacing = (graphWidth + graphHeight) / 2 / count; let spacing = (graphWidth + graphHeight) / 2 / count;
const MAX_ATTEMPTS = 100; const MAX_ATTEMPTS = 100;
const sorted = [...populated].sort((a, b) => v(b) - v(a)); const sorted = [...populated].sort((a, b) => sortingFn(b) - sortingFn(a));
const max = Math.floor(sorted.length / 2); const max = Math.floor(sorted.length / 2);
let cellId = 0; let cellId = 0;
@ -187,12 +190,13 @@ window.Cultures = (function () {
name = Names.getCulture(culture, 5, 8, ""); name = Names.getCulture(culture, 5, 8, "");
base = pack.cultures[culture].base; base = pack.cultures[culture].base;
} }
const code = abbreviate( const code = abbreviate(
name, name,
pack.cultures.map(c => c.code) pack.cultures.map(c => c.code)
); );
const i = pack.cultures.length; const i = pack.cultures.length;
const color = d3.color(d3.scaleSequential(d3.interpolateRainbow)(Math.random())).hex(); const color = getRandomColor();
// define emblem shape // define emblem shape
let shield = culture.shield; let shield = culture.shield;
@ -211,7 +215,7 @@ window.Cultures = (function () {
area: 0, area: 0,
rural: 0, rural: 0,
urban: 0, urban: 0,
origins: [0], origins: [pack.cells.culture[center]],
code, code,
shield shield
}); });

View file

@ -736,4 +736,111 @@ export function resolveVersionConflicts(version) {
.style("display", "none"); .style("display", "none");
vignette.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%"); vignette.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
} }
if (version < 1.96) {
// v1.96 added ocean rendering for heightmap
terrs.selectAll("*").remove();
const opacity = terrs.attr("opacity");
const filter = terrs.attr("filter");
const scheme = terrs.attr("scheme") || "bright";
const terracing = terrs.attr("terracing");
const skip = terrs.attr("skip");
const relax = terrs.attr("relax");
const curveTypes = {0: "curveBasisClosed", 1: "curveLinear", 2: "curveStep"};
const curve = curveTypes[terrs.attr("curve")] || "curveBasisClosed";
terrs
.attr("mask", null)
.attr("scheme", null)
.attr("terracing", null)
.attr("skip", null)
.attr("relax", null)
.attr("curve", null);
terrs
.append("g")
.attr("id", "oceanHeights")
.attr("data-render", 0)
.attr("opacity", opacity)
.attr("filter", filter)
.attr("scheme", scheme)
.attr("terracing", 0)
.attr("skip", 0)
.attr("relax", 1)
.attr("curve", curve);
terrs
.append("g")
.attr("id", "landHeights")
.attr("opacity", opacity)
.attr("scheme", scheme)
.attr("filter", filter)
.attr("terracing", terracing)
.attr("skip", skip)
.attr("relax", relax)
.attr("curve", curve)
.attr("mask", "url(#land)");
if (layerIsOn("toggleHeight")) drawHeightmap();
// v1.96.00 moved scaleBar options from units editor to style
d3.select("#scaleBar").remove();
scaleBar = svg
.insert("g", "#viewbox + *")
.attr("id", "scaleBar")
.attr("opacity", 1)
.attr("fill", "#353540")
.attr("data-bar-size", 2)
.attr("font-size", 10)
.attr("data-x", 99)
.attr("data-y", 99)
.attr("data-label", "");
scaleBar
.append("rect")
.attr("id", "scaleBarBack")
.attr("opacity", 0.2)
.attr("fill", "#ffffff")
.attr("stroke", "#000000")
.attr("stroke-width", 1)
.attr("filter", "url(#blur5)")
.attr("data-top", 20)
.attr("data-right", 15)
.attr("data-bottom", 15)
.attr("data-left", 10);
drawScaleBar(scaleBar, scale);
fitScaleBar(scaleBar, svgWidth, svgHeight);
if (!layerIsOn("toggleScaleBar")) scaleBar.style("display", "none");
// v1.96.00 changed coloring approach for regiments
armies.selectAll(":scope > g").each(function () {
const fill = this.getAttribute("fill");
if (!fill) return;
const darkerColor = d3.color(fill).darker().hex();
this.setAttribute("color", darkerColor);
this.querySelectorAll("g > rect:nth-child(2)").forEach(rect => {
rect.setAttribute("fill", "currentColor");
});
});
}
if (version < 1.97) {
// v1.97.00 changed MFCG link to an arbitrary preview URL
options.villageMaxPopulation = 2000;
options.showBurgPreview = options.showMFCGMap;
delete options.showMFCGMap;
pack.burgs.forEach(burg => {
if (!burg.i || burg.removed) return;
if (burg.MFCG) {
burg.link = getBurgLink(burg);
delete burg.MFCG;
}
});
}
} }

View file

@ -822,6 +822,7 @@ function addCulture() {
if (pack.cells.h[center] < 20) if (pack.cells.h[center] < 20)
return tip("You cannot place culture center into the water. Please click on a land cell", false, "error"); return tip("You cannot place culture center into the water. Please click on a land cell", false, "error");
const occupied = pack.cultures.some(c => !c.removed && c.center === center); const occupied = pack.cultures.some(c => !c.removed && c.center === center);
if (occupied) return tip("This cell is already a culture center. Please select a different cell", false, "error"); if (occupied) return tip("This cell is already a culture center. Please select a different cell", false, "error");
@ -857,8 +858,19 @@ function closeCulturesEditor() {
} }
async function uploadCulturesData() { async function uploadCulturesData() {
const csv = await Formats.csvParser(this.files[0]); const file = this.files[0];
this.value = ""; this.value = "";
const csv = await file.text();
const data = d3.csvParse(csv, d => ({
i: +d.Id,
name: d.Name,
color: d.Color,
expansionism: +d.Expansionism,
type: d.Type,
population: +d.Population,
emblemsShape: d["Emblems Shape"],
origins: d.Origins
}));
const {cultures, cells} = pack; const {cultures, cells} = pack;
const shapes = Object.keys(COA.shields.types) const shapes = Object.keys(COA.shields.types)
@ -870,20 +882,26 @@ async function uploadCulturesData() {
if (item.i) item.removed = true; if (item.i) item.removed = true;
}); });
for (const c of csv.iterator((a, b) => +a[0] > +b[0])) { for (const culture of data) {
let current; let current;
if (+c.id < cultures.length) { if (culture.i < cultures.length) {
current = cultures[c.id]; current = cultures[culture.i];
const ratio = current.urban / (current.rural + current.urban); const ratio = current.urban / (current.rural + current.urban);
applyPopulationChange(current.rural, current.urban, c.population * (1 - ratio), c.population * ratio, +c.id); applyPopulationChange(
current.rural,
current.urban,
culture.population * (1 - ratio),
culture.population * ratio,
culture.i
);
} else { } else {
current = {i: cultures.length, center: ra(populated), area: 0, cells: 0, origin: 0, rural: 0, urban: 0}; current = {i: cultures.length, center: ra(populated), area: 0, cells: 0, origin: 0, rural: 0, urban: 0};
cultures.push(current); cultures.push(current);
} }
current.removed = false; current.removed = false;
current.name = c.name; current.name = culture.name;
if (current.i) { if (current.i) {
current.code = abbreviate( current.code = abbreviate(
@ -891,10 +909,10 @@ async function uploadCulturesData() {
cultures.map(c => c.code) cultures.map(c => c.code)
); );
current.color = c.color; current.color = culture.color;
current.expansionism = +c.expansionism; current.expansionism = +culture.expansionism;
if (cultureTypes.includes(c.type)) current.type = c.type; if (cultureTypes.includes(culture.type)) current.type = culture.type;
else current.type = "Generic"; else current.type = "Generic";
} }
@ -913,13 +931,11 @@ async function uploadCulturesData() {
current.origins = originIds.filter(id => id !== null); current.origins = originIds.filter(id => id !== null);
if (!current.origins.length) current.origins = [0]; if (!current.origins.length) current.origins = [0];
} }
c.origins = current.i ? restoreOrigins(c.origins) : [null];
const shieldShape = c["emblems shape"].toLowerCase(); culture.origins = current.i ? restoreOrigins(culture.origins || "") : [null];
if (shapes.includes(shieldShape)) current.shield = shieldShape; current.shield = shapes.includes(culture.emblemsShape) ? culture.emblemsShape : "heater";
else current.shield = "heater";
const nameBaseIndex = nameBases.findIndex(n => n.name == c.namesbase); const nameBaseIndex = nameBases.findIndex(n => n.name == culture.namesbase);
current.base = nameBaseIndex === -1 ? 0 : nameBaseIndex; current.base = nameBaseIndex === -1 ? 0 : nameBaseIndex;
} }

View file

@ -624,28 +624,36 @@ function stateRemovePrompt(state) {
}); });
} }
function stateRemove(state) { function stateRemove(stateId) {
statesBody.select("#state" + state).remove(); statesBody.select("#state" + stateId).remove();
statesBody.select("#state-gap" + state).remove(); statesBody.select("#state-gap" + stateId).remove();
statesHalo.select("#state-border" + state).remove(); statesHalo.select("#state-border" + stateId).remove();
labels.select("#stateLabel" + state).remove(); labels.select("#stateLabel" + stateId).remove();
defs.select("#textPath_stateLabel" + state).remove(); defs.select("#textPath_stateLabel" + stateId).remove();
unfog("focusState" + state); unfog("focusState" + stateId);
pack.burgs.forEach(b => {
if (b.state === state) b.state = 0; pack.burgs.forEach(burg => {
if (burg.state === stateId) {
burg.state = 0;
if (burg.capital) {
burg.capital = 0;
moveBurgToGroup(burg.i, "towns");
}
}
}); });
pack.cells.state.forEach((s, i) => { pack.cells.state.forEach((s, i) => {
if (s === state) pack.cells.state[i] = 0; if (s === stateId) pack.cells.state[i] = 0;
}); });
// remove emblem // remove emblem
const coaId = "stateCOA" + state; const coaId = "stateCOA" + stateId;
byId(coaId).remove(); byId(coaId).remove();
emblems.select(`#stateEmblems > use[data-i='${state}']`).remove(); emblems.select(`#stateEmblems > use[data-i='${stateId}']`).remove();
// remove provinces // remove provinces
pack.states[state].provinces.forEach(p => { pack.states[stateId].provinces.forEach(p => {
pack.provinces[p] = {i: p, removed: true}; pack.provinces[p] = {i: p, removed: true};
pack.cells.province.forEach((pr, i) => { pack.cells.province.forEach((pr, i) => {
if (pr === p) pack.cells.province[i] = 0; if (pr === p) pack.cells.province[i] = 0;
@ -660,19 +668,14 @@ function stateRemove(state) {
}); });
// remove military // remove military
pack.states[state].military.forEach(m => { pack.states[stateId].military.forEach(m => {
const id = `regiment${state}-${m.i}`; const id = `regiment${stateId}-${m.i}`;
const index = notes.findIndex(n => n.id === id); const index = notes.findIndex(n => n.id === id);
if (index != -1) notes.splice(index, 1); if (index != -1) notes.splice(index, 1);
}); });
armies.select("g#army" + state).remove(); armies.select("g#army" + stateId).remove();
const capital = pack.states[state].capital; pack.states[stateId] = {i: stateId, removed: true};
pack.burgs[capital].capital = 0;
pack.burgs[capital].state = 0;
moveBurgToGroup(capital, "towns");
pack.states[state] = {i: state, removed: true};
debug.selectAll(".highlight").remove(); debug.selectAll(".highlight").remove();
if (!layerIsOn("toggleStates")) toggleStates(); if (!layerIsOn("toggleStates")) toggleStates();
@ -1394,6 +1397,7 @@ function openStateMergeDialog() {
function mergeStates(statesToMerge, rulingStateId) { function mergeStates(statesToMerge, rulingStateId) {
const rulingState = pack.states[rulingStateId]; const rulingState = pack.states[rulingStateId];
const rulingStateArmy = byId("army" + rulingStateId);
// remove states to be merged // remove states to be merged
statesToMerge.forEach(stateId => { statesToMerge.forEach(stateId => {
@ -1410,27 +1414,25 @@ function openStateMergeDialog() {
emblems.select(`#stateEmblems > use[data-i='${stateId}']`).remove(); emblems.select(`#stateEmblems > use[data-i='${stateId}']`).remove();
// add merged state regiments to the ruling state // add merged state regiments to the ruling state
state.military.forEach(m => { state.military.forEach(regiment => {
const oldId = `regiment${stateId}-${m.i}`; const oldId = `regiment${stateId}-${regiment.i}`;
const newIndex = rulingState.military.length;
const newRegiment = {...m, i: rulingState.military.length}; rulingState.military.push({...regiment, i: newIndex});
rulingState.military.push(newRegiment); const newId = `regiment${rulingStateId}-${newIndex}`;
const newId = `regiment${rulingStateId}-${newRegiment.i}`;
const note = notes.find(n => n.id === oldId); const note = notes.find(n => n.id === oldId);
if (note) note.id = newId; if (note) note.id = newId;
const rulingStateArmy = armies.select("g#army" + rulingStateId); const element = byId(oldId);
armies if (element) {
.select("g#army" + stateId) element.id = newId;
.selectAll("g") element.dataset.state = rulingStateId;
.each(function () { element.dataset.i = newIndex;
this.setAttribute("id", newId); rulingStateArmy.appendChild(element);
rulingStateArmy.node().appendChild(this); }
});
armies.select("g#army" + stateId).remove();
}); });
armies.select("g#army" + stateId).remove();
}); });
// reassing burgs // reassing burgs

View file

@ -88,12 +88,6 @@ function getSettings() {
heightUnit: heightUnit.value, heightUnit: heightUnit.value,
heightExponent: heightExponentInput.value, heightExponent: heightExponentInput.value,
temperatureScale: temperatureScale.value, temperatureScale: temperatureScale.value,
barSize: barSizeInput.value,
barLabel: barLabel.value,
barBackOpacity: barBackOpacity.value,
barBackColor: barBackColor.value,
barPosX: barPosX.value,
barPosY: barPosY.value,
populationRate: populationRate, populationRate: populationRate,
urbanization: urbanization, urbanization: urbanization,
mapSize: mapSizeOutput.value, mapSize: mapSizeOutput.value,

View file

@ -314,6 +314,6 @@ function confirmHeightmapEdit() {
function getHeightmapPreview(heights) { function getHeightmapPreview(heights) {
const scheme = getColorScheme(byId("heightmapSelectionColorScheme").value); const scheme = getColorScheme(byId("heightmapSelectionColorScheme").value);
const renderOcean = byId("heightmapSelectionRenderOcean").checked; const renderOcean = byId("heightmapSelectionRenderOcean").checked;
const dataUrl = drawHeights({heights, width: grid.cellsX, height: grid.cellsY, scheme, renderOcean}); const dataUrl = drawHeights({heights, width: graph.cellsX, height: graph.cellsY, scheme, renderOcean});
return dataUrl; return dataUrl;
} }

View file

@ -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.log("Dropbox response:", resp); DEBUG && 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.log("Access token:", token); DEBUG && 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.log("Dropbox link object:", resp.result); DEBUG && console.info("Dropbox link object:", resp.result);
return resp.result.url; return resp.result.url;
} }
}; };

View file

@ -71,70 +71,94 @@ async function exportToJpeg() {
} }
async function exportToPngTiles() { async function exportToPngTiles() {
return new Promise(async (resolve, reject) => { const status = byId("tileStatus");
// download schema status.innerHTML = "Preparing files...";
const urlSchema = await getMapURL("tiles", {debug: true, fullMap: true});
await import("../../libs/jszip.min.js");
const zip = new window.JSZip();
const canvas = document.createElement("canvas"); const urlSchema = await getMapURL("tiles", {debug: true, fullMap: true});
const ctx = canvas.getContext("2d"); await import("../../libs/jszip.min.js");
canvas.width = graphWidth; const zip = new window.JSZip();
canvas.height = graphHeight;
const imgSchema = new Image(); const canvas = document.createElement("canvas");
imgSchema.src = urlSchema; const ctx = canvas.getContext("2d");
imgSchema.onload = function () { canvas.width = graphWidth;
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height); canvas.height = graphHeight;
canvas.toBlob(blob => zip.file(`fmg_tile_schema.png`, blob));
};
// download tiles const imgSchema = new Image();
const url = await getMapURL("tiles", {fullMap: true}); imgSchema.src = urlSchema;
const tilesX = +document.getElementById("tileColsInput").value; await loadImage(imgSchema);
const tilesY = +document.getElementById("tileRowsInput").value;
const scale = +document.getElementById("tileScaleInput").value;
const tileW = (graphWidth / tilesX) | 0; status.innerHTML = "Drawing schema...";
const tileH = (graphHeight / tilesY) | 0; ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
const tolesTotal = tilesX * tilesY; const blob = await canvasToBlob(canvas, "image/png");
ctx.clearRect(0, 0, canvas.width, canvas.height);
zip.file("schema.png", blob);
const width = graphWidth * scale; // download tiles
const height = width * (tileH / tileW); const url = await getMapURL("tiles", {fullMap: true});
canvas.width = width; const tilesX = +byId("tileColsInput").value;
canvas.height = height; const tilesY = +byId("tileRowsInput").value;
const scale = +byId("tileScaleInput").value;
const tolesTotal = tilesX * tilesY;
let loaded = 0; const tileW = (graphWidth / tilesX) | 0;
const img = new Image(); const tileH = (graphHeight / tilesY) | 0;
img.src = url;
img.onload = function () {
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
const name = `fmg_tile_${i}.png`;
canvas.toBlob(blob => {
zip.file(name, blob);
loaded += 1;
if (loaded === tolesTotal) return downloadZip();
});
}
}
};
function downloadZip() { const width = graphWidth * scale;
const name = `${getFileName()}.zip`; const height = width * (tileH / tileW);
zip.generateAsync({type: "blob"}).then(blob => { canvas.width = width;
const link = document.createElement("a"); canvas.height = height;
link.href = URL.createObjectURL(blob);
link.download = name;
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(link.href), 5000); const img = new Image();
resolve(true); img.src = url;
}); await loadImage(img);
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (let y = 0, row = 0, id = 1; y + tileH <= graphHeight; y += tileH, row++) {
const rowName = alphabet[row % alphabet.length];
for (let x = 0, cell = 1; x + tileW <= graphWidth; x += tileW, cell++, id++) {
status.innerHTML = `Drawing tile ${rowName}${cell} (${id} of ${tolesTotal})...`;
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
const blob = await canvasToBlob(canvas, "image/png");
ctx.clearRect(0, 0, canvas.width, canvas.height);
zip.file(`${rowName}${cell}.png`, blob);
} }
}
status.innerHTML = "Zipping files...";
zip.generateAsync({type: "blob"}).then(blob => {
status.innerHTML = "Downloading the archive...";
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = getFileName() + ".zip";
link.click();
link.remove();
status.innerHTML = 'Done. Check .zip file in "Downloads" (crtl + J)';
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
}); });
// promisified img.onload
function loadImage(img) {
return new Promise((resolve, reject) => {
img.onload = () => resolve();
img.onerror = err => reject(err);
});
}
// promisified canvas.toBlob
function canvasToBlob(canvas, mimeType, qualityArgument = 1) {
return new Promise((resolve, reject) => {
canvas.toBlob(
blob => {
if (blob) resolve(blob);
else reject(new Error("Canvas toBlob() error"));
},
mimeType,
qualityArgument
);
});
}
} }
// parse map svg to object url // parse map svg to object url
@ -148,14 +172,14 @@ async function getMapURL(type, options) {
fullMap = false fullMap = false
} = options || {}; } = options || {};
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg const cloneEl = byId("map").cloneNode(true); // clone svg
cloneEl.id = "fantasyMap"; cloneEl.id = "fantasyMap";
document.body.appendChild(cloneEl); document.body.appendChild(cloneEl);
const clone = d3.select(cloneEl); const clone = d3.select(cloneEl);
if (!debug) clone.select("#debug")?.remove(); if (!debug) clone.select("#debug")?.remove();
const cloneDefs = cloneEl.getElementsByTagName("defs")[0]; const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
const svgDefs = document.getElementById("defElements"); const svgDefs = byId("defElements");
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isFirefox && type === "mesh") clone.select("#oceanPattern")?.remove(); if (isFirefox && type === "mesh") clone.select("#oceanPattern")?.remove();
@ -218,7 +242,7 @@ async function getMapURL(type, options) {
.forEach(el => { .forEach(el => {
const href = el.getAttribute("href") || el.getAttribute("xlink:href"); const href = el.getAttribute("href") || el.getAttribute("xlink:href");
if (!href) return; if (!href) return;
const emblem = document.getElementById(href.slice(1)); const emblem = byId(href.slice(1));
if (emblem) cloneDefs.append(emblem.cloneNode(true)); if (emblem) cloneDefs.append(emblem.cloneNode(true));
}); });
} else { } else {
@ -364,7 +388,7 @@ function removeUnusedElements(clone) {
function updateMeshCells(clone) { function updateMeshCells(clone) {
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20); const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
const scheme = getColorScheme(terrs.attr("scheme")); const scheme = getColorScheme(terrs.select("#landHeights").attr("scheme"));
clone.select("#heights").attr("filter", "url(#blur1)"); clone.select("#heights").attr("filter", "url(#blur1)");
clone clone
.select("#heights") .select("#heights")

View file

@ -1,24 +0,0 @@
"use strict";
window.Formats = (function () {
async function csvParser(file, separator = ",") {
const txt = await file.text();
const rows = txt.split("\n");
const headers = rows
.shift()
.split(separator)
.map(x => x.toLowerCase());
const data = rows.filter(a => a.trim() !== "").map(r => r.split(separator));
return {
headers,
data,
iterator: function* (sortf) {
const dataset = sortf ? this.data.sort(sortf) : this.data;
for (const d of dataset) yield Object.fromEntries(d.map((a, i) => [this.headers[i], a]));
}
};
}
return {csvParser};
})();

View file

@ -12,7 +12,7 @@ async function quickLoad() {
async function loadFromDropbox() { async function loadFromDropbox() {
const mapPath = byId("loadFromDropboxSelect")?.value; const mapPath = byId("loadFromDropboxSelect")?.value;
DEBUG && console.log("Loading map from Dropbox:", mapPath); DEBUG && 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);
} }
@ -183,22 +183,22 @@ function showUploadMessage(type, mapData, mapVersion) {
title = "Newer file"; title = "Newer file";
canBeLoaded = false; canBeLoaded = false;
} else if (type === "outdated") { } else if (type === "outdated") {
message = `The map version (${mapVersion}) does not match the Generator version (${version}).<br>That is fine, click OK to the get map <b style="color: #005000">auto-updated</b>.<br>In case of issues please keep using an ${archive} of the Generator`; INFO && console.info(`Loading map. Auto-update from ${mapVersion} to ${version}`);
title = "Outdated file"; parseLoadedData(mapData, mapVersion);
canBeLoaded = true; return;
} }
alertMessage.innerHTML = message; alertMessage.innerHTML = message;
const buttons = { const buttons = {
OK: function () { OK: function () {
$(this).dialog("close"); $(this).dialog("close");
if (canBeLoaded) parseLoadedData(mapData); if (canBeLoaded) parseLoadedData(mapData, mapVersion);
} }
}; };
$("#alert").dialog({title, buttons}); $("#alert").dialog({title, buttons});
} }
async function parseLoadedData(data) { async function parseLoadedData(data, mapVersion) {
try { try {
// exit customization // exit customization
if (window.closeDialogs) closeDialogs(); if (window.closeDialogs) closeDialogs();
@ -218,6 +218,7 @@ async function parseLoadedData(data) {
INFO && console.group("Loaded Map " + seed); INFO && console.group("Loaded Map " + seed);
// TODO: move all to options object
void (function parseSettings() { void (function parseSettings() {
const settings = data[1].split("|"); const settings = data[1].split("|");
if (settings[0]) applyOption(distanceUnitInput, settings[0]); if (settings[0]) applyOption(distanceUnitInput, settings[0]);
@ -226,23 +227,16 @@ async function parseLoadedData(data) {
if (settings[3]) applyOption(heightUnit, settings[3]); if (settings[3]) applyOption(heightUnit, settings[3]);
if (settings[4]) heightExponentInput.value = heightExponentOutput.value = settings[4]; if (settings[4]) heightExponentInput.value = heightExponentOutput.value = settings[4];
if (settings[5]) temperatureScale.value = settings[5]; if (settings[5]) temperatureScale.value = settings[5];
if (settings[6]) barSizeInput.value = barSizeOutput.value = settings[6]; // setting 6-11 (scaleBar) are part of style now, kept as "" in newer versions for compatibility
if (settings[7] !== undefined) barLabel.value = settings[7];
if (settings[8] !== undefined) barBackOpacity.value = settings[8];
if (settings[9]) barBackColor.value = settings[9];
if (settings[10]) barPosX.value = settings[10];
if (settings[11]) barPosY.value = settings[11];
if (settings[12]) populationRate = populationRateInput.value = populationRateOutput.value = settings[12]; if (settings[12]) populationRate = populationRateInput.value = populationRateOutput.value = settings[12];
if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13]; if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13];
if (settings[14]) mapSizeInput.value = mapSizeOutput.value = minmax(settings[14], 1, 100); if (settings[14]) mapSizeInput.value = mapSizeOutput.value = minmax(settings[14], 1, 100);
if (settings[15]) latitudeInput.value = latitudeOutput.value = minmax(settings[15], 0, 100); if (settings[15]) latitudeInput.value = latitudeOutput.value = minmax(settings[15], 0, 100);
if (settings[18]) precInput.value = precOutput.value = settings[18]; if (settings[18]) precInput.value = precOutput.value = settings[18];
if (settings[19]) options = JSON.parse(settings[19]); if (settings[19]) options = JSON.parse(settings[19]);
// setting 16 and 17 (temperature) are part of options now, kept as "" in newer versions for compatibility // setting 16 and 17 (temperature) are part of options now, kept as "" in newer versions for compatibility
if (settings[16]) options.temperatureEquator = +settings[16]; if (settings[16]) options.temperatureEquator = +settings[16];
if (settings[17]) options.temperatureNorthPole = options.temperatureSouthPole = +settings[17]; if (settings[17]) options.temperatureNorthPole = options.temperatureSouthPole = +settings[17];
if (settings[20]) mapName.value = settings[20]; if (settings[20]) mapName.value = settings[20];
if (settings[21]) hideLabels.checked = +settings[21]; if (settings[21]) hideLabels.checked = +settings[21];
if (settings[22]) stylePreset.value = settings[22]; if (settings[22]) stylePreset.value = settings[22];
@ -462,16 +456,16 @@ async function parseLoadedData(data) {
{ {
// dynamically import and run auto-update script // dynamically import and run auto-update script
const versionNumber = parseFloat(params[0]); const versionNumber = parseFloat(params[0]);
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.95.00"); const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.97.04");
resolveVersionConflicts(versionNumber); resolveVersionConflicts(versionNumber);
} }
{ // add custom heightmap color scheme if any
// add custom heightmap color scheme if any if (heightmapColorSchemes) {
const scheme = terrs.attr("scheme"); const oceanScheme = byId("oceanHeights")?.getAttribute("scheme");
if (!(scheme in heightmapColorSchemes)) { if (oceanScheme && !(oceanScheme in heightmapColorSchemes)) addCustomColorScheme(oceanScheme);
addCustomColorScheme(scheme); const landScheme = byId("#landHeights")?.getAttribute("scheme");
} if (landScheme && !(landScheme in heightmapColorSchemes)) addCustomColorScheme(landScheme);
} }
{ {
@ -484,7 +478,7 @@ async function parseLoadedData(data) {
const cells = pack.cells; const cells = pack.cells;
if (pack.cells.i.length !== pack.cells.state.length) { if (pack.cells.i.length !== pack.cells.state.length) {
const message = "Data Integrity Check. Striping issue detected. To fix edit the heightmap in erase mode"; const message = "Data integrity check. Striping issue detected. To fix edit the heightmap in ERASE mode";
ERROR && console.error(message); ERROR && console.error(message);
} }
@ -492,7 +486,7 @@ async function parseLoadedData(data) {
invalidStates.forEach(s => { invalidStates.forEach(s => {
const invalidCells = cells.i.filter(i => cells.state[i] === s); const invalidCells = cells.i.filter(i => cells.state[i] === s);
invalidCells.forEach(i => (cells.state[i] = 0)); invalidCells.forEach(i => (cells.state[i] = 0));
ERROR && console.error("Data Integrity Check. Invalid state", s, "is assigned to cells", invalidCells); ERROR && console.error("Data integrity check. Invalid state", s, "is assigned to cells", invalidCells);
}); });
const invalidProvinces = [...new Set(cells.province)].filter( const invalidProvinces = [...new Set(cells.province)].filter(
@ -501,14 +495,14 @@ async function parseLoadedData(data) {
invalidProvinces.forEach(p => { invalidProvinces.forEach(p => {
const invalidCells = cells.i.filter(i => cells.province[i] === p); const invalidCells = cells.i.filter(i => cells.province[i] === p);
invalidCells.forEach(i => (cells.province[i] = 0)); invalidCells.forEach(i => (cells.province[i] = 0));
ERROR && console.error("Data Integrity Check. Invalid province", p, "is assigned to cells", invalidCells); ERROR && console.error("Data integrity check. Invalid province", p, "is assigned to cells", invalidCells);
}); });
const invalidCultures = [...new Set(cells.culture)].filter(c => !pack.cultures[c] || pack.cultures[c].removed); const invalidCultures = [...new Set(cells.culture)].filter(c => !pack.cultures[c] || pack.cultures[c].removed);
invalidCultures.forEach(c => { invalidCultures.forEach(c => {
const invalidCells = cells.i.filter(i => cells.culture[i] === c); const invalidCells = cells.i.filter(i => cells.culture[i] === c);
invalidCells.forEach(i => (cells.province[i] = 0)); invalidCells.forEach(i => (cells.province[i] = 0));
ERROR && console.error("Data Integrity Check. Invalid culture", c, "is assigned to cells", invalidCells); ERROR && console.error("Data integrity check. Invalid culture", c, "is assigned to cells", invalidCells);
}); });
const invalidReligions = [...new Set(cells.religion)].filter( const invalidReligions = [...new Set(cells.religion)].filter(
@ -517,14 +511,14 @@ async function parseLoadedData(data) {
invalidReligions.forEach(r => { invalidReligions.forEach(r => {
const invalidCells = cells.i.filter(i => cells.religion[i] === r); const invalidCells = cells.i.filter(i => cells.religion[i] === r);
invalidCells.forEach(i => (cells.religion[i] = 0)); invalidCells.forEach(i => (cells.religion[i] = 0));
ERROR && console.error("Data Integrity Check. Invalid religion", r, "is assigned to cells", invalidCells); ERROR && console.error("Data integrity check. Invalid religion", r, "is assigned to cells", invalidCells);
}); });
const invalidFeatures = [...new Set(cells.f)].filter(f => f && !pack.features[f]); const invalidFeatures = [...new Set(cells.f)].filter(f => f && !pack.features[f]);
invalidFeatures.forEach(f => { invalidFeatures.forEach(f => {
const invalidCells = cells.i.filter(i => cells.f[i] === f); const invalidCells = cells.i.filter(i => cells.f[i] === f);
// No fix as for now // No fix as for now
ERROR && console.error("Data Integrity Check. Invalid feature", f, "is assigned to cells", invalidCells); ERROR && console.error("Data integrity check. Invalid feature", f, "is assigned to cells", invalidCells);
}); });
const invalidBurgs = [...new Set(cells.burg)].filter( const invalidBurgs = [...new Set(cells.burg)].filter(
@ -533,7 +527,7 @@ async function parseLoadedData(data) {
invalidBurgs.forEach(burgId => { invalidBurgs.forEach(burgId => {
const invalidCells = cells.i.filter(i => cells.burg[i] === burgId); const invalidCells = cells.i.filter(i => cells.burg[i] === burgId);
invalidCells.forEach(i => (cells.burg[i] = 0)); invalidCells.forEach(i => (cells.burg[i] = 0));
ERROR && console.error("Data Integrity Check. Invalid burg", burgId, "is assigned to cells", invalidCells); ERROR && console.error("Data integrity check. Invalid burg", burgId, "is assigned to cells", invalidCells);
}); });
const invalidRivers = [...new Set(cells.r)].filter(r => r && !pack.rivers.find(river => river.i === r)); const invalidRivers = [...new Set(cells.r)].filter(r => r && !pack.rivers.find(river => river.i === r));
@ -541,60 +535,112 @@ async function parseLoadedData(data) {
const invalidCells = cells.i.filter(i => cells.r[i] === r); const invalidCells = cells.i.filter(i => cells.r[i] === r);
invalidCells.forEach(i => (cells.r[i] = 0)); invalidCells.forEach(i => (cells.r[i] = 0));
rivers.select("river" + r).remove(); rivers.select("river" + r).remove();
ERROR && console.error("Data Integrity Check. Invalid river", r, "is assigned to cells", invalidCells); ERROR && console.error("Data integrity check. Invalid river", r, "is assigned to cells", invalidCells);
}); });
pack.burgs.forEach(burg => { pack.burgs.forEach(burg => {
if ((!burg.i || burg.removed) && burg.lock) { if (typeof burg.capital === "boolean") burg.capital = Number(burg.capital);
if (!burg.i && burg.lock) {
ERROR && console.error(`Data integrity check. Burg 0 is marked as locked, removing the status`);
delete burg.lock;
return;
}
if (burg.removed && burg.lock) {
ERROR && ERROR &&
console.error( console.error(`Data integrity check. Removed burg ${burg.i} is marked as locked. Unlocking the burg`);
`Data Integrity Check. Burg ${burg.i || "0"} is removed or invalid but still locked. Unlocking the burg`
);
delete burg.lock; delete burg.lock;
return; return;
} }
if (!burg.i || burg.removed) return; if (!burg.i || burg.removed) return;
if (burg.cell === undefined || burg.x === undefined || burg.y === undefined) { if (burg.cell === undefined || burg.x === undefined || burg.y === undefined) {
ERROR && ERROR &&
console.error( console.error(
`Data Integrity Check. Burg ${burg.i} is missing cell info or coordinates. Removing the burg` `Data integrity check. Burg ${burg.i} is missing cell info or coordinates. Removing the burg`
); );
burg.removed = true; burg.removed = true;
} }
if (burg.port < 0) { if (burg.port < 0) {
ERROR && console.error("Data Integrity Check. Burg", burg.i, "has invalid port value", burg.port); ERROR && console.error("Data integrity check. Burg", burg.i, "has invalid port value", burg.port);
burg.port = 0; burg.port = 0;
} }
if (burg.cell >= cells.i.length) { if (burg.cell >= cells.i.length) {
ERROR && console.error("Data Integrity Check. Burg", burg.i, "is linked to invalid cell", burg.cell); ERROR && console.error("Data integrity check. Burg", burg.i, "is linked to invalid cell", burg.cell);
burg.cell = findCell(burg.x, burg.y); burg.cell = findCell(burg.x, burg.y);
cells.i.filter(i => cells.burg[i] === burg.i).forEach(i => (cells.burg[i] = 0)); cells.i.filter(i => cells.burg[i] === burg.i).forEach(i => (cells.burg[i] = 0));
cells.burg[burg.cell] = burg.i; cells.burg[burg.cell] = burg.i;
} }
if (burg.state && !pack.states[burg.state]) { if (burg.state && !pack.states[burg.state]) {
ERROR && console.error("Data Integrity Check. Burg", burg.i, "is linked to invalid state", burg.state); ERROR && console.error("Data integrity check. Burg", burg.i, "is linked to invalid state", burg.state);
burg.state = 0; burg.state = 0;
} }
if (burg.state && pack.states[burg.state].removed) { if (burg.state && pack.states[burg.state].removed) {
ERROR && console.error("Data Integrity Check. Burg", burg.i, "is linked to removed state", burg.state); ERROR && console.error("Data integrity check. Burg", burg.i, "is linked to removed state", burg.state);
burg.state = 0; burg.state = 0;
} }
if (burg.state === undefined) { if (burg.state === undefined) {
ERROR && console.error("Data Integrity Check. Burg", burg.i, "has no state data"); ERROR && console.error("Data integrity check. Burg", burg.i, "has no state data");
burg.state = 0; burg.state = 0;
} }
}); });
pack.states.forEach(state => {
if (state.removed) return;
const stateBurgs = pack.burgs.filter(b => b.state === state.i && !b.removed);
const capitalBurgs = stateBurgs.filter(b => b.capital);
if (!state.i && capitalBurgs.length) {
ERROR &&
console.error(
`Data integrity check. Neutral burgs (${capitalBurgs
.map(b => b.i)
.join(", ")}) marked as capitals. Moving them to towns`
);
capitalBurgs.forEach(burg => {
burg.capital = 0;
moveBurgToGroup(burg.i, "towns");
});
return;
}
if (capitalBurgs.length > 1) {
const message = `Data integrity check. State ${state.i} has multiple capitals (${capitalBurgs
.map(b => b.i)
.join(", ")}) assigned. Keeping the first as capital and moving others to towns`;
ERROR && console.error(message);
capitalBurgs.forEach((burg, i) => {
if (!i) return;
burg.capital = 0;
moveBurgToGroup(burg.i, "towns");
});
return;
}
if (state.i && stateBurgs.length && !capitalBurgs.length) {
ERROR &&
console.error(`Data integrity check. State ${state.i} has no capital. Assigning the first burg as capital`);
stateBurgs[0].capital = 1;
moveBurgToGroup(stateBurgs[0].i, "cities");
}
});
pack.provinces.forEach(p => { pack.provinces.forEach(p => {
if (!p.i || p.removed) return; if (!p.i || p.removed) return;
if (pack.states[p.state] && !pack.states[p.state].removed) return; if (pack.states[p.state] && !pack.states[p.state].removed) return;
ERROR && console.error("Data Integrity Check. Province", p.i, "is linked to removed state", p.state); ERROR && console.error("Data integrity check. Province", p.i, "is linked to removed state", p.state);
p.removed = true; // remove incorrect province p.removed = true; // remove incorrect province
}); });
@ -604,7 +650,7 @@ async function parseLoadedData(data) {
pack.markers.forEach(marker => { pack.markers.forEach(marker => {
if (markerIds[marker.i]) { if (markerIds[marker.i]) {
ERROR && console.error("Data Integrity Check. Marker", marker.i, "has non-unique id. Changing to", nextId); ERROR && console.error("Data integrity check. Marker", marker.i, "has non-unique id. Changing to", nextId);
const domElements = document.querySelectorAll("#marker" + marker.i); const domElements = document.querySelectorAll("#marker" + marker.i);
if (domElements[1]) domElements[1].id = "marker" + nextId; // rename 2nd dom element if (domElements[1]) domElements[1].id = "marker" + nextId; // rename 2nd dom element
@ -645,7 +691,7 @@ async function parseLoadedData(data) {
ERROR && console.error(error); ERROR && console.error(error);
clearMainTip(); clearMainTip();
alertMessage.innerHTML = /* html */ `An error is occured on map loading. Select a different file to load, <br />generate a new random map or cancel the loading alertMessage.innerHTML = /* html */ `An error is occured on map loading. Select a different file to load, <br>generate a new random map or cancel the loading.<br>Map version: ${mapVersion}. Generator version: ${version}.
<p id="errorBox">${parseError(error)}</p>`; <p id="errorBox">${parseError(error)}</p>`;
$("#alert").dialog({ $("#alert").dialog({

View file

@ -49,12 +49,12 @@ function prepareMapData() {
heightUnit.value, heightUnit.value,
heightExponentInput.value, heightExponentInput.value,
temperatureScale.value, temperatureScale.value,
barSizeInput.value, "", // previously used for barSize.value
barLabel.value, "", // previously used for barLabel.value
barBackOpacity.value, "", // previously used for barBackColor.value
barBackColor.value, "", // previously used for barBackColor.value
barPosX.value, "", // previously used for barPosX.value
barPosY.value, "", // previously used for barPosY.value
populationRate, populationRate,
urbanization, urbanization,
mapSizeOutput.value, mapSizeOutput.value,

View file

@ -10,7 +10,18 @@ window.Military = (function () {
const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion
const area = d3.sum(valid.map(s => s.area)); // total area const area = d3.sum(valid.map(s => s.area)); // total area
const rate = {x: 0, Ally: -0.2, Friendly: -0.1, Neutral: 0, Suspicion: 0.1, Enemy: 1, Unknown: 0, Rival: 0.5, Vassal: 0.5, Suzerain: -0.5}; const rate = {
x: 0,
Ally: -0.2,
Friendly: -0.1,
Neutral: 0,
Suspicion: 0.1,
Enemy: 1,
Unknown: 0,
Rival: 0.5,
Vassal: 0.5,
Suzerain: -0.5
};
const stateModifier = { const stateModifier = {
melee: {Nomadic: 0.5, Highland: 1.2, Lake: 1, Naval: 0.7, Hunting: 1.2, River: 1.1}, melee: {Nomadic: 0.5, Highland: 1.2, Lake: 1, Naval: 0.7, Hunting: 1.2, River: 1.1},
@ -24,14 +35,59 @@ window.Military = (function () {
}; };
const cellTypeModifier = { const cellTypeModifier = {
nomadic: {melee: 0.2, ranged: 0.5, mounted: 3, machinery: 0.4, naval: 0.3, armored: 1.6, aviation: 1, magical: 0.5}, nomadic: {
wetland: {melee: 0.8, ranged: 2, mounted: 0.3, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5}, melee: 0.2,
highland: {melee: 1.2, ranged: 1.6, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2} ranged: 0.5,
mounted: 3,
machinery: 0.4,
naval: 0.3,
armored: 1.6,
aviation: 1,
magical: 0.5
},
wetland: {
melee: 0.8,
ranged: 2,
mounted: 0.3,
machinery: 1.2,
naval: 1.0,
armored: 0.2,
aviation: 0.5,
magical: 0.5
},
highland: {
melee: 1.2,
ranged: 1.6,
mounted: 0.3,
machinery: 3,
naval: 1.0,
armored: 0.8,
aviation: 0.3,
magical: 2
}
}; };
const burgTypeModifier = { const burgTypeModifier = {
nomadic: {melee: 0.3, ranged: 0.8, mounted: 3, machinery: 0.4, naval: 1.0, armored: 1.6, aviation: 1, magical: 0.5}, nomadic: {
wetland: {melee: 1, ranged: 1.6, mounted: 0.2, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5}, melee: 0.3,
ranged: 0.8,
mounted: 3,
machinery: 0.4,
naval: 1.0,
armored: 1.6,
aviation: 1,
magical: 0.5
},
wetland: {
melee: 1,
ranged: 1.6,
mounted: 0.2,
machinery: 1.2,
naval: 1.0,
armored: 0.2,
aviation: 0.5,
magical: 0.5
},
highland: {melee: 1.2, ranged: 2, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2} highland: {melee: 1.2, ranged: 2, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2}
}; };
@ -40,8 +96,16 @@ window.Military = (function () {
const d = s.diplomacy; const d = s.diplomacy;
const expansionRate = minmax(s.expansionism / expn / (s.area / area), 0.25, 4); // how much state expansionism is realized const expansionRate = minmax(s.expansionism / expn / (s.area / area), 0.25, 4); // how much state expansionism is realized
const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? 0.8 : d.some(d => d === "Suspicion") ? 0.5 : 0.1; // peacefulness const diplomacyRate = d.some(d => d === "Enemy")
const neighborsRateRaw = s.neighbors.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion")).reduce((s, r) => (s += rate[r]), 0.5); ? 1
: d.some(d => d === "Rival")
? 0.8
: d.some(d => d === "Suspicion")
? 0.5
: 0.1; // peacefulness
const neighborsRateRaw = s.neighbors
.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion"))
.reduce((s, r) => (s += rate[r]), 0.5);
const neighborsRate = minmax(neighborsRateRaw, 0.3, 3); // neighbors rate const neighborsRate = minmax(neighborsRateRaw, 0.3, 3); // neighbors rate
s.alert = minmax(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1, 5); // alert rate (area modifier) s.alert = minmax(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1, 5); // alert rate (area modifier)
s.temp.platoons = []; s.temp.platoons = [];
@ -86,8 +150,10 @@ window.Military = (function () {
let modifier = cells.pop[i] / 100; // basic rural army in percentages let modifier = cells.pop[i] / 100; // basic rural army in percentages
if (culture !== stateObj.culture) modifier = stateObj.form === "Union" ? modifier / 1.2 : modifier / 2; // non-dominant culture if (culture !== stateObj.culture) modifier = stateObj.form === "Union" ? modifier / 1.2 : modifier / 2; // non-dominant culture
if (religion !== cells.religion[stateObj.center]) modifier = stateObj.form === "Theocracy" ? modifier / 2.2 : modifier / 1.4; // non-dominant religion if (religion !== cells.religion[stateObj.center])
if (cells.f[i] !== cells.f[stateObj.center]) modifier = stateObj.type === "Naval" ? modifier / 1.2 : modifier / 1.8; // different landmass modifier = stateObj.form === "Theocracy" ? modifier / 2.2 : modifier / 1.4; // non-dominant religion
if (cells.f[i] !== cells.f[stateObj.center])
modifier = stateObj.type === "Naval" ? modifier / 1.2 : modifier / 1.8; // different landmass
const type = getType(i); const type = getType(i);
for (const unit of options.military) { for (const unit of options.military) {
@ -111,7 +177,17 @@ window.Military = (function () {
n = 1; n = 1;
} }
stateObj.temp.platoons.push({cell: i, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type}); stateObj.temp.platoons.push({
cell: i,
a: total,
t: total,
x,
y,
u: unit.name,
n,
s: unit.separate,
type: unit.type
});
} }
} }
@ -153,7 +229,17 @@ window.Military = (function () {
n = 1; n = 1;
} }
stateObj.temp.platoons.push({cell: b.cell, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type}); stateObj.temp.platoons.push({
cell: b.cell,
a: total,
t: total,
x,
y,
u: unit.name,
n,
s: unit.separate,
type: unit.type
});
} }
} }
@ -261,7 +347,8 @@ window.Military = (function () {
const army = armies const army = armies
.append("g") .append("g")
.attr("id", "army" + s) .attr("id", "army" + s)
.attr("fill", baseColor); .attr("fill", baseColor)
.attr("color", darkerColor);
const g = army const g = army
.selectAll("g") .selectAll("g")
@ -282,7 +369,7 @@ window.Military = (function () {
.attr("y", d => d.y) .attr("y", d => d.y)
.text(d => getTotal(d)); .text(d => getTotal(d));
g.append("rect") g.append("rect")
.attr("fill", darkerColor) .attr("fill", "currentColor")
.attr("x", d => x(d) - h) .attr("x", d => x(d) - h)
.attr("y", d => y(d)) .attr("y", d => y(d))
.attr("width", h) .attr("width", h)
@ -294,33 +381,34 @@ window.Military = (function () {
.text(d => d.icon); .text(d => d.icon);
}; };
const drawRegiment = function (reg, s) { const drawRegiment = function (reg, stateId) {
const size = +armies.attr("box-size"); const size = +armies.attr("box-size");
const w = reg.n ? size * 4 : size * 6; const w = reg.n ? size * 4 : size * 6;
const h = size * 2; const h = size * 2;
const x1 = rn(reg.x - w / 2, 2); const x1 = rn(reg.x - w / 2, 2);
const y1 = rn(reg.y - size, 2); const y1 = rn(reg.y - size, 2);
let army = armies.select("g#army" + s); let army = armies.select("g#army" + stateId);
if (!army.size()) { if (!army.size()) {
const baseColor = pack.states[s].color[0] === "#" ? pack.states[s].color : "#999"; const baseColor = pack.states[stateId].color[0] === "#" ? pack.states[stateId].color : "#999";
const darkerColor = d3.color(baseColor).darker().hex();
army = armies army = armies
.append("g") .append("g")
.attr("id", "army" + s) .attr("id", "army" + stateId)
.attr("fill", baseColor); .attr("fill", baseColor)
.attr("color", darkerColor);
} }
const darkerColor = d3.color(army.attr("fill")).darker().hex();
const g = army const g = army
.append("g") .append("g")
.attr("id", "regiment" + s + "-" + reg.i) .attr("id", "regiment" + stateId + "-" + reg.i)
.attr("data-name", reg.name) .attr("data-name", reg.name)
.attr("data-state", s) .attr("data-state", stateId)
.attr("data-id", reg.i); .attr("data-id", reg.i);
g.append("rect").attr("x", x1).attr("y", y1).attr("width", w).attr("height", h); 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(getTotal(reg)); g.append("text").attr("x", reg.x).attr("y", reg.y).text(getTotal(reg));
g.append("rect") g.append("rect")
.attr("fill", darkerColor) .attr("fill", "currentColor")
.attr("x", x1 - h) .attr("x", x1 - h)
.attr("y", y1) .attr("y", y1)
.attr("width", h) .attr("width", h)
@ -379,7 +467,13 @@ window.Military = (function () {
// get default regiment emblem // get default regiment emblem
const getEmblem = function (r) { const getEmblem = function (r) {
if (!r.n && !Object.values(r.u).length) return "🔰"; // "Newbie" regiment without troops if (!r.n && !Object.values(r.u).length) return "🔰"; // "Newbie" regiment without troops
if (!r.n && pack.states[r.state].form === "Monarchy" && pack.cells.burg[r.cell] && pack.burgs[pack.cells.burg[r.cell]].capital) return "👑"; // "Royal" regiment based in capital if (
!r.n &&
pack.states[r.state].form === "Monarchy" &&
pack.cells.burg[r.cell] &&
pack.burgs[pack.cells.burg[r.cell]].capital
)
return "👑"; // "Royal" regiment based in capital
const mainUnit = Object.entries(r.u).sort((a, b) => b[1] - a[1])[0][0]; // unit with more troops in regiment const mainUnit = Object.entries(r.u).sort((a, b) => b[1] - a[1])[0][0]; // unit with more troops in regiment
const unit = options.military.find(u => u.name === mainUnit); const unit = options.military.find(u => u.name === mainUnit);
return unit.icon; return unit.icon;
@ -400,7 +494,9 @@ window.Military = (function () {
.map(t => `${t}: ${r.u[t]}`) .map(t => `${t}: ${r.u[t]}`)
.join("\r\n") .join("\r\n")
: null; : null;
const troops = composition ? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.` : ""; const troops = composition
? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.`
: "";
const campaign = s.campaigns ? ra(s.campaigns) : null; const campaign = s.campaigns ? ra(s.campaigns) : null;
const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year - 100, 150, 1, options.year - 6); const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year - 100, 150, 1, options.year - 6);
@ -409,5 +505,16 @@ window.Military = (function () {
notes.push({id: `regiment${s.i}-${r.i}`, name: `${r.icon} ${r.name}`, legend}); notes.push({id: `regiment${s.i}-${r.i}`, name: `${r.icon} ${r.name}`, legend});
}; };
return {generate, redraw, getDefaultOptions, getName, generateNote, drawRegiments, drawRegiment, moveRegiment, getTotal, getEmblem}; return {
generate,
redraw,
getDefaultOptions,
getName,
generateNote,
drawRegiments,
drawRegiment,
moveRegiment,
getTotal,
getEmblem
};
})(); })();

View file

@ -4,6 +4,10 @@
function drawStateLabels(list) { function drawStateLabels(list) {
console.time("drawStateLabels"); console.time("drawStateLabels");
// temporary make the labels visible
const layerDisplay = labels.style("display");
labels.style("display", null);
const {cells, states, features} = pack; const {cells, states, features} = pack;
const stateIds = cells.state; const stateIds = cells.state;
@ -17,7 +21,11 @@ function drawStateLabels(list) {
const MAX_ITERATIONS = 100; const MAX_ITERATIONS = 100;
const labelPaths = getLabelPaths(); const labelPaths = getLabelPaths();
drawLabelPath(); const letterLength = checkExampleLetterLength();
drawLabelPath(letterLength);
// restore labels visibility
labels.style("display", layerDisplay);
function getLabelPaths() { function getLabelPaths() {
const labelPaths = []; const labelPaths = [];
@ -110,17 +118,22 @@ function drawStateLabels(list) {
} }
} }
function drawLabelPath() { function checkExampleLetterLength() {
const textGroup = d3.select("g#labels > g#states");
const testLabel = textGroup.append("text").attr("x", 0).attr("y", 0).text("Example");
const letterLength = testLabel.node().getComputedTextLength() / 7; // approximate length of 1 letter
testLabel.remove();
return letterLength;
}
function drawLabelPath(letterLength) {
const mode = options.stateLabelsMode || "auto"; const mode = options.stateLabelsMode || "auto";
const lineGen = d3.line().curve(d3.curveBundle.beta(1)); const lineGen = d3.line().curve(d3.curveBundle.beta(1));
const textGroup = d3.select("g#labels > g#states"); const textGroup = d3.select("g#labels > g#states");
const pathGroup = d3.select("defs > g#deftemp > g#textPaths"); const pathGroup = d3.select("defs > g#deftemp > g#textPaths");
const testLabel = textGroup.append("text").attr("x", 0).attr("y", 0).text("Example");
const letterLength = testLabel.node().getComputedTextLength() / 7; // approximate length of 1 letter
testLabel.remove();
for (const [stateId, pathPoints] of labelPaths) { for (const [stateId, pathPoints] of labelPaths) {
const state = states[stateId]; const state = states[stateId];
if (!state.i || state.removed) throw new Error("State must not be neutral or removed"); if (!state.i || state.removed) throw new Error("State must not be neutral or removed");

View file

@ -28,7 +28,7 @@ window.Submap = (function () {
const projection = options.projection; const projection = options.projection;
const inverse = options.inverse; const inverse = options.inverse;
const stage = s => INFO && console.log("SUBMAP:", s); const stage = s => INFO && console.info("SUBMAP:", s);
const timeStart = performance.now(); const timeStart = performance.now();
invokeActiveZooming(); invokeActiveZooming();
@ -36,7 +36,7 @@ window.Submap = (function () {
seed = parentMap.seed; seed = parentMap.seed;
Math.random = aleaPRNG(seed); Math.random = aleaPRNG(seed);
INFO && console.group("SubMap with seed: " + seed); INFO && console.group("SubMap with seed: " + seed);
DEBUG && console.log("Using Options:", options); DEBUG && console.info("Using Options:", options);
// create new grid // create new grid
applyGraphSize(); applyGraphSize();
@ -396,7 +396,7 @@ window.Submap = (function () {
b.removed = true; b.removed = true;
return; return;
} }
DEBUG && console.log(`Moving ${b.name} from ${cityCell} to ${newCell} near ${neighbor}.`); DEBUG && console.info(`Moving ${b.name} from ${cityCell} to ${newCell} near ${neighbor}.`);
[b.x, b.y] = b.port ? getMiddlePoint(newCell, neighbor) : cells.p[newCell]; [b.x, b.y] = b.port ? getMiddlePoint(newCell, neighbor) : cells.p[newCell];
if (b.port) b.port = cells.f[neighbor]; // copy feature number if (b.port) b.port = cells.f[neighbor]; // copy feature number
b.cell = newCell; b.cell = newCell;

View file

@ -21,38 +21,37 @@ function editBurg(id) {
modules.editBurg = true; modules.editBurg = true;
// add listeners // add listeners
document.getElementById("burgGroupShow").addEventListener("click", showGroupSection); byId("burgGroupShow").addEventListener("click", showGroupSection);
document.getElementById("burgGroupHide").addEventListener("click", hideGroupSection); byId("burgGroupHide").addEventListener("click", hideGroupSection);
document.getElementById("burgSelectGroup").addEventListener("change", changeGroup); byId("burgSelectGroup").addEventListener("change", changeGroup);
document.getElementById("burgInputGroup").addEventListener("change", createNewGroup); byId("burgInputGroup").addEventListener("change", createNewGroup);
document.getElementById("burgAddGroup").addEventListener("click", toggleNewGroupInput); byId("burgAddGroup").addEventListener("click", toggleNewGroupInput);
document.getElementById("burgRemoveGroup").addEventListener("click", removeBurgsGroup); byId("burgRemoveGroup").addEventListener("click", removeBurgsGroup);
document.getElementById("burgName").addEventListener("input", changeName); byId("burgName").addEventListener("input", changeName);
document.getElementById("burgNameReRandom").addEventListener("click", generateNameRandom); byId("burgNameReRandom").addEventListener("click", generateNameRandom);
document.getElementById("burgType").addEventListener("input", changeType); byId("burgType").addEventListener("input", changeType);
document.getElementById("burgCulture").addEventListener("input", changeCulture); byId("burgCulture").addEventListener("input", changeCulture);
document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture); byId("burgNameReCulture").addEventListener("click", generateNameCulture);
document.getElementById("burgPopulation").addEventListener("change", changePopulation); byId("burgPopulation").addEventListener("change", changePopulation);
burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature)); burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature));
document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed); byId("burgLinkOpen").addEventListener("click", openBurgLink);
document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed); byId("burgLinkEdit").addEventListener("click", changeBurgLink);
document.getElementById("addCustomMFCGBurgLink").addEventListener("click", addCustomMfcgLink);
document.getElementById("burgStyleShow").addEventListener("click", showStyleSection); byId("burgStyleShow").addEventListener("click", showStyleSection);
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection); byId("burgStyleHide").addEventListener("click", hideStyleSection);
document.getElementById("burgEditLabelStyle").addEventListener("click", editGroupLabelStyle); byId("burgEditLabelStyle").addEventListener("click", editGroupLabelStyle);
document.getElementById("burgEditIconStyle").addEventListener("click", editGroupIconStyle); byId("burgEditIconStyle").addEventListener("click", editGroupIconStyle);
document.getElementById("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle); byId("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle);
document.getElementById("burgEmblem").addEventListener("click", openEmblemEdit); byId("burgEmblem").addEventListener("click", openEmblemEdit);
document.getElementById("burgToggleMFCGMap").addEventListener("click", toggleMFCGMap); byId("burgTogglePreview").addEventListener("click", toggleBurgPreview);
document.getElementById("burgEditEmblem").addEventListener("click", openEmblemEdit); byId("burgEditEmblem").addEventListener("click", openEmblemEdit);
document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg); byId("burgRelocate").addEventListener("click", toggleRelocateBurg);
document.getElementById("burglLegend").addEventListener("click", editBurgLegend); byId("burglLegend").addEventListener("click", editBurgLegend);
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton); byId("burgLock").addEventListener("click", toggleBurgLockButton);
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg); byId("burgRemove").addEventListener("click", removeSelectedBurg);
document.getElementById("burgTemperatureGraph").addEventListener("click", showTemperatureGraph); byId("burgTemperatureGraph").addEventListener("click", showTemperatureGraph);
function updateBurgValues() { function updateBurgValues() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
@ -60,46 +59,46 @@ function editBurg(id) {
const province = pack.cells.province[b.cell]; const province = pack.cells.province[b.cell];
const provinceName = province ? pack.provinces[province].fullName + ", " : ""; const provinceName = province ? pack.provinces[province].fullName + ", " : "";
const stateName = pack.states[b.state].fullName || pack.states[b.state].name; const stateName = pack.states[b.state].fullName || pack.states[b.state].name;
document.getElementById("burgProvinceAndState").innerHTML = provinceName + stateName; byId("burgProvinceAndState").innerHTML = provinceName + stateName;
document.getElementById("burgName").value = b.name; byId("burgName").value = b.name;
document.getElementById("burgType").value = b.type || "Generic"; byId("burgType").value = b.type || "Generic";
document.getElementById("burgPopulation").value = rn(b.population * populationRate * urbanization); byId("burgPopulation").value = rn(b.population * populationRate * urbanization);
document.getElementById("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none"; byId("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none";
// update list and select culture // update list and select culture
const cultureSelect = document.getElementById("burgCulture"); const cultureSelect = byId("burgCulture");
cultureSelect.options.length = 0; cultureSelect.options.length = 0;
const cultures = pack.cultures.filter(c => !c.removed); const cultures = pack.cultures.filter(c => !c.removed);
cultures.forEach(c => cultureSelect.options.add(new Option(c.name, c.i, false, c.i === b.culture))); cultures.forEach(c => cultureSelect.options.add(new Option(c.name, c.i, false, c.i === b.culture)));
const temperature = grid.cells.temp[pack.cells.g[b.cell]]; const temperature = grid.cells.temp[pack.cells.g[b.cell]];
document.getElementById("burgTemperature").innerHTML = convertTemperature(temperature); byId("burgTemperature").innerHTML = convertTemperature(temperature);
document.getElementById("burgTemperatureLikeIn").innerHTML = getTemperatureLikeness(temperature); byId("burgTemperatureLikeIn").innerHTML = getTemperatureLikeness(temperature);
document.getElementById("burgElevation").innerHTML = getHeight(pack.cells.h[b.cell]); byId("burgElevation").innerHTML = getHeight(pack.cells.h[b.cell]);
// toggle features // toggle features
if (b.capital) document.getElementById("burgCapital").classList.remove("inactive"); if (b.capital) byId("burgCapital").classList.remove("inactive");
else document.getElementById("burgCapital").classList.add("inactive"); else byId("burgCapital").classList.add("inactive");
if (b.port) document.getElementById("burgPort").classList.remove("inactive"); if (b.port) byId("burgPort").classList.remove("inactive");
else document.getElementById("burgPort").classList.add("inactive"); else byId("burgPort").classList.add("inactive");
if (b.citadel) document.getElementById("burgCitadel").classList.remove("inactive"); if (b.citadel) byId("burgCitadel").classList.remove("inactive");
else document.getElementById("burgCitadel").classList.add("inactive"); else byId("burgCitadel").classList.add("inactive");
if (b.walls) document.getElementById("burgWalls").classList.remove("inactive"); if (b.walls) byId("burgWalls").classList.remove("inactive");
else document.getElementById("burgWalls").classList.add("inactive"); else byId("burgWalls").classList.add("inactive");
if (b.plaza) document.getElementById("burgPlaza").classList.remove("inactive"); if (b.plaza) byId("burgPlaza").classList.remove("inactive");
else document.getElementById("burgPlaza").classList.add("inactive"); else byId("burgPlaza").classList.add("inactive");
if (b.temple) document.getElementById("burgTemple").classList.remove("inactive"); if (b.temple) byId("burgTemple").classList.remove("inactive");
else document.getElementById("burgTemple").classList.add("inactive"); else byId("burgTemple").classList.add("inactive");
if (b.shanty) document.getElementById("burgShanty").classList.remove("inactive"); if (b.shanty) byId("burgShanty").classList.remove("inactive");
else document.getElementById("burgShanty").classList.add("inactive"); else byId("burgShanty").classList.add("inactive");
//toggle lock //toggle lock
updateBurgLockIcon(); updateBurgLockIcon();
// select group // select group
const group = elSelected.node().parentNode.id; const group = elSelected.node().parentNode.id;
const select = document.getElementById("burgSelectGroup"); const select = byId("burgSelectGroup");
select.options.length = 0; // remove all options select.options.length = 0; // remove all options
burgLabels.selectAll("g").each(function () { burgLabels.selectAll("g").each(function () {
@ -109,68 +108,16 @@ function editBurg(id) {
// set emlem image // set emlem image
const coaID = "burgCOA" + id; const coaID = "burgCOA" + id;
COArenderer.trigger(coaID, b.coa); COArenderer.trigger(coaID, b.coa);
document.getElementById("burgEmblem").setAttribute("href", "#" + coaID); byId("burgEmblem").setAttribute("href", "#" + coaID);
if (options.showMFCGMap) { if (options.showBurgPreview) {
document.getElementById("mfcgPreviewSection").style.display = "block"; byId("burgPreviewSection").style.display = "block";
updateMFCGFrame(b); updateBurgPreview(b);
if (b.link) {
document.getElementById("mfcgBurgSeedSection").style.display = "none";
} else {
document.getElementById("mfcgBurgSeedSection").style.display = "inline-block";
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
}
} else { } else {
document.getElementById("mfcgPreviewSection").style.display = "none"; byId("burgPreviewSection").style.display = "none";
} }
} }
// in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
function getTemperatureLikeness(temperature) {
if (temperature < -5) return "Yakutsk";
const cities = [
"Snag (Yukon)",
"Yellowknife (Canada)",
"Okhotsk (Russia)",
"Fairbanks (Alaska)",
"Nuuk (Greenland)",
"Murmansk", // -5 - 0
"Arkhangelsk",
"Anchorage",
"Tromsø",
"Reykjavik",
"Riga",
"Stockholm",
"Halifax",
"Prague",
"Copenhagen",
"London", // 1 - 10
"Antwerp",
"Paris",
"Milan",
"Batumi",
"Rome",
"Dubrovnik",
"Lisbon",
"Barcelona",
"Marrakesh",
"Alexandria", // 11 - 20
"Tegucigalpa",
"Guangzhou",
"Rio de Janeiro",
"Dakar",
"Miami",
"Jakarta",
"Mogadishu",
"Bangkok",
"Aden",
"Khartoum"
]; // 21 - 30
if (temperature > 30) return "Mecca";
return cities[temperature + 5] || null;
}
function dragBurgLabel() { function dragBurgLabel() {
const tr = parseTransform(this.getAttribute("transform")); const tr = parseTransform(this.getAttribute("transform"));
const dx = +tr[0] - d3.event.x, const dx = +tr[0] - d3.event.x,
@ -186,15 +133,15 @@ function editBurg(id) {
function showGroupSection() { function showGroupSection() {
document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none")); document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none"));
document.getElementById("burgGroupSection").style.display = "inline-block"; byId("burgGroupSection").style.display = "inline-block";
} }
function hideGroupSection() { function hideGroupSection() {
document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "inline-block")); document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "inline-block"));
document.getElementById("burgGroupSection").style.display = "none"; byId("burgGroupSection").style.display = "none";
document.getElementById("burgInputGroup").style.display = "none"; byId("burgInputGroup").style.display = "none";
document.getElementById("burgInputGroup").value = ""; byId("burgInputGroup").value = "";
document.getElementById("burgSelectGroup").style.display = "inline-block"; byId("burgSelectGroup").style.display = "inline-block";
} }
function changeGroup() { function changeGroup() {
@ -223,7 +170,7 @@ function editBurg(id) {
.replace(/ /g, "_") .replace(/ /g, "_")
.replace(/[^\w\s]/gi, ""); .replace(/[^\w\s]/gi, "");
if (document.getElementById(group)) { if (byId(group)) {
tip("Element with this id already exists. Please provide a unique name", false, "error"); tip("Element with this id already exists. Please provide a unique name", false, "error");
return; return;
} }
@ -251,10 +198,10 @@ function editBurg(id) {
// just rename if only 1 element left // just rename if only 1 element left
const count = elSelected.node().parentNode.childElementCount; const count = elSelected.node().parentNode.childElementCount;
if (oldGroup !== "cities" && oldGroup !== "towns" && count === 1) { if (oldGroup !== "cities" && oldGroup !== "towns" && count === 1) {
document.getElementById("burgSelectGroup").selectedOptions[0].remove(); byId("burgSelectGroup").selectedOptions[0].remove();
document.getElementById("burgSelectGroup").options.add(new Option(group, group, false, true)); byId("burgSelectGroup").options.add(new Option(group, group, false, true));
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("burgInputGroup").value = ""; byId("burgInputGroup").value = "";
labelG.id = group; labelG.id = group;
iconG.id = group; iconG.id = group;
if (anchor) anchorG.id = group; if (anchor) anchorG.id = group;
@ -262,9 +209,9 @@ function editBurg(id) {
} }
// create new groups // create new groups
document.getElementById("burgSelectGroup").options.add(new Option(group, group, false, true)); byId("burgSelectGroup").options.add(new Option(group, group, false, true));
toggleNewGroupInput(); toggleNewGroupInput();
document.getElementById("burgInputGroup").value = ""; byId("burgInputGroup").value = "";
addBurgsGroup(group); addBurgsGroup(group);
moveBurgToGroup(id, group); moveBurgToGroup(id, group);
@ -284,7 +231,9 @@ function editBurg(id) {
alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${ alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
basic || capital ? "all unlocked elements in the burg group" : "the entire burg group" basic || capital ? "all unlocked elements in the burg group" : "the entire burg group"
}? }?
<br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${burgsToRemove.length}`; <br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${
burgsToRemove.length
}`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Remove burg group", title: "Remove burg group",
@ -343,7 +292,10 @@ function editBurg(id) {
function changePopulation() { function changePopulation() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
const burg = pack.burgs[id];
pack.burgs[id].population = rn(burgPopulation.value / populationRate / urbanization, 4); pack.burgs[id].population = rn(burgPopulation.value / populationRate / urbanization, 4);
updateBurgPreview(burg);
} }
function toggleFeature() { function toggleFeature() {
@ -357,9 +309,9 @@ function editBurg(id) {
if (burg[feature]) this.classList.remove("inactive"); if (burg[feature]) this.classList.remove("inactive");
else if (!burg[feature]) this.classList.add("inactive"); else if (!burg[feature]) this.classList.add("inactive");
if (burg.port) document.getElementById("burgEditAnchorStyle").style.display = "inline-block"; if (burg.port) byId("burgEditAnchorStyle").style.display = "inline-block";
else document.getElementById("burgEditAnchorStyle").style.display = "none"; else byId("burgEditAnchorStyle").style.display = "none";
updateMFCGFrame(burg); updateBurgPreview(burg);
} }
function toggleBurgLockButton() { function toggleBurgLockButton() {
@ -374,22 +326,22 @@ function editBurg(id) {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
const b = pack.burgs[id]; const b = pack.burgs[id];
if (b.lock) { if (b.lock) {
document.getElementById("burgLock").classList.remove("icon-lock-open"); byId("burgLock").classList.remove("icon-lock-open");
document.getElementById("burgLock").classList.add("icon-lock"); byId("burgLock").classList.add("icon-lock");
} else { } else {
document.getElementById("burgLock").classList.remove("icon-lock"); byId("burgLock").classList.remove("icon-lock");
document.getElementById("burgLock").classList.add("icon-lock-open"); byId("burgLock").classList.add("icon-lock-open");
} }
} }
function showStyleSection() { function showStyleSection() {
document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none")); document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none"));
document.getElementById("burgStyleSection").style.display = "inline-block"; byId("burgStyleSection").style.display = "inline-block";
} }
function hideStyleSection() { function hideStyleSection() {
document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "inline-block")); document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "inline-block"));
document.getElementById("burgStyleSection").style.display = "none"; byId("burgStyleSection").style.display = "none";
} }
function editGroupLabelStyle() { function editGroupLabelStyle() {
@ -407,38 +359,38 @@ function editBurg(id) {
editStyle("anchors", g); editStyle("anchors", g);
} }
function updateMFCGFrame(burg) { function updateBurgPreview(burg) {
const mfcgURL = getMFCGlink(burg); const src = getBurgLink(burg) + "&preview=1";
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL + "&preview=1");
document.getElementById("mfcgLink").setAttribute("href", mfcgURL); // recreate object to force reload (Chrome bug)
const container = byId("burgPreviewObject");
container.innerHTML = "";
const object = document.createElement("object");
object.style.width = "100%";
object.data = src;
container.insertBefore(object, null);
} }
function changeSeed() { function openBurgLink() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
const burg = pack.burgs[id]; const burg = pack.burgs[id];
const burgSeed = +this.value;
burg.MFCG = burgSeed; openURL(getBurgLink(burg));
updateMFCGFrame(burg);
} }
function randomizeSeed() { function changeBurgLink() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
const burg = pack.burgs[id]; const burg = pack.burgs[id];
const burgSeed = rand(1e9 - 1);
burg.MFCG = burgSeed;
updateMFCGFrame(burg);
document.getElementById("mfcgBurgSeed").value = burgSeed;
}
function addCustomMfcgLink() { prompt(
const id = +elSelected.attr("data-id"); "Provide custom link to the burg map. It can be a link to Medieval Fantasy City Generator, a different tool, or just an image. Leave empty to use the default map",
const burg = pack.burgs[id]; {default: getBurgLink(burg), required: false},
const message = "Enter custom link to the burg map. It can be a link to Medieval Fantasy City Generator or other tool. Keep empty to use MFCG seed"; link => {
prompt(message, {default: burg.link || "", required: false}, link => { if (link) burg.link = link;
if (link) burg.link = link; else delete burg.link;
else delete burg.link; updateBurgPreview(burg);
updateMFCGFrame(burg); }
}); );
} }
function openEmblemEdit() { function openEmblemEdit() {
@ -447,16 +399,16 @@ function editBurg(id) {
editEmblem("burg", "burgCOA" + id, burg); editEmblem("burg", "burgCOA" + id, burg);
} }
function toggleMFCGMap() { function toggleBurgPreview() {
options.showMFCGMap = !options.showMFCGMap; options.showBurgPreview = !options.showBurgPreview;
document.getElementById("mfcgPreviewSection").style.display = options.showMFCGMap ? "block" : "none"; byId("burgPreviewSection").style.display = options.showBurgPreview ? "block" : "none";
document.getElementById("burgToggleMFCGMap").className = options.showMFCGMap ? "icon-map" : "icon-map-o"; byId("burgTogglePreview").className = options.showBurgPreview ? "icon-map" : "icon-map-o";
} }
function toggleRelocateBurg() { function toggleRelocateBurg() {
const toggler = document.getElementById("toggleCells"); const toggler = byId("toggleCells");
document.getElementById("burgRelocate").classList.toggle("pressed"); byId("burgRelocate").classList.toggle("pressed");
if (document.getElementById("burgRelocate").classList.contains("pressed")) { if (byId("burgRelocate").classList.contains("pressed")) {
viewbox.style("cursor", "crosshair").on("click", relocateBurgOnClick); viewbox.style("cursor", "crosshair").on("click", relocateBurgOnClick);
tip("Click on map to relocate burg. Hold Shift for continuous move", true); tip("Click on map to relocate burg. Hold Shift for continuous move", true);
if (!layerIsOn("toggleCells")) { if (!layerIsOn("toggleCells")) {
@ -576,8 +528,53 @@ function editBurg(id) {
} }
function closeBurgEditor() { function closeBurgEditor() {
document.getElementById("burgRelocate").classList.remove("pressed"); byId("burgRelocate").classList.remove("pressed");
burgLabels.selectAll("text").call(d3.drag().on("drag", null)).classed("draggable", false); burgLabels.selectAll("text").call(d3.drag().on("drag", null)).classed("draggable", false);
unselect(); unselect();
} }
} }
// in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
function getTemperatureLikeness(temperature) {
if (temperature < -5) return "Yakutsk";
const cities = [
"Snag (Yukon)",
"Yellowknife (Canada)",
"Okhotsk (Russia)",
"Fairbanks (Alaska)",
"Nuuk (Greenland)",
"Murmansk", // -5 - 0
"Arkhangelsk",
"Anchorage",
"Tromsø",
"Reykjavik",
"Riga",
"Stockholm",
"Halifax",
"Prague",
"Copenhagen",
"London", // 1 - 10
"Antwerp",
"Paris",
"Milan",
"Batumi",
"Rome",
"Dubrovnik",
"Lisbon",
"Barcelona",
"Marrakesh",
"Alexandria", // 11 - 20
"Tegucigalpa",
"Guangzhou",
"Rio de Janeiro",
"Dakar",
"Miami",
"Jakarta",
"Mogadishu",
"Bangkok",
"Aden",
"Khartoum"
]; // 21 - 30
if (temperature > 30) return "Mecca";
return cities[temperature + 5] || null;
}

View file

@ -1,5 +1,5 @@
"use strict"; "use strict";
function overviewBurgs(options = {stateId: null, cultureId: null}) { function overviewBurgs(settings = {stateId: null, cultureId: null}) {
if (customization) return; if (customization) return;
closeDialogs("#burgsOverview, .stable"); closeDialogs("#burgsOverview, .stable");
if (!layerIsOn("toggleIcons")) toggleIcons(); if (!layerIsOn("toggleIcons")) toggleIcons();
@ -45,7 +45,7 @@ function overviewBurgs(options = {stateId: null, cultureId: null}) {
function updateFilter() { function updateFilter() {
const stateFilter = byId("burgsFilterState"); const stateFilter = byId("burgsFilterState");
const selectedState = options.stateId !== null ? options.stateId : stateFilter.value || -1; const selectedState = settings.stateId !== null ? settings.stateId : stateFilter.value || -1;
stateFilter.options.length = 0; // remove all options stateFilter.options.length = 0; // remove all options
stateFilter.options.add(new Option("all", -1, false, selectedState === -1)); stateFilter.options.add(new Option("all", -1, false, selectedState === -1));
stateFilter.options.add(new Option(pack.states[0].name, 0, false, selectedState === 0)); stateFilter.options.add(new Option(pack.states[0].name, 0, false, selectedState === 0));
@ -53,7 +53,7 @@ function overviewBurgs(options = {stateId: null, cultureId: null}) {
statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState))); statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState)));
const cultureFilter = byId("burgsFilterCulture"); const cultureFilter = byId("burgsFilterCulture");
const selectedCulture = options.cultureId !== null ? options.cultureId : cultureFilter.value || -1; const selectedCulture = settings.cultureId !== null ? settings.cultureId : cultureFilter.value || -1;
cultureFilter.options.length = 0; // remove all options cultureFilter.options.length = 0; // remove all options
cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture === -1)); cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture === -1));
cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, selectedCulture === 0)); cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, selectedCulture === 0));
@ -480,10 +480,7 @@ function overviewBurgs(options = {stateId: null, cultureId: null}) {
} }
function downloadBurgsData() { function downloadBurgsData() {
let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,X,Y,Latitude,Longitude,Elevation (${heightUnit.value}),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town`; // headers let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,X,Y,Latitude,Longitude,Elevation (${heightUnit.value}),Temperature,Temperature likeness,Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town,Emblem,City Generator Link\n`; // headers
if (options.showMFCGMap) data += `,City Generator Link`;
data += "\n";
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
valid.forEach(b => { valid.forEach(b => {
@ -504,6 +501,9 @@ function overviewBurgs(options = {stateId: null, cultureId: null}) {
data += getLatitude(b.y, 2) + ","; data += getLatitude(b.y, 2) + ",";
data += getLongitude(b.x, 2) + ","; data += getLongitude(b.x, 2) + ",";
data += parseInt(getHeight(pack.cells.h[b.cell])) + ","; data += parseInt(getHeight(pack.cells.h[b.cell])) + ",";
const temperature = grid.cells.temp[pack.cells.g[b.cell]];
data += convertTemperature(temperature) + ",";
data += getTemperatureLikeness(temperature) + ",";
// add status data // add status data
data += b.capital ? "capital," : ","; data += b.capital ? "capital," : ",";
@ -513,7 +513,9 @@ function overviewBurgs(options = {stateId: null, cultureId: null}) {
data += b.plaza ? "plaza," : ","; data += b.plaza ? "plaza," : ",";
data += b.temple ? "temple," : ","; data += b.temple ? "temple," : ",";
data += b.shanty ? "shanty town," : ","; data += b.shanty ? "shanty town," : ",";
if (options.showMFCGMap) data += getMFCGlink(b); data += b.coa ? JSON.stringify(b.coa).replace(/"/g, "").replace(/,/g, ";") + "," : ",";
data += getBurgLink(b);
data += "\n"; data += "\n";
}); });

View file

@ -243,25 +243,22 @@ function removeBurg(id) {
} }
} }
function toggleCapital(burg) { function toggleCapital(burgId) {
const state = pack.burgs[burg].state; const {burgs, states} = pack;
if (!state) { if (burgs[burgId].capital)
tip("Neutral lands cannot have a capital", false, "error"); return tip("To change capital please assign a capital status to another burg of this state", false, "error");
return;
}
if (pack.burgs[burg].capital) {
tip("To change capital please assign a capital status to another burg of this state", false, "error");
return;
}
const old = pack.states[state].capital;
// change statuses const stateId = burgs[burgId].state;
pack.states[state].capital = burg; if (!stateId) return tip("Neutral lands cannot have a capital", false, "error");
pack.states[state].center = pack.burgs[burg].cell;
pack.burgs[burg].capital = 1; const prevCapitalId = states[stateId].capital;
pack.burgs[old].capital = 0; states[stateId].capital = burgId;
moveBurgToGroup(burg, "cities"); states[stateId].center = burgs[burgId].cell;
moveBurgToGroup(old, "towns"); burgs[burgId].capital = 1;
burgs[prevCapitalId].capital = 0;
moveBurgToGroup(burgId, "cities");
moveBurgToGroup(prevCapitalId, "towns");
} }
function togglePort(burg) { function togglePort(burg) {
@ -291,16 +288,20 @@ function togglePort(burg) {
.attr("height", size); .attr("height", size);
} }
function getBurgSeed(burg) { function getBurgLink(burg) {
return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`);
}
function getMFCGlink(burg) {
if (burg.link) return burg.link; if (burg.link) return burg.link;
const population = burg.population * populationRate * urbanization;
if (population >= options.villageMaxPopulation || burg.citadel || burg.walls || burg.temple || burg.shanty)
return createMfcgLink(burg);
return createVillageGeneratorLink(burg);
}
function createMfcgLink(burg) {
const {cells} = pack; const {cells} = pack;
const {i, name, population: burgPopulation, cell} = burg; const {i, name, population: burgPopulation, cell} = burg;
const seed = getBurgSeed(burg); const burgSeed = burg.MFCG || seed + String(burg.i).padStart(4, 0);
const sizeRaw = 2.13 * Math.pow((burgPopulation * populationRate) / urbanDensity, 0.385); const sizeRaw = 2.13 * Math.pow((burgPopulation * populationRate) / urbanDensity, 0.385);
const size = minmax(Math.ceil(sizeRaw), 6, 100); const size = minmax(Math.ceil(sizeRaw), 6, 100);
@ -308,11 +309,19 @@ function getMFCGlink(burg) {
const river = cells.r[cell] ? 1 : 0; const river = cells.r[cell] ? 1 : 0;
const coast = Number(burg.port > 0); const coast = Number(burg.port > 0);
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : null; const sea = (() => {
if (!coast || !cells.haven[cell]) return null;
// calculate see direction: 0 = south, 0.5 = west, 1 = north, 1.5 = east
const p1 = cells.p[cell];
const p2 = cells.p[cells.haven[cell]];
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
if (deg < 0) deg += 360;
return rn(normalize(deg, 0, 360) * 2, 2);
})();
const biome = cells.biome[cell];
const arableBiomes = river ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8]; const arableBiomes = river ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8];
const farms = +arableBiomes.includes(biome); const farms = +arableBiomes.includes(cells.biome[cell]);
const citadel = +burg.citadel; const citadel = +burg.citadel;
const urban_castle = +(citadel && each(2)(i)); const urban_castle = +(citadel && each(2)(i));
@ -324,19 +333,12 @@ function getMFCGlink(burg) {
const temple = +burg.temple; const temple = +burg.temple;
const shantytown = +burg.shanty; const shantytown = +burg.shanty;
function getSeaDirections(i) { const url = new URL("https://watabou.github.io/city-generator/");
const p1 = cells.p[i]; url.search = new URLSearchParams({
const p2 = cells.p[cells.haven[i]];
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
if (deg < 0) deg += 360;
return rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
}
const parameters = {
name, name,
population, population,
size, size,
seed, seed: burgSeed,
river, river,
coast, coast,
farms, farms,
@ -348,14 +350,60 @@ function getMFCGlink(burg) {
walls, walls,
shantytown, shantytown,
gates: -1 gates: -1
}; });
const url = new URL("https://watabou.github.io/city-generator/");
url.search = new URLSearchParams(parameters);
if (sea) url.searchParams.append("sea", sea); if (sea) url.searchParams.append("sea", sea);
return url.toString(); return url.toString();
} }
function createVillageGeneratorLink(burg) {
const {cells, features} = pack;
const {i, population, cell} = burg;
const pop = rn(population * populationRate * urbanization);
const burgSeed = seed + String(i).padStart(4, 0);
const tags = [];
if (cells.r[cell] && cells.haven[cell]) tags.push("estuary");
else if (cells.haven[cell] && features[cells.f[cell]].cells === 1) tags.push("island,district");
else if (burg.port) tags.push("coast");
else if (cells.conf[cell]) tags.push("confluence");
else if (cells.r[cell]) tags.push("river");
else if (pop < 200 && each(4)(cell)) tags.push("pond");
const roadsAround = cells.c[cell].filter(c => cells.h[c] >= 20 && cells.road[c]).length;
if (roadsAround > 1) tags.push("highway");
else if (roadsAround === 1) tags.push("dead end");
else tags.push("isolated");
const biome = cells.biome[cell];
const arableBiomes = cells.r[cell] ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8];
if (!arableBiomes.includes(biome)) tags.push("uncultivated");
else if (each(6)(cell)) tags.push("farmland");
const temp = grid.cells.temp[cells.g[cell]];
if (temp <= 0 || temp > 28 || (temp > 25 && each(3)(cell))) tags.push("no orchards");
if (!burg.plaza) tags.push("no square");
if (pop < 100) tags.push("sparse");
else if (pop > 300) tags.push("dense");
const width = (() => {
if (pop > 1500) return 1600;
if (pop > 1000) return 1400;
if (pop > 500) return 1000;
if (pop > 200) return 800;
if (pop > 100) return 600;
return 400;
})();
const height = rn(width / 2.2);
const url = new URL("https://watabou.github.io/village-generator/");
url.search = new URLSearchParams({pop, name: "", seed: burgSeed, width, height, tags});
return url.toString();
}
// draw legend box // draw legend box
function drawLegend(name, data) { function drawLegend(name, data) {
legend.selectAll("*").remove(); // fully redraw every time legend.selectAll("*").remove(); // fully redraw every time
@ -1096,12 +1144,12 @@ function selectIcon(initial, callback) {
input.oninput = e => callback(input.value); input.oninput = e => callback(input.value);
table.onclick = e => { table.onclick = e => {
if (e.target.tagName === "TD") { if (e.target.tagName === "TD") {
input.value = e.target.innerHTML; input.value = e.target.textContent;
callback(input.value); callback(input.value);
} }
}; };
table.onmouseover = e => { table.onmouseover = e => {
if (e.target.tagName === "TD") tip(`Click to select ${e.target.innerHTML} icon`); if (e.target.tagName === "TD") tip(`Click to select ${e.target.textContent} icon`);
}; };
$("#iconSelector").dialog({ $("#iconSelector").dialog({
@ -1176,18 +1224,18 @@ function refreshAllEditors() {
// dynamically loaded editors // dynamically loaded editors
async function editStates() { async function editStates() {
if (customization) return; if (customization) return;
const Editor = await import("../dynamic/editors/states-editor.js?v=1.93.10"); const Editor = await import("../dynamic/editors/states-editor.js?v=1.96.06");
Editor.open(); Editor.open();
} }
async function editCultures() { async function editCultures() {
if (customization) return; if (customization) return;
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.95.04"); const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.96.01");
Editor.open(); Editor.open();
} }
async function editReligions() { async function editReligions() {
if (customization) return; if (customization) return;
const Editor = await import("../dynamic/editors/religions-editor.js?v=1.89.10"); const Editor = await import("../dynamic/editors/religions-editor.js?v=1.96.00");
Editor.open(); Editor.open();
} }

View file

@ -193,8 +193,15 @@ function showElevationProfile(data, routeLen, isRiver) {
.attr("d", "M0,0 V4 L2,2 Z") .attr("d", "M0,0 V4 L2,2 Z")
.attr("fill", "darkgray"); .attr("fill", "darkgray");
let colors = getColorScheme(terrs.attr("scheme")); const colors = getColorScheme("natural");
const landdef = chart.select("defs").append("linearGradient").attr("id", "landdef").attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%"); const landdef = chart
.select("defs")
.append("linearGradient")
.attr("id", "landdef")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "0%")
.attr("y2", "100%");
if (chartData.mah == chartData.mih) { if (chartData.mah == chartData.mih) {
landdef landdef
@ -247,7 +254,14 @@ function showElevationProfile(data, routeLen, isRiver) {
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset); path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
path += " L" + parseInt(xscale(0) + +xOffset) + "," + parseInt(yscale(0) + +yOffset); path += " L" + parseInt(xscale(0) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
path += "Z"; path += "Z";
chart.append("g").attr("id", "epland").append("path").attr("d", path).attr("stroke", "purple").attr("stroke-width", "0").attr("fill", "url(#landdef)"); chart
.append("g")
.attr("id", "epland")
.append("path")
.attr("d", path)
.attr("stroke", "purple")
.attr("stroke-width", "0")
.attr("fill", "url(#landdef)");
// biome / heights // biome / heights
let g = chart.append("g").attr("id", "epbiomes"); let g = chart.append("g").attr("id", "epbiomes");
@ -289,7 +303,14 @@ function showElevationProfile(data, routeLen, isRiver) {
chartData.cell[k] + chartData.cell[k] +
")"; ")";
g.append("rect").attr("stroke", c).attr("fill", c).attr("x", x).attr("y", y).attr("width", xscale(1)).attr("height", 15).attr("data-tip", dataTip); g.append("rect")
.attr("stroke", c)
.attr("fill", c)
.attr("x", x)
.attr("y", y)
.attr("width", xscale(1))
.attr("height", 15)
.attr("data-tip", dataTip);
} }
const xAxis = d3 const xAxis = d3
@ -371,7 +392,17 @@ function showElevationProfile(data, routeLen, isRiver) {
// arrow from burg name to graph line // arrow from burg name to graph line
g.append("path") g.append("path")
.attr("id", "eparrow" + b) .attr("id", "eparrow" + b)
.attr("d", "M" + x1.toString() + "," + (y1 + 3).toString() + "L" + x1.toString() + "," + parseInt(chartData.points[k][1] - 3).toString()) .attr(
"d",
"M" +
x1.toString() +
"," +
(y1 + 3).toString() +
"L" +
x1.toString() +
"," +
parseInt(chartData.points[k][1] - 3).toString()
)
.attr("stroke", "darkgray") .attr("stroke", "darkgray")
.attr("fill", "lightgray") .attr("fill", "lightgray")
.attr("stroke-width", "1") .attr("stroke-width", "1")

View file

@ -188,92 +188,135 @@ function restoreLayers() {
} }
function toggleHeight(event) { function toggleHeight(event) {
if (customization === 1) { if (customization === 1) return tip("You cannot turn off the layer when heightmap is in edit mode", false, "error");
tip("You cannot turn off the layer when heightmap is in edit mode", false, "error");
return;
}
if (!terrs.selectAll("*").size()) { const children = terrs.selectAll("#oceanHeights > *, #landHeights > *");
if (!children.size()) {
turnButtonOn("toggleHeight"); turnButtonOn("toggleHeight");
drawHeightmap(); drawHeightmap();
if (event && isCtrlClick(event)) editStyle("terrs"); if (event && isCtrlClick(event)) editStyle("terrs");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("terrs");
editStyle("terrs");
return;
}
turnButtonOff("toggleHeight"); turnButtonOff("toggleHeight");
terrs.selectAll("*").remove(); children.remove();
} }
} }
function drawHeightmap() { function drawHeightmap() {
TIME && console.time("drawHeightmap"); TIME && console.time("drawHeightmap");
terrs.selectAll("*").remove();
const {cells, vertices} = pack; const ocean = terrs.select("#oceanHeights");
const n = cells.i.length; const land = terrs.select("#landHeights");
const used = new Uint8Array(cells.i.length);
const paths = new Array(101).fill("");
const scheme = getColorScheme(terrs.attr("scheme")); ocean.selectAll("*").remove();
const terracing = terrs.attr("terracing") / 10; // add additional shifted darker layer for pseudo-3d effect land.selectAll("*").remove();
const skip = +terrs.attr("skip") + 1;
const simplification = +terrs.attr("relax");
switch (+terrs.attr("curve")) { const paths = new Array(101);
case 0:
lineGen.curve(d3.curveBasisClosed); // ocean cells
break; const renderOceanCells = Boolean(+ocean.attr("data-render"));
case 1: if (renderOceanCells) {
lineGen.curve(d3.curveLinear); const {cells, vertices} = grid;
break; const used = new Uint8Array(cells.i.length);
case 2:
lineGen.curve(d3.curveStep); const skip = +ocean.attr("skip") + 1 || 1;
break; const relax = +ocean.attr("relax") || 0;
default: lineGen.curve(d3[ocean.attr("curve") || "curveBasisClosed"]);
lineGen.curve(d3.curveBasisClosed);
let currentLayer = 0;
const heights = Array.from(cells.i).sort((a, b) => cells.h[a] - cells.h[b]);
for (const i of heights) {
const h = cells.h[i];
if (h > currentLayer) currentLayer += skip;
if (h < currentLayer) continue;
if (currentLayer >= 20) break;
if (used[i]) continue; // already marked
const onborder = cells.c[i].some(n => cells.h[n] < h);
if (!onborder) continue;
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h));
const chain = connectVertices(cells, vertices, vertex, h, used);
if (chain.length < 3) continue;
const points = simplifyLine(chain, relax).map(v => vertices.p[v]);
if (!paths[h]) paths[h] = "";
paths[h] += round(lineGen(points));
}
} }
let currentLayer = 20; // land cells
const heights = cells.i.sort((a, b) => cells.h[a] - cells.h[b]); {
for (const i of heights) { const {cells, vertices} = pack;
const h = cells.h[i]; const used = new Uint8Array(cells.i.length);
if (h > currentLayer) currentLayer += skip;
if (currentLayer > 100) break; // no layers possible with height > 100 const skip = +land.attr("skip") + 1 || 1;
if (h < currentLayer) continue; const relax = +land.attr("relax") || 0;
if (used[i]) continue; // already marked lineGen.curve(d3[land.attr("curve") || "curveBasisClosed"]);
const onborder = cells.c[i].some(n => cells.h[n] < h);
if (!onborder) continue; let currentLayer = 20;
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h)); const heights = Array.from(cells.i).sort((a, b) => cells.h[a] - cells.h[b]);
const chain = connectVertices(vertex, h); for (const i of heights) {
if (chain.length < 3) continue; const h = cells.h[i];
const points = simplifyLine(chain).map(v => vertices.p[v]); if (h > currentLayer) currentLayer += skip;
paths[h] += round(lineGen(points)); if (h < currentLayer) continue;
if (currentLayer > 100) break; // no layers possible with height > 100
if (used[i]) continue; // already marked
const onborder = cells.c[i].some(n => cells.h[n] < h);
if (!onborder) continue;
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h));
const chain = connectVertices(cells, vertices, vertex, h, used);
if (chain.length < 3) continue;
const points = simplifyLine(chain, relax).map(v => vertices.p[v]);
if (!paths[h]) paths[h] = "";
paths[h] += round(lineGen(points));
}
} }
terrs // render paths
.append("rect") for (const height of d3.range(0, 101)) {
.attr("x", 0) const group = height < 20 ? ocean : land;
.attr("y", 0) const scheme = getColorScheme(group.attr("scheme"));
.attr("width", graphWidth)
.attr("height", graphHeight) if (height === 0 && renderOceanCells) {
.attr("fill", scheme(0.8)); // draw base layer // draw base ocean layer
for (const i of d3.range(20, 101)) { group
if (paths[i].length < 10) continue; .append("rect")
const color = getColor(i, scheme); .attr("x", 0)
if (terracing) .attr("y", 0)
terrs .attr("width", graphWidth)
.append("path") .attr("height", graphHeight)
.attr("d", paths[i]) .attr("fill", scheme(1));
.attr("transform", "translate(.7,1.4)") }
.attr("fill", d3.color(color).darker(terracing))
.attr("data-height", i); if (height === 20) {
terrs.append("path").attr("d", paths[i]).attr("fill", color).attr("data-height", i); // draw base land layer
group
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", graphWidth)
.attr("height", graphHeight)
.attr("fill", scheme(0.8));
}
if (paths[height] && paths[height].length >= 10) {
const terracing = group.attr("terracing") / 10 || 0;
const color = getColor(height, scheme);
if (terracing) {
group
.append("path")
.attr("d", paths[height])
.attr("transform", "translate(.7,1.4)")
.attr("fill", d3.color(color).darker(terracing))
.attr("data-height", height);
}
group.append("path").attr("d", paths[height]).attr("fill", color).attr("data-height", height);
}
} }
// connect vertices to chain // connect vertices to chain
function connectVertices(start, h) { function connectVertices(cells, vertices, start, h, used) {
const n = cells.i.length;
const chain = []; // vertices chain to form a path const chain = []; // vertices chain to form a path
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
const prev = chain[chain.length - 1]; // previous vertex in chain const prev = chain[chain.length - 1]; // previous vertex in chain
@ -295,7 +338,7 @@ function drawHeightmap() {
return chain; return chain;
} }
function simplifyLine(chain) { function simplifyLine(chain, simplification) {
if (!simplification) return chain; if (!simplification) return chain;
const n = simplification + 1; // filter each nth element const n = simplification + 1; // filter each nth element
return chain.filter((d, i) => i % n === 0); return chain.filter((d, i) => i % n === 0);
@ -1670,10 +1713,7 @@ function toggleLabels(event) {
invokeActiveZooming(); invokeActiveZooming();
if (event && isCtrlClick(event)) editStyle("labels"); if (event && isCtrlClick(event)) editStyle("labels");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("labels");
editStyle("labels");
return;
}
turnButtonOff("toggleLabels"); turnButtonOff("toggleLabels");
labels.style("display", "none"); labels.style("display", "none");
} }
@ -1685,10 +1725,7 @@ function toggleIcons(event) {
$("#icons").fadeIn(); $("#icons").fadeIn();
if (event && isCtrlClick(event)) editStyle("burgIcons"); if (event && isCtrlClick(event)) editStyle("burgIcons");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("burgIcons");
editStyle("burgIcons");
return;
}
turnButtonOff("toggleIcons"); turnButtonOff("toggleIcons");
$("#icons").fadeOut(); $("#icons").fadeOut();
} }
@ -1701,10 +1738,7 @@ function toggleRulers(event) {
rulers.draw(); rulers.draw();
ruler.style("display", null); ruler.style("display", null);
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("ruler");
editStyle("ruler");
return;
}
turnButtonOff("toggleRulers"); turnButtonOff("toggleRulers");
ruler.selectAll("*").remove(); ruler.selectAll("*").remove();
ruler.style("display", "none"); ruler.style("display", "none");
@ -1715,17 +1749,113 @@ function toggleScaleBar(event) {
if (!layerIsOn("toggleScaleBar")) { if (!layerIsOn("toggleScaleBar")) {
turnButtonOn("toggleScaleBar"); turnButtonOn("toggleScaleBar");
$("#scaleBar").fadeIn(); $("#scaleBar").fadeIn();
if (event && isCtrlClick(event)) editUnits(); if (event && isCtrlClick(event)) editStyle("scaleBar");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("scaleBar");
editUnits();
return;
}
$("#scaleBar").fadeOut(); $("#scaleBar").fadeOut();
turnButtonOff("toggleScaleBar"); turnButtonOff("toggleScaleBar");
} }
} }
function drawScaleBar(scaleBar, scaleLevel) {
if (!scaleBar.size() || scaleBar.style("display") === "none") return;
const distanceScale = +distanceScaleInput.value;
const unit = distanceUnitInput.value;
const size = +scaleBar.attr("data-bar-size");
const length = (function () {
const init = 100;
let val = (init * size * distanceScale) / scaleLevel; // bar length in distance unit
if (val > 900) val = rn(val, -3); // round to 1000
else if (val > 90) val = rn(val, -2); // round to 100
else if (val > 9) val = rn(val, -1); // round to 10
else val = rn(val); // round to 1
const length = (val * scaleLevel) / distanceScale; // actual length in pixels on this scale
return length;
})();
scaleBar.select("#scaleBarContent").remove(); // redraw content every time
const content = scaleBar.append("g").attr("id", "scaleBarContent");
const lines = content.append("g");
lines
.append("line")
.attr("x1", 0.5)
.attr("y1", 0)
.attr("x2", length + size - 0.5)
.attr("y2", 0)
.attr("stroke-width", size)
.attr("stroke", "white");
lines
.append("line")
.attr("x1", 0)
.attr("y1", size)
.attr("x2", length + size)
.attr("y2", size)
.attr("stroke-width", size)
.attr("stroke", "#3d3d3d");
lines
.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", length + size)
.attr("y2", 0)
.attr("stroke-width", rn(size * 3, 2))
.attr("stroke-dasharray", size + " " + rn(length / 5 - size, 2))
.attr("stroke", "#3d3d3d");
const texts = content.append("g").attr("text-anchor", "middle").attr("font-family", "var(--serif)");
texts
.selectAll("text")
.data(d3.range(0, 6))
.enter()
.append("text")
.attr("x", d => rn((d * length) / 5, 2))
.attr("y", 0)
.attr("dy", "-.6em")
.text(d => rn((((d * length) / 5) * distanceScale) / scaleLevel) + (d < 5 ? "" : " " + unit));
const label = scaleBar.attr("data-label");
if (label) {
texts
.append("text")
.attr("x", (length + 1) / 2)
.attr("dy", ".6em")
.attr("dominant-baseline", "text-before-edge")
.text(label);
}
const scaleBarBack = scaleBar.select("#scaleBarBack");
if (scaleBarBack.size()) {
const bbox = content.node().getBBox();
const paddingTop = +scaleBarBack.attr("data-top") || 0;
const paddingLeft = +scaleBarBack.attr("data-left") || 0;
const paddingRight = +scaleBarBack.attr("data-right") || 0;
const paddingBottom = +scaleBarBack.attr("data-bottom") || 0;
scaleBar
.select("#scaleBarBack")
.attr("x", -paddingLeft)
.attr("y", -paddingTop)
.attr("width", bbox.width + paddingRight)
.attr("height", bbox.height + paddingBottom);
}
}
// fit ScaleBar to screen size
function fitScaleBar(scaleBar, fullWidth, fullHeight) {
if (!scaleBar.select("rect").size() || scaleBar.style("display") === "none") return;
const posX = +scaleBar.attr("data-x") || 99;
const posY = +scaleBar.attr("data-y") || 99;
const bbox = scaleBar.select("rect").node().getBBox();
const x = rn((fullWidth * posX) / 100 - bbox.width + 10);
const y = rn((fullHeight * posY) / 100 - bbox.height + 20);
scaleBar.attr("transform", `translate(${x},${y})`);
}
function toggleZones(event) { function toggleZones(event) {
if (!layerIsOn("toggleZones")) { if (!layerIsOn("toggleZones")) {
turnButtonOn("toggleZones"); turnButtonOn("toggleZones");
@ -1748,10 +1878,7 @@ function toggleEmblems(event) {
$("#emblems").fadeIn(); $("#emblems").fadeIn();
if (event && isCtrlClick(event)) editStyle("emblems"); if (event && isCtrlClick(event)) editStyle("emblems");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("emblems");
editStyle("emblems");
return;
}
$("#emblems").fadeOut(); $("#emblems").fadeOut();
turnButtonOff("toggleEmblems"); turnButtonOff("toggleEmblems");
} }

View file

@ -532,101 +532,3 @@ class Planimeter extends Measurer {
this.el.select("text").attr("x", c[0]).attr("y", c[1]).text(area); this.el.select("text").attr("x", c[0]).attr("y", c[1]).text(area);
} }
} }
// Scale bar
function drawScaleBar(scaleBar, scaleLevel) {
if (!scaleBar.size() || scaleBar.style("display") === "none") return;
scaleBar.selectAll("*").remove(); // fully redraw every time
const distanceScale = +distanceScaleInput.value;
const unit = distanceUnitInput.value;
const size = +barSizeInput.value;
// calculate size
const init = 100;
let val = (init * size * distanceScale) / scaleLevel; // bar length in distance unit
if (val > 900) val = rn(val, -3);
// round to 1000
else if (val > 90) val = rn(val, -2);
// round to 100
else if (val > 9) val = rn(val, -1);
// round to 10
else val = rn(val); // round to 1
const length = (val * scaleLevel) / distanceScale; // actual length in pixels on this scale
scaleBar
.append("line")
.attr("x1", 0.5)
.attr("y1", 0)
.attr("x2", length + size - 0.5)
.attr("y2", 0)
.attr("stroke-width", size)
.attr("stroke", "white");
scaleBar
.append("line")
.attr("x1", 0)
.attr("y1", size)
.attr("x2", length + size)
.attr("y2", size)
.attr("stroke-width", size)
.attr("stroke", "#3d3d3d");
const dash = size + " " + rn(length / 5 - size, 2);
scaleBar
.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", length + size)
.attr("y2", 0)
.attr("stroke-width", rn(size * 3, 2))
.attr("stroke-dasharray", dash)
.attr("stroke", "#3d3d3d");
const fontSize = rn(5 * size, 1);
scaleBar
.selectAll("text")
.data(d3.range(0, 6))
.enter()
.append("text")
.attr("x", d => rn((d * length) / 5, 2))
.attr("y", 0)
.attr("dy", "-.6em")
.attr("font-size", fontSize)
.text(d => rn((((d * length) / 5) * distanceScale) / scaleLevel) + (d < 5 ? "" : " " + unit));
if (barLabel.value !== "") {
scaleBar
.append("text")
.attr("x", (length + 1) / 2)
.attr("y", 2 * size)
.attr("dominant-baseline", "text-before-edge")
.attr("font-size", fontSize)
.text(barLabel.value);
}
const bbox = scaleBar.node().getBBox();
// append backbround rectangle
scaleBar
.insert("rect", ":first-child")
.attr("x", -10)
.attr("y", -20)
.attr("width", bbox.width + 10)
.attr("height", bbox.height + 15)
.attr("stroke-width", size)
.attr("stroke", "none")
.attr("filter", "url(#blur5)")
.attr("fill", barBackColor.value)
.attr("opacity", +barBackOpacity.value);
}
// fit ScaleBar to screen size
function fitScaleBar(scaleBar, fullWidth, fullHeight) {
if (!scaleBar.select("rect").size() || scaleBar.style("display") === "none") return;
const px = isNaN(+barPosX.value) ? 0.99 : barPosX.value / 100;
const py = isNaN(+barPosY.value) ? 0.99 : barPosY.value / 100;
const bbox = scaleBar.select("rect").node().getBBox();
const x = rn(fullWidth * px - bbox.width + 10);
const y = rn(fullHeight * py - bbox.height + 20);
scaleBar.attr("transform", `translate(${x},${y})`);
}

View file

@ -54,7 +54,9 @@ function overviewMilitary() {
const insert = html => document.getElementById("militaryTotal").insertAdjacentHTML("beforebegin", html); const insert = html => document.getElementById("militaryTotal").insertAdjacentHTML("beforebegin", html);
for (const u of options.military) { for (const u of options.military) {
const label = capitalize(u.name.replace(/_/g, " ")); const label = capitalize(u.name.replace(/_/g, " "));
insert(`<div data-tip="State ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label}&nbsp;</div>`); insert(
`<div data-tip="State ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label}&nbsp;</div>`
);
} }
header.querySelectorAll(".removable").forEach(function (e) { header.querySelectorAll(".removable").forEach(function (e) {
e.addEventListener("click", function () { e.addEventListener("click", function () {
@ -76,7 +78,9 @@ function overviewMilitary() {
const rate = (total / population) * 100; const rate = (total / population) * 100;
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" "); const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" "); const lineData = options.military
.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`)
.join(" ");
lines += /* html */ `<div lines += /* html */ `<div
class="states" class="states"
@ -91,9 +95,14 @@ function overviewMilitary() {
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box> <fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly /> <input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly />
${lineData} ${lineData}
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div> <div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(
total
)}</div>
<div data-type="population" data-tip="State population">${si(population)}</div> <div data-type="population" data-tip="State population">${si(population)}</div>
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div> <div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(
rate,
2
)}%</div>
<input <input
data-tip="War Alert. Editable modifier to military forces number, depends of political situation" data-tip="War Alert. Editable modifier to military forces number, depends of political situation"
style="width:4.1em" style="width:4.1em"
@ -131,7 +140,9 @@ function overviewMilitary() {
}); });
const getForces = u => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0); const getForces = u => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0);
options.military.forEach(u => (line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u))); options.military.forEach(
u => (line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u))
);
const population = rn((s.rural + s.urban * urbanization) * populationRate); const population = rn((s.rural + s.urban * urbanization) * populationRate);
const total = (line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0)); const total = (line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0));
@ -237,7 +248,16 @@ function overviewMilitary() {
position: {my: "center", at: "center", of: "svg"}, position: {my: "center", at: "center", of: "svg"},
buttons: { buttons: {
Apply: applyMilitaryOptions, Apply: applyMilitaryOptions,
Add: () => addUnitLine({icon: "🛡️", name: "custom" + militaryOptionsTable.rows.length, rural: 0.2, urban: 0.5, crew: 1, power: 1, type: "melee"}), Add: () =>
addUnitLine({
icon: "🛡️",
name: "custom" + militaryOptionsTable.rows.length,
rural: 0.2,
urban: 0.5,
crew: 1,
power: 1,
type: "melee"
}),
Restore: restoreDefaultUnits, Restore: restoreDefaultUnits,
Cancel: function () { Cancel: function () {
$(this).dialog("close"); $(this).dialog("close");
@ -262,7 +282,7 @@ function overviewMilitary() {
if (el.tagName !== "BUTTON") return; if (el.tagName !== "BUTTON") return;
const type = el.dataset.type; const type = el.dataset.type;
if (type === "icon") return selectIcon(el.innerHTML, v => (el.innerHTML = v)); if (type === "icon") return selectIcon(el.textContent, v => (el.textContent = v));
if (type === "biomes") { if (type === "biomes") {
const {i, name, color} = biomesData; const {i, name, color} = biomesData;
const biomesArray = Array(i.length).fill(null); const biomesArray = Array(i.length).fill(null);
@ -294,7 +314,9 @@ function overviewMilitary() {
function addUnitLine(unit) { function addUnitLine(unit) {
const {type, icon, name, rural, urban, power, crew, separate} = unit; const {type, icon, name, rural, urban, power, crew, separate} = unit;
const row = document.createElement("tr"); const row = document.createElement("tr");
const typeOptions = types.map(t => `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" "); const typeOptions = types
.map(t => `<option ${type === t ? "selected" : ""} value="${t}">${t}</option>`)
.join(" ");
const getLimitButton = attr => const getLimitButton = attr =>
`<button `<button
@ -305,7 +327,9 @@ function overviewMilitary() {
${getLimitText(unit[attr])} ${getLimitText(unit[attr])}
</button>`; </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 || " "
}</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><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("biomes")}</td>
<td>${getLimitButton("states")}</td> <td>${getLimitButton("states")}</td>
@ -344,7 +368,9 @@ function overviewMilitary() {
const lines = filtered.map( const lines = filtered.map(
({i, name, fullName, color}) => ({i, name, fullName, color}) =>
`<tr data-tip="${name}"><td><span style="color:${color}">⬤</span></td> `<tr data-tip="${name}"><td><span style="color:${color}">⬤</span></td>
<td><input data-i="${i}" id="el${i}" type="checkbox" class="checkbox" ${!initial.length || initial.includes(i) ? "checked" : ""} > <td><input data-i="${i}" id="el${i}" type="checkbox" class="checkbox" ${
!initial.length || initial.includes(i) ? "checked" : ""
} >
<label for="el${i}" class="checkbox-label">${fullName || name}</label> <label for="el${i}" class="checkbox-label">${fullName || name}</label>
</td></tr>` </td></tr>`
); );
@ -387,22 +413,21 @@ function overviewMilitary() {
function applyMilitaryOptions() { function applyMilitaryOptions() {
const unitLines = Array.from(tableBody.querySelectorAll("tr")); const unitLines = Array.from(tableBody.querySelectorAll("tr"));
const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, "_")); const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, "_"));
if (new Set(names).size !== names.length) { if (new Set(names).size !== names.length) return tip("All units should have unique names", false, "error");
tip("All units should have unique names", false, "error");
return;
}
$("#militaryOptions").dialog("close"); $("#militaryOptions").dialog("close");
options.military = unitLines.map((r, i) => { options.military = unitLines.map((r, i) => {
const elements = Array.from(r.querySelectorAll("input, button, select")); const elements = Array.from(r.querySelectorAll("input, button, select"));
const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] = elements.map(el => { const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] =
const {type, value} = el.dataset || {}; elements.map(el => {
if (type === "icon") return el.innerHTML || ""; const {type, value} = el.dataset || {};
if (type) return value ? value.split(",").map(v => parseInt(v)) : null; if (type === "icon") return el.textContent || "";
if (el.type === "number") return +el.value || 0; if (type) return value ? value.split(",").map(v => parseInt(v)) : null;
if (el.type === "checkbox") return +el.checked || 0; if (el.type === "number") return +el.value || 0;
return el.value; if (el.type === "checkbox") return +el.checked || 0;
}); return el.value;
});
const unit = {icon, name: names[i], rural, urban, crew, power, type, separate}; const unit = {icon, name: names[i], rural, urban, crew, power, type, separate};
if (biomes) unit.biomes = biomes; if (biomes) unit.biomes = biomes;
@ -419,7 +444,8 @@ function overviewMilitary() {
} }
function militaryRecalculate() { function militaryRecalculate() {
alertMessage.innerHTML = "Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated"; alertMessage.innerHTML =
"Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated";
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Remove regiment", title: "Remove regiment",

View file

@ -691,7 +691,7 @@ function changeEra() {
} }
async function openTemplateSelectionDialog() { async function openTemplateSelectionDialog() {
const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=1.93.12"); const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=1.96.00");
HeightmapSelectionDialog.open(); HeightmapSelectionDialog.open();
} }
@ -774,7 +774,7 @@ function showExportPane() {
} }
async function exportToJson(type) { async function exportToJson(type) {
const {exportToJson} = await import("../dynamic/export-json.js?v=1.93.03"); const {exportToJson} = await import("../dynamic/export-json.js?v=1.96.00");
exportToJson(type); exportToJson(type);
} }
@ -867,11 +867,9 @@ byId("mapToLoad").addEventListener("change", function () {
}); });
function openExportToPngTiles() { function openExportToPngTiles() {
byId("tileStatus").innerHTML = "";
closeDialogs(); closeDialogs();
updateTilesOptions(); updateTilesOptions();
const status = byId("tileStatus");
status.innerHTML = "";
let loading = null;
const inputs = byId("exportToPngTilesScreen").querySelectorAll("input"); const inputs = byId("exportToPngTilesScreen").querySelectorAll("input");
inputs.forEach(input => input.addEventListener("input", updateTilesOptions)); inputs.forEach(input => input.addEventListener("input", updateTilesOptions));
@ -881,16 +879,7 @@ function openExportToPngTiles() {
title: "Download tiles", title: "Download tiles",
width: "23em", width: "23em",
buttons: { buttons: {
Download: function () { Download: () => exportToPngTiles(),
status.innerHTML = "Preparing for download...";
setTimeout(() => (status.innerHTML = "Downloading. It may take some time."), 1000);
loading = setInterval(() => (status.innerHTML += "."), 1000);
exportToPngTiles().then(() => {
clearInterval(loading);
status.innerHTML = /* html */ `Done. Check file in "Downloads" (crtl + J)`;
setTimeout(() => (status.innerHTML = ""), 8000);
});
},
Cancel: function () { Cancel: function () {
$(this).dialog("close"); $(this).dialog("close");
} }
@ -898,7 +887,6 @@ function openExportToPngTiles() {
close: () => { close: () => {
inputs.forEach(input => input.removeEventListener("input", updateTilesOptions)); inputs.forEach(input => input.removeEventListener("input", updateTilesOptions));
debug.selectAll("*").remove(); debug.selectAll("*").remove();
clearInterval(loading);
} }
}); });
} }
@ -928,18 +916,22 @@ function updateTilesOptions() {
const labels = []; const labels = [];
const tileW = (graphWidth / tilesX) | 0; const tileW = (graphWidth / tilesX) | 0;
const tileH = (graphHeight / tilesY) | 0; const tileH = (graphHeight / tilesY) | 0;
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) { const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (let y = 0, row = 0; y + tileH <= graphHeight; y += tileH, row++) {
for (let x = 0, column = 1; x + tileW <= graphWidth; x += tileW, column++) {
rects.push(`<rect x=${x} y=${y} width=${tileW} height=${tileH} />`); rects.push(`<rect x=${x} y=${y} width=${tileW} height=${tileH} />`);
labels.push(`<text x=${x + tileW / 2} y=${y + tileH / 2}>${i}</text>`); const label = alphabet[row % alphabet.length] + column;
labels.push(`<text x=${x + tileW / 2} y=${y + tileH / 2}>${label}</text>`);
} }
} }
const rectsG = "<g fill='none' stroke='#000'>" + rects.join("") + "</g>";
const labelsG = debug.html(`
"<g fill='#000' stroke='none' text-anchor='middle' dominant-baseline='central' font-size='24px'>" + <g fill='none' stroke='#000'>${rects.join("")}</g>
labels.join("") + <g fill='#000' stroke='none' text-anchor='middle' dominant-baseline='central' font-size='18px'>${labels.join(
"</g>"; ""
debug.html(rectsG + labelsG); )}</g>
`);
} }
// View mode // View mode

View file

@ -45,12 +45,14 @@ function editProvinces() {
cl = el.classList, cl = el.classList,
line = el.parentNode, line = el.parentNode,
p = +line.dataset.id; p = +line.dataset.id;
const stateId = pack.provinces[p].state;
if (el.tagName === "FILL-BOX") changeFill(el); if (el.tagName === "FILL-BOX") changeFill(el);
else if (cl.contains("name")) editProvinceName(p); else if (cl.contains("name")) editProvinceName(p);
else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]); else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]);
else if (cl.contains("icon-star-empty")) capitalZoomIn(p); else if (cl.contains("icon-star-empty")) capitalZoomIn(p);
else if (cl.contains("icon-flag-empty")) triggerIndependencePromps(p); else if (cl.contains("icon-flag-empty")) triggerIndependencePromps(p);
else if (cl.contains("icon-dot-circled")) overviewBurgs({stateId});
else if (cl.contains("culturePopulation")) changePopulation(p); else if (cl.contains("culturePopulation")) changePopulation(p);
else if (cl.contains("icon-pin")) toggleFog(p, cl); else if (cl.contains("icon-pin")) toggleFog(p, cl);
else if (cl.contains("icon-trash-empty")) removeProvince(p); else if (cl.contains("icon-trash-empty")) removeProvince(p);
@ -72,9 +74,8 @@ function editProvinces() {
} }
function collectStatistics() { function collectStatistics() {
const cells = pack.cells, const {cells, provinces, burgs} = pack;
provinces = pack.provinces,
burgs = pack.burgs;
provinces.forEach(p => { provinces.forEach(p => {
if (!p.i || p.removed) return; if (!p.i || p.removed) return;
p.area = p.rural = p.urban = 0; p.area = p.rural = p.urban = 0;
@ -108,16 +109,18 @@ function editProvinces() {
statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState))); statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState)));
} }
// add line for each state // add line for each province
function provincesEditorAddLines() { function provincesEditorAddLines() {
const unit = " " + getAreaUnit(); const unit = " " + getAreaUnit();
const selectedState = +document.getElementById("provincesFilterState").value; const selectedState = +document.getElementById("provincesFilterState").value;
let filtered = pack.provinces.filter(p => p.i && !p.removed); // all valid burgs let filtered = pack.provinces.filter(p => p.i && !p.removed); // all valid burgs
if (selectedState != -1) filtered = filtered.filter(p => p.state === selectedState); // filtered by state if (selectedState != -1) filtered = filtered.filter(p => p.state === selectedState); // filtered by state
body.innerHTML = ""; body.innerHTML = "";
let lines = "",
totalArea = 0, let lines = "";
totalPopulation = 0; let totalArea = 0;
let totalPopulation = 0;
let totalBurgs = 0;
for (const p of filtered) { for (const p of filtered) {
const area = getArea(p.area); const area = getArea(p.area);
@ -129,6 +132,7 @@ function editProvinces() {
rural rural
)}; Urban population: ${si(urban)}`; )}; Urban population: ${si(urban)}`;
totalPopulation += population; totalPopulation += population;
totalBurgs += p.burgs.length;
const stateName = pack.states[p.state].name; const stateName = pack.states[p.state].name;
const capital = p.burg ? pack.burgs[p.burg].name : ""; const capital = p.burg ? pack.burgs[p.burg].name : "";
@ -145,6 +149,7 @@ function editProvinces() {
data-state="${stateName}" data-state="${stateName}"
data-area=${area} data-area=${area}
data-population=${population} data-population=${population}
data-burgs=${p.burgs.length}
> >
<fill-box fill="${p.color}"></fill-box> <fill-box fill="${p.color}"></fill-box>
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly /> <input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly />
@ -164,6 +169,8 @@ function editProvinces() {
${p.burgs.length ? getCapitalOptions(p.burgs, p.burg) : ""} ${p.burgs.length ? getCapitalOptions(p.burgs, p.burg) : ""}
</select> </select>
<input data-tip="Province owner" class="provinceOwner" value="${stateName}" disabled"> <input data-tip="Province owner" class="provinceOwner" value="${stateName}" disabled">
<span data-tip="Click to overview province burgs" style="padding-right: 1px" class="icon-dot-circled pointer hide"></span>
<div data-tip="Burgs count" class="provinceBurgs hide">${p.burgs.length}</div>
<span data-tip="Province area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Province area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Province area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="Province area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
@ -180,11 +187,12 @@ function editProvinces() {
body.innerHTML = lines; body.innerHTML = lines;
// update footer // update footer
provincesFooterNumber.innerHTML = filtered.length; byId("provincesFooterNumber").innerHTML = filtered.length;
provincesFooterArea.innerHTML = filtered.length ? si(totalArea / filtered.length) + unit : 0 + unit; byId("provincesFooterBurgs").innerHTML = totalBurgs;
provincesFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0; byId("provincesFooterArea").innerHTML = filtered.length ? si(totalArea / filtered.length) + unit : 0 + unit;
provincesFooterArea.dataset.area = totalArea; byId("provincesFooterPopulation").innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0;
provincesFooterPopulation.dataset.population = totalPopulation; byId("provincesFooterArea").dataset.area = totalArea;
byId("provincesFooterPopulation").dataset.population = totalPopulation;
body.querySelectorAll("div.states").forEach(el => { body.querySelectorAll("div.states").forEach(el => {
el.addEventListener("click", selectProvinceOnLineClick); el.addEventListener("click", selectProvinceOnLineClick);
@ -295,7 +303,7 @@ function editProvinces() {
// move all burgs to a new state // move all burgs to a new state
province.burgs.forEach(b => (burgs[b].state = newStateId)); province.burgs.forEach(b => (burgs[b].state = newStateId));
// difine new state attributes // define new state attributes
const {cell: center, culture} = burgs[burgId]; const {cell: center, culture} = burgs[burgId];
const color = getRandomColor(); const color = getRandomColor();
const coa = province.coa; const coa = province.coa;
@ -502,6 +510,9 @@ function editProvinces() {
applyOption(provinceNameEditorSelectForm, p.formName); applyOption(provinceNameEditorSelectForm, p.formName);
document.getElementById("provinceNameEditorFull").value = p.fullName; document.getElementById("provinceNameEditorFull").value = p.fullName;
const cultureId = pack.cells.culture[p.center];
document.getElementById("provinceCultureDisplay").innerText = pack.cultures[cultureId].name;
$("#provinceNameEditor").dialog({ $("#provinceNameEditor").dialog({
resizable: false, resizable: false,
title: "Change province name", title: "Change province name",
@ -588,12 +599,15 @@ function editProvinces() {
function togglePercentageMode() { function togglePercentageMode() {
if (body.dataset.type === "absolute") { if (body.dataset.type === "absolute") {
body.dataset.type = "percentage"; body.dataset.type = "percentage";
const totalBurgs = +byId("provincesFooterBurgs").innerText;
const totalArea = +provincesFooterArea.dataset.area; const totalArea = +provincesFooterArea.dataset.area;
const totalPopulation = +provincesFooterPopulation.dataset.population; const totalPopulation = +provincesFooterPopulation.dataset.population;
body.querySelectorAll(":scope > div").forEach(function (el) { body.querySelectorAll(":scope > div").forEach(function (el) {
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%"; const {cells, burgs, area, population} = el.dataset;
el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%"; el.querySelector(".provinceBurgs").innerText = rn((+burgs / totalBurgs) * 100) + "%";
el.querySelector(".biomeArea").innerHTML = rn((+area / totalArea) * 100) + "%";
el.querySelector(".culturePopulation").innerHTML = rn((+population / totalPopulation) * 100) + "%";
}); });
} else { } else {
body.dataset.type = "absolute"; body.dataset.type = "absolute";
@ -1076,10 +1090,7 @@ function editProvinces() {
function downloadProvincesData() { function downloadProvincesData() {
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
let data = let data = `Id,Province,Full Name,Form,State,Color,Capital,Area ${unit},Total Population,Rural Population,Urban Population,Burgs\n`; // headers
"Id,Province,Full Name,Form,State,Color,Capital,Area " +
unit +
",Total Population,Rural Population,Urban Population\n"; // headers
body.querySelectorAll(":scope > div").forEach(function (el) { body.querySelectorAll(":scope > div").forEach(function (el) {
const key = parseInt(el.dataset.id); const key = parseInt(el.dataset.id);
@ -1093,8 +1104,9 @@ function editProvinces() {
data += el.dataset.capital + ","; data += el.dataset.capital + ",";
data += el.dataset.area + ","; data += el.dataset.area + ",";
data += el.dataset.population + ","; data += el.dataset.population + ",";
data += `${Math.round(provincePack.rural * populationRate)},`; data += Math.round(provincePack.rural * populationRate) + ",";
data += `${Math.round(provincePack.urban * populationRate * urbanization)}\n`; data += Math.round(provincePack.urban * populationRate * urbanization) + ",";
data += el.dataset.burgs + "\n";
}); });
const name = getFileName("Provinces") + ".csv"; const name = getFileName("Provinces") + ".csv";

View file

@ -14,6 +14,7 @@
byId("styleFilterInput").innerHTML = allOptions; byId("styleFilterInput").innerHTML = allOptions;
byId("styleStatesBodyFilter").innerHTML = allOptions; byId("styleStatesBodyFilter").innerHTML = allOptions;
byId("styleScaleBarBackgroundFilter").innerHTML = allOptions;
} }
// store some style inputs as options // store some style inputs as options
@ -31,6 +32,7 @@ function editStyle(element, group) {
styleElementSelect.classList.add("glow"); styleElementSelect.classList.add("glow");
if (group) styleGroupSelect.classList.add("glow"); if (group) styleGroupSelect.classList.add("glow");
setTimeout(() => { setTimeout(() => {
styleElementSelect.classList.remove("glow"); styleElementSelect.classList.remove("glow");
if (group) styleGroupSelect.classList.remove("glow"); if (group) styleGroupSelect.classList.remove("glow");
@ -81,10 +83,10 @@ function selectStyleElement() {
styleIsOff.style.display = isLayerOff ? "block" : "none"; styleIsOff.style.display = isLayerOff ? "block" : "none";
// active group element // active group element
const group = styleGroupSelect.value; if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders", "terrs"].includes(styleElement)) {
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(styleElement)) { const group = styleGroupSelect.value;
const gEl = group && el.select("#" + group); const defaultGroupSelector = styleElement === "terrs" ? "#landHeights" : "g";
el = group && gEl.size() ? gEl : el.select("g"); el = group && el.select("#" + group).size() ? el.select("#" + group) : el.select(defaultGroupSelector);
} }
// opacity // opacity
@ -94,13 +96,13 @@ function selectStyleElement() {
} }
// filter // filter
if (!["landmass", "legend", "regions"].includes(styleElement)) { if (!["landmass", "legend", "regions", "scaleBar"].includes(styleElement)) {
styleFilter.style.display = "block"; styleFilter.style.display = "block";
styleFilterInput.value = el.attr("filter") || ""; styleFilterInput.value = el.attr("filter") || "";
} }
// fill // fill
if (["rivers", "lakes", "landmass", "prec", "ice", "fogging", "vignette"].includes(styleElement)) { if (["rivers", "lakes", "landmass", "prec", "ice", "fogging", "scaleBar", "vignette"].includes(styleElement)) {
styleFill.style.display = "block"; styleFill.style.display = "block";
styleFillInput.value = styleFillOutput.value = el.attr("fill"); styleFillInput.value = styleFillOutput.value = el.attr("fill");
} }
@ -170,11 +172,14 @@ function selectStyleElement() {
if (styleElement === "terrs") { if (styleElement === "terrs") {
styleHeightmap.style.display = "block"; styleHeightmap.style.display = "block";
styleHeightmapScheme.value = terrs.attr("scheme"); styleHeightmapRenderOceanOption.style.display = el.attr("id") === "oceanHeights" ? "block" : "none";
styleHeightmapTerracingInput.value = styleHeightmapTerracingOutput.value = terrs.attr("terracing"); styleHeightmapRenderOcean.checked = +el.attr("data-render");
styleHeightmapSkipInput.value = styleHeightmapSkipOutput.value = terrs.attr("skip");
styleHeightmapSimplificationInput.value = styleHeightmapSimplificationOutput.value = terrs.attr("relax"); styleHeightmapScheme.value = el.attr("scheme");
styleHeightmapCurve.value = terrs.attr("curve"); styleHeightmapTerracingInput.value = styleHeightmapTerracingOutput.value = el.attr("terracing");
styleHeightmapSkipInput.value = styleHeightmapSkipOutput.value = el.attr("skip");
styleHeightmapSimplificationInput.value = styleHeightmapSimplificationOutput.value = el.attr("relax");
styleHeightmapCurve.value = el.attr("curve");
} }
if (styleElement === "markers") { if (styleElement === "markers") {
@ -336,7 +341,7 @@ function selectStyleElement() {
// update group options // update group options
styleGroupSelect.options.length = 0; // remove all options styleGroupSelect.options.length = 0; // remove all options
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(styleElement)) { if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders", "terrs"].includes(styleElement)) {
const groups = byId(styleElement).querySelectorAll("g"); const groups = byId(styleElement).querySelectorAll("g");
groups.forEach(el => { groups.forEach(el => {
if (el.id === "burgLabels") return; if (el.id === "burgLabels") return;
@ -356,6 +361,31 @@ function selectStyleElement() {
if (auto) styleFilter.style.display = "none"; if (auto) styleFilter.style.display = "none";
} }
if (styleElement === "scaleBar") {
styleScaleBar.style.display = "block";
styleScaleBarSize.value = el.attr("data-bar-size");
styleScaleBarFontSize.value = el.attr("font-size");
styleScaleBarPositionX.value = el.attr("data-x") || "99";
styleScaleBarPositionY.value = el.attr("data-y") || "99";
styleScaleBarLabel.value = el.attr("data-label") || "";
const scaleBarBack = el.select("#scaleBarBack");
if (scaleBarBack.size()) {
styleScaleBarBackgroundOpacityInput.value = styleScaleBarBackgroundOpacityOutput.value =
scaleBarBack.attr("opacity");
styleScaleBarBackgroundFillInput.value = styleScaleBarBackgroundFillOutput.value = scaleBarBack.attr("fill");
styleScaleBarBackgroundStrokeInput.value = styleScaleBarBackgroundStrokeOutput.value =
scaleBarBack.attr("stroke");
styleScaleBarBackgroundStrokeWidth.value = scaleBarBack.attr("stroke-width");
styleScaleBarBackgroundFilter.value = scaleBarBack.attr("filter");
styleScaleBarBackgroundPaddingTop.value = scaleBarBack.attr("data-top");
styleScaleBarBackgroundPaddingRight.value = scaleBarBack.attr("data-right");
styleScaleBarBackgroundPaddingBottom.value = scaleBarBack.attr("data-bottom");
styleScaleBarBackgroundPaddingLeft.value = scaleBarBack.attr("data-left");
}
}
if (styleElement === "vignette") { if (styleElement === "vignette") {
styleVignette.style.display = "block"; styleVignette.style.display = "block";
@ -519,18 +549,16 @@ outlineLayers.addEventListener("change", function () {
}); });
styleHeightmapScheme.addEventListener("change", function () { styleHeightmapScheme.addEventListener("change", function () {
terrs.attr("scheme", this.value); getEl().attr("scheme", this.value);
drawHeightmap(); drawHeightmap();
}); });
openCreateHeightmapSchemeButton.addEventListener("click", function () { openCreateHeightmapSchemeButton.addEventListener("click", function () {
// start with current scheme // start with current scheme
this.dataset.stops = terrs.attr("scheme").startsWith("#") const scheme = getEl().attr("scheme");
? terrs.attr("scheme") this.dataset.stops = scheme.startsWith("#")
: (function () { ? scheme
const scheme = heightmapColorSchemes[terrs.attr("scheme")]; : (() => [0, 0.25, 0.5, 0.75, 1].map(heightmapColorSchemes[scheme]).map(toHEX).join(","))();
return [0, 0.25, 0.5, 0.75, 1].map(scheme).map(toHEX).join(",");
})();
// render dialog base structure // render dialog base structure
alertMessage.innerHTML = /* html */ `<div> alertMessage.innerHTML = /* html */ `<div>
@ -622,7 +650,7 @@ openCreateHeightmapSchemeButton.addEventListener("click", function () {
if (stops in heightmapColorSchemes) return tip("This scheme already exists", false, "error"); if (stops in heightmapColorSchemes) return tip("This scheme already exists", false, "error");
addCustomColorScheme(stops); addCustomColorScheme(stops);
terrs.attr("scheme", stops); getEl().attr("scheme", stops);
drawHeightmap(); drawHeightmap();
handleClose(); handleClose();
@ -644,23 +672,28 @@ openCreateHeightmapSchemeButton.addEventListener("click", function () {
}); });
}); });
styleHeightmapRenderOcean.addEventListener("change", function () {
getEl().attr("data-render", +this.checked);
drawHeightmap();
});
styleHeightmapTerracingInput.addEventListener("input", function () { styleHeightmapTerracingInput.addEventListener("input", function () {
terrs.attr("terracing", this.value); getEl().attr("terracing", this.value);
drawHeightmap(); drawHeightmap();
}); });
styleHeightmapSkipInput.addEventListener("input", function () { styleHeightmapSkipInput.addEventListener("input", function () {
terrs.attr("skip", this.value); getEl().attr("skip", this.value);
drawHeightmap(); drawHeightmap();
}); });
styleHeightmapSimplificationInput.addEventListener("input", function () { styleHeightmapSimplificationInput.addEventListener("input", function () {
terrs.attr("relax", this.value); getEl().attr("relax", this.value);
drawHeightmap(); drawHeightmap();
}); });
styleHeightmapCurve.addEventListener("change", function () { styleHeightmapCurve.addEventListener("change", function () {
terrs.attr("curve", this.value); getEl().attr("curve", this.value);
drawHeightmap(); drawHeightmap();
}); });
@ -957,7 +990,7 @@ function textureProvideURL() {
} }
function fetchTextureURL(url) { function fetchTextureURL(url) {
INFO && console.log("Provided URL is", url); INFO && console.info("Provided URL is", url);
const img = new Image(); const img = new Image();
img.onload = function () { img.onload = function () {
const canvas = byId("texturePreview"); const canvas = byId("texturePreview");
@ -1043,6 +1076,44 @@ styleVignetteBlur.addEventListener("input", function () {
byId("vignette-rect")?.setAttribute("filter", `blur(${this.value}px)`); byId("vignette-rect")?.setAttribute("filter", `blur(${this.value}px)`);
}); });
styleScaleBar.addEventListener("input", function (event) {
const scaleBarBack = scaleBar.select("#scaleBarBack");
if (!scaleBarBack.size()) return;
const {id, value} = event.target;
if (id === "styleScaleBarSize") scaleBar.attr("data-bar-size", value);
else if (id === "styleScaleBarFontSize") scaleBar.attr("font-size", value);
else if (id === "styleScaleBarPositionX") scaleBar.attr("data-x", value);
else if (id === "styleScaleBarPositionY") scaleBar.attr("data-y", value);
else if (id === "styleScaleBarLabel") scaleBar.attr("data-label", value);
else if (id === "styleScaleBarBackgroundOpacityInput") scaleBarBack.attr("opacity", value);
else if (id === "styleScaleBarBackgroundFillInput") scaleBarBack.attr("fill", value);
else if (id === "styleScaleBarBackgroundStrokeInput") scaleBarBack.attr("stroke", value);
else if (id === "styleScaleBarBackgroundStrokeWidth") scaleBarBack.attr("stroke-width", value);
else if (id === "styleScaleBarBackgroundFilter") scaleBarBack.attr("filter", value);
else if (id === "styleScaleBarBackgroundPaddingTop") scaleBarBack.attr("data-top", value);
else if (id === "styleScaleBarBackgroundPaddingRight") scaleBarBack.attr("data-right", value);
else if (id === "styleScaleBarBackgroundPaddingBottom") scaleBarBack.attr("data-bottom", value);
else if (id === "styleScaleBarBackgroundPaddingLeft") scaleBarBack.attr("data-left", value);
if (
[
"styleScaleBarSize",
"styleScaleBarPositionX",
"styleScaleBarPositionY",
"styleScaleBarLabel",
"styleScaleBarBackgroundPaddingLeft",
"styleScaleBarBackgroundPaddingTop",
"styleScaleBarBackgroundPaddingRight",
"styleScaleBarBackgroundPaddingBottom"
].includes(id)
) {
drawScaleBar(scaleBar, scale);
fitScaleBar(scaleBar, svgWidth, svgHeight);
}
});
function updateElements() { function updateElements() {
// burgIcons to desired size // burgIcons to desired size
burgIcons.selectAll("g").each(function () { burgIcons.selectAll("g").each(function () {

View file

@ -140,6 +140,9 @@ function applyStyleWithUiRefresh(style) {
invokeActiveZooming(); invokeActiveZooming();
setPresetRemoveButtonVisibiliy(); setPresetRemoveButtonVisibiliy();
drawScaleBar(scaleBar, scale);
fitScaleBar(scaleBar, svgWidth, svgHeight);
} }
function addStylePreset() { function addStylePreset() {
@ -239,7 +242,18 @@ function addStylePreset() {
"#oceanLayers": ["filter", "layers"], "#oceanLayers": ["filter", "layers"],
"#oceanBase": ["fill"], "#oceanBase": ["fill"],
"#oceanicPattern": ["href", "opacity"], "#oceanicPattern": ["href", "opacity"],
"#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"], "#terrs #oceanHeights": [
"data-render",
"opacity",
"scheme",
"terracing",
"skip",
"relax",
"curve",
"filter",
"mask"
],
"#terrs #landHeights": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
"#legend": [ "#legend": [
"data-size", "data-size",
"font-size", "font-size",
@ -301,7 +315,19 @@ function addStylePreset() {
], ],
"#fogging": ["opacity", "fill", "filter"], "#fogging": ["opacity", "fill", "filter"],
"#vignette": ["opacity", "fill", "filter"], "#vignette": ["opacity", "fill", "filter"],
"#vignette-rect": ["x", "y", "width", "height", "rx", "ry", "filter"] "#vignette-rect": ["x", "y", "width", "height", "rx", "ry", "filter"],
"#scaleBar": ["opacity", "fill", "font-size", "data-bar-size", "data-x", "data-y", "data-label"],
"#scaleBarBack": [
"opacity",
"fill",
"stroke",
"stroke-width",
"filter",
"data-top",
"data-right",
"data-bottom",
"data-left"
]
}; };
for (const selector in attributes) { for (const selector in attributes) {

View file

@ -247,13 +247,16 @@ function recreateStates() {
capitalsTree.add([x, y]); capitalsTree.add([x, y]);
// update label id reference // update label id reference
labels byId(`textPath_stateLabel${state.i}`)?.setAttribute("id", `textPath_stateLabel${newId}`);
.select("#states") const $label = byId(`stateLabel${state.i}`);
.select(`#stateLabel${state.i}`) if ($label) {
.attr("id", `stateLabel${newId}`) $label.setAttribute("id", `stateLabel${newId}`);
.select("textPath") const $textPath = $label.querySelector("textPath");
.attr("xlink:href", `#textPath_stateLabel${newId}`); if ($textPath) {
defs.select("#textPaths").select(`#textPath_stateLabel${state.i}`).attr("id", `textPath_stateLabel${newId}`); $textPath.removeAttribute("href");
$textPath.setAttribute("href", `#textPath_stateLabel${newId}`);
}
}
// update emblem id reference // update emblem id reference
byId(`stateCOA${state.i}`)?.setAttribute("id", `stateCOA${newId}`); byId(`stateCOA${state.i}`)?.setAttribute("id", `stateCOA${newId}`);

View file

@ -24,13 +24,6 @@ function editUnits() {
byId("heightExponentInput").addEventListener("input", changeHeightExponent); byId("heightExponentInput").addEventListener("input", changeHeightExponent);
byId("heightExponentOutput").addEventListener("input", changeHeightExponent); byId("heightExponentOutput").addEventListener("input", changeHeightExponent);
byId("temperatureScale").addEventListener("change", changeTemperatureScale); byId("temperatureScale").addEventListener("change", changeTemperatureScale);
byId("barSizeOutput").addEventListener("input", renderScaleBar);
byId("barSizeInput").addEventListener("input", renderScaleBar);
byId("barLabel").addEventListener("input", renderScaleBar);
byId("barPosX").addEventListener("input", fitScaleBar);
byId("barPosY").addEventListener("input", fitScaleBar);
byId("barBackOpacity").addEventListener("input", changeScaleBarOpacity);
byId("barBackColor").addEventListener("input", changeScaleBarColor);
byId("populationRateOutput").addEventListener("input", changePopulationRate); byId("populationRateOutput").addEventListener("input", changePopulationRate);
byId("populationRateInput").addEventListener("change", changePopulationRate); byId("populationRateInput").addEventListener("change", changePopulationRate);
@ -84,14 +77,6 @@ function editUnits() {
if (layerIsOn("toggleTemp")) drawTemp(); if (layerIsOn("toggleTemp")) drawTemp();
} }
function changeScaleBarOpacity() {
scaleBar.select("rect").attr("opacity", this.value);
}
function changeScaleBarColor() {
scaleBar.select("rect").attr("fill", this.value);
}
function changePopulationRate() { function changePopulationRate() {
populationRate = +this.value; populationRate = +this.value;
} }
@ -129,19 +114,6 @@ function editUnits() {
localStorage.removeItem("heightExponent"); localStorage.removeItem("heightExponent");
calculateTemperatures(); calculateTemperatures();
// scale bar
barSizeOutput.value = barSizeInput.value = 2;
barLabel.value = "";
barBackOpacity.value = 0.2;
barBackColor.value = "#ffffff";
barPosX.value = barPosY.value = 99;
localStorage.removeItem("barSize");
localStorage.removeItem("barLabel");
localStorage.removeItem("barBackOpacity");
localStorage.removeItem("barBackColor");
localStorage.removeItem("barPosX");
localStorage.removeItem("barPosY");
renderScaleBar(); renderScaleBar();
// population // population

View file

@ -113,9 +113,11 @@ function editWorld() {
function toKilometer(v) { function toKilometer(v) {
if (unit === "km") return v; if (unit === "km") return v;
else if (unit === "mi") return v * 1.60934; if (unit === "mi") return v * 1.60934;
else if (unit === "lg") return v * 5.556; if (unit === "lg") return v * 4.828;
else if (unit === "vr") return v * 1.0668; if (unit === "vr") return v * 1.0668;
if (unit === "nmi") return v * 1.852;
if (unit === "nlg") return v * 5.556;
return 0; // 0 if distanceUnitInput is a custom unit return 0; // 0 if distanceUnitInput is a custom unit
} }

View file

@ -286,14 +286,25 @@
"href": "./images/kiwiroo.png", "href": "./images/kiwiroo.png",
"opacity": 0.4 "opacity": 0.4
}, },
"#terrs": { "#terrs > #oceanHeights": {
"opacity": null, "data-render": 0,
"scheme": "bright", "opacity": 1,
"scheme": "light",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": "url(#filter-sepia)",
"mask": null
},
"#terrs > #landHeights": {
"opacity": 1,
"scheme": "light",
"terracing": 0, "terracing": 0,
"skip": 2, "skip": 2,
"relax": 1, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"filter": "url(#blur3)", "filter": "url(#filter-sepia)",
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -398,5 +409,25 @@
"rx": "0%", "rx": "0%",
"ry": "0%", "ry": "0%",
"filter": "blur(50px)" "filter": "blur(50px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -286,13 +286,24 @@
"href": "", "href": "",
"opacity": 1 "opacity": 1
}, },
"#terrs": { "#terrs > #oceanHeights": {
"opacity": null, "data-render": 1,
"opacity": 1,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 1,
"scheme": "bright", "scheme": "bright",
"terracing": 0, "terracing": 0,
"skip": 0, "skip": 0,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(30px)" "filter": "blur(30px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 9,
"data-bar-size": 1.5,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": null,
"data-top": 18,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -288,13 +288,24 @@
"href": "", "href": "",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 1,
"opacity": 0.5,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.5, "opacity": 0.5,
"scheme": "bright", "scheme": "bright",
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 5,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
@ -400,5 +411,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 8,
"data-bar-size": 1.5,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "",
"data-top": 17,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -286,14 +286,25 @@
"href": "", "href": "",
"opacity": 0.15 "opacity": 0.15
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 1,
"opacity": 1, "opacity": 1,
"scheme": "monochrome", "scheme": "olive",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.8,
"scheme": "livid",
"terracing": 6, "terracing": 6,
"skip": 0, "skip": 0,
"relax": 2, "relax": 2,
"curve": 0, "curve": "curveBasisClosed",
"filter": "", "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#d0d0dc",
"font-size": 11,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.3,
"fill": "#05001f",
"stroke": "#000000",
"stroke-width": 1,
"filter": "",
"data-top": 23,
"data-right": 18,
"data-bottom": 18,
"data-left": 12
} }
} }

View file

@ -286,13 +286,24 @@
"href": "./images/pattern1.png", "href": "./images/pattern1.png",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs > #oceanHeights": {
"opacity": null, "data-render": 0,
"opacity": 1,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 1,
"scheme": "bright", "scheme": "bright",
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 5,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -288,14 +288,25 @@
"href": "./images/pattern3.png", "href": "./images/pattern3.png",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs > #oceanHeights": {
"opacity": 1, "data-render": 1,
"opacity": 0.8,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.8,
"scheme": "bright", "scheme": "bright",
"terracing": 2, "terracing": 2,
"skip": 1, "skip": 1,
"relax": 2, "relax": 2,
"curve": 0, "curve": "curveBasisClosed",
"filter": "url(#filter-grayscale)", "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -400,5 +411,25 @@
"rx": "10%", "rx": "10%",
"ry": "10%", "ry": "10%",
"filter": "blur(30px)" "filter": "blur(30px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#121212",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#607671",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -286,13 +286,24 @@
"href": "./images/pattern1.png", "href": "./images/pattern1.png",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 1,
"opacity": 0.4, "opacity": 0.4,
"scheme": "light", "scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": "url(#turbulence)",
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.4,
"scheme": "bright",
"terracing": 10, "terracing": 10,
"skip": 5, "skip": 5,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": "url(#turbulence)", "filter": "url(#turbulence)",
"mask": "url(#land)" "mask": "url(#land)"
}, },
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -281,14 +281,25 @@
"href": "", "href": "",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs #oceanHeights": {
"data-render": 1,
"opacity": 1, "opacity": 1,
"scheme": "monochrome", "scheme": "monochrome",
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 0,
"relax": 0, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"filter": "url(#blur3)", "filter": "url(#turbulence)",
"mask": null
},
"#terrs #landHeights": {
"opacity": 1,
"scheme": "monochrome",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": "url(#turbulence)",
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -394,5 +405,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#ffffff",
"font-size": 8,
"data-bar-size": 1.5,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "",
"data-top": 18,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -286,14 +286,25 @@
"href": "", "href": "",
"opacity": 0.3 "opacity": 0.3
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 1,
"opacity": 0.5,
"scheme": "livid",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 1, "opacity": 1,
"scheme": "livid", "scheme": "livid",
"terracing": 0, "terracing": 0,
"skip": 10, "skip": 10,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": "url(#blurFilter)", "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#b5c1d4",
"font-size": 11,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.5,
"fill": "#0a1e24",
"stroke": "#40547a",
"stroke-width": 1,
"filter": "",
"data-top": 22,
"data-right": 15,
"data-bottom": 16,
"data-left": 10
} }
} }

View file

@ -286,14 +286,25 @@
"href": "./images/kiwiroo.png", "href": "./images/kiwiroo.png",
"opacity": 0.3 "opacity": 0.3
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 0,
"opacity": 0.7,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.7, "opacity": 0.7,
"scheme": "bright", "scheme": "bright",
"terracing": 0, "terracing": 0,
"skip": 2, "skip": 2,
"relax": 1, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"filter": "", "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(30px)" "filter": "blur(30px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -286,13 +286,24 @@
"href": "./images/kiwiroo.png", "href": "./images/kiwiroo.png",
"opacity": 0.5 "opacity": 0.5
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 1,
"opacity": 0.5,
"scheme": "light",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.5, "opacity": 0.5,
"scheme": "light", "scheme": "light",
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 5,
"relax": 1, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -327,9 +327,10 @@ function drawCellsValue(data) {
// helper function non-used for the main generation // helper function non-used for the main generation
function drawPolygons(data) { function drawPolygons(data) {
const max = d3.max(data), const max = d3.max(data);
min = d3.min(data), const min = d3.min(data);
scheme = getColorScheme(terrs.attr("scheme")); const scheme = getColorScheme(terrs.select("#landHeights").attr("scheme"));
data = data.map(d => 1 - normalize(d, min, max)); data = data.map(d => 1 - normalize(d, min, max));
debug.selectAll("polygon").remove(); debug.selectAll("polygon").remove();
@ -338,7 +339,7 @@ function drawPolygons(data) {
.data(data) .data(data)
.enter() .enter()
.append("polygon") .append("polygon")
.attr("points", (d, i) => getPackPolygon(i)) .attr("points", (d, i) => getGridPolygon(i))
.attr("fill", d => scheme(d)) .attr("fill", d => scheme(d))
.attr("stroke", d => scheme(d)); .attr("stroke", d => scheme(d));
} }

View file

@ -1,7 +1,7 @@
"use strict"; "use strict";
// version and caching control // version and caching control
const version = "1.95.05"; // generator version, update each time const version = "1.97.05"; // generator version, update each time
{ {
document.title += " v" + version; document.title += " v" + version;
@ -28,6 +28,9 @@ const version = "1.95.05"; // generator version, update each time
<ul> <ul>
<strong>Latest changes:</strong> <strong>Latest changes:</strong>
<li>Preview villages map</li>
<li>Ability to render ocean heightmap</li>
<li>Scale bar styling features</li>
<li>Vignette visual layer and vignette styling options</li> <li>Vignette visual layer and vignette styling options</li>
<li>Ability to define custom heightmap color scheme</li> <li>Ability to define custom heightmap color scheme</li>
<li>New style preset Night and new heightmap color schemes</li> <li>New style preset Night and new heightmap color schemes</li>