mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-21 19:41:23 +01:00
Merge pull request #2 from Azgaar/master
Update from Azgaar version 1.1.08
This commit is contained in:
commit
84aa932dff
32 changed files with 2113 additions and 421 deletions
61
index.css
61
index.css
|
|
@ -80,6 +80,11 @@ button, select, a {
|
|||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
#lakes,
|
||||
#coastline {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#temperature {
|
||||
fill-rule: evenodd;
|
||||
font-family: sans-serif;
|
||||
|
|
@ -242,7 +247,10 @@ i.icon-lock {
|
|||
text-anchor: middle;
|
||||
}
|
||||
|
||||
#routeLength, #riverLength {
|
||||
#routeLength,
|
||||
#riverLength,
|
||||
#lakeArea,
|
||||
#coastlineArea {
|
||||
background-color: #eeeeee;
|
||||
border: 1px solid #a5a5a5;
|
||||
line-height: 1.3em;
|
||||
|
|
@ -252,7 +260,7 @@ i.icon-lock {
|
|||
#brushCircle {
|
||||
stroke: #373737;
|
||||
stroke-width: 1.5px;
|
||||
stroke-dasharray: 6;
|
||||
stroke-dasharray: 7;
|
||||
stroke-linecap: butt;
|
||||
fill: none;
|
||||
}
|
||||
|
|
@ -474,13 +482,7 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
#styleContent button.styleButton {
|
||||
font-size: 70%;
|
||||
border-radius: 15%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#styleContent button.presetButton {
|
||||
#layersContent button.presetButton {
|
||||
position: absolute;
|
||||
height: 2em;
|
||||
border-radius: 15%;
|
||||
|
|
@ -488,6 +490,13 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
font-size: .7em;
|
||||
}
|
||||
|
||||
#styleContent button.styleButton {
|
||||
font-size: 70%;
|
||||
border-radius: 15%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#layersContent button.active,
|
||||
#styleContent button:active {
|
||||
transform: translate(0px, 1px);
|
||||
}
|
||||
|
|
@ -531,9 +540,10 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
}
|
||||
|
||||
.tab > button.options {
|
||||
width: 23.25%;
|
||||
/* width: 23.25%; */
|
||||
width: 18.6%;
|
||||
height: 100%;
|
||||
padding: 7px 10px;
|
||||
padding: 7px 0px;
|
||||
}
|
||||
|
||||
button.options {
|
||||
|
|
@ -682,7 +692,7 @@ fieldset {
|
|||
}
|
||||
|
||||
#styleElements .whiteButton {
|
||||
padding: 0 9px;
|
||||
padding: 0 .8em;
|
||||
border: 0;
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
|
|
@ -873,7 +883,7 @@ body button.noicon {
|
|||
#controlPoints {
|
||||
fill: #ff0000;
|
||||
stroke: #841f1f;
|
||||
stroke-width: .3;
|
||||
stroke-width: .25;
|
||||
cursor: move;
|
||||
opacity: .8;
|
||||
}
|
||||
|
|
@ -886,6 +896,25 @@ body button.noicon {
|
|||
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 {
|
||||
border-left: 12px solid transparent;
|
||||
border-right: 12px solid #916e7f;
|
||||
|
|
@ -1183,6 +1212,11 @@ div.states .icon-pin {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.states .icon-flag-empty {
|
||||
cursor: pointer;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
div.states .icon-resize-vertical {
|
||||
cursor: row-resize;
|
||||
font-size: .9em;
|
||||
|
|
@ -1215,6 +1249,7 @@ div.states>.cultureName {
|
|||
|
||||
div.states>.culturePopulation {
|
||||
width: 4em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.states > .cultureBase,
|
||||
|
|
|
|||
165
index.html
165
index.html
|
|
@ -8,6 +8,7 @@
|
|||
<meta name="author" content="Azgaar (Max Ganiev)">
|
||||
<meta name="description" content="Azgaar's Fantasy Map Generator and Editor">
|
||||
<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:title" content="Azgaar's Fantasy Map Generator">
|
||||
<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="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="icons.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.1"/>
|
||||
<link rel="stylesheet" type="text/css" href="libs/jquery-ui.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -129,7 +130,7 @@
|
|||
<g id="hatching">
|
||||
<pattern id="hatch0" patternUnits="userSpaceOnUse" width="4" height="4">
|
||||
<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">
|
||||
<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="title_name">Azgaar's</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>
|
||||
</div>
|
||||
|
||||
|
|
@ -890,13 +891,14 @@
|
|||
|
||||
<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="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="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>
|
||||
</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>
|
||||
<select data-tip="Select a map layers preset" id="layersPreset" onchange="changePreset(this.value)" style="width:45%">
|
||||
<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="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>
|
||||
<ul data-tip="Click to toggle a layer, drag to raise or lower a layer" 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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="toggleScaleBar" data-tip="Scale Bar: click to toggle, drag to move. Shortcut: - (minus)" onclick="toggleScaleBar()" class="solid">Scale Bar</li>
|
||||
<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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Ctrl + click to edit style. Shortcut: - (minus)" onclick="toggleScaleBar(event)" class="solid">Scale Bar</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="styleContent" class="tabcontent">
|
||||
<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%">
|
||||
<option value="anchors">Anchor Icons</option>
|
||||
|
|
@ -980,7 +984,7 @@
|
|||
|
||||
<tbody id="styleGroup">
|
||||
<tr data-tip="Select element group">
|
||||
<td>Group</td>
|
||||
<td><b>Group</b></td>
|
||||
<td>
|
||||
<select id="styleGroupSelect"><option value="regions">regions</option></select>
|
||||
</td>
|
||||
|
|
@ -1460,7 +1464,7 @@
|
|||
</table>
|
||||
|
||||
<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="sepia" class="radio">Sepia</button>
|
||||
<button id="dingy" class="radio">Dingy</button>
|
||||
|
|
@ -1553,13 +1557,31 @@
|
|||
</td>
|
||||
<td>Cultures number</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>
|
||||
<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>
|
||||
</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">
|
||||
<td>
|
||||
<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="regenerateReligions" data-tip="Click to regenerate religions">Religions</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 id="addFeature">
|
||||
|
|
@ -1823,7 +1846,7 @@
|
|||
</div>
|
||||
|
||||
<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>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>
|
||||
|
|
@ -2001,6 +2024,8 @@
|
|||
<span id="labelTextRandom" data-tip="Generate random name" class="icon-shuffle pointer"></span>
|
||||
</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>
|
||||
<div id="labelSizeSection" style="display: none">
|
||||
<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>
|
||||
</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="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>
|
||||
|
|
@ -2051,6 +2077,8 @@
|
|||
<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>
|
||||
</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="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>
|
||||
|
|
@ -2058,6 +2086,35 @@
|
|||
<button id="routeRemove" data-tip="Remove route. Shortcut: Delete" class="icon-trash"></button>
|
||||
</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="reliefTools" data-tip="Select mode of operation">
|
||||
|
|
@ -2183,6 +2240,7 @@
|
|||
</div>
|
||||
|
||||
<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="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>
|
||||
|
|
@ -2208,6 +2266,14 @@
|
|||
<span id="burgNameReRandom" data-tip="Generate random name for the burg" class="icon-globe pointer"></span>
|
||||
</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="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>
|
||||
|
|
@ -2304,8 +2370,8 @@
|
|||
|
||||
<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>
|
||||
<input id="brushRadius" oninput="tip('Brush radius: '+this.value); brushRadiusNumber.value = this.value" type="range" min=5 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="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=1 max=50 value=25 style="border: 1px solid #d4d4d4">
|
||||
</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="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="convertColorsMinus" data-tip="Reduce the number of colors. Minimal number is 3" class="icon-minus-squared"></button>
|
||||
<button id="convertColorsPlus" data-tip="Increase the number of colors. Maximum number is 256" class="icon-plus-squared"></button>
|
||||
<button id="convertColorsButton" data-tip="Set number of colors" class="icon-signal"></button>
|
||||
<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>
|
||||
</div>
|
||||
|
|
@ -2456,6 +2521,7 @@
|
|||
|
||||
<div id="biomesBottom">
|
||||
<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="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>
|
||||
|
|
@ -2501,6 +2567,7 @@
|
|||
|
||||
<div id="statesBottom">
|
||||
<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="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>
|
||||
|
|
@ -2560,6 +2627,7 @@
|
|||
|
||||
<div id="provincesBottom">
|
||||
<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="provincesChart" data-tip="Show provinces chart" class="icon-chart-area"></button>
|
||||
<button id="provincesToggleLabels" data-tip="Toggle province labels" class="icon-font"></button>
|
||||
|
|
@ -2593,6 +2661,7 @@
|
|||
|
||||
<div id="diplomacyBottom" style="margin-top: .1em">
|
||||
<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="diplomacyHistory" data-tip="Show relations history" class="icon-hourglass-1"></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="burgsHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by burg name" class="sortable alphabetically" data-sortby="name">Burg </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 </div>
|
||||
<div style="left:7.6em" data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="province">Province </div>
|
||||
<div style="left:14em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="state">State </div>
|
||||
<div style="left:20.1em" data-tip="Click to sort by culture name" class="sortable alphabetically" data-sortby="culture">Culture </div>
|
||||
|
|
@ -2626,6 +2695,7 @@
|
|||
|
||||
<div id="burgsBottom">
|
||||
<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="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>
|
||||
|
|
@ -2655,6 +2725,7 @@
|
|||
|
||||
<div id="culturesBottom">
|
||||
<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="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>
|
||||
|
|
@ -2731,6 +2802,8 @@
|
|||
</div>
|
||||
|
||||
<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="zonesPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button>
|
||||
|
||||
|
|
@ -2791,10 +2864,11 @@
|
|||
|
||||
<div id="religionsBottom">
|
||||
<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="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="religionsExtinct" data-tip="Show/hide extinct religions (religions without cells)" class="icon-adjust"></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-eye-off"></button>
|
||||
|
||||
<button id="religionsManually" data-tip="Manually re-assign religions" class="icon-brush"></button>
|
||||
<div id="religionsManuallyButtons" style="display: none">
|
||||
|
|
@ -2981,7 +3055,6 @@
|
|||
<script src="libs/polylabel.min.js"></script>
|
||||
<script src="libs/jquery-ui.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 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/namesbase-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/rivers-editor.js"></script>
|
||||
<script defer src="modules/ui/relief-editor.js"></script>
|
||||
|
|
|
|||
3
libs/jquery-ui.css
vendored
3
libs/jquery-ui.css
vendored
|
|
@ -362,7 +362,8 @@ body .ui-dialog {
|
|||
border: 0;
|
||||
padding: .5em 1em;
|
||||
background: none;
|
||||
overflow: auto;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane {
|
||||
text-align: left;
|
||||
|
|
|
|||
506
main.js
506
main.js
|
|
@ -7,7 +7,7 @@
|
|||
// See also https://github.com/Azgaar/Fantasy-Map-Generator/issues/153
|
||||
|
||||
"use strict";
|
||||
const version = "1.0"; // generator version
|
||||
const version = "1.1"; // generator version
|
||||
document.title += " v" + version;
|
||||
|
||||
// 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 debug = viewbox.append("g").attr("id", "debug");
|
||||
|
||||
let freshwater = lakes.append("g").attr("id", "freshwater");
|
||||
let salt = lakes.append("g").attr("id", "salt");
|
||||
// lake and coast groups
|
||||
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", "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");
|
||||
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");
|
||||
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);
|
||||
|
|
@ -334,8 +338,15 @@ function applyDefaultStyle() {
|
|||
population.select("#rural").attr("stroke", "#0000ff");
|
||||
population.select("#urban").attr("stroke", "#ff0000");
|
||||
|
||||
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("#freshwater").attr("opacity", .5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").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);
|
||||
rivers.attr("opacity", null).attr("fill", "#5d97bb").attr("filter", null);
|
||||
|
|
@ -404,30 +415,21 @@ function applyDefaultStyle() {
|
|||
}
|
||||
|
||||
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>.
|
||||
|
||||
This version is compatible with versions 0.8b and 0.9b, but not with older .map files.
|
||||
Please use an <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog' target='_blank'>archived version</a> to open old files.
|
||||
This version is compatible with <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog' target='_blank'>previous version</a>,
|
||||
loaded <i>.map</i> files will be auto-updated.
|
||||
|
||||
<ul><a href=${link} target='_blank'>Main changes:</a>
|
||||
<li>Provinces and Provinces Editor</li>
|
||||
<li>Religions Layer and Religions Editor</li>
|
||||
<li>Full state names (state types)</li>
|
||||
<li>Multi-lined labels</li>
|
||||
<li>State relations (diplomacy)</li>
|
||||
<li>Custom layers (zones)</li>
|
||||
<li>Places of interest (auto-added markers)</li>
|
||||
<li>New color picker and hatching fill</li>
|
||||
<li>Legend boxes</li>
|
||||
<li>World Configurator presets</li>
|
||||
<li>Improved state labels placement</li>
|
||||
<li>Relief icons sets</li>
|
||||
<li>Fogging</li>
|
||||
<li>Custom layer presets</li>
|
||||
<li>Custom biomes</li>
|
||||
<li>State, province and burg COAs</li>
|
||||
<li>Desktop version (see <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A#is-there-a-desktop-version' target='_blank'>here)</a></li>
|
||||
<li>Lake Editor</li>
|
||||
<li>Coastline Editor</li>
|
||||
<li>New lake groups (types)</li>
|
||||
<li>Culture presets</li>
|
||||
<li>Provinces, states and burgs charts</li>
|
||||
<li>Editable religions tree</li>
|
||||
<li>Data export in geojson format</li>
|
||||
<li>Map quick save and quick load</li>
|
||||
<li>Map loading from URL</li>
|
||||
</ul>
|
||||
|
||||
<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>`;
|
||||
|
||||
$("#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")}},
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
close: () => localStorage.setItem("version", version)}
|
||||
|
|
@ -487,7 +489,7 @@ function invokeActiveZooming() {
|
|||
// toggle shade/blur filter for coatline on zoom
|
||||
let filter = scale > 2.6 ? "url(#blurFilter)" : "url(#dropShadow)";
|
||||
if (scale > 1.5 && scale <= 2.6) filter = null;
|
||||
coastline.attr("filter", filter);
|
||||
coastline.select("#sea_island").attr("filter", filter);
|
||||
}
|
||||
|
||||
// rescale lables on zoom
|
||||
|
|
@ -603,8 +605,8 @@ function generate() {
|
|||
drawStates();
|
||||
drawBorders();
|
||||
BurgsAndStates.drawStateLabels();
|
||||
addZone();
|
||||
addMarkers();
|
||||
addZones();
|
||||
Names.getMapName();
|
||||
|
||||
console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`);
|
||||
|
|
@ -646,7 +648,7 @@ function generateSeed() {
|
|||
// Place points to calculate Voronoi diagram
|
||||
function 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
|
||||
grid.boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
|
||||
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 start = findStart(i, type);
|
||||
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;
|
||||
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
|
||||
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].vertices = vchain;
|
||||
|
||||
const path = round(lineGen(points));
|
||||
const id = features[f].group + features[f].i;
|
||||
if (features[f].type === "lake") {
|
||||
landMask.append("path").attr("d", path).attr("fill", "black");
|
||||
// waterMask.append("path").attr("d", path).attr("fill", "white"); // uncomment to show over lakes
|
||||
lakes.select("#"+features[f].group).append("path").attr("d", path).attr("id", id); // draw the lake
|
||||
landMask.append("path").attr("d", path).attr("fill", "black").attr("id", "land_"+f);
|
||||
// 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", "lake_"+f).attr("data-f", f); // draw the lake
|
||||
} else {
|
||||
landMask.append("path").attr("d", path).attr("fill", "white");
|
||||
waterMask.append("path").attr("d", path).attr("fill", "black");
|
||||
coastline.append("path").attr("d", path).attr("id", id); // draw the coastline
|
||||
landMask.append("path").attr("d", path).attr("fill", "white").attr("id", "land_"+f);
|
||||
waterMask.append("path").attr("d", path).attr("fill", "black").attr("id", "water_"+f);
|
||||
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
|
||||
|
|
@ -1034,10 +1042,28 @@ function drawCoastline() {
|
|||
if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||
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;
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
|
|
@ -1045,18 +1071,17 @@ function drawCoastline() {
|
|||
function reMarkFeatures() {
|
||||
console.time("reMarkFeatures");
|
||||
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.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.harbor = new Uint16Array(cells.i.length); // cell harbor (number of adjacent water cells);
|
||||
|
||||
for (let i=1, queue=[0]; queue[0] !== -1; i++) {
|
||||
cells.f[queue[0]] = i; // feature number
|
||||
const land = cells.h[queue[0]] >= 20;
|
||||
const start = queue[0]; // first cell
|
||||
cells.f[start] = i; // assign feature number
|
||||
const land = cells.h[start] >= 20;
|
||||
let border = false; // true if feature touches map border
|
||||
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) {
|
||||
const q = queue.pop();
|
||||
|
|
@ -1082,13 +1107,30 @@ function reMarkFeatures() {
|
|||
|
||||
const type = land ? "island" : border ? "ocean" : "lake";
|
||||
let group;
|
||||
if (type === "lake") group = temp < 25 ? "freshwater" : "salt"; else
|
||||
if (type === "ocean") group = "ocean"; else
|
||||
if (type === "island") group = cellNumber > continentCells ? "continent" : cellNumber > islandCell ? "island" : "isle";
|
||||
features.push({i, land, border, type, cells: cellNumber, group});
|
||||
if (type === "lake") group = defineLakeGroup(start, cellNumber);
|
||||
else if (type === "ocean") group = "ocean";
|
||||
else if (type === "island") group = defineIslandGroup(start, cellNumber);
|
||||
features.push({i, land, border, type, cells: cellNumber, firstCell: start, group});
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
@ -1128,17 +1170,17 @@ function defineBiomes() {
|
|||
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");
|
||||
}
|
||||
|
||||
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
|
||||
function rankCells() {
|
||||
console.time('rankCells');
|
||||
|
|
@ -1160,7 +1202,9 @@ function rankCells() {
|
|||
const type = f[cells.f[cells.haven[i]]].type;
|
||||
const group = f[cells.f[cells.haven[i]]].group;
|
||||
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 {
|
||||
s += 5; // ocean coast is valued
|
||||
if (cells.harbor[i] === 1) s += 20; // safe sea harbor is valued
|
||||
|
|
@ -1175,23 +1219,7 @@ function rankCells() {
|
|||
console.timeEnd('rankCells');
|
||||
}
|
||||
|
||||
// add a zone as an example: rebels along one border
|
||||
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
|
||||
// generate some markers
|
||||
function addMarkers(number = 1) {
|
||||
console.time("addMarkers");
|
||||
const cells = pack.cells;
|
||||
|
|
@ -1205,7 +1233,7 @@ function addMarkers(number = 1) {
|
|||
const cell = mounts.splice(biased(0, mounts.length-1, 5), 1);
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
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("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);
|
||||
|
|
@ -1232,7 +1260,7 @@ function addMarkers(number = 1) {
|
|||
.attr("data-size", 1).attr("width", 30).attr("height", 30);
|
||||
|
||||
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}`});
|
||||
count--;
|
||||
}
|
||||
|
|
@ -1396,6 +1424,326 @@ function addMarkers(number = 1) {
|
|||
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
|
||||
function showStatistics() {
|
||||
const template = templateInput.value;
|
||||
|
|
|
|||
|
|
@ -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 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
|
||||
let burgsAdded = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -64,11 +64,14 @@
|
|||
}
|
||||
|
||||
function getRandomCultures(c) {
|
||||
const d = getDefault();
|
||||
const d = getDefault(), n = d.length-1;
|
||||
const count = Math.min(c, d.length);
|
||||
const cultures = [];
|
||||
while (cultures.length < c) {
|
||||
let culture = d[0];
|
||||
do {culture = d[rand(d.length-1)];} while (Math.random() > culture.odd || cultures.find(c => c.name === culture.name))
|
||||
while (cultures.length < count) {
|
||||
let culture = d[rand(n)];
|
||||
do {
|
||||
culture = d[rand(n)];
|
||||
} while (Math.random() > culture.odd || cultures.find(c => c.name === culture.name))
|
||||
cultures.push(culture);
|
||||
}
|
||||
return cultures;
|
||||
|
|
@ -113,6 +116,61 @@
|
|||
}
|
||||
|
||||
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 [
|
||||
{name:"Shwazen", base:0, odd: .7},
|
||||
{name:"Angshire", base:1, odd: 1},
|
||||
|
|
@ -123,28 +181,28 @@
|
|||
{name:"Norse", base:6, odd: .7},
|
||||
{name:"Elladan", base:7, odd: .7},
|
||||
{name:"Romian", base:8, odd: .7},
|
||||
{name:"Soumi", base:9, odd: .4},
|
||||
{name:"Koryo", base:10, odd: .5},
|
||||
{name:"Hantzu", base:11, odd: .5},
|
||||
{name:"Yamoto", base:12, odd: .5},
|
||||
{name:"Soumi", base:9, odd: .3},
|
||||
{name:"Koryo", base:10, odd: .1},
|
||||
{name:"Hantzu", base:11, odd: .1},
|
||||
{name:"Yamoto", base:12, odd: .1},
|
||||
{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:"Turchian", base: 16, odd: .2},
|
||||
{name:"Berberan", base: 17, odd: .2},
|
||||
{name:"Berberan", base: 17, odd: .1},
|
||||
{name:"Eurabic", base: 18, odd: .2},
|
||||
{name:"Inuk", base: 19, odd: .1},
|
||||
{name:"Euskati", base: 20, odd: .1},
|
||||
{name:"Inuk", base: 19, odd: .05},
|
||||
{name:"Euskati", base: 20, odd: .05},
|
||||
{name:"Negarian", base: 21, odd: .05},
|
||||
{name:"Keltan", base: 22, odd: .1},
|
||||
{name:"Efratic", base: 23, odd: .1},
|
||||
{name:"Keltan", base: 22, odd: .05},
|
||||
{name:"Efratic", base: 23, odd: .05},
|
||||
{name:"Tehrani", base: 24, odd: .1},
|
||||
{name:"Maui", base: 25, odd: .05},
|
||||
{name:"Carnatic", base: 26, odd: .1},
|
||||
{name:"Inqan", base: 27, odd: .1},
|
||||
{name:"Carnatic", base: 26, odd: .05},
|
||||
{name:"Inqan", base: 27, odd: .05},
|
||||
{name:"Kiswaili", base: 28, 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}
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,8 +126,9 @@
|
|||
// exclude endings inappropriate for states name
|
||||
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 > 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
|
||||
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 === 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
|
||||
|
||||
// define suffix
|
||||
let suffix = "";
|
||||
let suffix = "ia"; // standard suffix
|
||||
|
||||
const rnd = Math.random(), l = name.length;
|
||||
if (base === 3) suffix = rnd < .03 && l < 7 ? "terra" : "ia"; // Italian
|
||||
else if (base === 4) suffix = rnd < .03 && l < 7 ? "terra" : "ia"; // Spanish
|
||||
else if (base === 13) suffix = rnd < .03 && l < 7 ? "terra" : "ia"; // Portuguese
|
||||
else if (base === 2) suffix = rnd < .03 && l < 7 ? "terre" : "ia"; // French
|
||||
else if (base === 0) suffix = rnd < .5 && l < 7 ? "land" : "ia"; // German
|
||||
else if (base === 1) suffix = rnd < .4 && l < 7 ? "land" : "ia"; // English
|
||||
else if (base === 6) suffix = rnd < .3 && l < 7 ? "land" : "ia"; // Nordic
|
||||
else if (base === 7) suffix = rnd < .1 ? "eia" : "ia"; // Greek
|
||||
else if (base === 9) suffix = rnd < .35 ? "maa" : "ia"; // Finnic
|
||||
else if (base === 15) suffix = rnd < .6 && l < 6 ? "orszag" : "ia"; // Hungarian
|
||||
if (base === 3 && rnd < .03 && l < 7) suffix = "terra"; // Italian
|
||||
else if (base === 4 && rnd < .03 && l < 7) suffix = "terra"; // Spanish
|
||||
else if (base === 13 && rnd < .03 && l < 7) suffix = "terra"; // Portuguese
|
||||
else if (base === 2 && rnd < .03 && l < 7) suffix = "terre"; // French
|
||||
else if (base === 0 && rnd < .5 && l < 7) suffix = "land"; // German
|
||||
else if (base === 1 && rnd < .4 && l < 7 ) suffix = "land"; // English
|
||||
else if (base === 6 && rnd < .3 && l < 7) suffix = "land"; // Nordic
|
||||
else if (base === 7 && rnd < .1) suffix = "eia"; // Greek
|
||||
else if (base === 9 && rnd < .35) suffix = "maa"; // Finnic
|
||||
else if (base === 15 && rnd < .6 && l < 6) suffix = "orszag"; // Hungarian
|
||||
else if (base === 16) suffix = rnd < .5 ? "stan" : "ya"; // Turkish
|
||||
else if (base === 10) suffix = "guk"; // Korean
|
||||
else if (base === 11) suffix = " Guo"; // Chinese
|
||||
else if (base === 14) suffix = rnd < .6 && l < 7 ? "tlan" : "co"; // Nahuatl
|
||||
else if (base === 17) suffix = rnd < .8 ? "a" : "ia"; // Berber
|
||||
else if (base === 18) suffix = rnd < .8 ? "a" : "ia"; // Arabic
|
||||
else suffix = "ia" // other
|
||||
else if (base === 14) suffix = rnd < .6 && l < 6 ? "tlan" : "co"; // Nahuatl
|
||||
else if (base === 17 && rnd < .8) suffix = "a"; // Berber
|
||||
else if (base === 18 && rnd < .8) suffix = "a"; // Arabic
|
||||
|
||||
return validateSuffix(name, suffix);
|
||||
}
|
||||
|
||||
|
|
@ -194,30 +196,30 @@
|
|||
// name, min length, max length, letters to allow duplication, multi-word name rate
|
||||
return [
|
||||
{name: "German", min: 5, max: 12, d: "lt", m: 0},
|
||||
{name: "English", min: 6, max: 11, d: "", m: 0.1},
|
||||
{name: "French", min: 5, max: 13, d: "nlrs", m: 0.1},
|
||||
{name: "Italian", min: 5, max: 12, d: "cltr", m: 0.1},
|
||||
{name: "English", min: 6, max: 11, d: "", m: .1},
|
||||
{name: "French", min: 5, max: 13, d: "nlrs", m: .1},
|
||||
{name: "Italian", min: 5, max: 12, d: "cltr", m: .1},
|
||||
{name: "Castillian", min: 5, max: 11, d: "lr", m: 0},
|
||||
{name: "Ruthenian", min: 5, max: 10, d: "", m: 0},
|
||||
{name: "Nordic", min: 6, max: 10, d: "kln", m: 0.1},
|
||||
{name: "Greek", min: 5, max: 11, d: "s", m: 0.1},
|
||||
{name: "Roman", min: 6, max: 11, d: "ln", m: 0.1},
|
||||
{name: "Nordic", min: 6, max: 10, d: "kln", m: .1},
|
||||
{name: "Greek", min: 5, max: 11, d: "s", m: .1},
|
||||
{name: "Roman", min: 6, max: 11, d: "ln", m: .1},
|
||||
{name: "Finnic", min: 5, max: 11, d: "akiut", m: 0},
|
||||
{name: "Korean", min: 5, max: 11, d: "", m: 0},
|
||||
{name: "Chinese", min: 5, 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: "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: "Berber", min: 4, max: 10, d: "s", m: 0.2},
|
||||
{name: "Arabic", min: 4, max: 9, d: "ae", m: 0.2},
|
||||
{name: "Berber", min: 4, max: 10, d: "s", m: .2},
|
||||
{name: "Arabic", min: 4, max: 9, d: "ae", m: .2},
|
||||
{name: "Inuit", min: 5, max: 15, d: "alutsn", m: 0},
|
||||
{name: "Basque", min: 4, max: 11, d: "r", m: 0.1},
|
||||
{name: "Nigerian", min: 4, max: 10, d: "", m: 0.3},
|
||||
{name: "Basque", min: 4, max: 11, d: "r", m: .1},
|
||||
{name: "Nigerian", min: 4, max: 10, d: "", m: .3},
|
||||
{name: "Celtic", min: 4, max: 12, d: "nld", m: 0},
|
||||
{name: "Mesopotamian", min: 4, max: 9, d: "srpl", m: 0.1},
|
||||
{name: "Iranian", min: 5, max: 11, d: "", m: 0.1},
|
||||
{name: "Mesopotamian", min: 4, max: 9, d: "srpl", m: .1},
|
||||
{name: "Iranian", min: 5, max: 11, d: "", m: .1},
|
||||
{name: "Hawaiian", min: 5, max: 10, d: "auo", m: 1},
|
||||
{name: "Karnataka", min: 5, max: 11, d: "tnl", m: 0},
|
||||
{name: "Quechua", min: 6, max: 12, d: "l", m: 0},
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
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"],
|
||||
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"],
|
||||
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"]
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
|
||||
|
||||
|
|
@ -297,6 +342,34 @@ function saveGeoJSON_Rivers() {
|
|||
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) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength();
|
||||
|
|
@ -414,78 +487,6 @@ function getFileName(dataType) {
|
|||
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) {
|
||||
uploadFile.timeStart = performance.now();
|
||||
|
||||
|
|
@ -635,8 +636,6 @@ function parseLoadedData(data) {
|
|||
ruler = viewbox.select("#ruler");
|
||||
fogging = viewbox.select("#fogging");
|
||||
debug = viewbox.select("#debug");
|
||||
freshwater = lakes.select("#freshwater");
|
||||
salt = lakes.select("#salt");
|
||||
burgLabels = labels.select("#burgLabels");
|
||||
}()
|
||||
|
||||
|
|
@ -775,7 +774,7 @@ function parseLoadedData(data) {
|
|||
// 1.0 adds zones layer
|
||||
zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none");
|
||||
zones.attr("opacity", .6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt");
|
||||
addZone();
|
||||
addZones();
|
||||
if (!markers.selectAll("*").size()) {addMarkers(); turnButtonOn("toggleMarkers");}
|
||||
|
||||
// 1.0 add fogging layer (state focus)
|
||||
|
|
@ -824,6 +823,38 @@ function parseLoadedData(data) {
|
|||
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();
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ function editBiomes() {
|
|||
|
||||
// add listeners
|
||||
document.getElementById("biomesEditorRefresh").addEventListener("click", refreshBiomesEditor);
|
||||
document.getElementById("biomesEditStyle").addEventListener("click", () => editStyle("biomes"));
|
||||
document.getElementById("biomesLegend").addEventListener("click", toggleLegend);
|
||||
document.getElementById("biomesPercentage").addEventListener("click", togglePercentageMode);
|
||||
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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ function editBurg() {
|
|||
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";
|
||||
|
||||
document.getElementById("burgEditAnchorStyle").style.display = +pack.burgs[id].port ? "inline-block" : "none";
|
||||
|
||||
$("#burgEditor").dialog({
|
||||
title: "Edit Burg: " + elSelected.text(), resizable: false,
|
||||
position: {my, at, of: d3.event.target, collision: "fit"},
|
||||
|
|
@ -37,6 +39,12 @@ function editBurg() {
|
|||
document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture);
|
||||
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("burgOpenCOA").addEventListener("click", openInIAHG);
|
||||
document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg);
|
||||
|
|
@ -95,14 +103,18 @@ function editBurg() {
|
|||
|
||||
function createNewGroup() {
|
||||
if (!this.value) {tip("Please provide a valid group name", false, "error"); return;}
|
||||
let group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
|
||||
if (Number.isFinite(+group.charAt(0))) group = "g" + group;
|
||||
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;
|
||||
}
|
||||
|
||||
const id = +elSelected.attr("data-id");
|
||||
const oldGroup = elSelected.node().parentNode.id;
|
||||
|
||||
|
|
@ -211,6 +223,31 @@ function editBurg() {
|
|||
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() {
|
||||
const id = elSelected.attr("data-id");
|
||||
const name = elSelected.text();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ function editBurgs() {
|
|||
|
||||
// add listeners
|
||||
document.getElementById("burgsEditorRefresh").addEventListener("click", refreshBurgsEditor);
|
||||
document.getElementById("burgsChart").addEventListener("click", showBurgsChart);
|
||||
document.getElementById("burgsFilterState").addEventListener("change", burgsEditorAddLines);
|
||||
document.getElementById("burgsFilterCulture").addEventListener("change", burgsEditorAddLines);
|
||||
document.getElementById("regenerateBurgNames").addEventListener("click", regenerateNames);
|
||||
|
|
@ -72,7 +73,7 @@ function editBurgs() {
|
|||
const province = prov ? pack.provinces[prov].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>
|
||||
<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>
|
||||
|
|
@ -250,6 +251,133 @@ function editBurgs() {
|
|||
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'>‍</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 = "‍";
|
||||
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() {
|
||||
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
|
||||
|
|
@ -267,11 +395,11 @@ function editBurgs() {
|
|||
// add geography data
|
||||
data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ",";
|
||||
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
|
||||
data += b.capital ? "true," : "false,";
|
||||
data += b.port ? "true\n" : "false\n";
|
||||
data += b.capital ? "capital," : ",";
|
||||
data += b.port ? "port\n" : "\n";
|
||||
});
|
||||
|
||||
const dataBlob = new Blob([data], {type: "text/plain"});
|
||||
|
|
|
|||
187
modules/ui/coastline-editor.js
Normal file
187
modules/ui/coastline-editor.js
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ function editCultures() {
|
|||
|
||||
// add listeners
|
||||
document.getElementById("culturesEditorRefresh").addEventListener("click", refreshCulturesEditor);
|
||||
document.getElementById("culturesEditStyle").addEventListener("click", () => editStyle("cults"));
|
||||
document.getElementById("culturesLegend").addEventListener("click", toggleLegend);
|
||||
document.getElementById("culturesPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("culturesRecalculate").addEventListener("click", () => recalculateCultures(true));
|
||||
|
|
@ -62,7 +63,7 @@ function editCultures() {
|
|||
const rural = c.rural * populationRate.value;
|
||||
const urban = c.urban * populationRate.value * urbanization.value;
|
||||
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;
|
||||
totalPopulation += population;
|
||||
|
||||
|
|
@ -124,6 +125,7 @@ function editCultures() {
|
|||
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.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-trash-empty").forEach(el => el.addEventListener("click", cultureRemove));
|
||||
|
||||
|
|
@ -202,6 +204,66 @@ function editCultures() {
|
|||
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() {
|
||||
if (customization === 4) return;
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ function editDiplomacy() {
|
|||
|
||||
// add listeners
|
||||
document.getElementById("diplomacyEditorRefresh").addEventListener("click", refreshDiplomacyEditor);
|
||||
document.getElementById("diplomacyEditStyle").addEventListener("click", () => editStyle("regions"));
|
||||
document.getElementById("diplomacyRegenerate").addEventListener("click", regenerateRelations);
|
||||
document.getElementById("diplomacyMatrix").addEventListener("click", showRelationsMatrix);
|
||||
document.getElementById("diplomacyHistory").addEventListener("click", showRelationsHistory);
|
||||
|
|
|
|||
|
|
@ -15,16 +15,25 @@ function restoreDefaultEvents() {
|
|||
|
||||
// on viewbox click event - run function based on target
|
||||
function clicked() {
|
||||
const el = d3.event.target;
|
||||
const el = d3.event.target;
|
||||
if (!el || !el.parentElement || !el.parentElement.parentElement) return;
|
||||
const parent = el.parentElement, grand = parent.parentElement;
|
||||
if (parent.id === "rivers") editRiver(); else
|
||||
if (grand.id === "routes") editRoute(); else
|
||||
if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel(); else
|
||||
if (grand.id === "burgLabels") editBurg(); else
|
||||
if (grand.id === "burgIcons") editBurg(); else
|
||||
if (parent.id === "terrain") editReliefIcon(); else
|
||||
if (parent.id === "markers") editMarker();
|
||||
const p = d3.mouse(this);
|
||||
const i = findCell(p[0], p[1]);
|
||||
|
||||
if (parent.id === "rivers") editRiver();
|
||||
else if (grand.id === "routes") editRoute();
|
||||
else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel();
|
||||
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
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ function showDataTip(e) {
|
|||
|
||||
function moved() {
|
||||
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;
|
||||
showNotes(d3.event, i);
|
||||
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 subgroup = path[path.length - 8].id;
|
||||
const land = pack.cells.h[i] >= 20;
|
||||
//const type = pack.features[cells.f[i]].type;
|
||||
|
||||
// specific elements
|
||||
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 === "burgLabels") {tip("Click to edit the Burg"); return;}
|
||||
if (subgroup === "freshwater" && !land) {tip("Freshwater lake"); return;}
|
||||
if (subgroup === "salt" && !land) {tip("Salt lake"); return;}
|
||||
if (group === "lakes" && !land) {tip(`${capitalize(subgroup)} lake. Click to edit`); return;}
|
||||
if (group === "coastline") {tip("Click to edit the coastline"); return;}
|
||||
if (group === "zones") {tip(path[path.length-8].dataset.description); return;}
|
||||
|
||||
// covering elements
|
||||
|
|
@ -118,6 +119,7 @@ function showMapTooltip(point, e, i, g) {
|
|||
} 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 (pack.cells.t[i] === 1 && !tooltip.textContent) tip("Click to edit the coastline");
|
||||
}
|
||||
|
||||
// get cell info on mouse move
|
||||
|
|
@ -229,6 +231,42 @@ function applyOption(select, 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
|
||||
document.addEventListener("keydown", event => {
|
||||
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();
|
||||
|
||||
const key = event.keyCode, ctrl = event.ctrlKey, shift = event.shiftKey, meta = event.metaKey;
|
||||
if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
|
||||
else if (key === 9) toggleOptions(event); // Tab to toggle options
|
||||
|
||||
if (key === 112) showInfo(); // "F1" to show info
|
||||
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 === 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 === 83) saveAsImage("svg"); // Ctrl + "S" to save as SVG
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
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>`;
|
||||
|
||||
$("#alert").dialog({resizable: false, title: "Edit Heightmap", width: "28em",
|
||||
|
|
@ -62,7 +62,7 @@ function editHeightmap() {
|
|||
} else if (type === "risk") {
|
||||
terrs.attr("mask", null);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -174,8 +174,8 @@ function getHeight(h) {
|
|||
drawStates();
|
||||
drawBorders();
|
||||
BurgsAndStates.drawStateLabels();
|
||||
addZone();
|
||||
addMarkers();
|
||||
addZones();
|
||||
console.timeEnd("regenerateErasedData");
|
||||
console.groupEnd("Edit Heightmap");
|
||||
}
|
||||
|
|
@ -193,7 +193,6 @@ function getHeight(h) {
|
|||
terrs.attr("mask", "url(#land)");
|
||||
|
||||
// assign pack data to grid cells
|
||||
const change = changeHeights.checked;
|
||||
const l = grid.cells.i.length;
|
||||
const biome = new Uint8Array(l);
|
||||
const conf = new Uint8Array(l);
|
||||
|
|
@ -247,10 +246,9 @@ function getHeight(h) {
|
|||
reGraph();
|
||||
drawCoastline();
|
||||
|
||||
if (change) {
|
||||
if (changeHeights.checked) {
|
||||
elevateLakes();
|
||||
Rivers.generate();
|
||||
defineBiomes();
|
||||
}
|
||||
|
||||
// assign saved pack data from grid back to pack
|
||||
|
|
@ -265,25 +263,21 @@ function getHeight(h) {
|
|||
pack.cells.culture = new Uint16Array(n);
|
||||
pack.cells.religion = new Uint16Array(n);
|
||||
|
||||
if (!change) {
|
||||
pack.cells.r = new Uint16Array(n);
|
||||
pack.cells.conf = new Uint8Array(n);
|
||||
pack.cells.fl = new Uint16Array(n);
|
||||
pack.cells.biome = new Uint8Array(n);
|
||||
}
|
||||
pack.cells.r = new Uint16Array(n);
|
||||
pack.cells.conf = new Uint8Array(n);
|
||||
pack.cells.fl = new Uint16Array(n);
|
||||
pack.cells.biome = new Uint8Array(n);
|
||||
|
||||
for (const i of pack.cells.i) {
|
||||
const g = pack.cells.g[i];
|
||||
const land = pack.cells.h[i] >= 20;
|
||||
|
||||
if (!change) {
|
||||
pack.cells.r[i] = r[g];
|
||||
pack.cells.conf[i] = conf[g];
|
||||
pack.cells.fl[i] = fl[g];
|
||||
if (land && !biome[g]) pack.cells.biome[i] = getBiomeId(grid.cells.prec[g], grid.cells.temp[g]); else
|
||||
if (!land && biome[g]) pack.cells.biome[i] = 0; else
|
||||
pack.cells.biome[i] = biome[g];
|
||||
}
|
||||
pack.cells.r[i] = r[g];
|
||||
pack.cells.conf[i] = conf[g];
|
||||
pack.cells.fl[i] = fl[g];
|
||||
if (land && !biome[g]) pack.cells.biome[i] = getBiomeId(grid.cells.prec[g], grid.cells.temp[g]);
|
||||
else if (!land && biome[g]) pack.cells.biome[i] = 0;
|
||||
else pack.cells.biome[i] = biome[g];
|
||||
|
||||
if (!land) continue;
|
||||
pack.cells.culture[i] = culture[g];
|
||||
|
|
@ -939,8 +933,7 @@ function getHeight(h) {
|
|||
document.getElementById("imageToLoad").addEventListener("change", loadImage);
|
||||
document.getElementById("convertAutoLum").addEventListener("click", () => autoAssing("lum"));
|
||||
document.getElementById("convertAutoHue").addEventListener("click", () => autoAssing("hue"));
|
||||
document.getElementById("convertColorsPlus").addEventListener("click", () => changeConvertColorsNumber(1));
|
||||
document.getElementById("convertColorsMinus").addEventListener("click", () => changeConvertColorsNumber(-1));
|
||||
document.getElementById("convertColorsButton").addEventListener("click", setConvertColorsNumber);
|
||||
document.getElementById("convertComplete").addEventListener("click", () => $("#imageConverter").dialog("close"));
|
||||
document.getElementById("convertOverlay").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")
|
||||
.attr("data-color", i => i).style("background-color", i => i)
|
||||
.attr("class", "color-div").on("click", colorClicked);
|
||||
|
||||
convertColors.value = unassigned.length;
|
||||
}
|
||||
|
||||
function mapClicked() {
|
||||
|
|
@ -1078,9 +1073,11 @@ function getHeight(h) {
|
|||
colorsAssigned.style.display = "block";
|
||||
colorsUnassigned.style.display = "none";
|
||||
}
|
||||
|
||||
function changeConvertColorsNumber(change) {
|
||||
const number = Math.max(Math.min(+convertColors.value + change, 255), 3);
|
||||
|
||||
function setConvertColorsNumber() {
|
||||
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;
|
||||
heightsFromImage(number);
|
||||
}
|
||||
|
|
@ -1190,4 +1187,4 @@ function getHeight(h) {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ function editLabel() {
|
|||
document.getElementById("labelTextShow").addEventListener("click", showTextSection);
|
||||
document.getElementById("labelTextHide").addEventListener("click", hideTextSection);
|
||||
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("labelSizeHide").addEventListener("click", hideSizeSection);
|
||||
|
|
@ -181,14 +183,18 @@ function editLabel() {
|
|||
|
||||
function createNewGroup() {
|
||||
if (!this.value) {tip("Please provide a valid group name"); return;}
|
||||
let group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
|
||||
if (Number.isFinite(+group.charAt(0))) group = "g" + group;
|
||||
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;
|
||||
if (oldGroup !== "states" && oldGroup !== "addedLabels" && oldGroup.childElementCount === 1) {
|
||||
|
|
@ -281,6 +287,11 @@ function editLabel() {
|
|||
changeText();
|
||||
}
|
||||
|
||||
function editGroupStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle("labels", g);
|
||||
}
|
||||
|
||||
function showSizeSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => el.style.display = "none");
|
||||
document.getElementById("labelSizeSection").style.display = "inline-block";
|
||||
|
|
|
|||
193
modules/ui/lakes-editor.js
Normal file
193
modules/ui/lakes-editor.js
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -113,11 +113,13 @@ function getCurrentPreset() {
|
|||
savePresetButton.style.display = "inline-block";
|
||||
}
|
||||
|
||||
function toggleHeight() {
|
||||
function toggleHeight(event) {
|
||||
if (!terrs.selectAll("*").size()) {
|
||||
turnButtonOn("toggleHeight");
|
||||
drawHeightmap();
|
||||
if (event && event.ctrlKey) editStyle("terrs");
|
||||
} 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;}
|
||||
turnButtonOff("toggleHeight");
|
||||
terrs.selectAll("*").remove();
|
||||
|
|
@ -208,11 +210,13 @@ function getColor(value, scheme = getColorScheme()) {
|
|||
return scheme(1 - (value < 20 ? value - 5 : value) / 100);
|
||||
}
|
||||
|
||||
function toggleTemp() {
|
||||
function toggleTemp(event) {
|
||||
if (!temperature.selectAll("*").size()) {
|
||||
turnButtonOn("toggleTemp");
|
||||
drawTemp();
|
||||
if (event && event.ctrlKey) editStyle("temperature");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("temperature"); return;}
|
||||
turnButtonOff("toggleTemp");
|
||||
temperature.selectAll("*").remove();
|
||||
}
|
||||
|
|
@ -310,11 +314,13 @@ function drawTemp() {
|
|||
console.timeEnd("drawTemp");
|
||||
}
|
||||
|
||||
function toggleBiomes() {
|
||||
function toggleBiomes(event) {
|
||||
if (!biomes.selectAll("path").size()) {
|
||||
turnButtonOn("toggleBiomes");
|
||||
drawBiomes();
|
||||
if (event && event.ctrlKey) editStyle("biomes");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("biomes"); return;}
|
||||
biomes.selectAll("path").remove();
|
||||
turnButtonOff("toggleBiomes");
|
||||
}
|
||||
|
|
@ -365,11 +371,13 @@ function drawBiomes() {
|
|||
}
|
||||
}
|
||||
|
||||
function togglePrec() {
|
||||
function togglePrec(event) {
|
||||
if (!prec.selectAll("circle").size()) {
|
||||
turnButtonOn("togglePrec");
|
||||
drawPrec();
|
||||
if (event && event.ctrlKey) editStyle("prec");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("prec"); return;}
|
||||
turnButtonOff("togglePrec");
|
||||
const hide = d3.transition().duration(1000).ease(d3.easeSinIn);
|
||||
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));
|
||||
}
|
||||
|
||||
function togglePopulation() {
|
||||
function togglePopulation(event) {
|
||||
if (!population.selectAll("line").size()) {
|
||||
turnButtonOn("togglePopulation");
|
||||
drawPopulation();
|
||||
if (event && event.ctrlKey) editStyle("population");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("population"); return;}
|
||||
turnButtonOff("togglePopulation");
|
||||
const hide = d3.transition().duration(1000).ease(d3.easeSinIn);
|
||||
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();
|
||||
const cells = pack.cells, p = cells.p, burgs = pack.burgs;
|
||||
const show = d3.transition().duration(2000).ease(d3.easeSinIn);
|
||||
|
|
@ -421,11 +431,13 @@ function drawPopulation() {
|
|||
.transition(show).delay(500).attr("y2", d => d[2]);
|
||||
}
|
||||
|
||||
function toggleCells() {
|
||||
function toggleCells(event) {
|
||||
if (!cells.selectAll("path").size()) {
|
||||
turnButtonOn("toggleCells");
|
||||
drawCells();
|
||||
if (event && event.ctrlKey) editStyle("cells");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("cells"); return;}
|
||||
cells.selectAll("path").remove();
|
||||
turnButtonOff("toggleCells");
|
||||
}
|
||||
|
|
@ -440,17 +452,19 @@ function drawCells() {
|
|||
cells.append("path").attr("d", path);
|
||||
}
|
||||
|
||||
function toggleCultures() {
|
||||
function toggleCultures(event) {
|
||||
if (!cults.selectAll("path").size()) {
|
||||
turnButtonOn("toggleCultures");
|
||||
drawCultures();
|
||||
if (event && event.ctrlKey) editStyle("cults");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("cults"); return;}
|
||||
cults.selectAll("path").remove();
|
||||
turnButtonOff("toggleCultures");
|
||||
}
|
||||
}
|
||||
|
||||
function drawCultures() {
|
||||
function drawCultures(event) {
|
||||
console.time("drawCultures");
|
||||
|
||||
cults.selectAll("path").remove();
|
||||
|
|
@ -497,11 +511,13 @@ function drawCultures() {
|
|||
console.timeEnd("drawCultures");
|
||||
}
|
||||
|
||||
function toggleReligions() {
|
||||
function toggleReligions(event) {
|
||||
if (!relig.selectAll("path").size()) {
|
||||
turnButtonOn("toggleReligions");
|
||||
drawReligions();
|
||||
if (event && event.ctrlKey) editStyle("relig");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("relig"); return;}
|
||||
relig.selectAll("path").remove();
|
||||
turnButtonOff("toggleReligions");
|
||||
}
|
||||
|
|
@ -511,8 +527,9 @@ function drawReligions() {
|
|||
console.time("drawReligions");
|
||||
|
||||
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 fUsed = []; // already added features like lakes
|
||||
const paths = new Array(religions.length).fill("");
|
||||
|
||||
for (const i of cells.i) {
|
||||
|
|
@ -520,9 +537,17 @@ function drawReligions() {
|
|||
if (used[i]) continue;
|
||||
used[i] = 1;
|
||||
const r = cells.religion[i];
|
||||
const onborder = cells.c[i].some(n => cells.religion[n] !== r);
|
||||
if (!onborder) continue;
|
||||
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.religion[i] !== r));
|
||||
const onborder = cells.c[i].filter(n => cells.religion[n] !== r);
|
||||
if (!onborder.length) continue;
|
||||
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);
|
||||
if (chain.length < 3) continue;
|
||||
const points = chain.map(v => vertices.p[v]);
|
||||
|
|
@ -554,12 +579,14 @@ function drawReligions() {
|
|||
console.timeEnd("drawReligions");
|
||||
}
|
||||
|
||||
function toggleStates() {
|
||||
function toggleStates(event) {
|
||||
if (!layerIsOn("toggleStates")) {
|
||||
turnButtonOn("toggleStates");
|
||||
regions.attr("display", null);
|
||||
drawStates();
|
||||
if (event && event.ctrlKey) editStyle("regions");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("regions"); return;}
|
||||
regions.attr("display", "none").selectAll("path").remove();
|
||||
turnButtonOff("toggleStates");
|
||||
}
|
||||
|
|
@ -735,21 +762,25 @@ function drawBorders() {
|
|||
console.timeEnd("drawBorders");
|
||||
}
|
||||
|
||||
function toggleBorders() {
|
||||
function toggleBorders(event) {
|
||||
if (!layerIsOn("toggleBorders")) {
|
||||
turnButtonOn("toggleBorders");
|
||||
$('#borders').fadeIn();
|
||||
if (event && event.ctrlKey) editStyle("borders");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("borders"); return;}
|
||||
turnButtonOff("toggleBorders");
|
||||
$('#borders').fadeOut();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleProvinces() {
|
||||
function toggleProvinces(event) {
|
||||
if (!layerIsOn("toggleProvinces")) {
|
||||
turnButtonOn("toggleProvinces");
|
||||
drawProvinces();
|
||||
if (event && event.ctrlKey) editStyle("provs");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("provs"); return;}
|
||||
provs.selectAll("*").remove();
|
||||
turnButtonOff("toggleProvinces");
|
||||
}
|
||||
|
|
@ -828,12 +859,14 @@ function drawProvinces() {
|
|||
console.timeEnd("drawProvinces");
|
||||
}
|
||||
|
||||
function toggleGrid() {
|
||||
function toggleGrid(event) {
|
||||
if (!gridOverlay.selectAll("*").size()) {
|
||||
turnButtonOn("toggleGrid");
|
||||
drawGrid();
|
||||
calculateFriendlyGridSize();
|
||||
if (event && event.ctrlKey) editStyle("gridOverlay");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("gridOverlay"); return;}
|
||||
turnButtonOff("toggleGrid");
|
||||
gridOverlay.selectAll("*").remove();
|
||||
}
|
||||
|
|
@ -887,11 +920,13 @@ function drawGrid() {
|
|||
console.timeEnd("drawGrid");
|
||||
}
|
||||
|
||||
function toggleCoordinates() {
|
||||
function toggleCoordinates(event) {
|
||||
if (!coordinates.selectAll("*").size()) {
|
||||
turnButtonOn("toggleCoordinates");
|
||||
drawCoordinates();
|
||||
if (event && event.ctrlKey) editStyle("coordinates");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("coordinates"); return;}
|
||||
turnButtonOff("toggleCoordinates");
|
||||
coordinates.selectAll("*").remove();
|
||||
}
|
||||
|
|
@ -937,7 +972,7 @@ function getViewPoint(x, y) {
|
|||
return pt.matrixTransform(view.getScreenCTM().inverse());
|
||||
}
|
||||
|
||||
function toggleCompass() {
|
||||
function toggleCompass(event) {
|
||||
if (!layerIsOn("toggleCompass")) {
|
||||
turnButtonOn("toggleCompass");
|
||||
$('#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#sL2").attr("x1", -19000).attr("x2", 19000);
|
||||
}
|
||||
if (event && event.ctrlKey) editStyle("compass");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("compass"); return;}
|
||||
$('#compass').fadeOut();
|
||||
turnButtonOff("toggleCompass");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRelief() {
|
||||
function toggleRelief(event) {
|
||||
if (!layerIsOn("toggleRelief")) {
|
||||
turnButtonOn("toggleRelief");
|
||||
if (!terrain.selectAll("*").size()) ReliefIcons();
|
||||
$('#terrain').fadeIn();
|
||||
if (event && event.ctrlKey) editStyle("terrain");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("terrain"); return;}
|
||||
$('#terrain').fadeOut();
|
||||
turnButtonOff("toggleRelief");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTexture() {
|
||||
function toggleTexture(event) {
|
||||
if (!layerIsOn("toggleTexture")) {
|
||||
turnButtonOn("toggleTexture");
|
||||
// append default texture image selected by default. Don't append on load to not harm performance
|
||||
|
|
@ -976,88 +1015,106 @@ function toggleTexture() {
|
|||
}
|
||||
$('#texture').fadeIn();
|
||||
zoom.scaleBy(svg, 1.00001); // enforce browser re-draw
|
||||
if (event && event.ctrlKey) editStyle("texture");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("texture"); return;}
|
||||
$('#texture').fadeOut();
|
||||
turnButtonOff("toggleTexture");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRivers() {
|
||||
function toggleRivers(event) {
|
||||
if (!layerIsOn("toggleRivers")) {
|
||||
turnButtonOn("toggleRivers");
|
||||
$('#rivers').fadeIn();
|
||||
if (event && event.ctrlKey) editStyle("rivers");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("rivers"); return;}
|
||||
$('#rivers').fadeOut();
|
||||
turnButtonOff("toggleRivers");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRoutes() {
|
||||
function toggleRoutes(event) {
|
||||
if (!layerIsOn("toggleRoutes")) {
|
||||
turnButtonOn("toggleRoutes");
|
||||
$('#routes').fadeIn();
|
||||
if (event && event.ctrlKey) editStyle("routes");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("routes"); return;}
|
||||
$('#routes').fadeOut();
|
||||
turnButtonOff("toggleRoutes");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMarkers() {
|
||||
function toggleMarkers(event) {
|
||||
if (!layerIsOn("toggleMarkers")) {
|
||||
turnButtonOn("toggleMarkers");
|
||||
$('#markers').fadeIn();
|
||||
if (event && event.ctrlKey) editStyle("markers");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("markers"); return;}
|
||||
$('#markers').fadeOut();
|
||||
turnButtonOff("toggleMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLabels() {
|
||||
function toggleLabels(event) {
|
||||
if (!layerIsOn("toggleLabels")) {
|
||||
turnButtonOn("toggleLabels");
|
||||
labels.style("display", null)
|
||||
invokeActiveZooming();
|
||||
if (event && event.ctrlKey) editStyle("labels");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("labels"); return;}
|
||||
turnButtonOff("toggleLabels");
|
||||
labels.style("display", "none");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleIcons() {
|
||||
function toggleIcons(event) {
|
||||
if (!layerIsOn("toggleIcons")) {
|
||||
turnButtonOn("toggleIcons");
|
||||
$('#icons').fadeIn();
|
||||
if (event && event.ctrlKey) editStyle("burgIcons");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("burgIcons"); return;}
|
||||
turnButtonOff("toggleIcons");
|
||||
$('#icons').fadeOut();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRulers() {
|
||||
function toggleRulers(event) {
|
||||
if (!layerIsOn("toggleRulers")) {
|
||||
turnButtonOn("toggleRulers");
|
||||
$('#ruler').fadeIn();
|
||||
if (event && event.ctrlKey) editStyle("ruler");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("ruler"); return;}
|
||||
$('#ruler').fadeOut();
|
||||
turnButtonOff("toggleRulers");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleScaleBar() {
|
||||
function toggleScaleBar(event) {
|
||||
if (!layerIsOn("toggleScaleBar")) {
|
||||
turnButtonOn("toggleScaleBar");
|
||||
$('#scaleBar').fadeIn();
|
||||
if (event && event.ctrlKey) editUnits();
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editUnits(); return;}
|
||||
$('#scaleBar').fadeOut();
|
||||
turnButtonOff("toggleScaleBar");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleZones() {
|
||||
function toggleZones(event) {
|
||||
if (!layerIsOn("toggleZones")) {
|
||||
turnButtonOn("toggleZones");
|
||||
$('#zones').fadeIn();
|
||||
if (event && event.ctrlKey) editStyle("zones");
|
||||
} else {
|
||||
if (event && event.ctrlKey) {editStyle("zones"); return;}
|
||||
turnButtonOff("toggleZones");
|
||||
$('#zones').fadeOut();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -487,6 +487,8 @@ function editMarker() {
|
|||
buttons: {
|
||||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
const index = notes.findIndex(n => n.id === elSelected.attr("id"));
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
elSelected.remove();
|
||||
$("#markerEditor").dialog("close");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ options.querySelector("div.tab").addEventListener("click", function(event) {
|
|||
document.getElementById(id).classList.add("active");
|
||||
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 === "optionsTab") optionsContent.style.display = "block"; else
|
||||
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
|
||||
styleElementSelect.addEventListener("change", selectStyleElement);
|
||||
function selectStyleElement() {
|
||||
|
|
@ -97,7 +114,7 @@ function selectStyleElement() {
|
|||
// active group element
|
||||
const group = styleGroupSelect.value;
|
||||
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()
|
||||
? d3.select("#"+sel).select("g#"+group)
|
||||
: d3.select("#"+sel).select("g");
|
||||
|
|
@ -168,7 +185,7 @@ function selectStyleElement() {
|
|||
if (sel === "gridOverlay") styleGrid.style.display = "block";
|
||||
if (sel === "terrain") styleRelief.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 === "population") {
|
||||
|
|
@ -250,8 +267,10 @@ function selectStyleElement() {
|
|||
}
|
||||
|
||||
if (sel === "coastline") {
|
||||
styleCoastline.style.display = "block";
|
||||
if (styleCoastlineAuto.checked) styleFilter.style.display = "none";
|
||||
if (styleGroupSelect.value === "sea_island") {
|
||||
styleCoastline.style.display = "block";
|
||||
if (styleCoastlineAuto.checked) styleFilter.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
if (sel === "temperature") {
|
||||
|
|
@ -270,7 +289,7 @@ function selectStyleElement() {
|
|||
|
||||
// update group 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 => {
|
||||
if (el.id === "burgLabels") return;
|
||||
const count = el.childElementCount;
|
||||
|
|
@ -670,6 +689,7 @@ optionsContent.addEventListener("input", function(event) {
|
|||
else if (id === "densityInput" || id === "densityOutput") changeCellsDensity(+value);
|
||||
else if (id === "culturesInput") culturesOutput.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 === "provincesInput") provincesOutput.value = value;
|
||||
else if (id === "provincesOutput") provincesOutput.value = value;
|
||||
|
|
@ -795,8 +815,15 @@ function changeCellsDensity(value) {
|
|||
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) {
|
||||
regionsInput.value = regionsOutput.value = value;
|
||||
regionsOutput.style.color = +value ? null : "#b12117";
|
||||
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));
|
||||
}
|
||||
|
|
@ -880,6 +907,7 @@ function randomizeOptions() {
|
|||
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("cultures")) culturesInput.value = culturesOutput.value = gauss(12, 3, 5, 30);
|
||||
changeCultureSet();
|
||||
|
||||
// 'Configure World' settings
|
||||
if (!locked("prec")) precInput.value = precOutput.value = gauss(100, 20, 5, 500);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ function editProvinces() {
|
|||
|
||||
// add listeners
|
||||
document.getElementById("provincesEditorRefresh").addEventListener("click", refreshProvincesEditor);
|
||||
document.getElementById("provincesEditStyle").addEventListener("click", () => editStyle("provs"));
|
||||
document.getElementById("provincesFilterState").addEventListener("change", provincesEditorAddLines);
|
||||
document.getElementById("provincesPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("provincesChart").addEventListener("click", showChart);
|
||||
|
|
@ -37,6 +38,8 @@ function editProvinces() {
|
|||
if (cl.contains("zoneFill")) changeFill(el); else
|
||||
if (cl.contains("icon-fleur")) provinceOpenCOA(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-trash-empty")) removeProvince(p);
|
||||
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 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();
|
||||
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>
|
||||
|
|
@ -120,6 +124,7 @@ function editProvinces() {
|
|||
<div data-tip="Province area" class="biomeArea hide">${si(area) + unit}</div>
|
||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<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="Remove the province" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
|
|
@ -201,6 +206,124 @@ function editProvinces() {
|
|||
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) {
|
||||
const inactive = cl.contains("inactive");
|
||||
cl.toggle("inactive");
|
||||
|
|
@ -286,8 +409,10 @@ function editProvinces() {
|
|||
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"}
|
||||
});
|
||||
const provinces = pack.provinces.filter(p => p.i && !p.removed);
|
||||
provinces.forEach(p => p.id = p.i + states.length - 1);
|
||||
const provinces = pack.provinces.filter(p => p.i && !p.removed).map(p => {
|
||||
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 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 === "urban" ? d => d.urban
|
||||
: d => d.rural + d.urban;
|
||||
|
||||
const newRoot = d3.stratify().parentId(d => d.state)(data).sum(value);
|
||||
node.data(treeLayout(newRoot).leaves());
|
||||
|
||||
root.sum(value);
|
||||
node.data(treeLayout(root).leaves());
|
||||
|
||||
node.select("rect").transition().duration(1500)
|
||||
.attr("x", d => d.x0).attr("y", d => d.y0)
|
||||
|
|
@ -550,7 +675,7 @@ function editProvinces() {
|
|||
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;}
|
||||
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();
|
||||
|
||||
|
|
@ -638,7 +763,7 @@ function editProvinces() {
|
|||
|
||||
function closeProvincesEditor() {
|
||||
if (customization === 11) exitProvincesManualAssignment("close");
|
||||
if (customization === 12) exitAddStateMode();
|
||||
if (customization === 12) exitAddProvinceMode();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ function editReliefIcon() {
|
|||
document.getElementById("reliefEditorSet").addEventListener("change", changeIconsSet);
|
||||
reliefIconsDiv.querySelectorAll("svg").forEach(el => el.addEventListener("click", changeIcon));
|
||||
|
||||
document.getElementById("reliefEditStyle").addEventListener("click", () => editStyle("terrain"));
|
||||
document.getElementById("reliefCopy").addEventListener("click", copyIcon);
|
||||
document.getElementById("reliefMoveFront").addEventListener("click", () => elSelected.raise());
|
||||
document.getElementById("reliefMoveBack").addEventListener("click", () => elSelected.lower());
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ function editReligions() {
|
|||
|
||||
// add listeners
|
||||
document.getElementById("religionsEditorRefresh").addEventListener("click", refreshReligionsEditor);
|
||||
document.getElementById("religionsEditStyle").addEventListener("click", () => editStyle("relig"));
|
||||
document.getElementById("religionsLegend").addEventListener("click", toggleLegend);
|
||||
document.getElementById("religionsPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("religionsHeirarchy").addEventListener("click", showHierarchy);
|
||||
|
|
@ -65,7 +66,7 @@ function editReligions() {
|
|||
const urban = r.urban * populationRate.value * urbanization.value;
|
||||
const population = rn(rural + urban);
|
||||
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;
|
||||
totalPopulation += population;
|
||||
|
||||
|
|
@ -123,6 +124,7 @@ function editReligions() {
|
|||
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 > 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));
|
||||
|
||||
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
|
||||
|
|
@ -227,6 +229,67 @@ function editReligions() {
|
|||
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() {
|
||||
if (customization) return;
|
||||
const religion = +this.parentNode.dataset.id;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ function editRiver() {
|
|||
document.getElementById("riverScale").addEventListener("input", changeScale);
|
||||
document.getElementById("riverReset").addEventListener("click", resetTransformation);
|
||||
|
||||
document.getElementById("riverEditStyle").addEventListener("click", () => editStyle("rivers"));
|
||||
document.getElementById("riverCopy").addEventListener("click", copyRiver);
|
||||
document.getElementById("riverNew").addEventListener("click", toggleRiverCreationMode);
|
||||
document.getElementById("riverLegend").addEventListener("click", editRiverLegend);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ function editRoute(onClick) {
|
|||
document.getElementById("routeGroupName").addEventListener("change", createNewGroup);
|
||||
document.getElementById("routeGroupRemove").addEventListener("click", removeRouteGroup);
|
||||
document.getElementById("routeGroupsHide").addEventListener("click", hideGroupSection);
|
||||
|
||||
document.getElementById("routeEditStyle").addEventListener("click", editGroupStyle);
|
||||
document.getElementById("routeSplit").addEventListener("click", toggleRouteSplitMode);
|
||||
document.getElementById("routeLegend").addEventListener("click", editRouteLegend);
|
||||
document.getElementById("routeNew").addEventListener("click", toggleRouteCreationMode);
|
||||
|
|
@ -141,14 +143,17 @@ function editRoute(onClick) {
|
|||
|
||||
function createNewGroup() {
|
||||
if (!this.value) {tip("Please provide a valid group name"); return;}
|
||||
let group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
|
||||
if (Number.isFinite(+group.charAt(0))) group = "g" + group;
|
||||
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 = ["roads", "trails", "searoutes"].includes(oldGroup.id);
|
||||
|
|
@ -189,7 +194,12 @@ function editRoute(onClick) {
|
|||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function editGroupStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle("routes", g);
|
||||
}
|
||||
|
||||
function toggleRouteSplitMode() {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ function editStates() {
|
|||
|
||||
// add listeners
|
||||
document.getElementById("statesEditorRefresh").addEventListener("click", refreshStatesEditor);
|
||||
document.getElementById("statesEditStyle").addEventListener("click", () => editStyle("regions"));
|
||||
document.getElementById("statesLegend").addEventListener("click", toggleLegend);
|
||||
document.getElementById("statesPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("statesChart").addEventListener("click", showStatesChart);
|
||||
|
|
@ -42,6 +43,7 @@ function editStates() {
|
|||
if (cl.contains("zoneFill")) stateChangeFill(el); else
|
||||
if (cl.contains("icon-fleur")) stateOpenCOA(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-trash-empty")) stateRemove(state); 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 urban = s.urban * populationRate.value * urbanization.value;
|
||||
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;
|
||||
totalPopulation += population;
|
||||
totalBurgs += s.burgs;
|
||||
|
|
@ -289,6 +291,66 @@ function editStates() {
|
|||
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) {
|
||||
const capital = pack.states[state].capital;
|
||||
const l = burgLabels.select("[data-id='" + capital + "']");
|
||||
|
|
@ -419,7 +481,7 @@ function editStates() {
|
|||
|
||||
const node = graph.selectAll("g").data(root.leaves()).enter()
|
||||
.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("mouseleave", d => hideInfo(event, d));
|
||||
|
||||
|
|
@ -642,7 +704,7 @@ function editStates() {
|
|||
const provCells = cells.i.filter(i => cells.province[i] === p);
|
||||
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];
|
||||
if (owner) {
|
||||
const name = provinces[p].name;
|
||||
|
|
@ -714,46 +776,73 @@ function editStates() {
|
|||
}
|
||||
|
||||
function addState() {
|
||||
const states = pack.states, burgs = pack.burgs, cells = pack.cells;
|
||||
const point = d3.mouse(this);
|
||||
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;}
|
||||
let burg = pack.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 (cells.h[center] < 20) {tip("You cannot place state into the water. Please click on a land cell", false, "error"); return;}
|
||||
let burg = cells.burg[center];
|
||||
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
|
||||
|
||||
const oldState = cells.state[center];
|
||||
const newState = states.length;
|
||||
|
||||
// turn burg into a capital
|
||||
pack.burgs[burg].capital = true;
|
||||
pack.burgs[burg].state = pack.states.length;
|
||||
burgs[burg].capital = true;
|
||||
burgs[burg].state = newState;
|
||||
moveBurgToGroup(burg, "cities");
|
||||
|
||||
if (d3.event.shiftKey === false) exitAddStateMode();
|
||||
|
||||
const i = pack.states.length;
|
||||
const culture = pack.cells.culture[center];
|
||||
const basename = center%5 === 0 ? pack.burgs[burg].name : Names.getCulture(culture);
|
||||
const culture = cells.culture[center];
|
||||
const basename = center%5 === 0 ? burgs[burg].name : Names.getCulture(culture);
|
||||
const name = Names.getState(basename, culture);
|
||||
const color = d3.color(d3.scaleSequential(d3.interpolateRainbow)(Math.random())).hex();
|
||||
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 color = getRandomColor();
|
||||
|
||||
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;
|
||||
pack.cells.c[center].forEach(c => {
|
||||
if (pack.cells.h[c] < 20) return;
|
||||
if (pack.cells.burg[c]) return;
|
||||
affected.push(pack.cells.state[c]);
|
||||
pack.cells.state[c] = pack.states.length;
|
||||
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;
|
||||
});
|
||||
pack.states.push({i, name, diplomacy, provinces, color, expansionism:.5, capital:burg, type:"Generic", center, culture});
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.defineStateForms([i]);
|
||||
diplomacy.push("x");
|
||||
states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldState].name}`]);
|
||||
|
||||
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("toggleBorders")) toggleBorders(); else drawBorders();
|
||||
BurgsAndStates.drawStateLabels(affected);
|
||||
BurgsAndStates.drawStateLabels([...new Set(affectedStates)]);
|
||||
statesEditorAddLines();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ function processFeatureRegeneration(button) {
|
|||
if (button === "regenerateStates") regenerateStates(); else
|
||||
if (button === "regenerateProvinces") regenerateProvinces(); else
|
||||
if (button === "regenerateReligions") regenerateReligions(); else
|
||||
if (button === "regenerateMarkers") regenerateMarkers();
|
||||
if (button === "regenerateMarkers") regenerateMarkers(); else
|
||||
if (button === "regenerateZones") regenerateZones();
|
||||
}
|
||||
|
||||
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 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
|
||||
|
||||
for (let i=0; i < sorted.length && burgs.length < burgsCount; i++) {
|
||||
|
|
@ -107,7 +108,7 @@ function regenerateBurgs() {
|
|||
|
||||
const state = cells.state[cell];
|
||||
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 name = Names.getCulture(culture);
|
||||
|
|
@ -120,6 +121,7 @@ function regenerateBurgs() {
|
|||
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
|
||||
s.capital = burg;
|
||||
s.center = pack.burgs[burg].cell;
|
||||
pack.burgs[burg].capital = true;
|
||||
pack.burgs[burg].state = s.i;
|
||||
moveBurgToGroup(burg, "cities");
|
||||
|
|
@ -135,11 +137,18 @@ function regenerateBurgs() {
|
|||
|
||||
function regenerateStates() {
|
||||
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);
|
||||
// burgs sorted by a bit randomized population:
|
||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
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 capitalsTree = d3.quadtree();
|
||||
let spacing = (graphWidth + graphHeight) / 2 / states.length; // min distance between capitals
|
||||
|
||||
// turn all old capitals into towns
|
||||
burgs.filter(b => b.capital).forEach(b => {
|
||||
|
|
@ -147,30 +156,31 @@ function regenerateStates() {
|
|||
b.capital = false;
|
||||
});
|
||||
|
||||
states.forEach(s => {
|
||||
let newCapital = 0, x = 0, y = 0;
|
||||
const neutral = pack.states[0].name;
|
||||
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++) {
|
||||
newCapital = burgs[sorted[i]];
|
||||
x = newCapital.x, y = newCapital.y;
|
||||
if (capitalsTree.find(x, y, spacing) !== undefined) {
|
||||
spacing -= 1;
|
||||
if (spacing < 1) spacing = 1;
|
||||
newCapital = 0;
|
||||
}
|
||||
let capital = null, x = 0, y = 0;
|
||||
for (let i=0; i < sorted.length; i++) {
|
||||
capital = burgs[sorted[i]];
|
||||
x = capital.x, y = capital.y;
|
||||
if (capitalsTree.find(x, y, spacing) === undefined) break;
|
||||
spacing = Math.max(spacing - 1, 1);
|
||||
}
|
||||
|
||||
capitalsTree.add([x, y]);
|
||||
newCapital.capital = true;
|
||||
s.capital = newCapital.i;
|
||||
s.center = newCapital.cell;
|
||||
s.culture = newCapital.culture;
|
||||
s.expansionism = rn(Math.random() * powerInput.value + 1, 1);
|
||||
const basename = newCapital.name.length < 9 && newCapital.cell%5 === 0 ? newCapital.name : Names.getCulture(s.culture, 3, 6, "", 0);
|
||||
s.name = Names.getState(basename, s.culture);
|
||||
const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[newCapital.cell]);
|
||||
s.type = nomadic ? "Nomadic" : pack.cultures[s.culture].type === "Nomadic" ? "Generic" : pack.cultures[s.culture].type;
|
||||
moveBurgToGroup(newCapital.i, "cities");
|
||||
capital.capital = true;
|
||||
moveBurgToGroup(capital.i, "cities");
|
||||
|
||||
const culture = capital.culture;
|
||||
const basename = capital.name.length < 9 && capital.cell%5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0);
|
||||
const name = Names.getState(basename, culture);
|
||||
const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]);
|
||||
const type = nomadic ? "Nomadic" : pack.cultures[culture].type === "Nomadic" ? "Generic" : pack.cultures[culture].type;
|
||||
const expansionism = rn(Math.random() * powerInput.value + 1, 1);
|
||||
return {i, name, type, capital:capital.i, center:capital.cell, culture, expansionism};
|
||||
});
|
||||
|
||||
unfog();
|
||||
|
|
@ -202,10 +212,21 @@ function regenerateReligions() {
|
|||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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() {
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
restoreDefaultEvents();
|
||||
|
|
@ -244,6 +265,7 @@ function addLabelOnClick() {
|
|||
const x = width / -2; // x offset;
|
||||
example.remove();
|
||||
|
||||
group.classed("hidden", false);
|
||||
group.append("text").attr("id", id)
|
||||
.append("textPath").attr("xlink:href", "#textPath_"+id).attr("startOffset", "50%").attr("font-size", "100%")
|
||||
.append("tspan").attr("x", x).text(name);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ function editZones() {
|
|||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("zonesEditorRefresh").addEventListener("click", zonesEditorAddLines);
|
||||
document.getElementById("zonesEditStyle").addEventListener("click", () => editStyle("zones"));
|
||||
document.getElementById("zonesLegend").addEventListener("click", toggleLegend);
|
||||
document.getElementById("zonesPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("zonesManually").addEventListener("click", enterZonesManualAssignent);
|
||||
|
|
@ -25,6 +27,7 @@ function editZones() {
|
|||
|
||||
body.addEventListener("click", function(ev) {
|
||||
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-eye")) {toggleVisibility(el); 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 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 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 focused = defs.select("#fog #focus"+this.id).size();
|
||||
|
||||
|
|
@ -355,6 +358,65 @@ function editZones() {
|
|||
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) {
|
||||
zones.select("#"+zone).remove();
|
||||
unfocus(zone);
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue