mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 03:51:23 +01:00
Merge branch 'master' into geodata
This commit is contained in:
commit
5bd06e1804
13 changed files with 949 additions and 561 deletions
30
index.css
30
index.css
|
|
@ -996,14 +996,26 @@ div.slider .ui-slider-handle {
|
||||||
top: 100%;
|
top: 100%;
|
||||||
border: 1px solid #5e4fa2;
|
border: 1px solid #5e4fa2;
|
||||||
background-color: #a4879b;
|
background-color: #a4879b;
|
||||||
width: 44px;
|
width: 5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#loadDropdown {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 53%;
|
||||||
|
top: 100%;
|
||||||
|
border: 1px solid #5e4fa2;
|
||||||
|
background-color: #a4879b;
|
||||||
|
width: 9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loadDropdown>div,
|
||||||
#saveDropdown>div {
|
#saveDropdown>div {
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#loadDropdown>div:hover,
|
||||||
#saveDropdown>div:hover {
|
#saveDropdown>div:hover {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
@ -1498,6 +1510,7 @@ div.states > div.biomeArea {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#title_name {
|
#title_name {
|
||||||
|
|
@ -1796,6 +1809,12 @@ svg.button {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#info-line {
|
||||||
|
font-size: .9em;
|
||||||
|
color: gray;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
.optionsSeedRestore {
|
.optionsSeedRestore {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -1910,6 +1929,15 @@ svg.button {
|
||||||
margin: 10px 0 0 7px;
|
margin: 10px 0 0 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#errorBox {
|
||||||
|
font-size: .9em;
|
||||||
|
font-family: monospace;
|
||||||
|
color: #920303;
|
||||||
|
background-color: #dabdbd91;
|
||||||
|
padding: 2px;
|
||||||
|
border: 1px solid #916e7f;
|
||||||
|
}
|
||||||
|
|
||||||
#debug {
|
#debug {
|
||||||
font-size: 1px;
|
font-size: 1px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|
|
||||||
72
index.html
72
index.html
|
|
@ -882,7 +882,7 @@
|
||||||
|
|
||||||
<div id="collapsible">
|
<div id="collapsible">
|
||||||
<button id="optionsTrigger" data-tip="Click to show options pane. Shortcut: Tab" class="options glow" onclick="showOptions(event)" style="padding:7px 5px">►</button>
|
<button id="optionsTrigger" data-tip="Click to show options pane. Shortcut: Tab" class="options glow" onclick="showOptions(event)" style="padding:7px 5px">►</button>
|
||||||
<button id="regenerate" data-tip="Click to generate a new map. Shortcut: F7" onclick="regeneratePrompt()" class="options" style="display:none; padding:7px 8px">New Map!</button>
|
<button id="regenerate" data-tip="Click to generate a new map. Shortcut: F2" onclick="regeneratePrompt()" class="options" style="display:none; padding:7px 8px">New Map!</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="options" style="display:none">
|
<div id="options" style="display:none">
|
||||||
|
|
@ -909,7 +909,8 @@
|
||||||
<option value="landmass">Pure landmass</option>
|
<option value="landmass">Pure landmass</option>
|
||||||
<option hidden value="custom">Custom (not saved)</option>
|
<option hidden value="custom">Custom (not saved)</option>
|
||||||
</select>
|
</select>
|
||||||
<button id="savePreset" data-tip="Click to save displayed layers as a new preset" class="icon-plus styleButton" onclick="savePreset()"></button>
|
<button id="savePresetButton" data-tip="Click to save displayed layers as a new preset" class="icon-plus styleButton" style="display:none" onclick="savePreset()"></button>
|
||||||
|
<button id="removePresetButton" data-tip="Click to remove current custom preset" class="icon-minus styleButton" style="display:none" onclick="removePreset()"></button>
|
||||||
|
|
||||||
<p data-tip="Click to toggle a layer, drag to raise or lower a layer">Displayed layers:</p>
|
<p data-tip="Click to toggle a layer, drag to raise or lower a layer">Displayed layers:</p>
|
||||||
<ul data-tip="Click to toggle a layer, drag to raise or lower a layer" id="mapLayers">
|
<ul data-tip="Click to toggle a layer, drag to raise or lower a layer" id="mapLayers">
|
||||||
|
|
@ -1470,7 +1471,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="optionsContent" class="tabcontent">
|
<div id="optionsContent" class="tabcontent">
|
||||||
<p data-tip="Map generation settings. Generate a new map to apply the settings">Map Generation (new map to apply):</p>
|
<p data-tip="Map generation settings. Generate a new map to apply the settings">Map settings (new map to apply):</p>
|
||||||
<table>
|
<table>
|
||||||
|
|
||||||
<tr data-tip="Map height and width in pixels. Please consider reducing map size in case of performance issues">
|
<tr data-tip="Map height and width in pixels. Please consider reducing map size in case of performance issues">
|
||||||
|
|
@ -1563,7 +1564,7 @@
|
||||||
<td>
|
<td>
|
||||||
<i data-locked=0 id="lock_provinces" class="icon-lock-open"></i>
|
<i data-locked=0 id="lock_provinces" class="icon-lock-open"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>Provinces number</td>
|
<td>Provinces ratio</td>
|
||||||
<td>
|
<td>
|
||||||
<input id="provincesInput" data-stored="provinces" type="range" min=0 max=100 value=30>
|
<input id="provincesInput" data-stored="provinces" type="range" min=0 max=100 value=30>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -1585,7 +1586,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr data-tip="Define state and cultures growth rate. Defines how many lands will stay neutral">
|
<tr data-tip="Set state and cultures growth rate. Defines how many lands will stay neutral">
|
||||||
<td>
|
<td>
|
||||||
<i data-locked=0 id="lock_neutral" class="icon-lock-open"></i>
|
<i data-locked=0 id="lock_neutral" class="icon-lock-open"></i>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -1598,7 +1599,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr data-tip="Define how many towns (non-capital burgs) should be generated">
|
<tr data-tip="Define a number of towns to be placed (if suitable area is enougth)">
|
||||||
<td>
|
<td>
|
||||||
<i data-locked=0 id="lock_manors" class="icon-lock-open"></i>
|
<i data-locked=0 id="lock_manors" class="icon-lock-open"></i>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -1611,7 +1612,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr data-tip="Define how many organized religions and cults should be generated">
|
<tr data-tip="Define how many organized (!) religions and cults should be generated">
|
||||||
<td>
|
<td>
|
||||||
<i data-locked=0 id="lock_religions" class="icon-lock-open"></i>
|
<i data-locked=0 id="lock_religions" class="icon-lock-open"></i>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -1625,16 +1626,28 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p data-tip="Interface settings that don't affect generation. They are applied immediately on change">User Interface:</p>
|
<p data-tip="Tool settings that don't affect maps. Changes are getting applied immediately">Generator settings:</p>
|
||||||
<table>
|
<table>
|
||||||
<tr data-tip="Set user interface size">
|
<tr data-tip="Set what Generator should do on opening">
|
||||||
|
<td></td>
|
||||||
|
<td>Onload behavior</td>
|
||||||
|
<td>
|
||||||
|
<select id="onloadMap" data-stored="onloadMap">
|
||||||
|
<option value="random" selected>Generate random map</option>
|
||||||
|
<option value="saved">Open last saved map</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr data-tip="Set user interface size. Please note browser zoom also affects interface size (Ctrl + or Ctrl - to change)">
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>Interface size</td>
|
<td>Interface size</td>
|
||||||
<td>
|
<td>
|
||||||
<input id="uiSizeInput" data-stored="uiSize" type="range" min=.6 max=1.4 step=.1 value=1>
|
<input id="uiSizeInput" data-stored="uiSize" type="range" min=.8 max=2 step=.1 value=1>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input id="uiSizeOutput" data-stored="uiSize" type="number" min=.6 max=1.4 step=.1 value=1>
|
<input id="uiSizeOutput" data-stored="uiSize" type="number" min=.6 max=2 step=.1 value=1>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
@ -1801,7 +1814,13 @@
|
||||||
<p>Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share created maps. You may support the project on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>.</p>
|
<p>Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share created maps. You may support the project on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>.</p>
|
||||||
<p>The project is under active development. For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:maxganiev@yandex.ru" target="_blank">email</a>.</p>
|
<p>The project is under active development. For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:maxganiev@yandex.ru" target="_blank">email</a>.</p>
|
||||||
<p>A special thanks to all supporters! <i data-tip="Click to see supporters names" class="collapsible icon-down-open pointer"></i></p>
|
<p>A special thanks to all supporters! <i data-tip="Click to see supporters names" class="collapsible icon-down-open pointer"></i></p>
|
||||||
<p style="display:none">Supporters: Aaron Meyer, Ahmad Amerih, AstralJacks, aymeric, Billy Dean Goehring, Branndon Edwards, Chase Mayers, Curt Flood, cyninge, Dino Princip, E.M. White, es, Fondue, Fritjof Olsson, Gatsu, Johan Fröberg, Jonathan Moore, Joseph Miranda, Kate, KC138, Luke Nelson, Markus Finster, Massimo Vella, Mikey, Nathan Mitchell, Paavi1, Pat, Ryan Westcott, Sasquatch, Shawn Spencer, Sizz_TV, Timothée CALLET, UTG community, Vlad Tomash, Wil Sisney, William Merriott, Xariun, Gun Metal Games, Scott Marner, Spencer Sherman, Valerii Matskevych, Alloyed Clavicle, Stewart Walsh, Ruthlyn Mollett (Javan), Benjamin Mair-Pratt, Diagonath, Alexander Thomas, Ashley Wilson-Savoury, William Henry, Preston Brooks, JOSHUA QUALTIERI and many others!</p>
|
<p style="display:none">Supporters: Aaron Meyer, Ahmad Amerih, AstralJacks, aymeric, Billy Dean Goehring, Branndon Edwards,
|
||||||
|
Chase Mayers, Curt Flood, cyninge, Dino Princip, E.M. White, es, Fondue, Fritjof Olsson, Gatsu, Johan Fröberg, Jonathan Moore,
|
||||||
|
Joseph Miranda, Kate, KC138, Luke Nelson, Markus Finster, Massimo Vella, Mikey, Nathan Mitchell, Paavi1, Pat, Ryan Westcott,
|
||||||
|
Sasquatch, Shawn Spencer, Sizz_TV, Timothée CALLET, UTG community, Vlad Tomash, Wil Sisney, William Merriott, Xariun,
|
||||||
|
Gun Metal Games, Scott Marner, Spencer Sherman, Valerii Matskevych, Alloyed Clavicle, Stewart Walsh, Ruthlyn Mollett (Javan),
|
||||||
|
Benjamin Mair-Pratt, Diagonath, Alexander Thomas, Ashley Wilson-Savoury, William Henry, Preston Brooks, JOSHUA QUALTIERI,
|
||||||
|
Hilton Williams, Katharina Haase, Hisham Bedri, Ian arless, Karnat, Bird, Kevin and many others!</p>
|
||||||
|
|
||||||
<ul class="share-buttons">
|
<ul class="share-buttons">
|
||||||
<li><a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator%2F"e=" data-tip="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="images/Facebook.png" /></a></li>
|
<li><a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator%2F"e=" data-tip="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="images/Facebook.png" /></a></li>
|
||||||
|
|
@ -1813,15 +1832,23 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="sticked">
|
<div id="sticked">
|
||||||
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F7">New Map</button>
|
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F2">New Map</button>
|
||||||
<button id="saveButton" data-tip="Select file format to save map">Save as</button>
|
<button id="saveButton" data-tip="Select file format to save map">Save</button>
|
||||||
<div id="saveDropdown">
|
<div id="saveDropdown">
|
||||||
<div id="saveMap" data-tip="Save as fully functional map in .map format. Shortcut: Ctrl + M">.map</div>
|
<div id="saveMap" data-tip="Download the map as fully functional .map file to your machine. Shortcut: Ctrl + M">.map</div>
|
||||||
<div id="saveSVG" data-tip="Download the map as .svg image (open in browser or Inkscape). Shortcut: Ctrl + S">.svg</div>
|
<div id="saveSVG" data-tip="Download the map as vector image (open in browser or Inkscape). Shortcut: Ctrl + S">.svg</div>
|
||||||
<div id="savePNG" data-tip="Download visible part of the map as image. Texture will not be shown. Shortcut: Ctrl + P">.png</div>
|
<div id="savePNG" data-tip="Download visible part of the map as .png image. Shortcut: Ctrl + P">.png</div>
|
||||||
<div id="saveGeo" data-tip="Download cell data as GeoJSON (for use in GIS tools such as QGIS).">.json</div>
|
<div id="saveGeo" data-tip="Download cell data as GeoJSON (for use in GIS tools such as QGIS).">.json</div>
|
||||||
|
<div id="quickSave" data-tip="Save map to browser storage. Shortcut: F6">storage</div>
|
||||||
|
<!-- <div id="saveDropbox" data-tip="Save fully functional .map file to Dropbox. Shortcut: Ctrl + B">Dropbox</div> -->
|
||||||
|
</div>
|
||||||
|
<button id="loadButton" data-tip="Load fully functional map in a .map format">Load</button>
|
||||||
|
<div id="loadDropdown">
|
||||||
|
<div id="loadMap" data-tip="Load .map file from local disk. Shortcut: Ctrl + L">from local disk</div>
|
||||||
|
<div id="loadURL" data-tip="Load .map file from URL (server should allow CORS). Shortcut: Ctrl + U">from URL</div>
|
||||||
|
<div id="quickLoad" data-tip="Load map from browser storage (if saved before). Shortcut: F9">from storage</div>
|
||||||
|
<!-- <div id="loadDropbox" data-tip="Load .map file from Dropbox. Shortcut: Ctrl + D">from Dropbox</div> -->
|
||||||
</div>
|
</div>
|
||||||
<button id="loadMap" data-tip="Load fully functional map in a .map format. Shortcut: Ctrl + L">Load</button>
|
|
||||||
<button id="zoomReset" data-tip="Reset map zoom. Shortcut: 0">Reset Zoom</button>
|
<button id="zoomReset" data-tip="Reset map zoom. Shortcut: 0">Reset Zoom</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1995,7 +2022,7 @@
|
||||||
<span id="riverReset" data-tip="Reset transformation to default" class="icon-cancel pointer"></span>
|
<span id="riverReset" data-tip="Reset transformation to default" class="icon-cancel pointer"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="riverLength" data-tip="Route length in selected units">0</button>
|
<button id="riverLength" data-tip="River length in selected units">0</button>
|
||||||
<button id="riverCopy" data-tip="Copy river" class="icon-clone"></button>
|
<button id="riverCopy" data-tip="Copy river" class="icon-clone"></button>
|
||||||
<button id="riverNew" data-tip="Create new river clicking on map" class="icon-map-pin"></button>
|
<button id="riverNew" data-tip="Create new river clicking on map" class="icon-map-pin"></button>
|
||||||
<button id="riverLegend" data-tip="Edit free text notes (legend) for the river" class="icon-edit"></button>
|
<button id="riverLegend" data-tip="Edit free text notes (legend) for the river" class="icon-edit"></button>
|
||||||
|
|
@ -2011,7 +2038,7 @@
|
||||||
<span id="routeGroupAdd" data-tip="Create new group for this route" class="icon-plus pointer"></span>
|
<span id="routeGroupAdd" data-tip="Create new group for this route" class="icon-plus pointer"></span>
|
||||||
<span id="routeGroupRemove" data-tip="Remove all routes of this group" class="icon-trash-empty pointer"></span>
|
<span id="routeGroupRemove" data-tip="Remove all routes of this group" class="icon-trash-empty pointer"></span>
|
||||||
</div>
|
</div>
|
||||||
<button id="routeLength" data-tip="River length in selected units">0</button>
|
<button id="routeLength" data-tip="Route length in selected units">0</button>
|
||||||
<button id="routeSplit" data-tip="Click on a control point to split the route" class="icon-unlink"></button>
|
<button id="routeSplit" data-tip="Click on a control point to split the route" class="icon-unlink"></button>
|
||||||
<button id="routeLegend" data-tip="Edit free text notes (legend) for the route" class="icon-edit"></button>
|
<button id="routeLegend" data-tip="Edit free text notes (legend) for the route" class="icon-edit"></button>
|
||||||
<button id="routeNew" data-tip="Create new route clicking on map" class="icon-map-pin"></button>
|
<button id="routeNew" data-tip="Create new route clicking on map" class="icon-map-pin"></button>
|
||||||
|
|
@ -2941,11 +2968,12 @@
|
||||||
<script src="libs/polylabel.min.js"></script>
|
<script src="libs/polylabel.min.js"></script>
|
||||||
<script src="libs/jquery-ui.min.js"></script>
|
<script src="libs/jquery-ui.min.js"></script>
|
||||||
<script src="libs/seedrandom.min.js"></script>
|
<script src="libs/seedrandom.min.js"></script>
|
||||||
|
|
||||||
<script src="modules/ui/layers.js"></script>
|
<script src="modules/ui/layers.js"></script>
|
||||||
|
|
||||||
<script defer src="modules/ui/general.js"></script>
|
<script defer src="modules/ui/general.js"></script>
|
||||||
<script defer src="modules/ui/options.js"></script>
|
<script defer src="modules/ui/options.js"></script>
|
||||||
<script defer src="modules/ui/measurers.js"></script>
|
<script defer src="modules/ui/measurers.js"></script>
|
||||||
|
<script defer src="modules/save-and-load.js"></script>
|
||||||
<script defer src="main.js?version=1.0"></script>
|
<script defer src="main.js?version=1.0"></script>
|
||||||
<script defer src="modules/relief-icons.js"></script>
|
<script defer src="modules/relief-icons.js"></script>
|
||||||
<script defer src="modules/ui/tools.js"></script>
|
<script defer src="modules/ui/tools.js"></script>
|
||||||
|
|
@ -2970,7 +2998,7 @@
|
||||||
<script defer src="modules/ui/diplomacy-editor.js"></script>
|
<script defer src="modules/ui/diplomacy-editor.js"></script>
|
||||||
<script defer src="modules/ui/zones-editor.js"></script>
|
<script defer src="modules/ui/zones-editor.js"></script>
|
||||||
<script defer src="modules/ui/editors.js"></script>
|
<script defer src="modules/ui/editors.js"></script>
|
||||||
<script defer src="modules/save-and-load.js"></script>
|
|
||||||
<script defer src="libs/quantize.min.js"></script>
|
<script defer src="libs/quantize.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="https://www.dropbox.com/static/api/2/dropins.js" id="dropboxjs" data-app-key="a0ls5yab5ly1ot5"></script> -->
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
428
main.js
428
main.js
|
|
@ -8,7 +8,13 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
const version = "1.0"; // generator version
|
const version = "1.0"; // generator version
|
||||||
document.title += " v " + version;
|
document.title += " v" + version;
|
||||||
|
|
||||||
|
// if map version is not stored, clear localStorage and show a message
|
||||||
|
if (localStorage.getItem("version") != version) {
|
||||||
|
localStorage.clear();
|
||||||
|
setTimeout(showWelcomeMessage, 8000);
|
||||||
|
}
|
||||||
|
|
||||||
// append svg layers (in default order)
|
// append svg layers (in default order)
|
||||||
let svg = d3.select("#map");
|
let svg = d3.select("#map");
|
||||||
|
|
@ -81,9 +87,8 @@ population.append("g").attr("id", "urban");
|
||||||
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
|
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
|
||||||
|
|
||||||
// assign events separately as not a viewbox child
|
// assign events separately as not a viewbox child
|
||||||
scaleBar.on("mousemove", function() {tip("Click to open Units Editor")});
|
scaleBar.on("mousemove", () => tip("Click to open Units Editor"));
|
||||||
legend.on("mousemove", function() {tip("Drag to change the position. Click to hide the legend")})
|
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
|
||||||
.on("click", () => clearLegend());
|
|
||||||
|
|
||||||
// main data variables
|
// main data variables
|
||||||
let grid = {}; // initial grapg based on jittered square grid and data
|
let grid = {}; // initial grapg based on jittered square grid and data
|
||||||
|
|
@ -111,61 +116,169 @@ landmass.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr
|
||||||
oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
||||||
oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
||||||
|
|
||||||
applyDefaultNamesData(); // apply default namesbase on load
|
applyDefaultNamesData(); // always apply default namesbase load as namesdata is not stored in .map file
|
||||||
applyDefaultStyle(); // apply style on load
|
void function removeLoading() {
|
||||||
generate(); // generate map on load
|
d3.select("#loading").transition().duration(5000).style("opacity", 0).remove();
|
||||||
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
|
d3.select("#initial").transition().duration(5000).attr("opacity", 0).remove();
|
||||||
applyPreset(); // apply saved layers preset on load
|
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1);
|
||||||
|
d3.select("#tooltip").transition().duration(3000).style("opacity", 1);
|
||||||
|
}()
|
||||||
|
|
||||||
// show message on load if required
|
// decide which map should be loaded or generated on page load
|
||||||
setTimeout(showWelcomeMessage, 7000);
|
void function checkLoadParameters() {
|
||||||
function showWelcomeMessage() {
|
const url = new URL(window.location.href);
|
||||||
// Changelog dialog window
|
const params = url.searchParams;
|
||||||
if (localStorage.getItem("version") != version) {
|
|
||||||
const link = 'https://www.reddit.com/r/FantasyMapGenerator/comments/cxu1c5/update_new_version_is_published_v_10'; // announcement on Reddit
|
|
||||||
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
|
|
||||||
|
|
||||||
This version is compatible with versions 0.8b and 0.9b, but not with older .map files.
|
// of there is a valid maplink, try to load .map file from URL
|
||||||
Please use an <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog' target='_blank'>archived version</a> to open old files.
|
if (params.get("maplink")) {
|
||||||
|
console.warn("Load map from URL");
|
||||||
|
const maplink = params.get("maplink");
|
||||||
|
const pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
|
||||||
|
const valid = pattern.test(maplink);
|
||||||
|
if (valid) {loadMapFromURL(maplink, 1); return;}
|
||||||
|
else showUploadErrorMessage("Map link is not a valid URL", maplink);
|
||||||
|
}
|
||||||
|
|
||||||
<ul><a href=${link} target='_blank'>Main changes:</a>
|
// if there is a seed (user of MFCG provided), generate map for it
|
||||||
<li>Provinces and Provinces Editor</li>
|
if (params.get("seed")) {
|
||||||
<li>Religions Layer and Religions Editor</li>
|
console.warn("Generate map for seed");
|
||||||
<li>Full state names (state types)</li>
|
generateMapOnLoad();
|
||||||
<li>Multi-lined labels</li>
|
return;
|
||||||
<li>State relations (diplomacy)</li>
|
}
|
||||||
<li>Custom layers (zones)</li>
|
|
||||||
<li>Places of interest (auto-added markers)</li>
|
|
||||||
<li>New color picker and hatching fill</li>
|
|
||||||
<li>Legend boxes</li>
|
|
||||||
<li>World Configurator presets</li>
|
|
||||||
<li>Improved state labels placement</li>
|
|
||||||
<li>Relief icons sets</li>
|
|
||||||
<li>Fogging</li>
|
|
||||||
<li>Custom layer presets</li>
|
|
||||||
<li>Custom biomes</li>
|
|
||||||
<li>State, province and burg COAs</li>
|
|
||||||
<li>Desktop version (see <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A#is-there-a-desktop-version' target='_blank'>here)</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Join our <a href='https://www.reddit.com/r/FantasyMapGenerator' target='_blank'>Reddit community</a> and
|
// open latest map if option is active and map is stored
|
||||||
<a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a>
|
if (onloadMap.value === "saved") {
|
||||||
to ask questions, share maps, discuss the Generator, report bugs and propose new features.</p>
|
ldb.get("lastMap", blob => {
|
||||||
|
if (blob) {
|
||||||
<p>Thanks for all supporters on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>!</i></p>`;
|
console.warn("Load last saved map");
|
||||||
|
try {
|
||||||
$("#alert").dialog(
|
uploadFile(blob);
|
||||||
{resizable: false, title: "Fantasy Map Generator update", width: 310,
|
|
||||||
buttons: {
|
|
||||||
OK: function() {
|
|
||||||
localStorage.clear();
|
|
||||||
localStorage.setItem("version", version);
|
|
||||||
$(this).dialog("close");
|
|
||||||
}
|
}
|
||||||
},
|
catch(error) {
|
||||||
position: {my: "center", at: "center", of: "svg"}
|
console.error(error);
|
||||||
|
console.warn("Cannot load stored map, random map to be generated");
|
||||||
|
generateMapOnLoad();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("No map stored, random map to be generated");
|
||||||
|
generateMapOnLoad();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("Generate random map");
|
||||||
|
generateMapOnLoad();
|
||||||
|
}()
|
||||||
|
|
||||||
|
function loadMapFromURL(maplink, random) {
|
||||||
|
const URL = decodeURIComponent(maplink);
|
||||||
|
|
||||||
|
fetch(URL, {method: 'GET', mode: 'cors'})
|
||||||
|
.then(response => {
|
||||||
|
if(response.ok) return response.blob();
|
||||||
|
throw new Error("Cannot load map from URL");
|
||||||
|
}).then(blob => uploadFile(blob))
|
||||||
|
.catch(error => {
|
||||||
|
showUploadErrorMessage(error.message, URL, random);
|
||||||
|
if (random) generateMapOnLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUploadErrorMessage(error, URL, random) {
|
||||||
|
console.error(error);
|
||||||
|
alertMessage.innerHTML = `
|
||||||
|
Cannot load map from the <a href='${URL}' target='_blank'>link provided</a>.
|
||||||
|
${random?`A new random map is generated. `:''}
|
||||||
|
Please ensure the linked file is reachable and CORS is allowed on server side`;
|
||||||
|
$("#alert").dialog({title: "Loading error", width: 320, buttons: {OK: function() {$(this).dialog("close");}}});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateMapOnLoad() {
|
||||||
|
applyDefaultStyle(); // apply style
|
||||||
|
generate(); // generate map
|
||||||
|
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
|
||||||
|
applyPreset(); // apply saved layers preset
|
||||||
|
}
|
||||||
|
|
||||||
|
// focus on coordinates, cell or burg provided in searchParams
|
||||||
|
function focusOn() {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const params = url.searchParams;
|
||||||
|
|
||||||
|
if (params.get("from") === "MFCG") {
|
||||||
|
if (params.get("seed").length === 13) {
|
||||||
|
// show back burg from MFCG
|
||||||
|
params.set("burg", params.get("seed").slice(-4));
|
||||||
|
} else {
|
||||||
|
// select burg for MFCG
|
||||||
|
findBurgForMFCG(params);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = +params.get("scale") || 8;
|
||||||
|
let x = +params.get("x");
|
||||||
|
let y = +params.get("y");
|
||||||
|
|
||||||
|
const c = +params.get("cell");
|
||||||
|
if (c) {
|
||||||
|
x = pack.cells.p[c][0];
|
||||||
|
y = pack.cells.p[c][1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const b = +params.get("burg");
|
||||||
|
if (b && pack.burgs[b]) {
|
||||||
|
x = pack.burgs[b].x;
|
||||||
|
y = pack.burgs[b].y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x && y) zoomTo(x, y, s, 1600);
|
||||||
|
}
|
||||||
|
|
||||||
|
// find burg for MFCG and focus on it
|
||||||
|
function findBurgForMFCG(params) {
|
||||||
|
const cells = pack.cells, burgs = pack.burgs;
|
||||||
|
if (pack.burgs.length < 2) {console.error("Cannot select a burg for MFCG"); return;}
|
||||||
|
|
||||||
|
const size = +params.get("size");
|
||||||
|
const name = params.get("name");
|
||||||
|
let coast = +params.get("coast");
|
||||||
|
let port = +params.get("port");
|
||||||
|
let river = +params.get("river");
|
||||||
|
|
||||||
|
let selection = defineSelection(coast, port, river);
|
||||||
|
if (!selection.length) selection = defineSelection(coast, !port, !river);
|
||||||
|
if (!selection.length) selection = defineSelection(!coast, 0, !river);
|
||||||
|
if (!selection.length) selection = [burgs[1]]; // select first if nothing is found
|
||||||
|
|
||||||
|
function defineSelection(coast, port, river) {
|
||||||
|
if (port && river) return burgs.filter(b => b.port && cells.r[b.cell]);
|
||||||
|
if (!port && coast && river) return burgs.filter(b => !b.port && cells.t[b.cell] === 1 && cells.r[b.cell]);
|
||||||
|
if (!coast && !river) return burgs.filter(b => cells.t[b.cell] !== 1 && !cells.r[b.cell]);
|
||||||
|
if (!coast && river) return burgs.filter(b => cells.t[b.cell] !== 1 && cells.r[b.cell]);
|
||||||
|
if (coast && river) return burgs.filter(b => cells.t[b.cell] === 1 && cells.r[b.cell]);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// select a burg with closest population from selection
|
||||||
|
const selected = d3.scan(selection, (a, b) => Math.abs(a.population - size) - Math.abs(b.population - size));
|
||||||
|
const b = selection[selected].i;
|
||||||
|
if (!b) {console.error("Cannot select a burg for MFCG"); return;}
|
||||||
|
if (size) burgs[b].population = size;
|
||||||
|
if (name) burgs[b].name = name;
|
||||||
|
|
||||||
|
const label = burgLabels.select("[data-id='" + b + "']");
|
||||||
|
if (label.size()) {
|
||||||
|
tip("Here stands the glorious city of " + burgs[b].name, true, "success", 12000);
|
||||||
|
label.text(burgs[b].name).classed("drag", true).on("mouseover", function() {
|
||||||
|
d3.select(this).classed("drag", false);
|
||||||
|
label.on("mouseover", null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoomTo(burgs[b].x, burgs[b].y, 8, 1600);
|
||||||
|
invokeActiveZooming();
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyDefaultNamesData() {
|
function applyDefaultNamesData() {
|
||||||
|
|
@ -364,83 +477,45 @@ function applyDefaultStyle() {
|
||||||
fogging.attr("opacity", .8).attr("fill", "#000000").attr("stroke-width", 5);
|
fogging.attr("opacity", .8).attr("fill", "#000000").attr("stroke-width", 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// focus on coordinates, cell or burg provided in searchParams
|
function showWelcomeMessage() {
|
||||||
function focusOn() {
|
const link = 'https://www.reddit.com/r/FantasyMapGenerator/comments/cxu1c5/update_new_version_is_published_v_10'; // announcement on Reddit
|
||||||
const url = new URL(window.location.href);
|
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
|
||||||
const params = url.searchParams;
|
|
||||||
|
|
||||||
if (params.get("from") === "MFCG") {
|
This version is compatible with versions 0.8b and 0.9b, but not with older .map files.
|
||||||
if (params.get("seed").length === 13) {
|
Please use an <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog' target='_blank'>archived version</a> to open old files.
|
||||||
// show back burg from MFCG
|
|
||||||
params.set("burg", params.get("seed").slice(-4));
|
|
||||||
} else {
|
|
||||||
// select burg for MFCG
|
|
||||||
findBurgForMFCG(params);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const s = +params.get("scale") || 8;
|
<ul><a href=${link} target='_blank'>Main changes:</a>
|
||||||
let x = +params.get("x");
|
<li>Provinces and Provinces Editor</li>
|
||||||
let y = +params.get("y");
|
<li>Religions Layer and Religions Editor</li>
|
||||||
|
<li>Full state names (state types)</li>
|
||||||
|
<li>Multi-lined labels</li>
|
||||||
|
<li>State relations (diplomacy)</li>
|
||||||
|
<li>Custom layers (zones)</li>
|
||||||
|
<li>Places of interest (auto-added markers)</li>
|
||||||
|
<li>New color picker and hatching fill</li>
|
||||||
|
<li>Legend boxes</li>
|
||||||
|
<li>World Configurator presets</li>
|
||||||
|
<li>Improved state labels placement</li>
|
||||||
|
<li>Relief icons sets</li>
|
||||||
|
<li>Fogging</li>
|
||||||
|
<li>Custom layer presets</li>
|
||||||
|
<li>Custom biomes</li>
|
||||||
|
<li>State, province and burg COAs</li>
|
||||||
|
<li>Desktop version (see <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A#is-there-a-desktop-version' target='_blank'>here)</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
const c = +params.get("cell");
|
<p>Join our <a href='https://www.reddit.com/r/FantasyMapGenerator' target='_blank'>Reddit community</a> and
|
||||||
if (c) {
|
<a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a>
|
||||||
x = pack.cells.p[c][0];
|
to ask questions, share maps, discuss the Generator, report bugs and propose new features.</p>
|
||||||
y = pack.cells.p[c][1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const b = +params.get("burg");
|
<p>Thanks for all supporters on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>!</i></p>`;
|
||||||
if (b && pack.burgs[b]) {
|
|
||||||
x = pack.burgs[b].x;
|
|
||||||
y = pack.burgs[b].y;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x && y) zoomTo(x, y, s, 1600);
|
$("#alert").dialog(
|
||||||
}
|
{resizable: false, title: "Fantasy Map Generator update", width: 310,
|
||||||
|
buttons: {OK: function() {$(this).dialog("close")}},
|
||||||
// find burg for MFCG and focus on it
|
position: {my: "center", at: "center", of: "svg"},
|
||||||
function findBurgForMFCG(params) {
|
close: () => localStorage.setItem("version", version)}
|
||||||
const cells = pack.cells, burgs = pack.burgs;
|
);
|
||||||
if (pack.burgs.length < 2) {console.error("Cannot select a burg for MFCG"); return;}
|
|
||||||
|
|
||||||
const size = +params.get("size");
|
|
||||||
let coast = +params.get("coast");
|
|
||||||
let port = +params.get("port");
|
|
||||||
let river = +params.get("river");
|
|
||||||
|
|
||||||
let selection = defineSelection(coast, port, river);
|
|
||||||
if (!selection.length) selection = defineSelection(coast, !port, !river);
|
|
||||||
if (!selection.length) selection = defineSelection(!coast, 0, !river);
|
|
||||||
if (!selection.length) selection = [burgs[1]]; // select first if nothing is found
|
|
||||||
|
|
||||||
function defineSelection(coast, port, river) {
|
|
||||||
if (port && river) return burgs.filter(b => b.port && cells.r[b.cell]);
|
|
||||||
if (!port && coast && river) return burgs.filter(b => !b.port && cells.t[b.cell] === 1 && cells.r[b.cell]);
|
|
||||||
if (!coast && !river) return burgs.filter(b => cells.t[b.cell] !== 1 && !cells.r[b.cell]);
|
|
||||||
if (!coast && river) return burgs.filter(b => cells.t[b.cell] !== 1 && cells.r[b.cell]);
|
|
||||||
if (coast && river) return burgs.filter(b => cells.t[b.cell] === 1 && cells.r[b.cell]);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// select a burg with closest population from selection
|
|
||||||
const selected = d3.scan(selection, (a, b) => Math.abs(a.population - size) - Math.abs(b.population - size));
|
|
||||||
const b = selection[selected].i;
|
|
||||||
if (!b) {console.error("Cannot select a burg for MFCG"); return;}
|
|
||||||
if (size) burgs[b].population = size;
|
|
||||||
|
|
||||||
const label = burgLabels.select("[data-id='" + b + "']");
|
|
||||||
if (label.size()) {
|
|
||||||
tip("Here stands the glorious city of " + burgs[b].name, true, "error");
|
|
||||||
label.classed("drag", true).on("mouseover", function() {
|
|
||||||
d3.select(this).classed("drag", false);
|
|
||||||
label.on("mouseover", null);
|
|
||||||
tip("", true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomTo(burgs[b].x, burgs[b].y, 8, 1600);
|
|
||||||
invokeActiveZooming();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomed() {
|
function zoomed() {
|
||||||
|
|
@ -561,6 +636,7 @@ void function addDragToUpload() {
|
||||||
}
|
}
|
||||||
// all good - show uploading text and load the map
|
// all good - show uploading text and load the map
|
||||||
$("#map-dragged > p").text("Uploading<span>.</span><span>.</span><span>.</span>");
|
$("#map-dragged > p").text("Uploading<span>.</span><span>.</span><span>.</span>");
|
||||||
|
closeDialogs();
|
||||||
uploadFile(file, function onUploadFinish() {
|
uploadFile(file, function onUploadFinish() {
|
||||||
$("#map-dragged > p").text("Drop to upload");
|
$("#map-dragged > p").text("Drop to upload");
|
||||||
});
|
});
|
||||||
|
|
@ -568,45 +644,65 @@ void function addDragToUpload() {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
function generate() {
|
function generate() {
|
||||||
const timeStart = performance.now();
|
try {
|
||||||
console.time("TOTAL");
|
const timeStart = performance.now();
|
||||||
invokeActiveZooming();
|
invokeActiveZooming();
|
||||||
generateSeed();
|
generateSeed();
|
||||||
console.group("Map " + seed);
|
console.group("Generated Map " + seed);
|
||||||
applyMapSize();
|
applyMapSize();
|
||||||
randomizeOptions();
|
randomizeOptions();
|
||||||
placePoints();
|
placePoints();
|
||||||
calculateVoronoi(grid, grid.points);
|
calculateVoronoi(grid, grid.points);
|
||||||
drawScaleBar();
|
drawScaleBar();
|
||||||
HeightmapGenerator.generate();
|
HeightmapGenerator.generate();
|
||||||
markFeatures();
|
markFeatures();
|
||||||
openNearSeaLakes();
|
openNearSeaLakes();
|
||||||
OceanLayers();
|
OceanLayers();
|
||||||
calculateMapCoordinates();
|
calculateMapCoordinates();
|
||||||
calculateTemperatures();
|
calculateTemperatures();
|
||||||
generatePrecipitation();
|
generatePrecipitation();
|
||||||
reGraph();
|
reGraph();
|
||||||
drawCoastline();
|
drawCoastline();
|
||||||
|
|
||||||
elevateLakes();
|
elevateLakes();
|
||||||
Rivers.generate();
|
Rivers.generate();
|
||||||
defineBiomes();
|
defineBiomes();
|
||||||
|
|
||||||
rankCells();
|
rankCells();
|
||||||
Cultures.generate();
|
Cultures.generate();
|
||||||
Cultures.expand();
|
Cultures.expand();
|
||||||
BurgsAndStates.generate();
|
BurgsAndStates.generate();
|
||||||
Religions.generate();
|
Religions.generate();
|
||||||
|
|
||||||
drawStates();
|
drawStates();
|
||||||
drawBorders();
|
drawBorders();
|
||||||
BurgsAndStates.drawStateLabels();
|
BurgsAndStates.drawStateLabels();
|
||||||
addZone();
|
addZone();
|
||||||
addMarkers();
|
addMarkers();
|
||||||
|
|
||||||
|
console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`);
|
||||||
|
showStatistics();
|
||||||
|
console.groupEnd("Generated Map " + seed);
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
console.error(error);
|
||||||
|
clearMainTip();
|
||||||
|
|
||||||
|
const regex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||||
|
const errorNoURL = error.stack.replace(regex, url => '<i>' + last(url.split("/")) + '</i>');
|
||||||
|
const errorParsed = errorNoURL.replace(/at /ig, "<br> at ");
|
||||||
|
|
||||||
|
alertMessage.innerHTML = `An error is occured on map generation. Please retry.
|
||||||
|
<br>If error persists, clear the stored data and try again.
|
||||||
|
<p id="errorBox">${errorParsed}</p>`;
|
||||||
|
$("#alert").dialog({
|
||||||
|
resizable: false, title: "Generation error", maxWidth:500, buttons: {
|
||||||
|
"Clear data": function() {localStorage.clear(); localStorage.setItem("version", version);},
|
||||||
|
Regenerate: function() {regenerateMap(); $(this).dialog("close");}
|
||||||
|
}, position: {my: "center", at: "center", of: "svg"}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`);
|
|
||||||
showStatistics();
|
|
||||||
console.groupEnd("Map " + seed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate map seed (string!) or get it from URL searchParams
|
// generate map seed (string!) or get it from URL searchParams
|
||||||
|
|
@ -964,13 +1060,16 @@ function drawCoastline() {
|
||||||
if (start === -1) continue; // cannot start here
|
if (start === -1) continue; // cannot start here
|
||||||
const connectedVertices = connectVertices(start, type);
|
const connectedVertices = connectVertices(start, type);
|
||||||
used[f] = 1;
|
used[f] = 1;
|
||||||
const points = connectedVertices.map(v => vertices.p[v]);
|
let points = connectedVertices.map(v => vertices.p[v]);
|
||||||
|
const area = d3.polygonArea(points); // area with lakes/islands
|
||||||
|
if (area > 0 && features[f].type === "lake") points = points.reverse();
|
||||||
|
features[f].area = Math.abs(area);
|
||||||
|
|
||||||
const path = round(lineGen(points));
|
const path = round(lineGen(points));
|
||||||
const id = features[f].group + features[f].i;
|
const id = features[f].group + features[f].i;
|
||||||
if (features[f].type === "lake") {
|
if (features[f].type === "lake") {
|
||||||
landMask.append("path").attr("d", path).attr("fill", "black");
|
landMask.append("path").attr("d", path).attr("fill", "black");
|
||||||
//waterMask.append("path").attr("d", path).attr("fill", "white"); // uncomment to show over lakes
|
// waterMask.append("path").attr("d", path).attr("fill", "white"); // uncomment to show over lakes
|
||||||
lakes.select("#"+features[f].group).append("path").attr("d", path).attr("id", id); // draw the lake
|
lakes.select("#"+features[f].group).append("path").attr("d", path).attr("id", id); // draw the lake
|
||||||
} else {
|
} else {
|
||||||
landMask.append("path").attr("d", path).attr("fill", "white");
|
landMask.append("path").attr("d", path).attr("fill", "white");
|
||||||
|
|
@ -1006,9 +1105,9 @@ function drawCoastline() {
|
||||||
const c0 = c[0] >= n || cells.t[c[0]] === t;
|
const c0 = c[0] >= n || cells.t[c[0]] === t;
|
||||||
const c1 = c[1] >= n || cells.t[c[1]] === t;
|
const c1 = c[1] >= n || cells.t[c[1]] === t;
|
||||||
const c2 = c[2] >= n || cells.t[c[2]] === t;
|
const c2 = c[2] >= n || cells.t[c[2]] === t;
|
||||||
if (v[0] !== prev && c0 !== c1) current = v[0];
|
if (v[0] !== prev && c0 !== c1) current = v[0]; else
|
||||||
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
if (v[1] !== prev && c1 !== c2) current = v[1]; else
|
||||||
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||||
if (current === chain[chain.length-1]) {console.error("Next vertex is not found"); break;}
|
if (current === chain[chain.length-1]) {console.error("Next vertex is not found"); break;}
|
||||||
}
|
}
|
||||||
chain.push(chain[0]); // push first vertex as the last one
|
chain.push(chain[0]); // push first vertex as the last one
|
||||||
|
|
@ -1389,6 +1488,7 @@ function showStatistics() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const regenerateMap = debounce(function() {
|
const regenerateMap = debounce(function() {
|
||||||
|
console.warn("Generate new random map");
|
||||||
closeDialogs("#worldConfigurator");
|
closeDialogs("#worldConfigurator");
|
||||||
customization = 0;
|
customization = 0;
|
||||||
undraw();
|
undraw();
|
||||||
|
|
|
||||||
|
|
@ -103,21 +103,22 @@
|
||||||
// place secondary settlements based on geo and economical evaluation
|
// place secondary settlements based on geo and economical evaluation
|
||||||
function placeTowns() {
|
function placeTowns() {
|
||||||
console.time('placeTowns');
|
console.time('placeTowns');
|
||||||
const score = new Int16Array(cells.s.map(s => s * gauss(1,3,0,20,3))); // cell score for towns placement
|
const score = new Int16Array(cells.s.map(s => s * gauss(1,3,0,20,3))); // a bit randomized cell score for towns placement
|
||||||
const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
const sorted = cells.i.filter(i => !cells.burg[i] && score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
||||||
|
|
||||||
const desiredNumber = manorsInput.value == 1000 ? rn(sorted.length / 8 / densityInput.value ** .8) : manorsInput.valueAsNumber;
|
const desiredNumber = manorsInput.value == 1000 ? rn(sorted.length / 8 / densityInput.value ** .8) : manorsInput.valueAsNumber;
|
||||||
const burgsNumber = Math.min(desiredNumber, sorted.length);
|
const burgsNumber = Math.min(desiredNumber, sorted.length); // towns to generate
|
||||||
let burgsAdded = 0;
|
let burgsAdded = 0;
|
||||||
|
|
||||||
const burgsTree = burgs[0];
|
const burgsTree = burgs[0];
|
||||||
let spacing = (graphWidth + graphHeight) / 150 / (burgsNumber ** .7 / 66); // min distance between towns
|
let spacing = (graphWidth + graphHeight) / 150 / (burgsNumber ** .7 / 66); // min distance between towns
|
||||||
|
|
||||||
while (burgsAdded < burgsNumber) {
|
while (burgsAdded < burgsNumber && spacing > 1) {
|
||||||
for (let i=0; burgsAdded < burgsNumber && i < sorted.length; i++) {
|
for (let i=0; burgsAdded < burgsNumber && i < sorted.length; i++) {
|
||||||
|
if (cells.burg[sorted[i]]) continue;
|
||||||
const cell = sorted[i], x = cells.p[cell][0], y = cells.p[cell][1];
|
const cell = sorted[i], x = cells.p[cell][0], y = cells.p[cell][1];
|
||||||
const s = spacing * gauss(1, .3, .2, 2, 2); // randomize to make the placement not uniform
|
const s = spacing * gauss(1, .3, .2, 2, 2); // randomize to make placement not uniform
|
||||||
if (cells.burg[cell] || burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
|
if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
|
||||||
const burg = burgs.length;
|
const burg = burgs.length;
|
||||||
const culture = cells.culture[cell];
|
const culture = cells.culture[cell];
|
||||||
const name = Names.getCulture(culture);
|
const name = Names.getCulture(culture);
|
||||||
|
|
@ -133,11 +134,7 @@
|
||||||
console.error(`Cannot place all burgs. Requested ${desiredNumber}, placed ${burgsAdded}`);
|
console.error(`Cannot place all burgs. Requested ${desiredNumber}, placed ${burgsAdded}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
//const min = d3.min(score.filter(s => s)), max = d3.max(score);
|
burgs[0] = {name:undefined}; // do not store burgsTree anymore
|
||||||
//terrs.selectAll("polygon").data(sorted).enter().append("polygon").attr("points", d => getPackPolygon(d)).attr("fill", d => color(1 - normalize(score[d], min, max)));
|
|
||||||
//labels.selectAll("text").data(sorted).enter().append("text").attr("x", d => cells.p[d][0]).attr("y", d => cells.p[d][1]).text(d => score[d]).attr("font-size", 2);
|
|
||||||
|
|
||||||
burgs[0] = {name:undefined};
|
|
||||||
console.timeEnd('placeTowns');
|
console.timeEnd('placeTowns');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +262,8 @@
|
||||||
const type = states[s].type;
|
const type = states[s].type;
|
||||||
|
|
||||||
cells.c[n].forEach(function(e) {
|
cells.c[n].forEach(function(e) {
|
||||||
|
if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells
|
||||||
|
|
||||||
const cultureCost = states[s].culture === cells.culture[e] ? -9 : 700;
|
const cultureCost = states[s].culture === cells.culture[e] ? -9 : 700;
|
||||||
const biomeCost = getBiomeCost(cells.road[e], b, cells.biome[e], type);
|
const biomeCost = getBiomeCost(cells.road[e], b, cells.biome[e], type);
|
||||||
const heightCost = getHeightCost(pack.features[cells.f[e]], cells.h[e], type);
|
const heightCost = getHeightCost(pack.features[cells.f[e]], cells.h[e], type);
|
||||||
|
|
@ -278,7 +277,6 @@
|
||||||
if (cells.h[e] >= 20) cells.state[e] = s; // assign state to cell
|
if (cells.h[e] >= 20) cells.state[e] = s; // assign state to cell
|
||||||
cost[e] = totalCost;
|
cost[e] = totalCost;
|
||||||
queue.queue({e, p:totalCost, s, b});
|
queue.queue({e, p:totalCost, s, b});
|
||||||
|
|
||||||
//const points = [cells.p[n][0], cells.p[n][1], (cells.p[n][0]+cells.p[e][0])/2, (cells.p[n][1]+cells.p[e][1])/2, cells.p[e][0], cells.p[e][1]];
|
//const points = [cells.p[n][0], cells.p[n][1], (cells.p[n][0]+cells.p[e][0])/2, (cells.p[n][1]+cells.p[e][1])/2, cells.p[e][0], cells.p[e][1]];
|
||||||
//debug.append("text").attr("x", (cells.p[n][0]+cells.p[e][0])/2 - 1).attr("y", (cells.p[n][1]+cells.p[e][1])/2 - 1).text(rn(totalCost-p)).attr("font-size", .8);
|
//debug.append("text").attr("x", (cells.p[n][0]+cells.p[e][0])/2 - 1).attr("y", (cells.p[n][1]+cells.p[e][1])/2 - 1).text(rn(totalCost-p)).attr("font-size", .8);
|
||||||
//debug.append("polyline").attr("points", points).attr("marker-mid", "url(#arrow)").attr("opacity", .6);
|
//debug.append("polyline").attr("points", points).attr("marker-mid", "url(#arrow)").attr("opacity", .6);
|
||||||
|
|
@ -822,7 +820,7 @@
|
||||||
cells.province = new Uint16Array(cells.i.length); // cell state
|
cells.province = new Uint16Array(cells.i.length); // cell state
|
||||||
const percentage = +provincesInput.value;
|
const percentage = +provincesInput.value;
|
||||||
if (states.length < 2 || !percentage) return; // no provinces
|
if (states.length < 2 || !percentage) return; // no provinces
|
||||||
const max = gauss(400, 50, 300, 500) / percentage ** .5; // max growth in 300-30 range
|
const max = percentage == 100 ? 1000 : gauss(20, 5, 5, 100) * percentage ** .5; // max growth
|
||||||
|
|
||||||
const forms = {
|
const forms = {
|
||||||
Monarchy:{County:11, Earldom:3, Shire:1, Landgrave:1, Margrave:1, Barony:1},
|
Monarchy:{County:11, Earldom:3, Shire:1, Landgrave:1, Margrave:1, Barony:1},
|
||||||
|
|
|
||||||
|
|
@ -321,65 +321,75 @@ function GFontToDataURI(url) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save in .map format
|
// prepare map data for saving
|
||||||
function saveMap() {
|
function getMapData() {
|
||||||
if (customization) {tip("Map cannot be saved when is in edit mode, please exit the mode and retry", false, "error"); return;}
|
if (customization) return false;
|
||||||
console.time("saveMap");
|
console.time("createMapDataBlob");
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const date = new Date();
|
||||||
|
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
|
||||||
|
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
|
||||||
|
const params = [version, license, dateString, seed, graphWidth, graphHeight].join("|");
|
||||||
|
const options = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value, heightUnit.value, heightExponentInput.value, temperatureScale.value,
|
||||||
|
barSize.value, barLabel.value, barBackOpacity.value, barBackColor.value, barPosX.value, barPosY.value, populationRate.value, urbanization.value,
|
||||||
|
mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value, temperaturePoleOutput.value, precOutput.value, JSON.stringify(winds)].join("|");
|
||||||
|
const coords = JSON.stringify(mapCoordinates);
|
||||||
|
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
|
||||||
|
const notesData = JSON.stringify(notes);
|
||||||
|
|
||||||
|
// set transform values to default
|
||||||
|
svg.attr("width", graphWidth).attr("height", graphHeight);
|
||||||
|
const transform = d3.zoomTransform(svg.node());
|
||||||
|
viewbox.attr("transform", null);
|
||||||
|
const svg_xml = (new XMLSerializer()).serializeToString(svg.node());
|
||||||
|
|
||||||
|
// restore initial values
|
||||||
|
svg.attr("width", svgWidth).attr("height", svgHeight);
|
||||||
|
zoom.transform(svg, transform);
|
||||||
|
|
||||||
|
const gridGeneral = JSON.stringify({spacing:grid.spacing, cellsX:grid.cellsX, cellsY:grid.cellsY, boundary:grid.boundary, points:grid.points, features:grid.features});
|
||||||
|
const features = JSON.stringify(pack.features);
|
||||||
|
const cultures = JSON.stringify(pack.cultures);
|
||||||
|
const states = JSON.stringify(pack.states);
|
||||||
|
const burgs = JSON.stringify(pack.burgs);
|
||||||
|
const religions = JSON.stringify(pack.religions);
|
||||||
|
const provinces = JSON.stringify(pack.provinces);
|
||||||
|
|
||||||
|
// data format as below
|
||||||
|
const data = [params, options, coords, biomes, notesData, svg_xml,
|
||||||
|
gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp,
|
||||||
|
features, cultures, states, burgs,
|
||||||
|
pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl,
|
||||||
|
pack.cells.pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state,
|
||||||
|
pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces].join("\r\n");
|
||||||
|
const blob = new Blob([data], {type: "text/plain"});
|
||||||
|
|
||||||
|
console.timeEnd("createMapDataBlob");
|
||||||
|
resolve(blob);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download .map file
|
||||||
|
async function saveMap() {
|
||||||
|
if (customization) {tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return;}
|
||||||
closeDialogs();
|
closeDialogs();
|
||||||
const date = new Date();
|
|
||||||
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
|
|
||||||
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
|
|
||||||
const params = [version, license, dateString, seed, graphWidth, graphHeight].join("|");
|
|
||||||
const options = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value, heightUnit.value, heightExponentInput.value, temperatureScale.value,
|
|
||||||
barSize.value, barLabel.value, barBackOpacity.value, barBackColor.value, barPosX.value, barPosY.value, populationRate.value, urbanization.value,
|
|
||||||
mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value, temperaturePoleOutput.value, precOutput.value, JSON.stringify(winds)].join("|");
|
|
||||||
const coords = JSON.stringify(mapCoordinates);
|
|
||||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
|
|
||||||
const notesData = JSON.stringify(notes);
|
|
||||||
|
|
||||||
// set transform values to default
|
const blob = await getMapData();
|
||||||
svg.attr("width", graphWidth).attr("height", graphHeight);
|
const URL = window.URL.createObjectURL(blob);
|
||||||
const transform = d3.zoomTransform(svg.node());
|
|
||||||
viewbox.attr("transform", null);
|
|
||||||
const svg_xml = (new XMLSerializer()).serializeToString(svg.node());
|
|
||||||
|
|
||||||
const gridGeneral = JSON.stringify({spacing:grid.spacing, cellsX:grid.cellsX, cellsY:grid.cellsY, boundary:grid.boundary, points:grid.points, features:grid.features});
|
|
||||||
const features = JSON.stringify(pack.features);
|
|
||||||
const cultures = JSON.stringify(pack.cultures);
|
|
||||||
const states = JSON.stringify(pack.states);
|
|
||||||
const burgs = JSON.stringify(pack.burgs);
|
|
||||||
const religions = JSON.stringify(pack.religions);
|
|
||||||
const provinces = JSON.stringify(pack.provinces);
|
|
||||||
|
|
||||||
// data format as below
|
|
||||||
const data = [params, options, coords, biomes, notesData, svg_xml,
|
|
||||||
gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp,
|
|
||||||
features, cultures, states, burgs,
|
|
||||||
pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl,
|
|
||||||
pack.cells.pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state,
|
|
||||||
pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces].join("\r\n");
|
|
||||||
const dataBlob = new Blob([data], {type: "text/plain"});
|
|
||||||
const dataURL = window.URL.createObjectURL(dataBlob);
|
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.download = "fantasy_map_" + Date.now() + ".map";
|
link.download = "fantasy_map_" + Date.now() + ".map";
|
||||||
link.href = dataURL;
|
link.href = URL;
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check`, true, "warning");
|
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
||||||
|
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||||
// restore initial values
|
|
||||||
svg.attr("width", svgWidth).attr("height", svgHeight);
|
|
||||||
zoom.transform(svg, transform);
|
|
||||||
|
|
||||||
window.setTimeout(function() {
|
|
||||||
window.URL.revokeObjectURL(dataURL);
|
|
||||||
clearMainTip();
|
|
||||||
}, 3000);
|
|
||||||
console.timeEnd("saveMap");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFile(file, callback) {
|
function uploadFile(file, callback) {
|
||||||
console.time("loadMap");
|
uploadFile.timeStart = performance.now();
|
||||||
|
|
||||||
const fileReader = new FileReader();
|
const fileReader = new FileReader();
|
||||||
fileReader.onload = function(fileLoadedEvent) {
|
fileReader.onload = function(fileLoadedEvent) {
|
||||||
const dataLoaded = fileLoadedEvent.target.result;
|
const dataLoaded = fileLoadedEvent.target.result;
|
||||||
|
|
@ -413,281 +423,386 @@ function uploadFile(file, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseLoadedData(data) {
|
function parseLoadedData(data) {
|
||||||
closeDialogs();
|
try {
|
||||||
const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons
|
const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons
|
||||||
const hatching = document.getElementById("hatching").cloneNode(true); // save hatching
|
const hatching = document.getElementById("hatching").cloneNode(true); // save hatching
|
||||||
|
|
||||||
void function parseParameters() {
|
void function parseParameters() {
|
||||||
const params = data[0].split("|");
|
const params = data[0].split("|");
|
||||||
if (params[3]) {seed = params[3]; optionsSeed.value = seed;}
|
if (params[3]) {seed = params[3]; optionsSeed.value = seed;}
|
||||||
if (params[4]) graphWidth = +params[4];
|
if (params[4]) graphWidth = +params[4];
|
||||||
if (params[5]) graphHeight = +params[5];
|
if (params[5]) graphHeight = +params[5];
|
||||||
}()
|
}()
|
||||||
|
|
||||||
void function parseOptions() {
|
console.group("Loaded Map " + seed);
|
||||||
const options = data[1].split("|");
|
|
||||||
if (options[0]) applyOption(distanceUnitInput, options[0]);
|
|
||||||
if (options[1]) distanceScaleInput.value = distanceScaleOutput.value = options[1];
|
|
||||||
if (options[2]) areaUnit.value = options[2];
|
|
||||||
if (options[3]) applyOption(heightUnit, options[3]);
|
|
||||||
if (options[4]) heightExponentInput.value = heightExponentOutput.value = options[4];
|
|
||||||
if (options[5]) temperatureScale.value = options[5];
|
|
||||||
if (options[6]) barSize.value = barSizeOutput.value = options[6];
|
|
||||||
if (options[7] !== undefined) barLabel.value = options[7];
|
|
||||||
if (options[8] !== undefined) barBackOpacity.value = options[8];
|
|
||||||
if (options[9]) barBackColor.value = options[9];
|
|
||||||
if (options[10]) barPosX.value = options[10];
|
|
||||||
if (options[11]) barPosY.value = options[11];
|
|
||||||
if (options[12]) populationRate.value = populationRateOutput.value = options[12];
|
|
||||||
if (options[13]) urbanization.value = urbanizationOutput.value = options[13];
|
|
||||||
if (options[14]) mapSizeInput.value = mapSizeOutput.value = Math.max(Math.min(options[14], 100), 1);
|
|
||||||
if (options[15]) latitudeInput.value = latitudeOutput.value = Math.max(Math.min(options[15], 100), 0);
|
|
||||||
if (options[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = options[16];
|
|
||||||
if (options[17]) temperaturePoleInput.value = temperaturePoleOutput.value = options[17];
|
|
||||||
if (options[18]) precInput.value = precOutput.value = options[18];
|
|
||||||
if (options[19]) winds = JSON.parse(options[19]);
|
|
||||||
}()
|
|
||||||
|
|
||||||
void function parseConfiguration() {
|
void function parseOptions() {
|
||||||
if (data[2]) mapCoordinates = JSON.parse(data[2]);
|
const options = data[1].split("|");
|
||||||
if (data[4]) notes = JSON.parse(data[4]);
|
if (options[0]) applyOption(distanceUnitInput, options[0]);
|
||||||
|
if (options[1]) distanceScaleInput.value = distanceScaleOutput.value = options[1];
|
||||||
|
if (options[2]) areaUnit.value = options[2];
|
||||||
|
if (options[3]) applyOption(heightUnit, options[3]);
|
||||||
|
if (options[4]) heightExponentInput.value = heightExponentOutput.value = options[4];
|
||||||
|
if (options[5]) temperatureScale.value = options[5];
|
||||||
|
if (options[6]) barSize.value = barSizeOutput.value = options[6];
|
||||||
|
if (options[7] !== undefined) barLabel.value = options[7];
|
||||||
|
if (options[8] !== undefined) barBackOpacity.value = options[8];
|
||||||
|
if (options[9]) barBackColor.value = options[9];
|
||||||
|
if (options[10]) barPosX.value = options[10];
|
||||||
|
if (options[11]) barPosY.value = options[11];
|
||||||
|
if (options[12]) populationRate.value = populationRateOutput.value = options[12];
|
||||||
|
if (options[13]) urbanization.value = urbanizationOutput.value = options[13];
|
||||||
|
if (options[14]) mapSizeInput.value = mapSizeOutput.value = Math.max(Math.min(options[14], 100), 1);
|
||||||
|
if (options[15]) latitudeInput.value = latitudeOutput.value = Math.max(Math.min(options[15], 100), 0);
|
||||||
|
if (options[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = options[16];
|
||||||
|
if (options[17]) temperaturePoleInput.value = temperaturePoleOutput.value = options[17];
|
||||||
|
if (options[18]) precInput.value = precOutput.value = options[18];
|
||||||
|
if (options[19]) winds = JSON.parse(options[19]);
|
||||||
|
}()
|
||||||
|
|
||||||
const biomes = data[3].split("|");
|
void function parseConfiguration() {
|
||||||
biomesData = applyDefaultBiomesSystem();
|
if (data[2]) mapCoordinates = JSON.parse(data[2]);
|
||||||
biomesData.color = biomes[0].split(",");
|
if (data[4]) notes = JSON.parse(data[4]);
|
||||||
biomesData.habitability = biomes[1].split(",").map(h => +h);
|
|
||||||
biomesData.name = biomes[2].split(",");
|
|
||||||
|
|
||||||
// push custom biomes if any
|
const biomes = data[3].split("|");
|
||||||
for (let i=biomesData.i.length; i < biomesData.name.length; i++) {
|
biomesData = applyDefaultBiomesSystem();
|
||||||
biomesData.i.push(biomesData.i.length);
|
biomesData.color = biomes[0].split(",");
|
||||||
biomesData.iconsDensity.push(0);
|
biomesData.habitability = biomes[1].split(",").map(h => +h);
|
||||||
biomesData.icons.push([]);
|
biomesData.name = biomes[2].split(",");
|
||||||
biomesData.cost.push(50);
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
void function replaceSVG() {
|
// push custom biomes if any
|
||||||
svg.remove();
|
for (let i=biomesData.i.length; i < biomesData.name.length; i++) {
|
||||||
document.body.insertAdjacentHTML("afterbegin", data[5]);
|
biomesData.i.push(biomesData.i.length);
|
||||||
}()
|
biomesData.iconsDensity.push(0);
|
||||||
|
biomesData.icons.push([]);
|
||||||
|
biomesData.cost.push(50);
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
void function redefineElements() {
|
void function replaceSVG() {
|
||||||
svg = d3.select("#map");
|
svg.remove();
|
||||||
defs = svg.select("#deftemp");
|
document.body.insertAdjacentHTML("afterbegin", data[5]);
|
||||||
viewbox = svg.select("#viewbox");
|
}()
|
||||||
scaleBar = svg.select("#scaleBar");
|
|
||||||
legend = svg.select("#legend");
|
|
||||||
ocean = viewbox.select("#ocean");
|
|
||||||
oceanLayers = ocean.select("#oceanLayers");
|
|
||||||
oceanPattern = ocean.select("#oceanPattern");
|
|
||||||
lakes = viewbox.select("#lakes");
|
|
||||||
landmass = viewbox.select("#landmass");
|
|
||||||
texture = viewbox.select("#texture");
|
|
||||||
terrs = viewbox.select("#terrs");
|
|
||||||
biomes = viewbox.select("#biomes");
|
|
||||||
cells = viewbox.select("#cells");
|
|
||||||
gridOverlay = viewbox.select("#gridOverlay");
|
|
||||||
coordinates = viewbox.select("#coordinates");
|
|
||||||
compass = viewbox.select("#compass");
|
|
||||||
rivers = viewbox.select("#rivers");
|
|
||||||
terrain = viewbox.select("#terrain");
|
|
||||||
relig = viewbox.select("#relig");
|
|
||||||
cults = viewbox.select("#cults");
|
|
||||||
regions = viewbox.select("#regions");
|
|
||||||
statesBody = regions.select("#statesBody");
|
|
||||||
statesHalo = regions.select("#statesHalo");
|
|
||||||
provs = viewbox.select("#provs");
|
|
||||||
zones = viewbox.select("#zones");
|
|
||||||
borders = viewbox.select("#borders");
|
|
||||||
stateBorders = borders.select("#stateBorders");
|
|
||||||
provinceBorders = borders.select("#provinceBorders");
|
|
||||||
routes = viewbox.select("#routes");
|
|
||||||
roads = routes.select("#roads");
|
|
||||||
trails = routes.select("#trails");
|
|
||||||
searoutes = routes.select("#searoutes");
|
|
||||||
temperature = viewbox.select("#temperature");
|
|
||||||
coastline = viewbox.select("#coastline");
|
|
||||||
prec = viewbox.select("#prec");
|
|
||||||
population = viewbox.select("#population");
|
|
||||||
labels = viewbox.select("#labels");
|
|
||||||
icons = viewbox.select("#icons");
|
|
||||||
burgIcons = icons.select("#burgIcons");
|
|
||||||
anchors = icons.select("#anchors");
|
|
||||||
markers = viewbox.select("#markers");
|
|
||||||
ruler = viewbox.select("#ruler");
|
|
||||||
fogging = viewbox.select("#fogging");
|
|
||||||
debug = viewbox.select("#debug");
|
|
||||||
freshwater = lakes.select("#freshwater");
|
|
||||||
salt = lakes.select("#salt");
|
|
||||||
burgLabels = labels.select("#burgLabels");
|
|
||||||
}()
|
|
||||||
|
|
||||||
void function parseGridData() {
|
void function redefineElements() {
|
||||||
grid = JSON.parse(data[6]);
|
svg = d3.select("#map");
|
||||||
calculateVoronoi(grid, grid.points);
|
defs = svg.select("#deftemp");
|
||||||
grid.cells.h = Uint8Array.from(data[7].split(","));
|
viewbox = svg.select("#viewbox");
|
||||||
grid.cells.prec = Uint8Array.from(data[8].split(","));
|
scaleBar = svg.select("#scaleBar");
|
||||||
grid.cells.f = Uint16Array.from(data[9].split(","));
|
legend = svg.select("#legend");
|
||||||
grid.cells.t = Int8Array.from(data[10].split(","));
|
ocean = viewbox.select("#ocean");
|
||||||
grid.cells.temp = Int8Array.from(data[11].split(","));
|
oceanLayers = ocean.select("#oceanLayers");
|
||||||
}()
|
oceanPattern = ocean.select("#oceanPattern");
|
||||||
|
lakes = viewbox.select("#lakes");
|
||||||
|
landmass = viewbox.select("#landmass");
|
||||||
|
texture = viewbox.select("#texture");
|
||||||
|
terrs = viewbox.select("#terrs");
|
||||||
|
biomes = viewbox.select("#biomes");
|
||||||
|
cells = viewbox.select("#cells");
|
||||||
|
gridOverlay = viewbox.select("#gridOverlay");
|
||||||
|
coordinates = viewbox.select("#coordinates");
|
||||||
|
compass = viewbox.select("#compass");
|
||||||
|
rivers = viewbox.select("#rivers");
|
||||||
|
terrain = viewbox.select("#terrain");
|
||||||
|
relig = viewbox.select("#relig");
|
||||||
|
cults = viewbox.select("#cults");
|
||||||
|
regions = viewbox.select("#regions");
|
||||||
|
statesBody = regions.select("#statesBody");
|
||||||
|
statesHalo = regions.select("#statesHalo");
|
||||||
|
provs = viewbox.select("#provs");
|
||||||
|
zones = viewbox.select("#zones");
|
||||||
|
borders = viewbox.select("#borders");
|
||||||
|
stateBorders = borders.select("#stateBorders");
|
||||||
|
provinceBorders = borders.select("#provinceBorders");
|
||||||
|
routes = viewbox.select("#routes");
|
||||||
|
roads = routes.select("#roads");
|
||||||
|
trails = routes.select("#trails");
|
||||||
|
searoutes = routes.select("#searoutes");
|
||||||
|
temperature = viewbox.select("#temperature");
|
||||||
|
coastline = viewbox.select("#coastline");
|
||||||
|
prec = viewbox.select("#prec");
|
||||||
|
population = viewbox.select("#population");
|
||||||
|
labels = viewbox.select("#labels");
|
||||||
|
icons = viewbox.select("#icons");
|
||||||
|
burgIcons = icons.select("#burgIcons");
|
||||||
|
anchors = icons.select("#anchors");
|
||||||
|
markers = viewbox.select("#markers");
|
||||||
|
ruler = viewbox.select("#ruler");
|
||||||
|
fogging = viewbox.select("#fogging");
|
||||||
|
debug = viewbox.select("#debug");
|
||||||
|
freshwater = lakes.select("#freshwater");
|
||||||
|
salt = lakes.select("#salt");
|
||||||
|
burgLabels = labels.select("#burgLabels");
|
||||||
|
}()
|
||||||
|
|
||||||
void function parsePackData() {
|
void function parseGridData() {
|
||||||
pack = {};
|
grid = JSON.parse(data[6]);
|
||||||
reGraph();
|
calculateVoronoi(grid, grid.points);
|
||||||
reMarkFeatures();
|
grid.cells.h = Uint8Array.from(data[7].split(","));
|
||||||
pack.features = JSON.parse(data[12]);
|
grid.cells.prec = Uint8Array.from(data[8].split(","));
|
||||||
pack.cultures = JSON.parse(data[13]);
|
grid.cells.f = Uint16Array.from(data[9].split(","));
|
||||||
pack.states = JSON.parse(data[14]);
|
grid.cells.t = Int8Array.from(data[10].split(","));
|
||||||
pack.burgs = JSON.parse(data[15]);
|
grid.cells.temp = Int8Array.from(data[11].split(","));
|
||||||
pack.religions = data[29] ? JSON.parse(data[29]) : [{i: 0, name: "No religion"}];
|
}()
|
||||||
pack.provinces = data[30] ? JSON.parse(data[30]) : [0];
|
|
||||||
|
|
||||||
const cells = pack.cells;
|
void function parsePackData() {
|
||||||
cells.biome = Uint8Array.from(data[16].split(","));
|
pack = {};
|
||||||
cells.burg = Uint16Array.from(data[17].split(","));
|
reGraph();
|
||||||
cells.conf = Uint8Array.from(data[18].split(","));
|
reMarkFeatures();
|
||||||
cells.culture = Uint16Array.from(data[19].split(","));
|
pack.features = JSON.parse(data[12]);
|
||||||
cells.fl = Uint16Array.from(data[20].split(","));
|
pack.cultures = JSON.parse(data[13]);
|
||||||
cells.pop = Uint16Array.from(data[21].split(","));
|
pack.states = JSON.parse(data[14]);
|
||||||
cells.r = Uint16Array.from(data[22].split(","));
|
pack.burgs = JSON.parse(data[15]);
|
||||||
cells.road = Uint16Array.from(data[23].split(","));
|
pack.religions = data[29] ? JSON.parse(data[29]) : [{i: 0, name: "No religion"}];
|
||||||
cells.s = Uint16Array.from(data[24].split(","));
|
pack.provinces = data[30] ? JSON.parse(data[30]) : [0];
|
||||||
cells.state = Uint16Array.from(data[25].split(","));
|
|
||||||
cells.religion = data[26] ? Uint16Array.from(data[26].split(",")) : new Uint16Array(cells.i.length);
|
|
||||||
cells.province = data[27] ? Uint16Array.from(data[27].split(",")) : new Uint16Array(cells.i.length);
|
|
||||||
cells.crossroad = data[28] ? Uint16Array.from(data[28].split(",")) : new Uint16Array(cells.i.length);
|
|
||||||
}()
|
|
||||||
|
|
||||||
void function restoreLayersState() {
|
const cells = pack.cells;
|
||||||
if (texture.style("display") !== "none" && texture.select("image").size()) turnButtonOn("toggleTexture"); else turnButtonOff("toggleTexture");
|
cells.biome = Uint8Array.from(data[16].split(","));
|
||||||
if (terrs.selectAll("*").size()) turnButtonOn("toggleHeight"); else turnButtonOff("toggleHeight");
|
cells.burg = Uint16Array.from(data[17].split(","));
|
||||||
if (biomes.selectAll("*").size()) turnButtonOn("toggleBiomes"); else turnButtonOff("toggleBiomes");
|
cells.conf = Uint8Array.from(data[18].split(","));
|
||||||
if (cells.selectAll("*").size()) turnButtonOn("toggleCells"); else turnButtonOff("toggleCells");
|
cells.culture = Uint16Array.from(data[19].split(","));
|
||||||
if (gridOverlay.selectAll("*").size()) turnButtonOn("toggleGrid"); else turnButtonOff("toggleGrid");
|
cells.fl = Uint16Array.from(data[20].split(","));
|
||||||
if (coordinates.selectAll("*").size()) turnButtonOn("toggleCoordinates"); else turnButtonOff("toggleCoordinates");
|
cells.pop = Uint16Array.from(data[21].split(","));
|
||||||
if (compass.style("display") !== "none" && compass.select("use").size()) turnButtonOn("toggleCompass"); else turnButtonOff("toggleCompass");
|
cells.r = Uint16Array.from(data[22].split(","));
|
||||||
if (rivers.style("display") !== "none") turnButtonOn("toggleRivers"); else turnButtonOff("toggleRivers");
|
cells.road = Uint16Array.from(data[23].split(","));
|
||||||
if (terrain.style("display") !== "none" && terrain.selectAll("*").size()) turnButtonOn("toggleRelief"); else turnButtonOff("toggleRelief");
|
cells.s = Uint16Array.from(data[24].split(","));
|
||||||
if (relig.selectAll("*").size()) turnButtonOn("toggleReligions"); else turnButtonOff("toggleReligions");
|
cells.state = Uint16Array.from(data[25].split(","));
|
||||||
if (cults.selectAll("*").size()) turnButtonOn("toggleCultures"); else turnButtonOff("toggleCultures");
|
cells.religion = data[26] ? Uint16Array.from(data[26].split(",")) : new Uint16Array(cells.i.length);
|
||||||
if (statesBody.selectAll("*").size()) turnButtonOn("toggleStates"); else turnButtonOff("toggleStates");
|
cells.province = data[27] ? Uint16Array.from(data[27].split(",")) : new Uint16Array(cells.i.length);
|
||||||
if (provs.selectAll("*").size()) turnButtonOn("toggleProvinces"); else turnButtonOff("toggleProvinces");
|
cells.crossroad = data[28] ? Uint16Array.from(data[28].split(",")) : new Uint16Array(cells.i.length);
|
||||||
if (zones.selectAll("*").size() && zones.style("display") !== "none") turnButtonOn("toggleZones"); else turnButtonOff("toggleZones");
|
}()
|
||||||
if (borders.style("display") !== "none") turnButtonOn("toggleBorders"); else turnButtonOff("toggleBorders");
|
|
||||||
if (routes.style("display") !== "none" && routes.selectAll("path").size()) turnButtonOn("toggleRoutes"); else turnButtonOff("toggleRoutes");
|
|
||||||
if (temperature.selectAll("*").size()) turnButtonOn("toggleTemp"); else turnButtonOff("toggleTemp");
|
|
||||||
if (prec.selectAll("circle").size()) turnButtonOn("togglePrec"); else turnButtonOff("togglePrec");
|
|
||||||
if (labels.style("display") !== "none") turnButtonOn("toggleLabels"); else turnButtonOff("toggleLabels");
|
|
||||||
if (icons.style("display") !== "none") turnButtonOn("toggleIcons"); else turnButtonOff("toggleIcons");
|
|
||||||
if (markers.selectAll("*").size() && markers.style("display") !== "none") turnButtonOn("toggleMarkers"); else turnButtonOff("toggleMarkers");
|
|
||||||
if (ruler.style("display") !== "none") turnButtonOn("toggleRulers"); else turnButtonOff("toggleRulers");
|
|
||||||
if (scaleBar.style("display") !== "none") turnButtonOn("toggleScaleBar"); else turnButtonOff("toggleScaleBar");
|
|
||||||
|
|
||||||
// special case for population bars
|
void function restoreLayersState() {
|
||||||
const populationIsOn = population.selectAll("line").size();
|
if (texture.style("display") !== "none" && texture.select("image").size()) turnButtonOn("toggleTexture"); else turnButtonOff("toggleTexture");
|
||||||
if (populationIsOn) drawPopulation();
|
if (terrs.selectAll("*").size()) turnButtonOn("toggleHeight"); else turnButtonOff("toggleHeight");
|
||||||
if (populationIsOn) turnButtonOn("togglePopulation"); else turnButtonOff("togglePopulation");
|
if (biomes.selectAll("*").size()) turnButtonOn("toggleBiomes"); else turnButtonOff("toggleBiomes");
|
||||||
|
if (cells.selectAll("*").size()) turnButtonOn("toggleCells"); else turnButtonOff("toggleCells");
|
||||||
|
if (gridOverlay.selectAll("*").size()) turnButtonOn("toggleGrid"); else turnButtonOff("toggleGrid");
|
||||||
|
if (coordinates.selectAll("*").size()) turnButtonOn("toggleCoordinates"); else turnButtonOff("toggleCoordinates");
|
||||||
|
if (compass.style("display") !== "none" && compass.select("use").size()) turnButtonOn("toggleCompass"); else turnButtonOff("toggleCompass");
|
||||||
|
if (rivers.style("display") !== "none") turnButtonOn("toggleRivers"); else turnButtonOff("toggleRivers");
|
||||||
|
if (terrain.style("display") !== "none" && terrain.selectAll("*").size()) turnButtonOn("toggleRelief"); else turnButtonOff("toggleRelief");
|
||||||
|
if (relig.selectAll("*").size()) turnButtonOn("toggleReligions"); else turnButtonOff("toggleReligions");
|
||||||
|
if (cults.selectAll("*").size()) turnButtonOn("toggleCultures"); else turnButtonOff("toggleCultures");
|
||||||
|
if (statesBody.selectAll("*").size()) turnButtonOn("toggleStates"); else turnButtonOff("toggleStates");
|
||||||
|
if (provs.selectAll("*").size()) turnButtonOn("toggleProvinces"); else turnButtonOff("toggleProvinces");
|
||||||
|
if (zones.selectAll("*").size() && zones.style("display") !== "none") turnButtonOn("toggleZones"); else turnButtonOff("toggleZones");
|
||||||
|
if (borders.style("display") !== "none") turnButtonOn("toggleBorders"); else turnButtonOff("toggleBorders");
|
||||||
|
if (routes.style("display") !== "none" && routes.selectAll("path").size()) turnButtonOn("toggleRoutes"); else turnButtonOff("toggleRoutes");
|
||||||
|
if (temperature.selectAll("*").size()) turnButtonOn("toggleTemp"); else turnButtonOff("toggleTemp");
|
||||||
|
if (prec.selectAll("circle").size()) turnButtonOn("togglePrec"); else turnButtonOff("togglePrec");
|
||||||
|
if (labels.style("display") !== "none") turnButtonOn("toggleLabels"); else turnButtonOff("toggleLabels");
|
||||||
|
if (icons.style("display") !== "none") turnButtonOn("toggleIcons"); else turnButtonOff("toggleIcons");
|
||||||
|
if (markers.selectAll("*").size() && markers.style("display") !== "none") turnButtonOn("toggleMarkers"); else turnButtonOff("toggleMarkers");
|
||||||
|
if (ruler.style("display") !== "none") turnButtonOn("toggleRulers"); else turnButtonOff("toggleRulers");
|
||||||
|
if (scaleBar.style("display") !== "none") turnButtonOn("toggleScaleBar"); else turnButtonOff("toggleScaleBar");
|
||||||
|
|
||||||
getCurrentPreset();
|
// special case for population bars
|
||||||
}()
|
const populationIsOn = population.selectAll("line").size();
|
||||||
|
if (populationIsOn) drawPopulation();
|
||||||
|
if (populationIsOn) turnButtonOn("togglePopulation"); else turnButtonOff("togglePopulation");
|
||||||
|
|
||||||
void function restoreRulersEvents() {
|
getCurrentPreset();
|
||||||
ruler.selectAll("g").call(d3.drag().on("start", dragRuler));
|
}()
|
||||||
ruler.selectAll("text").on("click", removeParent);
|
|
||||||
ruler.selectAll("g.ruler circle").call(d3.drag().on("drag", dragRulerEdge));
|
|
||||||
ruler.selectAll("g.ruler circle").call(d3.drag().on("drag", dragRulerEdge));
|
|
||||||
ruler.selectAll("g.ruler rect").call(d3.drag().on("start", rulerCenterDrag));
|
|
||||||
ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd));
|
|
||||||
ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd));
|
|
||||||
}()
|
|
||||||
|
|
||||||
void function resolveVersionConflicts() {
|
void function restoreEvents() {
|
||||||
const version = parseFloat(data[0].split("|")[0]);
|
ruler.selectAll("g").call(d3.drag().on("start", dragRuler));
|
||||||
if (version == 0.8) {
|
ruler.selectAll("text").on("click", removeParent);
|
||||||
// 0.9 has additional relief icons to be included into older maps
|
ruler.selectAll("g.ruler circle").call(d3.drag().on("drag", dragRulerEdge));
|
||||||
document.getElementById("defs-relief").innerHTML = reliefIcons;
|
ruler.selectAll("g.ruler circle").call(d3.drag().on("drag", dragRulerEdge));
|
||||||
}
|
ruler.selectAll("g.ruler rect").call(d3.drag().on("start", rulerCenterDrag));
|
||||||
|
ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd));
|
||||||
|
ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd));
|
||||||
|
|
||||||
if (version < 1) {
|
scaleBar.on("mousemove", () => tip("Click to open Units Editor"));
|
||||||
// 1.0 adds a new religions layer
|
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
|
||||||
relig = viewbox.insert("g", "#terrain").attr("id", "relig");
|
}()
|
||||||
Religions.generate();
|
|
||||||
|
|
||||||
// 1.0 adds a legend box
|
void function resolveVersionConflicts() {
|
||||||
legend = svg.append("g").attr("id", "legend");
|
const version = parseFloat(data[0].split("|")[0]);
|
||||||
legend.attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC")
|
if (version == 0.8) {
|
||||||
.attr("font-size", 13).attr("data-size", 13).attr("data-x", 99).attr("data-y", 93)
|
// 0.9 has additional relief icons to be included into older maps
|
||||||
.attr("stroke-width", 2.5).attr("stroke", "#812929").attr("stroke-dasharray", "0 4 10 4").attr("stroke-linecap", "round");
|
document.getElementById("defs-relief").innerHTML = reliefIcons;
|
||||||
|
|
||||||
// 1.0 separated drawBorders fron drawStates()
|
|
||||||
stateBorders = borders.append("g").attr("id", "stateBorders");
|
|
||||||
provinceBorders = borders.append("g").attr("id", "provinceBorders");
|
|
||||||
borders.attr("opacity", null).attr("stroke", null).attr("stroke-width", null).attr("stroke-dasharray", null).attr("stroke-linecap", null).attr("filter", null);
|
|
||||||
stateBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt");
|
|
||||||
provinceBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt");
|
|
||||||
|
|
||||||
// 1.0 adds state relations, provinces, forms and full names
|
|
||||||
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", .6);
|
|
||||||
BurgsAndStates.collectStatistics();
|
|
||||||
BurgsAndStates.generateDiplomacy();
|
|
||||||
BurgsAndStates.defineStateForms();
|
|
||||||
drawStates();
|
|
||||||
BurgsAndStates.generateProvinces();
|
|
||||||
drawBorders();
|
|
||||||
if (!layerIsOn("toggleBorders")) $('#borders').fadeOut();
|
|
||||||
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
|
|
||||||
|
|
||||||
// 1.0 adds hatching
|
|
||||||
document.getElementsByTagName("defs")[0].appendChild(hatching);
|
|
||||||
|
|
||||||
// 1.0 adds zones layer
|
|
||||||
zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none");
|
|
||||||
zones.attr("opacity", .6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt");
|
|
||||||
addZone();
|
|
||||||
if (!markers.selectAll("*").size()) {addMarkers(); turnButtonOn("toggleMarkers");}
|
|
||||||
|
|
||||||
// 1.0 add fogging layer (state focus)
|
|
||||||
let fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)")
|
|
||||||
.append("g").attr("id", "fogging").attr("display", "none");
|
|
||||||
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
|
|
||||||
defs.append("mask").attr("id", "fog").append("rect").attr("x", 0).attr("y", 0).attr("width", "100%")
|
|
||||||
.attr("height", "100%").attr("fill", "white");
|
|
||||||
|
|
||||||
// 1.0 changes states opacity bask to regions level
|
|
||||||
if (statesBody.attr("opacity")) {
|
|
||||||
regions.attr("opacity", statesBody.attr("opacity"));
|
|
||||||
statesBody.attr("opacity", null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.0 changed labels to multi-lined
|
if (version < 1) {
|
||||||
labels.selectAll("textPath").each(function() {
|
// 1.0 adds a new religions layer
|
||||||
const text = this.textContent;
|
relig = viewbox.insert("g", "#terrain").attr("id", "relig");
|
||||||
const shift = this.getComputedTextLength() / -1.5;
|
Religions.generate();
|
||||||
this.innerHTML = `<tspan x="${shift}">${text}</tspan>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1.0 added new biome - Wetland
|
// 1.0 adds a legend box
|
||||||
biomesData.name.push("Wetland");
|
legend = svg.append("g").attr("id", "legend");
|
||||||
biomesData.color.push("#0b9131");
|
legend.attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC")
|
||||||
biomesData.habitability.push(12);
|
.attr("font-size", 13).attr("data-size", 13).attr("data-x", 99).attr("data-y", 93)
|
||||||
}
|
.attr("stroke-width", 2.5).attr("stroke", "#812929").attr("stroke-dasharray", "0 4 10 4").attr("stroke-linecap", "round");
|
||||||
|
|
||||||
if (version == 1) {
|
// 1.0 separated drawBorders fron drawStates()
|
||||||
// v 1.0 initial code had a bug with religion layer id
|
stateBorders = borders.append("g").attr("id", "stateBorders");
|
||||||
if (!relig.size()) relig = viewbox.insert("g", "#terrain").attr("id", "relig");
|
provinceBorders = borders.append("g").attr("id", "provinceBorders");
|
||||||
|
borders.attr("opacity", null).attr("stroke", null).attr("stroke-width", null).attr("stroke-dasharray", null).attr("stroke-linecap", null).attr("filter", null);
|
||||||
|
stateBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt");
|
||||||
|
provinceBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt");
|
||||||
|
|
||||||
// v 1.0 initially has Sympathy status then relaced with Friendly
|
// 1.0 adds state relations, provinces, forms and full names
|
||||||
for (const s of pack.states) {
|
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", .6);
|
||||||
s.diplomacy = s.diplomacy.map(r => r === "Sympathy" ? "Friendly" : r);
|
BurgsAndStates.collectStatistics();
|
||||||
|
BurgsAndStates.generateDiplomacy();
|
||||||
|
BurgsAndStates.defineStateForms();
|
||||||
|
drawStates();
|
||||||
|
BurgsAndStates.generateProvinces();
|
||||||
|
drawBorders();
|
||||||
|
if (!layerIsOn("toggleBorders")) $('#borders').fadeOut();
|
||||||
|
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
|
||||||
|
|
||||||
|
// 1.0 adds hatching
|
||||||
|
document.getElementsByTagName("defs")[0].appendChild(hatching);
|
||||||
|
|
||||||
|
// 1.0 adds zones layer
|
||||||
|
zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none");
|
||||||
|
zones.attr("opacity", .6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt");
|
||||||
|
addZone();
|
||||||
|
if (!markers.selectAll("*").size()) {addMarkers(); turnButtonOn("toggleMarkers");}
|
||||||
|
|
||||||
|
// 1.0 add fogging layer (state focus)
|
||||||
|
let fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)")
|
||||||
|
.append("g").attr("id", "fogging").attr("display", "none");
|
||||||
|
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
|
||||||
|
defs.append("mask").attr("id", "fog").append("rect").attr("x", 0).attr("y", 0).attr("width", "100%")
|
||||||
|
.attr("height", "100%").attr("fill", "white");
|
||||||
|
|
||||||
|
// 1.0 changes states opacity bask to regions level
|
||||||
|
if (statesBody.attr("opacity")) {
|
||||||
|
regions.attr("opacity", statesBody.attr("opacity"));
|
||||||
|
statesBody.attr("opacity", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.0 changed labels to multi-lined
|
||||||
|
labels.selectAll("textPath").each(function() {
|
||||||
|
const text = this.textContent;
|
||||||
|
const shift = this.getComputedTextLength() / -1.5;
|
||||||
|
this.innerHTML = `<tspan x="${shift}">${text}</tspan>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1.0 added new biome - Wetland
|
||||||
|
biomesData.name.push("Wetland");
|
||||||
|
biomesData.color.push("#0b9131");
|
||||||
|
biomesData.habitability.push(12);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
changeMapSize();
|
if (version == 1) {
|
||||||
restoreDefaultEvents();
|
// v 1.0 initial code had a bug with religion layer id
|
||||||
invokeActiveZooming();
|
if (!relig.size()) relig = viewbox.insert("g", "#terrain").attr("id", "relig");
|
||||||
tip("Map is loaded");
|
|
||||||
console.timeEnd("loadMap");
|
// v 1.0 initially has Sympathy status then relaced with Friendly
|
||||||
|
for (const s of pack.states) {
|
||||||
|
s.diplomacy = s.diplomacy.map(r => r === "Sympathy" ? "Friendly" : r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
changeMapSize();
|
||||||
|
if (window.restoreDefaultEvents) restoreDefaultEvents();
|
||||||
|
invokeActiveZooming();
|
||||||
|
|
||||||
|
console.warn(`TOTAL: ${rn((performance.now()-uploadFile.timeStart)/1000,2)}s`);
|
||||||
|
showStatistics();
|
||||||
|
console.groupEnd("Loaded Map " + seed);
|
||||||
|
tip("Map is successfully loaded", true, "success", 7000);
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
console.error(error);
|
||||||
|
clearMainTip();
|
||||||
|
|
||||||
|
const regex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||||
|
const errorNoURL = error.stack.replace(regex, url => '<i>' + last(url.split("/")) + '</i>');
|
||||||
|
const errorParsed = errorNoURL.replace(/ at /ig, "<br> at ");
|
||||||
|
|
||||||
|
alertMessage.innerHTML = `An error is occured on map loading. Select a different file to load,
|
||||||
|
<br>generate a new random map or cancel the loading
|
||||||
|
<p id="errorBox">${errorParsed}</p>`;
|
||||||
|
$("#alert").dialog({
|
||||||
|
resizable: false, title: "Loading error", maxWidth:500, buttons: {
|
||||||
|
"Select file": function() {$(this).dialog("close"); mapToLoad.click();},
|
||||||
|
"New map": function() {$(this).dialog("close"); regenerateMap();},
|
||||||
|
Cancel: function() {$(this).dialog("close")}
|
||||||
|
}, position: {my: "center", at: "center", of: "svg"}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function quickSave() {
|
||||||
|
const blob = await getMapData();
|
||||||
|
if (blob) ldb.set("lastMap", blob); // auto-save map
|
||||||
|
tip("Map is saved to browser memory", true, "success", 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function quickLoad() {
|
||||||
|
ldb.get("lastMap", blob => {
|
||||||
|
if (blob) {
|
||||||
|
loadMapPrompt(blob);
|
||||||
|
} else {
|
||||||
|
tip("No map stored. Save map to storage first", true, "error", 2000);
|
||||||
|
console.error("No map stored");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMapPrompt(blob) {
|
||||||
|
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||||
|
if (workingTime < 10) {loadLastSavedMap(); return;}
|
||||||
|
|
||||||
|
alertMessage.innerHTML = `Are you sure you want to load saved map?<br>
|
||||||
|
All unsaved changes made to the current map will be lost`;
|
||||||
|
$("#alert").dialog({resizable: false, title: "Load saved map",
|
||||||
|
buttons: {
|
||||||
|
Cancel: function() {$(this).dialog("close");},
|
||||||
|
Load: function() {loadLastSavedMap(); $(this).dialog("close");}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadLastSavedMap() {
|
||||||
|
console.warn("Load last saved map");
|
||||||
|
closeDialogs();
|
||||||
|
try {
|
||||||
|
uploadFile(blob);
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
console.error(error);
|
||||||
|
tip("Cannot load last saved map", true, "error", 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveReminder = function() {
|
||||||
|
if (localStorage.getItem("noReminder")) return;
|
||||||
|
const message = ["Please don't forget to save your work as a .map file",
|
||||||
|
"Please remember to save work as a .map file",
|
||||||
|
"Saving in .map format will ensure your data won't be lost in case of issues",
|
||||||
|
"Safety is number one priority. Please save the map",
|
||||||
|
"Don't forget to save your map on a regular basis!",
|
||||||
|
"Just a gentle reminder for you to save the map",
|
||||||
|
"Please forget to save your progress (saving as .map is the best option)",
|
||||||
|
"Don't want to be reminded about need to save? Press CTRL+Q"];
|
||||||
|
|
||||||
|
saveReminder.reminder = setInterval(() => {
|
||||||
|
if (customization) return;
|
||||||
|
tip(ra(message), true, "warn", 2500);
|
||||||
|
}, 1e6);
|
||||||
|
saveReminder.status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveReminder();
|
||||||
|
|
||||||
|
function toggleSaveReminder() {
|
||||||
|
if (saveReminder.status) {
|
||||||
|
tip("Save reminder is turned off. Press CTRL+Q again to re-initiate", true, "warn", 2000);
|
||||||
|
clearInterval(saveReminder.reminder);
|
||||||
|
localStorage.setItem("noReminder", true);
|
||||||
|
saveReminder.status = 0;
|
||||||
|
} else {
|
||||||
|
tip("Save reminder is turned on. Press CTRL+Q to turn off", true, "warn", 2000);
|
||||||
|
localStorage.removeItem("noReminder");
|
||||||
|
saveReminder();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ function editBurg() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewGroup() {
|
function createNewGroup() {
|
||||||
if (!this.value) {tip("Please provide a valid group name"); return;}
|
if (!this.value) {tip("Please provide a valid group name", false, "error"); return;}
|
||||||
let group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
|
let group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
|
||||||
if (Number.isFinite(+group.charAt(0))) group = "g" + group;
|
if (Number.isFinite(+group.charAt(0))) group = "g" + group;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ function editBurgs() {
|
||||||
|
|
||||||
function getCultureOptions(culture) {
|
function getCultureOptions(culture) {
|
||||||
let options = "";
|
let options = "";
|
||||||
pack.cultures.slice(1).forEach(c => options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`);
|
pack.cultures.forEach(c => options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`);
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ function editBurgs() {
|
||||||
function changeBurgPopulation() {
|
function changeBurgPopulation() {
|
||||||
const burg = +this.parentNode.dataset.id;
|
const burg = +this.parentNode.dataset.id;
|
||||||
if (this.value == "" || isNaN(+this.value)) {
|
if (this.value == "" || isNaN(+this.value)) {
|
||||||
tip("Please provide an integer number", false, "error");
|
tip("Please provide an integer number (like 10000, not 10K)", false, "error");
|
||||||
this.value = si(pack.burgs[burg].population * populationRate.value * urbanization.value);
|
this.value = si(pack.burgs[burg].population * populationRate.value * urbanization.value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +186,7 @@ function editBurgs() {
|
||||||
if (!pack.burgs[burg].port) {
|
if (!pack.burgs[burg].port) {
|
||||||
const haven = pack.cells.haven[pack.burgs[burg].cell];
|
const haven = pack.cells.haven[pack.burgs[burg].cell];
|
||||||
const port = haven ? pack.cells.f[haven] : -1;
|
const port = haven ? pack.cells.f[haven] : -1;
|
||||||
if (!haven) tip("Port haven is not found, system won't be able to make a searoute", false, "warning");
|
if (!haven) tip("Port haven is not found, system won't be able to make a searoute", false, "warn");
|
||||||
pack.burgs[burg].port = port;
|
pack.burgs[burg].port = port;
|
||||||
|
|
||||||
const g = pack.burgs[burg].capital ? "cities" : "towns";
|
const g = pack.burgs[burg].capital ? "cities" : "towns";
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,7 @@ function editCultures() {
|
||||||
cults.select("#culture"+culture).remove();
|
cults.select("#culture"+culture).remove();
|
||||||
debug.select("#cultureCenter"+culture).remove();
|
debug.select("#cultureCenter"+culture).remove();
|
||||||
|
|
||||||
pack.burgs.filter(b => b.culture === culture).forEach(b => b.culture = 0);
|
pack.burgs.filter(b => b.culture == culture).forEach(b => b.culture = 0);
|
||||||
pack.cells.culture.forEach((c, i) => {if(c === culture) pack.cells.culture[i] = 0;});
|
pack.cells.culture.forEach((c, i) => {if(c === culture) pack.cells.culture[i] = 0;});
|
||||||
pack.cultures[culture].removed = true;
|
pack.cultures[culture].removed = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -187,22 +187,43 @@ function editDiplomacy() {
|
||||||
const chronicle = pack.states[0].diplomacy;
|
const chronicle = pack.states[0].diplomacy;
|
||||||
if (!chronicle.length) {tip("Relations history is blank", false, "error"); return;}
|
if (!chronicle.length) {tip("Relations history is blank", false, "error"); return;}
|
||||||
|
|
||||||
let message = `<div>`;
|
let message = `<div autocorrect="off" spellcheck="false">`;
|
||||||
chronicle.forEach(e => {
|
chronicle.forEach((e, d) => {
|
||||||
message += `<div style="margin: 0.5em 0">`;
|
message += `<div>`;
|
||||||
e.forEach((l, i) => message += `<div${i ? "" : " style='font-weight:bold'"}>${l}</div>`);
|
e.forEach((l, i) => message += `<div contenteditable="true" data-id="${d}-${i}"${i ? "" : " style='font-weight:bold'"}>${l}</div>`);
|
||||||
message += `</div>`;
|
message += `‍</div>`;
|
||||||
});
|
});
|
||||||
alertMessage.innerHTML = message + `</div>`;
|
alertMessage.innerHTML = message + `</div><i id="info-line">Type to edit. Press Enter to add a new line, empty the element to remove it</i>`;
|
||||||
|
alertMessage.querySelectorAll("div[contenteditable='true']").forEach(el => el.addEventListener("input", changeReliationsHistory));
|
||||||
|
|
||||||
$("#alert").dialog({title: "Relations history", position: {my: "center", at: "center", of: "svg"},
|
$("#alert").dialog({title: "Relations history", position: {my: "center", at: "center", of: "svg"},
|
||||||
buttons: {
|
buttons: {
|
||||||
|
Save: function() {
|
||||||
|
const text = this.querySelector("div").innerText.split("\n").join("\r\n");
|
||||||
|
const dataBlob = new Blob([text], {type: "text/plain"});
|
||||||
|
const url = window.URL.createObjectURL(dataBlob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.download = "state_relations_history" + Date.now() + ".txt";
|
||||||
|
link.href = url;
|
||||||
|
link.click();
|
||||||
|
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
|
||||||
|
},
|
||||||
Clear: function() {pack.states[0].diplomacy = []; $(this).dialog("close");},
|
Clear: function() {pack.states[0].diplomacy = []; $(this).dialog("close");},
|
||||||
Close: function() {$(this).dialog("close");}
|
Close: function() {$(this).dialog("close");}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeReliationsHistory() {
|
||||||
|
const i = this.dataset.id.split("-");
|
||||||
|
const group = pack.states[0].diplomacy[i[0]];
|
||||||
|
if (this.innerHTML === "") {
|
||||||
|
group.splice(i[1], 1);
|
||||||
|
this.remove();
|
||||||
|
} else group[i[1]] = this.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
function showRelationsMatrix() {
|
function showRelationsMatrix() {
|
||||||
const states = pack.states.filter(s => s.i && !s.removed);
|
const states = pack.states.filter(s => s.i && !s.removed);
|
||||||
const valid = states.map(s => s.i);
|
const valid = states.map(s => s.i);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
// Module to store general UI functions
|
// Module to store general UI functions
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// ask before closing the window
|
|
||||||
window.onbeforeunload = () => "Are you sure you want to navigate away?";
|
|
||||||
|
|
||||||
// fit full-screen map if window is resized
|
// fit full-screen map if window is resized
|
||||||
$(window).resize(function(e) {
|
$(window).resize(function(e) {
|
||||||
mapWidthInput.value = window.innerWidth;
|
mapWidthInput.value = window.innerWidth;
|
||||||
|
|
@ -11,6 +8,8 @@ $(window).resize(function(e) {
|
||||||
changeMapSize();
|
changeMapSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.onbeforeunload = () => "Are you sure you want to navigate away?";
|
||||||
|
|
||||||
// Tooltips
|
// Tooltips
|
||||||
const tooltip = document.getElementById("tooltip");
|
const tooltip = document.getElementById("tooltip");
|
||||||
|
|
||||||
|
|
@ -18,16 +17,18 @@ const tooltip = document.getElementById("tooltip");
|
||||||
document.getElementById("dialogs").addEventListener("mousemove", showDataTip);
|
document.getElementById("dialogs").addEventListener("mousemove", showDataTip);
|
||||||
document.getElementById("optionsContainer").addEventListener("mousemove", showDataTip);
|
document.getElementById("optionsContainer").addEventListener("mousemove", showDataTip);
|
||||||
|
|
||||||
function tip(tip = "Tip is undefined", main = false, error = false) {
|
function tip(tip = "Tip is undefined", main, error, time) {
|
||||||
const reg = "linear-gradient(0.1turn, #ffffff00, #5e5c5c66, #ffffff00)";
|
|
||||||
const red = "linear-gradient(0.1turn, #ffffff00, #e3141499, #ffffff00)";
|
|
||||||
tooltip.innerHTML = tip;
|
tooltip.innerHTML = tip;
|
||||||
tooltip.style.background = error ? red : reg;
|
tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)";
|
||||||
if (main) tooltip.dataset.main = tip;
|
if (error === "error") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)"; else
|
||||||
|
if (error === "warn") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)"; else
|
||||||
|
if (error === "success") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)";
|
||||||
|
|
||||||
|
if (main) tooltip.dataset.main = tip; // set main tip
|
||||||
|
if (time) setTimeout(tooltip.dataset.main = "", time); // clear main in some time
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMainTip() {
|
function showMainTip() {
|
||||||
tooltip.style.background = "linear-gradient(0.1turn, #aaffff00, #3a26264d, #ffffff00)";
|
|
||||||
tooltip.innerHTML = tooltip.dataset.main;
|
tooltip.innerHTML = tooltip.dataset.main;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,14 +230,21 @@ function applyOption(select, option) {
|
||||||
document.addEventListener("keydown", function(event) {
|
document.addEventListener("keydown", function(event) {
|
||||||
const active = document.activeElement.tagName;
|
const active = document.activeElement.tagName;
|
||||||
if (active === "INPUT" || active === "SELECT" || active === "TEXTAREA") return; // don't trigger if user inputs a text
|
if (active === "INPUT" || active === "SELECT" || active === "TEXTAREA") return; // don't trigger if user inputs a text
|
||||||
|
if (active === "DIV" && document.activeElement.contentEditable === "true") return; // don't trigger if user inputs a text
|
||||||
const key = event.keyCode, ctrl = event.ctrlKey, shift = event.shiftKey;
|
const key = event.keyCode, ctrl = event.ctrlKey, shift = event.shiftKey;
|
||||||
if (key === 118) regeneratePrompt(); // "F7" for new map
|
if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
|
||||||
else if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
|
|
||||||
else if (key === 9) {toggleOptions(event); event.preventDefault();} // Tab to toggle options
|
else if (key === 9) {toggleOptions(event); event.preventDefault();} // Tab to toggle options
|
||||||
|
|
||||||
|
else if (key === 113) regeneratePrompt(); // "F2" for new map
|
||||||
|
else if (key === 117) quickSave(); // "F6" for quick save
|
||||||
|
else if (key === 120) quickLoad(); // "F9" for quick load
|
||||||
|
|
||||||
else if (ctrl && key === 80) saveAsImage("png"); // Ctrl + "P" to save as PNG
|
else if (ctrl && key === 80) saveAsImage("png"); // Ctrl + "P" to save as PNG
|
||||||
else if (ctrl && key === 83) saveAsImage("svg"); // Ctrl + "S" to save as SVG
|
else if (ctrl && key === 83) saveAsImage("svg"); // Ctrl + "S" to save as SVG
|
||||||
else if (ctrl && key === 77) saveMap(); // Ctrl + "M" to save MAP file
|
else if (ctrl && key === 77) saveMap(); // Ctrl + "M" to save MAP file
|
||||||
else if (ctrl && key === 76) mapToLoad.click(); // Ctrl + "L" to load MAP
|
else if (ctrl && key === 85) mapToLoad.click(); // Ctrl + "U" to load MAP from URL
|
||||||
|
else if (ctrl && key === 76) mapToLoad.click(); // Ctrl + "L" to load MAP from local file
|
||||||
|
else if (ctrl && key === 81) toggleSaveReminder(); // Ctrl + "Q" to toggle save reminder
|
||||||
else if (key === 46) removeElementOnKey(); // "Delete" to remove the selected element
|
else if (key === 46) removeElementOnKey(); // "Delete" to remove the selected element
|
||||||
|
|
||||||
else if (shift && key === 192) console.log(pack.cells); // Shift + "`" to log cells data
|
else if (shift && key === 192) console.log(pack.cells); // Shift + "`" to log cells data
|
||||||
|
|
|
||||||
|
|
@ -22,22 +22,25 @@ function restoreLayers() {
|
||||||
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
|
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// layers to be turned on; changable by user
|
|
||||||
let presets = {
|
|
||||||
"political": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"],
|
|
||||||
"cultural": ["toggleBorders", "toggleCultures", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
|
||||||
"religions": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleReligions", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
|
||||||
"provinces": ["toggleBorders", "toggleIcons", "toggleProvinces", "toggleRivers", "toggleScaleBar"],
|
|
||||||
"biomes": ["toggleBiomes", "toggleRivers", "toggleScaleBar"],
|
|
||||||
"heightmap": ["toggleHeight", "toggleRivers", "toggleScaleBar"],
|
|
||||||
"poi": ["toggleBorders", "toggleHeight", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
|
||||||
"landmass": ["toggleScaleBar"]
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreLayers(); // run on-load
|
restoreLayers(); // run on-load
|
||||||
|
let presets = {}; // global object
|
||||||
restoreCustomPresets(); // run on-load
|
restoreCustomPresets(); // run on-load
|
||||||
|
|
||||||
|
function getDefaultPresets() {
|
||||||
|
return {
|
||||||
|
"political": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"],
|
||||||
|
"cultural": ["toggleBorders", "toggleCultures", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||||
|
"religions": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleReligions", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||||
|
"provinces": ["toggleBorders", "toggleIcons", "toggleProvinces", "toggleRivers", "toggleScaleBar"],
|
||||||
|
"biomes": ["toggleBiomes", "toggleRivers", "toggleScaleBar"],
|
||||||
|
"heightmap": ["toggleHeight", "toggleRivers", "toggleScaleBar"],
|
||||||
|
"poi": ["toggleBorders", "toggleHeight", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||||
|
"landmass": ["toggleScaleBar"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function restoreCustomPresets() {
|
function restoreCustomPresets() {
|
||||||
|
presets = getDefaultPresets();
|
||||||
const storedPresets = JSON.parse(localStorage.getItem("presets"));
|
const storedPresets = JSON.parse(localStorage.getItem("presets"));
|
||||||
if (!storedPresets) return;
|
if (!storedPresets) return;
|
||||||
|
|
||||||
|
|
@ -45,6 +48,7 @@ function restoreCustomPresets() {
|
||||||
if (presets[preset]) continue;
|
if (presets[preset]) continue;
|
||||||
layersPreset.add(new Option(preset, preset));
|
layersPreset.add(new Option(preset, preset));
|
||||||
}
|
}
|
||||||
|
|
||||||
presets = storedPresets;
|
presets = storedPresets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,34 +66,50 @@ function changePreset(preset) {
|
||||||
});
|
});
|
||||||
layersPreset.value = preset;
|
layersPreset.value = preset;
|
||||||
localStorage.setItem("preset", preset);
|
localStorage.setItem("preset", preset);
|
||||||
|
|
||||||
|
const isDefault = getDefaultPresets()[preset];
|
||||||
|
removePresetButton.style.display = isDefault ? "none" : "inline-block";
|
||||||
|
savePresetButton.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
function savePreset() {
|
function savePreset() {
|
||||||
// don't allow if layers should already esist as a preset
|
|
||||||
if (layersPreset.value !== "custom") {
|
|
||||||
tip(`Current layers are already saved as a "${layersPreset.selectedOptions[0].label}" preset`, false, "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new preset
|
|
||||||
const preset = prompt("Please provide a preset name"); // preset name
|
const preset = prompt("Please provide a preset name"); // preset name
|
||||||
if (!preset) return;
|
if (!preset) return;
|
||||||
presets[preset] = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")).map(node => node.id).sort();
|
presets[preset] = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")).map(node => node.id).sort();
|
||||||
layersPreset.add(new Option(preset, preset, false, true));
|
layersPreset.add(new Option(preset, preset, false, true));
|
||||||
localStorage.setItem("presets", JSON.stringify(presets));
|
localStorage.setItem("presets", JSON.stringify(presets));
|
||||||
localStorage.setItem("preset", preset);
|
localStorage.setItem("preset", preset);
|
||||||
|
removePresetButton.style.display = "inline-block";
|
||||||
|
savePresetButton.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePreset() {
|
||||||
|
const preset = layersPreset.value;
|
||||||
|
delete presets[preset];
|
||||||
|
const index = Array.from(layersPreset.options).findIndex(o => o.value === preset);
|
||||||
|
layersPreset.options.remove(index);
|
||||||
|
layersPreset.value = "custom";
|
||||||
|
removePresetButton.style.display = "none";
|
||||||
|
savePresetButton.style.display = "inline-block";
|
||||||
|
|
||||||
|
localStorage.setItem("presets", JSON.stringify(presets));
|
||||||
|
localStorage.removeItem("preset");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentPreset() {
|
function getCurrentPreset() {
|
||||||
const layers = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")).map(node => node.id).sort();
|
const layers = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")).map(node => node.id).sort();
|
||||||
|
const defaultPresets = getDefaultPresets();
|
||||||
|
|
||||||
for (const preset in presets) {
|
for (const preset in presets) {
|
||||||
if (JSON.stringify(presets[preset]) !== JSON.stringify(layers)) continue;
|
if (JSON.stringify(presets[preset]) !== JSON.stringify(layers)) continue;
|
||||||
layersPreset.value = preset;
|
layersPreset.value = preset;
|
||||||
|
removePresetButton.style.display = defaultPresets[preset] ? "none" : "inline-block";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
layersPreset.value = "custom";
|
layersPreset.value = "custom";
|
||||||
|
removePresetButton.style.display = "none";
|
||||||
|
savePresetButton.style.display = "inline-block";
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleHeight() {
|
function toggleHeight() {
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,6 @@
|
||||||
$("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"});
|
$("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"});
|
||||||
$("#mapLayers").disableSelection();
|
$("#mapLayers").disableSelection();
|
||||||
|
|
||||||
// show control elements and remove loading screen on map load
|
|
||||||
d3.select("#loading").transition().duration(5000).style("opacity", 0).remove();
|
|
||||||
d3.select("#initial").transition().duration(5000).attr("opacity", 0).remove();
|
|
||||||
d3.select("#optionsContainer").transition().duration(5000).style("opacity", 1);
|
|
||||||
d3.select("#tooltip").transition().duration(5000).style("opacity", 1);
|
|
||||||
|
|
||||||
// remove glow if tip is aknowledged
|
// remove glow if tip is aknowledged
|
||||||
if (localStorage.getItem("disable_click_arrow_tooltip")) {
|
if (localStorage.getItem("disable_click_arrow_tooltip")) {
|
||||||
clearMainTip();
|
clearMainTip();
|
||||||
|
|
@ -677,6 +671,7 @@ optionsContent.addEventListener("input", function(event) {
|
||||||
else if (id === "provincesInput") provincesOutput.value = value;
|
else if (id === "provincesInput") provincesOutput.value = value;
|
||||||
else if (id === "provincesOutput") provincesOutput.value = value;
|
else if (id === "provincesOutput") provincesOutput.value = value;
|
||||||
else if (id === "provincesOutput") powerOutput.value = value;
|
else if (id === "provincesOutput") powerOutput.value = value;
|
||||||
|
else if (id === "powerInput") powerOutput.value = value;
|
||||||
else if (id === "powerOutput") powerInput.value = value;
|
else if (id === "powerOutput") powerInput.value = value;
|
||||||
else if (id === "neutralInput") neutralOutput.value = value;
|
else if (id === "neutralInput") neutralOutput.value = value;
|
||||||
else if (id === "neutralOutput") neutralInput.value = value;
|
else if (id === "neutralOutput") neutralInput.value = value;
|
||||||
|
|
@ -722,7 +717,7 @@ function changeMapSize() {
|
||||||
oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
|
oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
|
||||||
oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
|
oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
|
||||||
fitScaleBar();
|
fitScaleBar();
|
||||||
fitLegendBox();
|
if (window.fitLegendBox) fitLegendBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
// just apply map size that was already set, apply graph size!
|
// just apply map size that was already set, apply graph size!
|
||||||
|
|
@ -991,26 +986,32 @@ document.getElementById("sticked").addEventListener("click", function(event) {
|
||||||
const id = event.target.id;
|
const id = event.target.id;
|
||||||
if (id === "newMapButton") regeneratePrompt();
|
if (id === "newMapButton") regeneratePrompt();
|
||||||
else if (id === "saveButton") toggleSavePane();
|
else if (id === "saveButton") toggleSavePane();
|
||||||
else if (id === "loadMap") mapToLoad.click();
|
else if (id === "loadButton") toggleLoadPane();
|
||||||
else if (id === "zoomReset") resetZoom(1000);
|
else if (id === "zoomReset") resetZoom(1000);
|
||||||
|
else if (id === "quickSave") quickSave();
|
||||||
else if (id === "saveMap") saveMap();
|
else if (id === "saveMap") saveMap();
|
||||||
else if (id === "saveSVG") saveAsImage("svg");
|
else if (id === "saveSVG") saveAsImage("svg");
|
||||||
else if (id === "savePNG") saveAsImage("png");
|
else if (id === "savePNG") saveAsImage("png");
|
||||||
else if (id === "saveGeo") saveGeoJSON();
|
else if (id === "saveGeo") saveGeoJSON();
|
||||||
if (id === "saveMap" || id === "saveSVG" || id === "savePNG" || id === "saveGeo") toggleSavePane();
|
else if (id === "saveDropbox") saveDropbox();
|
||||||
|
if (id === "quickSave" || id === "saveMap" || id === "saveSVG" || id === "savePNG" || id === "saveGeo" || id === "saveDropbox") toggleSavePane();
|
||||||
|
if (id === "loadMap") mapToLoad.click();
|
||||||
|
else if (id === "quickLoad") quickLoad();
|
||||||
|
else if (id === "loadURL") loadURL();
|
||||||
|
else if (id === "loadDropbox") loadDropbox();
|
||||||
|
if (id === "quickLoad" || id === "loadURL" || id === "loadMap" || id === "loadDropbox") toggleLoadPane();
|
||||||
});
|
});
|
||||||
|
|
||||||
function regeneratePrompt() {
|
function regeneratePrompt() {
|
||||||
if (customization) {tip("Please exit the customization mode first", false, "warning"); return;}
|
|
||||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||||
if (workingTime < 15) {regenerateMap(); return;}
|
if (workingTime < 10) {regenerateMap(); return;}
|
||||||
|
|
||||||
alertMessage.innerHTML = `Are you sure you want to generate a new map?<br>
|
alertMessage.innerHTML = `Are you sure you want to generate a new map?<br>
|
||||||
All unsaved changes made to the current map will be lost`;
|
All unsaved changes made to the current map will be lost`;
|
||||||
$("#alert").dialog({resizable: false, title: "Generate new map",
|
$("#alert").dialog({resizable: false, title: "Generate new map",
|
||||||
buttons: {
|
buttons: {
|
||||||
Cancel: function() {$(this).dialog("close");},
|
Cancel: function() {$(this).dialog("close");},
|
||||||
Generate: regenerateMap
|
Generate: function() {closeDialogs(); regenerateMap();}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1034,13 +1035,73 @@ function toggleSavePane() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// async function saveDropbox() {
|
||||||
|
// const filename = "fantasy_map_" + Date.now() + ".map";
|
||||||
|
// const options = {
|
||||||
|
// files: [{'url': '...', 'filename': 'fantasy_map.map'}],
|
||||||
|
// success: function () {alert("Success! Files saved to your Dropbox.")},
|
||||||
|
// progress: function (progress) {console.log(progress)},
|
||||||
|
// cancel: function (cancel) {console.log(cancel)},
|
||||||
|
// error: function (error) {console.log(error)}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // working file: "https://dl.dropbox.com/s/llg93mwyonyzdmu/test.map?dl=1";
|
||||||
|
// const dataBlob = await getMapData();
|
||||||
|
// const URL = window.URL.createObjectURL(dataBlob);
|
||||||
|
// Dropbox.save(URL, filename, options);
|
||||||
|
// }
|
||||||
|
|
||||||
|
function toggleLoadPane() {
|
||||||
|
if (loadDropdown.style.display === "block") {loadDropdown.style.display = "none"; return;}
|
||||||
|
loadDropdown.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadURL() {
|
||||||
|
const pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
|
||||||
|
const inner = `Provide URL to a .map file:
|
||||||
|
<input id="mapURL" type="url" style="width: 254px" placeholder="https://e-cloud.com/test.map">
|
||||||
|
<br><i>Please note server should allow CORS for file to be loaded. If CORS is not allowed, save file to Dropbox and provide a direct link</i>`;
|
||||||
|
alertMessage.innerHTML = inner;
|
||||||
|
$("#alert").dialog({resizable: false, title: "Load map from URL", width: 280,
|
||||||
|
buttons: {
|
||||||
|
Load: function() {
|
||||||
|
const value = mapURL.value;
|
||||||
|
if (!pattern.test(value)) {tip("Please provide a valid URL", false, "error"); return;}
|
||||||
|
closeDialogs();
|
||||||
|
loadMapFromURL(value);
|
||||||
|
$(this).dialog("close");
|
||||||
|
},
|
||||||
|
Cancel: function() {$(this).dialog("close");}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// function loadDropbox() {
|
||||||
|
// const options = {
|
||||||
|
// success: function(file) {send_files(file)},
|
||||||
|
// cancel: function() {},
|
||||||
|
// linkType: "preview",
|
||||||
|
// multiselect: false,
|
||||||
|
// extensions:['.map'],
|
||||||
|
// };
|
||||||
|
// Dropbox.choose(options);
|
||||||
|
|
||||||
|
// function send_files(file) {
|
||||||
|
// const subject = "Shared File Links";
|
||||||
|
// let body = "";
|
||||||
|
// for (let i=0; i < file.length; i++) {
|
||||||
|
// body += file[i].name + "\n" + file[i].link + "\n\n";
|
||||||
|
// }
|
||||||
|
// location.href = 'mailto:coworker@example.com?Subject=' + escape(subject) + '&body='+ escape(body),'200','200';
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// load map
|
// load map
|
||||||
document.getElementById("mapToLoad").addEventListener("change", function() {
|
document.getElementById("mapToLoad").addEventListener("change", function() {
|
||||||
closeDialogs();
|
|
||||||
const fileToLoad = this.files[0];
|
const fileToLoad = this.files[0];
|
||||||
this.value = "";
|
this.value = "";
|
||||||
|
closeDialogs();
|
||||||
uploadFile(fileToLoad);
|
uploadFile(fileToLoad);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue