Merge pull request #2 from Azgaar/master

Update from Azgaar version 1.1.08
This commit is contained in:
evolvedexperiment 2019-10-01 20:03:37 +02:00 committed by GitHub
commit 84aa932dff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 2113 additions and 421 deletions

View file

@ -80,6 +80,11 @@ button, select, a {
fill-rule: evenodd; fill-rule: evenodd;
} }
#lakes,
#coastline {
cursor: pointer;
}
#temperature { #temperature {
fill-rule: evenodd; fill-rule: evenodd;
font-family: sans-serif; font-family: sans-serif;
@ -242,7 +247,10 @@ i.icon-lock {
text-anchor: middle; text-anchor: middle;
} }
#routeLength, #riverLength { #routeLength,
#riverLength,
#lakeArea,
#coastlineArea {
background-color: #eeeeee; background-color: #eeeeee;
border: 1px solid #a5a5a5; border: 1px solid #a5a5a5;
line-height: 1.3em; line-height: 1.3em;
@ -252,7 +260,7 @@ i.icon-lock {
#brushCircle { #brushCircle {
stroke: #373737; stroke: #373737;
stroke-width: 1.5px; stroke-width: 1.5px;
stroke-dasharray: 6; stroke-dasharray: 7;
stroke-linecap: butt; stroke-linecap: butt;
fill: none; fill: none;
} }
@ -474,13 +482,7 @@ input[type="color"]::-webkit-color-swatch-wrapper {
font-weight: bold; font-weight: bold;
} }
#styleContent button.styleButton { #layersContent button.presetButton {
font-size: 70%;
border-radius: 15%;
margin: 0;
}
#styleContent button.presetButton {
position: absolute; position: absolute;
height: 2em; height: 2em;
border-radius: 15%; border-radius: 15%;
@ -488,6 +490,13 @@ input[type="color"]::-webkit-color-swatch-wrapper {
font-size: .7em; font-size: .7em;
} }
#styleContent button.styleButton {
font-size: 70%;
border-radius: 15%;
margin: 0;
}
#layersContent button.active,
#styleContent button:active { #styleContent button:active {
transform: translate(0px, 1px); transform: translate(0px, 1px);
} }
@ -531,9 +540,10 @@ input[type="color"]::-webkit-color-swatch-wrapper {
} }
.tab > button.options { .tab > button.options {
width: 23.25%; /* width: 23.25%; */
width: 18.6%;
height: 100%; height: 100%;
padding: 7px 10px; padding: 7px 0px;
} }
button.options { button.options {
@ -682,7 +692,7 @@ fieldset {
} }
#styleElements .whiteButton { #styleElements .whiteButton {
padding: 0 9px; padding: 0 .8em;
border: 0; border: 0;
background-color: #ffffff !important; background-color: #ffffff !important;
} }
@ -873,7 +883,7 @@ body button.noicon {
#controlPoints { #controlPoints {
fill: #ff0000; fill: #ff0000;
stroke: #841f1f; stroke: #841f1f;
stroke-width: .3; stroke-width: .25;
cursor: move; cursor: move;
opacity: .8; opacity: .8;
} }
@ -886,6 +896,25 @@ body button.noicon {
cursor: pointer; cursor: pointer;
} }
#vertices > circle {
fill: #ff0000;
stroke: #841f1f;
stroke-width: .1;
cursor: move;
opacity: .8;
}
#vertices > polygon {
fill: none;
stroke: #808080;
stroke-width: .1;
}
#controlPoints > circle:hover,
#vertices> circle:hover {
stroke: #2c0808;
}
.drag-trigger { .drag-trigger {
border-left: 12px solid transparent; border-left: 12px solid transparent;
border-right: 12px solid #916e7f; border-right: 12px solid #916e7f;
@ -1183,6 +1212,11 @@ div.states .icon-pin {
cursor: pointer; cursor: pointer;
} }
div.states .icon-flag-empty {
cursor: pointer;
font-size: .9em;
}
div.states .icon-resize-vertical { div.states .icon-resize-vertical {
cursor: row-resize; cursor: row-resize;
font-size: .9em; font-size: .9em;
@ -1215,6 +1249,7 @@ div.states>.cultureName {
div.states>.culturePopulation { div.states>.culturePopulation {
width: 4em; width: 4em;
cursor: pointer;
} }
div.states > .cultureBase, div.states > .cultureBase,

View file

@ -8,6 +8,7 @@
<meta name="author" content="Azgaar (Max Ganiev)"> <meta name="author" content="Azgaar (Max Ganiev)">
<meta name="description" content="Azgaar's Fantasy Map Generator and Editor"> <meta name="description" content="Azgaar's Fantasy Map Generator and Editor">
<meta name="google" content="notranslate"> <meta name="google" content="notranslate">
<meta name="google-site-verification" content="6N9TRdPptDN1dCZKaMA5zJ-_UmNQE-3c4VizSlQcEeU"/>
<meta property="og:url" content="https://azgaar.github.io/Fantasy-Map-Generator"> <meta property="og:url" content="https://azgaar.github.io/Fantasy-Map-Generator">
<meta property="og:title" content="Azgaar's Fantasy Map Generator"> <meta property="og:title" content="Azgaar's Fantasy Map Generator">
<meta property="og:description" content="Web application generating interactive and customizable maps"> <meta property="og:description" content="Web application generating interactive and customizable maps">
@ -16,8 +17,8 @@
<link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16"/> <link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16"/>
<link rel="canonical" href="https://azgaar.github.io/Fantasy-Map-Generator/"> <link rel="canonical" href="https://azgaar.github.io/Fantasy-Map-Generator/">
<link rel="stylesheet" type="text/css" href="index.css?version=1.0"/> <link rel="stylesheet" type="text/css" href="index.css?version=1.1"/>
<link rel="stylesheet" type="text/css" href="icons.css?version=1.0"/> <link rel="stylesheet" type="text/css" href="icons.css?version=1.1"/>
<link rel="stylesheet" type="text/css" href="libs/jquery-ui.css"/> <link rel="stylesheet" type="text/css" href="libs/jquery-ui.css"/>
</head> </head>
<body> <body>
@ -129,7 +130,7 @@
<g id="hatching"> <g id="hatching">
<pattern id="hatch0" patternUnits="userSpaceOnUse" width="4" height="4"> <pattern id="hatch0" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/> <line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>; </pattern>
<pattern id="hatch1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4"> <pattern id="hatch1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/> <line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
@ -874,7 +875,7 @@
<div id="loading" style="opacity:1"> <div id="loading" style="opacity:1">
<div id="title_name">Azgaar's</div> <div id="title_name">Azgaar's</div>
<div id="title">Fantasy Map Generator</div> <div id="title">Fantasy Map Generator</div>
<div id="version">v. 1.0</div> <div id="version">v. 1.1</div>
<p id="loading-text">LOADING<span>.</span><span>.</span><span>.</span></p> <p id="loading-text">LOADING<span>.</span><span>.</span><span>.</span></p>
</div> </div>
@ -890,13 +891,14 @@
<div class="tab"> <div class="tab">
<button id="optionsHide" data-tip="Click to hide options pane. Shortcut: Tab to close this or Esc to close all dialogs" class="options" onclick="hideOptions(event)"></button> <button id="optionsHide" data-tip="Click to hide options pane. Shortcut: Tab to close this or Esc to close all dialogs" class="options" onclick="hideOptions(event)"></button>
<button id="styleTab" data-tip="Click to open style menu" class="options active">Style</button> <button id="layersTab" data-tip="Click to change map layers" class="options active">Layers</button>
<button id="styleTab" data-tip="Click to open style editor" class="options">Style</button>
<button id="optionsTab" data-tip="Click to change generation and UI options" class="options">Options</button> <button id="optionsTab" data-tip="Click to change generation and UI options" class="options">Options</button>
<button id="toolsTab" data-tip="Click to open tools menu" class="options">Tools</button> <button id="toolsTab" data-tip="Click to open tools menu" class="options">Tools</button>
<button id="aboutTab" data-tip="Click to see Generator info" class="options">About</button> <button id="aboutTab" data-tip="Click to see Generator info" class="options">About</button>
</div> </div>
<div id="styleContent" class="tabcontent" style="display: block"> <div id="layersContent" class="tabcontent" style="display: block">
<p data-tip="Select a map layers preset" style="display: inline-block;">Layers preset:</p> <p data-tip="Select a map layers preset" style="display: inline-block;">Layers preset:</p>
<select data-tip="Select a map layers preset" id="layersPreset" onchange="changePreset(this.value)" style="width:45%"> <select data-tip="Select a map layers preset" id="layersPreset" onchange="changePreset(this.value)" style="width:45%">
<option value="political" selected>Political map</option> <option value="political" selected>Political map</option>
@ -912,34 +914,36 @@
<button id="savePresetButton" data-tip="Click to save displayed layers as a new preset" class="icon-plus presetButton" style="display:none" onclick="savePreset()"></button> <button id="savePresetButton" data-tip="Click to save displayed layers as a new preset" class="icon-plus presetButton" style="display:none" onclick="savePreset()"></button>
<button id="removePresetButton" data-tip="Click to remove current custom preset" class="icon-minus presetButton" style="display:none" onclick="removePreset()"></button> <button id="removePresetButton" data-tip="Click to remove current custom preset" class="icon-minus presetButton" 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. Ctrl + click to edit layer style">Displayed layers and layers order:</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. Ctrl + click to edit layer style" id="mapLayers">
<li id="toggleTexture" data-tip="Texture overlay: click to toggle, drag to raise or lower the layer. Shortcut: X" class="buttonoff" onclick="toggleTexture()">Te<u>x</u>ture</li> <li id="toggleTexture" data-tip="Texture overlay: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: X" class="buttonoff" onclick="toggleTexture(event)">Te<u>x</u>ture</li>
<li id="toggleHeight" data-tip="Heightmap: click to toggle, drag to raise or lower the layer. Shortcut: H" class="buttonoff" onclick="toggleHeight()"><u>H</u>eightmap</li> <li id="toggleHeight" data-tip="Heightmap: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: H" class="buttonoff" onclick="toggleHeight(event)"><u>H</u>eightmap</li>
<li id="toggleBiomes" data-tip="Biomes: click to toggle, drag to raise or lower the layer. Shortcut: B" class="buttonoff" onclick="toggleBiomes()"><u>B</u>iomes</li> <li id="toggleBiomes" data-tip="Biomes: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: B" class="buttonoff" onclick="toggleBiomes(event)"><u>B</u>iomes</li>
<li id="toggleCells" data-tip="Cells structure: click to toggle, drag to raise or lower the layer. Shortcut: E" class="buttonoff" onclick="toggleCells()">C<u>e</u>lls</li> <li id="toggleCells" data-tip="Cells structure: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: E" class="buttonoff" onclick="toggleCells(event)">C<u>e</u>lls</li>
<li id="toggleGrid" data-tip="Grid: click to toggle, drag to raise or lower. Select type in styles dropdown below. Shortcut: G" class="buttonoff" onclick="toggleGrid()"><u>G</u>rid</li> <li id="toggleGrid" data-tip="Grid: click to toggle, drag to raise or lower. Select type in styles dropdown below. Ctrl + click to edit layer style. Shortcut: G" class="buttonoff" onclick="toggleGrid(event)"><u>G</u>rid</li>
<li id="toggleCoordinates" data-tip="Coordinate grid: click to toggle, drag to raise or lower the layer. Shortcut: O" class="buttonoff" onclick="toggleCoordinates()">C<u>o</u>ordinates</li> <li id="toggleCoordinates" data-tip="Coordinate grid: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: O" class="buttonoff" onclick="toggleCoordinates(event)">C<u>o</u>ordinates</li>
<li id="toggleCompass" data-tip="Wind (Compass) Rose: click to toggle, drag to raise or lower the layer. Shortcut: W" class="buttonoff" onclick="toggleCompass()"><u>W</u>ind Rose</li> <li id="toggleCompass" data-tip="Wind (Compass) Rose: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: W" class="buttonoff" onclick="toggleCompass(event)"><u>W</u>ind Rose</li>
<li id="toggleRivers" data-tip="Rivers: click to toggle, drag to raise or lower the layer. Shortcut: V" onclick="toggleRivers()">Ri<u>v</u>ers</li> <li id="toggleRivers" data-tip="Rivers: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: V" onclick="toggleRivers(event)">Ri<u>v</u>ers</li>
<li id="toggleRelief" data-tip="Relief and biome icons: click to toggle, drag to raise or lower the layer. Shortcut: F" class="buttonoff" onclick="toggleRelief()">Relie<u>f</u></li> <li id="toggleRelief" data-tip="Relief and biome icons: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: F" class="buttonoff" onclick="toggleRelief(event)">Relie<u>f</u></li>
<li id="toggleReligions" data-tip="Religions: click to toggle, drag to raise or lower the layer. Shortcut: R" class="buttonoff" onclick="toggleReligions()"><u>R</u>eligions</li> <li id="toggleReligions" data-tip="Religions: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: R" class="buttonoff" onclick="toggleReligions(event)"><u>R</u>eligions</li>
<li id="toggleCultures" data-tip="Cultures: click to toggle, drag to raise or lower the layer. Shortcut: C" class="buttonoff" onclick="toggleCultures()"><u>C</u>ultures</li> <li id="toggleCultures" data-tip="Cultures: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: C" class="buttonoff" onclick="toggleCultures(event)"><u>C</u>ultures</li>
<li id="toggleStates" data-tip="States: click to toggle, drag to raise or lower the layer. Shortcut: S" onclick="toggleStates()"><u>S</u>tates</li> <li id="toggleStates" data-tip="States: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: S" onclick="toggleStates(event)"><u>S</u>tates</li>
<li id="toggleProvinces" data-tip="Provinces: click to toggle, drag to raise or lower the layer. Shortcut: N" class="buttonoff" onclick="toggleProvinces()">Provi<u>n</u>ces</li> <li id="toggleProvinces" data-tip="Provinces: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: N" class="buttonoff" onclick="toggleProvinces(event)">Provi<u>n</u>ces</li>
<li id="toggleZones" data-tip="Zones: click to toggle, drag to raise or lower the layer. Shortcut: Z" class="buttonoff" onclick="toggleZones()"><u>Z</u>ones</li> <li id="toggleZones" data-tip="Zones: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: Z" class="buttonoff" onclick="toggleZones(event)"><u>Z</u>ones</li>
<li id="toggleBorders" data-tip="State borders: click to toggle, drag to raise or lower the layer. Shortcut: D" onclick="toggleBorders()">Bor<u>d</u>ers</li> <li id="toggleBorders" data-tip="State borders: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: D" onclick="toggleBorders(event)">Bor<u>d</u>ers</li>
<li id="toggleRoutes"data-tip="Trade routes: click to toggle, drag to raise or lower the layer. Shortcut: U" onclick="toggleRoutes()">Ro<u>u</u>tes</li> <li id="toggleRoutes"data-tip="Trade routes: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: U" onclick="toggleRoutes(event)">Ro<u>u</u>tes</li>
<li id="toggleTemp" data-tip="Temperature map: click to toggle, drag to raise or lower the layer. Shortcut: T" class="buttonoff" onclick="toggleTemp()"><u>T</u>emperature</li> <li id="toggleTemp" data-tip="Temperature map: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: T" class="buttonoff" onclick="toggleTemp(event)"><u>T</u>emperature</li>
<li id="togglePopulation" data-tip="Population map: click to toggle, drag to raise or lower the layer. Shortcut: P" class="buttonoff" onclick="togglePopulation()"><u>P</u>opulation</li> <li id="togglePopulation" data-tip="Population map: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: P" class="buttonoff" onclick="togglePopulation(event)"><u>P</u>opulation</li>
<li id="togglePrec" data-tip="Precipitation map: click to toggle, drag to raise or lower the layer. Shortcut: A" class="buttonoff" onclick="togglePrec()">Precipit<u>a</u>tion</li> <li id="togglePrec" data-tip="Precipitation map: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: A" class="buttonoff" onclick="togglePrec(event)">Precipit<u>a</u>tion</li>
<li id="toggleLabels" data-tip="Labels: click to toggle, drag to raise or lower the layer. Shortcut: L" onclick="toggleLabels()"><u>L</u>abels</li> <li id="toggleLabels" data-tip="Labels: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: L" onclick="toggleLabels(event)"><u>L</u>abels</li>
<li id="toggleIcons" data-tip="Burg icons: click to toggle, drag to raise or lower the layer. Shortcut: I" onclick="toggleIcons()"><u>I</u>cons</li> <li id="toggleIcons" data-tip="Burg icons: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: I" onclick="toggleIcons(event)"><u>I</u>cons</li>
<li id="toggleMarkers" data-tip="Markers: click to toggle, drag to raise or lower the layer. Shortcut: M" class="buttonoff" onclick="toggleMarkers()"><u>M</u>arkers</li> <li id="toggleMarkers" data-tip="Markers: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: M" class="buttonoff" onclick="toggleMarkers(event)"><u>M</u>arkers</li>
<li id="toggleRulers" data-tip="Rulers: click to toggle, drag to move, click on label to delete. Shortcut: = (equal)" class="buttonoff" onclick="toggleRulers()">Rulers</li> <li id="toggleRulers" data-tip="Rulers: click to toggle, drag to move, click on label to delete. Ctrl + click to edit layer style. Shortcut: = (equal)" class="buttonoff" onclick="toggleRulers(event)">Rulers</li>
<li id="toggleScaleBar" data-tip="Scale Bar: click to toggle, drag to move. Shortcut: - (minus)" onclick="toggleScaleBar()" class="solid">Scale Bar</li> <li id="toggleScaleBar" data-tip="Scale Bar: click to toggle, drag to move. Ctrl + click to edit style. Shortcut: - (minus)" onclick="toggleScaleBar(event)" class="solid">Scale Bar</li>
</ul> </ul>
</div>
<div id="styleContent" class="tabcontent">
<p data-tip="Select an element to edit its style" style="display: inline-block;">Select element:</p> <p data-tip="Select an element to edit its style" style="display: inline-block;">Select element:</p>
<select data-tip="Select an element to edit its style (list is ordered alphabetically)" id="styleElementSelect" style="width:42%"> <select data-tip="Select an element to edit its style (list is ordered alphabetically)" id="styleElementSelect" style="width:42%">
<option value="anchors">Anchor Icons</option> <option value="anchors">Anchor Icons</option>
@ -980,7 +984,7 @@
<tbody id="styleGroup"> <tbody id="styleGroup">
<tr data-tip="Select element group"> <tr data-tip="Select element group">
<td>Group</td> <td><b>Group</b></td>
<td> <td>
<select id="styleGroupSelect"><option value="regions">regions</option></select> <select id="styleGroupSelect"><option value="regions">regions</option></select>
</td> </td>
@ -1460,7 +1464,7 @@
</table> </table>
<div id="mapFilters" data-tip="Set a filter to be applied to the map in general"> <div id="mapFilters" data-tip="Set a filter to be applied to the map in general">
<p>Toggle filters:</p> <p>Toggle global filters:</p>
<button id="grayscale" class="radio">Grayscale</button> <button id="grayscale" class="radio">Grayscale</button>
<button id="sepia" class="radio">Sepia</button> <button id="sepia" class="radio">Sepia</button>
<button id="dingy" class="radio">Dingy</button> <button id="dingy" class="radio">Dingy</button>
@ -1553,13 +1557,31 @@
</td> </td>
<td>Cultures number</td> <td>Cultures number</td>
<td> <td>
<input id="culturesInput" data-stored="cultures" type="range" min=1 max=30 value=14> <input id="culturesInput" data-stored="cultures" type="range" min=1 max=32 value=14>
</td> </td>
<td> <td>
<input id="culturesOutput" data-stored="cultures" type="number" min=1 max=30 value=14> <input id="culturesOutput" data-stored="cultures" type="number" min=1 max=32 value=14>
</td> </td>
</tr> </tr>
<tr data-tip="Select a set of cultures to be used for names and cultures generation">
<td>
<i data-locked=0 id="lock_culturesSet" class="icon-lock-open"></i>
</td>
<td>Cultures set</td>
<td>
<select id="culturesSet" data-stored="culturesSet">
<option value="world" data-max="32" selected>All-world</option>
<option value="european" data-max="15">European</option>
<option value="oriental" data-max="13">Oriental</option>
<option value="english" data-max="10">English</option>
<!-- <option value="high" data-max="16">High Fantasy</option> -->
<!-- <option value="dark" data-max="16">Dark Fantasy</option> -->
</select>
</td>
<td></td>
</tr>
<tr data-tip="Define how many states and capitals should be generated"> <tr data-tip="Define how many states and capitals should be generated">
<td> <td>
<i data-locked=0 id="lock_regions" class="icon-lock-open"></i> <i data-locked=0 id="lock_regions" class="icon-lock-open"></i>
@ -1748,6 +1770,7 @@
<button id="regenerateProvinces" data-tip="Click to regenerate provinces. States will remain as they are">Provinces</button> <button id="regenerateProvinces" data-tip="Click to regenerate provinces. States will remain as they are">Provinces</button>
<button id="regenerateReligions" data-tip="Click to regenerate religions">Religions</button> <button id="regenerateReligions" data-tip="Click to regenerate religions">Religions</button>
<button id="regenerateMarkers" data-tip="Click to regenerate markers">Markers</button> <button id="regenerateMarkers" data-tip="Click to regenerate markers">Markers</button>
<button id="regenerateZones" data-tip="Click to regenerate zones">Zones</button>
</div> </div>
<div id="addFeature"> <div id="addFeature">
@ -1823,7 +1846,7 @@
</div> </div>
<div id="aboutContent" class="tabcontent"> <div id="aboutContent" class="tabcontent">
<p><a href="https://github.com/Azgaar/Fantasy-Map-Generator" target="_blank">Fantasy Map Generator</a> is a free <a href="https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE" target="_blank">open source</a> tool which procedurally generates fantasy maps. You may use auto-generated maps as they are, edit them or even create a new map from scratch. Check out the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial" target="_blank">quick start tutorial</a> and <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki" target="_blank">project wiki</a> for guidance.</p> <p><a href="https://github.com/Azgaar/Fantasy-Map-Generator" target="_blank">Fantasy Map Generator</a> is a free <a href="https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE" target="_blank">open source</a> tool which procedurally generates fantasy maps. You may use auto-generated maps as they are, edit them or even create a new map from scratch. Check out the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial" target="_blank">quick start tutorial</a> and <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A" target="_blank">Q&A</a> for guidance.</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>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:azgaar.fmg@yandex.by" 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:azgaar.fmg@yandex.by" 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>
@ -2001,6 +2024,8 @@
<span id="labelTextRandom" data-tip="Generate random name" class="icon-shuffle pointer"></span> <span id="labelTextRandom" data-tip="Generate random name" class="icon-shuffle pointer"></span>
</div> </div>
<button id="labelEditStyle" data-tip="Edit label group style in Style Editor" class="icon-brush"></button>
<button id="labelSizeShow" data-tip="Show the font size section" class="icon-text-height"></button> <button id="labelSizeShow" data-tip="Show the font size section" class="icon-text-height"></button>
<div id="labelSizeSection" style="display: none"> <div id="labelSizeSection" style="display: none">
<button id="labelSizeHide" data-tip="Hide the font size section" class="icon-text-height"></button> <button id="labelSizeHide" data-tip="Hide the font size section" class="icon-text-height"></button>
@ -2035,6 +2060,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="riverEditStyle" data-tip="Edit style for all rivers in Style Editor" class="icon-brush"></button>
<button id="riverLength" data-tip="River 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>
@ -2051,6 +2077,8 @@
<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="routeEditStyle" data-tip="Edit route group style in Style Editor" class="icon-brush"></button>
<button id="routeLength" data-tip="Route 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>
@ -2058,6 +2086,35 @@
<button id="routeRemove" data-tip="Remove route. Shortcut: Delete" class="icon-trash"></button> <button id="routeRemove" data-tip="Remove route. Shortcut: Delete" class="icon-trash"></button>
</div> </div>
<div id="lakeEditor" class="dialog" style="display: none">
<button id="lakeGroupsShow" data-tip="Show the group selection" class="icon-tags"></button>
<div id="lakeGroupsSelection" style="display: none">
<button id="lakeGroupsHide" data-tip="Hide the group section" class="icon-tags"></button>
<select id="lakeGroup" data-tip="Select a group for this lake" style="width:9em"></select>
<input id="lakeGroupName" placeholder="new group name" data-tip="Provide a name for the new group" style="display:none; width:9em"/>
<span id="lakeGroupAdd" data-tip="Create new group for this lake" class="icon-plus pointer"></span>
<span id="lakeGroupRemove" data-tip="Remove the group" class="icon-trash-empty pointer"></span>
</div>
<button id="lakeEditStyle" data-tip="Edit lake group style in Style Editor" class="icon-brush"></button>
<button id="lakeArea" data-tip="Lake area in selected units">0</button>
<button id="lakeLegend" data-tip="Edit free text notes (legend) for the lake" class="icon-edit"></button>
</div>
<div id="coastlineEditor" class="dialog" style="display: none">
<button id="coastlineGroupsShow" data-tip="Show the group selection" class="icon-tags"></button>
<div id="coastlineGroupsSelection" style="display: none">
<button id="coastlineGroupsHide" data-tip="Hide the group section" class="icon-tags"></button>
<select id="coastlineGroup" data-tip="Select a group for this coastline" style="width:9em"></select>
<input id="coastlineGroupName" placeholder="new group name" data-tip="Provide a name for the new group" style="display:none; width:9em"/>
<span id="coastlineGroupAdd" data-tip="Create new group for this coastline" class="icon-plus pointer"></span>
<span id="coastlineGroupRemove" data-tip="Remove the group" class="icon-trash-empty pointer"></span>
</div>
<button id="coastlineEditStyle" data-tip="Edit coastline group style in Style Editor" class="icon-brush"></button>
<button id="coastlineArea" data-tip="Lake area in selected units">0</button>
</div>
<div id="reliefEditor" class="dialog" style="display: none"> <div id="reliefEditor" class="dialog" style="display: none">
<div id="reliefTools" data-tip="Select mode of operation"> <div id="reliefTools" data-tip="Select mode of operation">
@ -2183,6 +2240,7 @@
</div> </div>
<div id="reliefBottom"> <div id="reliefBottom">
<button id="reliefEditStyle" data-tip="Edit Relief Icons style in Style Editor" class="icon-brush"></button>
<button id="reliefCopy" data-tip="Copy selected relief icon" class="icon-clone"></button> <button id="reliefCopy" data-tip="Copy selected relief icon" class="icon-clone"></button>
<button id="reliefMoveFront" data-tip="Move selected relief icon to front" class="icon-level-up"></button> <button id="reliefMoveFront" data-tip="Move selected relief icon to front" class="icon-level-up"></button>
<button id="reliefMoveBack" data-tip="Move selected relief icon back" class="icon-level-down"></button> <button id="reliefMoveBack" data-tip="Move selected relief icon back" class="icon-level-down"></button>
@ -2208,6 +2266,14 @@
<span id="burgNameReRandom" data-tip="Generate random name for the burg" class="icon-globe pointer"></span> <span id="burgNameReRandom" data-tip="Generate random name for the burg" class="icon-globe pointer"></span>
</div> </div>
<button id="burgStyleShow" data-tip="Show style edit section" class="icon-brush"></button>
<div id="burgStyleSection" style="display: none">
<button id="burgStyleHide" data-tip="Hide style edit section" class="icon-brush"></button>
<button id="burgEditLabelStyle" data-tip="Edit label style for burg group in Style Editor" class="icon-font"></button>
<button id="burgEditIconStyle" data-tip="Edit icon style for burg group in Style Editor" class="icon-dot-circled"></button>
<button id="burgEditAnchorStyle" data-tip="Edit port icon (anchor) style for burg group in Style Editor" class="icon-anchor"></button>
</div>
<button id="burgSeeInMFCG" data-tip="Open the burg representation in the Medieval Fantasy City Generator by Watabou" class="icon-map-o"></button> <button id="burgSeeInMFCG" data-tip="Open the burg representation in the Medieval Fantasy City Generator by Watabou" class="icon-map-o"></button>
<button id="burgOpenCOA" data-tip="Open burg's COA in the Iron Arachne Heraldry Generator" class="icon-fleur"></button> <button id="burgOpenCOA" data-tip="Open burg's COA in the Iron Arachne Heraldry Generator" class="icon-fleur"></button>
<button id="burgRelocate" data-tip="Relocate burg" class="icon-target"></button> <button id="burgRelocate" data-tip="Relocate burg" class="icon-target"></button>
@ -2304,8 +2370,8 @@
<div id="brushesSliders" style="display: none"> <div id="brushesSliders" style="display: none">
<div data-tip="Change brush size. Shortcut: + (increase), (decrease)" style="padding-bottom: 1px"><div style="width:3.2em; display: inline-block"><i>Radius:</i></div> <div data-tip="Change brush size. Shortcut: + (increase), (decrease)" style="padding-bottom: 1px"><div style="width:3.2em; display: inline-block"><i>Radius:</i></div>
<input id="brushRadius" oninput="tip('Brush radius: '+this.value); brushRadiusNumber.value = this.value" type="range" min=5 max=50 value=25> <input id="brushRadius" oninput="tip('Brush radius: '+this.value); brushRadiusNumber.value = this.value" type="range" min=1 max=50 value=25>
<input id="brushRadiusNumber" oninput="tip('Brush radius: '+this.value); brushRadius.value = this.value" type="number" min=5 max=50 value=25 style="border: 1px solid #d4d4d4"> <input id="brushRadiusNumber" oninput="tip('Brush radius: '+this.value); brushRadius.value = this.value" type="number" min=1 max=50 value=25 style="border: 1px solid #d4d4d4">
</div> </div>
<div data-tip="Set the brush power"><div style="width:3.2em; display: inline-block"><i>Power:</i></div> <div data-tip="Set the brush power"><div style="width:3.2em; display: inline-block"><i>Power:</i></div>
@ -2409,8 +2475,7 @@
<button id="convertImageLoad" data-tip="Load image to convert" class="icon-upload"></button> <button id="convertImageLoad" data-tip="Load image to convert" class="icon-upload"></button>
<button id="convertAutoLum" data-tip="Auto-assign colors based on liminosity (good to monochrome images)" class="icon-adjust"></button> <button id="convertAutoLum" data-tip="Auto-assign colors based on liminosity (good to monochrome images)" class="icon-adjust"></button>
<button id="convertAutoHue" data-tip="Auto-assign colors based on hue (good to colored images)" class="icon-brush"></button> <button id="convertAutoHue" data-tip="Auto-assign colors based on hue (good to colored images)" class="icon-brush"></button>
<button id="convertColorsMinus" data-tip="Reduce the number of colors. Minimal number is 3" class="icon-minus-squared"></button> <button id="convertColorsButton" data-tip="Set number of colors" class="icon-signal"></button>
<button id="convertColorsPlus" data-tip="Increase the number of colors. Maximum number is 256" class="icon-plus-squared"></button>
<input id="convertColors" value="18" style="display: none"/> <input id="convertColors" value="18" style="display: none"/>
<button id="convertComplete" data-tip="Complete the assignment. All unassigned colors will be considered as ocean" class="icon-check"></button> <button id="convertComplete" data-tip="Complete the assignment. All unassigned colors will be considered as ocean" class="icon-check"></button>
</div> </div>
@ -2456,6 +2521,7 @@
<div id="biomesBottom"> <div id="biomesBottom">
<button id="biomesEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button> <button id="biomesEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="biomesEditStyle" data-tip="Edit biomes style in Style Editor" class="icon-adjust"></button>
<button id="biomesLegend" data-tip="Toggle Legend box" class="icon-list-bullet"></button> <button id="biomesLegend" data-tip="Toggle Legend box" class="icon-list-bullet"></button>
<button id="biomesPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button> <button id="biomesPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button>
<button id="biomesManually" data-tip="Manually re-assign biomes to not follow the default moisture/temperature pattern" class="icon-brush"></button> <button id="biomesManually" data-tip="Manually re-assign biomes to not follow the default moisture/temperature pattern" class="icon-brush"></button>
@ -2501,6 +2567,7 @@
<div id="statesBottom"> <div id="statesBottom">
<button id="statesEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button> <button id="statesEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="statesEditStyle" data-tip="Edit states style in Style Editor" class="icon-adjust"></button>
<button id="statesLegend" data-tip="Toggle Legend box" class="icon-list-bullet"></button> <button id="statesLegend" data-tip="Toggle Legend box" class="icon-list-bullet"></button>
<button id="statesPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button> <button id="statesPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button>
<button id="statesChart" data-tip="Show states bubble chart" class="icon-chart-area"></button> <button id="statesChart" data-tip="Show states bubble chart" class="icon-chart-area"></button>
@ -2560,6 +2627,7 @@
<div id="provincesBottom"> <div id="provincesBottom">
<button id="provincesEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button> <button id="provincesEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="provincesEditStyle" data-tip="Edit provinces style in Style Editor" class="icon-adjust"></button>
<button id="provincesPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button> <button id="provincesPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button>
<button id="provincesChart" data-tip="Show provinces chart" class="icon-chart-area"></button> <button id="provincesChart" data-tip="Show provinces chart" class="icon-chart-area"></button>
<button id="provincesToggleLabels" data-tip="Toggle province labels" class="icon-font"></button> <button id="provincesToggleLabels" data-tip="Toggle province labels" class="icon-font"></button>
@ -2593,6 +2661,7 @@
<div id="diplomacyBottom" style="margin-top: .1em"> <div id="diplomacyBottom" style="margin-top: .1em">
<button id="diplomacyEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button> <button id="diplomacyEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="diplomacyEditStyle" data-tip="Edit states (including diplomacy view) style in Style Editor" class="icon-adjust"></button>
<button id="diplomacyRegenerate" data-tip="Regenerate diplomatical relations" class="icon-retweet"></button> <button id="diplomacyRegenerate" data-tip="Regenerate diplomatical relations" class="icon-retweet"></button>
<button id="diplomacyHistory" data-tip="Show relations history" class="icon-hourglass-1"></button> <button id="diplomacyHistory" data-tip="Show relations history" class="icon-hourglass-1"></button>
<button id="diplomacyMatrix" data-tip="Show relations matrix" class="icon-list-bullet"></button> <button id="diplomacyMatrix" data-tip="Show relations matrix" class="icon-list-bullet"></button>
@ -2602,7 +2671,7 @@
<div id="burgsEditor" class="dialog stable" style="display: none"> <div id="burgsEditor" class="dialog stable" style="display: none">
<div id="burgsHeader" class="header"> <div id="burgsHeader" class="header">
<div style="left:1.4em" data-tip="Click to sort by burg name" class="sortable alphabetically" data-sortby="name">Burg&nbsp;</div> <div style="left:1.4em" data-tip="Click to sort by burg name" class="sortable alphabetically icon-sort-name-up" data-sortby="name">Burg&nbsp;</div>
<div style="left:7.6em" data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="province">Province&nbsp;</div> <div style="left:7.6em" data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="province">Province&nbsp;</div>
<div style="left:14em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="state">State&nbsp;</div> <div style="left:14em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="state">State&nbsp;</div>
<div style="left:20.1em" data-tip="Click to sort by culture name" class="sortable alphabetically" data-sortby="culture">Culture&nbsp;</div> <div style="left:20.1em" data-tip="Click to sort by culture name" class="sortable alphabetically" data-sortby="culture">Culture&nbsp;</div>
@ -2626,6 +2695,7 @@
<div id="burgsBottom"> <div id="burgsBottom">
<button id="burgsEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button> <button id="burgsEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="burgsChart" data-tip="Show burgs bubble chart" class="icon-chart-area"></button>
<button id="regenerateBurgNames" data-tip="Regenerate burg names based on assigned culture" class="icon-retweet"></button> <button id="regenerateBurgNames" data-tip="Regenerate burg names based on assigned culture" class="icon-retweet"></button>
<button id="addNewBurg" data-tip="Add a new burg. Hold Shift to add multiple" class="icon-plus"></button> <button id="addNewBurg" data-tip="Add a new burg. Hold Shift to add multiple" class="icon-plus"></button>
<button id="burgsExport" data-tip="Save burgs-related data as a text file (.csv)" class="icon-download"></button> <button id="burgsExport" data-tip="Save burgs-related data as a text file (.csv)" class="icon-download"></button>
@ -2655,6 +2725,7 @@
<div id="culturesBottom"> <div id="culturesBottom">
<button id="culturesEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button> <button id="culturesEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="culturesEditStyle" data-tip="Edit cultures style in Style Editor" class="icon-adjust"></button>
<button id="culturesLegend" data-tip="Toggle Legend box" class="icon-list-bullet"></button> <button id="culturesLegend" data-tip="Toggle Legend box" class="icon-list-bullet"></button>
<button id="culturesPercentage" data-tip="Toggle percentage / absolute values display mode" class="icon-percent"></button> <button id="culturesPercentage" data-tip="Toggle percentage / absolute values display mode" class="icon-percent"></button>
<button id="culturesManually" data-tip="Manually re-assign cultures" class="icon-brush"></button> <button id="culturesManually" data-tip="Manually re-assign cultures" class="icon-brush"></button>
@ -2731,6 +2802,8 @@
</div> </div>
<div id="zonesBottom"> <div id="zonesBottom">
<button id="zonesEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="zonesEditStyle" data-tip="Edit zones style in Style Editor" class="icon-adjust"></button>
<button id="zonesLegend" data-tip="Toggle Legend box" class="icon-list-bullet"></button> <button id="zonesLegend" data-tip="Toggle Legend box" class="icon-list-bullet"></button>
<button id="zonesPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button> <button id="zonesPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button>
@ -2791,10 +2864,11 @@
<div id="religionsBottom"> <div id="religionsBottom">
<button id="religionsEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button> <button id="religionsEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="religionsEditStyle" data-tip="Edit religions style in Style Editor" class="icon-adjust"></button>
<button id="religionsLegend" data-tip="Toggle Legend box" class="icon-list-bullet"></button> <button id="religionsLegend" data-tip="Toggle Legend box" class="icon-list-bullet"></button>
<button id="religionsPercentage" data-tip="Toggle percentage / absolute values display mode" class="icon-percent"></button> <button id="religionsPercentage" data-tip="Toggle percentage / absolute values display mode" class="icon-percent"></button>
<button id="religionsHeirarchy" data-tip="Show religions hierarchy tree" class="icon-fork"></button> <button id="religionsHeirarchy" data-tip="Show religions hierarchy tree" class="icon-sitemap"></button>
<button id="religionsExtinct" data-tip="Show/hide extinct religions (religions without cells)" class="icon-adjust"></button> <button id="religionsExtinct" data-tip="Show/hide extinct religions (religions without cells)" class="icon-eye-off"></button>
<button id="religionsManually" data-tip="Manually re-assign religions" class="icon-brush"></button> <button id="religionsManually" data-tip="Manually re-assign religions" class="icon-brush"></button>
<div id="religionsManuallyButtons" style="display: none"> <div id="religionsManuallyButtons" style="display: none">
@ -2981,7 +3055,6 @@
<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="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.3.0/simplex-noise.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>
@ -3000,6 +3073,8 @@
<script defer src="modules/ui/cultures-editor.js"></script> <script defer src="modules/ui/cultures-editor.js"></script>
<script defer src="modules/ui/namesbase-editor.js"></script> <script defer src="modules/ui/namesbase-editor.js"></script>
<script defer src="modules/ui/routes-editor.js"></script> <script defer src="modules/ui/routes-editor.js"></script>
<script defer src="modules/ui/lakes-editor.js"></script>
<script defer src="modules/ui/coastline-editor.js"></script>
<script defer src="modules/ui/labels-editor.js"></script> <script defer src="modules/ui/labels-editor.js"></script>
<script defer src="modules/ui/rivers-editor.js"></script> <script defer src="modules/ui/rivers-editor.js"></script>
<script defer src="modules/ui/relief-editor.js"></script> <script defer src="modules/ui/relief-editor.js"></script>

3
libs/jquery-ui.css vendored
View file

@ -362,7 +362,8 @@ body .ui-dialog {
border: 0; border: 0;
padding: .5em 1em; padding: .5em 1em;
background: none; background: none;
overflow: auto; overflow-y: auto;
overflow-x: hidden;
} }
.ui-dialog .ui-dialog-buttonpane { .ui-dialog .ui-dialog-buttonpane {
text-align: left; text-align: left;

506
main.js
View file

@ -7,7 +7,7 @@
// See also https://github.com/Azgaar/Fantasy-Map-Generator/issues/153 // See also https://github.com/Azgaar/Fantasy-Map-Generator/issues/153
"use strict"; "use strict";
const version = "1.0"; // generator version const version = "1.1"; // generator version
document.title += " v" + version; document.title += " v" + version;
// if map version is not stored, clear localStorage and show a message // if map version is not stored, clear localStorage and show a message
@ -64,8 +64,14 @@ let fogging = viewbox.append("g").attr("id", "fogging-cont").attr("mask", "url(#
let ruler = viewbox.append("g").attr("id", "ruler").attr("display", "none"); let ruler = viewbox.append("g").attr("id", "ruler").attr("display", "none");
let debug = viewbox.append("g").attr("id", "debug"); let debug = viewbox.append("g").attr("id", "debug");
let freshwater = lakes.append("g").attr("id", "freshwater"); // lake and coast groups
let salt = lakes.append("g").attr("id", "salt"); lakes.append("g").attr("id", "freshwater");
lakes.append("g").attr("id", "salt");
lakes.append("g").attr("id", "sinkhole");
lakes.append("g").attr("id", "frozen");
lakes.append("g").attr("id", "lava");
coastline.append("g").attr("id", "sea_island");
coastline.append("g").attr("id", "lake_island");
labels.append("g").attr("id", "states"); labels.append("g").attr("id", "states");
labels.append("g").attr("id", "addedLabels"); labels.append("g").attr("id", "addedLabels");
@ -321,8 +327,6 @@ function applyDefaultStyle() {
compass.attr("opacity", .8).attr("transform", null).attr("filter", null).attr("mask", "url(#water)").attr("shape-rendering", "optimizespeed"); compass.attr("opacity", .8).attr("transform", null).attr("filter", null).attr("mask", "url(#water)").attr("shape-rendering", "optimizespeed");
if (!d3.select("#initial").size()) d3.select("#rose").attr("transform", "translate(80 80) scale(.25)"); if (!d3.select("#initial").size()) d3.select("#rose").attr("transform", "translate(80 80) scale(.25)");
coastline.attr("opacity", .5).attr("stroke", "#1f3846").attr("stroke-width", .7).attr("filter", "url(#dropShadow)");
styleCoastlineAuto.checked = true;
relig.attr("opacity", .7).attr("stroke", "#404040").attr("stroke-width", .7).attr("filter", null).attr("fill-rule", "evenodd"); relig.attr("opacity", .7).attr("stroke", "#404040").attr("stroke-width", .7).attr("filter", null).attr("fill-rule", "evenodd");
cults.attr("opacity", .6).attr("stroke", "#777777").attr("stroke-width", .5).attr("filter", null).attr("fill-rule", "evenodd"); cults.attr("opacity", .6).attr("stroke", "#777777").attr("stroke-width", .5).attr("filter", null).attr("fill-rule", "evenodd");
icons.selectAll("g").attr("opacity", null).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("filter", null).attr("mask", null); icons.selectAll("g").attr("opacity", null).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("filter", null).attr("mask", null);
@ -334,8 +338,15 @@ function applyDefaultStyle() {
population.select("#rural").attr("stroke", "#0000ff"); population.select("#rural").attr("stroke", "#0000ff");
population.select("#urban").attr("stroke", "#ff0000"); population.select("#urban").attr("stroke", "#ff0000");
freshwater.attr("opacity", .5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", .7).attr("filter", null); lakes.select("#freshwater").attr("opacity", .5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", .7).attr("filter", null);
salt.attr("opacity", .5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", .7).attr("filter", null); lakes.select("#salt").attr("opacity", .5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", .7).attr("filter", null);
lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", .7).attr("filter", null);
lakes.select("#frozen").attr("opacity", .95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null);
lakes.select("#lava").attr("opacity", .7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)");
coastline.select("#sea_island").attr("opacity", .5).attr("stroke", "#1f3846").attr("stroke-width", .7).attr("filter", "url(#dropShadow)");
coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", .35).attr("filter", null);
styleCoastlineAuto.checked = true;
terrain.attr("opacity", null).attr("filter", null).attr("mask", null); terrain.attr("opacity", null).attr("filter", null).attr("mask", null);
rivers.attr("opacity", null).attr("fill", "#5d97bb").attr("filter", null); rivers.attr("opacity", null).attr("fill", "#5d97bb").attr("filter", null);
@ -404,30 +415,21 @@ function applyDefaultStyle() {
} }
function showWelcomeMessage() { function showWelcomeMessage() {
const link = 'https://www.reddit.com/r/FantasyMapGenerator/comments/cxu1c5/update_new_version_is_published_v_10'; // announcement on Reddit const link = 'https://www.reddit.com/r/FantasyMapGenerator/comments/daf6g2/update_new_version_is_published_v_11'; // announcement on Reddit
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>. alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
This version is compatible with <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog' target='_blank'>previous version</a>,
This version is compatible with versions 0.8b and 0.9b, but not with older .map files. loaded <i>.map</i> files will be auto-updated.
Please use an <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog' target='_blank'>archived version</a> to open old files.
<ul><a href=${link} target='_blank'>Main changes:</a> <ul><a href=${link} target='_blank'>Main changes:</a>
<li>Provinces and Provinces Editor</li> <li>Lake Editor</li>
<li>Religions Layer and Religions Editor</li> <li>Coastline Editor</li>
<li>Full state names (state types)</li> <li>New lake groups (types)</li>
<li>Multi-lined labels</li> <li>Culture presets</li>
<li>State relations (diplomacy)</li> <li>Provinces, states and burgs charts</li>
<li>Custom layers (zones)</li> <li>Editable religions tree</li>
<li>Places of interest (auto-added markers)</li> <li>Data export in geojson format</li>
<li>New color picker and hatching fill</li> <li>Map quick save and quick load</li>
<li>Legend boxes</li> <li>Map loading from URL</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> </ul>
<p>Join our <a href='https://www.reddit.com/r/FantasyMapGenerator' target='_blank'>Reddit community</a> and <p>Join our <a href='https://www.reddit.com/r/FantasyMapGenerator' target='_blank'>Reddit community</a> and
@ -437,7 +439,7 @@ function showWelcomeMessage() {
<p>Thanks for all supporters on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>!</i></p>`; <p>Thanks for all supporters on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>!</i></p>`;
$("#alert").dialog( $("#alert").dialog(
{resizable: false, title: "Fantasy Map Generator update", width: "31em", {resizable: false, title: "Fantasy Map Generator update", width: "28em",
buttons: {OK: function() {$(this).dialog("close")}}, buttons: {OK: function() {$(this).dialog("close")}},
position: {my: "center", at: "center", of: "svg"}, position: {my: "center", at: "center", of: "svg"},
close: () => localStorage.setItem("version", version)} close: () => localStorage.setItem("version", version)}
@ -487,7 +489,7 @@ function invokeActiveZooming() {
// toggle shade/blur filter for coatline on zoom // toggle shade/blur filter for coatline on zoom
let filter = scale > 2.6 ? "url(#blurFilter)" : "url(#dropShadow)"; let filter = scale > 2.6 ? "url(#blurFilter)" : "url(#dropShadow)";
if (scale > 1.5 && scale <= 2.6) filter = null; if (scale > 1.5 && scale <= 2.6) filter = null;
coastline.attr("filter", filter); coastline.select("#sea_island").attr("filter", filter);
} }
// rescale lables on zoom // rescale lables on zoom
@ -603,8 +605,8 @@ function generate() {
drawStates(); drawStates();
drawBorders(); drawBorders();
BurgsAndStates.drawStateLabels(); BurgsAndStates.drawStateLabels();
addZone();
addMarkers(); addMarkers();
addZones();
Names.getMapName(); Names.getMapName();
console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`); console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`);
@ -646,7 +648,7 @@ function generateSeed() {
// Place points to calculate Voronoi diagram // Place points to calculate Voronoi diagram
function placePoints() { function placePoints() {
console.time("placePoints"); console.time("placePoints");
const cellsDesired = 10000 * densityInput.value; // generate 10k points for graphSize = 1 const cellsDesired = 10000 * densityInput.value; // generate 10k points for each densityInput point
const spacing = grid.spacing = rn(Math.sqrt(graphWidth * graphHeight / cellsDesired), 2); // spacing between points before jirrering const spacing = grid.spacing = rn(Math.sqrt(graphWidth * graphHeight / cellsDesired), 2); // spacing between points before jirrering
grid.boundary = getBoundaryPoints(graphWidth, graphHeight, spacing); grid.boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
grid.points = getJitteredGrid(graphWidth, graphHeight, spacing); // jittered square grid grid.points = getJitteredGrid(graphWidth, graphHeight, spacing); // jittered square grid
@ -982,23 +984,29 @@ function drawCoastline() {
const type = features[f].type === "lake" ? 1 : -1; // type value to search for const type = features[f].type === "lake" ? 1 : -1; // type value to search for
const start = findStart(i, type); const start = findStart(i, type);
if (start === -1) continue; // cannot start here if (start === -1) continue; // cannot start here
const connectedVertices = connectVertices(start, type); let vchain = connectVertices(start, type);
if (features[f].type === "lake") relax(vchain, 1.2);
used[f] = 1; used[f] = 1;
let points = connectedVertices.map(v => vertices.p[v]); let points = vchain.map(v => vertices.p[v]);
const area = d3.polygonArea(points); // area with lakes/islands const area = d3.polygonArea(points); // area with lakes/islands
if (area > 0 && features[f].type === "lake") points = points.reverse(); if (area > 0 && features[f].type === "lake") {
points = points.reverse();
vchain = vchain.reverse();
}
features[f].area = Math.abs(area); features[f].area = Math.abs(area);
features[f].vertices = vchain;
const path = round(lineGen(points)); const path = round(lineGen(points));
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").attr("id", "land_"+f);
// waterMask.append("path").attr("d", path).attr("fill", "white"); // uncomment to show over lakes // waterMask.append("path").attr("d", path).attr("fill", "white").attr("id", "water_"+id); // 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", "lake_"+f).attr("data-f", f); // draw the lake
} else { } else {
landMask.append("path").attr("d", path).attr("fill", "white"); landMask.append("path").attr("d", path).attr("fill", "white").attr("id", "land_"+f);
waterMask.append("path").attr("d", path).attr("fill", "black"); waterMask.append("path").attr("d", path).attr("fill", "black").attr("id", "water_"+f);
coastline.append("path").attr("d", path).attr("id", id); // draw the coastline const g = features[f].group === "lake_island" ? "lake_island" : "sea_island";
coastline.select("#"+g).append("path").attr("d", path).attr("id", "island_"+f).attr("data-f", f); // draw the coastline
} }
// draw ruler to cover the biggest land piece // draw ruler to cover the biggest land piece
@ -1034,10 +1042,28 @@ function drawCoastline() {
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
return chain; return chain;
} }
// move vertices that are too close to already added ones
function relax(vchain, r) {
const p = vertices.p, tree = d3.quadtree();
for (let i=0; i < vchain.length; i++) {
const v = vchain[i];
let [x, y] = [p[v][0], p[v][1]];
if (i && vchain[i+1] && tree.find(x, y, r) !== undefined) {
const v1 = vchain[i-1], v2 = vchain[i+1];
const [x1, y1] = [p[v1][0], p[v1][1]];
const [x2, y2] = [p[v2][0], p[v2][1]];
[x, y] = [(x1 + x2) / 2, (y1 + y2) / 2];
p[v] = [x, y];
}
tree.add([x, y]);
}
}
console.timeEnd('drawCoastline'); console.timeEnd('drawCoastline');
} }
@ -1045,18 +1071,17 @@ function drawCoastline() {
function reMarkFeatures() { function reMarkFeatures() {
console.time("reMarkFeatures"); console.time("reMarkFeatures");
const cells = pack.cells, features = pack.features = [0]; const cells = pack.cells, features = pack.features = [0];
const continentCells = grid.cells.i.length / 10, islandCell = continentCells / 50;
cells.f = new Uint16Array(cells.i.length); // cell feature number cells.f = new Uint16Array(cells.i.length); // cell feature number
cells.t = new Int8Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast; cells.t = new Int8Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast;
cells.haven = new Uint16Array(cells.i.length); // cell haven (opposite water cell); cells.haven = new Uint16Array(cells.i.length); // cell haven (opposite water cell);
cells.harbor = new Uint16Array(cells.i.length); // cell harbor (number of adjacent water cells); cells.harbor = new Uint16Array(cells.i.length); // cell harbor (number of adjacent water cells);
for (let i=1, queue=[0]; queue[0] !== -1; i++) { for (let i=1, queue=[0]; queue[0] !== -1; i++) {
cells.f[queue[0]] = i; // feature number const start = queue[0]; // first cell
const land = cells.h[queue[0]] >= 20; cells.f[start] = i; // assign feature number
const land = cells.h[start] >= 20;
let border = false; // true if feature touches map border let border = false; // true if feature touches map border
let cellNumber = 1; // to count cells number in a feature let cellNumber = 1; // to count cells number in a feature
const temp = grid.cells.temp[cells.g[queue[0]]]; // first cell temparature
while (queue.length) { while (queue.length) {
const q = queue.pop(); const q = queue.pop();
@ -1082,13 +1107,30 @@ function reMarkFeatures() {
const type = land ? "island" : border ? "ocean" : "lake"; const type = land ? "island" : border ? "ocean" : "lake";
let group; let group;
if (type === "lake") group = temp < 25 ? "freshwater" : "salt"; else if (type === "lake") group = defineLakeGroup(start, cellNumber);
if (type === "ocean") group = "ocean"; else else if (type === "ocean") group = "ocean";
if (type === "island") group = cellNumber > continentCells ? "continent" : cellNumber > islandCell ? "island" : "isle"; else if (type === "island") group = defineIslandGroup(start, cellNumber);
features.push({i, land, border, type, cells: cellNumber, group}); features.push({i, land, border, type, cells: cellNumber, firstCell: start, group});
queue[0] = cells.f.findIndex(f => !f); // find unmarked cell queue[0] = cells.f.findIndex(f => !f); // find unmarked cell
} }
function defineLakeGroup(cell, number) {
const temp = grid.cells.temp[cells.g[cell]];
if (temp > 24) return "salt";
if (temp < -3) return "frozen";
const height = d3.max(cells.c[cell].map(c => cells.h[c]));
if (height > 69 && number < 3 && cell%5 === 0) return "sinkhole";
if (height > 69 && number < 10 && cell%5 === 0) return "lava";
return "freshwater";
}
function defineIslandGroup(cell, number) {
if (cell && features[cells.f[cell-1]].type === "lake") return "lake_island";
if (number > grid.cells.i.length / 10) return "continent";
if (number > grid.cells.i.length / 1000) return "island";
return "isle";
}
console.timeEnd("reMarkFeatures"); console.timeEnd("reMarkFeatures");
} }
@ -1128,17 +1170,17 @@ function defineBiomes() {
cells.biome[i] = getBiomeId(moist, temp, cells.h[i]); cells.biome[i] = getBiomeId(moist, temp, cells.h[i]);
} }
function getBiomeId(moisture, temperature, height) {
if (temperature < -5) return 11; // permafrost biome
if (moisture > 40 && height < 25 || moisture > 24 && height > 24) return 12; // wetland biome
const m = Math.min(moisture / 5 | 0, 4); // moisture band from 0 to 4
const t = Math.min(Math.max(20 - temperature, 0), 25); // temparature band from 0 to 25
return biomesData.biomesMartix[m][t];
}
console.timeEnd("defineBiomes"); console.timeEnd("defineBiomes");
} }
function getBiomeId(moisture, temperature, height) {
if (temperature < -5) return 11; // permafrost biome
if (moisture > 40 && height < 25 || moisture > 24 && height > 24) return 12; // wetland biome
const m = Math.min(moisture / 5 | 0, 4); // moisture band from 0 to 4
const t = Math.min(Math.max(20 - temperature, 0), 25); // temparature band from 0 to 25
return biomesData.biomesMartix[m][t];
}
// assess cells suitability to calculate population and rand cells for culture center and burgs placement // assess cells suitability to calculate population and rand cells for culture center and burgs placement
function rankCells() { function rankCells() {
console.time('rankCells'); console.time('rankCells');
@ -1160,7 +1202,9 @@ function rankCells() {
const type = f[cells.f[cells.haven[i]]].type; const type = f[cells.f[cells.haven[i]]].type;
const group = f[cells.f[cells.haven[i]]].group; const group = f[cells.f[cells.haven[i]]].group;
if (type === "lake") { if (type === "lake") {
if (group === "salt") s += 10; else s += 30; // lake coast is valued // lake coast is valued
if (group === "freshwater") s += 30;
else if (group !== "lava") s += 10;
} else { } else {
s += 5; // ocean coast is valued s += 5; // ocean coast is valued
if (cells.harbor[i] === 1) s += 20; // safe sea harbor is valued if (cells.harbor[i] === 1) s += 20; // safe sea harbor is valued
@ -1175,23 +1219,7 @@ function rankCells() {
console.timeEnd('rankCells'); console.timeEnd('rankCells');
} }
// add a zone as an example: rebels along one border // generate some markers
function addZone() {
const cells = pack.cells, states = pack.states;
const state = states.find(s => s.i && s.neighbors.size > 0 && s.neighbors.values().next().value);
if (!state) return;
const neib = state.neighbors.values().next().value;
const data = cells.i.filter(i => cells.state[i] === state.i && cells.c[i].some(c => cells.state[c] === neib));
const rebels = rw({Rebels:5, Insurgents:2, Recusants:1, Mutineers:1, Rioters:1, Dissenters:1, Secessionists:1, Insurrection:2, Rebellion:1, Conspiracy:2});
const name = getAdjective(states[neib].name) + " " + rebels;
const zone = zones.append("g").attr("id", "zone0").attr("data-description", name).attr("data-cells", data).attr("fill", "url(#hatch3)");
zone.selectAll("polygon").data(data).enter().append("polygon").attr("points", d => getPackPolygon(d)).attr("id", d => "zone0_"+d);
}
// add some markers as an example
function addMarkers(number = 1) { function addMarkers(number = 1) {
console.time("addMarkers"); console.time("addMarkers");
const cells = pack.cells; const cells = pack.cells;
@ -1205,7 +1233,7 @@ function addMarkers(number = 1) {
const cell = mounts.splice(biased(0, mounts.length-1, 5), 1); const cell = mounts.splice(biased(0, mounts.length-1, 5), 1);
const x = cells.p[cell][0], y = cells.p[cell][1]; const x = cells.p[cell][0], y = cells.p[cell][1];
const id = getNextId("markerElement"); const id = getNextId("markerElement");
markers.append("use").attr("id", id) markers.append("use").attr("id", id).attr("data-cell", cell)
.attr("xlink:href", "#marker_volcano").attr("data-id", "#marker_volcano") .attr("xlink:href", "#marker_volcano").attr("data-id", "#marker_volcano")
.attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30) .attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
.attr("data-size", 1).attr("width", 30).attr("height", 30); .attr("data-size", 1).attr("width", 30).attr("height", 30);
@ -1232,7 +1260,7 @@ function addMarkers(number = 1) {
.attr("data-size", 1).attr("width", 30).attr("height", 30); .attr("data-size", 1).attr("width", 30).attr("height", 30);
const proper = Names.getCulture(cells.culture[cell]); const proper = Names.getCulture(cells.culture[cell]);
const temp = convertTemperature(gauss(25,15,20,100)); const temp = convertTemperature(gauss(30,15,20,100));
notes.push({id, name: proper + " Hot Springs", legend:`A hot springs area. Temperature: ${temp}`}); notes.push({id, name: proper + " Hot Springs", legend:`A hot springs area. Temperature: ${temp}`});
count--; count--;
} }
@ -1396,6 +1424,326 @@ function addMarkers(number = 1) {
console.timeEnd("addMarkers"); console.timeEnd("addMarkers");
} }
// regenerate some zones
function addZones(number = 1) {
console.time("addZones");
const data = [], cells = pack.cells, states = pack.states, burgs = pack.burgs;
const used = new Uint8Array(cells.i.length); // to store used cells
for (let i=0; i < rn(Math.random() * 1.8 * number); i++) addInvasion(); // invasion of enemy lands
for (let i=0; i < rn(Math.random() * 1.6 * number); i++) addRebels(); // rebels along a state border
for (let i=0; i < rn(Math.random() * 1.6 * number); i++) addProselytism(); // proselitism of organized religion
for (let i=0; i < rn(Math.random() * 1.6 * number); i++) addCrusade(); // crusade on heresy lands
for (let i=0; i < rn(Math.random() * 1.8 * number); i++) addDisease(); // disease starting in a random city
for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addDisaster(); // disaster starting in a random city
for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addEruption(); // volcanic eruption aroung volcano
for (let i=0; i < rn(Math.random() * 1.0 * number); i++) addAvalanche(); // avalanche impacting highland road
for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addFault(); // fault line in elevated areas
for (let i=0; i < rn(Math.random() * 1.4 * number); i++) addFlood() // flood on river banks
for (let i=0; i < rn(Math.random() * 1.2 * number); i++) addTsunami() // tsunami starting near coast
function addInvasion() {
const atWar = states.filter(s => s.diplomacy && s.diplomacy.some(d => d === "Enemy"));
if (!atWar.length) return;
const invader = ra(atWar);
const target = invader.diplomacy.findIndex(d => d === "Enemy");
const cell = ra(cells.i.filter(i => cells.state[i] === target && cells.c[i].some(c => cells.state[c] === invader.i)));
if (!cell) return;
const cellsArray = [], queue = [cell], power = rand(5, 30);
while (queue.length) {
const q = Math.random() < .4 ? queue.shift() : queue.pop();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
if (cells.state[e] !== target) return;
used[e] = 1;
queue.push(e);
});
}
const invasion = rw({"Invasion":4, "Occupation":3, "Raid":2, "Conquest":2,
"Subjugation":1, "Foray":1, "Irruption":1, "Incursion":2, "Pillage":1, "Intervention":1});
const name = getAdjective(invader.name) + " " + invasion;
data.push({name, type:"Invasion", cells:cellsArray, fill:"url(#hatch1)"});
}
function addRebels() {
const state = ra(states.filter(s => s.i && Array.from(s.neighbors).some(n => n)));
if (!state) return;
const neib = ra(Array.from(state.neighbors).filter(n => n));
const cell = cells.i.find(i => cells.state[i] === state.i && cells.c[i].some(c => cells.state[c] === neib));
const cellsArray = [], queue = [cell], power = rand(10, 30);
while (queue.length) {
const q = queue.shift();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
if (cells.state[e] !== state.i) return;
used[e] = 1;
if (e%4 !== 0 && !cells.c[e].some(c => cells.state[c] === neib)) return;
queue.push(e);
});
}
const rebels = rw({"Rebels":5, "Insurgents":2, "Recusants":1,
"Mutineers":1, "Rioters":1, "Dissenters":1, "Secessionists":1,
"Insurrection":2, "Rebellion":1, "Conspiracy":2});
const name = getAdjective(states[neib].name) + " " + rebels;
data.push({name, type:"Rebels", cells:cellsArray, fill:"url(#hatch3)"});
}
function addProselytism() {
const organized = ra(pack.religions.filter(r => r.type === "Organized"));
if (!organized) return;
const cell = ra(cells.i.filter(i => cells.religion[i] && cells.religion[i] !== organized.i && cells.c[i].some(c => cells.religion[c] === organized.i)));
if (!cell) return;
const target = cells.religion[cell];
const cellsArray = [], queue = [cell], power = rand(10, 30);
while (queue.length) {
const q = queue.shift();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
if (cells.religion[e] !== target) return;
if (cells.h[e] < 20) return;
used[e] = 1;
//if (e%2 !== 0 && !cells.c[e].some(c => cells.state[c] === neib)) return;
queue.push(e);
});
}
const name = getAdjective(organized.name.split(" ")[0]) + " Proselytism";
data.push({name, type:"Proselytism", cells:cellsArray, fill:"url(#hatch6)"});
}
function addCrusade() {
const heresy = ra(pack.religions.filter(r => r.type === "Heresy"));
if (!heresy) return;
const cellsArray = cells.i.filter(i => !used[i] && cells.religion[i] === heresy.i);
if (!cellsArray.length) return;
cellsArray.forEach(i => used[i] = 1);
const name = getAdjective(heresy.name.split(" ")[0]) + " Crusade";
data.push({name, type:"Crusade", cells:cellsArray, fill:"url(#hatch6)"});
}
function addDisease() {
const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg
if (!burg) return;
const cellsArray = [], cost = [], power = rand(20, 37);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
queue.queue({e:burg.cell, p:0});
while (queue.length) {
const next = queue.dequeue();
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
used[next.e] = 1;
cells.c[next.e].forEach(function(e) {
const r = cells.road[next.e];
const c = r ? Math.max(10 - r, 1) : 100;
const p = next.p + c;
if (p > power) return;
if (!cost[e] || p < cost[e]) {
cost[e] = p;
queue.queue({e, p});
}
});
}
const adjective = () => ra(["Great", "Silent", "Severe", "Blind", "Unknown", "Loud", "Deadly", "Burning", "Bloody", "Brutal", "Fatal"]);
const animal = () => ra(["Ape", "Bear", "Boar", "Cat", "Cow", "Dog", "Pig", "Fox", "Bird", "Horse", "Rat", "Raven", "Sheep", "Spider", "Wolf"]);
const color = () => ra(["Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]);
const type = rw({"Fever":5, "Pestilence":2, "Flu":2, "Pox":2, "Smallpox":2, "Plague":4, "Cholera":2, "Ague":1, "Dropsy":1, "Leprosy":2});
const name = rw({[color()]:4, [animal()]:2, [adjective()]:1}) + " " + type;
data.push({name, type:"Disease", cells:cellsArray, fill:"url(#hatch12)"});
}
function addDisaster() {
const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg
if (!burg) return;
const cellsArray = [], cost = [], power = rand(5, 25);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
queue.queue({e:burg.cell, p:0});
while (queue.length) {
const next = queue.dequeue();
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
used[next.e] = 1;
cells.c[next.e].forEach(function(e) {
const c = rand(1, 10);
const p = next.p + c;
if (p > power) return;
if (!cost[e] || p < cost[e]) {
cost[e] = p;
queue.queue({e, p});
}
});
}
const type = rw({"Famine":5, "Dearth":1, "Drought":3, "Earthquake":3, "Tornadoes":1, "Wildfires":1});
const name = getAdjective(burg.name) + " " + type;
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch5)"});
}
function addEruption() {
const volcanoes = [];
markers.selectAll("use[data-id='#marker_volcano']").each(function() {
volcanoes.push(this.dataset.cell);
});
if (!volcanoes.length) return;
const cell = +ra(volcanoes);
const id = markers.select("use[data-cell='"+cell+"']").attr("id");
const note = notes.filter(n => n.id === id);
if (note[0]) note[0].legend = note[0].legend.replace("Active volcano", "Erupting volcano");
const name = note[0] ? note[0].name.replace(" Volcano", "") + " Eruption" : "Volcano Eruption";
const cellsArray = [], queue = [cell], power = rand(10, 30);
while (queue.length) {
const q = Math.random() < .5 ? queue.shift() : queue.pop();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
used[e] = 1;
queue.push(e);
});
}
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch7)"});
}
function addAvalanche() {
const roads = cells.i.filter(i => !used[i] && cells.road[i] && cells.h[i] >= 70);
if (!roads.length) return;
const cell = +ra(roads);
const cellsArray = [], queue = [cell], power = rand(3, 15);
while (queue.length) {
const q = Math.random() < .3 ? queue.shift() : queue.pop();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e] || cells.h[e] < 65) return;
used[e] = 1;
queue.push(e);
});
}
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Avalanche";
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch5)"});
}
function addFault() {
const elevated = cells.i.filter(i => !used[i] && cells.h[i] > 50 && cells.h[i] < 70);
if (!elevated.length) return;
const cell = ra(elevated);
const cellsArray = [], queue = [cell], power = rand(3, 15);
while (queue.length) {
const q = queue.pop();
if (cells.h[q] >= 20) cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e] || cells.r[e]) return;
used[e] = 1;
queue.push(e);
});
}
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Fault";
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch2)"});
}
function addFlood() {
const fl = cells.fl.filter(fl => fl), meanFlux = d3.mean(fl), maxFlux = d3.max(fl), flux = (maxFlux - meanFlux) / 2 + meanFlux;
const rivers = cells.i.filter(i => !used[i] && cells.h[i] < 50 && cells.r[i] && cells.fl[i] > flux && cells.burg[i]);
if (!rivers.length) return;
const cell = +ra(rivers), river = cells.r[cell];
const cellsArray = [], queue = [cell], power = rand(5, 30);
while (queue.length) {
const q = queue.pop();
cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e] || cells.h[e] < 20 || cells.r[e] !== river || cells.h[e] > 50 || cells.fl[e] < meanFlux) return;
used[e] = 1;
queue.push(e);
});
}
const name = getAdjective(burgs[cells.burg[cell]].name) + " Flood";
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch13)"});
}
function addTsunami() {
const coastal = cells.i.filter(i => !used[i] && cells.t[i] === -1 && pack.features[cells.f[i]].type !== "lake");
if (!coastal.length) return;
const cell = +ra(coastal);
const cellsArray = [], queue = [cell], power = rand(10, 30);
while (queue.length) {
const q = queue.shift();
if (cells.t[q] === 1) cellsArray.push(q);
if (cellsArray.length > power) break;
cells.c[q].forEach(e => {
if (used[e]) return;
if (cells.h[e] >= 20 && !cells.t[e]) return;
if (pack.features[cells.f[e]].type === "lake") return;
used[e] = 1;
queue.push(e);
});
}
const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Tsunami";
data.push({name, type:"Disaster", cells:cellsArray, fill:"url(#hatch13)"});
}
void function drawZones() {
zones.selectAll("g").data(data).enter().append("g")
.attr("id", (d, i) => "zone"+i).attr("data-description", d => d.name).attr("data-type", d => d.type)
.attr("data-cells", d => d.cells.join(",")).attr("fill", d => d.fill)
.selectAll("polygon").data(d => d.cells).enter().append("polygon")
.attr("points", d => getPackPolygon(d)).attr("id", function(d) {return this.parentNode.id+"_"+d});
}()
console.timeEnd("addZones");
}
// show map stats on generation complete // show map stats on generation complete
function showStatistics() { function showStatistics() {
const template = templateInput.value; const template = templateInput.value;

View file

@ -106,7 +106,7 @@
const score = new Int16Array(cells.s.map(s => s * gauss(1,3,0,20,3))); // a bit randomized 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 => !cells.burg[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 / (grid.points.length / 10000) ** .8) : manorsInput.valueAsNumber;
const burgsNumber = Math.min(desiredNumber, sorted.length); // towns to generate const burgsNumber = Math.min(desiredNumber, sorted.length); // towns to generate
let burgsAdded = 0; let burgsAdded = 0;

View file

@ -64,11 +64,14 @@
} }
function getRandomCultures(c) { function getRandomCultures(c) {
const d = getDefault(); const d = getDefault(), n = d.length-1;
const count = Math.min(c, d.length);
const cultures = []; const cultures = [];
while (cultures.length < c) { while (cultures.length < count) {
let culture = d[0]; let culture = d[rand(n)];
do {culture = d[rand(d.length-1)];} while (Math.random() > culture.odd || cultures.find(c => c.name === culture.name)) do {
culture = d[rand(n)];
} while (Math.random() > culture.odd || cultures.find(c => c.name === culture.name))
cultures.push(culture); cultures.push(culture);
} }
return cultures; return cultures;
@ -113,6 +116,61 @@
} }
const getDefault = function() { const getDefault = function() {
if (culturesSet.value === "european") {
return [
{name:"Shwazen", base:0, odd: 1},
{name:"Angshire", base:1, odd: 1},
{name:"Luari", base:2, odd: 1},
{name:"Tallian", base:3, odd: 1},
{name:"Astellian", base:4, odd: 1},
{name:"Slovan", base:5, odd: 1},
{name:"Norse", base:6, odd: 1},
{name:"Elladan", base:7, odd: 1},
{name:"Romian", base:8, odd: .2},
{name:"Soumi", base:9, odd: 1},
{name:"Portuzian", base:13, odd: 1},
{name:"Vengrian", base: 15, odd: 1},
{name:"Turchian", base: 16, odd: .05},
{name:"Euskati", base: 20, odd: .05},
{name:"Keltan", base: 22, odd: .05}
];
}
if (culturesSet.value === "oriental") {
return [
{name:"Koryo", base:10, odd: 1},
{name:"Hantzu", base:11, odd: 1},
{name:"Yamoto", base:12, odd: 1},
{name:"Turchian", base: 16, odd: 1},
{name:"Berberan", base: 17, odd: .2},
{name:"Eurabic", base: 18, odd: 1},
{name:"Efratic", base: 23, odd: .1},
{name:"Tehrani", base: 24, odd: 1},
{name:"Maui", base: 25, odd: .2},
{name:"Carnatic", base: 26, odd: .5},
{name:"Vietic", base: 29, odd: .8},
{name:"Guantzu", base:30, odd: .5},
{name:"Ulus", base:31, odd: 1}
];
}
if (culturesSet.value === "english") {
const getName = () => Names.getBase(1, 5, 9, "", 0);
return [
{name:getName(), base:1, odd: 1},
{name:getName(), base:1, odd: 1},
{name:getName(), base:1, odd: 1},
{name:getName(), base:1, odd: 1},
{name:getName(), base:1, odd: 1},
{name:getName(), base:1, odd: 1},
{name:getName(), base:1, odd: 1},
{name:getName(), base:1, odd: 1},
{name:getName(), base:1, odd: 1},
{name:getName(), base:1, odd: 1}
];
}
// all-world
return [ return [
{name:"Shwazen", base:0, odd: .7}, {name:"Shwazen", base:0, odd: .7},
{name:"Angshire", base:1, odd: 1}, {name:"Angshire", base:1, odd: 1},
@ -123,28 +181,28 @@
{name:"Norse", base:6, odd: .7}, {name:"Norse", base:6, odd: .7},
{name:"Elladan", base:7, odd: .7}, {name:"Elladan", base:7, odd: .7},
{name:"Romian", base:8, odd: .7}, {name:"Romian", base:8, odd: .7},
{name:"Soumi", base:9, odd: .4}, {name:"Soumi", base:9, odd: .3},
{name:"Koryo", base:10, odd: .5}, {name:"Koryo", base:10, odd: .1},
{name:"Hantzu", base:11, odd: .5}, {name:"Hantzu", base:11, odd: .1},
{name:"Yamoto", base:12, odd: .5}, {name:"Yamoto", base:12, odd: .1},
{name:"Portuzian", base:13, odd: .4}, {name:"Portuzian", base:13, odd: .4},
{name:"Nawatli", base:14, odd: .2}, {name:"Nawatli", base:14, odd: .1},
{name:"Vengrian", base: 15, odd: .2}, {name:"Vengrian", base: 15, odd: .2},
{name:"Turchian", base: 16, odd: .2}, {name:"Turchian", base: 16, odd: .2},
{name:"Berberan", base: 17, odd: .2}, {name:"Berberan", base: 17, odd: .1},
{name:"Eurabic", base: 18, odd: .2}, {name:"Eurabic", base: 18, odd: .2},
{name:"Inuk", base: 19, odd: .1}, {name:"Inuk", base: 19, odd: .05},
{name:"Euskati", base: 20, odd: .1}, {name:"Euskati", base: 20, odd: .05},
{name:"Negarian", base: 21, odd: .05}, {name:"Negarian", base: 21, odd: .05},
{name:"Keltan", base: 22, odd: .1}, {name:"Keltan", base: 22, odd: .05},
{name:"Efratic", base: 23, odd: .1}, {name:"Efratic", base: 23, odd: .05},
{name:"Tehrani", base: 24, odd: .1}, {name:"Tehrani", base: 24, odd: .1},
{name:"Maui", base: 25, odd: .05}, {name:"Maui", base: 25, odd: .05},
{name:"Carnatic", base: 26, odd: .1}, {name:"Carnatic", base: 26, odd: .05},
{name:"Inqan", base: 27, odd: .1}, {name:"Inqan", base: 27, odd: .05},
{name:"Kiswaili", base: 28, odd: .1}, {name:"Kiswaili", base: 28, odd: .1},
{name:"Vietic", base: 29, odd: .1}, {name:"Vietic", base: 29, odd: .1},
{name:"Guantzu", base:30, odd: 1}, {name:"Guantzu", base:30, odd: .1},
{name:"Ulus", base:31, odd: .1} {name:"Ulus", base:31, odd: .1}
]; ];
} }

View file

@ -126,8 +126,9 @@
// exclude endings inappropriate for states name // exclude endings inappropriate for states name
if (name.includes(" ")) name = capitalize(name.replace(/ /g, "").toLowerCase()); // don't allow multiword state names if (name.includes(" ")) name = capitalize(name.replace(/ /g, "").toLowerCase()); // don't allow multiword state names
if (name.length > 6 && name.slice(-4) === "berg") name = name.slice(0,-4); // remove -berg for any if (name.length > 6 && name.slice(-4) === "berg") name = name.slice(0,-4); // remove -berg for any
if (name.length > 5 && name.slice(-3) === "ton") name = name.slice(0,-3); // remove -ton for any
if (base === 5 && ["sk", "ev", "ov"].includes(name.slice(-2))) name = name.slice(0,-2); // remove -sk/-ev/-ov for Ruthenian if (base === 5 && ["sk", "ev", "ov"].includes(name.slice(-2))) name = name.slice(0,-2); // remove -sk/-ev/-ov for Ruthenian
else if (base === 1 && name.length > 5 && name.slice(-3) === "ton") name = name.slice(0,-3); // remove -ton for English
else if (base === 12) return vowel(name.slice(-1)) ? name : name + "u"; // Japanese ends on any vowel or -u else if (base === 12) return vowel(name.slice(-1)) ? name : name + "u"; // Japanese ends on any vowel or -u
else if (base === 18 && Math.random() < .4) name = vowel(name.slice(0,1).toLowerCase()) ? "Al" + name.toLowerCase() : "Al " + name; // Arabic starts with -Al else if (base === 18 && Math.random() < .4) name = vowel(name.slice(0,1).toLowerCase()) ? "Al" + name.toLowerCase() : "Al " + name; // Arabic starts with -Al
@ -139,25 +140,26 @@
} else if (Math.random() < .4) return name; // 60% for cc and vc } else if (Math.random() < .4) return name; // 60% for cc and vc
// define suffix // define suffix
let suffix = ""; let suffix = "ia"; // standard suffix
const rnd = Math.random(), l = name.length; const rnd = Math.random(), l = name.length;
if (base === 3) suffix = rnd < .03 && l < 7 ? "terra" : "ia"; // Italian if (base === 3 && rnd < .03 && l < 7) suffix = "terra"; // Italian
else if (base === 4) suffix = rnd < .03 && l < 7 ? "terra" : "ia"; // Spanish else if (base === 4 && rnd < .03 && l < 7) suffix = "terra"; // Spanish
else if (base === 13) suffix = rnd < .03 && l < 7 ? "terra" : "ia"; // Portuguese else if (base === 13 && rnd < .03 && l < 7) suffix = "terra"; // Portuguese
else if (base === 2) suffix = rnd < .03 && l < 7 ? "terre" : "ia"; // French else if (base === 2 && rnd < .03 && l < 7) suffix = "terre"; // French
else if (base === 0) suffix = rnd < .5 && l < 7 ? "land" : "ia"; // German else if (base === 0 && rnd < .5 && l < 7) suffix = "land"; // German
else if (base === 1) suffix = rnd < .4 && l < 7 ? "land" : "ia"; // English else if (base === 1 && rnd < .4 && l < 7 ) suffix = "land"; // English
else if (base === 6) suffix = rnd < .3 && l < 7 ? "land" : "ia"; // Nordic else if (base === 6 && rnd < .3 && l < 7) suffix = "land"; // Nordic
else if (base === 7) suffix = rnd < .1 ? "eia" : "ia"; // Greek else if (base === 7 && rnd < .1) suffix = "eia"; // Greek
else if (base === 9) suffix = rnd < .35 ? "maa" : "ia"; // Finnic else if (base === 9 && rnd < .35) suffix = "maa"; // Finnic
else if (base === 15) suffix = rnd < .6 && l < 6 ? "orszag" : "ia"; // Hungarian else if (base === 15 && rnd < .6 && l < 6) suffix = "orszag"; // Hungarian
else if (base === 16) suffix = rnd < .5 ? "stan" : "ya"; // Turkish else if (base === 16) suffix = rnd < .5 ? "stan" : "ya"; // Turkish
else if (base === 10) suffix = "guk"; // Korean else if (base === 10) suffix = "guk"; // Korean
else if (base === 11) suffix = " Guo"; // Chinese else if (base === 11) suffix = " Guo"; // Chinese
else if (base === 14) suffix = rnd < .6 && l < 7 ? "tlan" : "co"; // Nahuatl else if (base === 14) suffix = rnd < .6 && l < 6 ? "tlan" : "co"; // Nahuatl
else if (base === 17) suffix = rnd < .8 ? "a" : "ia"; // Berber else if (base === 17 && rnd < .8) suffix = "a"; // Berber
else if (base === 18) suffix = rnd < .8 ? "a" : "ia"; // Arabic else if (base === 18 && rnd < .8) suffix = "a"; // Arabic
else suffix = "ia" // other
return validateSuffix(name, suffix); return validateSuffix(name, suffix);
} }
@ -194,30 +196,30 @@
// name, min length, max length, letters to allow duplication, multi-word name rate // name, min length, max length, letters to allow duplication, multi-word name rate
return [ return [
{name: "German", min: 5, max: 12, d: "lt", m: 0}, {name: "German", min: 5, max: 12, d: "lt", m: 0},
{name: "English", min: 6, max: 11, d: "", m: 0.1}, {name: "English", min: 6, max: 11, d: "", m: .1},
{name: "French", min: 5, max: 13, d: "nlrs", m: 0.1}, {name: "French", min: 5, max: 13, d: "nlrs", m: .1},
{name: "Italian", min: 5, max: 12, d: "cltr", m: 0.1}, {name: "Italian", min: 5, max: 12, d: "cltr", m: .1},
{name: "Castillian", min: 5, max: 11, d: "lr", m: 0}, {name: "Castillian", min: 5, max: 11, d: "lr", m: 0},
{name: "Ruthenian", min: 5, max: 10, d: "", m: 0}, {name: "Ruthenian", min: 5, max: 10, d: "", m: 0},
{name: "Nordic", min: 6, max: 10, d: "kln", m: 0.1}, {name: "Nordic", min: 6, max: 10, d: "kln", m: .1},
{name: "Greek", min: 5, max: 11, d: "s", m: 0.1}, {name: "Greek", min: 5, max: 11, d: "s", m: .1},
{name: "Roman", min: 6, max: 11, d: "ln", m: 0.1}, {name: "Roman", min: 6, max: 11, d: "ln", m: .1},
{name: "Finnic", min: 5, max: 11, d: "akiut", m: 0}, {name: "Finnic", min: 5, max: 11, d: "akiut", m: 0},
{name: "Korean", min: 5, max: 11, d: "", m: 0}, {name: "Korean", min: 5, max: 11, d: "", m: 0},
{name: "Chinese", min: 5, max: 10, d: "", m: 0}, {name: "Chinese", min: 5, max: 10, d: "", m: 0},
{name: "Japanese", min: 4, max: 10, d: "", m: 0}, {name: "Japanese", min: 4, max: 10, d: "", m: 0},
{name: "Portuguese", min: 5, max: 11, d: "", m: 0.1}, {name: "Portuguese", min: 5, max: 11, d: "", m: .1},
{name: "Nahuatl", min: 6, max: 13, d: "l", m: 0}, {name: "Nahuatl", min: 6, max: 13, d: "l", m: 0},
{name: "Hungarian", min: 6, max: 13, d: "", m: 0.1}, {name: "Hungarian", min: 6, max: 13, d: "", m: .1},
{name: "Turkish", min: 4, max: 10, d: "", m: 0}, {name: "Turkish", min: 4, max: 10, d: "", m: 0},
{name: "Berber", min: 4, max: 10, d: "s", m: 0.2}, {name: "Berber", min: 4, max: 10, d: "s", m: .2},
{name: "Arabic", min: 4, max: 9, d: "ae", m: 0.2}, {name: "Arabic", min: 4, max: 9, d: "ae", m: .2},
{name: "Inuit", min: 5, max: 15, d: "alutsn", m: 0}, {name: "Inuit", min: 5, max: 15, d: "alutsn", m: 0},
{name: "Basque", min: 4, max: 11, d: "r", m: 0.1}, {name: "Basque", min: 4, max: 11, d: "r", m: .1},
{name: "Nigerian", min: 4, max: 10, d: "", m: 0.3}, {name: "Nigerian", min: 4, max: 10, d: "", m: .3},
{name: "Celtic", min: 4, max: 12, d: "nld", m: 0}, {name: "Celtic", min: 4, max: 12, d: "nld", m: 0},
{name: "Mesopotamian", min: 4, max: 9, d: "srpl", m: 0.1}, {name: "Mesopotamian", min: 4, max: 9, d: "srpl", m: .1},
{name: "Iranian", min: 5, max: 11, d: "", m: 0.1}, {name: "Iranian", min: 5, max: 11, d: "", m: .1},
{name: "Hawaiian", min: 5, max: 10, d: "auo", m: 1}, {name: "Hawaiian", min: 5, max: 10, d: "auo", m: 1},
{name: "Karnataka", min: 5, max: 11, d: "tnl", m: 0}, {name: "Karnataka", min: 5, max: 11, d: "tnl", m: 0},
{name: "Quechua", min: 6, max: 12, d: "l", m: 0}, {name: "Quechua", min: 6, max: 12, d: "l", m: 0},

View file

@ -22,7 +22,7 @@
number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"], number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"],
being: ["God", "Goddess", "Lord", "Lady", "Deity", "Creator", "Maker", "Overlord", "Ruler", "Chief", "Master", "Spirit", "Ancestor", "Father", "Forebear", "Forefather", "Mother", "Brother", "Sister", "Elder", "Numen", "Ancient", "Virgin", "Giver", "Council", "Guardian", "Reaper"], being: ["God", "Goddess", "Lord", "Lady", "Deity", "Creator", "Maker", "Overlord", "Ruler", "Chief", "Master", "Spirit", "Ancestor", "Father", "Forebear", "Forefather", "Mother", "Brother", "Sister", "Elder", "Numen", "Ancient", "Virgin", "Giver", "Council", "Guardian", "Reaper"],
animal: ["Antelope", "Ape", "Badger", "Bear", "Beaver", "Bison", "Boar", "Buffalo", "Cat", "Cobra", "Crane", "Crocodile", "Crow", "Deer", "Dog", "Eagle", "Elk", "Fox", "Goat", "Goose", "Hare", "Hawk", "Heron", "Horse", "Hyena", "Ibis", "Jackal", "Jaguar", "Lark", "Leopard", "Lion", "Mantis", "Marten", "Moose", "Mule", "Narwhal", "Owl", "Panther", "Rat", "Raven", "Rook", "Scorpion", "Shark", "Sheep", "Snake", "Spider", "Swan", "Tiger", "Turtle", "Viper", "Vulture", "Walrus", "Wolf", "Wolverine", "Worm", "Camel", "Falcon", "Hound", "Ox", "Serpent"], animal: ["Antelope", "Ape", "Badger", "Bear", "Beaver", "Bison", "Boar", "Buffalo", "Cat", "Cobra", "Crane", "Crocodile", "Crow", "Deer", "Dog", "Eagle", "Elk", "Fox", "Goat", "Goose", "Hare", "Hawk", "Heron", "Horse", "Hyena", "Ibis", "Jackal", "Jaguar", "Lark", "Leopard", "Lion", "Mantis", "Marten", "Moose", "Mule", "Narwhal", "Owl", "Panther", "Rat", "Raven", "Rook", "Scorpion", "Shark", "Sheep", "Snake", "Spider", "Swan", "Tiger", "Turtle", "Viper", "Vulture", "Walrus", "Wolf", "Wolverine", "Worm", "Camel", "Falcon", "Hound", "Ox", "Serpent"],
adjective: ["New", "Good", "High", "Old", "Great", "Big", "Young", "Major", "Strong", "Happy", "Last", "Main", "Huge", "Far", "Beautiful", "Wild", "Fair", "Prime", "Crazy", "Ancient", "Golden", "Proud", "Secret", "Lucky", "Sad", "Silent", "Latter", "Severe", "Fat", "Holy", "Pure", "Aggressive", "Honest", "Giant", "Mad", "Pregnant", "Distant", "Lost", "Broken", "Blind", "Friendly", "Unknown", "Sleeping", "Slumbering", "Loud", "Hungry", "Wise", "Worried", "Sacred", "Magical", "Superior", "Patient", "Dead", "Deadly", "Peaceful", "Grateful", "Frozen", "Evil", "Scary", "Burning", "Divine", "Bloody", "Dying", "Waking", "Brutal", "Unhappy", "Calm", "Cruel", "Favorable", "Blond", "Explicit", "Disturbing", "Devastating", "Brave", "Sunny", "Troubled", "Flying", "Sustainable", "Marine", "Fatal", "Inherent", "Selected", "Naval", "Cheerful", "Almighty", "Benevolent", "Eternal", "Immutable", "Infallible"], adjective: ["New", "Good", "High", "Old", "Great", "Big", "Young", "Major", "Strong", "Happy", "Last", "Main", "Huge", "Far", "Beautiful", "Wild", "Fair", "Prime", "Crazy", "Ancient", "Proud", "Secret", "Lucky", "Sad", "Silent", "Latter", "Severe", "Fat", "Holy", "Pure", "Aggressive", "Honest", "Giant", "Mad", "Pregnant", "Distant", "Lost", "Broken", "Blind", "Friendly", "Unknown", "Sleeping", "Slumbering", "Loud", "Hungry", "Wise", "Worried", "Sacred", "Magical", "Superior", "Patient", "Dead", "Deadly", "Peaceful", "Grateful", "Frozen", "Evil", "Scary", "Burning", "Divine", "Bloody", "Dying", "Waking", "Brutal", "Unhappy", "Calm", "Cruel", "Favorable", "Blond", "Explicit", "Disturbing", "Devastating", "Brave", "Sunny", "Troubled", "Flying", "Sustainable", "Marine", "Fatal", "Inherent", "Selected", "Naval", "Cheerful", "Almighty", "Benevolent", "Eternal", "Immutable", "Infallible"],
genitive: ["Day", "Life", "Death", "Night", "Home", "Fog", "Snow", "Winter", "Summer", "Cold", "Springs", "Gates", "Nature", "Thunder", "Lightning", "War", "Ice", "Frost", "Fire", "Doom", "Fate", "Pain", "Heaven", "Justice", "Light", "Love", "Time", "Victory"], genitive: ["Day", "Life", "Death", "Night", "Home", "Fog", "Snow", "Winter", "Summer", "Cold", "Springs", "Gates", "Nature", "Thunder", "Lightning", "War", "Ice", "Frost", "Fire", "Doom", "Fate", "Pain", "Heaven", "Justice", "Light", "Love", "Time", "Victory"],
theGenitive: ["World", "Word", "South", "West", "North", "East", "Sun", "Moon", "Peak", "Fall", "Dawn", "Eclipse", "Abyss", "Blood", "Tree", "Earth", "Harvest", "Rainbow", "Sea", "Sky", "Stars", "Storm", "Underworld", "Wild"], theGenitive: ["World", "Word", "South", "West", "North", "East", "Sun", "Moon", "Peak", "Fall", "Dawn", "Eclipse", "Abyss", "Blood", "Tree", "Earth", "Harvest", "Rainbow", "Sea", "Sky", "Stars", "Storm", "Underworld", "Wild"],
color: ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"] color: ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]

View file

@ -246,6 +246,51 @@ function saveGeoJSON() {
}); });
} }
function saveGeoJSON_Cells() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
const cells = pack.cells, v = pack.vertices;
cells.i.forEach(i => {
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [[";
cells.v[i].forEach(n => {
let x = mapCoordinates.lonW + (v.p[n][0] / graphWidth) * mapCoordinates.lonT;
let y = mapCoordinates.latN - (v.p[n][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
data += "["+x+","+y+"],";
});
// close the ring
let x = mapCoordinates.lonW + (v.p[cells.v[i][0]][0] / graphWidth) * mapCoordinates.lonT;
let y = mapCoordinates.latN - (v.p[cells.v[i][0]][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
data += "["+x+","+y+"]";
data += "]] },\n \"properties\": {\n";
let height = parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]]));
data += " \"id\": \""+i+"\",\n";
data += " \"height\": \""+height+"\",\n";
data += " \"biome\": \""+cells.biome[i]+"\",\n";
data += " \"type\": \""+pack.features[cells.f[i]].type+"\",\n";
data += " \"population\": \""+getFriendlyPopulation(i)+"\",\n";
data += " \"state\": \""+cells.state[i]+"\",\n";
data += " \"province\": \""+cells.province[i]+"\",\n";
data += " \"culture\": \""+cells.culture[i]+"\",\n";
data += " \"religion\": \""+cells.religion[i]+"\"\n";
data +=" }\n},\n";
});
data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
data += "]}";
const dataBlob = new Blob([data], {type: "application/json"});
const url = window.URL.createObjectURL(dataBlob);
const link = document.createElement("a");
document.body.appendChild(link);
link.download = getFileName("Cells") + ".geojson";
link.href = url;
link.click();
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
}
function saveGeoJSON_Roads() { function saveGeoJSON_Roads() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n"; let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
@ -297,6 +342,34 @@ function saveGeoJSON_Rivers() {
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000); window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
} }
function saveGeoJSON_Markers() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
markers._groups[0][0].childNodes.forEach(n => {
let x = mapCoordinates.lonW + (n.dataset.x / graphWidth) * mapCoordinates.lonT;
let y = mapCoordinates.latN - (n.dataset.y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Point\", \"coordinates\": ["+x+", "+y+"]";
data += " },\n \"properties\": {\n";
data += " \"id\": \""+n.id+"\",\n";
data += " \"type\": \""+n.dataset.id.substring(8)+"\"\n";
data +=" }\n},\n";
});
data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
data += "]}";
const dataBlob = new Blob([data], {type: "application/json"});
const url = window.URL.createObjectURL(dataBlob);
const link = document.createElement("a");
document.body.appendChild(link);
link.download = getFileName("Markers") + ".geojson";
link.href = url;
link.click();
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
}
function getRoadPoints(node) { function getRoadPoints(node) {
let points = []; let points = [];
const l = node.getTotalLength(); const l = node.getTotalLength();
@ -414,78 +487,6 @@ function getFileName(dataType) {
return name + " " + type + day + " " + time; return name + " " + type + day + " " + time;
} }
function saveGeoJSON_Cells() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
const cells = pack.cells, v = pack.vertices;
cells.i.forEach(i => {
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [[";
cells.v[i].forEach(n => {
let x = mapCoordinates.lonW + (v.p[n][0] / graphWidth) * mapCoordinates.lonT;
let y = mapCoordinates.latN - (v.p[n][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
data += "["+x+","+y+"],";
});
// close the ring
let x = mapCoordinates.lonW + (v.p[cells.v[i][0]][0] / graphWidth) * mapCoordinates.lonT;
let y = mapCoordinates.latN - (v.p[cells.v[i][0]][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
data += "["+x+","+y+"]";
data += "]] },\n \"properties\": {\n";
let height = parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]]));
data += " \"id\": \""+i+"\",\n";
data += " \"height\": \""+height+"\",\n";
data += " \"biome\": \""+cells.biome[i]+"\",\n";
data += " \"type\": \""+pack.features[cells.f[i]].type+"\",\n";
data += " \"population\": \""+getFriendlyPopulation(i)+"\",\n";
data += " \"state\": \""+cells.state[i]+"\",\n";
data += " \"province\": \""+cells.province[i]+"\",\n";
data += " \"culture\": \""+cells.culture[i]+"\",\n";
data += " \"religion\": \""+cells.religion[i]+"\"\n";
data +=" }\n},\n";
});
data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
data += "]}";
const dataBlob = new Blob([data], {type: "application/json"});
const url = window.URL.createObjectURL(dataBlob);
const link = document.createElement("a");
document.body.appendChild(link);
link.download = getFileName("Cells") + ".geojson";
link.href = url;
link.click();
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
}
function saveGeoJSON_Markers() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
markers._groups[0][0].childNodes.forEach(n => {
let x = mapCoordinates.lonW + (n.dataset.x / graphWidth) * mapCoordinates.lonT;
let y = mapCoordinates.latN - (n.dataset.y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Point\", \"coordinates\": ["+x+", "+y+"]";
data += " },\n \"properties\": {\n";
data += " \"id\": \""+n.id+"\",\n";
data += " \"type\": \""+n.dataset.id.substring(8)+"\"\n";
data +=" }\n},\n";
});
data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
data += "]}";
const dataBlob = new Blob([data], {type: "application/json"});
const url = window.URL.createObjectURL(dataBlob);
const link = document.createElement("a");
document.body.appendChild(link);
link.download = getFileName("Markers") + ".geojson";
link.href = url;
link.click();
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
}
function uploadFile(file, callback) { function uploadFile(file, callback) {
uploadFile.timeStart = performance.now(); uploadFile.timeStart = performance.now();
@ -635,8 +636,6 @@ function parseLoadedData(data) {
ruler = viewbox.select("#ruler"); ruler = viewbox.select("#ruler");
fogging = viewbox.select("#fogging"); fogging = viewbox.select("#fogging");
debug = viewbox.select("#debug"); debug = viewbox.select("#debug");
freshwater = lakes.select("#freshwater");
salt = lakes.select("#salt");
burgLabels = labels.select("#burgLabels"); burgLabels = labels.select("#burgLabels");
}() }()
@ -775,7 +774,7 @@ function parseLoadedData(data) {
// 1.0 adds zones layer // 1.0 adds zones layer
zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none"); 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"); zones.attr("opacity", .6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt");
addZone(); addZones();
if (!markers.selectAll("*").size()) {addMarkers(); turnButtonOn("toggleMarkers");} if (!markers.selectAll("*").size()) {addMarkers(); turnButtonOn("toggleMarkers");}
// 1.0 add fogging layer (state focus) // 1.0 add fogging layer (state focus)
@ -824,6 +823,38 @@ function parseLoadedData(data) {
r.code = r.name.slice(0, 2); r.code = r.name.slice(0, 2);
}); });
} }
if (!document.getElementById("freshwater")) {
lakes.append("g").attr("id", "freshwater");
lakes.select("#freshwater").attr("opacity", .5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", .7).attr("filter", null);
}
if (!document.getElementById("salt")) {
lakes.append("g").attr("id", "salt");
lakes.select("#salt").attr("opacity", .5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", .7).attr("filter", null);
}
// v 1.1 added new lake and coast groups
if (!document.getElementById("sinkhole")) {
lakes.append("g").attr("id", "sinkhole");
lakes.append("g").attr("id", "frozen");
lakes.append("g").attr("id", "lava");
lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", .7).attr("filter", null);
lakes.select("#frozen").attr("opacity", .95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null);
lakes.select("#lava").attr("opacity", .7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)");
coastline.append("g").attr("id", "sea_island");
coastline.append("g").attr("id", "lake_island");
coastline.select("#sea_island").attr("opacity", .5).attr("stroke", "#1f3846").attr("stroke-width", .7).attr("filter", "url(#dropShadow)");
coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", .35).attr("filter", null);
}
// v 1.1 features stores more data
defs.select("#land").selectAll("path").remove();
defs.select("#water").selectAll("path").remove();
coastline.selectAll("path").remove();
lakes.selectAll("path").remove();
drawCoastline();
} }
}() }()

View file

@ -22,6 +22,7 @@ function editBiomes() {
// add listeners // add listeners
document.getElementById("biomesEditorRefresh").addEventListener("click", refreshBiomesEditor); document.getElementById("biomesEditorRefresh").addEventListener("click", refreshBiomesEditor);
document.getElementById("biomesEditStyle").addEventListener("click", () => editStyle("biomes"));
document.getElementById("biomesLegend").addEventListener("click", toggleLegend); document.getElementById("biomesLegend").addEventListener("click", toggleLegend);
document.getElementById("biomesPercentage").addEventListener("click", togglePercentageMode); document.getElementById("biomesPercentage").addEventListener("click", togglePercentageMode);
document.getElementById("biomesManually").addEventListener("click", enterBiomesCustomizationMode); document.getElementById("biomesManually").addEventListener("click", enterBiomesCustomizationMode);
@ -88,7 +89,7 @@ function editBiomes() {
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${b.color[i]}" class="zoneFill"></svg> <svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${b.color[i]}" class="zoneFill"></svg>
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false"> <input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
<span data-tip="Biome habitability percent" class="hide">%</span> <span data-tip="Biome habitability percent" class="hide">%</span>
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 step=1 class="biomeHabitability hide" value=${b.habitability[i]}> <input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 class="biomeHabitability hide" value=${b.habitability[i]}>
<span data-tip="Cells count" class="icon-check-empty hide"></span> <span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="biomeCells hide">${b.cells[i]}</div> <div data-tip="Cells count" class="biomeCells hide">${b.cells[i]}</div>
<span data-tip="Biome area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Biome area" style="padding-right: 4px" class="icon-map-o hide"></span>

View file

@ -14,6 +14,8 @@ function editBurg() {
const my = elSelected.attr("id") == d3.event.target.id ? "center bottom" : "center top+10"; const my = elSelected.attr("id") == d3.event.target.id ? "center bottom" : "center top+10";
const at = elSelected.attr("id") == d3.event.target.id ? "top" : "bottom"; const at = elSelected.attr("id") == d3.event.target.id ? "top" : "bottom";
document.getElementById("burgEditAnchorStyle").style.display = +pack.burgs[id].port ? "inline-block" : "none";
$("#burgEditor").dialog({ $("#burgEditor").dialog({
title: "Edit Burg: " + elSelected.text(), resizable: false, title: "Edit Burg: " + elSelected.text(), resizable: false,
position: {my, at, of: d3.event.target, collision: "fit"}, position: {my, at, of: d3.event.target, collision: "fit"},
@ -37,6 +39,12 @@ function editBurg() {
document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture); document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture);
document.getElementById("burgNameReRandom").addEventListener("click", generateNameRandom); document.getElementById("burgNameReRandom").addEventListener("click", generateNameRandom);
document.getElementById("burgStyleShow").addEventListener("click", showStyleSection);
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection);
document.getElementById("burgEditLabelStyle").addEventListener("click", editGroupLabelStyle);
document.getElementById("burgEditIconStyle").addEventListener("click", editGroupIconStyle);
document.getElementById("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle);
document.getElementById("burgSeeInMFCG").addEventListener("click", openInMFCG); document.getElementById("burgSeeInMFCG").addEventListener("click", openInMFCG);
document.getElementById("burgOpenCOA").addEventListener("click", openInIAHG); document.getElementById("burgOpenCOA").addEventListener("click", openInIAHG);
document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg); document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg);
@ -95,14 +103,18 @@ function editBurg() {
function createNewGroup() { function createNewGroup() {
if (!this.value) {tip("Please provide a valid group name", false, "error"); 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, ""); const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
if (Number.isFinite(+group.charAt(0))) group = "g" + group;
if (document.getElementById(group)) { if (document.getElementById(group)) {
tip("Element with this id already exists. Please provide a unique name", false, "error"); tip("Element with this id already exists. Please provide a unique name", false, "error");
return; return;
} }
if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error");
return;
}
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
const oldGroup = elSelected.node().parentNode.id; const oldGroup = elSelected.node().parentNode.id;
@ -211,6 +223,31 @@ function editBurg() {
changeName(); changeName();
} }
function showStyleSection() {
document.querySelectorAll("#burgEditor > button").forEach(el => el.style.display = "none");
document.getElementById("burgStyleSection").style.display = "inline-block";
}
function hideStyleSection() {
document.querySelectorAll("#burgEditor > button").forEach(el => el.style.display = "inline-block");
document.getElementById("burgStyleSection").style.display = "none";
}
function editGroupLabelStyle() {
const g = elSelected.node().parentNode.id;
editStyle("labels", g);
}
function editGroupIconStyle() {
const g = elSelected.node().parentNode.id;
editStyle("burgIcons", g);
}
function editGroupAnchorStyle() {
const g = elSelected.node().parentNode.id;
editStyle("anchors", g);
}
function openInMFCG() { function openInMFCG() {
const id = elSelected.attr("data-id"); const id = elSelected.attr("data-id");
const name = elSelected.text(); const name = elSelected.text();

View file

@ -20,6 +20,7 @@ function editBurgs() {
// add listeners // add listeners
document.getElementById("burgsEditorRefresh").addEventListener("click", refreshBurgsEditor); document.getElementById("burgsEditorRefresh").addEventListener("click", refreshBurgsEditor);
document.getElementById("burgsChart").addEventListener("click", showBurgsChart);
document.getElementById("burgsFilterState").addEventListener("change", burgsEditorAddLines); document.getElementById("burgsFilterState").addEventListener("change", burgsEditorAddLines);
document.getElementById("burgsFilterCulture").addEventListener("change", burgsEditorAddLines); document.getElementById("burgsFilterCulture").addEventListener("change", burgsEditorAddLines);
document.getElementById("regenerateBurgNames").addEventListener("click", regenerateNames); document.getElementById("regenerateBurgNames").addEventListener("click", regenerateNames);
@ -72,7 +73,7 @@ function editBurgs() {
const province = prov ? pack.provinces[prov].name : ""; const province = prov ? pack.provinces[prov].name : "";
const culture = pack.cultures[b.culture].name; const culture = pack.cultures[b.culture].name;
lines += `<div class="states" data-id=${b.i} data-name=${b.name} data-state=${state} data-province=${province} data-culture=${culture} data-population=${population} data-type=${type}> lines += `<div class="states" data-id=${b.i} data-name="${b.name}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
<span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span> <span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span>
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false"> <input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false">
<input data-tip="Burg province" class="burgState" value="${province}" disabled> <input data-tip="Burg province" class="burgState" value="${province}" disabled>
@ -250,6 +251,133 @@ function editBurgs() {
if (addNewBurg.classList.contains("pressed")) addNewBurg.classList.remove("pressed"); if (addNewBurg.classList.contains("pressed")) addNewBurg.classList.remove("pressed");
} }
function showBurgsChart() {
// build hierarchy tree
const states = pack.states.map(s => {
const color = s.color ? s.color : "#ccc";
const name = s.fullName ? s.fullName : s.name;
return {id:s.i, state: s.i ? 0 : null, color, name}
});
const burgs = pack.burgs.filter(b => b.i && !b.removed).map(b => {
const id = b.i+states.length-1;
const population = b.population;
const capital = b.capital;
const province = pack.cells.province[b.cell];
const parent = province ? province + states.length-1 : b.state;
return {id, i:b.i, state:b.state, culture:b.culture, province, parent, name:b.name, population, capital, x:b.x, y:b.y}
});
const data = states.concat(burgs);
const root = d3.stratify().parentId(d => d.state)(data)
.sum(d => d.population).sort((a, b) => b.value - a.value);
const width = 150 + 200 * uiSizeOutput.value, height = 150 + 200 * uiSizeOutput.value;
const margin = {top: 0, right: -50, bottom: -10, left: -50};
const w = width - margin.left - margin.right;
const h = height - margin.top - margin.bottom;
const treeLayout = d3.pack().size([w, h]).padding(3);
// prepare svg
alertMessage.innerHTML = `<select id="burgsTreeType" style="display:block; margin-left:13px; font-size:11px">
<option value="states" selected>Group by state</option>
<option value="cultures">Group by culture</option>
<option value="parent">Group by province and state</option>
<option value="provinces">Group by province</option></select>`;
alertMessage.innerHTML += `<div id='burgsInfo' class='chartInfo'>&#8205;</div>`;
const svg = d3.select("#alertMessage").insert("svg", "#burgsInfo").attr("id", "burgsTree")
.attr("width", width).attr("height", height-10).attr("stroke-width", 2);
const graph = svg.append("g").attr("transform", `translate(-50, -10)`);
document.getElementById("burgsTreeType").addEventListener("change", updateChart);
treeLayout(root);
const node = graph.selectAll("circle").data(root.leaves())
.join("circle").attr("data-id", d => d.data.i)
.attr("r", d => d.r).attr("fill", d => d.parent.data.color)
.attr("cx", d => d.x).attr("cy", d => d.y)
.on("mouseenter", d => showInfo(event, d))
.on("mouseleave", d => hideInfo(event, d))
.on("click", d => zoomTo(d.data.x, d.data.y, 8, 2000));
function showInfo(ev, d) {
d3.select(ev.target).transition().duration(1500).attr("stroke", "#c13119");
const name = d.data.name;
const parent = d.parent.data.name;
const population = si(d.value * populationRate.value * urbanization.value);
burgsInfo.innerHTML = `${name}. ${parent}. Population: ${population}`;
burgHighlightOn(ev);
tip("Click to zoom into view");
}
function hideInfo(ev) {
burgHighlightOff(ev);
if (!document.getElementById("burgsInfo")) return;
burgsInfo.innerHTML = "&#8205;";
d3.select(ev.target).transition().attr("stroke", "null");
tip("");
}
function updateChart() {
const getStatesData = () => pack.states.map(s => {
const color = s.color ? s.color : "#ccc";
const name = s.fullName ? s.fullName : s.name;
return {id:s.i, state: s.i ? 0 : null, color, name}
});
const getCulturesData = () => pack.cultures.map(c => {
const color = c.color ? c.color : "#ccc";
return {id:c.i, culture: c.i ? 0 : null, color, name:c.name}
});
const getParentData = () => {
const states = pack.states.map(s => {
const color = s.color ? s.color : "#ccc";
const name = s.fullName ? s.fullName : s.name;
return {id:s.i, parent: s.i ? 0 : null, color, name}
});
const provinces = pack.provinces.filter(p => p.i && !p.removed).map(p => {
return {id:p.i + states.length-1, parent: p.state, color:p.color, name:p.fullName}
});
return states.concat(provinces);
}
const getProvincesData = () => pack.provinces.map(p => {
const color = p.color ? p.color : "#ccc";
const name = p.fullName ? p.fullName : p.name;
return {id:p.i ? p.i : 0, province: p.i ? 0 : null, color, name}
});
const value = d => {
if (this.value === "states") return d.state;
if (this.value === "cultures") return d.culture;
if (this.value === "parent") return d.parent;
if (this.value === "provinces") return d.province;
}
const base = this.value === "states" ? getStatesData()
: this.value === "cultures" ? getCulturesData()
: this.value === "parent" ? getParentData() : getProvincesData();
burgs.forEach(b => b.id = b.i+base.length-1);
const data = base.concat(burgs);
const root = d3.stratify().parentId(d => value(d))(data)
.sum(d => d.population).sort((a, b) => b.value - a.value);
node.data(treeLayout(root).leaves()).transition().duration(2000)
.attr("data-id", d => d.data.i).attr("fill", d => d.parent.data.color)
.attr("cx", d => d.x).attr("cy", d => d.y).attr("r", d => d.r);
}
$("#alert").dialog({
title: "Burgs bubble chart", width: fitContent(),
position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, buttons: {},
close: () => {alertMessage.innerHTML = "";}
});
}
function downloadBurgsData() { function downloadBurgsData() {
let data = "Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation ("+heightUnit.value+"),Capital,Port\n"; // headers let data = "Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation ("+heightUnit.value+"),Capital,Port\n"; // headers
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
@ -267,11 +395,11 @@ function editBurgs() {
// add geography data // add geography data
data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ","; data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ",";
data += mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT + ","; // this is inverted in QGIS otherwise data += mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT + ","; // this is inverted in QGIS otherwise
data += parseInt(getFriendlyHeight([b.x,b.y])) + ","; data += parseInt(getHeight(pack.cells.h[b.cell])) + ",";
// add status data // add status data
data += b.capital ? "true," : "false,"; data += b.capital ? "capital," : ",";
data += b.port ? "true\n" : "false\n"; data += b.port ? "port\n" : "\n";
}); });
const dataBlob = new Blob([data], {type: "text/plain"}); const dataBlob = new Blob([data], {type: "text/plain"});

View file

@ -0,0 +1,187 @@
"use strict";
function editCoastline(node = d3.event.target) {
if (customization) return;
closeDialogs(".stable");
if (layerIsOn("toggleCells")) toggleCells();
$("#coastlineEditor").dialog({
title: "Edit Coastline", resizable: false,
position: {my: "center top+20", at: "top", of: d3.event, collision: "fit"},
close: closeCoastlineEditor
});
debug.append("g").attr("id", "vertices");
elSelected = d3.select(node);
selectCoastlineGroup(node);
drawCoastlineVertices();
viewbox.on("touchmove mousemove", null);
if (modules.editCoastline) return;
modules.editCoastline = true;
// add listeners
document.getElementById("coastlineGroupsShow").addEventListener("click", showGroupSection);
document.getElementById("coastlineGroup").addEventListener("change", changeCoastlineGroup);
document.getElementById("coastlineGroupAdd").addEventListener("click", toggleNewGroupInput);
document.getElementById("coastlineGroupName").addEventListener("change", createNewGroup);
document.getElementById("coastlineGroupRemove").addEventListener("click", removeCoastlineGroup);
document.getElementById("coastlineGroupsHide").addEventListener("click", hideGroupSection);
document.getElementById("coastlineEditStyle").addEventListener("click", editGroupStyle);
function drawCoastlineVertices() {
const f = +elSelected.attr("data-f"); // feature id
const v = pack.features[f].vertices; // coastline outer vertices
const l = pack.cells.i.length;
const c = [... new Set(v.map(v => pack.vertices.c[v]).flat())].filter(c => c < l);
debug.select("#vertices").selectAll("polygon").data(c).enter().append("polygon")
.attr("points", d => getPackPolygon(d)).attr("data-c", d => d);
debug.select("#vertices").selectAll("circle").data(v).enter().append("circle")
.attr("cx", d => pack.vertices.p[d][0]).attr("cy", d => pack.vertices.p[d][1])
.attr("r", .4).attr("data-v", d => d).call(d3.drag().on("drag", dragVertex))
.on("mousemove", () => tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights"));
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
const area = pack.features[f].area;
coastlineArea.innerHTML = si(area * distanceScaleInput.value ** 2) + unit;
}
function dragVertex() {
const x = rn(d3.event.x, 2), y = rn(d3.event.y, 2);
this.setAttribute("cx", x);
this.setAttribute("cy", y);
const v = +this.dataset.v;
pack.vertices.p[v] = [x, y];
debug.select("#vertices").selectAll("polygon").attr("points", d => getPackPolygon(d));
redrawCoastline();
}
function redrawCoastline() {
lineGen.curve(d3.curveBasisClosed);
const f = +elSelected.attr("data-f");
const vertices = pack.features[f].vertices;
const points = vertices.map(v => pack.vertices.p[v]);
const d = round(lineGen(points));
elSelected.attr("d", d);
defs.select("mask#land > path#land_"+f).attr("d", d); // update land mask
defs.select("mask#water > path#water_"+f).attr("d", d); // update water mask
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
const area = Math.abs(d3.polygonArea(points));
coastlineArea.innerHTML = si(area * distanceScaleInput.value ** 2) + unit;
}
function showGroupSection() {
document.querySelectorAll("#coastlineEditor > button").forEach(el => el.style.display = "none");
document.getElementById("coastlineGroupsSelection").style.display = "inline-block";
}
function hideGroupSection() {
document.querySelectorAll("#coastlineEditor > button").forEach(el => el.style.display = "inline-block");
document.getElementById("coastlineGroupsSelection").style.display = "none";
document.getElementById("coastlineGroupName").style.display = "none";
document.getElementById("coastlineGroupName").value = "";
document.getElementById("coastlineGroup").style.display = "inline-block";
}
function selectCoastlineGroup(node) {
const group = node.parentNode.id;
const select = document.getElementById("coastlineGroup");
select.options.length = 0; // remove all options
coastline.selectAll("g").each(function() {
select.options.add(new Option(this.id, this.id, false, this.id === group));
});
}
function changeCoastlineGroup() {
document.getElementById(this.value).appendChild(elSelected.node());
}
function toggleNewGroupInput() {
if (coastlineGroupName.style.display === "none") {
coastlineGroupName.style.display = "inline-block";
coastlineGroupName.focus();
coastlineGroup.style.display = "none";
} else {
coastlineGroupName.style.display = "none";
coastlineGroup.style.display = "inline-block";
}
}
function createNewGroup() {
if (!this.value) {tip("Please provide a valid group name"); return;}
const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
if (document.getElementById(group)) {
tip("Element with this id already exists. Please provide a unique name", false, "error");
return;
}
if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error");
return;
}
// just rename if only 1 element left
const oldGroup = elSelected.node().parentNode;
const basic = ["sea_island", "lake_island"].includes(oldGroup.id);
if (!basic && oldGroup.childElementCount === 1) {
document.getElementById("coastlineGroup").selectedOptions[0].remove();
document.getElementById("coastlineGroup").options.add(new Option(group, group, false, true));
oldGroup.id = group;
toggleNewGroupInput();
document.getElementById("coastlineGroupName").value = "";
return;
}
// create a new group
const newGroup = elSelected.node().parentNode.cloneNode(false);
document.getElementById("coastline").appendChild(newGroup);
newGroup.id = group;
document.getElementById("coastlineGroup").options.add(new Option(group, group, false, true));
document.getElementById(group).appendChild(elSelected.node());
toggleNewGroupInput();
document.getElementById("coastlineGroupName").value = "";
}
function removeCoastlineGroup() {
const group = elSelected.node().parentNode.id;
if (["sea_island", "lake_island"].includes(group)) {
tip("This is one of the default groups, it cannot be removed", false, "error");
return;
}
const count = elSelected.node().parentNode.childElementCount;
alertMessage.innerHTML = `Are you sure you want to remove the group?
All coastline elements of the group (${count}) will be moved under <i>sea_island</i> group`;
$("#alert").dialog({resizable: false, title: "Remove coastline group", width:"26em",
buttons: {
Remove: function() {
$(this).dialog("close");
const sea = document.getElementById("sea_island");
const groupEl = document.getElementById(group);
while (groupEl.childNodes.length) {
sea.appendChild(groupEl.childNodes[0]);
}
groupEl.remove();
document.getElementById("coastlineGroup").selectedOptions[0].remove();
document.getElementById("coastlineGroup").value = "sea_island";
},
Cancel: function() {$(this).dialog("close");}
}
});
}
function editGroupStyle() {
const g = elSelected.node().parentNode.id;
editStyle("coastline", g);
}
function closeCoastlineEditor() {
debug.select("#vertices").remove();
unselect();
}
}

View file

@ -22,6 +22,7 @@ function editCultures() {
// add listeners // add listeners
document.getElementById("culturesEditorRefresh").addEventListener("click", refreshCulturesEditor); document.getElementById("culturesEditorRefresh").addEventListener("click", refreshCulturesEditor);
document.getElementById("culturesEditStyle").addEventListener("click", () => editStyle("cults"));
document.getElementById("culturesLegend").addEventListener("click", toggleLegend); document.getElementById("culturesLegend").addEventListener("click", toggleLegend);
document.getElementById("culturesPercentage").addEventListener("click", togglePercentageMode); document.getElementById("culturesPercentage").addEventListener("click", togglePercentageMode);
document.getElementById("culturesRecalculate").addEventListener("click", () => recalculateCultures(true)); document.getElementById("culturesRecalculate").addEventListener("click", () => recalculateCultures(true));
@ -62,7 +63,7 @@ function editCultures() {
const rural = c.rural * populationRate.value; const rural = c.rural * populationRate.value;
const urban = c.urban * populationRate.value * urbanization.value; const urban = c.urban * populationRate.value * urbanization.value;
const population = rn(rural + urban); const population = rn(rural + urban);
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`; const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to edit`;
totalArea += area; totalArea += area;
totalPopulation += population; totalPopulation += population;
@ -124,6 +125,7 @@ function editCultures() {
body.querySelectorAll("div > input.statePower").forEach(el => el.addEventListener("input", cultureChangeExpansionism)); body.querySelectorAll("div > input.statePower").forEach(el => el.addEventListener("input", cultureChangeExpansionism));
body.querySelectorAll("div > select.cultureType").forEach(el => el.addEventListener("change", cultureChangeType)); body.querySelectorAll("div > select.cultureType").forEach(el => el.addEventListener("change", cultureChangeType));
body.querySelectorAll("div > select.cultureBase").forEach(el => el.addEventListener("change", cultureChangeBase)); body.querySelectorAll("div > select.cultureBase").forEach(el => el.addEventListener("change", cultureChangeBase));
body.querySelectorAll("div > div.culturePopulation").forEach(el => el.addEventListener("click", changePopulation));
body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.addEventListener("click", cultureRegenerateBurgs)); body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.addEventListener("click", cultureRegenerateBurgs));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", cultureRemove)); body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", cultureRemove));
@ -202,6 +204,66 @@ function editCultures() {
this.parentNode.dataset.base = pack.cultures[culture].base = v; this.parentNode.dataset.base = pack.cultures[culture].base = v;
} }
function changePopulation() {
const culture = +this.parentNode.dataset.id;
const c = pack.cultures[culture];
if (!c.cells) {tip("Culture does not have any cells, cannot change population", false, "error"); return;}
const rural = rn(c.rural * populationRate.value);
const urban = rn(c.urban * populationRate.value * urbanization.value);
const total = rural + urban;
const l = n => Number(n).toLocaleString();
const burgs = pack.burgs.filter(b => !b.removed && b.culture === culture);
alertMessage.innerHTML = `
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length?'':"disabled"}>
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function() {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
if (isNaN(totalNew)) return;
totalPop.innerHTML = l(totalNew);
totalPopPerc.innerHTML = rn(totalNew / total * 100);
}
ruralPop.oninput = () => update();
urbanPop.oninput = () => update();
$("#alert").dialog({
resizable: false, title: "Change culture population", width: "24em", buttons: {
Apply: function() {applyPopulationChange(); $(this).dialog("close");},
Cancel: function() {$(this).dialog("close");}
}, position: {my: "center", at: "center", of: "svg"}
});
function applyPopulationChange() {
const ruralChange = rn(ruralPop.value / rural, 4);
if (isFinite(ruralChange) && ruralChange !== 1) {
const cells = pack.cells.i.filter(i => pack.cells.culture[i] === culture);
cells.forEach(i => pack.cells.pop[i] *= ruralChange);
}
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
const points = ruralPop.value / populationRate.value;
const cells = pack.cells.i.filter(i => pack.cells.culture[i] === culture);
const pop = rn(points / cells.length);
cells.forEach(i => pack.cells.pop[i] = pop);
}
const urbanChange = rn(urbanPop.value / urban, 4);
if (isFinite(urbanChange) && urbanChange !== 1) {
burgs.forEach(b => b.population *= urbanChange);
}
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
const points = urbanPop.value / populationRate.value / urbanization.value;
const population = rn(points / burgs.length);
burgs.forEach(b => b.population = population);
}
refreshCulturesEditor();
}
}
function cultureRegenerateBurgs() { function cultureRegenerateBurgs() {
if (customization === 4) return; if (customization === 4) return;
const culture = +this.parentNode.dataset.id; const culture = +this.parentNode.dataset.id;

View file

@ -34,6 +34,7 @@ function editDiplomacy() {
// add listeners // add listeners
document.getElementById("diplomacyEditorRefresh").addEventListener("click", refreshDiplomacyEditor); document.getElementById("diplomacyEditorRefresh").addEventListener("click", refreshDiplomacyEditor);
document.getElementById("diplomacyEditStyle").addEventListener("click", () => editStyle("regions"));
document.getElementById("diplomacyRegenerate").addEventListener("click", regenerateRelations); document.getElementById("diplomacyRegenerate").addEventListener("click", regenerateRelations);
document.getElementById("diplomacyMatrix").addEventListener("click", showRelationsMatrix); document.getElementById("diplomacyMatrix").addEventListener("click", showRelationsMatrix);
document.getElementById("diplomacyHistory").addEventListener("click", showRelationsHistory); document.getElementById("diplomacyHistory").addEventListener("click", showRelationsHistory);

View file

@ -15,16 +15,25 @@ function restoreDefaultEvents() {
// on viewbox click event - run function based on target // on viewbox click event - run function based on target
function clicked() { function clicked() {
const el = d3.event.target; const el = d3.event.target;
if (!el || !el.parentElement || !el.parentElement.parentElement) return; if (!el || !el.parentElement || !el.parentElement.parentElement) return;
const parent = el.parentElement, grand = parent.parentElement; const parent = el.parentElement, grand = parent.parentElement;
if (parent.id === "rivers") editRiver(); else const p = d3.mouse(this);
if (grand.id === "routes") editRoute(); else const i = findCell(p[0], p[1]);
if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel(); else
if (grand.id === "burgLabels") editBurg(); else if (parent.id === "rivers") editRiver();
if (grand.id === "burgIcons") editBurg(); else else if (grand.id === "routes") editRoute();
if (parent.id === "terrain") editReliefIcon(); else else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel();
if (parent.id === "markers") editMarker(); else if (grand.id === "burgLabels") editBurg();
else if (grand.id === "burgIcons") editBurg();
else if (parent.id === "terrain") editReliefIcon();
else if (parent.id === "markers") editMarker();
else if (grand.id === "coastline") editCoastline();
else if (pack.cells.t[i] === 1) {
const node = document.getElementById("island_"+pack.cells.f[i]);
editCoastline(node);
}
else if (grand.id === "lakes") editLake();
} }
// clear elSelected variable // clear elSelected variable

View file

@ -45,7 +45,7 @@ function showDataTip(e) {
function moved() { function moved() {
const point = d3.mouse(this); const point = d3.mouse(this);
const i = findCell(point[0], point[1]); // pack ell id const i = findCell(point[0], point[1]); // pack cell id
if (i === undefined) return; if (i === undefined) return;
showNotes(d3.event, i); showNotes(d3.event, i);
const g = findGridCell(point[0], point[1]); // grid cell id const g = findGridCell(point[0], point[1]); // grid cell id
@ -80,6 +80,7 @@ function showMapTooltip(point, e, i, g) {
const group = path[path.length - 7].id; const group = path[path.length - 7].id;
const subgroup = path[path.length - 8].id; const subgroup = path[path.length - 8].id;
const land = pack.cells.h[i] >= 20; const land = pack.cells.h[i] >= 20;
//const type = pack.features[cells.f[i]].type;
// specific elements // specific elements
if (group === "rivers") {tip("Click to edit the River"); return;} if (group === "rivers") {tip("Click to edit the River"); return;}
@ -96,8 +97,8 @@ function showMapTooltip(point, e, i, g) {
} }
if (subgroup === "burgIcons") {tip("Click to edit the Burg"); return;} if (subgroup === "burgIcons") {tip("Click to edit the Burg"); return;}
if (subgroup === "burgLabels") {tip("Click to edit the Burg"); return;} if (subgroup === "burgLabels") {tip("Click to edit the Burg"); return;}
if (subgroup === "freshwater" && !land) {tip("Freshwater lake"); return;} if (group === "lakes" && !land) {tip(`${capitalize(subgroup)} lake. Click to edit`); return;}
if (subgroup === "salt" && !land) {tip("Salt lake"); return;} if (group === "coastline") {tip("Click to edit the coastline"); return;}
if (group === "zones") {tip(path[path.length-8].dataset.description); return;} if (group === "zones") {tip(path[path.length-8].dataset.description); return;}
// covering elements // covering elements
@ -118,6 +119,7 @@ function showMapTooltip(point, e, i, g) {
} else } else
if (layerIsOn("toggleCultures") && pack.cells.culture[i]) tip("Culture: " + pack.cultures[pack.cells.culture[i]].name); else if (layerIsOn("toggleCultures") && pack.cells.culture[i]) tip("Culture: " + pack.cultures[pack.cells.culture[i]].name); else
if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point)); if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point));
//if (pack.cells.t[i] === 1 && !tooltip.textContent) tip("Click to edit the coastline");
} }
// get cell info on mouse move // get cell info on mouse move
@ -229,6 +231,42 @@ function applyOption(select, option) {
select.value = option; select.value = option;
} }
// show info about the generator in a popup
function showInfo() {
const Discord = link("https://discordapp.com/invite/X7E84HU", "Discord");
const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit")
const Patreon = link("https://www.patreon.com/azgaar", "Patreon");
const Trello = link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Trello");
const QuickStart = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial", "Quick start tutorial");
const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page");
alertMessage.innerHTML = `
<b>Fantasy Map Generator</b> (FMG) is an open-source application, it means the code is published an anyone can use it.
In case of FMG is also means that you own all created maps and can use them as you wish, you can even sell them.
<p>The development is supported by community, you can donate on ${Patreon}.
You can also help creating overviews, tutorials and spreding the word about the Generator.</p>
<p>The best way to get help is to contact the community on ${Discord} and ${Reddit}.
Before asking questions, please check out the ${QuickStart} and the ${QAA}.</p>
<p>You can track the development process on ${Trello}.</p>
Links:
<ul style="columns:2">
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator", "GitHub repository")}</li>
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE", "License")}</li>
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "Changelog")}</li>
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys", "Hotkeys")}</li>
</ul>`;
$("#alert").dialog({resizable: false, title: document.title, width: "28em",
buttons: {OK: function() {$(this).dialog("close");}},
position: {my: "center", at: "center", of: "svg"}
});
}
// prevent default browser behavior for FMG-used hotkeys // prevent default browser behavior for FMG-used hotkeys
document.addEventListener("keydown", event => { document.addEventListener("keydown", event => {
if ([112, 113, 117, 120, 9].includes(event.keyCode)) event.preventDefault(); // F1, F2, F6, F9, Tab if ([112, 113, 117, 120, 9].includes(event.keyCode)) event.preventDefault(); // F1, F2, F6, F9, Tab
@ -242,13 +280,14 @@ document.addEventListener("keyup", event => {
event.stopPropagation(); event.stopPropagation();
const key = event.keyCode, ctrl = event.ctrlKey, shift = event.shiftKey, meta = event.metaKey; const key = event.keyCode, ctrl = event.ctrlKey, shift = event.shiftKey, meta = event.metaKey;
if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs if (key === 112) showInfo(); // "F1" to show info
else if (key === 9) toggleOptions(event); // Tab to toggle options
else if (key === 113) regeneratePrompt(); // "F2" for new map else if (key === 113) regeneratePrompt(); // "F2" for new map
else if (key === 46) removeElementOnKey(); // "Delete" to remove the selected element else if (key === 113) regeneratePrompt(); // "F2" for a new map
else if (key === 117) quickSave(); // "F6" for quick save else if (key === 117) quickSave(); // "F6" for quick save
else if (key === 120) quickLoad(); // "F9" for quick load else if (key === 120) quickLoad(); // "F9" for quick load
else if (key === 9) toggleOptions(event); // Tab to toggle options
else if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
else if (key === 46) removeElementOnKey(); // "Delete" to remove the selected element
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

View file

@ -11,8 +11,8 @@ function editHeightmap() {
<p>If you need to change the coastline and keep the data, you may try the <i>risk</i> edit option. <p>If you need to change the coastline and keep the data, you may try the <i>risk</i> edit option.
The data will be restored as much as possible, but the coastline change can cause unexpected fluctuations and errors.</p> The data will be restored as much as possible, but the coastline change can cause unexpected fluctuations and errors.</p>
<p>Check out <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization" target="_blank">wiki</a> for guidance.</p> <p>Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>
<p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before edditing the heightmap!</p>`; <p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before edditing the heightmap!</p>`;
$("#alert").dialog({resizable: false, title: "Edit Heightmap", width: "28em", $("#alert").dialog({resizable: false, title: "Edit Heightmap", width: "28em",
@ -62,7 +62,7 @@ function editHeightmap() {
} else if (type === "risk") { } else if (type === "risk") {
terrs.attr("mask", null); terrs.attr("mask", null);
defs.selectAll("#land, #water").selectAll("path").remove(); defs.selectAll("#land, #water").selectAll("path").remove();
viewbox.selectAll("#coastline *, #lakes *, #oceanLayers path").remove(); viewbox.selectAll("#coastline path, #lakes path, #oceanLayers path").remove();
changeOnlyLand.checked = false; changeOnlyLand.checked = false;
} }
@ -174,8 +174,8 @@ function getHeight(h) {
drawStates(); drawStates();
drawBorders(); drawBorders();
BurgsAndStates.drawStateLabels(); BurgsAndStates.drawStateLabels();
addZone();
addMarkers(); addMarkers();
addZones();
console.timeEnd("regenerateErasedData"); console.timeEnd("regenerateErasedData");
console.groupEnd("Edit Heightmap"); console.groupEnd("Edit Heightmap");
} }
@ -193,7 +193,6 @@ function getHeight(h) {
terrs.attr("mask", "url(#land)"); terrs.attr("mask", "url(#land)");
// assign pack data to grid cells // assign pack data to grid cells
const change = changeHeights.checked;
const l = grid.cells.i.length; const l = grid.cells.i.length;
const biome = new Uint8Array(l); const biome = new Uint8Array(l);
const conf = new Uint8Array(l); const conf = new Uint8Array(l);
@ -247,10 +246,9 @@ function getHeight(h) {
reGraph(); reGraph();
drawCoastline(); drawCoastline();
if (change) { if (changeHeights.checked) {
elevateLakes(); elevateLakes();
Rivers.generate(); Rivers.generate();
defineBiomes();
} }
// assign saved pack data from grid back to pack // assign saved pack data from grid back to pack
@ -265,25 +263,21 @@ function getHeight(h) {
pack.cells.culture = new Uint16Array(n); pack.cells.culture = new Uint16Array(n);
pack.cells.religion = new Uint16Array(n); pack.cells.religion = new Uint16Array(n);
if (!change) { pack.cells.r = new Uint16Array(n);
pack.cells.r = new Uint16Array(n); pack.cells.conf = new Uint8Array(n);
pack.cells.conf = new Uint8Array(n); pack.cells.fl = new Uint16Array(n);
pack.cells.fl = new Uint16Array(n); pack.cells.biome = new Uint8Array(n);
pack.cells.biome = new Uint8Array(n);
}
for (const i of pack.cells.i) { for (const i of pack.cells.i) {
const g = pack.cells.g[i]; const g = pack.cells.g[i];
const land = pack.cells.h[i] >= 20; const land = pack.cells.h[i] >= 20;
if (!change) { pack.cells.r[i] = r[g];
pack.cells.r[i] = r[g]; pack.cells.conf[i] = conf[g];
pack.cells.conf[i] = conf[g]; pack.cells.fl[i] = fl[g];
pack.cells.fl[i] = fl[g]; if (land && !biome[g]) pack.cells.biome[i] = getBiomeId(grid.cells.prec[g], grid.cells.temp[g]);
if (land && !biome[g]) pack.cells.biome[i] = getBiomeId(grid.cells.prec[g], grid.cells.temp[g]); else else if (!land && biome[g]) pack.cells.biome[i] = 0;
if (!land && biome[g]) pack.cells.biome[i] = 0; else else pack.cells.biome[i] = biome[g];
pack.cells.biome[i] = biome[g];
}
if (!land) continue; if (!land) continue;
pack.cells.culture[i] = culture[g]; pack.cells.culture[i] = culture[g];
@ -939,8 +933,7 @@ function getHeight(h) {
document.getElementById("imageToLoad").addEventListener("change", loadImage); document.getElementById("imageToLoad").addEventListener("change", loadImage);
document.getElementById("convertAutoLum").addEventListener("click", () => autoAssing("lum")); document.getElementById("convertAutoLum").addEventListener("click", () => autoAssing("lum"));
document.getElementById("convertAutoHue").addEventListener("click", () => autoAssing("hue")); document.getElementById("convertAutoHue").addEventListener("click", () => autoAssing("hue"));
document.getElementById("convertColorsPlus").addEventListener("click", () => changeConvertColorsNumber(1)); document.getElementById("convertColorsButton").addEventListener("click", setConvertColorsNumber);
document.getElementById("convertColorsMinus").addEventListener("click", () => changeConvertColorsNumber(-1));
document.getElementById("convertComplete").addEventListener("click", () => $("#imageConverter").dialog("close")); document.getElementById("convertComplete").addEventListener("click", () => $("#imageConverter").dialog("close"));
document.getElementById("convertOverlay").addEventListener("input", function() {setOverlayOpacity(this.value)}); document.getElementById("convertOverlay").addEventListener("input", function() {setOverlayOpacity(this.value)});
document.getElementById("convertOverlayNumber").addEventListener("input", function() {setOverlayOpacity(this.value)}); document.getElementById("convertOverlayNumber").addEventListener("input", function() {setOverlayOpacity(this.value)});
@ -1003,6 +996,8 @@ function getHeight(h) {
unassignedContainer.selectAll("div").data(unassigned).enter().append("div") unassignedContainer.selectAll("div").data(unassigned).enter().append("div")
.attr("data-color", i => i).style("background-color", i => i) .attr("data-color", i => i).style("background-color", i => i)
.attr("class", "color-div").on("click", colorClicked); .attr("class", "color-div").on("click", colorClicked);
convertColors.value = unassigned.length;
} }
function mapClicked() { function mapClicked() {
@ -1078,9 +1073,11 @@ function getHeight(h) {
colorsAssigned.style.display = "block"; colorsAssigned.style.display = "block";
colorsUnassigned.style.display = "none"; colorsUnassigned.style.display = "none";
} }
function changeConvertColorsNumber(change) { function setConvertColorsNumber() {
const number = Math.max(Math.min(+convertColors.value + change, 255), 3); const number = +prompt(`Please provide a desired number of colors. Min value is 3, max is 255. An actual number depends on color scheme and may vary from desired number`,
convertColors.value);
if (Number.isNaN(number) || number < 3 || number > 255) {tip("The number should be an integer in 3-255 range", false, "error"); return;}
convertColors.value = number; convertColors.value = number;
heightsFromImage(number); heightsFromImage(number);
} }
@ -1190,4 +1187,4 @@ function getHeight(h) {
} }
} }
} }

View file

@ -34,7 +34,9 @@ function editLabel() {
document.getElementById("labelTextShow").addEventListener("click", showTextSection); document.getElementById("labelTextShow").addEventListener("click", showTextSection);
document.getElementById("labelTextHide").addEventListener("click", hideTextSection); document.getElementById("labelTextHide").addEventListener("click", hideTextSection);
document.getElementById("labelText").addEventListener("input", changeText); document.getElementById("labelText").addEventListener("input", changeText);
document.getElementById("labelTextRandom").addEventListener("click", generateRandomName); document.getElementById("labelTextRandom").addEventListener("click", generateRandomName);
document.getElementById("labelEditStyle").addEventListener("click", editGroupStyle);
document.getElementById("labelSizeShow").addEventListener("click", showSizeSection); document.getElementById("labelSizeShow").addEventListener("click", showSizeSection);
document.getElementById("labelSizeHide").addEventListener("click", hideSizeSection); document.getElementById("labelSizeHide").addEventListener("click", hideSizeSection);
@ -181,14 +183,18 @@ function editLabel() {
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"); return;}
let group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, ""); const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
if (Number.isFinite(+group.charAt(0))) group = "g" + group;
if (document.getElementById(group)) { if (document.getElementById(group)) {
tip("Element with this id already exists. Please provide a unique name", false, "error"); tip("Element with this id already exists. Please provide a unique name", false, "error");
return; return;
} }
if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error");
return;
}
// just rename if only 1 element left // just rename if only 1 element left
const oldGroup = elSelected.node().parentNode; const oldGroup = elSelected.node().parentNode;
if (oldGroup !== "states" && oldGroup !== "addedLabels" && oldGroup.childElementCount === 1) { if (oldGroup !== "states" && oldGroup !== "addedLabels" && oldGroup.childElementCount === 1) {
@ -281,6 +287,11 @@ function editLabel() {
changeText(); changeText();
} }
function editGroupStyle() {
const g = elSelected.node().parentNode.id;
editStyle("labels", g);
}
function showSizeSection() { function showSizeSection() {
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "none"); document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "none");
document.getElementById("labelSizeSection").style.display = "inline-block"; document.getElementById("labelSizeSection").style.display = "inline-block";

193
modules/ui/lakes-editor.js Normal file
View file

@ -0,0 +1,193 @@
"use strict";
function editLake() {
if (customization) return;
closeDialogs(".stable");
if (layerIsOn("toggleCells")) toggleCells();
$("#lakeEditor").dialog({
title: "Edit Lake", resizable: false,
position: {my: "center top+20", at: "top", of: d3.event, collision: "fit"},
close: closeLakesEditor
});
const node = d3.event.target;
debug.append("g").attr("id", "vertices");
elSelected = d3.select(node);
selectLakeGroup(node);
drawLakeVertices();
viewbox.on("touchmove mousemove", null);
if (modules.editLake) return;
modules.editLake = true;
// add listeners
document.getElementById("lakeGroupsShow").addEventListener("click", showGroupSection);
document.getElementById("lakeGroup").addEventListener("change", changeLakeGroup);
document.getElementById("lakeGroupAdd").addEventListener("click", toggleNewGroupInput);
document.getElementById("lakeGroupName").addEventListener("change", createNewGroup);
document.getElementById("lakeGroupRemove").addEventListener("click", removeLakeGroup);
document.getElementById("lakeGroupsHide").addEventListener("click", hideGroupSection);
document.getElementById("lakeEditStyle").addEventListener("click", editGroupStyle);
document.getElementById("lakeLegend").addEventListener("click", editLakeLegend);
function drawLakeVertices() {
const f = +elSelected.attr("data-f"); // feature id
const v = pack.features[f].vertices; // lake outer vertices
const c = [... new Set(v.map(v => pack.vertices.c[v]).flat())];
debug.select("#vertices").selectAll("polygon").data(c).enter().append("polygon")
.attr("points", d => getPackPolygon(d)).attr("data-c", d => d);
debug.select("#vertices").selectAll("circle").data(v).enter().append("circle")
.attr("cx", d => pack.vertices.p[d][0]).attr("cy", d => pack.vertices.p[d][1])
.attr("r", .4).attr("data-v", d => d).call(d3.drag().on("drag", dragVertex))
.on("mousemove", () => tip("Drag to move the vertex, please use for fine-tuning only. Edit heightmap to change actual cell heights"));
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
const area = pack.features[f].area;
lakeArea.innerHTML = si(area * distanceScaleInput.value ** 2) + unit;
}
function dragVertex() {
const x = rn(d3.event.x, 2), y = rn(d3.event.y, 2);
this.setAttribute("cx", x);
this.setAttribute("cy", y);
const v = +this.dataset.v;
pack.vertices.p[v] = [x, y];
debug.select("#vertices").selectAll("polygon").attr("points", d => getPackPolygon(d));
redrawLake();
}
function redrawLake() {
lineGen.curve(d3.curveBasisClosed);
const f = +elSelected.attr("data-f");
const vertices = pack.features[f].vertices;
const points = vertices.map(v => pack.vertices.p[v]);
const d = round(lineGen(points));
elSelected.attr("d", d);
defs.select("mask#land > path#land_"+f).attr("d", d); // update land mask
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
const area = Math.abs(d3.polygonArea(points));
lakeArea.innerHTML = si(area * distanceScaleInput.value ** 2) + unit;
}
function showGroupSection() {
document.querySelectorAll("#lakeEditor > button").forEach(el => el.style.display = "none");
document.getElementById("lakeGroupsSelection").style.display = "inline-block";
}
function hideGroupSection() {
document.querySelectorAll("#lakeEditor > button").forEach(el => el.style.display = "inline-block");
document.getElementById("lakeGroupsSelection").style.display = "none";
document.getElementById("lakeGroupName").style.display = "none";
document.getElementById("lakeGroupName").value = "";
document.getElementById("lakeGroup").style.display = "inline-block";
}
function selectLakeGroup(node) {
const group = node.parentNode.id;
const select = document.getElementById("lakeGroup");
select.options.length = 0; // remove all options
lakes.selectAll("g").each(function() {
select.options.add(new Option(this.id, this.id, false, this.id === group));
});
}
function changeLakeGroup() {
document.getElementById(this.value).appendChild(elSelected.node());
}
function toggleNewGroupInput() {
if (lakeGroupName.style.display === "none") {
lakeGroupName.style.display = "inline-block";
lakeGroupName.focus();
lakeGroup.style.display = "none";
} else {
lakeGroupName.style.display = "none";
lakeGroup.style.display = "inline-block";
}
}
function createNewGroup() {
if (!this.value) {tip("Please provide a valid group name"); return;}
const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
if (document.getElementById(group)) {
tip("Element with this id already exists. Please provide a unique name", false, "error");
return;
}
if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error");
return;
}
// just rename if only 1 element left
const oldGroup = elSelected.node().parentNode;
const basic = ["freshwater", "salt", "sinkhole", "frozen", "lava"].includes(oldGroup.id);
if (!basic && oldGroup.childElementCount === 1) {
document.getElementById("lakeGroup").selectedOptions[0].remove();
document.getElementById("lakeGroup").options.add(new Option(group, group, false, true));
oldGroup.id = group;
toggleNewGroupInput();
document.getElementById("lakeGroupName").value = "";
return;
}
// create a new group
const newGroup = elSelected.node().parentNode.cloneNode(false);
document.getElementById("lakes").appendChild(newGroup);
newGroup.id = group;
document.getElementById("lakeGroup").options.add(new Option(group, group, false, true));
document.getElementById(group).appendChild(elSelected.node());
toggleNewGroupInput();
document.getElementById("lakeGroupName").value = "";
}
function removeLakeGroup() {
const group = elSelected.node().parentNode.id;
if (["freshwater", "salt", "sinkhole", "frozen", "lava"].includes(group)) {
tip("This is one of the default groups, it cannot be removed", false, "error");
return;
}
const count = elSelected.node().parentNode.childElementCount;
alertMessage.innerHTML = `Are you sure you want to remove the group?
All lakes of the group (${count}) will be turned into Freshwater`;
$("#alert").dialog({resizable: false, title: "Remove lake group", width:"26em",
buttons: {
Remove: function() {
$(this).dialog("close");
const freshwater = document.getElementById("freshwater");
const groupEl = document.getElementById(group);
while (groupEl.childNodes.length) {
freshwater.appendChild(groupEl.childNodes[0]);
}
groupEl.remove();
document.getElementById("lakeGroup").selectedOptions[0].remove();
document.getElementById("lakeGroup").value = "freshwater";
},
Cancel: function() {$(this).dialog("close");}
}
});
}
function editGroupStyle() {
const g = elSelected.node().parentNode.id;
editStyle("lakes", g);
}
function editLakeLegend() {
const id = elSelected.attr("id");
editNotes(id, id);
}
function closeLakesEditor() {
debug.select("#vertices").remove();
unselect();
}
}

View file

@ -113,11 +113,13 @@ function getCurrentPreset() {
savePresetButton.style.display = "inline-block"; savePresetButton.style.display = "inline-block";
} }
function toggleHeight() { function toggleHeight(event) {
if (!terrs.selectAll("*").size()) { if (!terrs.selectAll("*").size()) {
turnButtonOn("toggleHeight"); turnButtonOn("toggleHeight");
drawHeightmap(); drawHeightmap();
if (event && event.ctrlKey) editStyle("terrs");
} else { } else {
if (event && event.ctrlKey) {editStyle("terrs"); return;}
if (customization === 1) {tip("You cannot turn off the layer when heightmap is in edit mode", false, "error"); return;} if (customization === 1) {tip("You cannot turn off the layer when heightmap is in edit mode", false, "error"); return;}
turnButtonOff("toggleHeight"); turnButtonOff("toggleHeight");
terrs.selectAll("*").remove(); terrs.selectAll("*").remove();
@ -208,11 +210,13 @@ function getColor(value, scheme = getColorScheme()) {
return scheme(1 - (value < 20 ? value - 5 : value) / 100); return scheme(1 - (value < 20 ? value - 5 : value) / 100);
} }
function toggleTemp() { function toggleTemp(event) {
if (!temperature.selectAll("*").size()) { if (!temperature.selectAll("*").size()) {
turnButtonOn("toggleTemp"); turnButtonOn("toggleTemp");
drawTemp(); drawTemp();
if (event && event.ctrlKey) editStyle("temperature");
} else { } else {
if (event && event.ctrlKey) {editStyle("temperature"); return;}
turnButtonOff("toggleTemp"); turnButtonOff("toggleTemp");
temperature.selectAll("*").remove(); temperature.selectAll("*").remove();
} }
@ -310,11 +314,13 @@ function drawTemp() {
console.timeEnd("drawTemp"); console.timeEnd("drawTemp");
} }
function toggleBiomes() { function toggleBiomes(event) {
if (!biomes.selectAll("path").size()) { if (!biomes.selectAll("path").size()) {
turnButtonOn("toggleBiomes"); turnButtonOn("toggleBiomes");
drawBiomes(); drawBiomes();
if (event && event.ctrlKey) editStyle("biomes");
} else { } else {
if (event && event.ctrlKey) {editStyle("biomes"); return;}
biomes.selectAll("path").remove(); biomes.selectAll("path").remove();
turnButtonOff("toggleBiomes"); turnButtonOff("toggleBiomes");
} }
@ -365,11 +371,13 @@ function drawBiomes() {
} }
} }
function togglePrec() { function togglePrec(event) {
if (!prec.selectAll("circle").size()) { if (!prec.selectAll("circle").size()) {
turnButtonOn("togglePrec"); turnButtonOn("togglePrec");
drawPrec(); drawPrec();
if (event && event.ctrlKey) editStyle("prec");
} else { } else {
if (event && event.ctrlKey) {editStyle("prec"); return;}
turnButtonOff("togglePrec"); turnButtonOff("togglePrec");
const hide = d3.transition().duration(1000).ease(d3.easeSinIn); const hide = d3.transition().duration(1000).ease(d3.easeSinIn);
prec.selectAll("text").attr("opacity", 1).transition(hide).attr("opacity", 0); prec.selectAll("text").attr("opacity", 1).transition(hide).attr("opacity", 0);
@ -391,11 +399,13 @@ function drawPrec() {
.transition(show).attr("r", d => rn(Math.max(Math.sqrt(cells.prec[d] * .5), .8),2)); .transition(show).attr("r", d => rn(Math.max(Math.sqrt(cells.prec[d] * .5), .8),2));
} }
function togglePopulation() { function togglePopulation(event) {
if (!population.selectAll("line").size()) { if (!population.selectAll("line").size()) {
turnButtonOn("togglePopulation"); turnButtonOn("togglePopulation");
drawPopulation(); drawPopulation();
if (event && event.ctrlKey) editStyle("population");
} else { } else {
if (event && event.ctrlKey) {editStyle("population"); return;}
turnButtonOff("togglePopulation"); turnButtonOff("togglePopulation");
const hide = d3.transition().duration(1000).ease(d3.easeSinIn); const hide = d3.transition().duration(1000).ease(d3.easeSinIn);
population.select("#rural").selectAll("line").transition(hide).attr("y2", d => d[1]).remove(); population.select("#rural").selectAll("line").transition(hide).attr("y2", d => d[1]).remove();
@ -403,7 +413,7 @@ function togglePopulation() {
} }
} }
function drawPopulation() { function drawPopulation(event) {
population.selectAll("line").remove(); population.selectAll("line").remove();
const cells = pack.cells, p = cells.p, burgs = pack.burgs; const cells = pack.cells, p = cells.p, burgs = pack.burgs;
const show = d3.transition().duration(2000).ease(d3.easeSinIn); const show = d3.transition().duration(2000).ease(d3.easeSinIn);
@ -421,11 +431,13 @@ function drawPopulation() {
.transition(show).delay(500).attr("y2", d => d[2]); .transition(show).delay(500).attr("y2", d => d[2]);
} }
function toggleCells() { function toggleCells(event) {
if (!cells.selectAll("path").size()) { if (!cells.selectAll("path").size()) {
turnButtonOn("toggleCells"); turnButtonOn("toggleCells");
drawCells(); drawCells();
if (event && event.ctrlKey) editStyle("cells");
} else { } else {
if (event && event.ctrlKey) {editStyle("cells"); return;}
cells.selectAll("path").remove(); cells.selectAll("path").remove();
turnButtonOff("toggleCells"); turnButtonOff("toggleCells");
} }
@ -440,17 +452,19 @@ function drawCells() {
cells.append("path").attr("d", path); cells.append("path").attr("d", path);
} }
function toggleCultures() { function toggleCultures(event) {
if (!cults.selectAll("path").size()) { if (!cults.selectAll("path").size()) {
turnButtonOn("toggleCultures"); turnButtonOn("toggleCultures");
drawCultures(); drawCultures();
if (event && event.ctrlKey) editStyle("cults");
} else { } else {
if (event && event.ctrlKey) {editStyle("cults"); return;}
cults.selectAll("path").remove(); cults.selectAll("path").remove();
turnButtonOff("toggleCultures"); turnButtonOff("toggleCultures");
} }
} }
function drawCultures() { function drawCultures(event) {
console.time("drawCultures"); console.time("drawCultures");
cults.selectAll("path").remove(); cults.selectAll("path").remove();
@ -497,11 +511,13 @@ function drawCultures() {
console.timeEnd("drawCultures"); console.timeEnd("drawCultures");
} }
function toggleReligions() { function toggleReligions(event) {
if (!relig.selectAll("path").size()) { if (!relig.selectAll("path").size()) {
turnButtonOn("toggleReligions"); turnButtonOn("toggleReligions");
drawReligions(); drawReligions();
if (event && event.ctrlKey) editStyle("relig");
} else { } else {
if (event && event.ctrlKey) {editStyle("relig"); return;}
relig.selectAll("path").remove(); relig.selectAll("path").remove();
turnButtonOff("toggleReligions"); turnButtonOff("toggleReligions");
} }
@ -511,8 +527,9 @@ function drawReligions() {
console.time("drawReligions"); console.time("drawReligions");
relig.selectAll("path").remove(); relig.selectAll("path").remove();
const cells = pack.cells, vertices = pack.vertices, religions = pack.religions, n = cells.i.length; const cells = pack.cells, vertices = pack.vertices, religions = pack.religions, features = pack.features, n = cells.i.length;
const used = new Uint8Array(cells.i.length); const used = new Uint8Array(cells.i.length);
const fUsed = []; // already added features like lakes
const paths = new Array(religions.length).fill(""); const paths = new Array(religions.length).fill("");
for (const i of cells.i) { for (const i of cells.i) {
@ -520,9 +537,17 @@ function drawReligions() {
if (used[i]) continue; if (used[i]) continue;
used[i] = 1; used[i] = 1;
const r = cells.religion[i]; const r = cells.religion[i];
const onborder = cells.c[i].some(n => cells.religion[n] !== r); const onborder = cells.c[i].filter(n => cells.religion[n] !== r);
if (!onborder) continue; if (!onborder.length) continue;
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.religion[i] !== r)); const f = cells.f[onborder[0]];
if (fUsed[f]) continue;
if (features[f].type === "lake") {
paths[r] += defs.select("mask#land > path#land_"+f).attr("d");
fUsed[f] = 1;
continue;
}
const vertex = cells.v[i].find(v => vertices.c[v].some(n => cells.religion[n] !== r));
const chain = connectVertices(vertex, r); const chain = connectVertices(vertex, r);
if (chain.length < 3) continue; if (chain.length < 3) continue;
const points = chain.map(v => vertices.p[v]); const points = chain.map(v => vertices.p[v]);
@ -554,12 +579,14 @@ function drawReligions() {
console.timeEnd("drawReligions"); console.timeEnd("drawReligions");
} }
function toggleStates() { function toggleStates(event) {
if (!layerIsOn("toggleStates")) { if (!layerIsOn("toggleStates")) {
turnButtonOn("toggleStates"); turnButtonOn("toggleStates");
regions.attr("display", null); regions.attr("display", null);
drawStates(); drawStates();
if (event && event.ctrlKey) editStyle("regions");
} else { } else {
if (event && event.ctrlKey) {editStyle("regions"); return;}
regions.attr("display", "none").selectAll("path").remove(); regions.attr("display", "none").selectAll("path").remove();
turnButtonOff("toggleStates"); turnButtonOff("toggleStates");
} }
@ -735,21 +762,25 @@ function drawBorders() {
console.timeEnd("drawBorders"); console.timeEnd("drawBorders");
} }
function toggleBorders() { function toggleBorders(event) {
if (!layerIsOn("toggleBorders")) { if (!layerIsOn("toggleBorders")) {
turnButtonOn("toggleBorders"); turnButtonOn("toggleBorders");
$('#borders').fadeIn(); $('#borders').fadeIn();
if (event && event.ctrlKey) editStyle("borders");
} else { } else {
if (event && event.ctrlKey) {editStyle("borders"); return;}
turnButtonOff("toggleBorders"); turnButtonOff("toggleBorders");
$('#borders').fadeOut(); $('#borders').fadeOut();
} }
} }
function toggleProvinces() { function toggleProvinces(event) {
if (!layerIsOn("toggleProvinces")) { if (!layerIsOn("toggleProvinces")) {
turnButtonOn("toggleProvinces"); turnButtonOn("toggleProvinces");
drawProvinces(); drawProvinces();
if (event && event.ctrlKey) editStyle("provs");
} else { } else {
if (event && event.ctrlKey) {editStyle("provs"); return;}
provs.selectAll("*").remove(); provs.selectAll("*").remove();
turnButtonOff("toggleProvinces"); turnButtonOff("toggleProvinces");
} }
@ -828,12 +859,14 @@ function drawProvinces() {
console.timeEnd("drawProvinces"); console.timeEnd("drawProvinces");
} }
function toggleGrid() { function toggleGrid(event) {
if (!gridOverlay.selectAll("*").size()) { if (!gridOverlay.selectAll("*").size()) {
turnButtonOn("toggleGrid"); turnButtonOn("toggleGrid");
drawGrid(); drawGrid();
calculateFriendlyGridSize(); calculateFriendlyGridSize();
if (event && event.ctrlKey) editStyle("gridOverlay");
} else { } else {
if (event && event.ctrlKey) {editStyle("gridOverlay"); return;}
turnButtonOff("toggleGrid"); turnButtonOff("toggleGrid");
gridOverlay.selectAll("*").remove(); gridOverlay.selectAll("*").remove();
} }
@ -887,11 +920,13 @@ function drawGrid() {
console.timeEnd("drawGrid"); console.timeEnd("drawGrid");
} }
function toggleCoordinates() { function toggleCoordinates(event) {
if (!coordinates.selectAll("*").size()) { if (!coordinates.selectAll("*").size()) {
turnButtonOn("toggleCoordinates"); turnButtonOn("toggleCoordinates");
drawCoordinates(); drawCoordinates();
if (event && event.ctrlKey) editStyle("coordinates");
} else { } else {
if (event && event.ctrlKey) {editStyle("coordinates"); return;}
turnButtonOff("toggleCoordinates"); turnButtonOff("toggleCoordinates");
coordinates.selectAll("*").remove(); coordinates.selectAll("*").remove();
} }
@ -937,7 +972,7 @@ function getViewPoint(x, y) {
return pt.matrixTransform(view.getScreenCTM().inverse()); return pt.matrixTransform(view.getScreenCTM().inverse());
} }
function toggleCompass() { function toggleCompass(event) {
if (!layerIsOn("toggleCompass")) { if (!layerIsOn("toggleCompass")) {
turnButtonOn("toggleCompass"); turnButtonOn("toggleCompass");
$('#compass').fadeIn(); $('#compass').fadeIn();
@ -949,24 +984,28 @@ function toggleCompass() {
svg.select("g#rose > g#sL > line#sL1").attr("y1", -19000).attr("y2", 19000); svg.select("g#rose > g#sL > line#sL1").attr("y1", -19000).attr("y2", 19000);
svg.select("g#rose > g#sL > line#sL2").attr("x1", -19000).attr("x2", 19000); svg.select("g#rose > g#sL > line#sL2").attr("x1", -19000).attr("x2", 19000);
} }
if (event && event.ctrlKey) editStyle("compass");
} else { } else {
if (event && event.ctrlKey) {editStyle("compass"); return;}
$('#compass').fadeOut(); $('#compass').fadeOut();
turnButtonOff("toggleCompass"); turnButtonOff("toggleCompass");
} }
} }
function toggleRelief() { function toggleRelief(event) {
if (!layerIsOn("toggleRelief")) { if (!layerIsOn("toggleRelief")) {
turnButtonOn("toggleRelief"); turnButtonOn("toggleRelief");
if (!terrain.selectAll("*").size()) ReliefIcons(); if (!terrain.selectAll("*").size()) ReliefIcons();
$('#terrain').fadeIn(); $('#terrain').fadeIn();
if (event && event.ctrlKey) editStyle("terrain");
} else { } else {
if (event && event.ctrlKey) {editStyle("terrain"); return;}
$('#terrain').fadeOut(); $('#terrain').fadeOut();
turnButtonOff("toggleRelief"); turnButtonOff("toggleRelief");
} }
} }
function toggleTexture() { function toggleTexture(event) {
if (!layerIsOn("toggleTexture")) { if (!layerIsOn("toggleTexture")) {
turnButtonOn("toggleTexture"); turnButtonOn("toggleTexture");
// append default texture image selected by default. Don't append on load to not harm performance // append default texture image selected by default. Don't append on load to not harm performance
@ -976,88 +1015,106 @@ function toggleTexture() {
} }
$('#texture').fadeIn(); $('#texture').fadeIn();
zoom.scaleBy(svg, 1.00001); // enforce browser re-draw zoom.scaleBy(svg, 1.00001); // enforce browser re-draw
if (event && event.ctrlKey) editStyle("texture");
} else { } else {
if (event && event.ctrlKey) {editStyle("texture"); return;}
$('#texture').fadeOut(); $('#texture').fadeOut();
turnButtonOff("toggleTexture"); turnButtonOff("toggleTexture");
} }
} }
function toggleRivers() { function toggleRivers(event) {
if (!layerIsOn("toggleRivers")) { if (!layerIsOn("toggleRivers")) {
turnButtonOn("toggleRivers"); turnButtonOn("toggleRivers");
$('#rivers').fadeIn(); $('#rivers').fadeIn();
if (event && event.ctrlKey) editStyle("rivers");
} else { } else {
if (event && event.ctrlKey) {editStyle("rivers"); return;}
$('#rivers').fadeOut(); $('#rivers').fadeOut();
turnButtonOff("toggleRivers"); turnButtonOff("toggleRivers");
} }
} }
function toggleRoutes() { function toggleRoutes(event) {
if (!layerIsOn("toggleRoutes")) { if (!layerIsOn("toggleRoutes")) {
turnButtonOn("toggleRoutes"); turnButtonOn("toggleRoutes");
$('#routes').fadeIn(); $('#routes').fadeIn();
if (event && event.ctrlKey) editStyle("routes");
} else { } else {
if (event && event.ctrlKey) {editStyle("routes"); return;}
$('#routes').fadeOut(); $('#routes').fadeOut();
turnButtonOff("toggleRoutes"); turnButtonOff("toggleRoutes");
} }
} }
function toggleMarkers() { function toggleMarkers(event) {
if (!layerIsOn("toggleMarkers")) { if (!layerIsOn("toggleMarkers")) {
turnButtonOn("toggleMarkers"); turnButtonOn("toggleMarkers");
$('#markers').fadeIn(); $('#markers').fadeIn();
if (event && event.ctrlKey) editStyle("markers");
} else { } else {
if (event && event.ctrlKey) {editStyle("markers"); return;}
$('#markers').fadeOut(); $('#markers').fadeOut();
turnButtonOff("toggleMarkers"); turnButtonOff("toggleMarkers");
} }
} }
function toggleLabels() { function toggleLabels(event) {
if (!layerIsOn("toggleLabels")) { if (!layerIsOn("toggleLabels")) {
turnButtonOn("toggleLabels"); turnButtonOn("toggleLabels");
labels.style("display", null) labels.style("display", null)
invokeActiveZooming(); invokeActiveZooming();
if (event && event.ctrlKey) editStyle("labels");
} else { } else {
if (event && event.ctrlKey) {editStyle("labels"); return;}
turnButtonOff("toggleLabels"); turnButtonOff("toggleLabels");
labels.style("display", "none"); labels.style("display", "none");
} }
} }
function toggleIcons() { function toggleIcons(event) {
if (!layerIsOn("toggleIcons")) { if (!layerIsOn("toggleIcons")) {
turnButtonOn("toggleIcons"); turnButtonOn("toggleIcons");
$('#icons').fadeIn(); $('#icons').fadeIn();
if (event && event.ctrlKey) editStyle("burgIcons");
} else { } else {
if (event && event.ctrlKey) {editStyle("burgIcons"); return;}
turnButtonOff("toggleIcons"); turnButtonOff("toggleIcons");
$('#icons').fadeOut(); $('#icons').fadeOut();
} }
} }
function toggleRulers() { function toggleRulers(event) {
if (!layerIsOn("toggleRulers")) { if (!layerIsOn("toggleRulers")) {
turnButtonOn("toggleRulers"); turnButtonOn("toggleRulers");
$('#ruler').fadeIn(); $('#ruler').fadeIn();
if (event && event.ctrlKey) editStyle("ruler");
} else { } else {
if (event && event.ctrlKey) {editStyle("ruler"); return;}
$('#ruler').fadeOut(); $('#ruler').fadeOut();
turnButtonOff("toggleRulers"); turnButtonOff("toggleRulers");
} }
} }
function toggleScaleBar() { function toggleScaleBar(event) {
if (!layerIsOn("toggleScaleBar")) { if (!layerIsOn("toggleScaleBar")) {
turnButtonOn("toggleScaleBar"); turnButtonOn("toggleScaleBar");
$('#scaleBar').fadeIn(); $('#scaleBar').fadeIn();
if (event && event.ctrlKey) editUnits();
} else { } else {
if (event && event.ctrlKey) {editUnits(); return;}
$('#scaleBar').fadeOut(); $('#scaleBar').fadeOut();
turnButtonOff("toggleScaleBar"); turnButtonOff("toggleScaleBar");
} }
} }
function toggleZones() { function toggleZones(event) {
if (!layerIsOn("toggleZones")) { if (!layerIsOn("toggleZones")) {
turnButtonOn("toggleZones"); turnButtonOn("toggleZones");
$('#zones').fadeIn(); $('#zones').fadeIn();
if (event && event.ctrlKey) editStyle("zones");
} else { } else {
if (event && event.ctrlKey) {editStyle("zones"); return;}
turnButtonOff("toggleZones"); turnButtonOff("toggleZones");
$('#zones').fadeOut(); $('#zones').fadeOut();
} }

View file

@ -487,6 +487,8 @@ function editMarker() {
buttons: { buttons: {
Remove: function() { Remove: function() {
$(this).dialog("close"); $(this).dialog("close");
const index = notes.findIndex(n => n.id === elSelected.attr("id"));
if (index != -1) notes.splice(index, 1);
elSelected.remove(); elSelected.remove();
$("#markerEditor").dialog("close"); $("#markerEditor").dialog("close");
}, },

View file

@ -59,6 +59,7 @@ options.querySelector("div.tab").addEventListener("click", function(event) {
document.getElementById(id).classList.add("active"); document.getElementById(id).classList.add("active");
options.querySelectorAll(".tabcontent").forEach(e => e.style.display = "none"); options.querySelectorAll(".tabcontent").forEach(e => e.style.display = "none");
if (id === "layersTab") layersContent.style.display = "block"; else
if (id === "styleTab") styleContent.style.display = "block"; else if (id === "styleTab") styleContent.style.display = "block"; else
if (id === "optionsTab") optionsContent.style.display = "block"; else if (id === "optionsTab") optionsContent.style.display = "block"; else
if (id === "toolsTab") customization === 1 if (id === "toolsTab") customization === 1
@ -81,6 +82,22 @@ function collapse(e) {
} }
} }
// select element to be edited
function editStyle(element, group) {
showOptions();
styleTab.click();
styleElementSelect.value = element;
if (group) styleGroupSelect.options.add(new Option(group, group, true, true));
selectStyleElement();
styleElementSelect.classList.add("glow");
if (group) styleGroupSelect.classList.add("glow");
setTimeout(() => {
styleElementSelect.classList.remove("glow");
if (group) styleGroupSelect.classList.remove("glow");
}, 1500);
}
// Toggle style sections on element select // Toggle style sections on element select
styleElementSelect.addEventListener("change", selectStyleElement); styleElementSelect.addEventListener("change", selectStyleElement);
function selectStyleElement() { function selectStyleElement() {
@ -97,7 +114,7 @@ function selectStyleElement() {
// active group element // active group element
const group = styleGroupSelect.value; const group = styleGroupSelect.value;
if (sel == "ocean") el = oceanLayers.select("rect"); if (sel == "ocean") el = oceanLayers.select("rect");
else if (sel == "routes" || sel == "labels" || sel == "lakes" || sel == "anchors" || sel == "burgIcons" || sel == "borders") { else if (sel == "routes" || sel == "labels" || sel === "coastline" || sel == "lakes" || sel == "anchors" || sel == "burgIcons" || sel == "borders") {
el = d3.select("#"+sel).select("g#"+group).size() el = d3.select("#"+sel).select("g#"+group).size()
? d3.select("#"+sel).select("g#"+group) ? d3.select("#"+sel).select("g#"+group)
: d3.select("#"+sel).select("g"); : d3.select("#"+sel).select("g");
@ -168,7 +185,7 @@ function selectStyleElement() {
if (sel === "gridOverlay") styleGrid.style.display = "block"; if (sel === "gridOverlay") styleGrid.style.display = "block";
if (sel === "terrain") styleRelief.style.display = "block"; if (sel === "terrain") styleRelief.style.display = "block";
if (sel === "texture") styleTexture.style.display = "block"; if (sel === "texture") styleTexture.style.display = "block";
if (sel === "routes" || sel === "labels" || sel == "anchors" || sel == "burgIcons" || sel === "lakes" || sel === "borders") styleGroup.style.display = "block"; if (sel === "routes" || sel === "labels" || sel == "anchors" || sel == "burgIcons" || sel === "coastline" || sel === "lakes" || sel === "borders") styleGroup.style.display = "block";
if (sel === "markers") styleMarkers.style.display = "block"; if (sel === "markers") styleMarkers.style.display = "block";
if (sel === "population") { if (sel === "population") {
@ -250,8 +267,10 @@ function selectStyleElement() {
} }
if (sel === "coastline") { if (sel === "coastline") {
styleCoastline.style.display = "block"; if (styleGroupSelect.value === "sea_island") {
if (styleCoastlineAuto.checked) styleFilter.style.display = "none"; styleCoastline.style.display = "block";
if (styleCoastlineAuto.checked) styleFilter.style.display = "none";
}
} }
if (sel === "temperature") { if (sel === "temperature") {
@ -270,7 +289,7 @@ function selectStyleElement() {
// update group options // update group options
styleGroupSelect.options.length = 0; // remove all options styleGroupSelect.options.length = 0; // remove all options
if (sel === "routes" || sel === "labels" || sel === "lakes" || sel === "anchors" || sel === "burgIcons" || sel === "borders") { if (sel === "routes" || sel === "labels" || sel === "coastline" || sel === "lakes" || sel === "anchors" || sel === "burgIcons" || sel === "borders") {
document.getElementById(sel).querySelectorAll("g").forEach(el => { document.getElementById(sel).querySelectorAll("g").forEach(el => {
if (el.id === "burgLabels") return; if (el.id === "burgLabels") return;
const count = el.childElementCount; const count = el.childElementCount;
@ -670,6 +689,7 @@ optionsContent.addEventListener("input", function(event) {
else if (id === "densityInput" || id === "densityOutput") changeCellsDensity(+value); else if (id === "densityInput" || id === "densityOutput") changeCellsDensity(+value);
else if (id === "culturesInput") culturesOutput.value = value; else if (id === "culturesInput") culturesOutput.value = value;
else if (id === "culturesOutput") culturesInput.value = value; else if (id === "culturesOutput") culturesInput.value = value;
else if (id === "culturesSet") changeCultureSet();
else if (id === "regionsInput" || id === "regionsOutput") changeStatesNumber(value); else if (id === "regionsInput" || id === "regionsOutput") changeStatesNumber(value);
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;
@ -795,8 +815,15 @@ function changeCellsDensity(value) {
else densityOutput.style.color = "#038603"; else densityOutput.style.color = "#038603";
} }
function changeCultureSet() {
const max = culturesSet.selectedOptions[0].dataset.max;
culturesInput.max = culturesOutput.max = max
if (+culturesOutput.value > +max) culturesInput.value = culturesOutput.value = max;
}
function changeStatesNumber(value) { function changeStatesNumber(value) {
regionsInput.value = regionsOutput.value = value; regionsInput.value = regionsOutput.value = value;
regionsOutput.style.color = +value ? null : "#b12117";
burgLabels.select("#capitals").attr("data-size", Math.max(rn(6 - value / 20), 3)); burgLabels.select("#capitals").attr("data-size", Math.max(rn(6 - value / 20), 3));
labels.select("#countries").attr("data-size", Math.max(rn(18 - value / 6), 4)); labels.select("#countries").attr("data-size", Math.max(rn(18 - value / 6), 4));
} }
@ -880,6 +907,7 @@ function randomizeOptions() {
if (!locked("power")) powerInput.value = powerOutput.value = gauss(3, 2, 0, 10); if (!locked("power")) powerInput.value = powerOutput.value = gauss(3, 2, 0, 10);
if (!locked("neutral")) neutralInput.value = neutralOutput.value = rn(1 + Math.random(), 1); if (!locked("neutral")) neutralInput.value = neutralOutput.value = rn(1 + Math.random(), 1);
if (!locked("cultures")) culturesInput.value = culturesOutput.value = gauss(12, 3, 5, 30); if (!locked("cultures")) culturesInput.value = culturesOutput.value = gauss(12, 3, 5, 30);
changeCultureSet();
// 'Configure World' settings // 'Configure World' settings
if (!locked("prec")) precInput.value = precOutput.value = gauss(100, 20, 5, 500); if (!locked("prec")) precInput.value = precOutput.value = gauss(100, 20, 5, 500);

View file

@ -20,6 +20,7 @@ function editProvinces() {
// add listeners // add listeners
document.getElementById("provincesEditorRefresh").addEventListener("click", refreshProvincesEditor); document.getElementById("provincesEditorRefresh").addEventListener("click", refreshProvincesEditor);
document.getElementById("provincesEditStyle").addEventListener("click", () => editStyle("provs"));
document.getElementById("provincesFilterState").addEventListener("change", provincesEditorAddLines); document.getElementById("provincesFilterState").addEventListener("change", provincesEditorAddLines);
document.getElementById("provincesPercentage").addEventListener("click", togglePercentageMode); document.getElementById("provincesPercentage").addEventListener("click", togglePercentageMode);
document.getElementById("provincesChart").addEventListener("click", showChart); document.getElementById("provincesChart").addEventListener("click", showChart);
@ -37,6 +38,8 @@ function editProvinces() {
if (cl.contains("zoneFill")) changeFill(el); else if (cl.contains("zoneFill")) changeFill(el); else
if (cl.contains("icon-fleur")) provinceOpenCOA(p); else if (cl.contains("icon-fleur")) provinceOpenCOA(p); else
if (cl.contains("icon-star-empty")) capitalZoomIn(p); else if (cl.contains("icon-star-empty")) capitalZoomIn(p); else
if (cl.contains("icon-flag-empty")) declareProvinceIndependence(p); else
if (cl.contains("culturePopulation")) changePopulation(p); else
if (cl.contains("icon-pin")) focusOn(p, cl); else if (cl.contains("icon-pin")) focusOn(p, cl); else
if (cl.contains("icon-trash-empty")) removeProvince(p); if (cl.contains("icon-trash-empty")) removeProvince(p);
if (cl.contains("hoverButton") && cl.contains("stateName")) regenerateName(p, line); else if (cl.contains("hoverButton") && cl.contains("stateName")) regenerateName(p, line); else
@ -105,6 +108,7 @@ function editProvinces() {
const stateName = pack.states[p.state].name; const stateName = pack.states[p.state].name;
const capital = p.burg ? pack.burgs[p.burg].name : ''; const capital = p.burg ? pack.burgs[p.burg].name : '';
const separable = p.burg && p.burg !== pack.states[p.state].capital;
const focused = defs.select("#fog #focusProvince"+p.i).size(); const focused = defs.select("#fog #focusProvince"+p.i).size();
lines += `<div class="states" data-id=${p.i} data-name=${p.name} data-form=${p.formName} data-color="${p.color}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}> lines += `<div class="states" data-id=${p.i} data-name=${p.name} data-form=${p.formName} data-color="${p.color}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}>
<svg data-tip="Province fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${p.color}" class="zoneFill"></svg> <svg data-tip="Province fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${p.color}" class="zoneFill"></svg>
@ -120,6 +124,7 @@ function editProvinces() {
<div data-tip="Province area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="Province area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div> <div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
<span data-tip="Declare province independence (turn province into a new state)" class="icon-flag-empty ${separable ? '' : 'placeholder'} hide"></span>
<span data-tip="Toggle province focus" class="icon-pin ${focused?'':' inactive'} hide"></span> <span data-tip="Toggle province focus" class="icon-pin ${focused?'':' inactive'} hide"></span>
<span data-tip="Remove the province" class="icon-trash-empty hide"></span> <span data-tip="Remove the province" class="icon-trash-empty hide"></span>
</div>`; </div>`;
@ -201,6 +206,124 @@ function editProvinces() {
zoomTo(x, y, 8, 2000); zoomTo(x, y, 8, 2000);
} }
function declareProvinceIndependence(p) {
const states = pack.states, provinces = pack.provinces, cells = pack.cells;
const oldState = pack.provinces[p].state;
const newState = pack.states.length;
// turn burg into a capital
const burg = provinces[p].burg;
if (!burg) return;
pack.burgs[burg].capital = true;
pack.burgs[burg].state = newState;
moveBurgToGroup(burg, "cities");
// difine new state attributes
const center = provinces[p].center;
const culture = cells.culture[center];
const name = provinces[p].name;
const color = getRandomColor();
// update cells
cells.i.filter(i => cells.province[i] === p).forEach(i => {
cells.province[i] = 0;
cells.state[i] = newState;
});
// update diplomacy and reverse relations
const diplomacy = states.map(s => {
if (!s.i) return "x";
let relations = states[oldState].diplomacy[s.i]; // relations between Nth state and old overlord
if (s.i === oldState) relations = "Enemy"; // new state is Enemy to its old overlord
else if (relations === "Ally") relations = "Suspicion";
else if (relations === "Friendly") relations = "Suspicion";
else if (relations === "Suspicion") relations = "Neutral";
else if (relations === "Enemy") relations = "Friendly";
else if (relations === "Rival") relations = "Friendly";
else if (relations === "Vassal") relations = "Suspicion";
else if (relations === "Suzerain") relations = "Enemy";
s.diplomacy.push(relations);
return relations;
});
diplomacy.push("x");
states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldState].name}`]);
// create new state
states.push({i:newState, name, diplomacy, provinces:[], color, expansionism:.5, capital:burg, type:"Generic", center, culture});
BurgsAndStates.collectStatistics();
BurgsAndStates.defineStateForms([newState]);
if (layerIsOn("toggleProvinces")) toggleProvinces();
if (!layerIsOn("toggleStates")) toggleStates(); else drawStates();
if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders();
BurgsAndStates.drawStateLabels([newState, oldState]);
// remove old province
unfocus(p);
if (states[oldState].provinces.includes(p)) states[oldState].provinces.splice(states[oldState].provinces.indexOf(p), 1);
provinces[p].removed = true;
closeDialogs();
editStates();
}
function changePopulation(province) {
const p = pack.provinces[province];
const cells = pack.cells.i.filter(i => pack.cells.province[i] === province);
if (!cells.length) {tip("Province does not have any cells, cannot change population", false, "error"); return;}
const rural = rn(p.rural * populationRate.value);
const urban = rn(p.urban * populationRate.value * urbanization.value);
const total = rural + urban;
const l = n => Number(n).toLocaleString();
alertMessage.innerHTML = `
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${p.burgs.length?'':"disabled"}>
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function() {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
if (isNaN(totalNew)) return;
totalPop.innerHTML = l(totalNew);
totalPopPerc.innerHTML = rn(totalNew / total * 100);
}
ruralPop.oninput = () => update();
urbanPop.oninput = () => update();
$("#alert").dialog({
resizable: false, title: "Change province population", width: "24em", buttons: {
Apply: function() {applyPopulationChange(); $(this).dialog("close");},
Cancel: function() {$(this).dialog("close");}
}, position: {my: "center", at: "center", of: "svg"}
});
function applyPopulationChange() {
const ruralChange = rn(ruralPop.value / rural, 4);
if (isFinite(ruralChange) && ruralChange !== 1) {
cells.forEach(i => pack.cells.pop[i] *= ruralChange);
}
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
const points = ruralPop.value / populationRate.value;
const pop = rn(points / cells.length);
cells.forEach(i => pack.cells.pop[i] = pop);
}
const urbanChange = rn(urbanPop.value / urban, 4);
if (isFinite(urbanChange) && urbanChange !== 1) {
p.burgs.forEach(b => pack.burgs[b].population *= urbanChange);
}
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
const points = urbanPop.value / populationRate.value / urbanization.value;
const population = rn(points / burgs.length);
p.burgs.forEach(b => pack.burgs[b].population = population);
}
refreshProvincesEditor();
}
}
function focusOn(p, cl) { function focusOn(p, cl) {
const inactive = cl.contains("inactive"); const inactive = cl.contains("inactive");
cl.toggle("inactive"); cl.toggle("inactive");
@ -286,8 +409,10 @@ function editProvinces() {
const states = pack.states.map(s => { const states = pack.states.map(s => {
return {id:s.i, state: s.i?0:null, color: s.i && s.color[0] === "#" ? d3.color(s.color).darker() : "#666"} return {id:s.i, state: s.i?0:null, color: s.i && s.color[0] === "#" ? d3.color(s.color).darker() : "#666"}
}); });
const provinces = pack.provinces.filter(p => p.i && !p.removed); const provinces = pack.provinces.filter(p => p.i && !p.removed).map(p => {
provinces.forEach(p => p.id = p.i + states.length - 1); return {id:p.i+states.length-1, i:p.i, state:p.state, color:p.color,
name:p.name, fullName:p.fullName, area:p.area, urban:p.urban, rural:p.rural}
});
const data = states.concat(provinces); const data = states.concat(provinces);
const root = d3.stratify().parentId(d => d.state)(data).sum(d => d.area); const root = d3.stratify().parentId(d => d.state)(data).sum(d => d.area);
@ -371,9 +496,9 @@ function editProvinces() {
: this.value === "rural" ? d => d.rural : this.value === "rural" ? d => d.rural
: this.value === "urban" ? d => d.urban : this.value === "urban" ? d => d.urban
: d => d.rural + d.urban; : d => d.rural + d.urban;
const newRoot = d3.stratify().parentId(d => d.state)(data).sum(value); root.sum(value);
node.data(treeLayout(newRoot).leaves()); node.data(treeLayout(root).leaves());
node.select("rect").transition().duration(1500) node.select("rect").transition().duration(1500)
.attr("x", d => d.x0).attr("y", d => d.y0) .attr("x", d => d.x0).attr("y", d => d.y0)
@ -550,7 +675,7 @@ function editProvinces() {
const oldProvince = cells.province[center]; const oldProvince = cells.province[center];
if (oldProvince && provinces[oldProvince].center === center) {tip("The cell is already a center of a different province. Select other cell", false, "error"); return;} if (oldProvince && provinces[oldProvince].center === center) {tip("The cell is already a center of a different province. Select other cell", false, "error"); return;}
const state = cells.state[center]; const state = cells.state[center];
if (!state) {tip("You cannot create a province in neutral lands> Please assign this land to a state first", false, "error"); return;} if (!state) {tip("You cannot create a province in neutral lands. Please assign this land to a state first", false, "error"); return;}
if (d3.event.shiftKey === false) exitAddProvinceMode(); if (d3.event.shiftKey === false) exitAddProvinceMode();
@ -638,7 +763,7 @@ function editProvinces() {
function closeProvincesEditor() { function closeProvincesEditor() {
if (customization === 11) exitProvincesManualAssignment("close"); if (customization === 11) exitProvincesManualAssignment("close");
if (customization === 12) exitAddStateMode(); if (customization === 12) exitAddProvinceMode();
} }
} }

View file

@ -30,6 +30,7 @@ function editReliefIcon() {
document.getElementById("reliefEditorSet").addEventListener("change", changeIconsSet); document.getElementById("reliefEditorSet").addEventListener("change", changeIconsSet);
reliefIconsDiv.querySelectorAll("svg").forEach(el => el.addEventListener("click", changeIcon)); reliefIconsDiv.querySelectorAll("svg").forEach(el => el.addEventListener("click", changeIcon));
document.getElementById("reliefEditStyle").addEventListener("click", () => editStyle("terrain"));
document.getElementById("reliefCopy").addEventListener("click", copyIcon); document.getElementById("reliefCopy").addEventListener("click", copyIcon);
document.getElementById("reliefMoveFront").addEventListener("click", () => elSelected.raise()); document.getElementById("reliefMoveFront").addEventListener("click", () => elSelected.raise());
document.getElementById("reliefMoveBack").addEventListener("click", () => elSelected.lower()); document.getElementById("reliefMoveBack").addEventListener("click", () => elSelected.lower());

View file

@ -23,6 +23,7 @@ function editReligions() {
// add listeners // add listeners
document.getElementById("religionsEditorRefresh").addEventListener("click", refreshReligionsEditor); document.getElementById("religionsEditorRefresh").addEventListener("click", refreshReligionsEditor);
document.getElementById("religionsEditStyle").addEventListener("click", () => editStyle("relig"));
document.getElementById("religionsLegend").addEventListener("click", toggleLegend); document.getElementById("religionsLegend").addEventListener("click", toggleLegend);
document.getElementById("religionsPercentage").addEventListener("click", togglePercentageMode); document.getElementById("religionsPercentage").addEventListener("click", togglePercentageMode);
document.getElementById("religionsHeirarchy").addEventListener("click", showHierarchy); document.getElementById("religionsHeirarchy").addEventListener("click", showHierarchy);
@ -65,7 +66,7 @@ function editReligions() {
const urban = r.urban * populationRate.value * urbanization.value; const urban = r.urban * populationRate.value * urbanization.value;
const population = rn(rural + urban); const population = rn(rural + urban);
if (r.i && !r.cells && body.dataset.extinct !== "show") continue; // hide extinct religions if (r.i && !r.cells && body.dataset.extinct !== "show") continue; // hide extinct religions
const populationTip = `Believers: ${si(population)}; Rural areas: ${si(rural)}; Urban areas: ${si(urban)}`; const populationTip = `Believers: ${si(population)}; Rural areas: ${si(rural)}; Urban areas: ${si(urban)}. Click to change`;
totalArea += area; totalArea += area;
totalPopulation += population; totalPopulation += population;
@ -123,6 +124,7 @@ function editReligions() {
body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm)); body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm));
body.querySelectorAll("div > input.religionDeity").forEach(el => el.addEventListener("input", religionChangeDeity)); body.querySelectorAll("div > input.religionDeity").forEach(el => el.addEventListener("input", religionChangeDeity));
body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.addEventListener("click", regenerateDeity)); body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.addEventListener("click", regenerateDeity));
body.querySelectorAll("div > div.culturePopulation").forEach(el => el.addEventListener("click", changePopulation));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", religionRemove)); body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", religionRemove));
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
@ -227,6 +229,67 @@ function editReligions() {
this.nextElementSibling.value = deity; this.nextElementSibling.value = deity;
} }
function changePopulation() {
const religion = +this.parentNode.dataset.id;
const r = pack.religions[religion];
if (!r.cells) {tip("Religion does not have any cells, cannot change population", false, "error"); return;}
const rural = rn(r.rural * populationRate.value);
const urban = rn(r.urban * populationRate.value * urbanization.value);
const total = rural + urban;
const l = n => Number(n).toLocaleString();
const burgs = pack.burgs.filter(b => !b.removed && pack.cells.religion[b.cell] === religion);
alertMessage.innerHTML = `<p><i>Please note all population of religion territory is considered
believers of this religion. It means believers number change will directly change population</i></p>
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length?'':"disabled"}>
<p>Total believers: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function() {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
if (isNaN(totalNew)) return;
totalPop.innerHTML = l(totalNew);
totalPopPerc.innerHTML = rn(totalNew / total * 100);
}
ruralPop.oninput = () => update();
urbanPop.oninput = () => update();
$("#alert").dialog({
resizable: false, title: "Change believers number", width: "24em", buttons: {
Apply: function() {applyPopulationChange(); $(this).dialog("close");},
Cancel: function() {$(this).dialog("close");}
}, position: {my: "center", at: "center", of: "svg"}
});
function applyPopulationChange() {
const ruralChange = rn(ruralPop.value / rural, 4);
if (isFinite(ruralChange) && ruralChange !== 1) {
const cells = pack.cells.i.filter(i => pack.cells.religion[i] === religion);
cells.forEach(i => pack.cells.pop[i] *= ruralChange);
}
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
const points = ruralPop.value / populationRate.value;
const cells = pack.cells.i.filter(i => pack.cells.religion[i] === religion);
const pop = rn(points / cells.length);
cells.forEach(i => pack.cells.pop[i] = pop);
}
const urbanChange = rn(urbanPop.value / urban, 4);
if (isFinite(urbanChange) && urbanChange !== 1) {
burgs.forEach(b => b.population *= urbanChange);
}
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
const points = urbanPop.value / populationRate.value / urbanization.value;
const population = rn(points / burgs.length);
burgs.forEach(b => b.population = population);
}
refreshReligionsEditor();
}
}
function religionRemove() { function religionRemove() {
if (customization) return; if (customization) return;
const religion = +this.parentNode.dataset.id; const religion = +this.parentNode.dataset.id;

View file

@ -34,6 +34,7 @@ function editRiver() {
document.getElementById("riverScale").addEventListener("input", changeScale); document.getElementById("riverScale").addEventListener("input", changeScale);
document.getElementById("riverReset").addEventListener("click", resetTransformation); document.getElementById("riverReset").addEventListener("click", resetTransformation);
document.getElementById("riverEditStyle").addEventListener("click", () => editStyle("rivers"));
document.getElementById("riverCopy").addEventListener("click", copyRiver); document.getElementById("riverCopy").addEventListener("click", copyRiver);
document.getElementById("riverNew").addEventListener("click", toggleRiverCreationMode); document.getElementById("riverNew").addEventListener("click", toggleRiverCreationMode);
document.getElementById("riverLegend").addEventListener("click", editRiverLegend); document.getElementById("riverLegend").addEventListener("click", editRiverLegend);

View file

@ -30,6 +30,8 @@ function editRoute(onClick) {
document.getElementById("routeGroupName").addEventListener("change", createNewGroup); document.getElementById("routeGroupName").addEventListener("change", createNewGroup);
document.getElementById("routeGroupRemove").addEventListener("click", removeRouteGroup); document.getElementById("routeGroupRemove").addEventListener("click", removeRouteGroup);
document.getElementById("routeGroupsHide").addEventListener("click", hideGroupSection); document.getElementById("routeGroupsHide").addEventListener("click", hideGroupSection);
document.getElementById("routeEditStyle").addEventListener("click", editGroupStyle);
document.getElementById("routeSplit").addEventListener("click", toggleRouteSplitMode); document.getElementById("routeSplit").addEventListener("click", toggleRouteSplitMode);
document.getElementById("routeLegend").addEventListener("click", editRouteLegend); document.getElementById("routeLegend").addEventListener("click", editRouteLegend);
document.getElementById("routeNew").addEventListener("click", toggleRouteCreationMode); document.getElementById("routeNew").addEventListener("click", toggleRouteCreationMode);
@ -141,14 +143,17 @@ function editRoute(onClick) {
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"); return;}
let group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, ""); const group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
if (Number.isFinite(+group.charAt(0))) group = "g" + group;
if (document.getElementById(group)) { if (document.getElementById(group)) {
tip("Element with this id already exists. Please provide a unique name", false, "error"); tip("Element with this id already exists. Please provide a unique name", false, "error");
return; return;
} }
if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error");
return;
}
// just rename if only 1 element left // just rename if only 1 element left
const oldGroup = elSelected.node().parentNode; const oldGroup = elSelected.node().parentNode;
const basic = ["roads", "trails", "searoutes"].includes(oldGroup.id); const basic = ["roads", "trails", "searoutes"].includes(oldGroup.id);
@ -189,7 +194,12 @@ function editRoute(onClick) {
}, },
Cancel: function() {$(this).dialog("close");} Cancel: function() {$(this).dialog("close");}
} }
}); });
}
function editGroupStyle() {
const g = elSelected.node().parentNode.id;
editStyle("routes", g);
} }
function toggleRouteSplitMode() { function toggleRouteSplitMode() {

View file

@ -22,6 +22,7 @@ function editStates() {
// add listeners // add listeners
document.getElementById("statesEditorRefresh").addEventListener("click", refreshStatesEditor); document.getElementById("statesEditorRefresh").addEventListener("click", refreshStatesEditor);
document.getElementById("statesEditStyle").addEventListener("click", () => editStyle("regions"));
document.getElementById("statesLegend").addEventListener("click", toggleLegend); document.getElementById("statesLegend").addEventListener("click", toggleLegend);
document.getElementById("statesPercentage").addEventListener("click", togglePercentageMode); document.getElementById("statesPercentage").addEventListener("click", togglePercentageMode);
document.getElementById("statesChart").addEventListener("click", showStatesChart); document.getElementById("statesChart").addEventListener("click", showStatesChart);
@ -42,6 +43,7 @@ function editStates() {
if (cl.contains("zoneFill")) stateChangeFill(el); else if (cl.contains("zoneFill")) stateChangeFill(el); else
if (cl.contains("icon-fleur")) stateOpenCOA(state); else if (cl.contains("icon-fleur")) stateOpenCOA(state); else
if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state); else if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state); else
if (cl.contains("culturePopulation")) changePopulation(state); else
if (cl.contains("icon-pin")) focusOnState(state, cl); else if (cl.contains("icon-pin")) focusOnState(state, cl); else
if (cl.contains("icon-trash-empty")) stateRemove(state); else if (cl.contains("icon-trash-empty")) stateRemove(state); else
if (cl.contains("hoverButton") && cl.contains("stateName")) regenerateName(state, line); else if (cl.contains("hoverButton") && cl.contains("stateName")) regenerateName(state, line); else
@ -75,7 +77,7 @@ function editStates() {
const rural = s.rural * populationRate.value; const rural = s.rural * populationRate.value;
const urban = s.urban * populationRate.value * urbanization.value; const urban = s.urban * populationRate.value * urbanization.value;
const population = rn(rural + urban); const population = rn(rural + urban);
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`; const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`;
totalArea += area; totalArea += area;
totalPopulation += population; totalPopulation += population;
totalBurgs += s.burgs; totalBurgs += s.burgs;
@ -289,6 +291,66 @@ function editStates() {
window.open(url, '_blank'); window.open(url, '_blank');
} }
function changePopulation(state) {
const s = pack.states[state];
if (!s.cells) {tip("State does not have any cells, cannot change population", false, "error"); return;}
const rural = rn(s.rural * populationRate.value);
const urban = rn(s.urban * populationRate.value * urbanization.value);
const total = rural + urban;
const l = n => Number(n).toLocaleString();
alertMessage.innerHTML = `
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${s.burgs?'':"disabled"}>
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function() {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
if (isNaN(totalNew)) return;
totalPop.innerHTML = l(totalNew);
totalPopPerc.innerHTML = rn(totalNew / total * 100);
}
ruralPop.oninput = () => update();
urbanPop.oninput = () => update();
$("#alert").dialog({
resizable: false, title: "Change state population", width: "24em", buttons: {
Apply: function() {applyPopulationChange(); $(this).dialog("close");},
Cancel: function() {$(this).dialog("close");}
}, position: {my: "center", at: "center", of: "svg"}
});
function applyPopulationChange() {
const ruralChange = rn(ruralPop.value / rural, 4);
if (isFinite(ruralChange) && ruralChange !== 1) {
const cells = pack.cells.i.filter(i => pack.cells.state[i] === state);
cells.forEach(i => pack.cells.pop[i] *= ruralChange);
}
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
const points = ruralPop.value / populationRate.value;
const cells = pack.cells.i.filter(i => pack.cells.state[i] === state);
const pop = rn(points / cells.length);
cells.forEach(i => pack.cells.pop[i] = pop);
}
const urbanChange = rn(urbanPop.value / urban, 4);
if (isFinite(urbanChange) && urbanChange !== 1) {
const burgs = pack.burgs.filter(b => !b.removed && b.state === state);
burgs.forEach(b => b.population *= urbanChange);
}
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
const points = urbanPop.value / populationRate.value / urbanization.value;
const burgs = pack.burgs.filter(b => !b.removed && b.state === state);
const population = rn(points / burgs.length);
burgs.forEach(b => b.population = population);
}
refreshStatesEditor();
}
}
function stateCapitalZoomIn(state) { function stateCapitalZoomIn(state) {
const capital = pack.states[state].capital; const capital = pack.states[state].capital;
const l = burgLabels.select("[data-id='" + capital + "']"); const l = burgLabels.select("[data-id='" + capital + "']");
@ -419,7 +481,7 @@ function editStates() {
const node = graph.selectAll("g").data(root.leaves()).enter() const node = graph.selectAll("g").data(root.leaves()).enter()
.append("g").attr("transform", d => `translate(${d.x},${d.y})`) .append("g").attr("transform", d => `translate(${d.x},${d.y})`)
.attr("data-id", d => d.data.id) .attr("data-id", d => d.data.i)
.on("mouseenter", d => showInfo(event, d)) .on("mouseenter", d => showInfo(event, d))
.on("mouseleave", d => hideInfo(event, d)); .on("mouseleave", d => hideInfo(event, d));
@ -642,7 +704,7 @@ function editStates() {
const provCells = cells.i.filter(i => cells.province[i] === p); const provCells = cells.i.filter(i => cells.province[i] === p);
const provStates = [...new Set(provCells.map(i => cells.state[i]))]; const provStates = [...new Set(provCells.map(i => cells.state[i]))];
// assign province its center owner; if center is neutral, remove province // assign province to its center owner; if center is neutral, remove province
const owner = cells.state[provinces[p].center]; const owner = cells.state[provinces[p].center];
if (owner) { if (owner) {
const name = provinces[p].name; const name = provinces[p].name;
@ -714,46 +776,73 @@ function editStates() {
} }
function addState() { function addState() {
const states = pack.states, burgs = pack.burgs, cells = pack.cells;
const point = d3.mouse(this); const point = d3.mouse(this);
const center = findCell(point[0], point[1]); const center = findCell(point[0], point[1]);
if (pack.cells.h[center] < 20) {tip("You cannot place state into the water. Please click on a land cell", false, "error"); return;} if (cells.h[center] < 20) {tip("You cannot place state into the water. Please click on a land cell", false, "error"); return;}
let burg = pack.cells.burg[center]; let burg = cells.burg[center];
if (burg && pack.burgs[burg].capital) {tip("Existing capital cannot be selected as a new state capital! Select other cell", false, "error"); return;} if (burg && burgs[burg].capital) {tip("Existing capital cannot be selected as a new state capital! Select other cell", false, "error"); return;}
if (!burg) burg = addBurg(point); // add new burg if (!burg) burg = addBurg(point); // add new burg
const oldState = cells.state[center];
const newState = states.length;
// turn burg into a capital // turn burg into a capital
pack.burgs[burg].capital = true; burgs[burg].capital = true;
pack.burgs[burg].state = pack.states.length; burgs[burg].state = newState;
moveBurgToGroup(burg, "cities"); moveBurgToGroup(burg, "cities");
if (d3.event.shiftKey === false) exitAddStateMode(); if (d3.event.shiftKey === false) exitAddStateMode();
const i = pack.states.length; const culture = cells.culture[center];
const culture = pack.cells.culture[center]; const basename = center%5 === 0 ? burgs[burg].name : Names.getCulture(culture);
const basename = center%5 === 0 ? pack.burgs[burg].name : Names.getCulture(culture);
const name = Names.getState(basename, culture); const name = Names.getState(basename, culture);
const color = d3.color(d3.scaleSequential(d3.interpolateRainbow)(Math.random())).hex(); const color = getRandomColor();
const diplomacy = pack.states.map(s => s.i ? "Neutral" : "x")
diplomacy.push("x");
pack.states.forEach(s => {if (s.i) {s.diplomacy.push("Neutral");}});
const provinces = [];
const affected = [pack.states.length, pack.cells.state[center]]; // update diplomacy and reverse relations
const diplomacy = states.map(s => {
if (!s.i) return "x";
if (!oldState) {
s.diplomacy.push("Neutral");
return "Neutral";
}
pack.cells.state[center] = pack.states.length; let relations = states[oldState].diplomacy[s.i]; // relations between Nth state and old overlord
pack.cells.c[center].forEach(c => { if (s.i === oldState) relations = "Enemy"; // new state is Enemy to its old overlord
if (pack.cells.h[c] < 20) return; else if (relations === "Ally") relations = "Suspicion";
if (pack.cells.burg[c]) return; else if (relations === "Friendly") relations = "Suspicion";
affected.push(pack.cells.state[c]); else if (relations === "Suspicion") relations = "Neutral";
pack.cells.state[c] = pack.states.length; else if (relations === "Enemy") relations = "Friendly";
else if (relations === "Rival") relations = "Friendly";
else if (relations === "Vassal") relations = "Suspicion";
else if (relations === "Suzerain") relations = "Enemy";
s.diplomacy.push(relations);
return relations;
}); });
pack.states.push({i, name, diplomacy, provinces, color, expansionism:.5, capital:burg, type:"Generic", center, culture}); diplomacy.push("x");
BurgsAndStates.collectStatistics(); states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldState].name}`]);
BurgsAndStates.defineStateForms([i]);
const affectedStates = [newState, oldState];
const affectedProvinces = [cells.province[center]];
cells.state[center] = newState;
cells.province[center] = 0;
cells.c[center].forEach(c => {
if (cells.h[c] < 20) return;
if (cells.burg[c]) return;
affectedStates.push(cells.state[c]);
affectedProvinces.push(cells.province[c]);
cells.state[c] = newState;
cells.province[c] = 0;
});
states.push({i:newState, name, diplomacy, provinces:[], color, expansionism:.5, capital:burg, type:"Generic", center, culture});
BurgsAndStates.collectStatistics();
BurgsAndStates.defineStateForms([newState]);
adjustProvinces([...new Set(affectedProvinces)]);
if (layerIsOn("toggleProvinces")) toggleProvinces();
if (!layerIsOn("toggleStates")) toggleStates(); else drawStates(); if (!layerIsOn("toggleStates")) toggleStates(); else drawStates();
if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders();
BurgsAndStates.drawStateLabels(affected); BurgsAndStates.drawStateLabels([...new Set(affectedStates)]);
statesEditorAddLines(); statesEditorAddLines();
} }

View file

@ -61,7 +61,8 @@ function processFeatureRegeneration(button) {
if (button === "regenerateStates") regenerateStates(); else if (button === "regenerateStates") regenerateStates(); else
if (button === "regenerateProvinces") regenerateProvinces(); else if (button === "regenerateProvinces") regenerateProvinces(); else
if (button === "regenerateReligions") regenerateReligions(); else if (button === "regenerateReligions") regenerateReligions(); else
if (button === "regenerateMarkers") regenerateMarkers(); if (button === "regenerateMarkers") regenerateMarkers(); else
if (button === "regenerateZones") regenerateZones();
} }
function regenerateRivers() { function regenerateRivers() {
@ -94,7 +95,7 @@ function regenerateBurgs() {
const score = new Int16Array(cells.s.map(s => s * Math.random())); // cell score for capitals placement const score = new Int16Array(cells.s.map(s => s * Math.random())); // cell score for capitals 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 => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
const burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 10 / densityInput.value ** .8) + states.length : +manorsInput.value + states.length; const burgsCount = manorsInput.value == 1000 ? rn(sorted.length / 10 / (grid.points.length / 10000) ** .8) + states.length : +manorsInput.value + states.length;
const spacing = (graphWidth + graphHeight) / 200 / (burgsCount / 500); // base min distance between towns const spacing = (graphWidth + graphHeight) / 200 / (burgsCount / 500); // base min distance between towns
for (let i=0; i < sorted.length && burgs.length < burgsCount; i++) { for (let i=0; i < sorted.length && burgs.length < burgsCount; i++) {
@ -107,7 +108,7 @@ function regenerateBurgs() {
const state = cells.state[cell]; const state = cells.state[cell];
const capital = !states[state].capital; // if state doesn't have capital, make this burg a capital const capital = !states[state].capital; // if state doesn't have capital, make this burg a capital
if (capital) {states[state].capital = id; states[state].cell = cell;} if (capital) {states[state].capital = id; states[state].center = cell;}
const culture = cells.culture[cell]; const culture = cells.culture[cell];
const name = Names.getCulture(culture); const name = Names.getCulture(culture);
@ -120,6 +121,7 @@ function regenerateBurgs() {
states.filter(s => s.i && !s.removed && !s.capital).forEach(s => { states.filter(s => s.i && !s.removed && !s.capital).forEach(s => {
const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg const burg = addBurg([cells.p[s.center][0], cells.p[s.center][1]]); // add new burg
s.capital = burg; s.capital = burg;
s.center = pack.burgs[burg].cell;
pack.burgs[burg].capital = true; pack.burgs[burg].capital = true;
pack.burgs[burg].state = s.i; pack.burgs[burg].state = s.i;
moveBurgToGroup(burg, "cities"); moveBurgToGroup(burg, "cities");
@ -135,11 +137,18 @@ function regenerateBurgs() {
function regenerateStates() { function regenerateStates() {
Math.seedrandom(Math.floor(Math.random() * 1e9)); // new random seed Math.seedrandom(Math.floor(Math.random() * 1e9)); // new random seed
const burgs = pack.burgs.filter(b => b.i && !b.removed), states = pack.states.filter(s => s.i && !s.removed); const burgs = pack.burgs.filter(b => b.i && !b.removed);
// burgs sorted by a bit randomized population: if (!burgs.length) {
tip("No burgs to generate states. Please create burgs first", false, "error");
return;
}
if (burgs.length < +regionsInput.value) {
tip(`Not enought burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, "warn");
}
// burg ids sorted by a bit randomized population:
const sorted = burgs.map(b => [b.i, b.population * Math.random()]).sort((a, b) => b[1] - a[1]).map(b => b[0]); const sorted = burgs.map(b => [b.i, b.population * Math.random()]).sort((a, b) => b[1] - a[1]).map(b => b[0]);
const capitalsTree = d3.quadtree(); const capitalsTree = d3.quadtree();
let spacing = (graphWidth + graphHeight) / 2 / states.length; // min distance between capitals
// turn all old capitals into towns // turn all old capitals into towns
burgs.filter(b => b.capital).forEach(b => { burgs.filter(b => b.capital).forEach(b => {
@ -147,30 +156,31 @@ function regenerateStates() {
b.capital = false; b.capital = false;
}); });
states.forEach(s => { const neutral = pack.states[0].name;
let newCapital = 0, x = 0, y = 0; const count = Math.min(+regionsInput.value, burgs.length);
let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals
pack.states = d3.range(count).map(i => {
if (!i) return {i, name: neutral};
for (let i=0; i < sorted.length && !newCapital; i++) { let capital = null, x = 0, y = 0;
newCapital = burgs[sorted[i]]; for (let i=0; i < sorted.length; i++) {
x = newCapital.x, y = newCapital.y; capital = burgs[sorted[i]];
if (capitalsTree.find(x, y, spacing) !== undefined) { x = capital.x, y = capital.y;
spacing -= 1; if (capitalsTree.find(x, y, spacing) === undefined) break;
if (spacing < 1) spacing = 1; spacing = Math.max(spacing - 1, 1);
newCapital = 0;
}
} }
capitalsTree.add([x, y]); capitalsTree.add([x, y]);
newCapital.capital = true; capital.capital = true;
s.capital = newCapital.i; moveBurgToGroup(capital.i, "cities");
s.center = newCapital.cell;
s.culture = newCapital.culture; const culture = capital.culture;
s.expansionism = rn(Math.random() * powerInput.value + 1, 1); const basename = capital.name.length < 9 && capital.cell%5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0);
const basename = newCapital.name.length < 9 && newCapital.cell%5 === 0 ? newCapital.name : Names.getCulture(s.culture, 3, 6, "", 0); const name = Names.getState(basename, culture);
s.name = Names.getState(basename, s.culture); const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]);
const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[newCapital.cell]); const type = nomadic ? "Nomadic" : pack.cultures[culture].type === "Nomadic" ? "Generic" : pack.cultures[culture].type;
s.type = nomadic ? "Nomadic" : pack.cultures[s.culture].type === "Nomadic" ? "Generic" : pack.cultures[s.culture].type; const expansionism = rn(Math.random() * powerInput.value + 1, 1);
moveBurgToGroup(newCapital.i, "cities"); return {i, name, type, capital:capital.i, center:capital.cell, culture, expansionism};
}); });
unfog(); unfog();
@ -202,10 +212,21 @@ function regenerateReligions() {
} }
function regenerateMarkers() { function regenerateMarkers() {
markers.selectAll("use").remove(); // remove existing markers and assigned notes
markers.selectAll("use").each(function() {
const index = notes.findIndex(n => n.id === this.id);
if (index != -1) notes.splice(index, 1);
}).remove();
addMarkers(gauss(1, .5, .3, 5, 2)); addMarkers(gauss(1, .5, .3, 5, 2));
} }
function regenerateZones() {
zones.selectAll("g").remove(); // remove existing zones
addZones(gauss(1, .5, .6, 5, 2));
if (document.getElementById("zonesEditorRefresh").offsetParent) zonesEditorRefresh.click();
}
function unpressClickToAddButton() { function unpressClickToAddButton() {
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed")); addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
restoreDefaultEvents(); restoreDefaultEvents();
@ -244,6 +265,7 @@ function addLabelOnClick() {
const x = width / -2; // x offset; const x = width / -2; // x offset;
example.remove(); example.remove();
group.classed("hidden", false);
group.append("text").attr("id", id) group.append("text").attr("id", id)
.append("textPath").attr("xlink:href", "#textPath_"+id).attr("startOffset", "50%").attr("font-size", "100%") .append("textPath").attr("xlink:href", "#textPath_"+id).attr("startOffset", "50%").attr("font-size", "100%")
.append("tspan").attr("x", x).text(name); .append("tspan").attr("x", x).text(name);

View file

@ -14,6 +14,8 @@ function editZones() {
}); });
// add listeners // add listeners
document.getElementById("zonesEditorRefresh").addEventListener("click", zonesEditorAddLines);
document.getElementById("zonesEditStyle").addEventListener("click", () => editStyle("zones"));
document.getElementById("zonesLegend").addEventListener("click", toggleLegend); document.getElementById("zonesLegend").addEventListener("click", toggleLegend);
document.getElementById("zonesPercentage").addEventListener("click", togglePercentageMode); document.getElementById("zonesPercentage").addEventListener("click", togglePercentageMode);
document.getElementById("zonesManually").addEventListener("click", enterZonesManualAssignent); document.getElementById("zonesManually").addEventListener("click", enterZonesManualAssignent);
@ -25,6 +27,7 @@ function editZones() {
body.addEventListener("click", function(ev) { body.addEventListener("click", function(ev) {
const el = ev.target, cl = el.classList, zone = el.parentNode.dataset.id; const el = ev.target, cl = el.classList, zone = el.parentNode.dataset.id;
if (cl.contains("culturePopulation")) {changePopulation(zone); return;}
if (cl.contains("icon-trash-empty")) {zoneRemove(zone); return;} if (cl.contains("icon-trash-empty")) {zoneRemove(zone); return;}
if (cl.contains("icon-eye")) {toggleVisibility(el); return;} if (cl.contains("icon-eye")) {toggleVisibility(el); return;}
if (cl.contains("icon-pin")) {focusOnZone(zone, cl); return;} if (cl.contains("icon-pin")) {focusOnZone(zone, cl); return;}
@ -50,7 +53,7 @@ function editZones() {
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate.value; const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate.value;
const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate.value * urbanization.value; const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate.value * urbanization.value;
const population = rural + urban; const population = rural + urban;
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`; const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`;
const inactive = this.style.display === "none"; const inactive = this.style.display === "none";
const focused = defs.select("#fog #focus"+this.id).size(); const focused = defs.select("#fog #focus"+this.id).size();
@ -355,6 +358,65 @@ function editZones() {
this.classList.toggle("pressed"); this.classList.toggle("pressed");
} }
function changePopulation(zone) {
const dataCells = zones.select("#"+zone).attr("data-cells");
const cells = dataCells ? dataCells.split(",").map(i => +i).filter(i => pack.cells.h[i] >= 20) : [];
if (!cells.length) {tip("Zone does not have any land cells, cannot change population", false, "error"); return;}
const burgs = pack.burgs.filter(b => !b.removed && cells.includes(b.cell));
const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate.value);
const urban = rn(d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate.value * urbanization.value);
const total = rural + urban;
const l = n => Number(n).toLocaleString();
alertMessage.innerHTML = `
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length?'':"disabled"}>
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function() {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
if (isNaN(totalNew)) return;
totalPop.innerHTML = l(totalNew);
totalPopPerc.innerHTML = rn(totalNew / total * 100);
}
ruralPop.oninput = () => update();
urbanPop.oninput = () => update();
$("#alert").dialog({
resizable: false, title: "Change zone population", width: "24em", buttons: {
Apply: function() {applyPopulationChange(); $(this).dialog("close");},
Cancel: function() {$(this).dialog("close");}
}, position: {my: "center", at: "center", of: "svg"}
});
function applyPopulationChange() {
const ruralChange = rn(ruralPop.value / rural, 4);
if (isFinite(ruralChange) && ruralChange !== 1) {
cells.forEach(i => pack.cells.pop[i] *= ruralChange);
}
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
const points = ruralPop.value / populationRate.value;
const pop = rn(points / cells.length);
cells.forEach(i => pack.cells.pop[i] = pop);
}
const urbanChange = rn(urbanPop.value / urban, 4);
if (isFinite(urbanChange) && urbanChange !== 1) {
burgs.forEach(b => b.population *= urbanChange);
}
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
const points = urbanPop.value / populationRate.value / urbanization.value;
const population = rn(points / burgs.length);
burgs.forEach(b => b.population = population);
}
zonesEditorAddLines();
}
}
function zoneRemove(zone) { function zoneRemove(zone) {
zones.select("#"+zone).remove(); zones.select("#"+zone).remove();
unfocus(zone); unfocus(zone);

File diff suppressed because one or more lines are too long