mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-23 12:31:24 +01:00
Merge branch 'master' into labels
This commit is contained in:
commit
ec111fe139
28 changed files with 2931 additions and 1751 deletions
147
index.css
147
index.css
|
|
@ -36,6 +36,12 @@ textarea {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#map {
|
||||
background-color: #000000;
|
||||
mask-mode: alpha;
|
||||
|
|
@ -222,18 +228,12 @@ t,
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#options .pressed {
|
||||
background-color: #896c77 !important;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
i.icon-lock {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#routeEditor > *,
|
||||
#labelEditor div,
|
||||
#markerEditor div {
|
||||
#labelEditor div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
|
@ -323,10 +323,21 @@ text.drag {
|
|||
text-shadow: 0 0 1px red;
|
||||
}
|
||||
|
||||
#dialogs {
|
||||
background-color: var(--bg-dialogs);
|
||||
}
|
||||
|
||||
.draggable {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.ui-widget-header {
|
||||
border-bottom: 1px solid var(--dark-solid);
|
||||
background: var(--header);
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ui-dialog,
|
||||
#optionsContainer {
|
||||
user-select: none;
|
||||
|
|
@ -338,6 +349,7 @@ text.drag {
|
|||
border: solid 1px #5e4fa2;
|
||||
margin: 10px;
|
||||
padding-bottom: 0.3em;
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
#options input,
|
||||
|
|
@ -356,7 +368,7 @@ text.drag {
|
|||
}
|
||||
|
||||
.tab {
|
||||
border-bottom: 1px solid #5d4651;
|
||||
border-bottom: 1px solid var(--dark-solid);
|
||||
height: 2.2em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
@ -370,19 +382,19 @@ div.tab > button#optionsHide {
|
|||
|
||||
button.options {
|
||||
width: 100%;
|
||||
background-color: #997b89;
|
||||
background-color: var(--bg-main);
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
button.options:hover {
|
||||
background-color: #806070 !important;
|
||||
color: white !important;
|
||||
button.active {
|
||||
background-color: var(--header);
|
||||
color: white;
|
||||
}
|
||||
|
||||
button.active {
|
||||
background-color: #916e7f;
|
||||
button.options:hover {
|
||||
background-color: var(--header-active);
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
|
@ -412,10 +424,35 @@ button.active {
|
|||
|
||||
#options i {
|
||||
color: #31272c;
|
||||
font-size: 0.8em;
|
||||
font-size: 0.85em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#options button i.icon-cog {
|
||||
position: absolute;
|
||||
padding: 0.1em 0.3em;
|
||||
background-color: var(--bg-lighter);
|
||||
border-radius: 50%;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
#options button i.icon-cog:hover {
|
||||
color: #111;
|
||||
background-color: var(--bg-light);
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
|
||||
#options button i.icon-cog:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
#options button:hover i.icon-cog {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
input[type="color"] {
|
||||
-webkit-appearance: none;
|
||||
cursor: pointer;
|
||||
|
|
@ -461,11 +498,11 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
border-radius: 15%;
|
||||
width: 0.91em;
|
||||
height: 0.91em;
|
||||
background: #a58394;
|
||||
border: 1px solid #5d4651;
|
||||
background: var(--light-solid);
|
||||
border: 1px solid var(--dark-solid);
|
||||
cursor: pointer;
|
||||
margin-top: -0.4em;
|
||||
box-shadow: 0.5px 0.5px 0px #5d4651;
|
||||
box-shadow: 0.5px 0.5px 0px var(--dark-solid);
|
||||
}
|
||||
|
||||
#options input[type="range"]::-moz-range-thumb {
|
||||
|
|
@ -473,10 +510,10 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
border-radius: 15%;
|
||||
width: 0.73em;
|
||||
height: 0.73em;
|
||||
background: #a58394;
|
||||
border: 1px solid #5d4651;
|
||||
background: var(--light-solid);
|
||||
border: 1px solid var(--dark-solid);
|
||||
cursor: pointer;
|
||||
box-shadow: 0.5px 0.5px 0px #5d4651;
|
||||
box-shadow: 0.5px 0.5px 0px var(--dark-solid);
|
||||
}
|
||||
|
||||
#options input[type="range"]::-webkit-slider-runnable-track {
|
||||
|
|
@ -520,7 +557,7 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
}
|
||||
|
||||
#optionsContent input[type="number"]:hover {
|
||||
outline: 1px solid #5d4651;
|
||||
outline: 1px solid var(--dark-solid);
|
||||
}
|
||||
|
||||
#optionsContent input.paired {
|
||||
|
|
@ -542,10 +579,9 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
#optionsSeedGenerate:before {
|
||||
content: "✓";
|
||||
margin-left: -2px;
|
||||
font-weight: bold;
|
||||
#options input[type="color"] {
|
||||
width: 2em;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.tabcontent button.sideButton {
|
||||
|
|
@ -601,7 +637,7 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
|
||||
#exitCustomization > div {
|
||||
width: 12em;
|
||||
background: #5d4651;
|
||||
background: var(--dark-solid);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
|
|
@ -637,7 +673,7 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
}
|
||||
|
||||
.tabcontent button {
|
||||
background-color: #916e7f;
|
||||
background-color: var(--bg-lighter);
|
||||
border: none;
|
||||
padding: 0.45em 0.75em;
|
||||
margin: 0.35em 0;
|
||||
|
|
@ -645,8 +681,13 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
font-size: 1em;
|
||||
}
|
||||
|
||||
.tabcontent button.pressed {
|
||||
background-color: var(--header);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tabcontent button:hover {
|
||||
background-color: #a8879d !important;
|
||||
background-color: var(--header-active);
|
||||
}
|
||||
|
||||
#toolsContent div {
|
||||
|
|
@ -679,12 +720,12 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #5d4651;
|
||||
border: 1px solid var(--dark-solid);
|
||||
}
|
||||
|
||||
.tabcontent li {
|
||||
list-style-type: none;
|
||||
background-color: #997b89;
|
||||
background-color: var(--bg-main);
|
||||
cursor: pointer;
|
||||
padding: 0.35em;
|
||||
margin: 0.2em 0.3em;
|
||||
|
|
@ -693,14 +734,17 @@ fieldset {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
#options .buttonoff {
|
||||
background-color: #b6b4b440 !important;
|
||||
color: #666;
|
||||
.tabcontent .buttonoff {
|
||||
background-color: var(--bg-disabled);
|
||||
color: #444444aa;
|
||||
}
|
||||
|
||||
.tabcontent li:hover {
|
||||
box-shadow: 0 0 2px 2px var(--dark-solid) 17;
|
||||
}
|
||||
|
||||
.tabcontent li:hover,
|
||||
.tabcontent button:hover {
|
||||
box-shadow: 0 0 2px 2px #5d465117;
|
||||
background-color: var(--header);
|
||||
}
|
||||
|
||||
#optionsContainer span {
|
||||
|
|
@ -799,7 +843,7 @@ fieldset {
|
|||
|
||||
table.matrix-table th,
|
||||
table.matrix-table td {
|
||||
border: 1px solid #5d4651;
|
||||
border: 1px solid var(--dark-solid);
|
||||
height: 2em;
|
||||
padding: 0.2em;
|
||||
position: relative;
|
||||
|
|
@ -815,7 +859,7 @@ table.matrix-table tr:hover th {
|
|||
}
|
||||
|
||||
table.matrix-table td:hover {
|
||||
outline: 2px solid #5d4651;
|
||||
outline: 2px solid var(--dark-solid);
|
||||
outline-offset: -1px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
@ -1142,7 +1186,7 @@ i.resetButton {
|
|||
}
|
||||
|
||||
i.resetButton:active {
|
||||
color: #5d4651;
|
||||
color: var(--dark-solid);
|
||||
}
|
||||
|
||||
.ui-dialog button.pressed {
|
||||
|
|
@ -1183,7 +1227,7 @@ i.resetButton:active {
|
|||
}
|
||||
|
||||
.ui-dialog input[type="number"] {
|
||||
width: 3.5em;
|
||||
width: 4.5em;
|
||||
}
|
||||
|
||||
.ui-dialog .disabled {
|
||||
|
|
@ -1519,9 +1563,11 @@ div.states > .coaIcon > use {
|
|||
|
||||
#stateNameEditor div.label,
|
||||
#provinceNameEditor div.label,
|
||||
#regimentBody div.label {
|
||||
#regimentBody div.label,
|
||||
#markerEditor div.label {
|
||||
display: inline-block;
|
||||
width: 5.5em;
|
||||
padding: 0.3em 0;
|
||||
}
|
||||
|
||||
#saveTilesScreen div.label {
|
||||
|
|
@ -1529,10 +1575,6 @@ div.states > .coaIcon > use {
|
|||
width: 5em;
|
||||
}
|
||||
|
||||
#regimentBody div {
|
||||
margin: 0.1em 0;
|
||||
}
|
||||
|
||||
#regimentBody input[type="number"] {
|
||||
width: 5em;
|
||||
}
|
||||
|
|
@ -1677,8 +1719,8 @@ div.editorLine {
|
|||
}
|
||||
|
||||
#pickerHeader {
|
||||
fill: #916e7f;
|
||||
stroke: #5d4651;
|
||||
fill: var(--header);
|
||||
stroke: var(--dark-solid);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
|
|
@ -1692,7 +1734,7 @@ div.editorLine {
|
|||
|
||||
#pickerCloseRect {
|
||||
cursor: pointer;
|
||||
fill: #916e7f;
|
||||
fill: var(--header);
|
||||
stroke: #f8ffff;
|
||||
}
|
||||
|
||||
|
|
@ -1965,10 +2007,7 @@ div.textual span,
|
|||
font-family: monospace;
|
||||
user-select: none;
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
#markerEditor > button {
|
||||
vertical-align: top;
|
||||
dominant-baseline: central;
|
||||
}
|
||||
|
||||
.highlighted {
|
||||
|
|
@ -2198,14 +2237,14 @@ svg.button {
|
|||
color: #920303;
|
||||
background-color: #dabdbd91;
|
||||
padding: 2px;
|
||||
border: 1px solid #916e7f;
|
||||
border: 1px solid var(--header);
|
||||
}
|
||||
|
||||
.announcement {
|
||||
background-color: #a18888;
|
||||
color: white;
|
||||
padding: 0.4em 0.5em;
|
||||
border: dashed 1px #5d4651;
|
||||
border: dashed 1px var(--dark-solid);
|
||||
}
|
||||
|
||||
.speaker {
|
||||
|
|
|
|||
290
index.html
290
index.html
|
|
@ -16,7 +16,7 @@
|
|||
<link rel="canonical" href="https://azgaar.github.io/Fantasy-Map-Generator/">
|
||||
|
||||
<style type="text/css">
|
||||
body {margin: 0; font-size: 11px; overflow: hidden;}
|
||||
body {margin: 0; font-size: 10px; overflow: hidden;}
|
||||
#map {position: absolute;}
|
||||
#initial {fill: none; stroke: black; pointer-events: none;}
|
||||
#init-rose {animation: 20s infinite spin; opacity: .7; transform-origin: center;}
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
#loading-text span:nth-child(2), #mapOverlay > span:nth-child(2) {animation-delay: 1s;}
|
||||
#loading-text span:nth-child(3), #mapOverlay > span:nth-child(3) {animation-delay: 2s;}
|
||||
@keyframes blink {0% {opacity: 0;} 20% {opacity: 1;} 100% {opacity: .1;}}
|
||||
|
||||
</style>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<link rel="stylesheet" href="icons.css">
|
||||
|
|
@ -210,14 +211,6 @@
|
|||
<g id="defs-emblems"></g>
|
||||
</g>
|
||||
|
||||
<g id="defs-markers">
|
||||
<symbol id="marker0" viewBox="0 0 30 30">
|
||||
<path d="M6,19 l9,10 L24,19" fill="#000000" stroke="none" />
|
||||
<circle cx="15" cy="15" r="10" stroke-width="1" stroke="#000000" fill="#ffffff" />
|
||||
<text x="50%" y="50%" fill="#000000" stroke-width="0" stroke="#3200ff" font-size="22px" dominant-baseline="central">?</text>
|
||||
</symbol>
|
||||
</g>
|
||||
|
||||
<pattern id="oceanic" width="100" height="100" patternUnits="userSpaceOnUse">
|
||||
<image id="oceanicPattern" href="./images/pattern1.png"></image>
|
||||
</pattern>
|
||||
|
|
@ -234,7 +227,7 @@
|
|||
<div id="loading">
|
||||
<div id="titleName"><t data-t="titleName">Azgaar's</t></div>
|
||||
<div id="title"><t data-t="title">Fantasy Map Generator</t></div>
|
||||
<div id="version"><t data-t="version">v. </t>1.66</div>
|
||||
<div id="version"><t data-t="version">v. </t>1.7</div>
|
||||
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
|
||||
</div>
|
||||
|
||||
|
|
@ -242,7 +235,7 @@
|
|||
|
||||
<div id="collapsible">
|
||||
<button id="optionsTrigger" data-t="tipOptionsTrigger" data-tip="Click to show the Menu. Shortcut: Tab" class="options glow" onclick="showOptions(event)" style="padding:.6em .45em">►</button>
|
||||
<button id="regenerate" data-t="tipRegenerate" data-tip="Click to generate a new map. Shortcut: F2" onclick="regeneratePrompt()" class="options" style="display: none"><t data-t="newMap">New Map!</t></button>
|
||||
<button id="regenerate" data-t="tipRegenerate" data-tip="Click to generate a new map. Shortcut: F2" onclick="regeneratePrompt('drawer')" class="options" style="display: none"><t data-t="newMap">New Map!</t></button>
|
||||
</div>
|
||||
|
||||
<div id="options" style="display:none">
|
||||
|
|
@ -949,14 +942,13 @@
|
|||
|
||||
<tr data-tip="Map seed number. Seed produces the same map only if canvas size and options are the same">
|
||||
<td>
|
||||
<i data-tip="Click to generate a map for this seed" id="optionsSeedGenerate"></i>
|
||||
<i data-tip="Show seed history to apply a previous seed" id="optionsMapHistory" class="icon-history"></i>
|
||||
</td>
|
||||
<td>Map seed</td>
|
||||
<td>
|
||||
<input id="optionsSeed" class="long" type="number" min=1 max=999999999 step=1>
|
||||
</td>
|
||||
<td>
|
||||
<i data-tip="Show seed history to apply a previous seed" id="optionsMapHistory" class="icon-history"></i>
|
||||
<i data-tip="Copy map seed as URL. It will produce the same map only if options are default or the same" id="optionsCopySeed" class="icon-docs"></i>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -1153,10 +1145,10 @@
|
|||
<td></td>
|
||||
<td>Interface size</td>
|
||||
<td>
|
||||
<input id="uiSizeInput" data-stored="uiSize" type="range" min=.6 max=3 step=.1 value=1>
|
||||
<input id="uiSizeInput" data-stored="uiSize" type="range" min=.6 max=3 step=.1>
|
||||
</td>
|
||||
<td>
|
||||
<input id="uiSizeOutput" data-stored="uiSize" type="number" min=.6 max=3 step=.1 value=1>
|
||||
<input id="uiSizeOutput" data-stored="uiSize" type="number" min=.6 max=3 step=.1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
|
@ -1171,14 +1163,27 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Set theme hue for dialogs and tool windows">
|
||||
<td>
|
||||
<i data-tip="Restore default theme color: pale magenta" id="themeColorRestore" class="icon-ccw"></i>
|
||||
</td>
|
||||
<td>Theme color</td>
|
||||
<td>
|
||||
<input id="themeHueInput" type="range" min=0 max=359 >
|
||||
</td>
|
||||
<td>
|
||||
<input id="themeColorInput" data-stored="themeColor" type="color" >
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Set dialog and tool windows transparency">
|
||||
<td></td>
|
||||
<td>Transparency</td>
|
||||
<td>
|
||||
<input id="transparencyInput" data-stored="transparency" type="range" min=0 max=100 value=15>
|
||||
<input id="transparencyInput" data-stored="transparency" type="range" min=0 max=100 >
|
||||
</td>
|
||||
<td>
|
||||
<input id="transparencyOutput" data-stored="transparency" type="number" min=0 max=100 value=15>
|
||||
<input id="transparencyOutput" data-stored="transparency" type="number" min=0 max=100 >
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
|
@ -1271,7 +1276,9 @@
|
|||
</tr>
|
||||
|
||||
<tr data-tip="Set minimum and maximum possible zoom level">
|
||||
<td></td>
|
||||
<td>
|
||||
<i data-tip="Restore default zoom extent: [1, 20]" id="zoomExtentDefault" class="icon-ccw"></i>
|
||||
</td>
|
||||
<td>Zoom extent</td>
|
||||
<td>
|
||||
<span data-tip="Mimimal possible zoom level (should be > 0)">min</span>
|
||||
|
|
@ -1280,7 +1287,6 @@
|
|||
<input data-tip="Maximal possible zoom level (should be > 1)" id="zoomExtentMax" class="paired" type="number" min=1 max=50 value=20>
|
||||
</td>
|
||||
<td>
|
||||
<i data-tip="Restore default zoom extent (1, 20)" id="zoomExtentDefault" class="icon-ccw"></i>
|
||||
<i data-tip="Allow to drag map beyond canvas borders" id="translateExtent" data-on=0 class="icon-hand-paper-o"></i>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -1338,6 +1344,7 @@
|
|||
<button id="overviewBurgsButton" data-tip="Click to open Burgs Overview. Shortcut: Shift + T">Burgs</button>
|
||||
<button id="overviewRiversButton" data-tip="Click to open Rivers Overview. Shortcut: Shift + V">Rivers</button>
|
||||
<button id="overviewMilitaryButton" data-tip="Click to open Military Forces Overview. Shortcut: Shift + M">Military</button>
|
||||
<button id="overviewMarkersButton" data-tip="Click to open Markers Overview. Shortcut: Shift + K">Markers</button>
|
||||
<button id="overviewCellsButton" data-tip="Click to open Cell details view. Shortcut: Shift + E">Cells</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -1356,7 +1363,7 @@
|
|||
<button id="regenerateCultures" data-tip="Click to regenerate cultures">Cultures</button>
|
||||
<button id="regenerateMilitary" data-tip="Click to recalculate military forces based on current military options">Military</button>
|
||||
<button id="regenerateIce" data-tip="Click to icebergs and glaciers">Ice</button>
|
||||
<button id="regenerateMarkers" data-tip="Click to regenerate markers. Hold Ctrl and click to set markers number multiplier">Markers</button>
|
||||
<button id="regenerateMarkers" data-tip="Click to regenerate markers">Markers <i id="configRegenerateMarkers" class="icon-cog" data-tip="Click to set number multiplier"></i></button>
|
||||
<button id="regenerateZones" data-tip="Click to regenerate zones. Hold Ctrl and click to set zones number multiplier">Zones</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -1462,8 +1469,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="dialogs" style="background-color: #ffffff">
|
||||
|
||||
<div id="dialogs">
|
||||
<div id="worldConfigurator" class="dialog stable" style="display: none">
|
||||
|
||||
<div id="worldControls">
|
||||
|
|
@ -1947,8 +1953,9 @@
|
|||
<div id="burgEditor" class="dialog" style="display: none">
|
||||
|
||||
<div id="burgBody" style="padding-bottom: .3em">
|
||||
<svg viewBox="0 0 200 200" width="14em" height="14em"><use id="burgEmblem"></use></svg>
|
||||
<div style="float: right">
|
||||
<div style="display: flex; align-items: center">
|
||||
<svg data-tip="Burg emblem. Click to edit" class="pointer" viewBox="0 0 200 200" width="13em" height="13em"><use id="burgEmblem"></use></svg>
|
||||
<div>
|
||||
<div id="burgProvinceAndState" style="font-style: italic; max-width: 16em"></div>
|
||||
|
||||
<div>
|
||||
|
|
@ -2003,6 +2010,16 @@
|
|||
<div class="label">Elevation:</div>
|
||||
<span id="burgElevation"></span> above sea level
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-tip="Burg preview in the Medieval Fantasy City Generator. Default seed is a conbimation of map seed and burg id" style="display: flex; flex-direction: column">
|
||||
<div>
|
||||
See in <a id="mfcgLink" target="_blank">City Generator by Watabou</a>.
|
||||
Seed: <input id="mfcgBurgSeed" style="width: 10em" type="number" min=1 max="1e13" step="1" />
|
||||
<i id="regenerateMFCGBurgSeed" data-tip="Randomize Medieval Fantasy City Generator burg seed" class="icon-arrows-cw pointer" style="margin-left: .1em"></i>
|
||||
</div>
|
||||
<iframe id="mfcgPreview" sandbox="allow-scripts allow-same-origin"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -2024,59 +2041,77 @@
|
|||
<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 burg in the Medieval Fantasy City Generator by Watabou. Ctrl + click to change the seed" class="icon-map-o"></button>
|
||||
<button id="burgEditEmblem" data-tip="Edit emblem" class="icon-shield-alt"></button>
|
||||
<button id="burgRelocate" data-tip="Relocate burg" class="icon-target"></button>
|
||||
<button id="burglLegend" data-tip="Edit free text notes (legend) for this burg" class="icon-edit"></button>
|
||||
<button id="burgLock" class="icon-lock-open"></button>
|
||||
<button id="burgLock" class="icon-lock-open" onmouseover="showElementLockTip(event)"></button>
|
||||
<button id="burgRemove" data-tip="Remove non-capital burg. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="markerEditor" class="dialog" style="display: none">
|
||||
<div id="markerBody" style="padding-bottom: .3em">
|
||||
<div data-tip="Marker type. Style changes will apply to all markers of the same type. Leave blank if the marker is unique">
|
||||
<div class="label">Type:</div>
|
||||
<input id="markerType" style="width: 10.3em" />
|
||||
</div>
|
||||
|
||||
<button id="markerGroup" data-tip="Change marker type" class="icon-tags"></button>
|
||||
<div id="markerGroupSection" style="display: none">
|
||||
<select id="markerSelectGroup" data-tip="Select type for the selected marker" style="width: 10em"></select>
|
||||
<input id="markerInputGroup" data-tip="Create new type for selected marker" placeholder="type new name" style="display: none; width: 10em"/>
|
||||
<i id="markerAddGroup" data-tip="Create new markers type" class="icon-plus pointer"></i>
|
||||
<i id="markerRemoveGroup" data-tip="Remove all markers of that type" class="icon-trash pointer"></i>
|
||||
<div data-tip="Marker icon. Paste any Unicode symbol or select from the predefined list">
|
||||
<div class="label">Icon:</div>
|
||||
<input id="markerIcon" style="width:5em" />
|
||||
<button id="markerIconSelect" style="width: 5em">select</button>
|
||||
</div>
|
||||
|
||||
<div data-tip="Marker marker element and icon sizes in pixels">
|
||||
<div class="label">Size:</div>
|
||||
<input data-tip="Marker element size in pixels" id="markerSize" type="number" min="2" max="500" style="width: 5em" />
|
||||
<input data-tip="Marker icon sizes in pixels" id="markerIconSize" type="number" min="2" max="20" step="0.5" style="width: 5em" />
|
||||
</div>
|
||||
|
||||
<div data-tip="Marker icon shift (by X and by Y axis), percent. Set to 50 to position icon in center">
|
||||
<div class="label">Icon shift:</div>
|
||||
<input id="markerIconShiftX" type="number" min="0" max="100" step="1" style="width:5em" />
|
||||
<input id="markerIconShiftY" type="number" min="0" max="100" step="1" style="width:5em" />
|
||||
</div>
|
||||
|
||||
<div data-tip="Marker pin shape">
|
||||
<div class="label">Pin shape:</div>
|
||||
<select id="markerPin" style="width: 10.3em">
|
||||
<option value="bubble">Bubble</option>
|
||||
<option value="pin">Pin</option>
|
||||
<option value="square">Square</option>
|
||||
<option value="squarish">Squarish</option>
|
||||
<option value="diamond">Diamond</option>
|
||||
<option value="hex">Hex</option>
|
||||
<option value="hexy">Hexy</option>
|
||||
<option value="shieldy">Shieldy</option>
|
||||
<option value="shield">Shield</option>
|
||||
<option value="pentagon">Pentagon</option>
|
||||
<option value="heptagon">Heptagon</option>
|
||||
<option value="circle">Circle</option>
|
||||
<option value="no">No</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-tip="Pin fill and stroke colors">
|
||||
<div class="label">Pin colors:</div>
|
||||
<input id="markerFill" type="color" style="width:5em; height:1.6em" />
|
||||
<input id="markerStroke" type="color" style="width:5em; height:1.6em" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="markerIcon" data-tip="Change marker icon and edit positioning" class="icon-star"></button>
|
||||
<div id="markerIconSection" style="display: none">
|
||||
<i data-tip="Change marker icon size" class="icon-resize-full"></i>
|
||||
<input id="markerIconSize" data-tip="Change marker icon size" type="range" min=5 max=30 step=.5 value=22 style="width:12em"><br>
|
||||
<i data-tip="Marker Icon" class="icon-info"></i>
|
||||
<button id="markerIconSelect" data-tip="Click to select icon"></button>
|
||||
<i data-tip="Change marker horizontal shift" class="icon-resize-horizontal"></i>
|
||||
<input id="markerIconShiftX" data-tip="Change icon horizontal shift" type="number" value=50>
|
||||
<i data-tip="Change marker vertical shift" class="icon-resize-vertical"></i>
|
||||
<input id="markerIconShiftY" data-tip="Change vertical shift" type="number" min=0 max=100 value=50>
|
||||
<div id="markerBottom">
|
||||
<button id="markerNotes" data-tip="Edit place legend (notes)" class="icon-edit"></button>
|
||||
<button id="markerLock" class="icon-lock-open" onmouseover="showElementLockTip(event)"></button>
|
||||
<button id="markerAdd" data-tip="Add additional marker of that type" class="icon-plus"></button>
|
||||
<button id="markerRemove" data-tip="Remove the marker. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
|
||||
<button id="markerStyle" data-tip="Change marker size and colors" class="icon-brush"></button>
|
||||
<div id="markerStyleSection" style="display: none">
|
||||
<i data-tip="Change marker base (pin) style" class="icon-map-pin"></i>
|
||||
<input id="markerSize" data-tip="Change marker size" type="range" min=.01 max=10 step=.1 value=1 style="width:12em">
|
||||
<input id="markerBaseStroke" data-tip="Change pin stroke color" type="color" value="#ffffff">
|
||||
<input id="markerBaseFill" data-tip="Change pin fill color" type="color" alue="#000000">
|
||||
<br><i data-tip="Change marker icon style" class="icon-star"></i>
|
||||
<input id="markerIconStrokeWidth" data-tip="Change icon stroke width" type="range" min=0 max=1 step=.02 value=0 style="width:12em">
|
||||
<input id="markerIconStroke" data-tip="Change icon stroke color. Ensure icon stroke width is non-zero" type="color" value="#3200ff">
|
||||
<input id="markerIconFill" data-tip="Change icon fill color" type="color" value="#000000">
|
||||
</div>
|
||||
|
||||
<button id="markerToggleBubble" data-tip="Toggle pin (bubble) display" class="icon-info-circled"></button>
|
||||
<button id="markerLegendButton" data-tip="Edit place legend (free text notes)" class="icon-edit"></button>
|
||||
<button id="markerAdd" data-tip="Add additional marker of that type" class="icon-plus"></button>
|
||||
<button id="markerRemove" data-tip="Remove the marker. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
|
||||
<div id="regimentEditor" class="dialog" style="display: none">
|
||||
|
||||
<div id="regimentBody">
|
||||
<div>
|
||||
<div id="regimentBody" style="padding-bottom: 0.3em">
|
||||
<div style="padding-bottom: 0.2em">
|
||||
<button id="regimentType" data-tip="Regiment type (land or naval). Click to change"></button>
|
||||
<input id="regimentName" data-tip="Type to rename the regiment" autocorrect="off" spellcheck="false" style="width: 13em">
|
||||
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
|
||||
|
|
@ -2530,7 +2565,7 @@
|
|||
</div>
|
||||
|
||||
<div id="stateNameEditor" class="dialog" data-state="0" style="display: none">
|
||||
<div style="padding: .1em">
|
||||
<div>
|
||||
<div data-tip="State short name" class="label">Short name:</div>
|
||||
<input id="stateNameEditorShort" data-tip="Type to change the short name" autocorrect="off" spellcheck="false" style="width: 11em">
|
||||
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
|
||||
|
|
@ -2538,7 +2573,7 @@
|
|||
<span id="stateNameEditorShortRandom" data-tip="Generate random name" class="icon-globe pointer"></span>
|
||||
</div>
|
||||
|
||||
<div style="padding: .1em" data-tip="Select form name">
|
||||
<div data-tip="Select form name">
|
||||
<div data-tip="State form name" class="label">Form name:</div>
|
||||
<select id="stateNameEditorSelectForm" style="width: 11em">
|
||||
<option value="">blank</option>
|
||||
|
|
@ -2617,7 +2652,7 @@
|
|||
<span id="stateNameEditorAddForm" data-tip="Click to add custom state form name to the list" class="icon-plus pointer"></span>
|
||||
</div>
|
||||
|
||||
<div style="padding: .1em">
|
||||
<div>
|
||||
<div data-tip="State full name" class="label">Full name:</div>
|
||||
<input id="stateNameEditorFull" data-tip="Type to change the full name" autocorrect="off" spellcheck="false" style="width: 11em">
|
||||
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
|
||||
|
|
@ -2706,7 +2741,7 @@
|
|||
</div>
|
||||
|
||||
<div id="provinceNameEditor" class="dialog" data-province="0" style="display: none">
|
||||
<div style="padding: .1em">
|
||||
<div>
|
||||
<div data-tip="Province short name" class="label">Short name:</div>
|
||||
<input id="provinceNameEditorShort" data-tip="Type to change the short name" autocorrect="off" spellcheck="false" style="width: 11em">
|
||||
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
|
||||
|
|
@ -2714,7 +2749,7 @@
|
|||
<span id="provinceNameEditorShortRandom" data-tip="Generate random name" class="icon-globe pointer"></span>
|
||||
</div>
|
||||
|
||||
<div style="padding: .1em" data-tip="Select form name">
|
||||
<div data-tip="Select form name">
|
||||
<div data-tip="Province form name" class="label">Form name:</div>
|
||||
<select id="provinceNameEditorSelectForm" style="display: inline-block; width: 11em; height: 1.645em">
|
||||
<option value="">blank</option>
|
||||
|
|
@ -2754,7 +2789,7 @@
|
|||
<span id="provinceNameEditorAddForm" data-tip="Click to add custom province form name to the list" class="icon-plus pointer"></span>
|
||||
</div>
|
||||
|
||||
<div style="padding: .1em">
|
||||
<div>
|
||||
<div data-tip="Province full name" class="label">Full name:</div>
|
||||
<input id="provinceNameEditorFull" data-tip="Type to change the full name" autocorrect="off" spellcheck="false" style="width: 11em">
|
||||
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
|
||||
|
|
@ -3326,6 +3361,26 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="markersOverview" class="dialog stable" style="display: none">
|
||||
<div id="markersHeader" class="header">
|
||||
<div style="left:1em" data-tip="Click to sort by marker type" class="sortable alphabetically" data-sortby="type">Type </div>
|
||||
</div>
|
||||
|
||||
<div id="markersBody" class="table"></div>
|
||||
|
||||
<div id="markersFooter" class="totalLine">
|
||||
<div data-tip="Markers number" style="margin-left: 4px">Total: <span id="markersFooterNumber">0</span> markers</div>
|
||||
</div>
|
||||
|
||||
<div id="markersBottom">
|
||||
<button id="markersOverviewRefresh" data-tip="Refresh the Overview screen" class="icon-cw"></button>
|
||||
<button id="markersAddFromOverview" data-tip="Add a new marker. Hold Shift to add multiple" class="icon-plus"></button>
|
||||
<button id="markersGenerationConfig" data-tip="Config markers generation options" class="icon-cog"></button>
|
||||
<button id="markersRemoveAll" data-tip="Remove all unlocked markers" class="icon-trash"></button>
|
||||
<button id="markersExport" data-tip="Save markers data as a text file (.csv)" class="icon-download"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="styleSaver" class="dialog stable textual" style="display: none">
|
||||
<div id="styleSaverHeader" style="padding:2px 0">
|
||||
<span>Preset name:</span>
|
||||
|
|
@ -3509,7 +3564,7 @@
|
|||
<div id="loadMapData" style="display: none" class="dialog">
|
||||
<div>
|
||||
<strong>Load map from</strong>
|
||||
<button onclick="mapToLoad.click()" data-tip="Load .map file from local disk">local disk</button>
|
||||
<button onclick="mapToLoad.click()" data-tip="Load .map file from your local disk">machine</button>
|
||||
<button onclick="loadURL()" data-tip="Load .map file from URL (server should allow CORS)">URL</button>
|
||||
<button onclick="quickLoad()" data-tip="Load map from browser storage (if saved before)">storage</button>
|
||||
</div>
|
||||
|
|
@ -4269,54 +4324,57 @@
|
|||
<script src="modules/fonts.js"></script>
|
||||
<script src="modules/ui/layers.js"></script>
|
||||
<script src="modules/ui/measurers.js"></script>
|
||||
<script src="libs/umami.js"></script>
|
||||
|
||||
<script defer src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
|
||||
<script defer src="modules/ui/general.js"></script>
|
||||
<script defer src="modules/ui/options.js"></script>
|
||||
<script defer src="modules/ui/style.js"></script>
|
||||
<script defer src="modules/save.js"></script>
|
||||
<script defer src="modules/load.js"></script>
|
||||
<script defer src="modules/cloud.js"></script>
|
||||
<script defer src="main.js"></script>
|
||||
<script defer src="modules/relief-icons.js"></script>
|
||||
<script defer src="modules/ui/tools.js"></script>
|
||||
<script defer src="modules/ui/world-configurator.js"></script>
|
||||
<script defer src="modules/ui/heightmap-editor.js"></script>
|
||||
<script defer src="modules/ui/states-editor.js"></script>
|
||||
<script defer src="modules/ui/provinces-editor.js"></script>
|
||||
<script defer src="modules/ui/biomes-editor.js"></script>
|
||||
<script defer src="modules/ui/cultures-editor.js"></script>
|
||||
<script defer src="modules/ui/namesbase-editor.js"></script>
|
||||
<script defer src="modules/ui/elevation-profile.js"></script>
|
||||
<script defer src="modules/ui/routes-editor.js"></script>
|
||||
<script defer src="modules/ui/ice-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/rivers-creator.js"></script>
|
||||
<script defer src="modules/ui/relief-editor.js"></script>
|
||||
<script defer src="modules/ui/religions-editor.js"></script>
|
||||
<script defer src="modules/ui/markers-editor.js"></script>
|
||||
<script defer src="modules/ui/burg-editor.js"></script>
|
||||
<script defer src="modules/ui/units-editor.js"></script>
|
||||
<script defer src="modules/ui/notes-editor.js"></script>
|
||||
<script defer src="modules/ui/diplomacy-editor.js"></script>
|
||||
<script defer src="modules/ui/zones-editor.js"></script>
|
||||
<script defer src="modules/ui/burgs-overview.js"></script>
|
||||
<script defer src="modules/ui/rivers-overview.js"></script>
|
||||
<script defer src="modules/ui/military-overview.js"></script>
|
||||
<script defer src="modules/ui/regiments-overview.js"></script>
|
||||
<script defer src="modules/ui/regiment-editor.js"></script>
|
||||
<script defer src="modules/ui/battle-screen.js"></script>
|
||||
<script defer src="modules/coa-renderer.js"></script>
|
||||
<script defer src="modules/ui/emblems-editor.js"></script>
|
||||
<script defer src="modules/ui/editors.js"></script>
|
||||
<script defer src="modules/ui/3d.js"></script>
|
||||
<script defer src="modules/ui/hotkeys.js"></script>
|
||||
<script defer src="libs/rgbquant.min.js"></script>
|
||||
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
||||
<script defer src="libs/pell.min.js"></script>
|
||||
<script defer src="libs/jszip.min.js"></script>
|
||||
<script async defer src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
|
||||
<script async defer src="modules/ui/general.js"></script>
|
||||
<script async defer src="modules/ui/options.js"></script>
|
||||
<script async defer src="modules/ui/style.js"></script>
|
||||
<script async defer src="modules/load.js"></script>
|
||||
<script async defer src="modules/cloud.js"></script>
|
||||
<script async defer src="main.js"></script>
|
||||
<script async defer src="modules/save.js"></script>
|
||||
<script async defer src="modules/export.js"></script>
|
||||
<script async defer src="modules/relief-icons.js"></script>
|
||||
<script async defer src="modules/ui/tools.js"></script>
|
||||
<script async defer src="modules/ui/world-configurator.js"></script>
|
||||
<script async defer src="modules/ui/heightmap-editor.js"></script>
|
||||
<script async defer src="modules/ui/states-editor.js"></script>
|
||||
<script async defer src="modules/ui/provinces-editor.js"></script>
|
||||
<script async defer src="modules/ui/biomes-editor.js"></script>
|
||||
<script async defer src="modules/ui/cultures-editor.js"></script>
|
||||
<script async defer src="modules/ui/namesbase-editor.js"></script>
|
||||
<script async defer src="modules/ui/elevation-profile.js"></script>
|
||||
<script async defer src="modules/ui/routes-editor.js"></script>
|
||||
<script async defer src="modules/ui/ice-editor.js"></script>
|
||||
<script async defer src="modules/ui/lakes-editor.js"></script>
|
||||
<script async defer src="modules/ui/coastline-editor.js"></script>
|
||||
<script async defer src="modules/ui/labels-editor.js"></script>
|
||||
<script async defer src="modules/ui/rivers-editor.js"></script>
|
||||
<script async defer src="modules/ui/rivers-creator.js"></script>
|
||||
<script async defer src="modules/ui/relief-editor.js"></script>
|
||||
<script async defer src="modules/ui/religions-editor.js"></script>
|
||||
<script async defer src="modules/ui/markers-editor.js"></script>
|
||||
<script async defer src="modules/ui/burg-editor.js"></script>
|
||||
<script async defer src="modules/ui/units-editor.js"></script>
|
||||
<script async defer src="modules/ui/notes-editor.js"></script>
|
||||
<script async defer src="modules/ui/diplomacy-editor.js"></script>
|
||||
<script async defer src="modules/ui/zones-editor.js"></script>
|
||||
<script async defer src="modules/ui/burgs-overview.js"></script>
|
||||
<script async defer src="modules/ui/rivers-overview.js"></script>
|
||||
<script async defer src="modules/ui/military-overview.js"></script>
|
||||
<script async defer src="modules/ui/regiments-overview.js"></script>
|
||||
<script async defer src="modules/ui/markers-overview.js"></script>
|
||||
<script async defer src="modules/ui/regiment-editor.js"></script>
|
||||
<script async defer src="modules/ui/battle-screen.js"></script>
|
||||
<script async defer src="modules/coa-renderer.js"></script>
|
||||
<script async defer src="modules/ui/emblems-editor.js"></script>
|
||||
<script async defer src="modules/ui/editors.js"></script>
|
||||
<script async defer src="modules/ui/3d.js"></script>
|
||||
<script async defer src="modules/ui/hotkeys.js"></script>
|
||||
<script async defer src="libs/rgbquant.min.js"></script>
|
||||
<script async defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
||||
<script async defer src="libs/pell.min.js"></script>
|
||||
<script async defer src="libs/jszip.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
2
libs/dropins.min.js
vendored
2
libs/dropins.min.js
vendored
File diff suppressed because one or more lines are too long
530
libs/jquery-ui.css
vendored
530
libs/jquery-ui.css
vendored
|
|
@ -3,81 +3,79 @@
|
|||
* Copyright jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
.ui-draggable-handle {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.ui-helper-hidden {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
.ui-helper-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
.ui-helper-reset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
font-size: 100%;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
font-size: 100%;
|
||||
list-style: none;
|
||||
}
|
||||
.ui-helper-clearfix:before,
|
||||
.ui-helper-clearfix:after {
|
||||
content: "";
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
content: "";
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.ui-helper-clearfix:after {
|
||||
clear: both;
|
||||
clear: both;
|
||||
}
|
||||
.ui-helper-zfix {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
filter:Alpha(Opacity=0); /* support: IE8 */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
filter: Alpha(Opacity=0); /* support: IE8 */
|
||||
}
|
||||
|
||||
.ui-front {
|
||||
z-index: 100;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-disabled {
|
||||
cursor: default !important;
|
||||
pointer-events: none;
|
||||
cursor: default !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
.ui-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: -.25em;
|
||||
position: relative;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: -0.25em;
|
||||
position: relative;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ui-widget-icon-block {
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
display: block;
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Misc visuals
|
||||
|
|
@ -85,102 +83,102 @@
|
|||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable {
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
.ui-resizable-handle {
|
||||
position: absolute;
|
||||
font-size: 0.1px;
|
||||
display: block;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
position: absolute;
|
||||
font-size: 0.1px;
|
||||
display: block;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.ui-resizable-disabled .ui-resizable-handle,
|
||||
.ui-resizable-autohide .ui-resizable-handle {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
.ui-resizable-n {
|
||||
cursor: n-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
cursor: n-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-resizable-s {
|
||||
cursor: s-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
cursor: s-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-resizable-e {
|
||||
cursor: e-resize;
|
||||
width: 7px;
|
||||
right: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
cursor: e-resize;
|
||||
width: 7px;
|
||||
right: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable-w {
|
||||
cursor: w-resize;
|
||||
width: 7px;
|
||||
left: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
cursor: w-resize;
|
||||
width: 7px;
|
||||
left: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable-se {
|
||||
cursor: se-resize;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
right: 1px;
|
||||
bottom: 1px;
|
||||
cursor: se-resize;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
right: 1px;
|
||||
bottom: 1px;
|
||||
}
|
||||
.ui-resizable-sw {
|
||||
cursor: sw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
bottom: -5px;
|
||||
cursor: sw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
bottom: -5px;
|
||||
}
|
||||
.ui-resizable-nw {
|
||||
cursor: nw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
top: -5px;
|
||||
cursor: nw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
.ui-resizable-ne {
|
||||
cursor: ne-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
cursor: ne-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
.ui-sortable-handle {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.ui-button {
|
||||
padding: .4em 1em;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
margin-right: .1em;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
padding: 0.4em 1em;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
margin-right: 0.1em;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
/* Support: IE <= 11 */
|
||||
overflow: visible;
|
||||
/* Support: IE <= 11 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ui-button,
|
||||
|
|
@ -188,128 +186,126 @@
|
|||
.ui-button:visited,
|
||||
.ui-button:hover,
|
||||
.ui-button:active {
|
||||
text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* to make room for the icon, a width needs to be set here */
|
||||
.ui-button-icon-only {
|
||||
width: 2em;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
width: 2em;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* button icon element(s) */
|
||||
.ui-button-icon-only .ui-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -8px;
|
||||
margin-left: -8px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -8px;
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.ui-button.ui-icon-notext .ui-icon {
|
||||
padding: 0;
|
||||
width: 2.1em;
|
||||
height: 2.1em;
|
||||
text-indent: -9999px;
|
||||
white-space: nowrap;
|
||||
|
||||
padding: 0;
|
||||
width: 2.1em;
|
||||
height: 2.1em;
|
||||
text-indent: -9999px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
input.ui-button.ui-icon-notext .ui-icon {
|
||||
width: auto;
|
||||
height: auto;
|
||||
text-indent: 0;
|
||||
white-space: normal;
|
||||
padding: .4em 1em;
|
||||
width: auto;
|
||||
height: auto;
|
||||
text-indent: 0;
|
||||
white-space: normal;
|
||||
padding: 0.4em 1em;
|
||||
}
|
||||
|
||||
/* workarounds */
|
||||
/* Support: Firefox 5 - 40 */
|
||||
input.ui-button::-moz-focus-inner,
|
||||
button.ui-button::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.ui-controlgroup {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
.ui-controlgroup > .ui-controlgroup-item {
|
||||
float: left;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
float: left;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
.ui-controlgroup > .ui-controlgroup-item:focus,
|
||||
.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus {
|
||||
z-index: 9999;
|
||||
z-index: 9999;
|
||||
}
|
||||
.ui-controlgroup-vertical > .ui-controlgroup-item {
|
||||
display: block;
|
||||
float: none;
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
display: block;
|
||||
float: none;
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
}
|
||||
.ui-controlgroup-vertical .ui-controlgroup-item {
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ui-controlgroup .ui-controlgroup-label {
|
||||
padding: .4em 1em;
|
||||
padding: 0.4em 1em;
|
||||
}
|
||||
.ui-controlgroup .ui-controlgroup-label span {
|
||||
font-size: 80%;
|
||||
font-size: 80%;
|
||||
}
|
||||
.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item {
|
||||
border-left: none;
|
||||
border-left: none;
|
||||
}
|
||||
.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item {
|
||||
border-top: none;
|
||||
border-top: none;
|
||||
}
|
||||
.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content {
|
||||
border-right: none;
|
||||
border-right: none;
|
||||
}
|
||||
.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content {
|
||||
border-bottom: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Spinner specific style fixes */
|
||||
.ui-controlgroup-vertical .ui-spinner-input {
|
||||
|
||||
/* Support: IE8 only, Android < 4.4 only */
|
||||
width: 75%;
|
||||
width: calc( 100% - 2.4em );
|
||||
/* Support: IE8 only, Android < 4.4 only */
|
||||
width: 75%;
|
||||
width: calc(100% - 2.4em);
|
||||
}
|
||||
.ui-controlgroup-vertical .ui-spinner .ui-spinner-up {
|
||||
border-top-style: solid;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.ui-checkboxradio-label .ui-icon-background {
|
||||
box-shadow: inset 1px 1px 1px #ccc;
|
||||
border-radius: .12em;
|
||||
border: none;
|
||||
box-shadow: inset 1px 1px 1px #ccc;
|
||||
border-radius: 0.12em;
|
||||
border: none;
|
||||
}
|
||||
.ui-checkboxradio-radio-label .ui-icon-background {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 1em;
|
||||
overflow: visible;
|
||||
border: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 1em;
|
||||
overflow: visible;
|
||||
border: none;
|
||||
}
|
||||
.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,
|
||||
.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon {
|
||||
background-image: none;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-width: 4px;
|
||||
border-style: solid;
|
||||
background-image: none;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-width: 4px;
|
||||
border-style: solid;
|
||||
}
|
||||
.ui-checkboxradio-disabled {
|
||||
pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
body .ui-dialog {
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
outline: 0;
|
||||
|
|
@ -317,30 +313,30 @@ body .ui-dialog {
|
|||
background-color: inherit;
|
||||
}
|
||||
.ui-dialog .ui-dialog-titlebar {
|
||||
padding: .4em 1em;
|
||||
position: relative;
|
||||
font-size: 1.2em;
|
||||
padding: 0.4em 1em;
|
||||
position: relative;
|
||||
font-size: 1.2em;
|
||||
min-width: 150px;
|
||||
}
|
||||
.ui-dialog .ui-dialog-title {
|
||||
float: left;
|
||||
margin: .1em 0;
|
||||
white-space: nowrap;
|
||||
width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
float: left;
|
||||
margin: 0.1em 0;
|
||||
white-space: nowrap;
|
||||
width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-titlebar button {
|
||||
position: absolute;
|
||||
right: .5em;
|
||||
.ui-dialog .ui-dialog-titlebar button {
|
||||
position: absolute;
|
||||
right: 0.5em;
|
||||
top: 53%;
|
||||
padding: 0;
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
color: #ffffff;
|
||||
background: none;
|
||||
font-size: .75em;
|
||||
font-size: 0.75em;
|
||||
border: 1px solid #c5c5c5;
|
||||
}
|
||||
|
||||
|
|
@ -349,113 +345,107 @@ body .ui-dialog {
|
|||
}
|
||||
|
||||
.ui-dialog .ui-dialog-titlebar button.ui-dialog-titlebar-close {
|
||||
margin: -1em 0 0;
|
||||
margin: -1em 0 0;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-titlebar button:active {
|
||||
.ui-dialog .ui-dialog-titlebar button:active {
|
||||
border: 1px solid #5d4651;
|
||||
color: #5d4651;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-content {
|
||||
position: relative;
|
||||
border: 0;
|
||||
padding: .5em 1em;
|
||||
background: none;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
border: 0;
|
||||
padding: 0.5em 1em;
|
||||
background: none;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane {
|
||||
text-align: left;
|
||||
border-width: 1px 0 0 0;
|
||||
background-image: none;
|
||||
margin-top: .5em;
|
||||
padding: .3em 1em .5em .4em;
|
||||
text-align: left;
|
||||
border-width: 1px 0 0 0;
|
||||
background-image: none;
|
||||
margin-top: 0.5em;
|
||||
padding: 0.3em 1em 0.5em 0.4em;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
|
||||
float: right;
|
||||
float: right;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane button {
|
||||
margin: .5em .4em .5em 0;
|
||||
cursor: pointer;
|
||||
margin: 0.5em 0.4em 0.5em 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui-dialog .ui-resizable-n {
|
||||
height: 2px;
|
||||
top: 0;
|
||||
height: 2px;
|
||||
top: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-e {
|
||||
width: 2px;
|
||||
right: 0;
|
||||
width: 2px;
|
||||
right: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-s {
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-w {
|
||||
width: 2px;
|
||||
left: 0;
|
||||
width: 2px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-se,
|
||||
.ui-dialog .ui-resizable-sw,
|
||||
.ui-dialog .ui-resizable-ne,
|
||||
.ui-dialog .ui-resizable-nw {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
.ui-dialog .ui-resizable-se {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-sw {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-ne {
|
||||
right: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-nw {
|
||||
left: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.ui-draggable .ui-dialog-titlebar {
|
||||
cursor: move;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
/* Component containers
|
||||
----------------------------------*/
|
||||
.ui-widget {
|
||||
font-family: Arial,Helvetica,sans-serif;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.ui-widget input,
|
||||
.ui-widget select,
|
||||
.ui-widget textarea,
|
||||
.ui-widget button {
|
||||
font-family: Arial,Helvetica,sans-serif;
|
||||
font-size: 1em;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget button[class^="icon-"] {
|
||||
padding: 1px 6px;
|
||||
padding: 1px 6px;
|
||||
}
|
||||
.ui-widget.ui-widget-content {
|
||||
border: 1px solid #5e4fa2;
|
||||
border: 1px solid #5e4fa2;
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-content {
|
||||
border: 1px solid #dddddd;
|
||||
color: #333333;
|
||||
border: 1px solid #dddddd;
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-content a {
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-header {
|
||||
border-bottom: 1px solid #5d4651;
|
||||
background: #916e7f;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-header a {
|
||||
color: #333333;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* Interaction states
|
||||
|
|
@ -469,10 +459,10 @@ body .ui-dialog {
|
|||
works properly when clicked or hovered */
|
||||
html .ui-button.ui-state-disabled:hover,
|
||||
html .ui-button.ui-state-disabled:active {
|
||||
border: 1px solid #c5c5c5;
|
||||
background: #f6f6f6;
|
||||
font-weight: normal;
|
||||
color: #454545;
|
||||
border: 1px solid #c5c5c5;
|
||||
background: #f6f6f6;
|
||||
font-weight: normal;
|
||||
color: #454545;
|
||||
}
|
||||
.ui-state-default a,
|
||||
.ui-state-default a:link,
|
||||
|
|
@ -481,10 +471,10 @@ a.ui-button,
|
|||
a:link.ui-button,
|
||||
a:visited.ui-button,
|
||||
.ui-button {
|
||||
color: #454545;
|
||||
color: #454545;
|
||||
}
|
||||
.ui-button:active {
|
||||
color: #5d4651;
|
||||
color: #5d4651;
|
||||
border-color: #5d4651;
|
||||
}
|
||||
.ui-state-hover a,
|
||||
|
|
@ -497,12 +487,12 @@ a:visited.ui-button,
|
|||
.ui-state-focus a:visited,
|
||||
a.ui-button:hover,
|
||||
a.ui-button:focus {
|
||||
color: #2b2b2b;
|
||||
text-decoration: none;
|
||||
color: #2b2b2b;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ui-visual-focus {
|
||||
box-shadow: 0 0 3px 1px rgb(94, 158, 214);
|
||||
box-shadow: 0 0 3px 1px rgb(94, 158, 214);
|
||||
}
|
||||
|
||||
/* Interaction Cues
|
||||
|
|
@ -510,68 +500,68 @@ a.ui-button:focus {
|
|||
.ui-state-highlight,
|
||||
.ui-widget-content .ui-state-highlight,
|
||||
.ui-widget-header .ui-state-highlight {
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
color: #777620;
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
color: #777620;
|
||||
}
|
||||
.ui-state-checked {
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
}
|
||||
.ui-state-highlight a,
|
||||
.ui-widget-content .ui-state-highlight a,
|
||||
.ui-widget-header .ui-state-highlight a {
|
||||
color: #777620;
|
||||
color: #777620;
|
||||
}
|
||||
.ui-state-error,
|
||||
.ui-widget-content .ui-state-error,
|
||||
.ui-widget-header .ui-state-error {
|
||||
border: 1px solid #f1a899;
|
||||
background: #fddfdf;
|
||||
color: #5f3f3f;
|
||||
border: 1px solid #f1a899;
|
||||
background: #fddfdf;
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-state-error a,
|
||||
.ui-widget-content .ui-state-error a,
|
||||
.ui-widget-header .ui-state-error a {
|
||||
color: #5f3f3f;
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-state-error-text,
|
||||
.ui-widget-content .ui-state-error-text,
|
||||
.ui-widget-header .ui-state-error-text {
|
||||
color: #5f3f3f;
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-priority-primary,
|
||||
.ui-widget-content .ui-priority-primary,
|
||||
.ui-widget-header .ui-priority-primary {
|
||||
font-weight: bold;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-priority-secondary,
|
||||
.ui-widget-content .ui-priority-secondary,
|
||||
.ui-widget-header .ui-priority-secondary {
|
||||
opacity: .7;
|
||||
filter:Alpha(Opacity=70); /* support: IE8 */
|
||||
font-weight: normal;
|
||||
opacity: 0.7;
|
||||
filter: Alpha(Opacity=70); /* support: IE8 */
|
||||
font-weight: normal;
|
||||
}
|
||||
.ui-state-disabled,
|
||||
.ui-widget-content .ui-state-disabled,
|
||||
.ui-widget-header .ui-state-disabled {
|
||||
opacity: .35;
|
||||
filter:Alpha(Opacity=35); /* support: IE8 */
|
||||
background-image: none;
|
||||
opacity: 0.35;
|
||||
filter: Alpha(Opacity=35); /* support: IE8 */
|
||||
background-image: none;
|
||||
}
|
||||
.ui-state-disabled .ui-icon {
|
||||
filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
|
||||
filter: Alpha(Opacity=35); /* support: IE8 - See #6059 */
|
||||
}
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
background: #aaaaaa;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
background: #aaaaaa;
|
||||
opacity: 0.3;
|
||||
filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
}
|
||||
.ui-widget-shadow {
|
||||
-webkit-box-shadow: 0px 0px 5px #666666;
|
||||
box-shadow: 0px 0px 5px #666666;
|
||||
-webkit-box-shadow: 0px 0px 5px #666666;
|
||||
box-shadow: 0px 0px 5px #666666;
|
||||
}
|
||||
|
|
|
|||
157
libs/pell.js
157
libs/pell.js
|
|
@ -1,157 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
window.Pell = (function () {
|
||||
const defaultParagraphSeparatorString = "defaultParagraphSeparator";
|
||||
const formatBlock = "formatBlock";
|
||||
const addEventListener = (parent, type, listener) => parent.addEventListener(type, listener);
|
||||
const appendChild = (parent, child) => parent.appendChild(child);
|
||||
const createElement = tag => document.createElement(tag);
|
||||
const queryCommandState = command => document.queryCommandState(command);
|
||||
const queryCommandValue = command => document.queryCommandValue(command);
|
||||
const exec = (command, value = null) => document.execCommand(command, false, value);
|
||||
|
||||
const defaultActions = {
|
||||
bold: {
|
||||
icon: "<b>B</b>",
|
||||
title: "Bold",
|
||||
state: () => queryCommandState("bold"),
|
||||
result: () => exec("bold")
|
||||
},
|
||||
italic: {
|
||||
icon: "<i>I</i>",
|
||||
title: "Italic",
|
||||
state: () => queryCommandState("italic"),
|
||||
result: () => exec("italic")
|
||||
},
|
||||
underline: {
|
||||
icon: "<u>U</u>",
|
||||
title: "Underline",
|
||||
state: () => queryCommandState("underline"),
|
||||
result: () => exec("underline")
|
||||
},
|
||||
strikethrough: {
|
||||
icon: "<strike>S</strike>",
|
||||
title: "Strike-through",
|
||||
state: () => queryCommandState("strikeThrough"),
|
||||
result: () => exec("strikeThrough")
|
||||
},
|
||||
heading1: {
|
||||
icon: "<b>H<sub>1</sub></b>",
|
||||
title: "Heading 1",
|
||||
result: () => exec(formatBlock, "<h1>")
|
||||
},
|
||||
heading2: {
|
||||
icon: "<b>H<sub>2</sub></b>",
|
||||
title: "Heading 2",
|
||||
result: () => exec(formatBlock, "<h2>")
|
||||
},
|
||||
paragraph: {
|
||||
icon: "¶",
|
||||
title: "Paragraph",
|
||||
result: () => exec(formatBlock, "<p>")
|
||||
},
|
||||
quote: {
|
||||
icon: "“ ”",
|
||||
title: "Quote",
|
||||
result: () => exec(formatBlock, "<blockquote>")
|
||||
},
|
||||
olist: {
|
||||
icon: "#",
|
||||
title: "Ordered List",
|
||||
result: () => exec("insertOrderedList")
|
||||
},
|
||||
ulist: {
|
||||
icon: "•",
|
||||
title: "Unordered List",
|
||||
result: () => exec("insertUnorderedList")
|
||||
},
|
||||
code: {
|
||||
icon: "</>",
|
||||
title: "Code",
|
||||
result: () => exec(formatBlock, "<pre>")
|
||||
},
|
||||
line: {
|
||||
icon: "―",
|
||||
title: "Horizontal Line",
|
||||
result: () => exec("insertHorizontalRule")
|
||||
},
|
||||
link: {
|
||||
icon: "🔗",
|
||||
title: "Link",
|
||||
result: () => navigator.clipboard.readText().then(url => exec("createLink", url))
|
||||
},
|
||||
image: {
|
||||
icon: "📷",
|
||||
title: "Image",
|
||||
result: () => {
|
||||
navigator.clipboard.readText().then(url => exec("insertImage", url));
|
||||
exec("enableObjectResizing");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const defaultClasses = {
|
||||
actionbar: "pell-actionbar",
|
||||
button: "pell-button",
|
||||
content: "pell-content",
|
||||
selected: "pell-button-selected"
|
||||
};
|
||||
|
||||
const init = settings => {
|
||||
const actions = settings.actions
|
||||
? settings.actions.map(action => {
|
||||
if (typeof action === "string") return defaultActions[action];
|
||||
else if (defaultActions[action.name]) return {...defaultActions[action.name], ...action};
|
||||
return action;
|
||||
})
|
||||
: Object.keys(defaultActions).map(action => defaultActions[action]);
|
||||
|
||||
const classes = {...defaultClasses, ...settings.classes};
|
||||
|
||||
const defaultParagraphSeparator = settings[defaultParagraphSeparatorString] || "div";
|
||||
|
||||
const actionbar = createElement("div");
|
||||
actionbar.className = classes.actionbar;
|
||||
appendChild(settings.element, actionbar);
|
||||
|
||||
const content = (settings.element.content = createElement("div"));
|
||||
content.contentEditable = true;
|
||||
content.className = classes.content;
|
||||
content.oninput = ({target: {firstChild}}) => {
|
||||
if (firstChild && firstChild.nodeType === 3) exec(formatBlock, `<${defaultParagraphSeparator}>`);
|
||||
else if (content.innerHTML === "<br>") content.innerHTML = "";
|
||||
settings.onChange(content.innerHTML);
|
||||
};
|
||||
content.onkeydown = event => {
|
||||
if (event.key === "Enter" && queryCommandValue(formatBlock) === "blockquote") {
|
||||
setTimeout(() => exec(formatBlock, `<${defaultParagraphSeparator}>`), 0);
|
||||
}
|
||||
};
|
||||
appendChild(settings.element, content);
|
||||
|
||||
actions.forEach(action => {
|
||||
const button = createElement("button");
|
||||
button.className = classes.button;
|
||||
button.innerHTML = action.icon;
|
||||
button.title = action.title;
|
||||
button.setAttribute("type", "button");
|
||||
button.onclick = () => action.result() && content.focus();
|
||||
|
||||
if (action.state) {
|
||||
const handler = () => button.classList[action.state() ? "add" : "remove"](classes.selected);
|
||||
addEventListener(content, "keyup", handler);
|
||||
addEventListener(content, "mouseup", handler);
|
||||
addEventListener(button, "click", handler);
|
||||
}
|
||||
|
||||
appendChild(actionbar, button);
|
||||
});
|
||||
|
||||
if (settings.styleWithCSS) exec("styleWithCSS");
|
||||
exec(defaultParagraphSeparatorString, defaultParagraphSeparator);
|
||||
|
||||
return settings.element;
|
||||
};
|
||||
|
||||
return {exec, init};
|
||||
})();
|
||||
36
libs/umami.js
Normal file
36
libs/umami.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
(window => {
|
||||
const noTrack = window.localStorage.getItem("noTrack");
|
||||
|
||||
const {
|
||||
screen: {width, height},
|
||||
navigator: {language},
|
||||
location: {hostname, pathname, search},
|
||||
document: {referrer}
|
||||
} = window;
|
||||
|
||||
const website = "4f6fd0ae-646a-4946-a9da-7aad63284e48";
|
||||
const root = "https://fmg-stats.herokuapp.com";
|
||||
const screen = `${width}x${height}`;
|
||||
const url = `${pathname}${search}`;
|
||||
|
||||
const post = (url, data) => {
|
||||
const req = new XMLHttpRequest();
|
||||
req.open("POST", url, true);
|
||||
req.setRequestHeader("Content-Type", "application/json");
|
||||
req.send(JSON.stringify(data));
|
||||
};
|
||||
|
||||
const collect = (type, params) => {
|
||||
if (noTrack) return;
|
||||
|
||||
const payload = {website, hostname, screen, language, cache: false};
|
||||
Object.keys(params).forEach(key => {
|
||||
payload[key] = params[key];
|
||||
});
|
||||
|
||||
post(`${root}/api/collect`, {type, payload});
|
||||
};
|
||||
|
||||
collect("pageview", {url, referrer});
|
||||
window.track = (event_type = "reach", event_value = "") => collect("event", {event_type, event_value, url});
|
||||
})(window);
|
||||
99
main.js
99
main.js
|
|
@ -2,7 +2,7 @@
|
|||
// https://github.com/Azgaar/Fantasy-Map-Generator
|
||||
|
||||
"use strict";
|
||||
const version = "1.661"; // generator version
|
||||
const version = "1.7"; // generator version
|
||||
document.title += " v" + version;
|
||||
|
||||
// Switches to disable/enable logging features
|
||||
|
|
@ -64,7 +64,7 @@ let icons = viewbox.append("g").attr("id", "icons");
|
|||
let burgIcons = icons.append("g").attr("id", "burgIcons");
|
||||
let anchors = icons.append("g").attr("id", "anchors");
|
||||
let armies = viewbox.append("g").attr("id", "armies").style("display", "none");
|
||||
let markers = viewbox.append("g").attr("id", "markers").style("display", "none");
|
||||
let markers = viewbox.append("g").attr("id", "markers");
|
||||
let fogging = viewbox.append("g").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("display", "none");
|
||||
let ruler = viewbox.append("g").attr("id", "ruler").style("display", "none");
|
||||
let debug = viewbox.append("g").attr("id", "debug");
|
||||
|
|
@ -318,7 +318,6 @@ function findBurgForMFCG(params) {
|
|||
else if (p[0] === "shantytown") b.shanty = +p[1];
|
||||
else b[p[0]] = +p[1]; // other parameters
|
||||
}
|
||||
b.MFCGlink = document.referrer; // set direct link to MFCG
|
||||
if (params.get("name") && params.get("name") != "null") b.name = params.get("name");
|
||||
|
||||
const label = burgLabels.select("[data-id='" + burgId + "']");
|
||||
|
|
@ -339,11 +338,39 @@ function findBurgForMFCG(params) {
|
|||
|
||||
// apply default biomes data
|
||||
function applyDefaultBiomesSystem() {
|
||||
const name = ["Marine", "Hot desert", "Cold desert", "Savanna", "Grassland", "Tropical seasonal forest", "Temperate deciduous forest", "Tropical rainforest", "Temperate rainforest", "Taiga", "Tundra", "Glacier", "Wetland"];
|
||||
const name = [
|
||||
"Marine",
|
||||
"Hot desert",
|
||||
"Cold desert",
|
||||
"Savanna",
|
||||
"Grassland",
|
||||
"Tropical seasonal forest",
|
||||
"Temperate deciduous forest",
|
||||
"Tropical rainforest",
|
||||
"Temperate rainforest",
|
||||
"Taiga",
|
||||
"Tundra",
|
||||
"Glacier",
|
||||
"Wetland"
|
||||
];
|
||||
const color = ["#466eab", "#fbe79f", "#b5b887", "#d2d082", "#c8d68f", "#b6d95d", "#29bc56", "#7dcb35", "#409c43", "#4b6b32", "#96784b", "#d5e7eb", "#0b9131"];
|
||||
const habitability = [0, 4, 10, 22, 30, 50, 100, 80, 90, 12, 4, 0, 12];
|
||||
const iconsDensity = [0, 3, 2, 120, 120, 120, 120, 150, 150, 100, 5, 0, 150];
|
||||
const icons = [{}, {dune: 3, cactus: 6, deadTree: 1}, {dune: 9, deadTree: 1}, {acacia: 1, grass: 9}, {grass: 1}, {acacia: 8, palm: 1}, {deciduous: 1}, {acacia: 5, palm: 3, deciduous: 1, swamp: 1}, {deciduous: 6, swamp: 1}, {conifer: 1}, {grass: 1}, {}, {swamp: 1}];
|
||||
const icons = [
|
||||
{},
|
||||
{dune: 3, cactus: 6, deadTree: 1},
|
||||
{dune: 9, deadTree: 1},
|
||||
{acacia: 1, grass: 9},
|
||||
{grass: 1},
|
||||
{acacia: 8, palm: 1},
|
||||
{deciduous: 1},
|
||||
{acacia: 5, palm: 3, deciduous: 1, swamp: 1},
|
||||
{deciduous: 6, swamp: 1},
|
||||
{conifer: 1},
|
||||
{grass: 1},
|
||||
{},
|
||||
{swamp: 1}
|
||||
];
|
||||
const cost = [10, 200, 150, 60, 50, 70, 70, 80, 90, 200, 1000, 5000, 150]; // biome movement cost
|
||||
const biomesMartix = [
|
||||
// hot ↔ cold [>19°C; <-4°C]; dry ↕ wet
|
||||
|
|
@ -377,11 +404,14 @@ function showWelcomeMessage() {
|
|||
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
|
||||
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
|
||||
<ul>Main changes:
|
||||
<li>Add custom fonts dialog</li>
|
||||
<li>Save and load <i>.map</i> files to Dropbox</li>
|
||||
<li>Ability to add control points on river edit</li>
|
||||
<li>New heightmap template: Taklamakan</li>
|
||||
<li>Option to not scale labels on zoom</li>
|
||||
<li>New marker types</li>
|
||||
<li>New markers editor</li>
|
||||
<li>Markers overview screen</li>
|
||||
<li>Markers regeneration menu</li>
|
||||
<li>Burg editor update</li>
|
||||
<li>Editable theme color</li>
|
||||
<li>Add font dialog</li>
|
||||
<li>Save to Dropbox</li>
|
||||
</ul>
|
||||
|
||||
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
||||
|
|
@ -492,19 +522,18 @@ function invokeActiveZooming() {
|
|||
}
|
||||
|
||||
// rescale map markers
|
||||
if (+markers.attr("rescale") && markers.style("display") !== "none") {
|
||||
markers.selectAll("use").each(function () {
|
||||
const x = +this.dataset.x,
|
||||
y = +this.dataset.y,
|
||||
desired = +this.dataset.size;
|
||||
const size = Math.max(desired * 5 + 25 / scale, 1);
|
||||
d3.select(this)
|
||||
.attr("x", x - size / 2)
|
||||
.attr("y", y - size)
|
||||
.attr("width", size)
|
||||
.attr("height", size);
|
||||
+markers.attr("rescale") &&
|
||||
pack.markers?.forEach(marker => {
|
||||
const {i, x, y, size = 30, hidden} = marker;
|
||||
const el = !hidden && document.getElementById(`marker${i}`);
|
||||
if (!el) return;
|
||||
|
||||
const zoomedSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
|
||||
el.setAttribute("width", zoomedSize);
|
||||
el.setAttribute("height", zoomedSize);
|
||||
el.setAttribute("x", rn(x - zoomedSize / 2, 1));
|
||||
el.setAttribute("y", rn(y - zoomedSize, 1));
|
||||
});
|
||||
}
|
||||
|
||||
// rescale rulers to have always the same size
|
||||
if (ruler.style("display") !== "none") {
|
||||
|
|
@ -626,11 +655,13 @@ function generate() {
|
|||
INFO && console.groupEnd("Generated Map " + seed);
|
||||
} catch (error) {
|
||||
ERROR && console.error(error);
|
||||
const parsedError = parseError(error);
|
||||
track("error", parsedError);
|
||||
clearMainTip();
|
||||
|
||||
alertMessage.innerHTML = `An error is occured on map generation. Please retry.
|
||||
<br>If error is critical, clear the stored data and try again.
|
||||
<p id="errorBox">${parseError(error)}</p>`;
|
||||
<p id="errorBox">${parsedError}</p>`;
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Generation error",
|
||||
|
|
@ -641,7 +672,7 @@ function generate() {
|
|||
localStorage.setItem("version", version);
|
||||
},
|
||||
Regenerate: function () {
|
||||
regenerateMap();
|
||||
regenerateMap("generation error");
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Ignore: function () {
|
||||
|
|
@ -670,6 +701,7 @@ function generateSeed() {
|
|||
// Place points to calculate Voronoi diagram
|
||||
function placePoints() {
|
||||
TIME && console.time("placePoints");
|
||||
Math.random = aleaPRNG(seed); // reset PRNG
|
||||
|
||||
const cellsDesired = +pointsInput.dataset.cells;
|
||||
const spacing = (grid.spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2)); // spacing between points before jirrering
|
||||
|
|
@ -1482,7 +1514,18 @@ function addZones(number = 1) {
|
|||
});
|
||||
}
|
||||
|
||||
const invasion = rw({Invasion: 4, Occupation: 3, Raid: 2, Conquest: 2, Subjugation: 1, Foray: 1, Skirmishes: 1, Incursion: 2, Pillaging: 1, Intervention: 1});
|
||||
const invasion = rw({
|
||||
Invasion: 4,
|
||||
Occupation: 3,
|
||||
Raid: 2,
|
||||
Conquest: 2,
|
||||
Subjugation: 1,
|
||||
Foray: 1,
|
||||
Skirmishes: 1,
|
||||
Incursion: 2,
|
||||
Pillaging: 1,
|
||||
Intervention: 1
|
||||
});
|
||||
const name = getAdjective(invader.name) + " " + invasion;
|
||||
data.push({name, type: "Invasion", cells: cellsArray, fill: "url(#hatch1)"});
|
||||
}
|
||||
|
|
@ -1792,7 +1835,7 @@ function addZones(number = 1) {
|
|||
|
||||
// show map stats on generation complete
|
||||
function showStatistics() {
|
||||
const template = templateInput.value;
|
||||
const template = templateInput.options[templateInput.selectedIndex].text;
|
||||
const templateRandom = locked("template") ? "" : "(random)";
|
||||
const stats = ` Seed: ${seed}
|
||||
Canvas size: ${graphWidth}x${graphHeight}
|
||||
|
|
@ -1810,9 +1853,10 @@ function showStatistics() {
|
|||
mapId = Date.now(); // unique map id is it's creation date number
|
||||
mapHistory.push({seed, width: graphWidth, height: graphHeight, template, created: mapId});
|
||||
INFO && console.log(stats);
|
||||
track("generate", `Template: ${template} ${templateRandom}. Points: ${pointsInput.dataset.cells}`);
|
||||
}
|
||||
|
||||
const regenerateMap = debounce(function () {
|
||||
const regenerateMap = debounce(function (source) {
|
||||
WARN && console.warn("Generate new random map");
|
||||
closeDialogs("#worldConfigurator, #options3d");
|
||||
customization = 0;
|
||||
|
|
@ -1822,6 +1866,7 @@ const regenerateMap = debounce(function () {
|
|||
restoreLayers();
|
||||
if (ThreeD.options.isOn) ThreeD.redraw();
|
||||
if ($("#worldConfigurator").is(":visible")) editWorld();
|
||||
track("regenerate", `from ${source}`);
|
||||
}, 1000);
|
||||
|
||||
// clear the map
|
||||
|
|
|
|||
498
modules/export.js
Normal file
498
modules/export.js
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
"use strict";
|
||||
// Functions to export map to image or data files
|
||||
|
||||
// download map as SVG
|
||||
async function saveSVG() {
|
||||
TIME && console.time("saveSVG");
|
||||
track("export", "svg");
|
||||
const url = await getMapURL("svg");
|
||||
const link = document.createElement("a");
|
||||
link.download = getFileName() + ".svg";
|
||||
link.href = url;
|
||||
link.click();
|
||||
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
|
||||
TIME && console.timeEnd("saveSVG");
|
||||
}
|
||||
|
||||
// download map as PNG
|
||||
async function savePNG() {
|
||||
TIME && console.time("savePNG");
|
||||
track("export", "png");
|
||||
const url = await getMapURL("png");
|
||||
|
||||
const link = document.createElement("a");
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = svgWidth * pngResolutionInput.value;
|
||||
canvas.height = svgHeight * pngResolutionInput.value;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
img.onload = function () {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
link.download = getFileName() + ".png";
|
||||
canvas.toBlob(function (blob) {
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.click();
|
||||
window.setTimeout(function () {
|
||||
canvas.remove();
|
||||
window.URL.revokeObjectURL(link.href);
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
TIME && console.timeEnd("savePNG");
|
||||
}
|
||||
|
||||
// download map as JPEG
|
||||
async function saveJPEG() {
|
||||
TIME && console.time("saveJPEG");
|
||||
track("export", "jpg");
|
||||
const url = await getMapURL("png");
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = svgWidth * pngResolutionInput.value;
|
||||
canvas.height = svgHeight * pngResolutionInput.value;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
img.onload = async function () {
|
||||
canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92);
|
||||
const URL = await canvas.toDataURL("image/jpeg", quality);
|
||||
const link = document.createElement("a");
|
||||
link.download = getFileName() + ".jpeg";
|
||||
link.href = URL;
|
||||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||
};
|
||||
|
||||
TIME && console.timeEnd("saveJPEG");
|
||||
}
|
||||
|
||||
// download map as png tiles
|
||||
async function saveTiles() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// download schema
|
||||
const urlSchema = await getMapURL("tiles", {debug: true});
|
||||
const zip = new JSZip();
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = graphWidth;
|
||||
canvas.height = graphHeight;
|
||||
|
||||
const imgSchema = new Image();
|
||||
imgSchema.src = urlSchema;
|
||||
imgSchema.onload = function () {
|
||||
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob(blob => zip.file(`fmg_tile_schema.png`, blob));
|
||||
};
|
||||
|
||||
// download tiles
|
||||
const url = await getMapURL("tiles");
|
||||
const tilesX = +document.getElementById("tileColsInput").value;
|
||||
const tilesY = +document.getElementById("tileRowsInput").value;
|
||||
const scale = +document.getElementById("tileScaleInput").value;
|
||||
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
const tolesTotal = tilesX * tilesY;
|
||||
|
||||
const width = graphWidth * scale;
|
||||
const height = width * (tileH / tileW);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
let loaded = 0;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
||||
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
|
||||
const name = `fmg_tile_${i}.png`;
|
||||
canvas.toBlob(blob => {
|
||||
zip.file(name, blob);
|
||||
loaded += 1;
|
||||
if (loaded === tolesTotal) return downloadZip();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function downloadZip() {
|
||||
const name = `${getFileName()}.zip`;
|
||||
zip.generateAsync({type: "blob"}).then(blob => {
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = name;
|
||||
link.click();
|
||||
link.remove();
|
||||
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// parse map svg to object url
|
||||
async function getMapURL(type, options = {}) {
|
||||
const {debug = false, globe = false, noLabels = false, noWater = false} = options;
|
||||
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg
|
||||
cloneEl.id = "fantasyMap";
|
||||
document.body.appendChild(cloneEl);
|
||||
const clone = d3.select(cloneEl);
|
||||
if (!debug) clone.select("#debug")?.remove();
|
||||
|
||||
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
||||
const svgDefs = document.getElementById("defElements");
|
||||
|
||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
|
||||
if (isFirefox && type === "mesh") clone.select("#oceanPattern")?.remove();
|
||||
if (globe) clone.select("#scaleBar")?.remove();
|
||||
if (noLabels) {
|
||||
clone.select("#labels #states")?.remove();
|
||||
clone.select("#labels #burgLabels")?.remove();
|
||||
clone.select("#icons #burgIcons")?.remove();
|
||||
}
|
||||
if (noWater) {
|
||||
clone.select("#oceanBase").attr("opacity", 0);
|
||||
clone.select("#oceanPattern").attr("opacity", 0);
|
||||
}
|
||||
if (type !== "png") {
|
||||
// reset transform to show the whole map
|
||||
clone.attr("width", graphWidth).attr("height", graphHeight);
|
||||
clone.select("#viewbox").attr("transform", null);
|
||||
}
|
||||
|
||||
if (type === "svg") removeUnusedElements(clone);
|
||||
if (customization && type === "mesh") updateMeshCells(clone);
|
||||
inlineStyle(clone);
|
||||
|
||||
// remove unused filters
|
||||
const filters = cloneEl.querySelectorAll("filter");
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
const id = filters[i].id;
|
||||
if (cloneEl.querySelector("[filter='url(#" + id + ")']")) continue;
|
||||
if (cloneEl.getAttribute("filter") === "url(#" + id + ")") continue;
|
||||
filters[i].remove();
|
||||
}
|
||||
|
||||
// remove unused patterns
|
||||
const patterns = cloneEl.querySelectorAll("pattern");
|
||||
for (let i = 0; i < patterns.length; i++) {
|
||||
const id = patterns[i].id;
|
||||
if (cloneEl.querySelector("[fill='url(#" + id + ")']")) continue;
|
||||
patterns[i].remove();
|
||||
}
|
||||
|
||||
// remove unused symbols
|
||||
const symbols = cloneEl.querySelectorAll("symbol");
|
||||
for (let i = 0; i < symbols.length; i++) {
|
||||
const id = symbols[i].id;
|
||||
if (cloneEl.querySelector("use[*|href='#" + id + "']")) continue;
|
||||
symbols[i].remove();
|
||||
}
|
||||
|
||||
// add displayed emblems
|
||||
if (layerIsOn("toggleEmblems") && emblems.selectAll("use").size()) {
|
||||
cloneEl
|
||||
.getElementById("emblems")
|
||||
?.querySelectorAll("use")
|
||||
.forEach(el => {
|
||||
const href = el.getAttribute("href") || el.getAttribute("xlink:href");
|
||||
if (!href) return;
|
||||
const emblem = document.getElementById(href.slice(1));
|
||||
if (emblem) cloneDefs.append(emblem.cloneNode(true));
|
||||
});
|
||||
} else {
|
||||
cloneDefs.querySelector("#defs-emblems")?.remove();
|
||||
}
|
||||
|
||||
// replace ocean pattern href to base64
|
||||
if (location.hostname && cloneEl.getElementById("oceanicPattern")) {
|
||||
const el = cloneEl.getElementById("oceanicPattern");
|
||||
const url = el.getAttribute("href");
|
||||
await new Promise(resolve => {
|
||||
getBase64(url, base64 => {
|
||||
el.setAttribute("href", base64);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add relief icons
|
||||
if (cloneEl.getElementById("terrain")) {
|
||||
const uniqueElements = new Set();
|
||||
const terrainNodes = cloneEl.getElementById("terrain").childNodes;
|
||||
for (let i = 0; i < terrainNodes.length; i++) {
|
||||
const href = terrainNodes[i].getAttribute("href") || terrainNodes[i].getAttribute("xlink:href");
|
||||
uniqueElements.add(href);
|
||||
}
|
||||
|
||||
const defsRelief = svgDefs.getElementById("defs-relief");
|
||||
for (const terrain of [...uniqueElements]) {
|
||||
const element = defsRelief.querySelector(terrain);
|
||||
if (element) cloneDefs.appendChild(element.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
// add wind rose
|
||||
if (cloneEl.getElementById("compass")) {
|
||||
const rose = svgDefs.getElementById("rose");
|
||||
if (rose) cloneDefs.appendChild(rose.cloneNode(true));
|
||||
}
|
||||
|
||||
// add port icon
|
||||
if (cloneEl.getElementById("anchors")) {
|
||||
const anchor = svgDefs.getElementById("icon-anchor");
|
||||
if (anchor) cloneDefs.appendChild(anchor.cloneNode(true));
|
||||
}
|
||||
|
||||
// add grid pattern
|
||||
if (cloneEl.getElementById("gridOverlay")?.hasChildNodes()) {
|
||||
const type = cloneEl.getElementById("gridOverlay").getAttribute("type");
|
||||
const pattern = svgDefs.getElementById("pattern_" + type);
|
||||
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
|
||||
}
|
||||
|
||||
if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching")?.remove(); // remove unused hatching group
|
||||
if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog
|
||||
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths
|
||||
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths
|
||||
|
||||
// add armies style
|
||||
if (cloneEl.getElementById("armies"))
|
||||
cloneEl.insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
"<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>"
|
||||
);
|
||||
|
||||
// add xlink: for href to support svg1.1
|
||||
if (type === "svg") {
|
||||
cloneEl.querySelectorAll("[href]").forEach(el => {
|
||||
const href = el.getAttribute("href");
|
||||
el.removeAttribute("href");
|
||||
el.setAttribute("xlink:href", href);
|
||||
});
|
||||
}
|
||||
|
||||
const usedFonts = getUsedFonts(cloneEl);
|
||||
const fontsToLoad = usedFonts.filter(font => font.src);
|
||||
if (fontsToLoad.length) {
|
||||
const dataURLfonts = await loadFontsAsDataURI(fontsToLoad);
|
||||
|
||||
const fontFaces = dataURLfonts
|
||||
.map(({family, src, unicodeRange = "", variant = "normal"}) => {
|
||||
return `@font-face {font-family: "${family}"; src: ${src}; unicode-range: ${unicodeRange}; font-variant: ${variant};}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.setAttribute("type", "text/css");
|
||||
style.innerHTML = fontFaces;
|
||||
cloneEl.querySelector("defs").appendChild(style);
|
||||
}
|
||||
|
||||
clone.remove();
|
||||
|
||||
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + new XMLSerializer().serializeToString(cloneEl);
|
||||
const blob = new Blob([serialized], {type: "image/svg+xml;charset=utf-8"});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
|
||||
return url;
|
||||
}
|
||||
|
||||
// remove hidden g elements and g elements without children to make downloaded svg smaller in size
|
||||
function removeUnusedElements(clone) {
|
||||
if (!terrain.selectAll("use").size()) clone.select("#defs-relief")?.remove();
|
||||
|
||||
for (let empty = 1; empty; ) {
|
||||
empty = 0;
|
||||
clone.selectAll("g").each(function () {
|
||||
if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) {
|
||||
empty++;
|
||||
this.remove();
|
||||
}
|
||||
if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateMeshCells(clone) {
|
||||
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
|
||||
const scheme = getColorScheme();
|
||||
clone.select("#heights").attr("filter", "url(#blur1)");
|
||||
clone
|
||||
.select("#heights")
|
||||
.selectAll("polygon")
|
||||
.data(data)
|
||||
.join("polygon")
|
||||
.attr("points", d => getGridPolygon(d))
|
||||
.attr("id", d => "cell" + d)
|
||||
.attr("stroke", d => getColor(grid.cells.h[d], scheme));
|
||||
}
|
||||
|
||||
// for each g element get inline style
|
||||
function inlineStyle(clone) {
|
||||
const emptyG = clone.append("g").node();
|
||||
const defaultStyles = window.getComputedStyle(emptyG);
|
||||
|
||||
clone.selectAll("g, #ruler *, #scaleBar > text").each(function () {
|
||||
const compStyle = window.getComputedStyle(this);
|
||||
let style = "";
|
||||
|
||||
for (let i = 0; i < compStyle.length; i++) {
|
||||
const key = compStyle[i];
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
// Firefox mask hack
|
||||
if (key === "mask-image" && value !== defaultStyles.getPropertyValue(key)) {
|
||||
style += "mask-image: url('#land');";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === "cursor") continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
style += key + ":" + value + ";";
|
||||
}
|
||||
|
||||
for (const key in compStyle) {
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
if (key === "cursor") continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
style += key + ":" + value + ";";
|
||||
}
|
||||
|
||||
if (style != "") this.setAttribute("style", style);
|
||||
});
|
||||
|
||||
emptyG.remove();
|
||||
}
|
||||
|
||||
function saveGeoJSON_Cells() {
|
||||
track("export", "getJSON cells");
|
||||
const json = {type: "FeatureCollection", features: []};
|
||||
const cells = pack.cells;
|
||||
const getPopulation = i => {
|
||||
const [r, u] = getCellPopulation(i);
|
||||
return rn(r + u);
|
||||
};
|
||||
const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]]));
|
||||
|
||||
cells.i.forEach(i => {
|
||||
const coordinates = getCellCoordinates(cells.v[i]);
|
||||
const height = getHeight(i);
|
||||
const biome = cells.biome[i];
|
||||
const type = pack.features[cells.f[i]].type;
|
||||
const population = getPopulation(i);
|
||||
const state = cells.state[i];
|
||||
const province = cells.province[i];
|
||||
const culture = cells.culture[i];
|
||||
const religion = cells.religion[i];
|
||||
const neighbors = cells.c[i];
|
||||
|
||||
const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors};
|
||||
const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName("Cells") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), name, "application/json");
|
||||
}
|
||||
|
||||
function saveGeoJSON_Routes() {
|
||||
track("export", "getJSON routes");
|
||||
const json = {type: "FeatureCollection", features: []};
|
||||
|
||||
routes.selectAll("g > path").each(function () {
|
||||
const coordinates = getRoutePoints(this);
|
||||
const id = this.id;
|
||||
const type = this.parentElement.id;
|
||||
|
||||
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, type}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName("Routes") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), name, "application/json");
|
||||
}
|
||||
|
||||
function saveGeoJSON_Rivers() {
|
||||
track("export", "getJSON rivers");
|
||||
const json = {type: "FeatureCollection", features: []};
|
||||
|
||||
rivers.selectAll("path").each(function () {
|
||||
const coordinates = getRiverPoints(this);
|
||||
const id = this.id;
|
||||
const width = +this.dataset.increment;
|
||||
const increment = +this.dataset.increment;
|
||||
const river = pack.rivers.find(r => r.i === +id.slice(5));
|
||||
const name = river ? river.name : "";
|
||||
const type = river ? river.type : "";
|
||||
const i = river ? river.i : "";
|
||||
const basin = river ? river.basin : "";
|
||||
|
||||
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, i, basin, name, type, width, increment}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName("Rivers") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), name, "application/json");
|
||||
}
|
||||
|
||||
function saveGeoJSON_Markers() {
|
||||
track("export", "getJSON markers");
|
||||
|
||||
const features = pack.markers.map(marker => {
|
||||
const {i, type, icon, x, y, size, fill, stroke} = marker;
|
||||
const coordinates = getQGIScoordinates(x, y);
|
||||
const id = `marker${i}`;
|
||||
const note = notes.find(note => note.id === id);
|
||||
const properties = {id, type, icon, ...note, size, fill, stroke};
|
||||
return {type: "Feature", geometry: {type: "Point", coordinates}, properties};
|
||||
});
|
||||
|
||||
const json = {type: "FeatureCollection", features};
|
||||
|
||||
const fileName = getFileName("Markers") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||
}
|
||||
|
||||
function getCellCoordinates(vertices) {
|
||||
const p = pack.vertices.p;
|
||||
const coordinates = vertices.map(n => getQGIScoordinates(p[n][0], p[n][1]));
|
||||
return [coordinates.concat([coordinates[0]])];
|
||||
}
|
||||
|
||||
function getRoutePoints(node) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength();
|
||||
const increment = l / Math.ceil(l / 2);
|
||||
for (let i = 0; i <= l; i += increment) {
|
||||
const p = node.getPointAtLength(i);
|
||||
points.push(getQGIScoordinates(p.x, p.y));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
function getRiverPoints(node) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength() / 2; // half-length
|
||||
const increment = 0.25; // defines density of points
|
||||
for (let i = l, c = i; i >= 0; i -= increment, c += increment) {
|
||||
const p1 = node.getPointAtLength(i);
|
||||
const p2 = node.getPointAtLength(c);
|
||||
const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
|
||||
points.push([x, y]);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Functions to save and load the map
|
||||
"use strict";
|
||||
// Functions to load and parse .map files
|
||||
|
||||
function quickLoad() {
|
||||
ldb.get("lastMap", blob => {
|
||||
|
|
@ -13,6 +13,7 @@ function quickLoad() {
|
|||
}
|
||||
|
||||
async function loadFromDropbox() {
|
||||
track("load", `from dropbox`);
|
||||
const mapPath = document.getElementById("loadFromDropboxSelect")?.value;
|
||||
|
||||
DEBUG && console.log("Loading map from Dropbox:", mapPath);
|
||||
|
|
@ -68,6 +69,7 @@ function loadMapPrompt(blob) {
|
|||
|
||||
function loadLastSavedMap() {
|
||||
WARN && console.warn("Load last saved map");
|
||||
track("load", `from browser storage`);
|
||||
try {
|
||||
uploadMap(blob);
|
||||
} catch (error) {
|
||||
|
|
@ -78,6 +80,7 @@ function loadMapPrompt(blob) {
|
|||
}
|
||||
|
||||
function loadMapFromURL(maplink, random) {
|
||||
track("load", `from url`);
|
||||
const URL = decodeURIComponent(maplink);
|
||||
|
||||
fetch(URL, {method: "GET", mode: "cors"})
|
||||
|
|
@ -93,6 +96,7 @@ function loadMapFromURL(maplink, random) {
|
|||
}
|
||||
|
||||
function showUploadErrorMessage(error, URL, random) {
|
||||
track("error", `map load from url`);
|
||||
ERROR && console.error(error);
|
||||
alertMessage.innerHTML = `Cannot load map from the ${link(URL, "link provided")}.
|
||||
${random ? `A new random map is generated. ` : ""}
|
||||
|
|
@ -340,6 +344,7 @@ function parseLoadedData(data) {
|
|||
pack.religions = data[29] ? JSON.parse(data[29]) : [{i: 0, name: "No religion"}];
|
||||
pack.provinces = data[30] ? JSON.parse(data[30]) : [0];
|
||||
pack.rivers = data[32] ? JSON.parse(data[32]) : [];
|
||||
pack.markers = data[35] ? JSON.parse(data[35]) : [];
|
||||
|
||||
const cells = pack.cells;
|
||||
cells.biome = Uint8Array.from(data[16].split(","));
|
||||
|
|
@ -405,7 +410,7 @@ function parseLoadedData(data) {
|
|||
if (notHidden(labels)) turnOn("toggleLabels");
|
||||
if (notHidden(icons)) turnOn("toggleIcons");
|
||||
if (hasChildren(armies) && notHidden(armies)) turnOn("toggleMilitary");
|
||||
if (hasChildren(markers) && notHidden(markers)) turnOn("toggleMarkers");
|
||||
if (hasChildren(markers)) turnOn("toggleMarkers");
|
||||
if (notHidden(ruler)) turnOn("toggleRulers");
|
||||
if (notHidden(scaleBar)) turnOn("toggleScaleBar");
|
||||
|
||||
|
|
@ -431,12 +436,27 @@ function parseLoadedData(data) {
|
|||
|
||||
// 1.0 adds a legend box
|
||||
legend = svg.append("g").attr("id", "legend");
|
||||
legend.attr("font-family", "Almendra SC").attr("font-size", 13).attr("data-size", 13).attr("data-x", 99).attr("data-y", 93).attr("stroke-width", 2.5).attr("stroke", "#812929").attr("stroke-dasharray", "0 4 10 4").attr("stroke-linecap", "round");
|
||||
legend
|
||||
.attr("font-family", "Almendra SC")
|
||||
.attr("font-size", 13)
|
||||
.attr("data-size", 13)
|
||||
.attr("data-x", 99)
|
||||
.attr("data-y", 93)
|
||||
.attr("stroke-width", 2.5)
|
||||
.attr("stroke", "#812929")
|
||||
.attr("stroke-dasharray", "0 4 10 4")
|
||||
.attr("stroke-linecap", "round");
|
||||
|
||||
// 1.0 separated drawBorders fron drawStates()
|
||||
stateBorders = borders.append("g").attr("id", "stateBorders");
|
||||
provinceBorders = borders.append("g").attr("id", "provinceBorders");
|
||||
borders.attr("opacity", null).attr("stroke", null).attr("stroke-width", null).attr("stroke-dasharray", null).attr("stroke-linecap", null).attr("filter", null);
|
||||
borders
|
||||
.attr("opacity", null)
|
||||
.attr("stroke", null)
|
||||
.attr("stroke-width", null)
|
||||
.attr("stroke-dasharray", null)
|
||||
.attr("stroke-linecap", null)
|
||||
.attr("filter", null);
|
||||
stateBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt");
|
||||
provinceBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 0.5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt");
|
||||
|
||||
|
|
@ -815,6 +835,7 @@ function parseLoadedData(data) {
|
|||
const riverPoints = [];
|
||||
|
||||
const length = node.getTotalLength() / 2;
|
||||
if (!length) continue;
|
||||
const segments = Math.ceil(length / 6);
|
||||
const increment = length / segments;
|
||||
|
||||
|
|
@ -848,6 +869,52 @@ function parseLoadedData(data) {
|
|||
rivers.attr("style", null);
|
||||
borders.attr("style", null);
|
||||
}
|
||||
|
||||
if (version < 1.7) {
|
||||
// v 1.7 changed markers data
|
||||
const defs = document.getElementById("defs-markers");
|
||||
const markersGroup = document.getElementById("markers");
|
||||
const markerElements = markersGroup.querySelectorAll("use");
|
||||
|
||||
pack.markers = Array.from(markerElements).map((el, i) => {
|
||||
const id = el.getAttribute("id");
|
||||
const note = notes.find(note => note.id === id);
|
||||
if (note) note.id = `marker${i}`;
|
||||
|
||||
const x = rn(+el.dataset.x, 1);
|
||||
const y = rn(+el.dataset.y, 1);
|
||||
const cell = findCell(x, y);
|
||||
const size = rn(el.dataset.size * 30, 1);
|
||||
|
||||
const href = el.href.baseVal;
|
||||
const type = href.replace("#marker_", "");
|
||||
const symbol = defs.querySelector(`symbol${href}`);
|
||||
const text = symbol.querySelector("text");
|
||||
const circle = symbol.querySelector("circle");
|
||||
|
||||
const icon = text.innerHTML;
|
||||
const px = Number(text.getAttribute("font-size")?.replace("px", ""));
|
||||
const dx = Number(text.getAttribute("x")?.replace("%", ""));
|
||||
const dy = Number(text.getAttribute("y")?.replace("%", ""));
|
||||
const fill = circle.getAttribute("fill");
|
||||
const stroke = circle.getAttribute("stroke");
|
||||
|
||||
const marker = {i, icon, type, x, y, size, cell};
|
||||
if (size && size !== 30) marker.size = size;
|
||||
if (!isNaN(px) && px !== 12) marker.px = px;
|
||||
if (!isNaN(dx) && dx !== 50) marker.dx = dx;
|
||||
if (!isNaN(dy) && dy !== 50) marker.dy = dy;
|
||||
if (fill && fill !== "#ffffff") marker.fill = fill;
|
||||
if (stroke && stroke !== "#000000") marker.stroke = stroke;
|
||||
|
||||
return marker;
|
||||
});
|
||||
|
||||
markersGroup.style.display = null;
|
||||
defs.remove();
|
||||
markerElements.forEach(el => el.remove());
|
||||
if (layerIsOn("markers")) drawMarkers();
|
||||
}
|
||||
})();
|
||||
|
||||
void (function checkDataIntegrity() {
|
||||
|
|
@ -975,7 +1042,7 @@ function parseLoadedData(data) {
|
|||
},
|
||||
"New map": function () {
|
||||
$(this).dialog("close");
|
||||
regenerateMap();
|
||||
regenerateMap("loading error");
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
|
|
|
|||
|
|
@ -1,237 +1,809 @@
|
|||
"use strict";
|
||||
|
||||
window.Markers = (function () {
|
||||
const generate = (number = 1) => {
|
||||
if (number === 0) return;
|
||||
TIME && console.time("addMarkers");
|
||||
let config = [];
|
||||
let occupied = [];
|
||||
|
||||
addVolcanoes(number);
|
||||
addHotSprings(number);
|
||||
addMines(number);
|
||||
addBridges(number);
|
||||
addInns(number);
|
||||
addLighthouses(number);
|
||||
addWaterfalls(number);
|
||||
addBattlefields(number);
|
||||
addDungeons(number);
|
||||
function getDefaultConfig() {
|
||||
const culturesSet = document.getElementById("culturesSet").value;
|
||||
const isFantasy = culturesSet.includes("Fantasy");
|
||||
|
||||
TIME && console.timeEnd("addMarkers");
|
||||
return [
|
||||
{type: "volcanoes", icon: "🌋", multiplier: 1, fn: addVolcanoes},
|
||||
{type: "hot-springs", icon: "♨️", multiplier: 1, fn: addHotSprings},
|
||||
{type: "mines", icon: "⛏️", multiplier: 1, fn: addMines},
|
||||
{type: "bridges", icon: "🌉", multiplier: 1, fn: addBridges},
|
||||
{type: "inns", icon: "🍻", multiplier: 1, fn: addInns},
|
||||
{type: "lighthouses", icon: "🚨", multiplier: 1, fn: addLighthouses},
|
||||
{type: "waterfalls", icon: "⟱", multiplier: 1, fn: addWaterfalls},
|
||||
{type: "battlefields", icon: "⚔️", multiplier: 1, fn: addBattlefields},
|
||||
{type: "dungeons", icon: "🗝️", multiplier: 1, fn: addDungeons},
|
||||
{type: "lake-monsters", icon: "🐉", multiplier: 1, fn: addLakeMonsters},
|
||||
{type: "sea-monsters", icon: "🦑", multiplier: 1, fn: addSeaMonsters},
|
||||
{type: "hill-monsters", icon: "👹", multiplier: 1, fn: addHillMonsters},
|
||||
{type: "sacred-mountains", icon: "🗻", multiplier: 1, fn: addSacredMountains},
|
||||
{type: "sacred-forests", icon: "🌳", multiplier: 1, fn: addSacredForests},
|
||||
{type: "sacred-pineries", icon: "🌲", multiplier: 1, fn: addSacredPineries},
|
||||
{type: "sacred-palm-groves", icon: "🌴", multiplier: 1, fn: addSacredPalmGroves},
|
||||
{type: "brigands", icon: "💰", multiplier: 1, fn: addBrigands},
|
||||
{type: "pirates", icon: "🏴☠️", multiplier: 1, fn: addPirates},
|
||||
{type: "statues", icon: "🗿", multiplier: 1, fn: addStatues},
|
||||
{type: "ruines", icon: "🏺", multiplier: 1, fn: addRuines},
|
||||
{type: "portals", icon: "🌀", multiplier: +isFantasy, fn: addPortals}
|
||||
];
|
||||
}
|
||||
|
||||
const getConfig = () => config;
|
||||
|
||||
const setConfig = newConfig => {
|
||||
config = newConfig;
|
||||
};
|
||||
|
||||
function addVolcanoes(number) {
|
||||
const generate = function () {
|
||||
setConfig(getDefaultConfig());
|
||||
pack.markers = [];
|
||||
generateTypes();
|
||||
};
|
||||
|
||||
const regenerate = () => {
|
||||
pack.markers = pack.markers.filter(({i, lock}) => {
|
||||
if (lock) return true;
|
||||
|
||||
const id = `marker${i}`;
|
||||
document.getElementById(id)?.remove();
|
||||
const index = notes.findIndex(note => note.id === id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
return false;
|
||||
});
|
||||
|
||||
generateTypes();
|
||||
};
|
||||
|
||||
function generateTypes() {
|
||||
TIME && console.time("addMarkers");
|
||||
|
||||
config.forEach(({type, icon, multiplier, fn}) => {
|
||||
if (multiplier === 0) return;
|
||||
fn(type, icon, multiplier);
|
||||
});
|
||||
|
||||
occupied = [];
|
||||
TIME && console.timeEnd("addMarkers");
|
||||
}
|
||||
|
||||
function getQuantity(array, min, each, multiplier) {
|
||||
if (!array.length || array.length < min / multiplier) return 0;
|
||||
const requestQty = Math.ceil((array.length / each) * multiplier);
|
||||
return array.length < requestQty ? array.length : requestQty;
|
||||
}
|
||||
|
||||
function extractAnyElement(array) {
|
||||
const index = Math.floor(Math.random() * array.length);
|
||||
return array.splice(index, 1);
|
||||
}
|
||||
|
||||
function getMarkerCoordinates(cell) {
|
||||
const {cells, burgs} = pack;
|
||||
const burgId = cells.burg[cell];
|
||||
|
||||
if (burgId) {
|
||||
const {x, y} = burgs[burgId];
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
return cells.p[cell];
|
||||
}
|
||||
|
||||
function addMarker({cell, type, icon, dx, dy, px}) {
|
||||
const i = pack.markers.length;
|
||||
const [x, y] = getMarkerCoordinates(cell);
|
||||
const marker = {i, icon, type, x, y, cell};
|
||||
if (dx) marker.dx = dx;
|
||||
if (dy) marker.dy = dy;
|
||||
if (px) marker.px = px;
|
||||
pack.markers.push(marker);
|
||||
occupied[cell] = true;
|
||||
return "marker" + i;
|
||||
}
|
||||
|
||||
function addVolcanoes(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let mounts = Array.from(cells.i.filter(i => cells.h[i] > 70).sort((a, b) => cells.h[b] - cells.h[a]));
|
||||
let count = mounts.length < 10 ? 0 : Math.ceil((mounts.length / 300) * number);
|
||||
if (count) addMarker("volcano", "🌋", 52, 50, 13);
|
||||
let mountains = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] >= 70).sort((a, b) => cells.h[b] - cells.h[a]));
|
||||
let quantity = getQuantity(mountains, 10, 300, multiplier);
|
||||
if (!quantity) return;
|
||||
const highestMountains = mountains.slice(0, 20);
|
||||
|
||||
while (count && mounts.length) {
|
||||
const cell = mounts.splice(biased(0, mounts.length - 1, 5), 1);
|
||||
const [x, y] = cells.p[cell];
|
||||
const id = appendMarker(cell, "volcano");
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(highestMountains);
|
||||
const id = addMarker({cell, icon, type, dx: 52, px: 13});
|
||||
const proper = Names.getCulture(cells.culture[cell]);
|
||||
const name = P(0.3) ? "Mount " + proper : Math.random() > 0.3 ? proper + " Volcano" : proper;
|
||||
notes.push({id, name, legend: `Active volcano. Height: ${getFriendlyHeight([x, y])}`});
|
||||
count--;
|
||||
notes.push({id, name, legend: `Active volcano. Height: ${getFriendlyHeight(cells.p[cell])}`});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addHotSprings(number) {
|
||||
function addHotSprings(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let springs = Array.from(cells.i.filter(i => cells.h[i] > 50).sort((a, b) => cells.h[b] - cells.h[a]));
|
||||
let count = springs.length < 30 ? 0 : Math.ceil((springs.length / 1000) * number);
|
||||
if (count) addMarker("hot_springs", "♨️", 50, 52, 12.5);
|
||||
let springs = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] > 50).sort((a, b) => cells.h[b] - cells.h[a]));
|
||||
let quantity = getQuantity(springs, 30, 800, multiplier);
|
||||
if (!quantity) return;
|
||||
const highestSprings = springs.slice(0, 40);
|
||||
|
||||
while (count && springs.length) {
|
||||
const cell = springs.splice(biased(1, springs.length - 1, 3), 1);
|
||||
const id = appendMarker(cell, "hot_springs");
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(highestSprings);
|
||||
const id = addMarker({cell, icon, type, dy: 52});
|
||||
const proper = Names.getCulture(cells.culture[cell]);
|
||||
const temp = convertTemperature(gauss(30, 15, 20, 100));
|
||||
notes.push({id, name: proper + " Hot Springs", legend: `A hot springs area. Temperature: ${temp}`});
|
||||
count--;
|
||||
const temp = convertTemperature(gauss(35, 15, 20, 100));
|
||||
notes.push({id, name: proper + " Hot Springs", legend: `A hot springs area. Average temperature: ${temp}`});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addMines(number) {
|
||||
function addMines(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let hills = Array.from(cells.i.filter(i => cells.h[i] > 47 && cells.burg[i]));
|
||||
let count = !hills.length ? 0 : Math.ceil((hills.length / 7) * number);
|
||||
if (!count) return;
|
||||
let hillyBurgs = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] > 47 && cells.burg[i]));
|
||||
let quantity = getQuantity(hillyBurgs, 1, 15, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
addMarker("mine", "⛏️", 48, 50, 13.5);
|
||||
const resources = {salt: 5, gold: 2, silver: 4, copper: 2, iron: 3, lead: 1, tin: 1};
|
||||
|
||||
while (count && hills.length) {
|
||||
const cell = hills.splice(Math.floor(Math.random() * hills.length), 1);
|
||||
const id = appendMarker(cell, "mine");
|
||||
while (quantity && hillyBurgs.length) {
|
||||
const [cell] = extractAnyElement(hillyBurgs);
|
||||
const id = addMarker({cell, icon, type, dx: 48, px: 13});
|
||||
const resource = rw(resources);
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const name = `${burg.name} — ${resource} mining town`;
|
||||
const population = rn(burg.population * populationRate * urbanization);
|
||||
const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`;
|
||||
notes.push({id, name, legend});
|
||||
count--;
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addBridges(number) {
|
||||
const {cells} = pack;
|
||||
function addBridges(type, icon, multiplier) {
|
||||
const {cells, burgs} = pack;
|
||||
|
||||
const meanRoad = d3.mean(cells.road.filter(r => r));
|
||||
const meanFlux = d3.mean(cells.fl.filter(fl => fl));
|
||||
let bridges = Array.from(
|
||||
cells.i.filter(i => !occupied[i] && cells.burg[i] && cells.t[i] !== 1 && burgs[cells.burg[i]].population > 20 && cells.r[i] && cells.fl[i] > meanFlux)
|
||||
);
|
||||
let quantity = getQuantity(bridges, 1, 5, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
let bridges = Array.from(cells.i.filter(i => cells.burg[i] && cells.h[i] >= 20 && cells.r[i] && cells.fl[i] > meanFlux && cells.road[i] > meanRoad).sort((a, b) => cells.road[b] + cells.fl[b] / 10 - (cells.road[a] + cells.fl[a] / 10)));
|
||||
let count = !bridges.length ? 0 : Math.ceil((bridges.length / 12) * number);
|
||||
if (count) addMarker("bridge", "🌉", 50, 50, 14);
|
||||
|
||||
while (count && bridges.length) {
|
||||
const cell = bridges.splice(0, 1);
|
||||
const id = appendMarker(cell, "bridge");
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(bridges);
|
||||
const id = addMarker({cell, icon, type, px: 14});
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
|
||||
const riverName = river ? `${river.name} ${river.type}` : "river";
|
||||
const name = river && P(0.2) ? river.name : burg.name;
|
||||
notes.push({id, name: `${name} Bridge`, legend: `A stone bridge over the ${riverName} near ${burg.name}`});
|
||||
count--;
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addInns(number) {
|
||||
function addInns(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
const maxRoad = d3.max(cells.road) * 0.9;
|
||||
let taverns = Array.from(cells.i.filter(i => cells.crossroad[i] && cells.h[i] >= 20 && cells.road[i] > maxRoad));
|
||||
if (!taverns.length) return;
|
||||
const count = Math.ceil(4 * number);
|
||||
addMarker("inn", "🍻", 50, 50, 14.5);
|
||||
let taverns = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.road[i] > 4 && cells.pop[i] > 10));
|
||||
let quantity = getQuantity(taverns, 1, 100, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const color = ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"];
|
||||
const animal = ["Antelope", "Ape", "Badger", "Bear", "Beaver", "Bison", "Boar", "Buffalo", "Cat", "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", "Wolf", "Wolverine", "Camel", "Falcon", "Hound", "Ox"];
|
||||
const adj = ["New", "Good", "High", "Old", "Great", "Big", "Major", "Happy", "Main", "Huge", "Far", "Beautiful", "Fair", "Prime", "Ancient", "Golden", "Proud", "Lucky", "Fat", "Honest", "Giant", "Distant", "Friendly", "Loud", "Hungry", "Magical", "Superior", "Peaceful", "Frozen", "Divine", "Favorable", "Brave", "Sunny", "Flying"];
|
||||
const colors = [
|
||||
"Dark",
|
||||
"Light",
|
||||
"Bright",
|
||||
"Golden",
|
||||
"White",
|
||||
"Black",
|
||||
"Red",
|
||||
"Pink",
|
||||
"Purple",
|
||||
"Blue",
|
||||
"Green",
|
||||
"Yellow",
|
||||
"Amber",
|
||||
"Orange",
|
||||
"Brown",
|
||||
"Grey"
|
||||
];
|
||||
const animals = [
|
||||
"Antelope",
|
||||
"Ape",
|
||||
"Badger",
|
||||
"Bear",
|
||||
"Beaver",
|
||||
"Bison",
|
||||
"Boar",
|
||||
"Buffalo",
|
||||
"Cat",
|
||||
"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",
|
||||
"Wolf",
|
||||
"Wolverine",
|
||||
"Camel",
|
||||
"Falcon",
|
||||
"Hound",
|
||||
"Ox"
|
||||
];
|
||||
const adjectives = [
|
||||
"New",
|
||||
"Good",
|
||||
"High",
|
||||
"Old",
|
||||
"Great",
|
||||
"Big",
|
||||
"Major",
|
||||
"Happy",
|
||||
"Main",
|
||||
"Huge",
|
||||
"Far",
|
||||
"Beautiful",
|
||||
"Fair",
|
||||
"Prime",
|
||||
"Ancient",
|
||||
"Golden",
|
||||
"Proud",
|
||||
"Lucky",
|
||||
"Fat",
|
||||
"Honest",
|
||||
"Giant",
|
||||
"Distant",
|
||||
"Friendly",
|
||||
"Loud",
|
||||
"Hungry",
|
||||
"Magical",
|
||||
"Superior",
|
||||
"Peaceful",
|
||||
"Frozen",
|
||||
"Divine",
|
||||
"Favorable",
|
||||
"Brave",
|
||||
"Sunny",
|
||||
"Flying"
|
||||
];
|
||||
const methods = [
|
||||
"Boiled",
|
||||
"Grilled",
|
||||
"Roasted",
|
||||
"Spit-roasted",
|
||||
"Stewed",
|
||||
"Stuffed",
|
||||
"Jugged",
|
||||
"Mashed",
|
||||
"Baked",
|
||||
"Braised",
|
||||
"Poached",
|
||||
"Marinated",
|
||||
"Pickled",
|
||||
"Smoked",
|
||||
"Dried",
|
||||
"Dry-aged",
|
||||
"Corned",
|
||||
"Fried",
|
||||
"Pan-fried",
|
||||
"Deep-fried",
|
||||
"Dressed",
|
||||
"Steamed",
|
||||
"Cured",
|
||||
"Syrupped",
|
||||
"Flame-Broiled"
|
||||
];
|
||||
const courses = [
|
||||
"beef",
|
||||
"pork",
|
||||
"bacon",
|
||||
"chicken",
|
||||
"lamb",
|
||||
"chevon",
|
||||
"hare",
|
||||
"rabbit",
|
||||
"hart",
|
||||
"deer",
|
||||
"antlers",
|
||||
"bear",
|
||||
"buffalo",
|
||||
"badger",
|
||||
"beaver",
|
||||
"turkey",
|
||||
"pheasant",
|
||||
"duck",
|
||||
"goose",
|
||||
"teal",
|
||||
"quail",
|
||||
"pigeon",
|
||||
"seal",
|
||||
"carp",
|
||||
"bass",
|
||||
"pike",
|
||||
"catfish",
|
||||
"sturgeon",
|
||||
"escallop",
|
||||
"pie",
|
||||
"cake",
|
||||
"pottage",
|
||||
"pudding",
|
||||
"onions",
|
||||
"carrot",
|
||||
"potato",
|
||||
"beet",
|
||||
"garlic",
|
||||
"cabbage",
|
||||
"eggplant",
|
||||
"eggs",
|
||||
"broccoli",
|
||||
"zucchini",
|
||||
"pepper",
|
||||
"olives",
|
||||
"pumpkin",
|
||||
"spinach",
|
||||
"peas",
|
||||
"chickpea",
|
||||
"beans",
|
||||
"rice",
|
||||
"pasta",
|
||||
"bread",
|
||||
"apples",
|
||||
"peaches",
|
||||
"pears",
|
||||
"melon",
|
||||
"oranges",
|
||||
"mango",
|
||||
"tomatoes",
|
||||
"cheese",
|
||||
"corn",
|
||||
"rat tails",
|
||||
"pig ears"
|
||||
];
|
||||
const types = ["hot", "cold", "fire", "ice", "smoky", "misty", "shiny", "sweet", "bitter", "salty", "sour", "sparkling", "smelly"];
|
||||
const drinks = [
|
||||
"wine",
|
||||
"brandy",
|
||||
"jinn",
|
||||
"whisky",
|
||||
"rom",
|
||||
"beer",
|
||||
"cider",
|
||||
"mead",
|
||||
"liquor",
|
||||
"spirit",
|
||||
"vodka",
|
||||
"tequila",
|
||||
"absinthe",
|
||||
"nectar",
|
||||
"milk",
|
||||
"kvass",
|
||||
"kumis",
|
||||
"tea",
|
||||
"water",
|
||||
"juice",
|
||||
"sap"
|
||||
];
|
||||
|
||||
for (let i = 0; i < taverns.length && i < count; i++) {
|
||||
const cell = taverns.splice(Math.floor(Math.random() * taverns.length), 1);
|
||||
const id = appendMarker(cell, "inn");
|
||||
const type = P(0.3) ? "inn" : "tavern";
|
||||
const name = P(0.5) ? ra(color) + " " + ra(animal) : P(0.6) ? ra(adj) + " " + ra(animal) : ra(adj) + " " + capitalize(type);
|
||||
notes.push({id, name: "The " + name, legend: `A big and famous roadside ${type}`});
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(taverns);
|
||||
const id = addMarker({cell, icon, type, px: 14});
|
||||
const typeName = P(0.3) ? "inn" : "tavern";
|
||||
const isAnimalThemed = P(0.7);
|
||||
const animal = ra(animals);
|
||||
const name = isAnimalThemed ? (P(0.6) ? ra(colors) + " " + animal : ra(adjectives) + " " + animal) : ra(adjectives) + " " + capitalize(type);
|
||||
const meal = isAnimalThemed && P(0.3) ? animal : ra(courses);
|
||||
const course = `${ra(methods)} ${meal}`.toLowerCase();
|
||||
const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra(drinks)}`.toLowerCase();
|
||||
const legend = `A big and famous roadside ${typeName}. Delicious ${course} with ${drink} is served here`;
|
||||
notes.push({id, name: "The " + name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addLighthouses(number) {
|
||||
function addLighthouses(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
const lighthouses = Array.from(cells.i.filter(i => cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && cells.road[c])));
|
||||
if (lighthouses.length) addMarker("lighthouse", "🚨", 50, 50, 16);
|
||||
const count = Math.ceil(4 * number);
|
||||
const lighthouses = Array.from(cells.i.filter(i => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && cells.road[c])));
|
||||
let quantity = getQuantity(lighthouses, 1, 2, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
for (let i = 0; i < lighthouses.length && i < count; i++) {
|
||||
const cell = lighthouses.splice(Math.floor(Math.random() * lighthouses.length), 1);
|
||||
const id = appendMarker(cell, "lighthouse");
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(lighthouses);
|
||||
const id = addMarker({cell, icon, type, px: 14});
|
||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||
notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend: `A lighthouse to keep the navigation safe`});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addWaterfalls(number) {
|
||||
function addWaterfalls(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
const waterfalls = Array.from(cells.i.filter(i => cells.r[i] && cells.h[i] > 70));
|
||||
if (waterfalls.length) addMarker("waterfall", "⟱", 50, 54, 16.5);
|
||||
const count = Math.ceil(3 * number);
|
||||
const waterfalls = Array.from(cells.i.filter(i => cells.r[i] && !occupied[i] && cells.h[i] >= 50 && cells.c[i].some(c => cells.h[c] < 40 && cells.r[c])));
|
||||
const quantity = getQuantity(waterfalls, 1, 5, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
for (let i = 0; i < waterfalls.length && i < count; i++) {
|
||||
for (let i = 0; i < waterfalls.length && i < quantity; i++) {
|
||||
const cell = waterfalls[i];
|
||||
const id = appendMarker(cell, "waterfall");
|
||||
const id = addMarker({cell, icon, type, dy: 54, px: 16});
|
||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||
notes.push({id, name: getAdjective(proper) + " Waterfall" + name, legend: `An extremely beautiful waterfall`});
|
||||
}
|
||||
}
|
||||
|
||||
function addBattlefields(number) {
|
||||
function addBattlefields(type, icon, multiplier) {
|
||||
const {cells, states} = pack;
|
||||
|
||||
let battlefields = Array.from(cells.i.filter(i => cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25));
|
||||
let count = battlefields.length < 100 ? 0 : Math.ceil((battlefields.length / 500) * number);
|
||||
if (count) addMarker("battlefield", "⚔️", 50, 52, 12);
|
||||
let battlefields = Array.from(cells.i.filter(i => !occupied[i] && cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25));
|
||||
let quantity = getQuantity(battlefields, 50, 700, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (count && battlefields.length) {
|
||||
const cell = battlefields.splice(Math.floor(Math.random() * battlefields.length), 1);
|
||||
const id = appendMarker(cell, "battlefield");
|
||||
while (quantity && battlefields.length) {
|
||||
const [cell] = extractAnyElement(battlefields);
|
||||
const id = addMarker({cell, icon, type, dy: 52});
|
||||
const campaign = ra(states[cells.state[cell]].campaigns);
|
||||
const date = generateDate(campaign.start, campaign.end);
|
||||
const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
|
||||
const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`;
|
||||
notes.push({id, name, legend});
|
||||
count--;
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addDungeons(number) {
|
||||
function addDungeons(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let dungeons = Array.from(cells.i.filter(i => cells.pop[i] > 0));
|
||||
let count = dungeons.length < 100 ? 0 : Math.ceil((dungeons.length / 1000) * number);
|
||||
if (count) addMarker("dungeon", "🗝️", 50, 52, 12);
|
||||
let dungeons = Array.from(cells.i.filter(i => !occupied[i] && cells.pop[i] && cells.pop[i] < 3));
|
||||
let quantity = getQuantity(dungeons, 30, 200, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (count && dungeons.length) {
|
||||
const cell = dungeons.splice(Math.floor(Math.random() * dungeons.length), 1);
|
||||
const id = appendMarker(cell, "dungeon");
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(dungeons);
|
||||
const id = addMarker({cell, icon, type, dy: 51, px: 13});
|
||||
|
||||
const dungeonSeed = `${seed}${cell}`;
|
||||
const name = "Dungeon";
|
||||
const legend = `<div>Undiscovered dungeon. See <a href="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" target="_blank">One page dungeon</a>.</div><iframe src="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" frameborder="0"></iframe>`;
|
||||
const legend = `<div>Undiscovered dungeon. See <a href="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" target="_blank">One page dungeon</a></div><iframe style="height: 33vh" src="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" sandbox="allow-scripts allow-same-origin"></iframe>`;
|
||||
notes.push({id, name, legend});
|
||||
count--;
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addMarker(id, icon, x, y, size) {
|
||||
const markers = svg.select("#defs-markers");
|
||||
if (markers.select("#marker_" + id).size()) return;
|
||||
function addLakeMonsters(type, icon, multiplier) {
|
||||
const {features} = pack;
|
||||
|
||||
const symbol = markers
|
||||
.append("symbol")
|
||||
.attr("id", "marker_" + id)
|
||||
.attr("viewBox", "0 0 30 30");
|
||||
symbol.append("path").attr("d", "M6,19 l9,10 L24,19").attr("fill", "#000000").attr("stroke", "none");
|
||||
symbol.append("circle").attr("cx", 15).attr("cy", 15).attr("r", 10).attr("fill", "#ffffff").attr("stroke", "#000000").attr("stroke-width", 1);
|
||||
symbol
|
||||
.append("text")
|
||||
.attr("x", x + "%")
|
||||
.attr("y", y + "%")
|
||||
.attr("fill", "#000000")
|
||||
.attr("stroke", "#3200ff")
|
||||
.attr("stroke-width", 0)
|
||||
.attr("font-size", size + "px")
|
||||
.attr("dominant-baseline", "central")
|
||||
.text(icon);
|
||||
const lakes = features.filter(feature => feature.type === "lake" && feature.group === "freshwater" && !occupied[feature.firstCell]);
|
||||
let quantity = getQuantity(lakes, 2, 10, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [lake] = extractAnyElement(lakes);
|
||||
const cell = lake.firstCell;
|
||||
const id = addMarker({cell, icon, type, dy: 48});
|
||||
const name = `${lake.name} Monster`;
|
||||
const length = gauss(10, 5, 5, 100);
|
||||
const legend = `Rumors said a relic monster of ${length} ${heightUnit.value} long inhabits ${lake.name} Lake. Truth or lie, but folks are affraid to fish in the lake`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function appendMarker(cell, type) {
|
||||
function addSeaMonsters(type, icon, multiplier) {
|
||||
const {cells, features} = pack;
|
||||
|
||||
const sea = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i] && features[cells.f[i]].type === "ocean"));
|
||||
let quantity = getQuantity(sea, 50, 700, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(sea);
|
||||
const id = addMarker({cell, icon, type});
|
||||
const name = `${Names.getCultureShort(0)} Monster`;
|
||||
const length = gauss(25, 10, 10, 100);
|
||||
const legend = `Old sailors tell stories of a gigantic sea monster inhabiting these dangerous waters. Rumors say it can be ${length} ${heightUnit.value} long`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addHillMonsters(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
const [x, y] = cells.p[cell];
|
||||
const id = getNextId("markerElement");
|
||||
const name = "#marker_" + type;
|
||||
const hills = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]));
|
||||
let quantity = getQuantity(hills, 30, 600, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
markers
|
||||
.append("use")
|
||||
.attr("id", id)
|
||||
.attr("xlink:href", name)
|
||||
.attr("data-id", name)
|
||||
.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);
|
||||
const subjects = ["Locals", "Old folks", "Old books", "Tipplers"];
|
||||
const species = ["Ogre", "Troll", "Cyclops", "Giant", "Monster", "Beast", "Dragon", "Undead", "Ghoul", "Vampire"];
|
||||
const modusOperandi = [
|
||||
"steals their cattle",
|
||||
"doesn't mind eating children",
|
||||
"doesn't mind of human flesh",
|
||||
"keeps the region at bay",
|
||||
"eats their kids",
|
||||
"abducts young women"
|
||||
];
|
||||
|
||||
return id;
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(hills);
|
||||
const id = addMarker({cell, icon, type, dy: 54, px: 13});
|
||||
const monster = ra(species);
|
||||
const toponym = Names.getCulture(cells.culture[cell]);
|
||||
const name = `${toponym} ${monster}`;
|
||||
const legend = `${ra(subjects)} tell tales of an old ${monster} who inhabits ${toponym} hills and ${ra(modusOperandi)}`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
return {generate};
|
||||
function addSacredMountains(type, icon, multiplier) {
|
||||
const {cells, cultures} = pack;
|
||||
|
||||
let lonelyMountains = Array.from(
|
||||
cells.i.filter(i => !occupied[i] && cells.h[i] >= 70 && cells.c[i].some(c => cells.culture[c]) && cells.c[i].every(c => cells.h[c] < 60))
|
||||
);
|
||||
let quantity = getQuantity(lonelyMountains, 1, 5, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(lonelyMountains);
|
||||
const id = addMarker({cell, icon, type, dy: 48});
|
||||
const culture = cells.c[cell].map(c => cells.culture[c]).find(c => c);
|
||||
const name = `${Names.getCulture(culture)} Mountain`;
|
||||
const height = getFriendlyHeight(cells.p[cell]);
|
||||
const legend = `A sacred mountain of ${cultures[culture].name} culture. Height: ${height}`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addSacredForests(type, icon, multiplier) {
|
||||
const {cells, cultures} = pack;
|
||||
|
||||
let temperateForests = Array.from(cells.i.filter(i => !occupied[i] && cells.culture[i] && [6, 8].includes(cells.biome[i])));
|
||||
let quantity = getQuantity(temperateForests, 30, 1000, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(temperateForests);
|
||||
const id = addMarker({cell, icon, type});
|
||||
const culture = cells.culture[cell];
|
||||
const name = `${Names.getCulture(culture)} Forest`;
|
||||
const legend = `A sacred forest of ${cultures[culture].name} culture`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addSacredPineries(type, icon, multiplier) {
|
||||
const {cells, cultures} = pack;
|
||||
|
||||
let borealForests = Array.from(cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.biome[i] === 9));
|
||||
let quantity = getQuantity(borealForests, 30, 800, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(borealForests);
|
||||
const id = addMarker({cell, icon, type, px: 13});
|
||||
const culture = cells.culture[cell];
|
||||
const name = `${Names.getCulture(culture)} Pinery`;
|
||||
const legend = `A sacred pinery of ${cultures[culture].name} culture`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addSacredPalmGroves(type, icon, multiplier) {
|
||||
const {cells, cultures} = pack;
|
||||
|
||||
let oasises = Array.from(cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.biome[i] === 1 && cells.pop[i] > 1 && cells.road[i]));
|
||||
let quantity = getQuantity(oasises, 1, 100, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(oasises);
|
||||
const id = addMarker({cell, icon, type, px: 13});
|
||||
const culture = cells.culture[cell];
|
||||
const name = `${Names.getCulture(culture)} Palm Grove`;
|
||||
const legend = `A sacred palm grove of ${cultures[culture].name} culture`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addBrigands(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let roads = Array.from(cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.road[i] > 4));
|
||||
let quantity = getQuantity(roads, 50, 100, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const animals = [
|
||||
"Apes",
|
||||
"Badgers",
|
||||
"Bears",
|
||||
"Beavers",
|
||||
"Bisons",
|
||||
"Boars",
|
||||
"Cats",
|
||||
"Crows",
|
||||
"Dogs",
|
||||
"Foxes",
|
||||
"Hares",
|
||||
"Hawks",
|
||||
"Hyenas",
|
||||
"Jackals",
|
||||
"Jaguars",
|
||||
"Leopards",
|
||||
"Lions",
|
||||
"Owls",
|
||||
"Panthers",
|
||||
"Rats",
|
||||
"Ravens",
|
||||
"Rooks",
|
||||
"Scorpions",
|
||||
"Sharks",
|
||||
"Snakes",
|
||||
"Spiders",
|
||||
"Tigers",
|
||||
"Wolfs",
|
||||
"Wolverines",
|
||||
"Falcons"
|
||||
];
|
||||
const types = {brigands: 4, bandits: 3, robbers: 1, highwaymen: 1};
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(roads);
|
||||
const id = addMarker({cell, icon, type, px: 13});
|
||||
const culture = cells.culture[cell];
|
||||
const biome = cells.biome[cell];
|
||||
const height = cells.p[cell];
|
||||
const locality =
|
||||
height >= 70
|
||||
? "highlander"
|
||||
: [1, 2].includes(biome)
|
||||
? "desert"
|
||||
: [3, 4].includes(biome)
|
||||
? "mounted"
|
||||
: [5, 6, 7, 8, 9].includes(biome)
|
||||
? "forest"
|
||||
: biome === 12
|
||||
? "swamp"
|
||||
: "angry";
|
||||
const name = `${Names.getCulture(culture)} ${ra(animals)}`;
|
||||
const legend = `A gang of ${locality} ${rw(types)}`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addPirates(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let searoutes = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && cells.road[i]));
|
||||
let quantity = getQuantity(searoutes, 40, 300, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(searoutes);
|
||||
const id = addMarker({cell, icon, type, dx: 51});
|
||||
const name = `Pirates`;
|
||||
const legend = `Pirate ships have been spotted in these waters`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addStatues(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
let statues = Array.from(cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.h[i] < 40));
|
||||
let quantity = getQuantity(statues, 80, 1200, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const variants = ["Statue", "Obelisk", "Monument", "Column", "Monolith", "Pillar", "Megalith", "Stele", "Runestone"];
|
||||
const scripts = {
|
||||
cypriot: "𐠁𐠂𐠃𐠄𐠅𐠈𐠊𐠋𐠌𐠍𐠎𐠏𐠐𐠑𐠒𐠓𐠔𐠕𐠖𐠗𐠘𐠙𐠚𐠛𐠜𐠝𐠞𐠟𐠠𐠡𐠢𐠣𐠤𐠥𐠦𐠧𐠨𐠩𐠪𐠫𐠬𐠭𐠮𐠯𐠰𐠱𐠲𐠳𐠴𐠵𐠷𐠸𐠼𐠿 ",
|
||||
geez: "ሀለሐመሠረሰቀበተኀነአከወዐዘየደገጠጰጸፀፈፐ ",
|
||||
coptic: "ⲲⲴⲶⲸⲺⲼⲾⳀⳁⳂⳃⳄⳆⳈⳊⳌⳎⳐⳒⳔⳖⳘⳚⳜⳞⳠⳢⳤ⳥⳧⳩⳪ⳫⳬⳭⳲ⳹⳾ ",
|
||||
tibetan: "ༀ༁༂༃༄༅༆༇༈༉༊་༌༐༑༒༓༔༕༖༗༘༙༚༛༜༠༡༢༣༤༥༦༧༨༩༪༫༬༭༮༯༰༱༲༳༴༵༶༷༸༹༺༻༼༽༾༿",
|
||||
mongolian: "᠀᠐᠑᠒ᠠᠡᠦᠧᠨᠩᠪᠭᠮᠯᠰᠱᠲᠳᠵᠻᠼᠽᠾᠿᡀᡁᡆᡍᡎᡏᡐᡑᡒᡓᡔᡕᡖᡗᡙᡜᡝᡞᡟᡠᡡᡭᡮᡯᡰᡱᡲᡳᡴᢀᢁᢂᢋᢏᢐᢑᢒᢓᢛᢜᢞᢟᢠᢡᢢᢤᢥᢦ"
|
||||
};
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(statues);
|
||||
const id = addMarker({cell, icon, type});
|
||||
const culture = cells.culture[cell];
|
||||
|
||||
const variant = ra(variants);
|
||||
const name = `${Names.getCulture(culture)} ${variant}`;
|
||||
const script = scripts[ra(Object.keys(scripts))];
|
||||
const inscription = Array(rand(40, 100))
|
||||
.fill(null)
|
||||
.map(() => ra(script))
|
||||
.join("");
|
||||
const legend = `An ancient ${variant.toLowerCase()}. It has an inscription, but no one can translate it:
|
||||
<div style="font-size: 1.8em; line-break: anywhere;">${inscription}</div>`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addRuines(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
let ruins = Array.from(cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && cells.h[i] < 60));
|
||||
let quantity = getQuantity(ruins, 80, 1200, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const types = ["City", "Town", "Pyramid", "Fort"];
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(ruins);
|
||||
const id = addMarker({cell, icon, type});
|
||||
|
||||
const ruinType = ra(types);
|
||||
const name = `Ruined ${ruinType}`;
|
||||
const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. A good place for a treasures hunt`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addPortals(type, icon, multiplier) {
|
||||
const {burgs} = pack;
|
||||
let portals = burgs
|
||||
.slice(1, Math.ceil(burgs.length / 10) + 1)
|
||||
.filter(({cell}) => !occupied[cell])
|
||||
.map(burg => [burg.name, burg.cell]);
|
||||
let quantity = getQuantity(portals, 16, 8, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [portal] = extractAnyElement(portals);
|
||||
const [burgName, cell] = portal;
|
||||
const id = addMarker({cell, icon, type, px: 14});
|
||||
const name = `${burgName} Portal`;
|
||||
const legend = `An element of the magic portal system connecting major cities. Portals installed centuries ago, but still work fine`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
return {generate, regenerate, getConfig, setConfig};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ window.Names = (function () {
|
|||
{name: "English", i: 1, min: 6, max: 11, d: "", m: .1, b: "Abingdon,Albrighton,Alcester,Almondbury,Altrincham,Amersham,Andover,Appleby,Ashboume,Atherstone,Aveton,Axbridge,Aylesbury,Baldock,Bamburgh,Barton,Basingstoke,Berden,Bere,Berkeley,Berwick,Betley,Bideford,Bingley,Birmingham,Blandford,Blechingley,Bodmin,Bolton,Bootham,Boroughbridge,Boscastle,Bossinney,Bramber,Brampton,Brasted,Bretford,Bridgetown,Bridlington,Bromyard,Bruton,Buckingham,Bungay,Burton,Calne,Cambridge,Canterbury,Carlisle,Castleton,Caus,Charmouth,Chawleigh,Chichester,Chillington,Chinnor,Chipping,Chisbury,Cleobury,Clifford,Clifton,Clitheroe,Cockermouth,Coleshill,Combe,Congleton,Crafthole,Crediton,Cuddenbeck,Dalton,Darlington,Dodbrooke,Drax,Dudley,Dunstable,Dunster,Dunwich,Durham,Dymock,Exeter,Exning,Faringdon,Felton,Fenny,Finedon,Flookburgh,Fowey,Frampton,Gateshead,Gatton,Godmanchester,Grampound,Grantham,Guildford,Halesowen,Halton,Harbottle,Harlow,Hatfield,Hatherleigh,Haydon,Helston,Henley,Hertford,Heytesbury,Hinckley,Hitchin,Holme,Hornby,Horsham,Kendal,Kenilworth,Kilkhampton,Kineton,Kington,Kinver,Kirby,Knaresborough,Knutsford,Launceston,Leighton,Lewes,Linton,Louth,Luton,Lyme,Lympstone,Macclesfield,Madeley,Malborough,Maldon,Manchester,Manningtree,Marazion,Marlborough,Marshfield,Mere,Merryfield,Middlewich,Midhurst,Milborne,Mitford,Modbury,Montacute,Mousehole,Newbiggin,Newborough,Newbury,Newenden,Newent,Norham,Northleach,Noss,Oakham,Olney,Orford,Ormskirk,Oswestry,Padstow,Paignton,Penkneth,Penrith,Penzance,Pershore,Petersfield,Pevensey,Pickering,Pilton,Pontefract,Portsmouth,Preston,Quatford,Reading,Redcliff,Retford,Rockingham,Romney,Rothbury,Rothwell,Salisbury,Saltash,Seaford,Seasalter,Sherston,Shifnal,Shoreham,Sidmouth,Skipsea,Skipton,Solihull,Somerton,Southam,Southwark,Standon,Stansted,Stapleton,Stottesdon,Sudbury,Swavesey,Tamerton,Tarporley,Tetbury,Thatcham,Thaxted,Thetford,Thornbury,Tintagel,Tiverton,Torksey,Totnes,Towcester,Tregoney,Trematon,Tutbury,Uxbridge,Wallingford,Wareham,Warenmouth,Wargrave,Warton,Watchet,Watford,Wendover,Westbury,Westcheap,Weymouth,Whitford,Wickwar,Wigan,Wigmore,Winchelsea,Winkleigh,Wiscombe,Witham,Witheridge,Wiveliscombe,Woodbury,Yeovil"},
|
||||
{name: "French", i: 2, min: 5, max: 13, d: "nlrs", m: .1, b: "Adon,Aillant,Amilly,Andonville,Ardon,Artenay,Ascheres,Ascoux,Attray,Aubin,Audeville,Aulnay,Autruy,Auvilliers,Auxy,Aveyron,Baccon,Bardon,Barville,Batilly,Baule,Bazoches,Beauchamps,Beaugency,Beaulieu,Beaune,Bellegarde,Boesses,Boigny,Boiscommun,Boismorand,Boisseaux,Bondaroy,Bonnee,Bonny,Bordes,Bou,Bougy,Bouilly,Boulay,Bouzonville,Bouzy,Boynes,Bray,Breteau,Briare,Briarres,Bricy,Bromeilles,Bucy,Cepoy,Cercottes,Cerdon,Cernoy,Cesarville,Chailly,Chaingy,Chalette,Chambon,Champoulet,Chanteau,Chantecoq,Chapell,Charme,Charmont,Charsonville,Chateau,Chateauneuf,Chatel,Chatenoy,Chatillon,Chaussy,Checy,Chevannes,Chevillon,Chevilly,Chevry,Chilleurs,Choux,Chuelles,Clery,Coinces,Coligny,Combleux,Combreux,Conflans,Corbeilles,Corquilleroy,Cortrat,Coudroy,Coullons,Coulmiers,Courcelles,Courcy,Courtemaux,Courtempierre,Courtenay,Cravant,Crottes,Dadonville,Dammarie,Dampierre,Darvoy,Desmonts,Dimancheville,Donnery,Dordives,Dossainville,Douchy,Dry,Echilleuses,Egry,Engenville,Epieds,Erceville,Ervauville,Escrennes,Escrignelles,Estouy,Faverelles,Fay,Feins,Ferolles,Ferrieres,Fleury,Fontenay,Foret,Foucherolles,Freville,Gatinais,Gaubertin,Gemigny,Germigny,Gidy,Gien,Girolles,Givraines,Gondreville,Grangermont,Greneville,Griselles,Guigneville,Guilly,Gyleslonains,Huetre,Huisseau,Ingrannes,Ingre,Intville,Isdes,Jargeau,Jouy,Juranville,Bussiere,Laas,Ladon,Lailly,Langesse,Leouville,Ligny,Lombreuil,Lorcy,Lorris,Loury,Louzouer,Malesherbois,Marcilly,Mardie,Mareau,Marigny,Marsainvilliers,Melleroy,Menestreau,Merinville,Messas,Meung,Mezieres,Migneres,Mignerette,Mirabeau,Montargis,Montbarrois,Montbouy,Montcresson,Montereau,Montigny,Montliard,Mormant,Morville,Moulinet,Moulon,Nancray,Nargis,Nesploy,Neuville,Neuvy,Nevoy,Nibelle,Nogent,Noyers,Ocre,Oison,Olivet,Ondreville,Onzerain,Orleans,Ormes,Orville,Oussoy,Outarville,Ouzouer,Pannecieres,Pannes,Patay,Paucourt,Pers,Pierrefitte,Pithiverais,Pithiviers,Poilly,Potier,Prefontaines,Presnoy,Pressigny,Puiseaux,Quiers,Ramoulu,Rebrechien,Rouvray,Rozieres,Rozoy,Ruan,Sandillon,Santeau,Saran,Sceaux,Seichebrieres,Semoy,Sennely,Sermaises,Sigloy,Solterre,Sougy,Sully,Sury,Tavers,Thignonville,Thimory,Thorailles,Thou,Tigy,Tivernon,Tournoisis,Trainou,Treilles,Trigueres,Trinay,Vannes,Varennes,Vennecy,Vieilles,Vienne,Viglain,Vignes,Villamblain,Villemandeur,Villemoutiers,Villemurlin,Villeneuve,Villereau,Villevoques,Villorceau,Vimory,Vitry,Vrigny,Ivre"},
|
||||
{name: "Italian", i: 3, min: 5, max: 12, d: "cltr", m: .1, b: "Accumoli,Acquafondata,Acquapendente,Acuto,Affile,Agosta,Alatri,Albano,Allumiere,Alvito,Amaseno,Amatrice,Anagni,Anguillara,Anticoli,Antrodoco,Anzio,Aprilia,Aquino,Arce,Arcinazzo,Ardea,Ariccia,Arlena,Arnara,Arpino,Arsoli,Artena,Ascrea,Atina,Ausonia,Bagnoregio,Barbarano,Bassano,Bassiano,Bellegra,Belmonte,Blera,Bolsena,Bomarzo,Borbona,Borgo,Borgorose,Boville,Bracciano,Broccostella,Calcata,Camerata,Campagnano,Campodimele,Campoli,Canale,Canepina,Canino,Cantalice,Cantalupo,Canterano,Capena,Capodimonte,Capranica,Caprarola,Carbognano,Casalattico,Casalvieri,Casape,Casaprota,Casperia,Cassino,Castelforte,Castelliri,Castello,Castelnuovo,Castiglione,Castro,Castrocielo,Cave,Ceccano,Celleno,Cellere,Ceprano,Cerreto,Cervara,Cervaro,Cerveteri,Ciampino,Ciciliano,Cineto,Cisterna,Cittaducale,Cittareale,Civita,Civitavecchia,Civitella,Colfelice,Collalto,Colle,Colleferro,Collegiove,Collepardo,Collevecchio,Colli,Colonna,Concerviano,Configni,Contigliano,Corchiano,Coreno,Cori,Cottanello,Esperia,Fabrica,Faleria,Fara,Farnese,Ferentino,Fiamignano,Fiano,Filacciano,Filettino,Fiuggi,Fiumicino,Fondi,Fontana,Fonte,Fontechiari,Forano,Formello,Formia,Frascati,Frasso,Frosinone,Fumone,Gaeta,Gallese,Gallicano,Gallinaro,Gavignano,Genazzano,Genzano,Gerano,Giuliano,Gorga,Gradoli,Graffignano,Greccio,Grottaferrata,Grotte,Guarcino,Guidonia,Ischia,Isola,Itri,Jenne,Labico,Labro,Ladispoli,Lanuvio,Lariano,Latera,Lenola,Leonessa,Licenza,Longone,Lubriano,Maenza,Magliano,Mandela,Manziana,Marano,Marcellina,Marcetelli,Marino,Marta,Mazzano,Mentana,Micigliano,Minturno,Mompeo,Montalto,Montasola,Monte,Montebuono,Montefiascone,Monteflavio,Montelanico,Monteleone,Montelibretti,Montenero,Monterosi,Monterotondo,Montopoli,Montorio,Moricone,Morlupo,Morolo,Morro,Nazzano,Nemi,Nepi,Nerola,Nespolo,Nettuno,Norma,Olevano,Onano,Oriolo,Orte,Orvinio,Paganico,Palestrina,Paliano,Palombara,Pastena,Patrica,Percile,Pescorocchiano,Pescosolido,Petrella,Piansano,Picinisco,Pico,Piedimonte,Piglio,Pignataro,Pisoniano,Pofi,Poggio,Poli,Pomezia,Pontecorvo,Pontinia,Ponza,Ponzano,Posta,Pozzaglia,Priverno,Proceno,Prossedi,Riano,Rieti,Rignano,Riofreddo,Ripi,Rivodutri,Rocca,Roccagiovine,Roccagorga,Roccantica,Roccasecca,Roiate,Ronciglione,Roviano,Sabaudia,Sacrofano,Salisano,Sambuci,Santa,Santi,Santopadre,Saracinesco,Scandriglia,Segni,Selci,Sermoneta,Serrone,Settefrati,Sezze,Sgurgola,Sonnino,Sora,Soriano,Sperlonga,Spigno,Stimigliano,Strangolagalli,Subiaco,Supino,Sutri,Tarano,Tarquinia,Terelle,Terracina,Tessennano,Tivoli,Toffia,Tolfa,Torre,Torri,Torrice,Torricella,Torrita,Trevi,Trevignano,Trivigliano,Turania,Tuscania,Vacone,Valentano,Vallecorsa,Vallemaio,Vallepietra,Vallerano,Vallerotonda,Vallinfreda,Valmontone,Varco,Vasanello,Vejano,Velletri,Ventotene,Veroli,Vetralla,Vicalvi,Vico,Vicovaro,Vignanello,Viterbo,Viticuso,Vitorchiano,Vivaro,Zagarolo"},
|
||||
{name: "Castillian", i: 4, min: 5, max: 11, d: "lr", m: 0, b: "Abanades,Ablanque,Adobes,Ajofrin,Alameda,Alaminos,Alarilla,Albalate,Albares,Albarreal,Albendiego,Alcabon,Alcanizo,Alcaudete,Alcocer,Alcolea,Alcoroches,Aldea,Aldeanueva,Algar,Algora,Alhondiga,Alique,Almadrones,Almendral,Almoguera,Almonacid,Almorox,Alocen,Alovera,Alustante,Angon,Anguita,Anover,Anquela,Arbancon,Arbeteta,Arcicollar,Argecilla,Arges,Armallones,Armuna,Arroyo,Atanzon,Atienza,Aunon,Azuqueca,Azutan,Baides,Banos,Banuelos,Barcience,Bargas,Barriopedro,Belvis,Berninches,Borox,Brihuega,Budia,Buenaventura,Bujalaro,Burguillos,Burujon,Bustares,Cabanas,Cabanillas,Calera,Caleruela,Calzada,Camarena,Campillo,Camunas,Canizar,Canredondo,Cantalojas,Cardiel,Carmena,Carranque,Carriches,Casa,Casarrubios,Casas,Casasbuenas,Caspuenas,Castejon,Castellar,Castilforte,Castillo,Castilnuevo,Cazalegas,Cebolla,Cedillo,Cendejas,Centenera,Cervera,Checa,Chequilla,Chillaron,Chiloeches,Chozas,Chueca,Cifuentes,Cincovillas,Ciruelas,Ciruelos,Cobeja,Cobeta,Cobisa,Cogollor,Cogolludo,Condemios,Congostrina,Consuegra,Copernal,Corduente,Corral,Cuerva,Domingo,Dosbarrios,Driebes,Duron,El,Embid,Erustes,Escalona,Escalonilla,Escamilla,Escariche,Escopete,Espinosa,Espinoso,Esplegares,Esquivias,Estables,Estriegana,Fontanar,Fuembellida,Fuensalida,Fuentelsaz,Gajanejos,Galve,Galvez,Garciotum,Gascuena,Gerindote,Guadamur,Henche,Heras,Herreria,Herreruela,Hijes,Hinojosa,Hita,Hombrados,Hontanar,Hontoba,Horche,Hormigos,Huecas,Huermeces,Huerta,Hueva,Humanes,Illan,Illana,Illescas,Iniestola,Irueste,Jadraque,Jirueque,Lagartera,Las,Layos,Ledanca,Lillo,Lominchar,Loranca,Los,Lucillos,Lupiana,Luzaga,Luzon,Madridejos,Magan,Majaelrayo,Malaga,Malaguilla,Malpica,Mandayona,Mantiel,Manzaneque,Maqueda,Maranchon,Marchamalo,Marjaliza,Marrupe,Mascaraque,Masegoso,Matarrubia,Matillas,Mazarete,Mazuecos,Medranda,Megina,Mejorada,Mentrida,Mesegar,Miedes,Miguel,Millana,Milmarcos,Mirabueno,Miralrio,Mocejon,Mochales,Mohedas,Molina,Monasterio,Mondejar,Montarron,Mora,Moratilla,Morenilla,Muduex,Nambroca,Navalcan,Negredo,Noblejas,Noez,Nombela,Noves,Numancia,Nuno,Ocana,Ocentejo,Olias,Olmeda,Ontigola,Orea,Orgaz,Oropesa,Otero,Palmaces,Palomeque,Pantoja,Pardos,Paredes,Pareja,Parrillas,Pastrana,Pelahustan,Penalen,Penalver,Pepino,Peralejos,Peralveche,Pinilla,Pioz,Piqueras,Polan,Portillo,Poveda,Pozo,Pradena,Prados,Puebla,Puerto,Pulgar,Quer,Quero,Quintanar,Quismondo,Rebollosa,Recas,Renera,Retamoso,Retiendas,Riba,Rielves,Rillo,Riofrio,Robledillo,Robledo,Romanillos,Romanones,Rueda,Sacecorbo,Sacedon,Saelices,Salmeron,San,Santa,Santiuste,Santo,Sartajada,Sauca,Sayaton,Segurilla,Selas,Semillas,Sesena,Setiles,Sevilleja,Sienes,Siguenza,Solanillos,Somolinos,Sonseca,Sotillo,Sotodosos,Talavera,Tamajon,Taragudo,Taravilla,Tartanedo,Tembleque,Tendilla,Terzaga,Tierzo,Tordellego,Tordelrabano,Tordesilos,Torija,Torralba,Torre,Torrecilla,Torrecuadrada,Torrejon,Torremocha,Torrico,Torrijos,Torrubia,Tortola,Tortuera,Tortuero,Totanes,Traid,Trijueque,Trillo,Turleque,Uceda,Ugena,Ujados,Urda,Utande,Valdarachas,Valdesotos,Valhermoso,Valtablado,Valverde,Velada,Viana,Vinuelas,Yebes,Yebra,Yelamos,Yeles,Yepes,Yuncler,Yunclillos,Yuncos,Yunquera,Zaorejas,Zarzuela,Zorita"},
|
||||
{name: "Castillian", i: 4, min: 5, max: 11, d: "lr", m: 0, b: "Abanades,Ablanque,Adobes,Ajofrin,Alameda,Alaminos,Alarilla,Albalate,Albares,Albarreal,Albendiego,Alcabon,Alcanizo,Alcaudete,Alcocer,Alcolea,Alcoroches,Aldea,Aldeanueva,Algar,Algora,Alhondiga,Alique,Almadrones,Almendral,Almoguera,Almonacid,Almorox,Alocen,Alovera,Alustante,Angon,Anguita,Anover,Anquela,Arbancon,Arbeteta,Arcicollar,Argecilla,Arges,Armallones,Armuna,Arroyo,Atanzon,Atienza,Aunon,Azuqueca,Azutan,Baides,Banos,Banuelos,Barcience,Bargas,Barriopedro,Belvis,Berninches,Borox,Brihuega,Budia,Buenaventura,Bujalaro,Burguillos,Burujon,Bustares,Cabanas,Cabanillas,Calera,Caleruela,Calzada,Camarena,Campillo,Camunas,Canizar,Canredondo,Cantalojas,Cardiel,Carmena,Carranque,Carriches,Casa,Casarrubios,Casas,Casasbuenas,Caspuenas,Castejon,Castellar,Castilforte,Castillo,Castilnuevo,Cazalegas,Cebolla,Cedillo,Cendejas,Centenera,Cervera,Checa,Chequilla,Chillaron,Chiloeches,Chozas,Chueca,Cifuentes,Cincovillas,Ciruelas,Ciruelos,Cobeja,Cobeta,Cobisa,Cogollor,Cogolludo,Condemios,Congostrina,Consuegra,Copernal,Corduente,Corral,Cuerva,Domingo,Dosbarrios,Driebes,Duron,El,Embid,Erustes,Escalona,Escalonilla,Escamilla,Escariche,Escopete,Espinosa,Espinoso,Esplegares,Esquivias,Estables,Estriegana,Fontanar,Fuembellida,Fuensalida,Fuentelsaz,Gajanejos,Galve,Galvez,Garciotum,Gascuena,Gerindote,Guadamur,Henche,Heras,Herreria,Herreruela,Hijes,Hinojosa,Hita,Hombrados,Hontanar,Hontoba,Horche,Hormigos,Huecas,Huermeces,Huerta,Hueva,Humanes,Illan,Illana,Illescas,Iniestola,Irueste,Jadraque,Jirueque,Lagartera,Las,Layos,Ledanca,Lillo,Lominchar,Loranca,Los,Lucillos,Lupiana,Luzaga,Luzon,Madridejos,Magan,Majaelrayo,Malaga,Malaguilla,Malpica,Mandayona,Mantiel,Manzaneque,Maqueda,Maranchon,Marchamalo,Marjaliza,Marrupe,Mascaraque,Masegoso,Matarrubia,Matillas,Mazarete,Mazuecos,Medranda,Megina,Mejorada,Mentrida,Mesegar,Miedes,Miguel,Millana,Milmarcos,Mirabueno,Miralrio,Mocejon,Mochales,Mohedas,Molina,Monasterio,Mondejar,Montarron,Mora,Moratilla,Morenilla,Muduex,Nambroca,Navalcan,Negredo,Noblejas,Noez,Nombela,Noves,Numancia,Nuno,Ocana,Ocentejo,Olias,Olmeda,Ontigola,Orea,Orgaz,Oropesa,Otero,Palmaces,Palomeque,Pantoja,Pardos,Paredes,Pareja,Parrillas,Pastrana,Pelahustan,Penalen,Penalver,Pepino,Peralejos,Peralveche,Pinilla,Pioz,Piqueras,Polan,Portillo,Poveda,Pozo,Pradena,Prados,Puebla,Puerto,Pulgar,Quer,Quero,Quintanar,Quismondo,Rebollosa,Recas,Renera,Retamoso,Retiendas,Riba,Rielves,Rillo,Riofrio,Robledillo,Robledo,Romanillos,Romanones,Rueda,Sacecorbo,Sacedon,Saelices,Salmeron,San,Santa,Santiuste,Santo,Sartajada,Sauca,Sayaton,Segurilla,Selas,Semillas,Sesena,Setiles,Sevilleja,Sienes,Siguenza,Solanillos,Somolinos,Sonseca,Sotillo,Sotodasos,Talavera,Tamajon,Taragudo,Taravilla,Tartanedo,Tembleque,Tendilla,Terzaga,Tierzo,Tordellego,Tordelrabano,Tordesilos,Torija,Torralba,Torre,Torrecilla,Torrecuadrada,Torrejon,Torremocha,Torrico,Torrijos,Torrubia,Tortola,Tortuera,Tortuero,Totanes,Traid,Trijueque,Trillo,Turleque,Uceda,Ugena,Ujados,Urda,Utande,Valdarachas,Valdesotos,Valhermoso,Valtablado,Valverde,Velada,Viana,Vinuelas,Yebes,Yebra,Yelamos,Yeles,Yepes,Yuncler,Yunclillos,Yuncos,Yunquera,Zaorejas,Zarzuela,Zorita"},
|
||||
{name: "Ruthenian", i: 5, min: 5, max: 10, d: "", m: 0, b: "Belgorod,Beloberezhye,Belyi,Belz,Berestiy,Berezhets,Berezovets,Berezutsk,Bobruisk,Bolonets,Borisov,Borovsk,Bozhesk,Bratslav,Bryansk,Brynsk,Buryn,Byhov,Chechersk,Chemesov,Cheremosh,Cherlen,Chern,Chernigov,Chernitsa,Chernobyl,Chernogorod,Chertoryesk,Chetvertnia,Demyansk,Derevesk,Devyagoresk,Dichin,Dmitrov,Dorogobuch,Dorogobuzh,Drestvin,Drokov,Drutsk,Dubechin,Dubichi,Dubki,Dubkov,Dveren,Galich,Glebovo,Glinsk,Goloty,Gomiy,Gorodets,Gorodische,Gorodno,Gorohovets,Goroshin,Gorval,Goryshon,Holm,Horobor,Hoten,Hotin,Hotmyzhsk,Ilovech,Ivan,Izborsk,Izheslavl,Kamenets,Kanev,Karachev,Karna,Kavarna,Klechesk,Klyapech,Kolomyya,Kolyvan,Kopyl,Korec,Kornik,Korochunov,Korshev,Korsun,Koshkin,Kotelno,Kovyla,Kozelsk,Kozelsk,Kremenets,Krichev,Krylatsk,Ksniatin,Kulatsk,Kursk,Kursk,Lebedev,Lida,Logosko,Lomihvost,Loshesk,Loshichi,Lubech,Lubno,Lubutsk,Lutsk,Luchin,Luki,Lukoml,Luzha,Lvov,Mtsensk,Mdin,Medniki,Melecha,Merech,Meretsk,Mescherskoe,Meshkovsk,Metlitsk,Mezetsk,Mglin,Mihailov,Mikitin,Mikulino,Miloslavichi,Mogilev,Mologa,Moreva,Mosalsk,Moschiny,Mozyr,Mstislav,Mstislavets,Muravin,Nemech,Nemiza,Nerinsk,Nichan,Novgorod,Novogorodok,Obolichi,Obolensk,Obolensk,Oleshsk,Olgov,Omelnik,Opoka,Opoki,Oreshek,Orlets,Osechen,Oster,Ostrog,Ostrov,Perelai,Peremil,Peremyshl,Pererov,Peresechen,Perevitsk,Pereyaslav,Pinsk,Ples,Polotsk,Pronsk,Proposhesk,Punia,Putivl,Rechitsa,Rodno,Rogachev,Romanov,Romny,Roslavl,Rostislavl,Rostovets,Rsha,Ruza,Rybchesk,Rylsk,Rzhavesk,Rzhev,Rzhischev,Sambor,Serensk,Serensk,Serpeysk,Shilov,Shuya,Sinech,Sizhka,Skala,Slovensk,Slutsk,Smedin,Sneporod,Snitin,Snovsk,Sochevo,Sokolec,Starica,Starodub,Stepan,Sterzh,Streshin,Sutesk,Svinetsk,Svisloch,Terebovl,Ternov,Teshilov,Teterin,Tiversk,Torchevsk,Toropets,Torzhok,Tripolye,Trubchevsk,Tur,Turov,Usvyaty,Uteshkov,Vasilkov,Velil,Velye,Venev,Venicha,Verderev,Vereya,Veveresk,Viazma,Vidbesk,Vidychev,Voino,Volodimer,Volok,Volyn,Vorobesk,Voronich,Voronok,Vorotynsk,Vrev,Vruchiy,Vselug,Vyatichsk,Vyatka,Vyshegorod,Vyshgorod,Vysokoe,Yagniatin,Yaropolch,Yasenets,Yuryev,Yuryevets,Zaraysk,Zhitomel,Zholvazh,Zizhech,Zubkov,Zudechev,Zvenigorod"},
|
||||
{name: "Nordic", i: 6, min: 6, max: 10, d: "kln", m: .1, b: "Akureyri,Aldra,Alftanes,Andenes,Austbo,Auvog,Bakkafjordur,Ballangen,Bardal,Beisfjord,Bifrost,Bildudalur,Bjerka,Bjerkvik,Bjorkosen,Bliksvaer,Blokken,Blonduos,Bolga,Bolungarvik,Borg,Borgarnes,Bosmoen,Bostad,Bostrand,Botsvika,Brautarholt,Breiddalsvik,Bringsli,Brunahlid,Budardalur,Byggdakjarni,Dalvik,Djupivogur,Donnes,Drageid,Drangsnes,Egilsstadir,Eiteroga,Elvenes,Engavogen,Ertenvog,Eskifjordur,Evenes,Eyrarbakki,Fagernes,Fallmoen,Fellabaer,Fenes,Finnoya,Fjaer,Fjelldal,Flakstad,Flateyri,Flostrand,Fludir,Gardaber,Gardur,Gimstad,Givaer,Gjeroy,Gladstad,Godoya,Godoynes,Granmoen,Gravdal,Grenivik,Grimsey,Grindavik,Grytting,Hafnir,Halsa,Hauganes,Haugland,Hauknes,Hella,Helland,Hellissandur,Hestad,Higrav,Hnifsdalur,Hofn,Hofsos,Holand,Holar,Holen,Holkestad,Holmavik,Hopen,Hovden,Hrafnagil,Hrisey,Husavik,Husvik,Hvammstangi,Hvanneyri,Hveragerdi,Hvolsvollur,Igeroy,Indre,Inndyr,Innhavet,Innes,Isafjordur,Jarklaustur,Jarnsreykir,Junkerdal,Kaldvog,Kanstad,Karlsoy,Kavosen,Keflavik,Kjelde,Kjerstad,Klakk,Kopasker,Kopavogur,Korgen,Kristnes,Krutoga,Krystad,Kvina,Lande,Laugar,Laugaras,Laugarbakki,Laugarvatn,Laupstad,Leines,Leira,Leiren,Leland,Lenvika,Loding,Lodingen,Lonsbakki,Lopsmarka,Lovund,Luroy,Maela,Melahverfi,Meloy,Mevik,Misvaer,Mornes,Mosfellsber,Moskenes,Myken,Naurstad,Nesberg,Nesjahverfi,Nesset,Nevernes,Obygda,Ofoten,Ogskardet,Okervika,Oknes,Olafsfjordur,Oldervika,Olstad,Onstad,Oppeid,Oresvika,Orsnes,Orsvog,Osmyra,Overdal,Prestoya,Raudalaekur,Raufarhofn,Reipo,Reykholar,Reykholt,Reykjahlid,Rif,Rinoya,Rodoy,Rognan,Rosvika,Rovika,Salhus,Sanden,Sandgerdi,Sandoker,Sandset,Sandvika,Saudarkrokur,Selfoss,Selsoya,Sennesvik,Setso,Siglufjordur,Silvalen,Skagastrond,Skjerstad,Skonland,Skorvogen,Skrova,Sleneset,Snubba,Softing,Solheim,Solheimar,Sorarnoy,Sorfugloy,Sorland,Sormela,Sorvaer,Sovika,Stamsund,Stamsvika,Stave,Stokka,Stokkseyri,Storjord,Storo,Storvika,Strand,Straumen,Strendene,Sudavik,Sudureyri,Sundoya,Sydalen,Thingeyri,Thorlakshofn,Thorshofn,Tjarnabyggd,Tjotta,Tosbotn,Traelnes,Trofors,Trones,Tverro,Ulvsvog,Unnstad,Utskor,Valla,Vandved,Varmahlid,Vassos,Vevelstad,Vidrek,Vik,Vikholmen,Vogar,Vogehamn,Vopnafjordur"},
|
||||
{name: "Greek", i: 7, min: 5, max: 11, d: "s", m: .1, b: "Abdera,Abila,Abydos,Acanthus,Acharnae,Actium,Adramyttium,Aegae,Aegina,Aegium,Aenus,Agrinion,Aigosthena,Akragas,Akrai,Akrillai,Akroinon,Akrotiri,Alalia,Alexandreia,Alexandretta,Alexandria,Alinda,Amarynthos,Amaseia,Ambracia,Amida,Amisos,Amnisos,Amphicaea,Amphigeneia,Amphipolis,Amphissa,Ankon,Antigona,Antipatrea,Antioch,Antioch,Antiochia,Andros,Apamea,Aphidnae,Apollonia,Argos,Arsuf,Artanes,Artemita,Argyroupoli,Asine,Asklepios,Aspendos,Assus,Astacus,Athenai,Athmonia,Aytos,Ancient,Baris,Bhrytos,Borysthenes,Berge,Boura,Bouthroton,Brauron,Byblos,Byllis,Byzantium,Bythinion,Callipolis,Cebrene,Chalcedon,Calydon,Carystus,Chamaizi,Chalcis,Chersonesos,Chios,Chytri,Clazomenae,Cleonae,Cnidus,Colosse,Corcyra,Croton,Cyme,Cyrene,Cythera,Decelea,Delos,Delphi,Demetrias,Dicaearchia,Dimale,Didyma,Dion,Dioscurias,Dodona,Dorylaion,Dyme,Edessa,Elateia,Eleusis,Eleutherna,Emporion,Ephesus,Ephyra,Epidamnos,Epidauros,Eresos,Eretria,Erythrae,Eubea,Gangra,Gaza,Gela,Golgi,Gonnos,Gorgippia,Gournia,Gortyn,Gythium,Hagios,Hagia,Halicarnassus,Halieis,Helike,Heliopolis,Hellespontos,Helorus,Hemeroskopeion,Heraclea,Hermione,Hermonassa,Hierapetra,Hierapolis,Himera,Histria,Hubla,Hyele,Ialysos,Iasus,Idalium,Imbros,Iolcus,Itanos,Ithaca,Juktas,Kallipolis,Kamares,Kameiros,Kannia,Kamarina,Kasmenai,Katane,Kerkinitida,Kepoi,Kimmerikon,Kios,Klazomenai,Knidos,Knossos,Korinthos,Kos,Kourion,Kume,Kydonia,Kynos,Kyrenia,Lamia,Lampsacus,Laodicea,Lapithos,Larissa,Lato,Laus,Lebena,Lefkada,Lekhaion,Leibethra,Leontinoi,Lepreum,Lessa,Lilaea,Lindus,Lissus,Epizephyrian,Madytos,Magnesia,Mallia,Mantineia,Marathon,Marmara,Maroneia,Masis,Massalia,Megalopolis,Megara,Mesembria,Messene,Metapontum,Methana,Methone,Methumna,Miletos,Misenum,Mochlos,Monastiraki,Morgantina,Mulai,Mukenai,Mylasa,Myndus,Myonia,Myra,Myrmekion,Mutilene,Myos,Nauplios,Naucratis,Naupactus,Naxos,Neapoli,Neapolis,Nemea,Nicaea,Nicopolis,Nirou,Nymphaion,Nysa,Oenoe,Oenus,Odessos,Olbia,Olous,Olympia,Olynthus,Opus,Orchomenus,Oricos,Orestias,Oreus,Oropus,Onchesmos,Pactye,Pagasae,Palaikastro,Pandosia,Panticapaeum,Paphos,Parium,Paros,Parthenope,Patrae,Pavlopetri,Pegai,Pelion,Peiraies,Pella,Percote,Pergamum,Petsofa,Phaistos,Phaleron,Phanagoria,Pharae,Pharnacia,Pharos,Phaselis,Philippi,Pithekussa,Philippopolis,Platanos,Phlius,Pherae,Phocaea,Pinara,Pisa,Pitane,Pitiunt,Pixous,Plataea,Poseidonia,Potidaea,Priapus,Priene,Prousa,Pseira,Psychro,Pteleum,Pydna,Pylos,Pyrgos,Rhamnus,Rhegion,Rhithymna,Rhodes,Rhypes,Rizinia,Salamis,Same,Samos,Scyllaeum,Selinus,Seleucia,Semasus,Sestos,Scidrus,Sicyon,Side,Sidon,Siteia,Sinope,Siris,Sklavokampos,Smyrna,Soli,Sozopolis,Sparta,Stagirus,Stratos,Stymphalos,Sybaris,Surakousai,Taras,Tanagra,Tanais,Tauromenion,Tegea,Temnos,Tenedos,Tenea,Teos,Thapsos,Thassos,Thebai,Theodosia,Therma,Thespiae,Thronion,Thoricus,Thurii,Thyreum,Thyria,Tiruns,Tithoraea,Tomis,Tragurion,Trapeze,Trapezus,Tripolis,Troizen,Troliton,Troy,Tylissos,Tyras,Tyros,Tyritake,Vasiliki,Vathypetros,Zakynthos,Zakros,Zankle"},
|
||||
|
|
|
|||
575
modules/save.js
575
modules/save.js
|
|
@ -1,377 +1,5 @@
|
|||
// Functions to save and load the map
|
||||
"use strict";
|
||||
|
||||
// download map as SVG
|
||||
async function saveSVG() {
|
||||
TIME && console.time("saveSVG");
|
||||
const url = await getMapURL("svg");
|
||||
const link = document.createElement("a");
|
||||
link.download = getFileName() + ".svg";
|
||||
link.href = url;
|
||||
link.click();
|
||||
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
|
||||
TIME && console.timeEnd("saveSVG");
|
||||
}
|
||||
|
||||
// download map as PNG
|
||||
async function savePNG() {
|
||||
TIME && console.time("savePNG");
|
||||
const url = await getMapURL("png");
|
||||
|
||||
const link = document.createElement("a");
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = svgWidth * pngResolutionInput.value;
|
||||
canvas.height = svgHeight * pngResolutionInput.value;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
img.onload = function () {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
link.download = getFileName() + ".png";
|
||||
canvas.toBlob(function (blob) {
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.click();
|
||||
window.setTimeout(function () {
|
||||
canvas.remove();
|
||||
window.URL.revokeObjectURL(link.href);
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
TIME && console.timeEnd("savePNG");
|
||||
}
|
||||
|
||||
// download map as JPEG
|
||||
async function saveJPEG() {
|
||||
TIME && console.time("saveJPEG");
|
||||
const url = await getMapURL("png");
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = svgWidth * pngResolutionInput.value;
|
||||
canvas.height = svgHeight * pngResolutionInput.value;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
img.onload = async function () {
|
||||
canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92);
|
||||
const URL = await canvas.toDataURL("image/jpeg", quality);
|
||||
const link = document.createElement("a");
|
||||
link.download = getFileName() + ".jpeg";
|
||||
link.href = URL;
|
||||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||
};
|
||||
|
||||
TIME && console.timeEnd("saveJPEG");
|
||||
}
|
||||
|
||||
// download map as png tiles
|
||||
async function saveTiles() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// download schema
|
||||
const urlSchema = await getMapURL("tiles", {debug: true});
|
||||
const zip = new JSZip();
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = graphWidth;
|
||||
canvas.height = graphHeight;
|
||||
|
||||
const imgSchema = new Image();
|
||||
imgSchema.src = urlSchema;
|
||||
imgSchema.onload = function () {
|
||||
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob(blob => zip.file(`fmg_tile_schema.png`, blob));
|
||||
};
|
||||
|
||||
// download tiles
|
||||
const url = await getMapURL("tiles");
|
||||
const tilesX = +document.getElementById("tileColsInput").value;
|
||||
const tilesY = +document.getElementById("tileRowsInput").value;
|
||||
const scale = +document.getElementById("tileScaleInput").value;
|
||||
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
const tolesTotal = tilesX * tilesY;
|
||||
|
||||
const width = graphWidth * scale;
|
||||
const height = width * (tileH / tileW);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
let loaded = 0;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
||||
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
|
||||
const name = `fmg_tile_${i}.png`;
|
||||
canvas.toBlob(blob => {
|
||||
zip.file(name, blob);
|
||||
loaded += 1;
|
||||
if (loaded === tolesTotal) return downloadZip();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function downloadZip() {
|
||||
const name = `${getFileName()}.zip`;
|
||||
zip.generateAsync({type: "blob"}).then(blob => {
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = name;
|
||||
link.click();
|
||||
link.remove();
|
||||
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// parse map svg to object url
|
||||
async function getMapURL(type, options = {}) {
|
||||
const {debug = false, globe = false, noLabels = false, noWater = false} = options;
|
||||
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg
|
||||
cloneEl.id = "fantasyMap";
|
||||
document.body.appendChild(cloneEl);
|
||||
const clone = d3.select(cloneEl);
|
||||
if (!debug) clone.select("#debug")?.remove();
|
||||
|
||||
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
||||
const svgDefs = document.getElementById("defElements");
|
||||
|
||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
|
||||
if (isFirefox && type === "mesh") clone.select("#oceanPattern")?.remove();
|
||||
if (globe) clone.select("#scaleBar")?.remove();
|
||||
if (noLabels) {
|
||||
clone.select("#labels #states")?.remove();
|
||||
clone.select("#labels #burgLabels")?.remove();
|
||||
clone.select("#icons #burgIcons")?.remove();
|
||||
}
|
||||
if (noWater) {
|
||||
clone.select("#oceanBase").attr("opacity", 0);
|
||||
clone.select("#oceanPattern").attr("opacity", 0);
|
||||
}
|
||||
if (type !== "png") {
|
||||
// reset transform to show the whole map
|
||||
clone.attr("width", graphWidth).attr("height", graphHeight);
|
||||
clone.select("#viewbox").attr("transform", null);
|
||||
}
|
||||
|
||||
if (type === "svg") removeUnusedElements(clone);
|
||||
if (customization && type === "mesh") updateMeshCells(clone);
|
||||
inlineStyle(clone);
|
||||
|
||||
// remove unused filters
|
||||
const filters = cloneEl.querySelectorAll("filter");
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
const id = filters[i].id;
|
||||
if (cloneEl.querySelector("[filter='url(#" + id + ")']")) continue;
|
||||
if (cloneEl.getAttribute("filter") === "url(#" + id + ")") continue;
|
||||
filters[i].remove();
|
||||
}
|
||||
|
||||
// remove unused patterns
|
||||
const patterns = cloneEl.querySelectorAll("pattern");
|
||||
for (let i = 0; i < patterns.length; i++) {
|
||||
const id = patterns[i].id;
|
||||
if (cloneEl.querySelector("[fill='url(#" + id + ")']")) continue;
|
||||
patterns[i].remove();
|
||||
}
|
||||
|
||||
// remove unused symbols
|
||||
const symbols = cloneEl.querySelectorAll("symbol");
|
||||
for (let i = 0; i < symbols.length; i++) {
|
||||
const id = symbols[i].id;
|
||||
if (cloneEl.querySelector("use[*|href='#" + id + "']")) continue;
|
||||
symbols[i].remove();
|
||||
}
|
||||
|
||||
// add displayed emblems
|
||||
if (layerIsOn("toggleEmblems") && emblems.selectAll("use").size()) {
|
||||
cloneEl
|
||||
.getElementById("emblems")
|
||||
?.querySelectorAll("use")
|
||||
.forEach(el => {
|
||||
const href = el.getAttribute("href") || el.getAttribute("xlink:href");
|
||||
if (!href) return;
|
||||
const emblem = document.getElementById(href.slice(1));
|
||||
if (emblem) cloneDefs.append(emblem.cloneNode(true));
|
||||
});
|
||||
} else {
|
||||
cloneDefs.querySelector("#defs-emblems")?.remove();
|
||||
}
|
||||
|
||||
// replace ocean pattern href to base64
|
||||
if (location.hostname && cloneEl.getElementById("oceanicPattern")) {
|
||||
const el = cloneEl.getElementById("oceanicPattern");
|
||||
const url = el.getAttribute("href");
|
||||
await new Promise(resolve => {
|
||||
getBase64(url, base64 => {
|
||||
el.setAttribute("href", base64);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add relief icons
|
||||
if (cloneEl.getElementById("terrain")) {
|
||||
const uniqueElements = new Set();
|
||||
const terrainNodes = cloneEl.getElementById("terrain").childNodes;
|
||||
for (let i = 0; i < terrainNodes.length; i++) {
|
||||
const href = terrainNodes[i].getAttribute("href") || terrainNodes[i].getAttribute("xlink:href");
|
||||
uniqueElements.add(href);
|
||||
}
|
||||
|
||||
const defsRelief = svgDefs.getElementById("defs-relief");
|
||||
for (const terrain of [...uniqueElements]) {
|
||||
const element = defsRelief.querySelector(terrain);
|
||||
if (element) cloneDefs.appendChild(element.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
// add wind rose
|
||||
if (cloneEl.getElementById("compass")) {
|
||||
const rose = svgDefs.getElementById("rose");
|
||||
if (rose) cloneDefs.appendChild(rose.cloneNode(true));
|
||||
}
|
||||
|
||||
// add port icon
|
||||
if (cloneEl.getElementById("anchors")) {
|
||||
const anchor = svgDefs.getElementById("icon-anchor");
|
||||
if (anchor) cloneDefs.appendChild(anchor.cloneNode(true));
|
||||
}
|
||||
|
||||
// add grid pattern
|
||||
if (cloneEl.getElementById("gridOverlay")?.hasChildNodes()) {
|
||||
const type = cloneEl.getElementById("gridOverlay").getAttribute("type");
|
||||
const pattern = svgDefs.getElementById("pattern_" + type);
|
||||
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
|
||||
}
|
||||
|
||||
if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching")?.remove(); // remove unused hatching group
|
||||
if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog
|
||||
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths
|
||||
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths
|
||||
|
||||
// add armies style
|
||||
if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", "<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>");
|
||||
|
||||
// add xlink: for href to support svg1.1
|
||||
if (type === "svg") {
|
||||
cloneEl.querySelectorAll("[href]").forEach(el => {
|
||||
const href = el.getAttribute("href");
|
||||
el.removeAttribute("href");
|
||||
el.setAttribute("xlink:href", href);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: add dataURL for all used fonts
|
||||
const usedFonts = getUsedFonts(cloneEl);
|
||||
const fontsToLoad = usedFonts.filter(font => font.src);
|
||||
if (fontsToLoad.length) {
|
||||
const dataURLfonts = await loadFontsAsDataURI(fontsToLoad);
|
||||
|
||||
const fontFaces = dataURLfonts
|
||||
.map(({family, src, unicodeRange = "", variant = "normal"}) => {
|
||||
return `@font-face {font-family: "${family}"; src: ${src}; unicode-range: ${unicodeRange}; font-variant: ${variant};}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.setAttribute("type", "text/css");
|
||||
style.innerHTML = fontFaces;
|
||||
cloneEl.querySelector("defs").appendChild(style);
|
||||
}
|
||||
|
||||
clone.remove();
|
||||
|
||||
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + new XMLSerializer().serializeToString(cloneEl);
|
||||
const blob = new Blob([serialized], {type: "image/svg+xml;charset=utf-8"});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
|
||||
return url;
|
||||
}
|
||||
|
||||
// remove hidden g elements and g elements without children to make downloaded svg smaller in size
|
||||
function removeUnusedElements(clone) {
|
||||
if (!terrain.selectAll("use").size()) clone.select("#defs-relief")?.remove();
|
||||
if (markers.style("display") === "none") clone.select("#defs-markers")?.remove();
|
||||
|
||||
for (let empty = 1; empty; ) {
|
||||
empty = 0;
|
||||
clone.selectAll("g").each(function () {
|
||||
if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) {
|
||||
empty++;
|
||||
this.remove();
|
||||
}
|
||||
if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateMeshCells(clone) {
|
||||
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
|
||||
const scheme = getColorScheme();
|
||||
clone.select("#heights").attr("filter", "url(#blur1)");
|
||||
clone
|
||||
.select("#heights")
|
||||
.selectAll("polygon")
|
||||
.data(data)
|
||||
.join("polygon")
|
||||
.attr("points", d => getGridPolygon(d))
|
||||
.attr("id", d => "cell" + d)
|
||||
.attr("stroke", d => getColor(grid.cells.h[d], scheme));
|
||||
}
|
||||
|
||||
// for each g element get inline style
|
||||
function inlineStyle(clone) {
|
||||
const emptyG = clone.append("g").node();
|
||||
const defaultStyles = window.getComputedStyle(emptyG);
|
||||
|
||||
clone.selectAll("g, #ruler *, #scaleBar > text").each(function () {
|
||||
const compStyle = window.getComputedStyle(this);
|
||||
let style = "";
|
||||
|
||||
for (let i = 0; i < compStyle.length; i++) {
|
||||
const key = compStyle[i];
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
// Firefox mask hack
|
||||
if (key === "mask-image" && value !== defaultStyles.getPropertyValue(key)) {
|
||||
style += "mask-image: url('#land');";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === "cursor") continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
style += key + ":" + value + ";";
|
||||
}
|
||||
|
||||
for (const key in compStyle) {
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
if (key === "cursor") continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
style += key + ":" + value + ";";
|
||||
}
|
||||
|
||||
if (style != "") this.setAttribute("style", style);
|
||||
});
|
||||
|
||||
emptyG.remove();
|
||||
}
|
||||
// functions to save project as .map file
|
||||
|
||||
// prepare map data for saving
|
||||
function getMapData() {
|
||||
|
|
@ -381,7 +9,32 @@ function getMapData() {
|
|||
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
|
||||
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
|
||||
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|");
|
||||
const settings = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value, heightUnit.value, heightExponentInput.value, temperatureScale.value, barSizeInput.value, barLabel.value, barBackOpacity.value, barBackColor.value, barPosX.value, barPosY.value, populationRate, urbanization, mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value, temperaturePoleOutput.value, precOutput.value, JSON.stringify(options), mapName.value, +hideLabels.checked, stylePreset.value, +rescaleLabels.checked].join("|");
|
||||
const settings = [
|
||||
distanceUnitInput.value,
|
||||
distanceScaleInput.value,
|
||||
areaUnit.value,
|
||||
heightUnit.value,
|
||||
heightExponentInput.value,
|
||||
temperatureScale.value,
|
||||
barSizeInput.value,
|
||||
barLabel.value,
|
||||
barBackOpacity.value,
|
||||
barBackColor.value,
|
||||
barPosX.value,
|
||||
barPosY.value,
|
||||
populationRate,
|
||||
urbanization,
|
||||
mapSizeOutput.value,
|
||||
latitudeOutput.value,
|
||||
temperatureEquatorOutput.value,
|
||||
temperaturePoleOutput.value,
|
||||
precOutput.value,
|
||||
JSON.stringify(options),
|
||||
mapName.value,
|
||||
+hideLabels.checked,
|
||||
stylePreset.value,
|
||||
+rescaleLabels.checked
|
||||
].join("|");
|
||||
const coords = JSON.stringify(mapCoordinates);
|
||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
|
||||
const notesData = JSON.stringify(notes);
|
||||
|
|
@ -409,6 +62,7 @@ function getMapData() {
|
|||
const religions = JSON.stringify(pack.religions);
|
||||
const provinces = JSON.stringify(pack.provinces);
|
||||
const rivers = JSON.stringify(pack.rivers);
|
||||
const markers = JSON.stringify(pack.markers);
|
||||
|
||||
// store name array only if not the same as default
|
||||
const defaultNB = Names.getNameBases();
|
||||
|
|
@ -423,7 +77,44 @@ function getMapData() {
|
|||
const pop = Array.from(pack.cells.pop).map(p => rn(p, 4));
|
||||
|
||||
// data format as below
|
||||
const mapData = [params, settings, coords, biomes, notesData, serializedSVG, gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp, packFeatures, cultures, states, burgs, pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl, pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state, pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces, namesData, rivers, rulersString, fonts].join("\r\n");
|
||||
const mapData = [
|
||||
params,
|
||||
settings,
|
||||
coords,
|
||||
biomes,
|
||||
notesData,
|
||||
serializedSVG,
|
||||
gridGeneral,
|
||||
grid.cells.h,
|
||||
grid.cells.prec,
|
||||
grid.cells.f,
|
||||
grid.cells.t,
|
||||
grid.cells.temp,
|
||||
packFeatures,
|
||||
cultures,
|
||||
states,
|
||||
burgs,
|
||||
pack.cells.biome,
|
||||
pack.cells.burg,
|
||||
pack.cells.conf,
|
||||
pack.cells.culture,
|
||||
pack.cells.fl,
|
||||
pop,
|
||||
pack.cells.r,
|
||||
pack.cells.road,
|
||||
pack.cells.s,
|
||||
pack.cells.state,
|
||||
pack.cells.religion,
|
||||
pack.cells.province,
|
||||
pack.cells.crossroad,
|
||||
religions,
|
||||
provinces,
|
||||
namesData,
|
||||
rivers,
|
||||
rulersString,
|
||||
fonts,
|
||||
markers
|
||||
].join("\r\n");
|
||||
TIME && console.timeEnd("createMapData");
|
||||
return mapData;
|
||||
}
|
||||
|
|
@ -445,7 +136,6 @@ function dowloadMap() {
|
|||
}
|
||||
|
||||
async function saveToDropbox() {
|
||||
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
|
||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||
closeDialogs("#alert");
|
||||
const mapData = getMapData();
|
||||
|
|
@ -459,124 +149,6 @@ async function saveToDropbox() {
|
|||
}
|
||||
}
|
||||
|
||||
function saveGeoJSON_Cells() {
|
||||
const json = {type: "FeatureCollection", features: []};
|
||||
const cells = pack.cells;
|
||||
const getPopulation = i => {
|
||||
const [r, u] = getCellPopulation(i);
|
||||
return rn(r + u);
|
||||
};
|
||||
const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]]));
|
||||
|
||||
cells.i.forEach(i => {
|
||||
const coordinates = getCellCoordinates(cells.v[i]);
|
||||
const height = getHeight(i);
|
||||
const biome = cells.biome[i];
|
||||
const type = pack.features[cells.f[i]].type;
|
||||
const population = getPopulation(i);
|
||||
const state = cells.state[i];
|
||||
const province = cells.province[i];
|
||||
const culture = cells.culture[i];
|
||||
const religion = cells.religion[i];
|
||||
const neighbors = cells.c[i];
|
||||
|
||||
const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors};
|
||||
const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName("Cells") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), name, "application/json");
|
||||
}
|
||||
|
||||
function saveGeoJSON_Routes() {
|
||||
const json = {type: "FeatureCollection", features: []};
|
||||
|
||||
routes.selectAll("g > path").each(function () {
|
||||
const coordinates = getRoutePoints(this);
|
||||
const id = this.id;
|
||||
const type = this.parentElement.id;
|
||||
|
||||
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, type}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName("Routes") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), name, "application/json");
|
||||
}
|
||||
|
||||
function saveGeoJSON_Rivers() {
|
||||
const json = {type: "FeatureCollection", features: []};
|
||||
|
||||
rivers.selectAll("path").each(function () {
|
||||
const coordinates = getRiverPoints(this);
|
||||
const id = this.id;
|
||||
const width = +this.dataset.increment;
|
||||
const increment = +this.dataset.increment;
|
||||
const river = pack.rivers.find(r => r.i === +id.slice(5));
|
||||
const name = river ? river.name : "";
|
||||
const type = river ? river.type : "";
|
||||
const i = river ? river.i : "";
|
||||
const basin = river ? river.basin : "";
|
||||
|
||||
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, i, basin, name, type, width, increment}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName("Rivers") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), name, "application/json");
|
||||
}
|
||||
|
||||
function saveGeoJSON_Markers() {
|
||||
const json = {type: "FeatureCollection", features: []};
|
||||
|
||||
markers.selectAll("use").each(function () {
|
||||
const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y);
|
||||
const id = this.id;
|
||||
const type = this.dataset.id.substring(1);
|
||||
const icon = document.getElementById(type).textContent;
|
||||
const note = notes.length ? notes.find(note => note.id === this.id) : null;
|
||||
const name = note ? note.name : "";
|
||||
const legend = note ? note.legend : "";
|
||||
|
||||
const feature = {type: "Feature", geometry: {type: "Point", coordinates}, properties: {id, type, icon, name, legend}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName("Markers") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), name, "application/json");
|
||||
}
|
||||
|
||||
function getCellCoordinates(vertices) {
|
||||
const p = pack.vertices.p;
|
||||
const coordinates = vertices.map(n => getQGIScoordinates(p[n][0], p[n][1]));
|
||||
return [coordinates.concat([coordinates[0]])];
|
||||
}
|
||||
|
||||
function getRoutePoints(node) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength();
|
||||
const increment = l / Math.ceil(l / 2);
|
||||
for (let i = 0; i <= l; i += increment) {
|
||||
const p = node.getPointAtLength(i);
|
||||
points.push(getQGIScoordinates(p.x, p.y));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
function getRiverPoints(node) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength() / 2; // half-length
|
||||
const increment = 0.25; // defines density of points
|
||||
for (let i = l, c = i; i >= 0; i -= increment, c += increment) {
|
||||
const p1 = node.getPointAtLength(i);
|
||||
const p2 = node.getPointAtLength(c);
|
||||
const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
|
||||
points.push([x, y]);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
function quickSave() {
|
||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||
|
||||
|
|
@ -588,15 +160,24 @@ function quickSave() {
|
|||
|
||||
const saveReminder = function () {
|
||||
if (localStorage.getItem("noReminder")) return;
|
||||
const message = ["Please don't forget to save your work as a .map file", "Please remember to save work as a .map file", "Saving in .map format will ensure your data won't be lost in case of issues", "Safety is number one priority. Please save the map", "Don't forget to save your map on a regular basis!", "Just a gentle reminder for you to save the map", "Please don't forget to save your progress (saving as .map is the best option)", "Don't want to be reminded about need to save? Press CTRL+Q"];
|
||||
const message = [
|
||||
"Please don't forget to save your work as a .map file",
|
||||
"Please remember to save work as a .map file",
|
||||
"Saving in .map format will ensure your data won't be lost in case of issues",
|
||||
"Safety is number one priority. Please save the map",
|
||||
"Don't forget to save your map on a regular basis!",
|
||||
"Just a gentle reminder for you to save the map",
|
||||
"Please don't forget to save your progress (saving as .map is the best option)",
|
||||
"Don't want to be reminded about need to save? Press CTRL+Q"
|
||||
];
|
||||
const interval = 15 * 60 * 1000; // remind every 15 minutes
|
||||
|
||||
saveReminder.reminder = setInterval(() => {
|
||||
if (customization) return;
|
||||
tip(ra(message), true, "warn", 2500);
|
||||
}, 1e6);
|
||||
}, interval);
|
||||
saveReminder.status = 1;
|
||||
};
|
||||
|
||||
saveReminder();
|
||||
|
||||
function toggleSaveReminder() {
|
||||
|
|
|
|||
|
|
@ -677,7 +677,22 @@ class Battle {
|
|||
if (note) {
|
||||
const status = side === "attackers" ? battleStatus[0] : battleStatus[1];
|
||||
const losses = r.a ? Math.abs(d3.sum(Object.values(r.casualties))) / r.a : 1;
|
||||
const regStatus = losses === 1 ? "is destroyed" : losses > 0.8 ? "is almost completely destroyed" : losses > 0.5 ? "suffered terrible losses" : losses > 0.3 ? "suffered severe losses" : losses > 0.2 ? "suffered heavy losses" : losses > 0.05 ? "suffered significant losses" : losses > 0 ? "suffered unsignificant losses" : "left the battle without loss";
|
||||
const regStatus =
|
||||
losses === 1
|
||||
? "is destroyed"
|
||||
: losses > 0.8
|
||||
? "is almost completely destroyed"
|
||||
: losses > 0.5
|
||||
? "suffered terrible losses"
|
||||
: losses > 0.3
|
||||
? "suffered severe losses"
|
||||
: losses > 0.2
|
||||
? "suffered heavy losses"
|
||||
: losses > 0.05
|
||||
? "suffered significant losses"
|
||||
: losses > 0
|
||||
? "suffered unsignificant losses"
|
||||
: "left the battle without loss";
|
||||
const casualties = Object.keys(r.casualties)
|
||||
.map(t => (r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null))
|
||||
.filter(c => c);
|
||||
|
|
@ -691,40 +706,32 @@ class Battle {
|
|||
armies.select(`g#${id} > text`).text(Military.getTotal(r)); // update reg box
|
||||
}
|
||||
|
||||
// append battlefield marker
|
||||
void (function addMarkerSymbol() {
|
||||
if (svg.select("#defs-markers").select("#marker_battlefield").size()) return;
|
||||
const symbol = svg.select("#defs-markers").append("symbol").attr("id", "marker_battlefield").attr("viewBox", "0 0 30 30");
|
||||
symbol.append("path").attr("d", "M6,19 l9,10 L24,19").attr("fill", "#000000").attr("stroke", "none");
|
||||
symbol.append("circle").attr("cx", 15).attr("cy", 15).attr("r", 10).attr("fill", "#ffffff").attr("stroke", "#000000").attr("stroke-width", 1);
|
||||
symbol.append("text").attr("x", "50%").attr("y", "52%").attr("fill", "#000000").attr("stroke", "#3200ff").attr("stroke-width", 0).attr("font-size", "12px").attr("dominant-baseline", "central").text("⚔️");
|
||||
})();
|
||||
const i = last(pack.markers)?.i + 1 || 0;
|
||||
{
|
||||
// append battlefield marker
|
||||
const marker = {i, x: this.x, y: this.y, cell: this.cell, icon: "⚔️", type: "battlefields", dy: 52};
|
||||
pack.markers.push(marker);
|
||||
const markerHTML = drawMarker(marker);
|
||||
document.getElementById("markers").insertAdjacentHTML("beforeend", markerHTML);
|
||||
}
|
||||
|
||||
const getSide = (regs, n) => (regs.length > 1 ? `${n ? "regiments" : "forces"} of ${list([...new Set(regs.map(r => pack.states[r.state].name))])}` : getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name);
|
||||
const getSide = (regs, n) =>
|
||||
regs.length > 1
|
||||
? `${n ? "regiments" : "forces"} of ${list([...new Set(regs.map(r => pack.states[r.state].name))])}`
|
||||
: getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name;
|
||||
const getLosses = casualties => Math.min(rn(casualties * 100), 100);
|
||||
|
||||
const status = battleStatus[+P(0.7)];
|
||||
const result = `The ${this.getTypeName(this.type)} ended in ${status}`;
|
||||
const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide(this.defenders.regiments, 0)}. ${result}.
|
||||
const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide(
|
||||
this.defenders.regiments,
|
||||
0
|
||||
)}. ${result}.
|
||||
\r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(this.defenders.casualties)}%`;
|
||||
const id = getNextId("markerElement");
|
||||
notes.push({id, name: this.name, legend});
|
||||
notes.push({id: `marker${i}`, name: this.name, legend});
|
||||
|
||||
tip(`${this.name} is over. ${result}`, true, "success", 4000);
|
||||
|
||||
markers
|
||||
.append("use")
|
||||
.attr("id", id)
|
||||
.attr("xlink:href", "#marker_battlefield")
|
||||
.attr("data-id", "#marker_battlefield")
|
||||
.attr("data-x", this.x)
|
||||
.attr("data-y", this.y)
|
||||
.attr("x", this.x - 15)
|
||||
.attr("y", this.y - 30)
|
||||
.attr("data-size", 1)
|
||||
.attr("width", 30)
|
||||
.attr("height", 30);
|
||||
|
||||
$("#battleScreen").dialog("destroy");
|
||||
this.cleanData();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,11 @@ function editBurg(id) {
|
|||
burgLabels.selectAll("text").call(d3.drag().on("start", dragBurgLabel)).classed("draggable", true);
|
||||
updateBurgValues();
|
||||
|
||||
const my = id || d3.event.target.tagName === "text" ? "center bottom-20" : "center top+20";
|
||||
const at = id ? "center" : d3.event.target.tagName === "text" ? "top" : "bottom";
|
||||
const of = id ? "svg" : d3.event.target;
|
||||
|
||||
$("#burgEditor").dialog({
|
||||
title: "Edit Burg",
|
||||
resizable: false,
|
||||
close: closeBurgEditor,
|
||||
position: {my, at, of, collision: "fit"}
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
if (modules.editBurg) return;
|
||||
|
|
@ -39,6 +35,8 @@ function editBurg(id) {
|
|||
document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture);
|
||||
document.getElementById("burgPopulation").addEventListener("change", changePopulation);
|
||||
burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature));
|
||||
document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed);
|
||||
document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed);
|
||||
|
||||
document.getElementById("burgStyleShow").addEventListener("click", showStyleSection);
|
||||
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection);
|
||||
|
|
@ -46,12 +44,11 @@ function editBurg(id) {
|
|||
document.getElementById("burgEditIconStyle").addEventListener("click", editGroupIconStyle);
|
||||
document.getElementById("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle);
|
||||
|
||||
document.getElementById("burgSeeInMFCG").addEventListener("click", openInMFCG);
|
||||
document.getElementById("burgEmblem").addEventListener("click", openEmblemEdit);
|
||||
document.getElementById("burgEditEmblem").addEventListener("click", openEmblemEdit);
|
||||
document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg);
|
||||
document.getElementById("burglLegend").addEventListener("click", editBurgLegend);
|
||||
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton);
|
||||
document.getElementById("burgLock").addEventListener("mouseover", showBurgELockTip);
|
||||
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
|
||||
|
||||
function updateBurgValues() {
|
||||
|
|
@ -110,6 +107,9 @@ function editBurg(id) {
|
|||
const coaID = "burgCOA" + id;
|
||||
COArenderer.trigger(coaID, b.coa);
|
||||
document.getElementById("burgEmblem").setAttribute("href", "#" + coaID);
|
||||
|
||||
updateMFCGFrame(b);
|
||||
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
|
||||
}
|
||||
|
||||
// in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
|
||||
|
|
@ -372,11 +372,6 @@ function editBurg(id) {
|
|||
}
|
||||
}
|
||||
|
||||
function showBurgELockTip() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
showBurgLockTip(id);
|
||||
}
|
||||
|
||||
function showStyleSection() {
|
||||
document.querySelectorAll("#burgBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("burgStyleSection").style.display = "inline-block";
|
||||
|
|
@ -402,59 +397,63 @@ function editBurg(id) {
|
|||
editStyle("anchors", g);
|
||||
}
|
||||
|
||||
function openInMFCG(event) {
|
||||
const id = elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const defSeed = +(seed + id.padStart(4, 0));
|
||||
if (isCtrlClick(event)) {
|
||||
prompt(
|
||||
`Please provide a Medieval Fantasy City Generator seed.
|
||||
Seed should be a number. Default seed is FMG map seed + burg id padded to 4 chars with zeros (${defSeed}).
|
||||
Please note that if seed is custom, "Overworld" button from MFCG will open a different map`,
|
||||
{default: burg.MFCG || defSeed, step: 1, min: 1, max: 1e13 - 1},
|
||||
v => {
|
||||
burg.MFCG = v;
|
||||
openMFCG(v);
|
||||
}
|
||||
);
|
||||
} else openMFCG();
|
||||
function updateMFCGFrame(burg) {
|
||||
const mfcgURL = getMFCGlink(burg);
|
||||
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL);
|
||||
document.getElementById("mfcgLink").setAttribute("href", mfcgURL);
|
||||
}
|
||||
|
||||
function openMFCG(seed) {
|
||||
if (!seed && burg.MFCGlink) {
|
||||
openURL(burg.MFCGlink);
|
||||
return;
|
||||
}
|
||||
const cells = pack.cells;
|
||||
const name = elSelected.text();
|
||||
const size = Math.max(Math.min(rn(burg.population), 100), 6); // to be removed once change on MFDC is done
|
||||
const population = rn(burg.population * populationRate * urbanization);
|
||||
function getBurgSeed(burg) {
|
||||
return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`);
|
||||
}
|
||||
|
||||
const s = burg.MFCG || defSeed;
|
||||
const cell = burg.cell;
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
function getMFCGlink(burg) {
|
||||
const {cells} = pack;
|
||||
const {name, population, cell} = burg;
|
||||
const burgSeed = getBurgSeed(burg);
|
||||
const size = Math.max(Math.min(rn(population), 100), 6);
|
||||
const people = rn(population * populationRate * urbanization);
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const sea = coast && cells.haven[burg.cell] ? getSeaDirections(burg.cell) : "";
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return "&sea=" + norm;
|
||||
}
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
|
||||
const site = "http://fantasycities.watabou.ru/?random=0&continuous=0";
|
||||
const url = `${site}&name=${name}&population=${population}&size=${size}&seed=${s}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
openURL(url);
|
||||
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : "";
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return "&sea=" + norm;
|
||||
}
|
||||
|
||||
const baseURL = "https://watabou.github.io/city-generator/?random=0&continuous=0";
|
||||
const url = `${baseURL}&name=${name}&population=${people}&size=${size}&seed=${burgSeed}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
function changeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = +this.value;
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
}
|
||||
|
||||
function randomizeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = rand(1e9 - 1);
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
document.getElementById("mfcgBurgSeed").value = burgSeed;
|
||||
}
|
||||
|
||||
function openEmblemEdit() {
|
||||
|
|
|
|||
|
|
@ -79,20 +79,26 @@ function overviewBurgs() {
|
|||
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>
|
||||
<input data-tip="Burg state" class="burgState" value="${state}" disabled>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(b.culture)}</select>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(
|
||||
b.culture
|
||||
)}</select>
|
||||
<span data-tip="Burg population" class="icon-male"></span>
|
||||
<input data-tip="Burg population. Type to change" class="burgPopulation" value=${si(population)}>
|
||||
<div class="burgType">
|
||||
<span data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}" class="icon-star-empty${b.capital ? "" : " inactive pointer"}"></span>
|
||||
<span data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}" class="icon-star-empty${
|
||||
b.capital ? "" : " inactive pointer"
|
||||
}"></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? "" : " inactive"}" style="font-size:.9em"></span>
|
||||
</div>
|
||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||
<span class="locks pointer ${b.lock ? "icon-lock" : "icon-lock-open inactive"}"></span>
|
||||
<span class="locks pointer ${b.lock ? "icon-lock" : "icon-lock-open inactive"}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span data-tip="Remove burg" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -112,7 +118,6 @@ function overviewBurgs() {
|
|||
body.querySelectorAll("div > span.icon-star-empty").forEach(el => el.addEventListener("click", toggleCapitalStatus));
|
||||
body.querySelectorAll("div > span.icon-anchor").forEach(el => el.addEventListener("click", togglePortStatus));
|
||||
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleBurgLockStatus));
|
||||
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("mouseover", showBurgOLockTip));
|
||||
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openBurgEditor));
|
||||
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerBurgRemove));
|
||||
|
||||
|
|
@ -202,11 +207,6 @@ function overviewBurgs() {
|
|||
}
|
||||
}
|
||||
|
||||
function showBurgOLockTip() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
showBurgLockTip(burg);
|
||||
}
|
||||
|
||||
function openBurgEditor() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
editBurg(burg);
|
||||
|
|
@ -413,7 +413,14 @@ function overviewBurgs() {
|
|||
if (this.value === "provinces") return d.province;
|
||||
};
|
||||
|
||||
const base = this.value === "states" ? getStatesData() : this.value === "cultures" ? getCulturesData() : this.value === "parent" ? getParentData() : getProvincesData();
|
||||
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);
|
||||
|
|
@ -447,7 +454,10 @@ function overviewBurgs() {
|
|||
}
|
||||
|
||||
function downloadBurgsData() {
|
||||
let data = "Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,Longitude,Latitude,Elevation (" + heightUnit.value + "),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers
|
||||
let data =
|
||||
"Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,Longitude,Latitude,Elevation (" +
|
||||
heightUnit.value +
|
||||
"),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers
|
||||
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||
|
||||
valid.forEach(b => {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ function clicked() {
|
|||
else if (grand.id === "burgIcons") editBurg();
|
||||
else if (parent.id === "ice") editIce();
|
||||
else if (parent.id === "terrain") editReliefIcon();
|
||||
else if (parent.id === "markers") editMarker();
|
||||
else if (grand.id === "markers" || great.id === "markers") editMarker();
|
||||
else if (grand.id === "coastline") editCoastline();
|
||||
else if (great.id === "armies") editRegiment();
|
||||
else if (pack.cells.t[i] === 1) {
|
||||
|
|
@ -265,15 +265,6 @@ function toggleBurgLock(burg) {
|
|||
b.lock = b.lock ? 0 : 1;
|
||||
}
|
||||
|
||||
function showBurgLockTip(burg) {
|
||||
const b = pack.burgs[burg];
|
||||
if (b.lock) {
|
||||
tip("Click to Unlock burg and allow it to be change by regeneration tools");
|
||||
} else {
|
||||
tip("Click to Lock burg and prevent changes by regeneration tools");
|
||||
}
|
||||
}
|
||||
|
||||
// draw legend box
|
||||
function drawLegend(name, data) {
|
||||
legend.selectAll("*").remove(); // fully redraw every time
|
||||
|
|
@ -332,7 +323,15 @@ function drawLegend(name, data) {
|
|||
const width = bbox.width + colOffset * 2;
|
||||
const height = bbox.height + colOffset / 2 + vOffset;
|
||||
|
||||
legend.insert("rect", ":first-child").attr("id", "legendBox").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", backClr).attr("fill-opacity", opacity);
|
||||
legend
|
||||
.insert("rect", ":first-child")
|
||||
.attr("id", "legendBox")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("fill", backClr)
|
||||
.attr("fill-opacity", opacity);
|
||||
|
||||
fitLegendBox();
|
||||
}
|
||||
|
|
@ -385,7 +384,15 @@ function createPicker() {
|
|||
const closePicker = () => contaiter.style("display", "none");
|
||||
|
||||
const contaiter = d3.select("body").append("svg").attr("id", "pickerContainer").attr("width", "100%").attr("height", "100%");
|
||||
contaiter.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%").attr("opacity", 0.2).on("mousemove", cl).on("click", closePicker);
|
||||
contaiter
|
||||
.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("opacity", 0.2)
|
||||
.on("mousemove", cl)
|
||||
.on("click", closePicker);
|
||||
const picker = contaiter
|
||||
.append("g")
|
||||
.attr("id", "picker")
|
||||
|
|
@ -484,9 +491,25 @@ function createPicker() {
|
|||
const width = bbox.width + 8;
|
||||
const height = bbox.height + 9;
|
||||
|
||||
picker.insert("rect", ":first-child").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", "#ffffff").attr("stroke", "#5d4651").on("mousemove", pos);
|
||||
picker
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("fill", "#ffffff")
|
||||
.attr("stroke", "#5d4651")
|
||||
.on("mousemove", pos);
|
||||
picker.insert("text", ":first-child").attr("x", 291).attr("y", -10).attr("id", "pickerCloseText").text("✕");
|
||||
picker.insert("rect", ":first-child").attr("x", 288).attr("y", -21).attr("id", "pickerCloseRect").attr("width", 14).attr("height", 14).on("mousemove", cl).on("click", closePicker);
|
||||
picker
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", 288)
|
||||
.attr("y", -21)
|
||||
.attr("id", "pickerCloseRect")
|
||||
.attr("width", 14)
|
||||
.attr("height", 14)
|
||||
.on("mousemove", cl)
|
||||
.on("click", closePicker);
|
||||
picker.insert("text", ":first-child").attr("x", 12).attr("y", -10).attr("id", "pickerLabel").text("Color Picker").on("mousemove", pos);
|
||||
picker.insert("rect", ":first-child").attr("x", 0).attr("y", -30).attr("width", width).attr("height", 30).attr("id", "pickerHeader").on("mousemove", pos);
|
||||
picker.attr("transform", `translate(${(svgWidth - width) / 2},${(svgHeight - height) / 2})`);
|
||||
|
|
@ -696,23 +719,33 @@ function uploadFile(el, callback) {
|
|||
fileReader.onload = loaded => callback(loaded.target.result);
|
||||
}
|
||||
|
||||
function highlightElement(element) {
|
||||
if (debug.select(".highlighted").size()) return; // allow only 1 highlight element simultaniosly
|
||||
const box = element.getBBox();
|
||||
function getBBox(element) {
|
||||
const x = +element.getAttribute("x");
|
||||
const y = +element.getAttribute("y");
|
||||
const width = +element.getAttribute("width");
|
||||
const height = +element.getAttribute("height");
|
||||
return {x, y, width, height};
|
||||
}
|
||||
|
||||
function highlightElement(element, zoom) {
|
||||
if (debug.select(".highlighted").size()) return; // allow only 1 highlight element simultaneously
|
||||
const box = element.tagName === "svg" ? getBBox(element) : element.getBBox();
|
||||
const transform = element.getAttribute("transform") || null;
|
||||
const enter = d3.transition().duration(1000).ease(d3.easeBounceOut);
|
||||
const exit = d3.transition().duration(500).ease(d3.easeLinear);
|
||||
|
||||
const highlight = debug.append("rect").attr("x", box.x).attr("y", box.y).attr("width", box.width).attr("height", box.height).attr("transform", transform);
|
||||
const highlight = debug.append("rect").attr("x", box.x).attr("y", box.y).attr("width", box.width).attr("height", box.height);
|
||||
highlight.classed("highlighted", 1).attr("transform", transform);
|
||||
highlight.transition(enter).style("outline-offset", "0px").transition(exit).style("outline-color", "transparent").delay(1000).remove();
|
||||
|
||||
highlight.classed("highlighted", 1).transition(enter).style("outline-offset", "0px").transition(exit).style("outline-color", "transparent").delay(1000).remove();
|
||||
|
||||
const tr = parseTransform(transform);
|
||||
let x = box.x + box.width / 2;
|
||||
if (tr[0]) x += tr[0];
|
||||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
zoomTo(x, y, scale > 2 ? scale : 3, 1600);
|
||||
if (zoom) {
|
||||
const tr = parseTransform(transform);
|
||||
let x = box.x + box.width / 2;
|
||||
if (tr[0]) x += tr[0];
|
||||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
zoomTo(x, y, scale > 2 ? scale : zoom, 1600);
|
||||
}
|
||||
}
|
||||
|
||||
function selectIcon(initial, callback) {
|
||||
|
|
@ -922,6 +955,7 @@ function selectIcon(initial, callback) {
|
|||
}
|
||||
}
|
||||
|
||||
input.oninput = e => callback(input.value);
|
||||
table.onclick = e => {
|
||||
if (e.target.tagName === "TD") {
|
||||
input.value = e.target.innerHTML;
|
||||
|
|
@ -948,6 +982,37 @@ function selectIcon(initial, callback) {
|
|||
});
|
||||
}
|
||||
|
||||
function confirmationDialog(options) {
|
||||
const {
|
||||
title = "Confirm action",
|
||||
message = "Are you sure you want to continue? <br>The action cannot be reverted",
|
||||
cancel = "Cancel",
|
||||
confirm = "Continue",
|
||||
onCancel,
|
||||
onConfirm
|
||||
} = options;
|
||||
|
||||
const buttons = {
|
||||
[confirm]: function () {
|
||||
if (onConfirm) onConfirm();
|
||||
$(this).dialog("close");
|
||||
},
|
||||
[cancel]: function () {
|
||||
if (onCancel) onCancel();
|
||||
$(this).dialog("close");
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById("alertMessage").innerHTML = message;
|
||||
$("#alert").dialog({resizable: false, title, buttons});
|
||||
}
|
||||
|
||||
// add and register event listeners to clean up on editor closure
|
||||
function listen(element, event, handler) {
|
||||
element.addEventListener(event, handler);
|
||||
return () => element.removeEventListener(event, handler);
|
||||
}
|
||||
|
||||
// Calls the refresh functionality on all editors currently open.
|
||||
function refreshAllEditors() {
|
||||
TIME && console.time("refreshAllEditors");
|
||||
|
|
|
|||
|
|
@ -56,6 +56,15 @@ function showDataTip(e) {
|
|||
tip(dataTip);
|
||||
}
|
||||
|
||||
function showElementLockTip(event) {
|
||||
const locked = event?.target?.classList?.contains("icon-lock");
|
||||
if (locked) {
|
||||
tip("Click to unlock the element and allow it to be changed by regeneration tools");
|
||||
} else {
|
||||
tip("Click to lock the element and prevent changes to it by regeneration tools");
|
||||
}
|
||||
}
|
||||
|
||||
const moved = debounce(mouseMove, 100);
|
||||
function mouseMove() {
|
||||
const point = d3.mouse(this);
|
||||
|
|
@ -101,7 +110,8 @@ function showMapTooltip(point, e, i, g) {
|
|||
|
||||
if (group === "emblems" && e.target.tagName === "use") {
|
||||
const parent = e.target.parentNode;
|
||||
const [g, type] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
|
||||
const [g, type] =
|
||||
parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
|
||||
const i = +e.target.dataset.i;
|
||||
if (event.shiftKey) highlightEmblemElement(type, g[i]);
|
||||
|
||||
|
|
@ -328,7 +338,20 @@ function highlightEmblemElement(type, el) {
|
|||
|
||||
if (type === "burg") {
|
||||
const {x, y} = el;
|
||||
debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 0).attr("fill", "none").attr("stroke", "#d0240f").attr("stroke-width", 1).attr("opacity", 1).transition(animation).attr("r", 20).attr("opacity", 0.1).attr("stroke-width", 0).remove();
|
||||
debug
|
||||
.append("circle")
|
||||
.attr("cx", x)
|
||||
.attr("cy", y)
|
||||
.attr("r", 0)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "#d0240f")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.transition(animation)
|
||||
.attr("r", 20)
|
||||
.attr("opacity", 0.1)
|
||||
.attr("stroke-width", 0)
|
||||
.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,15 @@ function editHeightmap() {
|
|||
width: "28em",
|
||||
buttons: {
|
||||
Erase: function () {
|
||||
track("edit", "heightmap erase");
|
||||
enterHeightmapEditMode("erase");
|
||||
},
|
||||
Keep: function () {
|
||||
track("edit", "heightmap keep");
|
||||
enterHeightmapEditMode("keep");
|
||||
},
|
||||
Risk: function () {
|
||||
track("edit", "heightmap risk");
|
||||
enterHeightmapEditMode("risk");
|
||||
},
|
||||
Cancel: function () {
|
||||
|
|
@ -87,7 +90,16 @@ function editHeightmap() {
|
|||
exitCustomization.style.bottom = svgHeight / 2 + "px";
|
||||
exitCustomization.style.transform = "scale(2)";
|
||||
exitCustomization.style.display = "block";
|
||||
d3.select("#exitCustomization").transition().duration(1000).style("opacity", 1).transition().duration(2000).ease(d3.easeSinInOut).style("right", "10px").style("bottom", "10px").style("transform", "scale(1)");
|
||||
d3.select("#exitCustomization")
|
||||
.transition()
|
||||
.duration(1000)
|
||||
.style("opacity", 1)
|
||||
.transition()
|
||||
.duration(2000)
|
||||
.ease(d3.easeSinInOut)
|
||||
.style("right", "10px")
|
||||
.style("bottom", "10px")
|
||||
.style("transform", "scale(1)");
|
||||
} else exitCustomization.style.display = "block";
|
||||
|
||||
openBrushesPanel();
|
||||
|
|
@ -130,7 +142,8 @@ function editHeightmap() {
|
|||
|
||||
// Exit customization mode
|
||||
function finalizeHeightmap() {
|
||||
if (viewbox.select("#heights").selectAll("*").size() < 200) return tip("Insufficient land area! There should be at least 200 land cells to finalize the heightmap", null, "error");
|
||||
if (viewbox.select("#heights").selectAll("*").size() < 200)
|
||||
return tip("Insufficient land area! There should be at least 200 land cells to finalize the heightmap", null, "error");
|
||||
if (document.getElementById("imageConverter").offsetParent) return tip("Please exit the Image Conversion mode first", null, "error");
|
||||
|
||||
delete window.edits; // remove global variable
|
||||
|
|
@ -618,7 +631,10 @@ function editHeightmap() {
|
|||
else if (brush === "brushLower") s.forEach(i => (h[i] = lim(h[i] - power)));
|
||||
else if (brush === "brushDepress") s.forEach((i, d) => (h[i] = lim(h[i] - interpolate(d / Math.max(s.length - 1, 1)))));
|
||||
else if (brush === "brushAlign") s.forEach(i => (h[i] = lim(h[start])));
|
||||
else if (brush === "brushSmooth") s.forEach(i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1)));
|
||||
else if (brush === "brushSmooth")
|
||||
s.forEach(
|
||||
i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1))
|
||||
);
|
||||
else if (brush === "brushDisrupt") s.forEach(i => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power)));
|
||||
|
||||
mockHeightmapSelection(s);
|
||||
|
|
@ -688,6 +704,7 @@ function editHeightmap() {
|
|||
|
||||
function openTemplateEditor() {
|
||||
if ($("#templateEditor").is(":visible")) return;
|
||||
track("edit", "template editor");
|
||||
const body = document.getElementById("templateBody");
|
||||
|
||||
$("#templateEditor").dialog({
|
||||
|
|
@ -767,15 +784,29 @@ function editHeightmap() {
|
|||
|
||||
const TempY = `<span>y:<input class="templateY" data-tip="Placement range percentage along Y axis (minY-maxY)" value=${arg5 || "20-80"}></span>`;
|
||||
const TempX = `<span>x:<input class="templateX" data-tip="Placement range percentage along X axis (minX-maxX)" value=${arg4 || "15-85"}></span>`;
|
||||
const Height = `<span>h:<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${arg3 || "40-50"}></span>`;
|
||||
const Height = `<span>h:<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${
|
||||
arg3 || "40-50"
|
||||
}></span>`;
|
||||
const Count = `<span>n:<input class="templateCount" data-tip="Blobs to add, use hyphen to get a random number in range" value=${count || "1-2"}></span>`;
|
||||
const blob = `${common}${TempY}${TempX}${Height}${Count}</div>`;
|
||||
|
||||
if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough") return blob;
|
||||
if (type === "Strait") return `${common}<span>d:<select class="templateDist" data-tip="Strait direction"><option value="vertical" selected>vertical</option><option value="horizontal">horizontal</option></select></span><span>w:<input class="templateCount" data-tip="Strait width, use hyphen to get a random number in range" value=${count || "2-7"}></span></div>`;
|
||||
if (type === "Add") return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Add value to height of all cells (negative values are allowed)" type="number" value=${count || -10} min=-100 max=100 step=1></span></div>`;
|
||||
if (type === "Multiply") return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Multiply all cells Height by the value" type="number" value=${count || 1.1} min=0 max=10 step=.1></span></div>`;
|
||||
if (type === "Smooth") return `${common}<span>f:<input class="templateCount" data-tip="Set smooth fraction. 1 - full smooth, 2 - half-smooth, etc." type="number" min=1 max=10 value=${count || 2}></span></div>`;
|
||||
if (type === "Strait")
|
||||
return `${common}<span>d:<select class="templateDist" data-tip="Strait direction"><option value="vertical" selected>vertical</option><option value="horizontal">horizontal</option></select></span><span>w:<input class="templateCount" data-tip="Strait width, use hyphen to get a random number in range" value=${
|
||||
count || "2-7"
|
||||
}></span></div>`;
|
||||
if (type === "Add")
|
||||
return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Add value to height of all cells (negative values are allowed)" type="number" value=${
|
||||
count || -10
|
||||
} min=-100 max=100 step=1></span></div>`;
|
||||
if (type === "Multiply")
|
||||
return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Multiply all cells Height by the value" type="number" value=${
|
||||
count || 1.1
|
||||
} min=0 max=10 step=.1></span></div>`;
|
||||
if (type === "Smooth")
|
||||
return `${common}<span>f:<input class="templateCount" data-tip="Set smooth fraction. 1 - full smooth, 2 - half-smooth, etc." type="number" min=1 max=10 value=${
|
||||
count || 2
|
||||
}></span></div>`;
|
||||
}
|
||||
|
||||
function setRange(event) {
|
||||
|
|
@ -910,6 +941,7 @@ function editHeightmap() {
|
|||
|
||||
function openImageConverter() {
|
||||
if ($("#imageConverter").is(":visible")) return;
|
||||
track("edit", "convert image");
|
||||
imageToLoad.click();
|
||||
closeDialogs("#imageConverter");
|
||||
|
||||
|
|
@ -1170,10 +1202,14 @@ function editHeightmap() {
|
|||
}
|
||||
|
||||
function setConvertColorsNumber() {
|
||||
prompt(`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`, {default: +convertColors.value, step: 1, min: 3, max: 255}, number => {
|
||||
convertColors.value = number;
|
||||
heightsFromImage(number);
|
||||
});
|
||||
prompt(
|
||||
`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`,
|
||||
{default: +convertColors.value, step: 1, min: 3, max: 255},
|
||||
number => {
|
||||
convertColors.value = number;
|
||||
heightsFromImage(number);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function setOverlayOpacity(v) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ function handleKeyup(event) {
|
|||
const {tagName, contentEditable} = document.activeElement;
|
||||
if (["INPUT", "SELECT", "TEXTAREA"].includes(tagName)) return; // don't trigger if user inputs text
|
||||
if (tagName === "DIV" && contentEditable === "true") return; // don't trigger if user inputs a text
|
||||
if (document.getSelection().toString()) return; // don't trigger if user selects text
|
||||
event.stopPropagation();
|
||||
|
||||
const {ctrlKey, metaKey, shiftKey, altKey} = event;
|
||||
|
|
@ -25,7 +26,7 @@ function handleKeyup(event) {
|
|||
const alt = altKey || key === "Alt";
|
||||
|
||||
if (key === "F1") showInfo();
|
||||
else if (key === "F2") regeneratePrompt();
|
||||
else if (key === "F2") regeneratePrompt("hotkey");
|
||||
else if (key === "F6") quickSave();
|
||||
else if (key === "F9") quickLoad();
|
||||
else if (key === "TAB") toggleOptions(event);
|
||||
|
|
@ -52,6 +53,7 @@ function handleKeyup(event) {
|
|||
else if (shift && key === "T") overviewBurgs();
|
||||
else if (shift && key === "V") overviewRivers();
|
||||
else if (shift && key === "M") overviewMilitary();
|
||||
else if (shift && key === "K") overviewMarkers();
|
||||
else if (shift && key === "E") viewCellDetails();
|
||||
else if (shift && key === "1") toggleAddBurg();
|
||||
else if (shift && key === "2") toggleAddLabel();
|
||||
|
|
|
|||
|
|
@ -196,7 +196,8 @@ function drawHeightmap() {
|
|||
for (const i of d3.range(20, 101)) {
|
||||
if (paths[i].length < 10) continue;
|
||||
const color = getColor(i, scheme);
|
||||
if (terracing) terrs.append("path").attr("d", paths[i]).attr("transform", "translate(.7,1.4)").attr("fill", d3.color(color).darker(terracing)).attr("data-height", i);
|
||||
if (terracing)
|
||||
terrs.append("path").attr("d", paths[i]).attr("transform", "translate(.7,1.4)").attr("fill", d3.color(color).darker(terracing)).attr("data-height", i);
|
||||
terrs.append("path").attr("d", paths[i]).attr("fill", color).attr("data-height", i);
|
||||
}
|
||||
|
||||
|
|
@ -798,7 +799,10 @@ function drawReligions() {
|
|||
if (!vArray[r]) vArray[r] = [];
|
||||
vArray[r].push(points);
|
||||
body[r] += "M" + points.join("L");
|
||||
gap[r] += "M" + vertices.p[chain[0][0]] + chain.reduce((r2, v, i, d) => (!i ? r2 : !v[2] ? r2 + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r2 + "M" + vertices.p[v[0]] : r2), "");
|
||||
gap[r] +=
|
||||
"M" +
|
||||
vertices.p[chain[0][0]] +
|
||||
chain.reduce((r2, v, i, d) => (!i ? r2 : !v[2] ? r2 + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r2 + "M" + vertices.p[v[0]] : r2), "");
|
||||
}
|
||||
|
||||
const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]);
|
||||
|
|
@ -965,7 +969,14 @@ function drawStates() {
|
|||
const bodyString = bodyData.map(d => `<path id="state${d[1]}" d="${d[0]}" fill="${d[2]}" stroke="none"/>`).join("");
|
||||
const gapString = gapData.map(d => `<path id="state-gap${d[1]}" d="${d[0]}" fill="none" stroke="${d[2]}"/>`).join("");
|
||||
const clipString = bodyData.map(d => `<clipPath id="state-clip${d[1]}"><use href="#state${d[1]}"/></clipPath>`).join("");
|
||||
const haloString = haloData.map(d => `<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${d3.color(d[2]) ? d3.color(d[2]).darker().hex() : "#666666"}"/>`).join("");
|
||||
const haloString = haloData
|
||||
.map(
|
||||
d =>
|
||||
`<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${
|
||||
d3.color(d[2]) ? d3.color(d[2]).darker().hex() : "#666666"
|
||||
}"/>`
|
||||
)
|
||||
.join("");
|
||||
|
||||
statesBody.html(bodyString + gapString);
|
||||
defs.select("#statePaths").html(clipString);
|
||||
|
|
@ -1217,7 +1228,10 @@ function getProvincesVertices() {
|
|||
if (!vArray[p]) vArray[p] = [];
|
||||
vArray[p].push(points);
|
||||
body[p] += "M" + points.join("L");
|
||||
gap[p] += "M" + vertices.p[chain[0][0]] + chain.reduce((r, v, i, d) => (!i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r + "M" + vertices.p[v[0]] : r), "");
|
||||
gap[p] +=
|
||||
"M" +
|
||||
vertices.p[chain[0][0]] +
|
||||
chain.reduce((r, v, i, d) => (!i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r + "M" + vertices.p[v[0]] : r), "");
|
||||
}
|
||||
|
||||
// find province visual center
|
||||
|
|
@ -1298,7 +1312,12 @@ function drawGrid() {
|
|||
const maxWidth = Math.max(+mapWidthInput.value, graphWidth);
|
||||
const maxHeight = Math.max(+mapHeightInput.value, graphHeight);
|
||||
|
||||
d3.select(pattern).attr("stroke", stroke).attr("stroke-width", width).attr("stroke-dasharray", dasharray).attr("stroke-linecap", linecap).attr("patternTransform", tr);
|
||||
d3.select(pattern)
|
||||
.attr("stroke", stroke)
|
||||
.attr("stroke-width", width)
|
||||
.attr("stroke-dasharray", dasharray)
|
||||
.attr("stroke-linecap", linecap)
|
||||
.attr("patternTransform", tr);
|
||||
gridOverlay
|
||||
.append("rect")
|
||||
.attr("width", maxWidth)
|
||||
|
|
@ -1505,18 +1524,53 @@ function toggleMilitary() {
|
|||
function toggleMarkers(event) {
|
||||
if (!layerIsOn("toggleMarkers")) {
|
||||
turnButtonOn("toggleMarkers");
|
||||
$("#markers").fadeIn();
|
||||
drawMarkers();
|
||||
if (event && isCtrlClick(event)) editStyle("markers");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle("markers");
|
||||
return;
|
||||
}
|
||||
$("#markers").fadeOut();
|
||||
if (event && isCtrlClick(event)) return editStyle("markers");
|
||||
markers.selectAll("*").remove();
|
||||
turnButtonOff("toggleMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
function drawMarkers() {
|
||||
const rescale = +markers.attr("rescale");
|
||||
const pinned = +markers.attr("pinned");
|
||||
|
||||
const markersData = pinned ? pack.markers.filter(({pinned}) => pinned) : pack.markers;
|
||||
const html = markersData.map(marker => drawMarker(marker, rescale));
|
||||
markers.html(html.join(""));
|
||||
}
|
||||
|
||||
const getPin = (shape = "bubble", fill = "#fff", stroke = "#000") => {
|
||||
if (shape === "bubble")
|
||||
return `<path d="M6,19 l9,10 L24,19" fill="${stroke}" stroke="none" /><circle cx="15" cy="15" r="10" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === "pin")
|
||||
return `<path d="m 15,3 c -5.5,0 -9.7,4.09 -9.7,9.3 0,6.8 9.7,17 9.7,17 0,0 9.7,-10.2 9.7,-17 C 24.7,7.09 20.5,3 15,3 Z" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === "square") return `<path d="m 20,25 -5,4 -5,-4 z" fill="${stroke}"/><path d="M 5,5 H 25 V 25 H 5 Z" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === "squarish") return `<path d="m 5,5 h 20 v 20 h -6 l -4,4 -4,-4 H 5 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "diamond") return `<path d="M 2,15 15,1 28,15 15,29 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "hex") return `<path d="M 15,29 4.61,21 V 9 L 15,3 25.4,9 v 12 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "hexy") return `<path d="M 15,29 6,21 5,8 15,4 25,8 24,21 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "shieldy") return `<path d="M 15,29 6,21 5,7 c 0,0 5,-3 10,-3 5,0 10,3 10,3 l -1,14 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "shield") return `<path d="M 4.6,5.2 H 25 v 6.7 A 20.3,20.4 0 0 1 15,29 20.3,20.4 0 0 1 4.6,11.9 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "pentagon") return `<path d="M 4,16 9,4 h 12 l 5,12 -11,13 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "heptagon") return `<path d="M 15,29 6,22 4,12 10,4 h 10 l 6,8 -2,10 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "circle") return `<circle cx="15" cy="15" r="11" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === "no") return "";
|
||||
};
|
||||
|
||||
function drawMarker(marker, rescale = 1) {
|
||||
const {i, icon, x, y, dx = 50, dy = 50, px = 12, size = 30, pin, fill, stroke} = marker;
|
||||
const id = `marker${i}`;
|
||||
const zoomSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : 1;
|
||||
const viewX = rn(x - zoomSize / 2, 1);
|
||||
const viewY = rn(y - zoomSize, 1);
|
||||
const pinHTML = getPin(pin, fill, stroke);
|
||||
|
||||
return `<svg id="${id}" viewbox="0 0 30 30" width="${zoomSize}" height="${zoomSize}" x="${viewX}" y="${viewY}"><g>${pinHTML}</g><text x="${dx}%" y="${dy}%" font-size="${px}px" >${icon}</text></svg>`;
|
||||
}
|
||||
|
||||
function toggleLabels(event) {
|
||||
if (!layerIsOn("toggleLabels")) {
|
||||
turnButtonOn("toggleLabels");
|
||||
|
|
@ -1684,15 +1738,21 @@ function drawEmblems() {
|
|||
}
|
||||
|
||||
const burgNodes = nodes.filter(node => node.type === "burg");
|
||||
const burgString = burgNodes.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join("");
|
||||
const burgString = burgNodes
|
||||
.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`)
|
||||
.join("");
|
||||
emblems.select("#burgEmblems").attr("font-size", sizeBurgs).html(burgString);
|
||||
|
||||
const provinceNodes = nodes.filter(node => node.type === "province");
|
||||
const provinceString = provinceNodes.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join("");
|
||||
const provinceString = provinceNodes
|
||||
.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`)
|
||||
.join("");
|
||||
emblems.select("#provinceEmblems").attr("font-size", sizeProvinces).html(provinceString);
|
||||
|
||||
const stateNodes = nodes.filter(node => node.type === "state");
|
||||
const stateString = stateNodes.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join("");
|
||||
const stateString = stateNodes
|
||||
.map(d => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`)
|
||||
.join("");
|
||||
emblems.select("#stateEmblems").attr("font-size", sizeStates).html(stateString);
|
||||
|
||||
invokeActiveZooming();
|
||||
|
|
|
|||
|
|
@ -1,287 +1,258 @@
|
|||
"use strict";
|
||||
function editMarker() {
|
||||
function editMarker(markerI) {
|
||||
if (customization) return;
|
||||
closeDialogs("#markerEditor, .stable");
|
||||
$("#markerEditor").dialog();
|
||||
closeDialogs(".stable");
|
||||
|
||||
const [element, marker] = getElement(markerI, d3.event);
|
||||
if (!marker || !element) return;
|
||||
|
||||
elSelected = d3.select(element).raise().call(d3.drag().on("start", dragMarker)).classed("draggable", true);
|
||||
|
||||
// dom elements
|
||||
const markerType = document.getElementById("markerType");
|
||||
const markerIcon = document.getElementById("markerIcon");
|
||||
const markerIconSelect = document.getElementById("markerIconSelect");
|
||||
const markerIconSize = document.getElementById("markerIconSize");
|
||||
const markerIconShiftX = document.getElementById("markerIconShiftX");
|
||||
const markerIconShiftY = document.getElementById("markerIconShiftY");
|
||||
const markerSize = document.getElementById("markerSize");
|
||||
const markerPin = document.getElementById("markerPin");
|
||||
const markerFill = document.getElementById("markerFill");
|
||||
const markerStroke = document.getElementById("markerStroke");
|
||||
|
||||
const markerNotes = document.getElementById("markerNotes");
|
||||
const markerLock = document.getElementById("markerLock");
|
||||
const addMarker = document.getElementById("addMarker");
|
||||
const markerAdd = document.getElementById("markerAdd");
|
||||
const markerRemove = document.getElementById("markerRemove");
|
||||
|
||||
elSelected = d3.select(d3.event.target).call(d3.drag().on("start", dragMarker)).classed("draggable", true);
|
||||
updateInputs();
|
||||
|
||||
if (modules.editMarker) return;
|
||||
modules.editMarker = true;
|
||||
|
||||
$("#markerEditor").dialog({
|
||||
title: "Edit Marker", resizable: false,
|
||||
position: {my: "center top+30", at: "bottom", of: d3.event, collision: "fit"},
|
||||
title: "Edit Marker",
|
||||
resizable: false,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"},
|
||||
close: closeMarkerEditor
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("markerGroup").addEventListener("click", toggleGroupSection);
|
||||
document.getElementById("markerAddGroup").addEventListener("click", toggleGroupInput);
|
||||
document.getElementById("markerSelectGroup").addEventListener("change", changeGroup);
|
||||
document.getElementById("markerInputGroup").addEventListener("change", createGroup);
|
||||
document.getElementById("markerRemoveGroup").addEventListener("click", removeGroup);
|
||||
const listeners = [
|
||||
listen(markerType, "change", changeMarkerType),
|
||||
listen(markerIcon, "input", changeMarkerIcon),
|
||||
listen(markerIconSelect, "click", selectMarkerIcon),
|
||||
listen(markerIconSize, "input", changeIconSize),
|
||||
listen(markerIconShiftX, "input", changeIconShiftX),
|
||||
listen(markerIconShiftY, "input", changeIconShiftY),
|
||||
listen(markerSize, "input", changeMarkerSize),
|
||||
listen(markerPin, "change", changeMarkerPin),
|
||||
listen(markerFill, "input", changePinFill),
|
||||
listen(markerStroke, "input", changePinStroke),
|
||||
listen(markerNotes, "click", editMarkerLegend),
|
||||
listen(markerLock, "click", toggleMarkerLock),
|
||||
listen(markerAdd, "click", toggleAddMarker),
|
||||
listen(markerRemove, "click", confirmMarkerDeletion)
|
||||
];
|
||||
|
||||
document.getElementById("markerIcon").addEventListener("click", toggleIconSection);
|
||||
document.getElementById("markerIconSize").addEventListener("input", changeIconSize);
|
||||
document.getElementById("markerIconShiftX").addEventListener("input", changeIconShiftX);
|
||||
document.getElementById("markerIconShiftY").addEventListener("input", changeIconShiftY);
|
||||
document.getElementById("markerIconSelect").addEventListener("click", selectMarkerIcon);
|
||||
function getElement(markerI, event) {
|
||||
if (event) {
|
||||
const element = event.target?.closest("svg");
|
||||
const marker = pack.markers.find(({i}) => Number(element.id.slice(6)) === i);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
document.getElementById("markerStyle").addEventListener("click", toggleStyleSection);
|
||||
document.getElementById("markerSize").addEventListener("input", changeMarkerSize);
|
||||
document.getElementById("markerBaseStroke").addEventListener("input", changePinStroke);
|
||||
document.getElementById("markerBaseFill").addEventListener("input", changePinFill);
|
||||
document.getElementById("markerIconStrokeWidth").addEventListener("input", changeIconStrokeWidth);
|
||||
document.getElementById("markerIconStroke").addEventListener("input", changeIconStroke);
|
||||
document.getElementById("markerIconFill").addEventListener("input", changeIconFill);
|
||||
const element = document.getElementById(`marker${markerI}`);
|
||||
const marker = pack.markers.find(({i}) => i === markerI);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
document.getElementById("markerToggleBubble").addEventListener("click", togglePinVisibility);
|
||||
document.getElementById("markerLegendButton").addEventListener("click", editMarkerLegend);
|
||||
document.getElementById("markerAdd").addEventListener("click", toggleAddMarker);
|
||||
document.getElementById("markerRemove").addEventListener("click", removeMarker);
|
||||
|
||||
updateGroupOptions();
|
||||
function getSameTypeMarkers() {
|
||||
const currentType = marker.type;
|
||||
if (!currentType) return [marker];
|
||||
return pack.markers.filter(({type}) => type === currentType);
|
||||
}
|
||||
|
||||
function dragMarker() {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y;
|
||||
const dx = +this.getAttribute("x") - d3.event.x;
|
||||
const dy = +this.getAttribute("y") - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const transform = `translate(${(x + d3.event.x)},${(y + d3.event.y)})`;
|
||||
this.setAttribute("transform", transform);
|
||||
d3.event.on("drag", function () {
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", dx + x);
|
||||
this.setAttribute("y", dy + y);
|
||||
});
|
||||
|
||||
d3.event.on("end", function () {
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", rn(dx + x, 2));
|
||||
this.setAttribute("y", rn(dy + y, 2));
|
||||
|
||||
const size = marker.size || 30;
|
||||
const zoomSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
|
||||
|
||||
marker.x = rn(x + dx + zoomSize / 2, 1);
|
||||
marker.y = rn(y + dy + zoomSize, 1);
|
||||
marker.cell = findCell(marker.x, marker.y);
|
||||
});
|
||||
}
|
||||
|
||||
function updateInputs() {
|
||||
const id = elSelected.attr("data-id");
|
||||
const symbol = d3.select("#defs-markers").select(id);
|
||||
const icon = symbol.select("text");
|
||||
const {icon, type = "", size = 30, dx = 50, dy = 50, px = 12, stroke = "#000000", fill = "#ffffff", pin = "bubble", lock} = marker;
|
||||
|
||||
markerSelectGroup.value = id.slice(1);
|
||||
markerIconSize.value = parseFloat(icon.attr("font-size"));
|
||||
markerIconShiftX.value = parseFloat(icon.attr("x"));
|
||||
markerIconShiftY.value = parseFloat(icon.attr("y"));
|
||||
markerType.value = type;
|
||||
markerIcon.value = icon;
|
||||
markerIconSize.value = px;
|
||||
markerIconShiftX.value = dx;
|
||||
markerIconShiftY.value = dy;
|
||||
markerSize.value = size;
|
||||
markerPin.value = pin;
|
||||
markerFill.value = fill;
|
||||
markerStroke.value = stroke;
|
||||
|
||||
markerSize.value = elSelected.attr("data-size");
|
||||
markerBaseStroke.value = symbol.select("path").attr("fill");
|
||||
markerBaseFill.value = symbol.select("circle").attr("fill");
|
||||
|
||||
markerIconStrokeWidth.value = icon.attr("stroke-width");
|
||||
markerIconStroke.value = icon.attr("stroke");
|
||||
markerIconFill.value = icon.attr("fill");
|
||||
|
||||
markerToggleBubble.className = symbol.select("circle").attr("opacity") === "0" ? "icon-info" : "icon-info-circled";
|
||||
markerIconSelect.innerHTML = icon.text();
|
||||
markerLock.className = lock ? "icon-lock" : "icon-lock-open";
|
||||
}
|
||||
|
||||
function toggleGroupSection() {
|
||||
if (markerGroupSection.style.display === "inline-block") {
|
||||
markerEditor.querySelectorAll("button:not(#markerGroup)").forEach(b => b.style.display = "inline-block");
|
||||
markerGroupSection.style.display = "none";
|
||||
} else {
|
||||
markerEditor.querySelectorAll("button:not(#markerGroup)").forEach(b => b.style.display = "none");
|
||||
markerGroupSection.style.display = "inline-block";
|
||||
}
|
||||
function changeMarkerType() {
|
||||
marker.type = this.value;
|
||||
}
|
||||
|
||||
function updateGroupOptions() {
|
||||
markerSelectGroup.innerHTML = "";
|
||||
d3.select("#defs-markers").selectAll("symbol").each(function() {
|
||||
markerSelectGroup.options.add(new Option(this.id, this.id));
|
||||
function changeMarkerIcon() {
|
||||
const icon = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
markerSelectGroup.value = elSelected.attr("data-id").slice(1);
|
||||
}
|
||||
|
||||
function toggleGroupInput() {
|
||||
if (markerInputGroup.style.display === "inline-block") {
|
||||
markerSelectGroup.style.display = "inline-block";
|
||||
markerInputGroup.style.display = "none";
|
||||
} else {
|
||||
markerSelectGroup.style.display = "none";
|
||||
markerInputGroup.style.display = "inline-block";
|
||||
markerInputGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function changeGroup() {
|
||||
elSelected.attr("xlink:href", "#"+this.value);
|
||||
elSelected.attr("data-id", "#"+this.value);
|
||||
}
|
||||
|
||||
function createGroup() {
|
||||
let newGroup = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, "");
|
||||
if (Number.isFinite(+newGroup.charAt(0))) newGroup = "m" + newGroup;
|
||||
if (document.getElementById(newGroup)) {
|
||||
tip("Element with this id already exists. Please provide a unique name", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
markerInputGroup.value = "";
|
||||
// clone old group assigning new id
|
||||
const id = elSelected.attr("data-id");
|
||||
const clone = d3.select("#defs-markers").select(id).node().cloneNode(true);
|
||||
clone.id = newGroup;
|
||||
document.getElementById("defs-markers").insertBefore(clone, null);
|
||||
elSelected.attr("xlink:href", "#"+newGroup).attr("data-id", "#"+newGroup);
|
||||
|
||||
// select new group
|
||||
markerSelectGroup.options.add(new Option(newGroup, newGroup, false, true));
|
||||
toggleGroupInput();
|
||||
}
|
||||
|
||||
function removeGroup() {
|
||||
const id = elSelected.attr("data-id");
|
||||
const used = document.querySelectorAll("use[data-id='"+id+"']");
|
||||
const count = used.length === 1 ? "1 element" : used.length + " elements";
|
||||
alertMessage.innerHTML = "Are you sure you want to remove all markers of that type (" + count + ")?";
|
||||
|
||||
$("#alert").dialog({resizable: false, title: "Remove marker type",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
if (id !== "#marker0") d3.select("#defs-markers").select(id).remove();
|
||||
used.forEach(e => {
|
||||
const index = notes.findIndex(n => n.id === e.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
e.remove();
|
||||
});
|
||||
updateGroupOptions();
|
||||
updateGroupOptions();
|
||||
$("#markerEditor").dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleIconSection() {
|
||||
if (markerIconSection.style.display === "inline-block") {
|
||||
markerEditor.querySelectorAll("button:not(#markerIcon)").forEach(b => b.style.display = "inline-block");
|
||||
markerIconSection.style.display = "none";
|
||||
markerIconSelect.style.display = "none";
|
||||
} else {
|
||||
markerEditor.querySelectorAll("button:not(#markerIcon)").forEach(b => b.style.display = "none");
|
||||
markerIconSection.style.display = "inline-block";
|
||||
markerIconSelect.style.display = "inline-block";
|
||||
}
|
||||
}
|
||||
|
||||
function selectMarkerIcon() {
|
||||
selectIcon(this.innerHTML, v => {
|
||||
this.innerHTML = v;
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").text(v);
|
||||
selectIcon(marker.icon, icon => {
|
||||
markerIcon.value = icon;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconSize() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("font-size", this.value + "px");
|
||||
const px = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.px = px;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftX() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("x", this.value + "%");
|
||||
const dx = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dx = dx;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftY() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("y", this.value + "%");
|
||||
}
|
||||
|
||||
function toggleStyleSection() {
|
||||
if (markerStyleSection.style.display === "inline-block") {
|
||||
markerEditor.querySelectorAll("button:not(#markerStyle)").forEach(b => b.style.display = "inline-block");
|
||||
markerStyleSection.style.display = "none";
|
||||
} else {
|
||||
markerEditor.querySelectorAll("button:not(#markerStyle)").forEach(b => b.style.display = "none");
|
||||
markerStyleSection.style.display = "inline-block";
|
||||
}
|
||||
const dy = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dy = dy;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeMarkerSize() {
|
||||
const id = elSelected.attr("data-id");
|
||||
document.querySelectorAll("use[data-id='"+id+"']").forEach(e => {
|
||||
const x = +e.dataset.x, y = +e.dataset.y;
|
||||
const desired = e.dataset.size = +markerSize.value;
|
||||
const size = Math.max(desired * 5 + 25 / scale, 1);
|
||||
const size = +this.value;
|
||||
const rescale = +markers.attr("rescale");
|
||||
|
||||
e.setAttribute("x", x - size / 2);
|
||||
e.setAttribute("y", y - size / 2);
|
||||
e.setAttribute("width", size);
|
||||
e.setAttribute("height", size);
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.size = size;
|
||||
const {i, x, y, hidden} = marker;
|
||||
const el = !hidden && document.getElementById(`marker${i}`);
|
||||
if (!el) return;
|
||||
|
||||
const zoomedSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
|
||||
el.setAttribute("width", zoomedSize);
|
||||
el.setAttribute("height", zoomedSize);
|
||||
el.setAttribute("x", rn(x - zoomedSize / 2, 1));
|
||||
el.setAttribute("y", rn(y - zoomedSize, 1));
|
||||
});
|
||||
invokeActiveZooming();
|
||||
}
|
||||
|
||||
function changePinStroke() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select(id).select("path").attr("fill", this.value);
|
||||
d3.select(id).select("circle").attr("stroke", this.value);
|
||||
function changeMarkerPin() {
|
||||
const pin = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.pin = pin;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changePinFill() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select(id).select("circle").attr("fill", this.value);
|
||||
}
|
||||
|
||||
function changeIconStrokeWidth() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("stroke-width", this.value);
|
||||
}
|
||||
|
||||
function changeIconStroke() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("stroke", this.value);
|
||||
}
|
||||
|
||||
function changeIconFill() {
|
||||
const id = elSelected.attr("data-id");
|
||||
d3.select("#defs-markers").select(id).select("text").attr("fill", this.value);
|
||||
}
|
||||
|
||||
function togglePinVisibility() {
|
||||
const id = elSelected.attr("data-id");
|
||||
let show = 1;
|
||||
if (this.className === "icon-info-circled") {this.className = "icon-info"; show = 0; }
|
||||
else this.className = "icon-info-circled";
|
||||
d3.select(id).select("circle").attr("opacity", show);
|
||||
d3.select(id).select("path").attr("opacity", show);
|
||||
}
|
||||
|
||||
function editMarkerLegend() {
|
||||
const id = elSelected.attr("id");
|
||||
editNotes(id, id);
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
document.getElementById("addMarker").click();
|
||||
}
|
||||
|
||||
function removeMarker() {
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the marker?";
|
||||
$("#alert").dialog({resizable: false, title: "Remove marker",
|
||||
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");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
const fill = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.fill = fill;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changePinStroke() {
|
||||
const stroke = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.stroke = stroke;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function redrawIcon({i, hidden, icon, dx = 50, dy = 50, px = 12}) {
|
||||
const iconElement = !hidden && document.querySelector(`#marker${i} > text`);
|
||||
if (iconElement) {
|
||||
iconElement.innerHTML = icon;
|
||||
iconElement.setAttribute("x", dx + "%");
|
||||
iconElement.setAttribute("y", dy + "%");
|
||||
iconElement.setAttribute("font-size", px + "px");
|
||||
}
|
||||
}
|
||||
|
||||
function redrawPin({i, hidden, pin = "bubble", fill = "#fff", stroke = "#000"}) {
|
||||
const pinGroup = !hidden && document.querySelector(`#marker${i} > g`);
|
||||
if (pinGroup) pinGroup.innerHTML = getPin(pin, fill, stroke);
|
||||
}
|
||||
|
||||
function editMarkerLegend() {
|
||||
const id = element.id;
|
||||
editNotes(id, id);
|
||||
}
|
||||
|
||||
function toggleMarkerLock() {
|
||||
marker.lock = !marker.lock;
|
||||
markerLock.classList.toggle("icon-lock-open");
|
||||
markerLock.classList.toggle("icon-lock");
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
markerAdd.classList.toggle("pressed");
|
||||
addMarker.click();
|
||||
}
|
||||
|
||||
function confirmMarkerDeletion() {
|
||||
confirmationDialog({
|
||||
title: "Remove marker",
|
||||
message: "Are you sure you want to remove this marker? The action cannot be reverted",
|
||||
confirm: "Remove",
|
||||
onConfirm: deleteMarker
|
||||
});
|
||||
}
|
||||
|
||||
function deleteMarker() {
|
||||
notes = notes.filter(note => note.id !== element.id);
|
||||
pack.markers = pack.markers.filter(m => m.i !== marker.i);
|
||||
element.remove();
|
||||
$("#markerEditor").dialog("close");
|
||||
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function closeMarkerEditor() {
|
||||
listeners.forEach(removeListener => removeListener());
|
||||
|
||||
unselect();
|
||||
if (addMarker.classList.contains("pressed")) addMarker.classList.remove("pressed");
|
||||
if (markerAdd.classList.contains("pressed")) markerAdd.classList.remove("pressed");
|
||||
addMarker.classList.remove("pressed");
|
||||
markerAdd.classList.remove("pressed");
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
171
modules/ui/markers-overview.js
Normal file
171
modules/ui/markers-overview.js
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
"use strict";
|
||||
function overviewMarkers() {
|
||||
if (customization) return;
|
||||
closeDialogs("#markersOverview, .stable");
|
||||
if (!layerIsOn("toggleMarkers")) toggleMarkers();
|
||||
|
||||
const markerGroup = document.getElementById("markers");
|
||||
const body = document.getElementById("markersBody");
|
||||
const markersFooterNumber = document.getElementById("markersFooterNumber");
|
||||
const markersOverviewRefresh = document.getElementById("markersOverviewRefresh");
|
||||
const markersAddFromOverview = document.getElementById("markersAddFromOverview");
|
||||
const markersGenerationConfig = document.getElementById("markersGenerationConfig");
|
||||
const markersRemoveAll = document.getElementById("markersRemoveAll");
|
||||
const markersExport = document.getElementById("markersExport");
|
||||
|
||||
addLines();
|
||||
|
||||
$("#markersOverview").dialog({
|
||||
title: "Markers Overview",
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: close,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
const listeners = [
|
||||
listen(body, "click", handleLineClick),
|
||||
listen(markersOverviewRefresh, "click", addLines),
|
||||
listen(markersAddFromOverview, "click", toggleAddMarker),
|
||||
listen(markersGenerationConfig, "click", configMarkersGeneration),
|
||||
listen(markersRemoveAll, "click", triggerRemoveAll),
|
||||
listen(markersExport, "click", exportMarkers)
|
||||
];
|
||||
|
||||
function handleLineClick(ev) {
|
||||
const el = ev.target;
|
||||
const i = +el.parentNode.dataset.i;
|
||||
|
||||
if (el.classList.contains("icon-pencil")) return openEditor(i);
|
||||
if (el.classList.contains("icon-dot-circled")) return focusOnMarker(i);
|
||||
if (el.classList.contains("icon-pin")) return pinMarker(el, i);
|
||||
if (el.classList.contains("locks")) return toggleLockStatus(el, i);
|
||||
if (el.classList.contains("icon-trash-empty")) return triggerRemove(i);
|
||||
}
|
||||
|
||||
function addLines() {
|
||||
const lines = pack.markers
|
||||
.map(({i, type, icon, lock}) => {
|
||||
return `<div class="states" data-i=${i} data-type="${type}">
|
||||
<div data-tip="Marker icon and type" style="width:12em">${icon} ${type}</div>
|
||||
<span style="padding-right:.1em" data-tip="Edit marker" class="icon-pencil"></span>
|
||||
<span style="padding-right:.1em" data-tip="Focus on marker position" class="icon-dot-circled pointer"></span>
|
||||
<span style="padding-right:.1em" data-tip="Pin marker (display only pinned markers)" class="icon-pin inactive pointer"></span>
|
||||
<span style="padding-right:.1em" class="locks pointer ${lock ? "icon-lock" : "icon-lock-open inactive"}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span data-tip="Remove marker" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
body.innerHTML = lines;
|
||||
markersFooterNumber.innerText = pack.markers.length;
|
||||
|
||||
applySorting(markersHeader);
|
||||
}
|
||||
|
||||
function openEditor(i) {
|
||||
const marker = pack.markers.find(marker => marker.i === i);
|
||||
if (!marker) return;
|
||||
|
||||
const {x, y} = marker;
|
||||
zoomTo(x, y, 8, 2000);
|
||||
editMarker(i);
|
||||
}
|
||||
|
||||
function focusOnMarker(i) {
|
||||
highlightElement(document.getElementById(`marker${i}`), 2);
|
||||
}
|
||||
|
||||
function pinMarker(el, i) {
|
||||
const marker = pack.markers.find(marker => marker.i === i);
|
||||
if (marker.pinned) {
|
||||
delete marker.pinned;
|
||||
const anyPinned = pack.markers.some(marker => marker.pinned);
|
||||
if (!anyPinned) markerGroup.removeAttribute("pinned");
|
||||
} else {
|
||||
marker.pinned = true;
|
||||
markerGroup.setAttribute("pinned", 1);
|
||||
}
|
||||
el.classList.toggle("inactive");
|
||||
drawMarkers();
|
||||
}
|
||||
|
||||
function toggleLockStatus(el, i) {
|
||||
const marker = pack.markers.find(marker => marker.i === i);
|
||||
if (marker.lock) {
|
||||
delete marker.lock;
|
||||
el.className = "locks pointer icon-lock-open inactive";
|
||||
} else {
|
||||
marker.lock = true;
|
||||
el.className = "locks pointer icon-lock";
|
||||
}
|
||||
}
|
||||
|
||||
function triggerRemove(i) {
|
||||
confirmationDialog({
|
||||
title: "Remove marker",
|
||||
message: "Are you sure you want to remove this marker? The action cannot be reverted",
|
||||
confirm: "Remove",
|
||||
onConfirm: () => removeMarker(i)
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
markersAddFromOverview.classList.toggle("pressed");
|
||||
addMarker.click();
|
||||
}
|
||||
|
||||
function removeMarker(i) {
|
||||
notes = notes.filter(note => note.id !== `marker${i}`);
|
||||
pack.markers = pack.markers.filter(marker => marker.i !== i);
|
||||
document.getElementById(`marker${i}`)?.remove();
|
||||
addLines();
|
||||
}
|
||||
|
||||
function triggerRemoveAll() {
|
||||
confirmationDialog({
|
||||
title: "Remove all markers",
|
||||
message: "Are you sure you want to remove all non-locked markers? The action cannot be reverted",
|
||||
confirm: "Remove all",
|
||||
onConfirm: removeAllMarkers
|
||||
});
|
||||
}
|
||||
|
||||
function removeAllMarkers() {
|
||||
pack.markers = pack.markers.filter(({i, lock}) => {
|
||||
if (lock) return true;
|
||||
|
||||
const id = `marker${i}`;
|
||||
document.getElementById(id)?.remove();
|
||||
notes = notes.filter(note => note.id !== id);
|
||||
return false;
|
||||
});
|
||||
|
||||
addLines();
|
||||
}
|
||||
|
||||
function exportMarkers() {
|
||||
const headers = "Id,Type,Icon,Name,Note,X,Y\n";
|
||||
|
||||
const body = pack.markers.map(marker => {
|
||||
const {i, type, icon, x, y} = marker;
|
||||
const id = `marker${i}`;
|
||||
const note = notes.find(note => note.id === id);
|
||||
const legend = escape(note.legend);
|
||||
return [id, type, icon, note.name, legend, x, y].join(",");
|
||||
});
|
||||
|
||||
const data = headers + body.join("\n");
|
||||
const fileName = getFileName("Markers") + ".csv";
|
||||
downloadFile(data, fileName);
|
||||
}
|
||||
|
||||
function close() {
|
||||
listeners.forEach(removeListener => removeListener());
|
||||
|
||||
addMarker.classList.remove("pressed");
|
||||
markerAdd.classList.remove("pressed");
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
}
|
||||
}
|
||||
|
|
@ -108,7 +108,7 @@ function editNotes(id, name) {
|
|||
return;
|
||||
}
|
||||
|
||||
highlightElement(element); // if element is found
|
||||
highlightElement(element, 3); // if element is found
|
||||
}
|
||||
|
||||
function downloadLegends() {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ if (localStorage.getItem("disable_click_arrow_tooltip")) {
|
|||
|
||||
// Show options pane on trigger click
|
||||
function showOptions(event) {
|
||||
track("click", "show options");
|
||||
if (!localStorage.getItem("disable_click_arrow_tooltip")) {
|
||||
clearMainTip();
|
||||
localStorage.setItem("disable_click_arrow_tooltip", true);
|
||||
|
|
@ -75,6 +76,7 @@ document
|
|||
|
||||
// show popup with a list of Patreon supportes (updated manually, to be replaced with API call)
|
||||
function showSupporters() {
|
||||
track("click", "show supporters");
|
||||
const supporters = `Aaron Meyer,Ahmad Amerih,AstralJacks,aymeric,Billy Dean Goehring,Branndon Edwards,Chase Mayers,Curt Flood,cyninge,Dino Princip,
|
||||
E.M. White,es,Fondue,Fritjof Olsson,Gatsu,Johan Fröberg,Jonathan Moore,Joseph Miranda,Kate,KC138,Luke Nelson,Markus Finster,Massimo Vella,Mikey,
|
||||
Nathan Mitchell,Paavi1,Pat,Ryan Westcott,Sasquatch,Shawn Spencer,Sizz_TV,Timothée CALLET,UTG community,Vlad Tomash,Wil Sisney,William Merriott,
|
||||
|
|
@ -150,7 +152,9 @@ optionsContent.addEventListener("input", function (event) {
|
|||
else if (id === "regionsInput" || id === "regionsOutput") changeStatesNumber(value);
|
||||
else if (id === "emblemShape") changeEmblemShape(value);
|
||||
else if (id === "tooltipSizeInput" || id === "tooltipSizeOutput") changeTooltipSize(value);
|
||||
else if (id === "transparencyInput") changeDialogsTransparency(value);
|
||||
else if (id === "themeHueInput") changeThemeHue(value);
|
||||
else if (id === "themeColorInput") changeDialogsTheme(themeColorInput.value, transparencyInput.value);
|
||||
else if (id === "transparencyInput") changeDialogsTheme(themeColorInput.value, value);
|
||||
});
|
||||
|
||||
optionsContent.addEventListener("change", function (event) {
|
||||
|
|
@ -158,7 +162,7 @@ optionsContent.addEventListener("change", function (event) {
|
|||
const value = event.target.value;
|
||||
|
||||
if (id === "zoomExtentMin" || id === "zoomExtentMax") changeZoomExtent(value);
|
||||
else if (id === "optionsSeed") generateMapWithSeed();
|
||||
else if (id === "optionsSeed") generateMapWithSeed("seed change");
|
||||
else if (id === "uiSizeInput" || id === "uiSizeOutput") changeUIsize(value);
|
||||
if (id === "shapeRendering") viewbox.attr("shape-rendering", value);
|
||||
else if (id === "yearInput") changeYear();
|
||||
|
|
@ -168,13 +172,13 @@ optionsContent.addEventListener("change", function (event) {
|
|||
optionsContent.addEventListener("click", function (event) {
|
||||
const id = event.target.id;
|
||||
if (id === "toggleFullscreen") toggleFullscreen();
|
||||
else if (id === "optionsSeedGenerate") generateMapWithSeed();
|
||||
else if (id === "optionsMapHistory") showSeedHistoryDialog();
|
||||
else if (id === "optionsCopySeed") copyMapURL();
|
||||
else if (id === "optionsEraRegenerate") regenerateEra();
|
||||
else if (id === "zoomExtentDefault") restoreDefaultZoomExtent();
|
||||
else if (id === "translateExtent") toggleTranslateExtent(event.target);
|
||||
else if (id === "speakerTest") testSpeaker();
|
||||
else if (id === "themeColorRestore") restoreDefaultThemeColor();
|
||||
});
|
||||
|
||||
function mapSizeInputChange() {
|
||||
|
|
@ -208,8 +212,8 @@ function changeMapSize() {
|
|||
|
||||
// just apply canvas size that was already set
|
||||
function applyMapSize() {
|
||||
const zoomMin = +zoomExtentMin.value,
|
||||
zoomMax = +zoomExtentMax.value;
|
||||
const zoomMin = +zoomExtentMin.value;
|
||||
const zoomMax = +zoomExtentMax.value;
|
||||
graphWidth = +mapWidthInput.value;
|
||||
graphHeight = +mapHeightInput.value;
|
||||
svgWidth = Math.min(graphWidth, window.innerWidth);
|
||||
|
|
@ -277,12 +281,9 @@ function testSpeaker() {
|
|||
speechSynthesis.speak(speaker);
|
||||
}
|
||||
|
||||
function generateMapWithSeed() {
|
||||
if (optionsSeed.value == seed) {
|
||||
tip("The current map already has this seed", false, "error");
|
||||
return;
|
||||
}
|
||||
regeneratePrompt();
|
||||
function generateMapWithSeed(source) {
|
||||
if (optionsSeed.value == seed) return tip("The current map already has this seed", false, "error");
|
||||
regeneratePrompt(source);
|
||||
}
|
||||
|
||||
function showSeedHistoryDialog() {
|
||||
|
|
@ -313,7 +314,7 @@ function restoreSeed(id) {
|
|||
mapHeightInput.value = mapHistory[id].height;
|
||||
templateInput.value = mapHistory[id].template;
|
||||
if (locked("template")) unlock("template");
|
||||
regeneratePrompt();
|
||||
regeneratePrompt("seed history");
|
||||
}
|
||||
|
||||
function restoreDefaultZoomExtent() {
|
||||
|
|
@ -417,7 +418,7 @@ function changeUIsize(value) {
|
|||
if (+value > max) value = max;
|
||||
|
||||
uiSizeInput.value = uiSizeOutput.value = value;
|
||||
document.getElementsByTagName("body")[0].style.fontSize = value * 11 + "px";
|
||||
document.getElementsByTagName("body")[0].style.fontSize = rn(value * 10, 2) + "px";
|
||||
document.getElementById("options").style.width = value * 300 + "px";
|
||||
}
|
||||
|
||||
|
|
@ -429,19 +430,49 @@ function changeTooltipSize(value) {
|
|||
tooltip.style.fontSize = `calc(${value}px + 0.5vw)`;
|
||||
}
|
||||
|
||||
// change transparency for modal windows
|
||||
function changeDialogsTransparency(value) {
|
||||
transparencyInput.value = transparencyOutput.value = value;
|
||||
const alpha = (100 - +value) / 100;
|
||||
const optionsColor = "rgba(164, 139, 149, " + alpha + ")";
|
||||
const dialogsColor = "rgba(255, 255, 255, " + alpha + ")";
|
||||
const optionButtonsColor = "rgba(145, 110, 127, " + Math.min(alpha + 0.3, 1) + ")";
|
||||
const optionLiColor = "rgba(153, 123, 137, " + Math.min(alpha + 0.3, 1) + ")";
|
||||
document.getElementById("options").style.backgroundColor = optionsColor;
|
||||
document.getElementById("dialogs").style.backgroundColor = dialogsColor;
|
||||
document.querySelectorAll(".tabcontent button").forEach(el => (el.style.backgroundColor = optionButtonsColor));
|
||||
document.querySelectorAll(".tabcontent li").forEach(el => (el.style.backgroundColor = optionLiColor));
|
||||
document.querySelectorAll("button.options").forEach(el => (el.style.backgroundColor = optionLiColor));
|
||||
const THEME_COLOR = "#997787";
|
||||
function restoreDefaultThemeColor() {
|
||||
localStorage.removeItem("themeColor");
|
||||
changeDialogsTheme(THEME_COLOR, transparencyInput.value);
|
||||
}
|
||||
|
||||
function changeThemeHue(hue) {
|
||||
const {s, l} = d3.hsl(themeColorInput.value);
|
||||
const newColor = d3.hsl(+hue, s, l).hex();
|
||||
changeDialogsTheme(newColor, transparencyInput.value);
|
||||
}
|
||||
|
||||
// change color and transparency for modal windows
|
||||
function changeDialogsTheme(themeColor, transparency) {
|
||||
transparencyInput.value = transparencyOutput.value = transparency;
|
||||
const alpha = (100 - +transparency) / 100;
|
||||
const alphaReduced = Math.min(alpha + 0.3, 1);
|
||||
|
||||
const {h, s, l} = d3.hsl(themeColor || THEME_COLOR);
|
||||
themeColorInput.value = themeColor || THEME_COLOR;
|
||||
themeHueInput.value = h;
|
||||
|
||||
const getRGBA = (hue, saturation, lightness, alpha) => {
|
||||
const color = d3.hsl(hue, saturation, lightness, alpha);
|
||||
return color.toString();
|
||||
};
|
||||
|
||||
const theme = [
|
||||
{name: "--bg-main", h, s, l, alpha},
|
||||
{name: "--bg-lighter", h, s, l: l + 0.02, alpha},
|
||||
{name: "--bg-light", h, s: s - 0.02, l: l + 0.06, alpha},
|
||||
{name: "--light-solid", h, s: s + 0.01, l: l + 0.05, alpha: 1},
|
||||
{name: "--dark-solid", h, s, l: l - 0.2, alpha: 1},
|
||||
{name: "--header", h, s: s, l: l - 0.03, alpha: alphaReduced},
|
||||
{name: "--header-active", h, s: s, l: l - 0.09, alpha: alphaReduced},
|
||||
{name: "--bg-disabled", h, s: s - 0.04, l: l + 0.09, alphaReduced},
|
||||
{name: "--bg-dialogs", h: 0, s: 0, l: 0.98, alpha}
|
||||
];
|
||||
|
||||
const sx = document.documentElement.style;
|
||||
theme.forEach(({name, h, s, l, alpha}) => {
|
||||
sx.setProperty(name, getRGBA(h, s, l, alpha));
|
||||
});
|
||||
}
|
||||
|
||||
function changeZoomExtent(value) {
|
||||
|
|
@ -484,7 +515,6 @@ function applyStoredOptions() {
|
|||
.map(w => +w);
|
||||
if (localStorage.getItem("military")) options.military = JSON.parse(localStorage.getItem("military"));
|
||||
|
||||
changeDialogsTransparency(localStorage.getItem("transparency") || 5);
|
||||
if (localStorage.getItem("tooltipSize")) changeTooltipSize(localStorage.getItem("tooltipSize"));
|
||||
if (localStorage.getItem("regions")) changeStatesNumber(localStorage.getItem("regions"));
|
||||
|
||||
|
|
@ -499,6 +529,10 @@ function applyStoredOptions() {
|
|||
if (width) mapWidthInput.value = width;
|
||||
if (height) mapHeightInput.value = height;
|
||||
|
||||
const transparency = localStorage.getItem("transparency") || 5;
|
||||
const themeColor = localStorage.getItem("themeColor");
|
||||
changeDialogsTheme(themeColor, transparency);
|
||||
|
||||
// set shape rendering
|
||||
viewbox.attr("shape-rendering", shapeRendering.value);
|
||||
}
|
||||
|
|
@ -531,10 +565,9 @@ function randomizeOptions() {
|
|||
|
||||
// 'Units Editor' settings
|
||||
const US = navigator.language === "en-US";
|
||||
const UK = navigator.language === "en-GB";
|
||||
if (randomize || !locked("distanceScale")) distanceScaleOutput.value = distanceScaleInput.value = gauss(3, 1, 1, 5);
|
||||
if (!stored("distanceUnit")) distanceUnitInput.value = US || UK ? "mi" : "km";
|
||||
if (!stored("heightUnit")) heightUnit.value = US || UK ? "ft" : "m";
|
||||
if (!stored("distanceUnit")) distanceUnitInput.value = US ? "mi" : "km";
|
||||
if (!stored("heightUnit")) heightUnit.value = US ? "ft" : "m";
|
||||
if (!stored("temperatureScale")) temperatureScale.value = US ? "°F" : "°C";
|
||||
|
||||
// World settings
|
||||
|
|
@ -621,23 +654,17 @@ function restoreDefaultOptions() {
|
|||
// Sticked menu Options listeners
|
||||
document.getElementById("sticked").addEventListener("click", function (event) {
|
||||
const id = event.target.id;
|
||||
if (id === "newMapButton") regeneratePrompt();
|
||||
if (id === "newMapButton") regeneratePrompt("sticky button");
|
||||
else if (id === "saveButton") showSavePane();
|
||||
else if (id === "exportButton") showExportPane();
|
||||
else if (id === "loadButton") showLoadPane();
|
||||
else if (id === "zoomReset") resetZoom(1000);
|
||||
});
|
||||
|
||||
function regeneratePrompt() {
|
||||
if (customization) {
|
||||
tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error");
|
||||
return;
|
||||
}
|
||||
function regeneratePrompt(source) {
|
||||
if (customization) return tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error");
|
||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||
if (workingTime < 5) {
|
||||
regenerateMap();
|
||||
return;
|
||||
}
|
||||
if (workingTime < 5) return regenerateMap(source);
|
||||
|
||||
alertMessage.innerHTML = `Are you sure you want to generate a new map?<br>
|
||||
All unsaved changes made to the current map will be lost`;
|
||||
|
|
@ -650,7 +677,7 @@ function regeneratePrompt() {
|
|||
},
|
||||
Generate: function () {
|
||||
closeDialogs();
|
||||
regenerateMap();
|
||||
regenerateMap(source);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -757,6 +784,7 @@ function loadURL() {
|
|||
|
||||
// load map
|
||||
document.getElementById("mapToLoad").addEventListener("change", function () {
|
||||
track("load", `from local file`);
|
||||
const fileToLoad = this.files[0];
|
||||
this.value = "";
|
||||
closeDialogs();
|
||||
|
|
@ -770,12 +798,16 @@ function openSaveTiles() {
|
|||
status.innerHTML = "";
|
||||
let loading = null;
|
||||
|
||||
const inputs = document.getElementById("saveTilesScreen").querySelectorAll("input");
|
||||
inputs.forEach(input => input.addEventListener("input", updateTilesOptions));
|
||||
|
||||
$("#saveTilesScreen").dialog({
|
||||
resizable: false,
|
||||
title: "Download tiles",
|
||||
width: "23em",
|
||||
buttons: {
|
||||
Download: function () {
|
||||
track("export", `tiles`);
|
||||
status.innerHTML = "Preparing for download...";
|
||||
setTimeout(() => (status.innerHTML = "Downloading. It may take some time."), 1000);
|
||||
loading = setInterval(() => (status.innerHTML += "."), 1000);
|
||||
|
|
@ -790,17 +822,13 @@ function openSaveTiles() {
|
|||
}
|
||||
},
|
||||
close: () => {
|
||||
inputs.forEach(input => input.removeEventListener("input", updateTilesOptions));
|
||||
debug.selectAll("*").remove();
|
||||
clearInterval(loading);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("saveTilesScreen")
|
||||
.querySelectorAll("input")
|
||||
.forEach(el => el.addEventListener("input", updateTilesOptions));
|
||||
|
||||
function updateTilesOptions() {
|
||||
if (this?.tagName === "INPUT") {
|
||||
const {nextElementSibling: next, previousElementSibling: prev} = this;
|
||||
|
|
@ -865,6 +893,7 @@ function enterStandardView() {
|
|||
}
|
||||
|
||||
async function enter3dView(type) {
|
||||
track("click", `3d mode: ${type}`);
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.id = "canvas3d";
|
||||
canvas.dataset.type = type;
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ function overviewRivers() {
|
|||
function zoomToRiver() {
|
||||
const r = +this.parentNode.dataset.id;
|
||||
const river = rivers.select("#river" + r).node();
|
||||
highlightElement(river);
|
||||
highlightElement(river, 3);
|
||||
}
|
||||
|
||||
function toggleBasinsHightlight() {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||
"use strict";
|
||||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||
|
||||
toolsContent.addEventListener("click", function (event) {
|
||||
if (customization) {
|
||||
tip("Please exit the customization mode first", false, "warning");
|
||||
return;
|
||||
}
|
||||
if (event.target.tagName !== "BUTTON") return;
|
||||
if (!["BUTTON", "I"].includes(event.target.tagName)) return;
|
||||
const button = event.target.id;
|
||||
|
||||
// Click to open Editor buttons
|
||||
// click on open Editor buttons
|
||||
if (button === "editHeightmapButton") editHeightmap();
|
||||
else if (button === "editBiomesButton") editBiomes();
|
||||
else if (button === "editStatesButton") editStates();
|
||||
|
|
@ -25,9 +25,10 @@ toolsContent.addEventListener("click", function (event) {
|
|||
else if (button === "overviewBurgsButton") overviewBurgs();
|
||||
else if (button === "overviewRiversButton") overviewRivers();
|
||||
else if (button === "overviewMilitaryButton") overviewMilitary();
|
||||
else if (button === "overviewMarkersButton") overviewMarkers();
|
||||
else if (button === "overviewCellsButton") viewCellDetails();
|
||||
|
||||
// Click to Regenerate buttons
|
||||
// click on Regenerate buttons
|
||||
if (event.target.parentNode.id === "regenerateFeature") {
|
||||
if (sessionStorage.getItem("regenerateFeatureDontAsk")) {
|
||||
processFeatureRegeneration(event, button);
|
||||
|
|
@ -49,7 +50,9 @@ toolsContent.addEventListener("click", function (event) {
|
|||
},
|
||||
open: function () {
|
||||
const pane = $(this).dialog("widget").find(".ui-dialog-buttonpane");
|
||||
$('<span><input id="dontAsk" class="checkbox" type="checkbox"><label for="dontAsk" class="checkbox-label dontAsk"><i>do not ask again</i></label><span>').prependTo(pane);
|
||||
$(
|
||||
'<span><input id="dontAsk" class="checkbox" type="checkbox"><label for="dontAsk" class="checkbox-label dontAsk"><i>do not ask again</i></label><span>'
|
||||
).prependTo(pane);
|
||||
},
|
||||
close: function () {
|
||||
const box = $(this).dialog("widget").find(".checkbox")[0];
|
||||
|
|
@ -60,7 +63,10 @@ toolsContent.addEventListener("click", function (event) {
|
|||
});
|
||||
}
|
||||
|
||||
// Click to Add buttons
|
||||
// click on Configure regenerate buttons
|
||||
if (button === "configRegenerateMarkers") configMarkersGeneration();
|
||||
|
||||
// click on Add buttons
|
||||
if (button === "addLabel") toggleAddLabel();
|
||||
else if (button === "addBurgTool") toggleAddBurg();
|
||||
else if (button === "addRiver") toggleAddRiver();
|
||||
|
|
@ -88,7 +94,7 @@ function processFeatureRegeneration(event, button) {
|
|||
else if (button === "regenerateCultures") regenerateCultures();
|
||||
else if (button === "regenerateMilitary") regenerateMilitary();
|
||||
else if (button === "regenerateIce") regenerateIce();
|
||||
else if (button === "regenerateMarkers") regenerateMarkers(event);
|
||||
else if (button === "regenerateMarkers") regenerateMarkers();
|
||||
else if (button === "regenerateZones") regenerateZones(event);
|
||||
}
|
||||
|
||||
|
|
@ -262,7 +268,8 @@ 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 / 5 / (grid.points.length / 10000) ** 0.8) + states.length : +manorsInput.value + states.length;
|
||||
const burgsCount =
|
||||
manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** 0.8) + states.length : +manorsInput.value + states.length;
|
||||
const spacing = (graphWidth + graphHeight) / 150 / (burgsCount ** 0.7 / 66); // base min distance between towns
|
||||
|
||||
//clear locked list since ids will change
|
||||
|
|
@ -413,23 +420,11 @@ function regenerateIce() {
|
|||
drawIce();
|
||||
}
|
||||
|
||||
function regenerateMarkers(event) {
|
||||
if (isCtrlClick(event)) prompt("Please provide markers number multiplier", {default: 1, step: 0.01, min: 0, max: 100}, v => addNumberOfMarkers(v));
|
||||
else addNumberOfMarkers(gauss(1, 0.5, 0.3, 5, 2));
|
||||
|
||||
function addNumberOfMarkers(number) {
|
||||
// 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();
|
||||
|
||||
Markers.generate();
|
||||
if (!layerIsOn("toggleMarkers")) toggleMarkers();
|
||||
}
|
||||
function regenerateMarkers() {
|
||||
Markers.regenerate();
|
||||
turnButtonOn("toggleMarkers");
|
||||
drawMarkers();
|
||||
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateZones(event) {
|
||||
|
|
@ -478,7 +473,18 @@ function addLabelOnClick() {
|
|||
let selected = labelGroupSelect.value;
|
||||
const symbol = selected ? "#" + selected : "#addedLabels";
|
||||
let group = labels.select(symbol);
|
||||
if (!group.size()) group = labels.append("g").attr("id", "addedLabels").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("font-size", 18).attr("data-size", 18).attr("filter", null);
|
||||
if (!group.size())
|
||||
group = labels
|
||||
.append("g")
|
||||
.attr("id", "addedLabels")
|
||||
.attr("fill", "#3e3e4b")
|
||||
.attr("opacity", 1)
|
||||
.attr("stroke", "#3a3a3a")
|
||||
.attr("stroke-width", 0)
|
||||
.attr("font-family", "Almendra SC")
|
||||
.attr("font-size", 18)
|
||||
.attr("data-size", 18)
|
||||
.attr("filter", null);
|
||||
|
||||
const example = group.append("text").attr("x", 0).attr("x", 0).text(name);
|
||||
const width = example.node().getBBox().width;
|
||||
|
|
@ -677,7 +683,7 @@ function addRouteOnClick() {
|
|||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
const pressed = document.getElementById("addMarker").classList.contains("pressed");
|
||||
const pressed = document.getElementById("addMarker")?.classList.contains("pressed");
|
||||
if (pressed) {
|
||||
unpressClickToAddButton();
|
||||
return;
|
||||
|
|
@ -685,45 +691,115 @@ function toggleAddMarker() {
|
|||
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
addMarker.classList.add("pressed");
|
||||
closeDialogs(".stable");
|
||||
markersAddFromOverview.classList.add("pressed");
|
||||
|
||||
viewbox.style("cursor", "crosshair").on("click", addMarkerOnClick);
|
||||
tip("Click on map to add a marker. Hold Shift to add multiple", true);
|
||||
if (!layerIsOn("toggleMarkers")) toggleMarkers();
|
||||
}
|
||||
|
||||
function addMarkerOnClick() {
|
||||
const {markers} = pack;
|
||||
const point = d3.mouse(this);
|
||||
const x = rn(point[0], 2),
|
||||
y = rn(point[1], 2);
|
||||
const id = getNextId("markerElement");
|
||||
const x = rn(point[0], 2);
|
||||
const y = rn(point[1], 2);
|
||||
const i = last(markers).i + 1;
|
||||
|
||||
const selected = markerSelectGroup.value;
|
||||
const valid =
|
||||
selected &&
|
||||
d3
|
||||
.select("#defs-markers")
|
||||
.select("#" + selected)
|
||||
.size();
|
||||
const symbol = valid ? "#" + selected : "#marker0";
|
||||
const added = markers.select("[data-id='" + symbol + "']").size();
|
||||
let desired = valid && added ? markers.select("[data-id='" + symbol + "']").attr("data-size") : 1;
|
||||
if (isNaN(desired)) desired = 1;
|
||||
const size = desired * 5 + 25 / scale;
|
||||
const isMarkerSelected = elSelected?.node()?.parentElement?.id === "markers";
|
||||
const selectedMarker = isMarkerSelected ? markers.find(marker => marker.i === +elSelected.attr("id").slice(6)) : null;
|
||||
const baseMarker = selectedMarker || {icon: "❓"};
|
||||
const marker = {...baseMarker, i, x, y};
|
||||
|
||||
markers
|
||||
.append("use")
|
||||
.attr("id", id)
|
||||
.attr("xlink:href", symbol)
|
||||
.attr("data-id", symbol)
|
||||
.attr("data-x", x)
|
||||
.attr("data-y", y)
|
||||
.attr("x", x - size / 2)
|
||||
.attr("y", y - size)
|
||||
.attr("data-size", desired)
|
||||
.attr("width", size)
|
||||
.attr("height", size);
|
||||
markers.push(marker);
|
||||
const markersElement = document.getElementById("markers");
|
||||
const rescale = +markersElement.getAttribute("rescale");
|
||||
markersElement.insertAdjacentHTML("beforeend", drawMarker(marker, rescale));
|
||||
|
||||
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
||||
if (d3.event.shiftKey === false) {
|
||||
document.getElementById("markerAdd").classList.remove("pressed");
|
||||
document.getElementById("markersAddFromOverview").classList.remove("pressed");
|
||||
unpressClickToAddButton();
|
||||
}
|
||||
}
|
||||
|
||||
function configMarkersGeneration() {
|
||||
drawConfigTable();
|
||||
|
||||
function drawConfigTable() {
|
||||
const {markers} = pack;
|
||||
const config = Markers.getConfig();
|
||||
const headers = `<thead style='font-weight:bold'><tr>
|
||||
<td data-tip="Marker type name">Type</td>
|
||||
<td data-tip="Marker icon">Icon</td>
|
||||
<td data-tip="Marker number multiplier">Multiplier</td>
|
||||
<td data-tip="Number of markers of that type on the current map">Number</td>
|
||||
</tr></thead>`;
|
||||
const lines = config.map(({type, icon, multiplier}, index) => {
|
||||
const inputId = `markerIconInput${index}`;
|
||||
return `<tr>
|
||||
<td><input value="${type}" /></td>
|
||||
<td>
|
||||
<input id="${inputId}" style="width: 5em" value="${icon}" />
|
||||
<i class="icon-edit pointer" style="position: absolute; margin:.4em 0 0 -1.4em; font-size:.85em"></i>
|
||||
</td>
|
||||
<td><input type="number" min="0" max="100" step="0.1" value="${multiplier}" /></td>
|
||||
<td style="text-align:center">${markers.filter(marker => marker.type === type).length}</td>
|
||||
</tr>`;
|
||||
});
|
||||
const table = `<table class="table">${headers}<tbody>${lines.join("")}</tbody></table>`;
|
||||
alertMessage.innerHTML = table;
|
||||
|
||||
alertMessage.querySelectorAll("i").forEach(selectIconButton => {
|
||||
selectIconButton.addEventListener("click", function () {
|
||||
const input = this.previousElementSibling;
|
||||
selectIcon(input.value, icon => (input.value = icon));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const applyChanges = () => {
|
||||
const rows = alertMessage.querySelectorAll("tbody > tr");
|
||||
const rowsData = Array.from(rows).map(row => {
|
||||
const inputs = row.querySelectorAll("input");
|
||||
return {
|
||||
type: inputs[0].value,
|
||||
icon: inputs[1].value,
|
||||
multiplier: parseFloat(inputs[2].value)
|
||||
};
|
||||
});
|
||||
|
||||
const config = Markers.getConfig();
|
||||
const newConfig = config.map((markerType, index) => {
|
||||
const {type, icon, multiplier} = rowsData[index];
|
||||
return {...markerType, type, icon, multiplier};
|
||||
});
|
||||
|
||||
Markers.setConfig(newConfig);
|
||||
};
|
||||
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Markers generation settings",
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"},
|
||||
buttons: {
|
||||
Regenerate: () => {
|
||||
applyChanges();
|
||||
regenerateMarkers();
|
||||
drawConfigTable();
|
||||
},
|
||||
Close: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
},
|
||||
open: function () {
|
||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
|
||||
buttons[0].addEventListener("mousemove", () => tip("Apply changes and regenerate markers"));
|
||||
buttons[1].addEventListener("mousemove", () => tip("Close the window"));
|
||||
},
|
||||
close: function () {
|
||||
$(this).dialog("destroy");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function viewCellDetails() {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ function editWorld() {
|
|||
buttons[2].addEventListener("mousemove", () => tip("Click to set map size to cover the Tropical latitudes"));
|
||||
buttons[3].addEventListener("mousemove", () => tip("Click to set map size to cover the Southern latitudes"));
|
||||
buttons[4].addEventListener("mousemove", () => tip("Click to restore default wind directions"));
|
||||
},
|
||||
close: function () {
|
||||
$(this).dialog("destroy");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ function getBoundaryPoints(width, height, spacing) {
|
|||
function getJitteredGrid(width, height, spacing) {
|
||||
const radius = spacing / 2; // square radius
|
||||
const jittering = radius * 0.9; // max deviation
|
||||
const jitter = () => Math.random() * 2 * jittering - jittering;
|
||||
const doubleJittering = jittering * 2;
|
||||
const jitter = () => Math.random() * doubleJittering - jittering;
|
||||
|
||||
let points = [];
|
||||
for (let y = radius; y < height; y += spacing) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue