mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
1.3.2
This commit is contained in:
commit
834e9423c1
35 changed files with 2511 additions and 579 deletions
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"file": "${workspaceFolder}/index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -250,7 +250,9 @@
|
|||
.icon-smooth:before {font-weight: bold;content:'∼';}
|
||||
.icon-disrupt:before {font-weight: bold;content:'෴';}
|
||||
.icon-if:before {font-style: italic; font-weight: bold;content:'if';}
|
||||
.icon-fleur:before {content: '⚜'; font-size: 1.1em; margin: -2px;}
|
||||
/* .icon-coa:before {content: '⚜'; font-size: 1.1em; margin: -2px;} */
|
||||
.icon-coa:before {content:'\f3ed'; font-size: .9em; color: #999;} /* '' */
|
||||
.icon-half:before {font-weight: bold;content:'½';}
|
||||
.icon-curve:before {content: 'C';}
|
||||
.icon-area:before {content: 'O';}
|
||||
.icon-curve:before,
|
||||
|
|
|
|||
188
index.css
188
index.css
|
|
@ -10,16 +10,40 @@ body {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
t {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input, select, button {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
border: 0.5px solid #DBDFE6;
|
||||
border-radius: .5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
select {
|
||||
height: 1.6em;
|
||||
border-top-color: #abadb3;
|
||||
padding: 0;
|
||||
text-indent: 0px;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 1.1em;
|
||||
border-top-color: #abadb3;
|
||||
padding: 2px;
|
||||
text-indent: 1px;
|
||||
}
|
||||
|
||||
input:read-only {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
textarea {
|
||||
padding: 2px;
|
||||
text-indent: 1px;
|
||||
}
|
||||
|
||||
#map {
|
||||
|
|
@ -50,11 +74,7 @@ input, button, select, a, textarea {
|
|||
outline: none;
|
||||
}
|
||||
|
||||
button, select, a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
button, select, a, .pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +99,7 @@ button, select, a {
|
|||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
#lakes, #coastline {
|
||||
#lakes, #coastline, #armies {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
@ -101,10 +121,15 @@ button, select, a {
|
|||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
#regions, #provs, #terrs, #biomes, #tooltip, #temperature, #texture, #landmass {
|
||||
#regions, #cults, #relig, #biomes, #provs, #terrs, #biomes, #tooltip, #temperature, #texture, #landmass {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#armies text {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#statesBody, #provincesBody, #relig, #biomes, #cults {
|
||||
stroke-width: .6;
|
||||
fill-rule: evenodd;
|
||||
|
|
@ -231,8 +256,8 @@ i.icon-lock {
|
|||
marker-end: url(#end-arrow);
|
||||
stroke: #333333;
|
||||
stroke-dasharray: 5;
|
||||
stroke-dashoffset: 100;
|
||||
animation: dash 8s linear backwards;
|
||||
stroke-dashoffset: 1000;
|
||||
animation: dash 80s linear backwards;
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
|
|
@ -375,6 +400,8 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
margin-left: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#options input[type="range"]::-webkit-slider-thumb {
|
||||
|
|
@ -412,17 +439,12 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
}
|
||||
|
||||
#options input[type="number"] {
|
||||
height: 1.3em;
|
||||
line-height: 1.2em;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
#options input[type="text"] {
|
||||
border: 0px;
|
||||
padding-left: 3px;
|
||||
width: 62%;
|
||||
height: 1.1em;
|
||||
line-height: 1.3em;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
|
|
@ -441,8 +463,8 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
|
||||
#optionsContent input[type=number]::-webkit-inner-spin-button,
|
||||
#optionsContent input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#optionsContent input[type="number"]:hover {
|
||||
|
|
@ -450,8 +472,6 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
}
|
||||
|
||||
#optionsContent input.paired {
|
||||
width: 2.5em;
|
||||
margin-left: -.3em;
|
||||
text-align: center;
|
||||
background-color: white;
|
||||
}
|
||||
|
|
@ -460,8 +480,6 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
width: 100%;
|
||||
background-color: white;
|
||||
text-align: left;
|
||||
padding-left: 0.4em;
|
||||
box-sizing: border-box;
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
|
|
@ -509,7 +527,7 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
|||
#styleElements input[type="number"] {
|
||||
width: 52px;
|
||||
border: 0;
|
||||
padding-left: 2.5px;
|
||||
padding-left: 2.5px;
|
||||
}
|
||||
|
||||
#sticked button {
|
||||
|
|
@ -1006,6 +1024,7 @@ i.resetButton:active {
|
|||
}
|
||||
|
||||
.ui-dialog input[type="range"] {
|
||||
padding: 0;
|
||||
height: 2px;
|
||||
background: #d4d4d4;
|
||||
top: -.35em;
|
||||
|
|
@ -1035,8 +1054,7 @@ i.resetButton:active {
|
|||
}
|
||||
|
||||
.ui-dialog input[type="number"] {
|
||||
width: 2.5em;
|
||||
height: 1.1em;
|
||||
width: 3.5em;
|
||||
}
|
||||
|
||||
.ui-dialog .disabled {
|
||||
|
|
@ -1136,6 +1154,9 @@ div.header > div {
|
|||
font-size: .9em;
|
||||
display: inline-block;
|
||||
position: sticky;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.sortable {
|
||||
|
|
@ -1168,7 +1189,8 @@ div.states:hover {
|
|||
}
|
||||
|
||||
div.states > *,
|
||||
div.states sup {
|
||||
div.states sup,
|
||||
div.totalLine > div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
|
@ -1182,15 +1204,8 @@ div.states div {
|
|||
width: 3.2em;
|
||||
}
|
||||
|
||||
div.states .statePower {
|
||||
width: 3.2em;
|
||||
line-height: 1.4em;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
div.states .biomeHabitability {
|
||||
width: 3.6em;
|
||||
line-height: 1.4em;
|
||||
div.states .statePower, div.states .biomeHabitability {
|
||||
width: 4em;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
|
|
@ -1209,13 +1224,9 @@ div.states>.statePopulation {
|
|||
div.states .icon-pencil,
|
||||
div.states .icon-trash-empty,
|
||||
div.states .icon-eye,
|
||||
div.states .icon-pin {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.states .icon-pin,
|
||||
div.states .icon-flag-empty {
|
||||
cursor: pointer;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
div.states .icon-resize-vertical {
|
||||
|
|
@ -1332,18 +1343,22 @@ div.states>input.riverType {
|
|||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
#stateNameEditor input,
|
||||
#provinceNameEditor input {
|
||||
padding-left: .3em;
|
||||
}
|
||||
|
||||
#stateNameEditor div.label,
|
||||
#provinceNameEditor div.label,
|
||||
#burgBody div.label {
|
||||
#burgBody div.label,
|
||||
#regimentBody div.label {
|
||||
display: inline-block;
|
||||
width: 5.5em;
|
||||
}
|
||||
|
||||
#regimentBody div {
|
||||
margin: .1em 0;
|
||||
}
|
||||
|
||||
#regimentBody input[type="number"] {
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
.burgFeature {
|
||||
font-size: 1.2em;
|
||||
padding: 1px 2px;
|
||||
|
|
@ -1401,6 +1416,7 @@ div.states.Self {
|
|||
border-color: #858b8e;
|
||||
background-image: linear-gradient(to right, #f2f2f2 0%, #b0c6d9 100%);
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
margin-bottom: .2em;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
|
@ -1423,18 +1439,21 @@ rect.fillRect {
|
|||
stroke-width: 2;
|
||||
}
|
||||
|
||||
#militaryBody div.states > div {
|
||||
width: 4em;
|
||||
#militaryHeader > div,
|
||||
#regimentsHeader > div {
|
||||
width: 5.2em;
|
||||
}
|
||||
|
||||
#militaryBody div.states > input.militaryArmy {
|
||||
#militaryBody div.states > input {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
#militaryBody div.states > input,
|
||||
#militaryBody div.states > div,
|
||||
#regimentsBody div.states > div {
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
#militaryBody div.states > input.militaryFleet {
|
||||
width: 3.3em;
|
||||
}
|
||||
|
||||
#picker text {
|
||||
cursor: default;
|
||||
}
|
||||
|
|
@ -1522,6 +1541,7 @@ rect.fillRect {
|
|||
|
||||
#unitsBody>div>* {
|
||||
display: inline-block;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
|
||||
.unitsHeader {
|
||||
|
|
@ -1550,7 +1570,6 @@ rect.fillRect {
|
|||
width: 11.32em;
|
||||
}
|
||||
|
||||
|
||||
#unitsBody>div>input[type="number"] {
|
||||
width: 3.4em;
|
||||
}
|
||||
|
|
@ -1570,8 +1589,9 @@ rect.fillRect {
|
|||
}
|
||||
|
||||
#barBackColor {
|
||||
width: 3.45em;
|
||||
width: 3.5em;
|
||||
padding: 0px;
|
||||
height: 1.2em;
|
||||
}
|
||||
|
||||
#ruler {
|
||||
|
|
@ -1627,6 +1647,20 @@ rect.fillRect {
|
|||
font-family: Georgia;
|
||||
}
|
||||
|
||||
#militaryOptionsTable select {
|
||||
border: 1px solid #d4d4d4;
|
||||
}
|
||||
|
||||
#militaryOptionsTable input {
|
||||
width: 9em;
|
||||
padding-left: 3px;
|
||||
border: 1px solid #d4d4d4;
|
||||
}
|
||||
|
||||
#militaryOptionsTable input[type="number"] {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
#gridOverlay {
|
||||
fill: none;
|
||||
}
|
||||
|
|
@ -1686,34 +1720,6 @@ input[type="checkbox"] {
|
|||
color: #333333;
|
||||
}
|
||||
|
||||
.shadowed {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
text-align: center;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
#map-dragged p {
|
||||
font-size: 2.4em;
|
||||
color: #fff5da;
|
||||
text-shadow: 0px 1px 4px #4c3a35;
|
||||
}
|
||||
|
||||
#map-dragged p:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.textual select,
|
||||
div.textual textarea {
|
||||
font-family: Copperplate, monospace;
|
||||
|
|
@ -2010,6 +2016,24 @@ svg.button {
|
|||
border: dashed 1px #5d4651;
|
||||
}
|
||||
|
||||
#mapOverlay {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
text-align: center;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
font-size: 2.4em;
|
||||
color: #fff5da;
|
||||
text-shadow: 0px 1px 4px #4c3a35;
|
||||
}
|
||||
|
||||
#debug {
|
||||
font-size: 1px;
|
||||
opacity: .8;
|
||||
|
|
|
|||
293
index.html
293
index.html
|
|
@ -25,20 +25,20 @@
|
|||
@keyframes spin {0% {transform: rotate(0deg);} 100% {transform: rotate(359deg);}}
|
||||
#loading {opacity:1; font-size: 11px; color:#fff5da; text-align:center; text-shadow:0px 1px 4px #4c3a35; width:80%; max-width:600px; position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); pointer-events:none;}
|
||||
#loading-text {font-size: 1.8em; margin: 0.2em 0 0 1em;}
|
||||
#title_name {text-align: left;font-size: 3em;margin-left: 5%;}
|
||||
#titleName {text-align: left;font-size: 3em;margin-left: 5%;}
|
||||
#title {font-size: 7em;margin: -12px 0 -6px 0;}
|
||||
#version {text-align: right;font-size: 2em;margin-right: 3%;}
|
||||
#loading-text > span {font-size: 1.3em; padding-left: 1px; line-height: 0px;}
|
||||
#loading-text > span, #uploading-map span {animation: 3s infinite both blink;}
|
||||
#loading-text span:nth-child(2) {animation-delay: 1s;}
|
||||
#loading-text span:nth-child(3) {animation-delay: 2s;}
|
||||
#loading-text > span, #mapOverlay > span {animation: 3s infinite both blink;}
|
||||
#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="preload" href="index.css?version=1.2" as="style">
|
||||
<link rel="preload" href="icons.css?version=1.2" as="style">
|
||||
<link rel="preload" href="index.css?version=1.3" as="style">
|
||||
<link rel="preload" href="icons.css?version=1.3" as="style">
|
||||
<link rel="preload" href="libs/jquery-ui.css" as="style">
|
||||
<link rel="stylesheet" href="index.css?version=1.2">
|
||||
<link rel="stylesheet" href="icons.css?version=1.2">
|
||||
<link rel="stylesheet" href="index.css?version=1.3">
|
||||
<link rel="stylesheet" href="icons.css?version=1.3">
|
||||
<link rel="stylesheet" href="libs/jquery-ui.css">
|
||||
|
||||
</head>
|
||||
|
|
@ -890,36 +890,36 @@
|
|||
<g id="viewbox"></g>
|
||||
<g id="scaleBar" onclick="editUnits()"></g>
|
||||
<g id="initial" opacity=1>
|
||||
<rect x="-1%" y="-1%" width="102%" height="102%" fill="#53679f"></rect>
|
||||
<rect x="-1%" y="-1%" width="102%" height="102%" fill="#466eab"></rect>
|
||||
<rect x="-1%" y="-1%" width="102%" height="102%" fill="url(#oceanic)"></rect>
|
||||
<use xlink:href="#rose" id="init-rose" x="50%" y="50%"></use>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div id="loading">
|
||||
<div id="title_name">Azgaar's</div>
|
||||
<div id="title">Fantasy Map Generator</div>
|
||||
<div id="version">v. 1.2</div>
|
||||
<p id="loading-text">LOADING<span>.</span><span>.</span><span>.</span></p>
|
||||
<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.3</div>
|
||||
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
|
||||
</div>
|
||||
|
||||
<div id="optionsContainer" style="opacity:0">
|
||||
|
||||
<div id="collapsible">
|
||||
<button id="optionsTrigger" data-tip="Click to show options pane. Shortcut: Tab" class="options glow" onclick="showOptions(event)" style="padding:.6em .45em">►</button>
|
||||
<button id="regenerate" data-tip="Click to generate a new map. Shortcut: F2" onclick="regeneratePrompt()" class="options" style="display:none; padding:.6em 1em">New Map!</button>
|
||||
<button id="optionsTrigger" data-t="tipOptionsTrigger" data-tip="Click to show options pane. 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; padding:.6em 1em"><t data-t="newMap">New Map!</t></button>
|
||||
</div>
|
||||
|
||||
<div id="options" style="display:none">
|
||||
<div class="drag-trigger" data-tip="Drag to move options pane"></div>
|
||||
<div class="drag-trigger" data-t="optionsDragTrigger" data-tip="Drag to move options pane"></div>
|
||||
|
||||
<div class="tab">
|
||||
<button id="optionsHide" data-tip="Click to hide options pane. Shortcut: Tab to close this or Esc to close all dialogs" class="options" onclick="hideOptions(event)">◄</button>
|
||||
<button id="layersTab" data-tip="Click to change map layers" class="options active">Layers</button>
|
||||
<button id="styleTab" data-tip="Click to open style editor" class="options">Style</button>
|
||||
<button id="optionsTab" data-tip="Click to change generation and UI options" class="options">Options</button>
|
||||
<button id="toolsTab" data-tip="Click to open tools menu" class="options">Tools</button>
|
||||
<button id="aboutTab" data-tip="Click to see Generator info" class="options">About</button>
|
||||
<button id="optionsHide" data-t="optionsHide" data-tip="Click to hide options pane. Shortcut: Tab to close this or Esc to close all dialogs" class="options" onclick="hideOptions(event)">◄</button>
|
||||
<button id="layersTab" data-t="layersTab" data-tip="Click to change map layers" class="options active"><t data-t="layers">Layers</t></button>
|
||||
<button id="styleTab" data-t="styleTab" data-tip="Click to open style editor" class="options"><t data-t="style">Style</t></button>
|
||||
<button id="optionsTab" data-t="optionsTab" data-tip="Click to change generation and UI options" class="options"><t data-t="options">Options</t></button>
|
||||
<button id="toolsTab" data-t="toolsTab" data-tip="Click to open tools menu" class="options"><t data-t="tools">Tools</t></button>
|
||||
<button id="aboutTab" data-t="aboutTab" data-tip="Click to see Generator info" class="options"><t data-t="about">About</t></button>
|
||||
</div>
|
||||
|
||||
<div id="layersContent" class="tabcontent" style="display: block">
|
||||
|
|
@ -933,6 +933,7 @@
|
|||
<option value="heightmap">Heightmap</option>
|
||||
<option value="physical">Physical map</option>
|
||||
<option value="poi">Places of interest</option>
|
||||
<option value="military">Military map</option>
|
||||
<option value="landmass">Pure landmass</option>
|
||||
<option hidden value="custom">Custom (not saved)</option>
|
||||
</select>
|
||||
|
|
@ -962,9 +963,10 @@
|
|||
<li id="togglePrec" data-tip="Precipitation map: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: A" class="buttonoff" onclick="togglePrec(event)">Precipit<u>a</u>tion</li>
|
||||
<li id="toggleLabels" data-tip="Labels: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: L" onclick="toggleLabels(event)"><u>L</u>abels</li>
|
||||
<li id="toggleIcons" data-tip="Burg icons: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: I" onclick="toggleIcons(event)"><u>I</u>cons</li>
|
||||
<li id="toggleMarkers" data-tip="Markers: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: M" class="buttonoff" onclick="toggleMarkers(event)"><u>M</u>arkers</li>
|
||||
<li id="toggleMilitary" data-tip="Military forces: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: M" class="buttonoff" onclick="toggleMilitary(event)"><u>M</u>ilitary</li>
|
||||
<li id="toggleMarkers" data-tip="Markers: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: K" class="buttonoff" onclick="toggleMarkers(event)">Mar<u>k</u>ers</li>
|
||||
<li id="toggleRulers" data-tip="Rulers: click to toggle, drag to move, click on label to delete. Ctrl + click to edit layer style. Shortcut: = (equal)" class="buttonoff" onclick="toggleRulers(event)">Rulers</li>
|
||||
<li id="toggleScaleBar" data-tip="Scale Bar: click to toggle, drag to move. Ctrl + click to edit style. Shortcut: - (minus)" onclick="toggleScaleBar(event)" class="solid">Scale Bar</li>
|
||||
<li id="toggleScaleBar" data-tip="Scale Bar: click to toggle. Ctrl + click to edit style. Shortcut: - (minus)" onclick="toggleScaleBar(event)" class="solid">Scale Bar</li>
|
||||
</ul>
|
||||
|
||||
<div id="viewMode" data-tip="Set view node">
|
||||
|
|
@ -1005,6 +1007,7 @@
|
|||
<option value="landmass">Landmass</option>
|
||||
<option value="legend">Legend</option>
|
||||
<option value="markers">Markers</option>
|
||||
<option value="armies">Military</option>
|
||||
<option value="ocean">Ocean</option>
|
||||
<option value="population">Population</option>
|
||||
<option value="prec">Precipitation</option>
|
||||
|
|
@ -1188,8 +1191,8 @@
|
|||
<tr data-tip="Set foreground color. Visible if opacity > 0">
|
||||
<td>Foreground</td>
|
||||
<td>
|
||||
<input id="styleOceanFore" type="color" value="#53679f"/>
|
||||
<output id="styleOceanForeOutput">#53679f</output>
|
||||
<input id="styleOceanFore" type="color" value="#466eab"/>
|
||||
<output id="styleOceanForeOutput">#466eab</output>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
|
@ -1445,6 +1448,23 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody id="styleArmies">
|
||||
<tr data-tip="Set fill transparency. Set to 0 to make it fully transparent">
|
||||
<td>Fill opacity</td>
|
||||
<td>
|
||||
<input id="styleArmiesFillOpacity" type="range" min=0 max=1 step=.01 value=1>
|
||||
<output id="styleArmiesFillOpacityOutput">1</output>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-tip="Set regiment box size. All regiments will be redrawn on change (position will defaulted)">
|
||||
<td>Box Size</td>
|
||||
<td>
|
||||
<input id="styleArmiesSize" type="range" min=1 max=10 step=.1 value=3>
|
||||
<output id="styleArmiesSizeOutput">3</output>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody id="styleFilter" style="display: block">
|
||||
<tr data-tip="Select filter for element. Please note filters may cause performance issues!">
|
||||
<td>Filter</td>
|
||||
|
|
@ -1488,13 +1508,13 @@
|
|||
</tbody>
|
||||
|
||||
<tbody id="styleMarkers">
|
||||
<tr data-tip="Try to keep the same size on any map scale, turn off to get size change depending on scale">
|
||||
<td colspan=2>
|
||||
<input id="styleRescaleMarkers" class="checkbox" type="checkbox">
|
||||
<label for="styleRescaleMarkers" class="checkbox-label">Keep initial size on zoom change</label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tr data-tip="Try to keep the same size on any map scale, turn off to get size change depending on scale">
|
||||
<td colspan=2>
|
||||
<input id="styleRescaleMarkers" class="checkbox" type="checkbox">
|
||||
<label for="styleRescaleMarkers" class="checkbox-label">Keep initial size on zoom change</label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody id="styleVisibility">
|
||||
<tr data-tip="Define displayed label groups">
|
||||
|
|
@ -1520,14 +1540,14 @@
|
|||
<p data-tip="Map generation settings. Generate a new map to apply the settings">Map settings (new map to apply):</p>
|
||||
<table>
|
||||
|
||||
<tr data-tip="Canvas size in pixels. Defines map size on generation, then map size cannot be changed and canvas size changes only visible area. Keep canvas size equal to screen size or less to improve performance. The best aspect ratio for maps is 2:1">
|
||||
<tr data-tip="Canvas width and height in pixels. Defines map size on generation, then map size cannot be changed and canvas size changes only visible area. Keep canvas size equal to screen size or less to improve performance. The best aspect ratio for maps is 2:1">
|
||||
<td></td>
|
||||
<td>Canvas size</td>
|
||||
<td>
|
||||
<span>width</span>
|
||||
<input id="mapWidthInput" class="paired" type="number" min=240 value=960>
|
||||
<span>height</span>
|
||||
<span>x</span>
|
||||
<input id="mapHeightInput" class="paired" type="number" min=135 value=540>
|
||||
<span>px</span>
|
||||
</td>
|
||||
<td>
|
||||
<i data-tip="Toggle between screen size and initial canvas size" id="toggleFullscreen" class="icon-resize-full-alt"></i>
|
||||
|
|
@ -1779,10 +1799,20 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Select language (please note not all languages are fully supported). Reload the page to apply">
|
||||
<td></td>
|
||||
<td>Language</td>
|
||||
<td>
|
||||
<select id="selectLanguage" data-stored="lang">
|
||||
<option value="en" selected>English (100%)</option>
|
||||
<option value="ru">Русский (1%)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<button id="configureWorld" data-tip="Click to open world configurator to setup position on globe and world climate" onclick="editWorld()">Configure World</button>
|
||||
|
||||
<button id="configureWorld" data-tip="Click to open world configurator to setup map position on Globe and World climate" onclick="editWorld()">Configure World</button>
|
||||
<button id="optionsReset" data-tip="Click to restore default options (page will be reloaded)" onclick="restoreDefaultOptions()">Reset to defaults</button>
|
||||
|
||||
</div>
|
||||
|
|
@ -1807,7 +1837,7 @@
|
|||
<p>Click to overview:</p>
|
||||
<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="overviewMilitaryButton" data-tip="Click to open Military Forces Overview. Shortcut: Shift + M">Military</button>
|
||||
<button id="overviewCellsButton" data-tip="Click to open Cell details view. Shortcut: Shift + E">Cells</button>
|
||||
<!-- <button id="overviewLandmassedButton" data-tip="Click to open Landmasses Overview. Shortcut: Shift + L">Landmasses</button> -->
|
||||
<!-- <button id="overviewWaterbodiesButton" data-tip="Click to open Waterbodies Overview. Shortcut: Shift + W">Waterbodies</button> -->
|
||||
|
|
@ -1822,9 +1852,10 @@
|
|||
<button id="regenerateRivers" data-tip="Click to regenerate all rivers (restore default state)">Rivers</button>
|
||||
<button id="regeneratePopulation" data-tip="Click to recalculate rural and urban population">Population</button>
|
||||
<button id="regenerateBurgs" data-tip="Click to regenerate all burgs and routes. States will remain as they are">Burgs</button>
|
||||
<button id="regenerateStates" data-tip="Click to select new capitals and regenerate states. Burgs will remain as they are">States</button>
|
||||
<button id="regenerateStates" data-tip="Click to select new capitals and regenerate states. Military forces will be regenerated as well, burgs will remain as they are">States</button>
|
||||
<button id="regenerateProvinces" data-tip="Click to regenerate provinces. States will remain as they are">Provinces</button>
|
||||
<button id="regenerateReligions" data-tip="Click to regenerate religions">Religions</button>
|
||||
<button id="regenerateMilitary" data-tip="Click to recalculate military forces based on current military options">Military</button>
|
||||
<button id="regenerateMarkers" data-tip="Click to regenerate markers. Hold Ctrl and click to set markers number multiplier">Markers</button>
|
||||
<button id="regenerateZones" data-tip="Click to regenerate zones. Hold Ctrl and click to set zones number multiplier">Zones</button>
|
||||
</div>
|
||||
|
|
@ -1879,7 +1910,7 @@
|
|||
<div id="aboutContent" class="tabcontent">
|
||||
<p><a href="https://github.com/Azgaar/Fantasy-Map-Generator" target="_blank">Fantasy Map Generator</a> is a free <a href="https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE" target="_blank">open source</a> tool which procedurally generates fantasy maps. You may use auto-generated maps as they are, edit them or even create a new map from scratch. Check out the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial" target="_blank">quick start tutorial</a>, <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A" target="_blank">Q&A</a> and <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys" target="_blank">hotkeys</a> for guidance.</p>
|
||||
<p>Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share created maps. You may support the project on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>.</p>
|
||||
<p>The project is under active development. For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:azgaar.fmg@yandex.by" target="_blank">email</a>.</p>
|
||||
<p>The project is under active development. Creator and main maintainer: Azgaar. For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:azgaar.fmg@yandex.by" target="_blank">email</a>.</p>
|
||||
<p>Special thanks to all supporters on Patreon! <i data-tip="Click to see supporters names" class="collapsible icon-down-open pointer"></i></p>
|
||||
<p style="display:none">Patreon 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,
|
||||
|
|
@ -1887,10 +1918,12 @@
|
|||
Sasquatch, Shawn Spencer, Sizz_TV, Timothée CALLET, UTG community, Vlad Tomash, Wil Sisney, William Merriott, Xariun,
|
||||
Gun Metal Games, Scott Marner, Spencer Sherman, Valerii Matskevych, Alloyed Clavicle, Stewart Walsh, Ruthlyn Mollett (Javan),
|
||||
Benjamin Mair-Pratt, Diagonath, Alexander Thomas, Ashley Wilson-Savoury, William Henry, Preston Brooks, JOSHUA QUALTIERI,
|
||||
Hilton Williams, Katharina Haase, Hisham Bedri, Ian arless, Karnat, Bird, Kevin, Jessica Thomas, Steve Hyatt, Logicspren,
|
||||
Alfred García, Jonathan Killstring, John Ackley, Invad3r233, Norbert Žigmund, Jennifer, PoliticsBuff, _gfx_, Maggie,
|
||||
Connor McMartin, Jared McDaris, BlastWind, Franc Casanova Ferrer, Dead & Devil, Michael Carmody, Valerie Elise, naikibens220,
|
||||
Jordon Phillips, William Pucs, The Dungeon Masters, Brady R Rathbun, J, Shadow, Matthew Tiffany and many others!</p>
|
||||
Hilton Williams, Katharina Haase, Hisham Bedri, Ian arless, Karnat, Bird, Kevin, Jessica Thomas, Steve Hyatt, Logicspren,
|
||||
Alfred García, Jonathan Killstring, John Ackley, Invad3r233, Norbert Žigmund, Jennifer, PoliticsBuff, _gfx_, Maggie,
|
||||
Connor McMartin, Jared McDaris, BlastWind, Franc Casanova Ferrer, Dead & Devil, Michael Carmody, Valerie Elise, naikibens220,
|
||||
Jordon Phillips, William Pucs, The Dungeon Masters, Brady R Rathbun, J, Shadow, Matthew Tiffany, Huw Williams, Joseph Hamilton,
|
||||
FlippantFeline, Tamashi Toh, kms, Stephen Herron, MidnightMoon, Whakomatic x, Barished, Aaron bateson, Brice Moss, Diklyquill,
|
||||
PatronUser, Michael Greiner, Steven Bennett, Jacob Harrington, Miguel C., Reya C., Giant Monster Games, Noirbard and many others!</p>
|
||||
|
||||
<ul class="share-buttons">
|
||||
<li><a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator%2F"e=" data-tip="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="images/Facebook.png" /></a></li>
|
||||
|
|
@ -1934,7 +1967,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<i data-locked=0 id="lock_temperaturePole" class="icon-lock-open"></i>
|
||||
<label data-tip="Set temperature at poles">
|
||||
<label data-tip="Set temperature near poles">
|
||||
<i>Poles:</i>
|
||||
<input id="temperaturePoleInput" data-stored="temperaturePole" type="number" min="-30" max="30">°C =
|
||||
<span id="temperaturePoleF"></span>°F
|
||||
|
|
@ -2292,9 +2325,9 @@
|
|||
<span id="burgCapital" data-tip="Shows whether the burg is a state capital. Click to toggle" data-feature="capital" class="burgFeature icon-star"></span>
|
||||
<span id="burgPort" data-tip="Shows whether the burg is a port. Click to toggle" data-feature="port" class="burgFeature icon-anchor"></span>
|
||||
<span id="burgCitadel" data-tip="Shows whether the burg has a citadel (castle). Click to toggle" data-feature="citadel" class="burgFeature icon-chess-rook" style="font-size: 1.1em"></span>
|
||||
<span id="burgWalls" data-tip="Shows whether the burg has walls. Click to toggle" data-feature="walls" class="burgFeature icon-fort-awesome"></span>
|
||||
<span id="burgPlaza" data-tip="Shows whether the burg has a plaza (marketplace). Click to toggle" data-feature="plaza" class="burgFeature icon-store" style="font-size: 1em"></span>
|
||||
<span id="burgTemple" data-tip="Shows whether the burg has a place of worship. Click to toggle" data-feature="temple" class="burgFeature icon-bank" style="font-size: 1.1em; margin-left: 3px"></span>
|
||||
<span id="burgWalls" data-tip="Shows whether the burg is walled. Click to toggle" data-feature="walls" class="burgFeature icon-fort-awesome"></span>
|
||||
<span id="burgPlaza" data-tip="Shows whether the burg is a trade center (has big marketplace). Click to toggle" data-feature="plaza" class="burgFeature icon-store" style="font-size: 1em"></span>
|
||||
<span id="burgTemple" data-tip="Shows whether the burg is a religious center. Click to toggle" data-feature="temple" class="burgFeature icon-chess-bishop" style="font-size: 1.1em; margin-left: 3px"></span>
|
||||
<span id="burgShanty" data-tip="Shows whether the burg has a shanty town. Click to toggle" data-feature="shanty" class="burgFeature icon-campground" style="font-size: 1em"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2308,17 +2341,17 @@
|
|||
<i id="burgAddGroup" data-tip="Create new group for the burg" class="icon-plus pointer"></i>
|
||||
<i id="burgRemoveGroup" data-tip="Remove selected burg group" class="icon-trash pointer"></i>
|
||||
</div>
|
||||
|
||||
|
||||
<button id="burgStyleShow" data-tip="Show style edit section" class="icon-brush"></button>
|
||||
<div id="burgStyleSection" style="display: none">
|
||||
<button id="burgStyleHide" data-tip="Hide style edit section" class="icon-brush"></button>
|
||||
<button id="burgEditLabelStyle" data-tip="Edit label style for burg group in Style Editor" class="icon-font"></button>
|
||||
<button id="burgEditIconStyle" data-tip="Edit icon style for burg group in Style Editor" class="icon-dot-circled"></button>
|
||||
<button id="burgEditAnchorStyle" data-tip="Edit port icon (anchor) style for burg group in Style Editor" class="icon-anchor"></button>
|
||||
<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="burgOpenCOA" data-tip="Open burg's COA in the Iron Arachne Heraldry Generator. Ctrl + click to change the seed" class="icon-fleur"></button>
|
||||
<button id="burgOpenCOA" data-tip="Open burg's COA in the Iron Arachne Heraldry Generator. Ctrl + click to change the seed" 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="burgRemove" data-tip="Remove non-capital burg. Shortcut: Delete" class="icon-trash"></button>
|
||||
|
|
@ -2367,6 +2400,34 @@
|
|||
<button id="markerRemove" data-tip="Remove the marker. Shortcut: Delete" class="icon-trash"></button>
|
||||
</div>
|
||||
|
||||
<div id="regimentEditor" class="dialog" style="display: none">
|
||||
|
||||
<div id="regimentBody">
|
||||
<div>
|
||||
<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">
|
||||
<i id="regimentNameRestore" data-tip="Click to restore regiment's default name" class="icon-ccw pointer"></i>
|
||||
</div>
|
||||
|
||||
<div data-tip="Regiment emblem. Paste any Unicode symbol or select from the predefined list">
|
||||
<div class="label italic">Emblem:</div>
|
||||
<input id="regimentEmblem" style="width:5em">
|
||||
<button id="regimentEmblemSelect" style="padding: 0; width: 4.5em">select</button>
|
||||
</div>
|
||||
|
||||
<div id="regimentComposition" style="padding: .1em"></div>
|
||||
</div>
|
||||
|
||||
<div id="regimentBottom">
|
||||
<button id="regimentAdd" data-tip="Create new regiment or fleet" class="icon-user-plus"></button>
|
||||
<button id="regimentSplit" data-tip="Split regiment into 2 separate ones" class="icon-half"></button>
|
||||
<button id="regimentAttach" data-tip="Attach regiment to another one (include this regiment to another one)" class="icon-attach"></button>
|
||||
<button id="regimentRegenerateLegend" data-tip="Regenerate legend for this regiment" class="icon-retweet"></button>
|
||||
<button id="regimentLegend" data-tip="Edit free text notes (legend) for this regiment" class="icon-edit"></button>
|
||||
<button id="regimentRemove" data-tip="Remove regiment. Shortcut: Delete" class="icon-trash"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="brushesPanel" class="dialog stable" style="display: none">
|
||||
<div id="brushesButtons" style="display: inline-block">
|
||||
<button id="brushRaise" data-tip="Raise brush: increase height of cells in radius by Power value">
|
||||
|
|
@ -2509,7 +2570,8 @@
|
|||
<button id="templateRedo" data-tip="Redo the action" class="icon-cw" disabled></button>
|
||||
<button id="templateSave" data-tip="Download the template as a text file" class="icon-download"></button>
|
||||
<button id="templateLoad" data-tip="Open previously downloaded template" class="icon-upload"></button>
|
||||
<button id="templateCA" data-tip="Find or share custom template on Cartography Assets portal" class="icon-drafting-compass" onclick="openURL('https://www.cartographyassets.com/assets/categories/azgaar-generator.87')"></button>
|
||||
<button id="templateCA" data-tip="Find or share custom template on Cartography Assets portal" class="icon-drafting-compass" onclick="openURL('https://www.cartographyassets.com/assets/categories/templates.89')"></button>
|
||||
<button id="templateTutorial" data-tip="Open Template Editor Tutorial" class="icon-info" onclick="openURL('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-template-editor')"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -2548,7 +2610,7 @@
|
|||
|
||||
<div id="biomesEditor" class="dialog stable" style="display: none">
|
||||
<div id="biomesHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by biome name" class="sortable alphabetically" data-sortby="name">Biome </div>
|
||||
<div style="left:1.8em" data-tip="Click to sort by biome name" class="sortable alphabetically" data-sortby="name">Biome </div>
|
||||
<div style="left:12em" data-tip="Click to sort by biome habitability" class="sortable hide" data-sortby="habitability">Habitability </div>
|
||||
<div style="left:19em" data-tip="Click to sort by biome cells number" class="sortable hide icon-sort-number-down" data-sortby="cells">Cells </div>
|
||||
<div style="left:25em" data-tip="Click to sort by biome area" class="sortable hide" data-sortby="area">Area </div>
|
||||
|
|
@ -2588,11 +2650,11 @@
|
|||
|
||||
<div id="statesEditor" class="dialog stable" style="display: none">
|
||||
<div id="statesHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="name">State </div>
|
||||
<div style="left:8.7em" data-tip="Click to sort by state form name" class="sortable alphabetically" data-sortby="form">Form </div>
|
||||
<div style="left:15.9em" data-tip="Click to sort by capital name" class="sortable alphabetically hide" data-sortby="capital">Capital </div>
|
||||
<div style="left:22.3em" data-tip="Click to sort by state dominant culture" class="sortable alphabetically hide" data-sortby="culture">Culture </div>
|
||||
<div style="left:27em" data-tip="Click to sort by state burgs count" class="sortable hide" data-sortby="burgs">Burgs </div>
|
||||
<div style="left:1.8em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="name">State </div>
|
||||
<div style="left:9em" data-tip="Click to sort by state form name" class="sortable alphabetically" data-sortby="form">Form </div>
|
||||
<div style="left:16.3em" data-tip="Click to sort by capital name" class="sortable alphabetically hide" data-sortby="capital">Capital </div>
|
||||
<div style="left:22.2em" data-tip="Click to sort by state dominant culture" class="sortable alphabetically hide" data-sortby="culture">Culture </div>
|
||||
<div style="left:27.2em" data-tip="Click to sort by state burgs count" class="sortable hide" data-sortby="burgs">Burgs </div>
|
||||
<div style="left:32.5em" data-tip="Click to sort by state area" class="sortable hide icon-sort-number-down" data-sortby="area">Area </div>
|
||||
<div style="left:37em" data-tip="Click to sort by state population" class="sortable hide" data-sortby="population">Population </div>
|
||||
<div style="left:43.5em" data-tip="Click to sort by state type" class="sortable alphabetically hidden show hide" data-sortby="type">Type </div>
|
||||
|
|
@ -2662,7 +2724,7 @@
|
|||
|
||||
<div style="padding: .1em" data-tip="Select form name">
|
||||
<div data-tip="State form name" class="label">Form name:</div>
|
||||
<select id="stateNameEditorSelectForm" style="display: inline-block; width: 11.7em; height: 1.645em">
|
||||
<select id="stateNameEditorSelectForm" style="width: 11em">
|
||||
<option value="">blank</option>
|
||||
<option data-form="Monarchy" value="Beylik">Beylik</option>
|
||||
<option data-form="Theocracy" value="Caliphate">Caliphate</option>
|
||||
|
|
@ -2729,7 +2791,7 @@
|
|||
|
||||
<div id="provincesEditor" class="dialog stable" style="display: none">
|
||||
<div id="provincesHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="name">Province </div>
|
||||
<div style="left:1.8em" data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="name">Province </div>
|
||||
<div style="left:8.5em" data-tip="Click to sort by province form name" class="sortable alphabetically hide" data-sortby="form">Form </div>
|
||||
<div style="left:15.9em" data-tip="Click to sort by province capital" class="sortable alphabetically hide" data-sortby="capital">Capital </div>
|
||||
<div style="left:22em" data-tip="Click to sort by province owner" class="sortable alphabetically" data-sortby="state">State </div>
|
||||
|
|
@ -2783,7 +2845,7 @@
|
|||
<div data-tip="Ally means states formed a defensive pact and will protect each other in case of third party aggression">Ally</div>
|
||||
<div data-tip="State is friendly to anouther state when they share some common interests">Friendly</div>
|
||||
<div data-tip="Neutral means states relations are neither positive nor negative">Neutral</div>
|
||||
<div data-tip="Suspicion means shate has a cautious distrust of another state">Suspicion</div>
|
||||
<div data-tip="Suspicion means state has a cautious distrust of another state">Suspicion</div>
|
||||
<div data-tip="Enemies are states at war with each other">Enemy</div>
|
||||
<div data-tip="Relations are unknown if states do not have enought information about each other">Unknown</div>
|
||||
<div data-tip="Rivalry is a state of competing for dominance in the region">Rival</div>
|
||||
|
|
@ -2848,11 +2910,11 @@
|
|||
|
||||
<div id="culturesEditor" class="dialog stable" style="display: none">
|
||||
<div id="culturesHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by culture name" class="sortable alphabetically" data-sortby="name">Culture </div>
|
||||
<div style="left:1.8em" data-tip="Click to sort by culture name" class="sortable alphabetically" data-sortby="name">Culture </div>
|
||||
<div style="left:6.7em" data-tip="Click to sort by culture cells count" class="sortable hide" data-sortby="cells">Cells </div>
|
||||
<div style="left:11em" data-tip="Click to sort by expansionism" class="sortable hide" data-sortby="expansionism">Expan. </div>
|
||||
<div style="left:15.8em" data-tip="Click to sort by type" class="sortable alphabetically" data-sortby="type">Type </div>
|
||||
<div style="left:22.2em" data-tip="Click to sort by culture area" class="sortable hide" data-sortby="area">Area </div>
|
||||
<div style="left:11.2em" data-tip="Click to sort by expansionism" class="sortable hide" data-sortby="expansionism">Expan. </div>
|
||||
<div style="left:16.6em" data-tip="Click to sort by type" class="sortable alphabetically" data-sortby="type">Type </div>
|
||||
<div style="left:22.9em" data-tip="Click to sort by culture area" class="sortable hide" data-sortby="area">Area </div>
|
||||
<div style="left:26.8em" data-tip="Click to sort by culture population" class="sortable hide icon-sort-number-down" data-sortby="population">Population </div>
|
||||
<div style="left:33.8em" data-tip="Click to sort by culture namesbase" class="sortable" data-sortby="base">Namesbase </div>
|
||||
</div>
|
||||
|
|
@ -2930,7 +2992,7 @@
|
|||
|
||||
<div id="zonesEditor" class="dialog stable" style="display: none">
|
||||
<div id="customHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Zone description">Description </div>
|
||||
<div style="left:1.8em" data-tip="Zone description">Description </div>
|
||||
<div style="left:13em" data-tip="Zone cells count" class="hide">Cells </div>
|
||||
<div style="left:19em" data-tip="Zone area" class="hide">Area </div>
|
||||
<div style="left:24em" data-tip="Zone population" class="hide">Population </div>
|
||||
|
|
@ -2988,7 +3050,7 @@
|
|||
|
||||
<div id="religionsEditor" class="dialog stable" style="display: none">
|
||||
<div id="religionsHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by religion name" class="sortable alphabetically" data-sortby="name">Religion </div>
|
||||
<div style="left:1.8em" data-tip="Click to sort by religion name" class="sortable alphabetically" data-sortby="name">Religion </div>
|
||||
<div style="left:12.6em" data-tip="Click to sort by religion type" class="sortable alphabetically icon-sort-name-down" data-sortby="type">Type </div>
|
||||
<div style="left:18em" data-tip="Click to sort by religion form" class="sortable alphabetically hide" data-sortby="form">Form </div>
|
||||
<div style="left:25.1em" data-tip="Click to sort by supreme deity" class="sortable alphabetically hide" data-sortby="deity">Supreme Deity </div>
|
||||
|
|
@ -3073,7 +3135,7 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div data-tip="Set height exponent, i.e. a value showing altitude change sharpness">
|
||||
<div data-tip="Set height exponent, i.e. a value for altitude change sharpness. Altitude affects temperature and hence biomes">
|
||||
<div>Exponent:</div>
|
||||
<input id="heightExponentOutput" type="range" min=1.5 max=2.1 value=1.8 step=.01>
|
||||
<input id="heightExponentInput" data-stored="heightExponent" type="number" min=1.5 max=2.1 value=1.8 step=.01>
|
||||
|
|
@ -3122,8 +3184,8 @@
|
|||
|
||||
<div data-tip="Set position of the Scale bar bottom right corner in percents">
|
||||
<div>Bar position:</div>
|
||||
<i>x:</i><input id="barPosX" data-stored="barPosX" type="number" min=0 max=100 step=.1 value=99>
|
||||
<i>y:</i><input id="barPosY" data-stored="barPosY" type="number" min=0 max=100 step=.1 value=99>
|
||||
x:<input id="barPosX" data-stored="barPosX" type="number" min=0 max=100 step=.1 value=99>
|
||||
y:<input id="barPosY" data-stored="barPosY" type="number" min=0 max=100 step=.1 value=99>
|
||||
</div>
|
||||
|
||||
<div class="unitsHeader">
|
||||
|
|
@ -3133,8 +3195,8 @@
|
|||
|
||||
<div data-tip="Set how many people are in one population point">
|
||||
<div>1 population point =</div>
|
||||
<input id="populationRateOutput" type="range" min=10 max=9990 step=10 value=1000>
|
||||
<input id="populationRate" data-stored="populationRate" type="number" min=10 max=9990 step=10 value=1000 data-value=1000>
|
||||
<input id="populationRateOutput" type="range" min=10 max=9990 step=10 value=1000 style="width:6em">
|
||||
<input id="populationRate" data-stored="populationRate" type="number" min=10 max=9990 step=10 value=1000 data-value=1000 style="width:4.5em">
|
||||
</div>
|
||||
|
||||
<div data-tip="Set ubranization rate: burgs population relative to all population">
|
||||
|
|
@ -3155,7 +3217,7 @@
|
|||
|
||||
<div id="burgsOverview" class="dialog stable" style="display: none">
|
||||
<div id="burgsHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by burg name" class="sortable alphabetically icon-sort-name-up" data-sortby="name">Burg </div>
|
||||
<div style="left:1.8em" data-tip="Click to sort by burg name" class="sortable alphabetically icon-sort-name-up" data-sortby="name">Burg </div>
|
||||
<div style="left:7.6em" data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="province">Province </div>
|
||||
<div style="left:14em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="state">State </div>
|
||||
<div style="left:20.1em" data-tip="Click to sort by culture name" class="sortable alphabetically" data-sortby="culture">Culture </div>
|
||||
|
|
@ -3190,7 +3252,7 @@
|
|||
|
||||
<div id="riversOverview" class="dialog stable" style="display: none">
|
||||
<div id="riversHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by river name" class="sortable alphabetically" data-sortby="name">River </div>
|
||||
<div style="left:1.8em" data-tip="Click to sort by river name" class="sortable alphabetically" data-sortby="name">River </div>
|
||||
<div style="left:7.7em" data-tip="Click to sort by river type name" class="sortable alphabetically" data-sortby="type">Type </div>
|
||||
<div style="left:12.9em" data-tip="Click to sort by river length" class="sortable icon-sort-number-down" data-sortby="length">Length </div>
|
||||
<div style="left:18.2em" data-tip="Click to sort by river basin" class="sortable alphabetically" data-sortby="basin">Basin </div>
|
||||
|
|
@ -3214,29 +3276,68 @@
|
|||
|
||||
<div id="militaryOverview" class="dialog stable" style="display: none">
|
||||
<div id="militaryHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="State name. Click to sort" class="sortable alphabetically" data-sortby="state">State </div>
|
||||
<div style="left: 7.8em" data-tip="State infantry number. Click to sort" class="sortable" data-sortby="infantry">Infantry </div>
|
||||
<div style="left: 13em" data-tip="State archers number. Click to sort" class="sortable" data-sortby="archers">Archers </div>
|
||||
<div style="left: 18em" data-tip="State cavalry number. Click to sort" class="sortable" data-sortby="cavalry">Cavalry </div>
|
||||
<div style="left: 23em" data-tip="Number of ships in navy. Click to sort" class="sortable" data-sortby="fleet">Fleet </div>
|
||||
<div style="left: 27em" data-tip="Total military personnel (including ships crew). Click to sort" class="sortable icon-sort-number-down" data-sortby="total">Total </div>
|
||||
<div style="left: 31.4em" data-tip="Military personnel rate (% of state population). Depends on diplomatic situation. Click to sort" class="sortable" data-sortby="rate">Rate </div>
|
||||
<div style="left: 36em" data-tip="State manpower (reserve). Click to sort" class="sortable" data-sortby="reserve">Reserve </div>
|
||||
<div data-tip="State name. Click to sort" style="left:1.8em; width: 7.4em;" class="sortable alphabetically" data-sortby="state">State </div>
|
||||
<div data-tip="Total military personnel (considering crew). Click to sort" id="militaryTotal" class="sortable icon-sort-number-down" data-sortby="total">Total </div>
|
||||
<div data-tip="State population. Click to sort" style="width: 6.5em; margin-left: -1em" class="sortable" data-sortby="population">Population </div>
|
||||
<div data-tip="Military personnel rate (% of state population). Depends on war alert. Click to sort" style="width: 3.7em" class="sortable" data-sortby="rate">Rate </div>
|
||||
<div data-tip="War Alert. Modifier to military forces number, depends of political situation. Click to sort" class="sortable" data-sortby="alert">War Alert </div>
|
||||
</div>
|
||||
|
||||
<div id="militaryBody" class="table" data-type="absolute"></div>
|
||||
|
||||
<div id="militaryFooter" class="totalLine">
|
||||
<div data-tip="States number" style="margin-left: 4px">States: <span id="militaryFooterStates">0</span></div>
|
||||
<div data-tip="Average military per state" style="margin-left: 14px">Average military: <span id="militaryFooterAverage">0</span></div>
|
||||
<div data-tip="Total military forces" style="margin-left: 14px">Total forces: <span id="militaryFooterForcesTotal">0</span></div>
|
||||
<div data-tip="Average military forces per state" style="margin-left: 14px">Average forces: <span id="militaryFooterForces">0</span></div>
|
||||
<div data-tip="Average forces rate per state" style="margin-left: 14px">Average rate: <span id="militaryFooterRate">0%</span></div>
|
||||
<div data-tip="Average War Alert" style="margin-left: 14px">Average alert: <span id="militaryFooterAlert">0</span></div>
|
||||
</div>
|
||||
|
||||
<div id="militaryBottom">
|
||||
<button id="militaryOverviewRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
|
||||
<button id="militaryOverviewRefresh" data-tip="Refresh the overview screen" class="icon-cw"></button>
|
||||
<button id="militaryOptionsButton" data-tip="Edit Military units" class="icon-cog"></button>
|
||||
<button id="militaryRegimentsList" data-tip="Show regiments list" class="icon-list-bullet"></button>
|
||||
<button id="militaryPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button>
|
||||
<button id="militaryOverviewRecalculate" data-tip="Recalculate military forces based on current options" class="icon-retweet"></button>
|
||||
<button id="militaryExport" data-tip="Save military-related data as a text file (.csv)" class="icon-download"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="regimentsOverview" class="dialog stable" style="display: none">
|
||||
<div id="regimentsHeader" class="header">
|
||||
<div data-tip="State name. Click to sort" style="left:1.8em; width: 9em" class="sortable alphabetically" data-sortby="state">State </div>
|
||||
<div data-tip="Regiment emblem and name. Click to sort by name" style="width: 12em" class="sortable alphabetically" data-sortby="name">Name </div>
|
||||
<div data-tip="Total military personnel (not considering crew). Click to sort" style="margin-left: .8em" id="regimentsTotal" class="sortable icon-sort-number-down" data-sortby="total">Total </div>
|
||||
</div>
|
||||
|
||||
<div id="regimentsBody" class="table" data-type="absolute"></div>
|
||||
|
||||
<div id="regimentsBottom">
|
||||
<button id="regimentsOverviewRefresh" data-tip="Refresh the overview screen" class="icon-cw"></button>
|
||||
<button id="regimentsPercentage" data-tip="Toggle percentage / absolute values views" class="icon-percent"></button>
|
||||
<button id="regimentsAddNew" data-tip="Add new Regiment" class="icon-user-plus"></button>
|
||||
<div data-tip="Select state" style="display:inline-block"><span>State: </span><select id="regimentsFilter"></select></div>
|
||||
<button id="regimentsExport" data-tip="Save military-related data as a text file (.csv)" class="icon-download"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="militaryOptions" class="dialog stable" style="display: none">
|
||||
<table id="militaryOptionsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-tip="Unit name. If name is changed for existing unit, old unit will be replaced">Military unit</th>
|
||||
<th data-tip="Conscription percentage for rural population">Rural %</th>
|
||||
<th data-tip="Conscription percentage for urban population">Urban %</th>
|
||||
<th data-tip="Average number of people in crew">Crew</th>
|
||||
<th data-tip="Unit type to apply special rules on forces recalculation">Type</th>
|
||||
<th data-tip="Check if unit is separate and can be stacked only with units of the same type">Sep.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="styleSaver" class="dialog stable textual" style="display: none">
|
||||
<div id="styleSaverHeader" style="padding:2px 0">
|
||||
<span>Preset name:</span>
|
||||
|
|
@ -3266,7 +3367,8 @@
|
|||
<p><b>Precipitation:</b> <span id="infoPrec">0</span></p>
|
||||
<p><b>River:</b> <span id="infoRiver">no</span></p>
|
||||
<p><b>Population:</b> <span id="infoPopulation">0</span></p>
|
||||
<p><b>Height:</b> <span id="infoHeight">0</span></p>
|
||||
<p><b>Elevation:</b> <span id="infoEvelation">0</span></p>
|
||||
<p><b>Depth:</b> <span id="infoDepth">0</span></p>
|
||||
<p><b>Temperature:</b> <span id="infoTemp">0</span></p>
|
||||
<p><b>Biome:</b> <span id="infoBiome">n/a</span></p>
|
||||
<p><b>State:</b> <span id="infoState">n/a</span></p>
|
||||
|
|
@ -3310,8 +3412,8 @@
|
|||
</div>
|
||||
|
||||
<div data-tip="Set sky and water color" id="options3dColorSection" style="display: none">
|
||||
<span>Sky:</span><input id="options3dMeshSky" type="color" style="width: 4.4em; border: 0; padding: 0; margin: 0 .2em">
|
||||
<span>Water:</span><input id="options3dMeshWater" type="color" style="width: 4.4em; border: 0; padding: 0; margin: 0 .2em">
|
||||
<span>Sky:</span><input id="options3dMeshSky" type="color" style="width: 4.4em; height: 1em; border: 0; padding: 0; margin: 0 .2em">
|
||||
<span>Water:</span><input id="options3dMeshWater" type="color" style="width: 4.4em; height: 1em; border: 0; padding: 0; margin: 0 .2em">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -3337,6 +3439,7 @@
|
|||
|
||||
<div id="options3dBottom" style="margin-top: .2em">
|
||||
<button id="options3dUpdate" data-tip="Update the scene" class="icon-cw"></button>
|
||||
<button data-tip="Configure world and map size and climate settings" onclick="editWorld()" class="icon-globe"></button>
|
||||
<button id="options3dSave" data-tip="Save screenshot of the 3d scene" class="icon-download"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -3371,10 +3474,6 @@
|
|||
|
||||
</div>
|
||||
|
||||
<div id="map-dragged" class="shadowed" style="display: none">
|
||||
<p>Drop a .map file to open</p>
|
||||
</div>
|
||||
|
||||
<div id="notes">
|
||||
<div id="notesHeader"></div>
|
||||
<div id="notesBody"></div>
|
||||
|
|
@ -3382,6 +3481,8 @@
|
|||
|
||||
<div id="tooltip" style="opacity:0" data-main="Сlick the arrow button for options. Zoom in to see the map in details">Сlick the arrow button for options. Zoom in to see the map in details</div>
|
||||
|
||||
<div id="mapOverlay" style="display: none">Drop a .map file to open</div>
|
||||
|
||||
<div id="fileInputs" style="display: none">
|
||||
<input type="file" accept=".map" id="mapToLoad">
|
||||
<input type="file" accept=".txt,.csv" id="burgsListToLoad">
|
||||
|
|
@ -3392,6 +3493,7 @@
|
|||
<input type="file" accept=".json" id="styleToLoad">
|
||||
</div>
|
||||
|
||||
<!-- <script src="libs/translate.js"></script> -->
|
||||
<script src="libs/jquery-3.1.1.min.js"></script>
|
||||
<script src="libs/d3.min.js"></script>
|
||||
<script src="libs/priority-queue.min.js"></script>
|
||||
|
|
@ -3406,6 +3508,7 @@
|
|||
<script src="modules/burgs-and-states.js"></script>
|
||||
<script src="modules/routes-generator.js"></script>
|
||||
<script src="modules/religions-generator.js"></script>
|
||||
<script src="modules/military-generator.js"></script>
|
||||
<script src="libs/polylabel.min.js"></script>
|
||||
<script src="libs/jquery-ui.min.js"></script>
|
||||
<script src="libs/seedrandom.min.js"></script>
|
||||
|
|
@ -3441,7 +3544,9 @@
|
|||
<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/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/editors.js"></script>
|
||||
<script defer src="modules/ui/3d.js"></script>
|
||||
<script defer src="libs/quantize.min.js"></script>
|
||||
|
|
|
|||
511
lang/lang-en.js
Normal file
511
lang/lang-en.js
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
// Source translation file for FMG. This file is only used as a reference for tranlation. Version 0.01a
|
||||
window.translation = {
|
||||
titleFull: "Azgaar's Fantasy Map Generator",
|
||||
titleName: "Azgaar's",
|
||||
title: "Fantasy Map Generator",
|
||||
version: "v. ",
|
||||
loading: "Loading",
|
||||
newMap: "New Map!",
|
||||
layers: "Layers",
|
||||
style: "Style",
|
||||
options: "Options",
|
||||
tools: "Tools",
|
||||
about: "About",
|
||||
tipOptionsTrigger: "Click to show options pane. Shortcut: Tab",
|
||||
tipRegenerate: "Click to generate a new map. Shortcut: F2",
|
||||
optionsDragTrigger: "Drag to move options pane",
|
||||
optionsHide: "Click to hide options pane. Shortcut: Tab to close this or Esc to close all dialogs",
|
||||
layersTab: "Click to change map layers",
|
||||
styleTab: "Click to open style editor",
|
||||
optionsTab: "Click to change generation and UI options",
|
||||
toolsTab: "Click to open tools menu",
|
||||
aboutTab: "Click to see Generator info"
|
||||
};
|
||||
|
||||
return;
|
||||
// list of tooltips from init DOM (dynamically added elements are not icluded)
|
||||
const sourceDataForReference = {
|
||||
optionsTrigger: "Click to show options pane. Shortcut: Tab",
|
||||
NeedToAdd!: "Drag to move options pane",
|
||||
optionsHide: "Click to hide options pane. Shortcut: Tab to close this or Esc to close all dialogs",
|
||||
NeedToAdd!: "Select a map layers preset",
|
||||
savePresetButton: "Click to save displayed layers as a new preset",
|
||||
removePresetButton: "Click to remove current custom preset",
|
||||
NeedToAdd!: "Click to toggle a layer, drag to raise or lower a layer. Ctrl + click to edit layer style",
|
||||
toggleRulers: "Rulers: click to toggle, drag to move, click on label to delete. Ctrl + click to edit layer style. Shortcut: = (equal)",
|
||||
toggleScaleBar: "Scale Bar: click to toggle. Ctrl + click to edit style. Shortcut: - (minus)",
|
||||
viewStandard: "Standard view mode that allows to edit the map",
|
||||
viewMesh: "Map presentation in 3D scene. Works best for heightmap. Cannot be used for editing",
|
||||
viewGlobe: "Project map on globe. Cannot be used for editing",
|
||||
NeedToAdd!: "Select a style preset",
|
||||
addStyleButton: "Click to save current style as a new preset",
|
||||
removeStyleButton: "Click to remove current custom style preset",
|
||||
NeedToAdd!: "Select an element to edit its style",
|
||||
styleIsOff: "The selected layer is not visible. See the buttons above to toggle it on",
|
||||
NeedToAdd!: "Click and provide a URL to image to be set as a texture",
|
||||
styleTextureShiftX: "Shift texture by x axis in pixels",
|
||||
styleTextureShiftY: "Shift texture by y axis in pixels",
|
||||
styleGridSizeFriendly: "Distance between two adjacent cells in map scale",
|
||||
styleShiftX: "Shift by x axis in pixels",
|
||||
styleShiftY: "Shift by y axis in pixels",
|
||||
styleCompassShiftX: "Shift by x axis in pixels",
|
||||
styleCompassShiftY: "Shift by y axis in pixels",
|
||||
styleInputFont: "Provide a link to @font-face declaration or type Google font name",
|
||||
styleFontAdd: "Add custom font from the web",
|
||||
styleFontPlus: "Multiply font size by 1.1",
|
||||
styleFontMinus: "Multiply font size by 0.9",
|
||||
styleRadiusPlus: "Multiply radius by 1.1",
|
||||
styleRadiusMinus: "Multiply radius by 1.1",
|
||||
styleIconSizePlus: "Multiply size by 1.1",
|
||||
styleIconSizeMinus: "Multiply size by 1.1",
|
||||
NeedToAdd!: "Allow system to hide labels if their size in too small or too big on that scale",
|
||||
NeedToAdd!: "Map generation settings. Generate a new map to apply the settings",
|
||||
toggleFullscreen: "Toggle between screen size and initial canvas size",
|
||||
optionsSeedGenerate: "Click to generate a map for this seed",
|
||||
optionsMapHistory: "Show seed history to apply a previous seed",
|
||||
optionsCopySeed: "Copy map seed as URL. It will produce the same map only if options are default or the same",
|
||||
NeedToAdd!: "Regenerate map name",
|
||||
NeedToAdd!: "Tool settings that don't affect maps. Changes are getting applied immediately",
|
||||
NeedToAdd!: "Mimimal possible zoom level (should be > 0)",
|
||||
zoomExtentMin: "Mimimal possible zoom level (should be > 0)",
|
||||
NeedToAdd!: "Maximal possible zoom level (should be > 1)",
|
||||
zoomExtentMax: "Maximal possible zoom level (should be > 1)",
|
||||
zoomExtentDefault: "Restore default [1, 20] zoom extent",
|
||||
configureWorld: "Click to open world configurator to setup map position on Globe and World climate",
|
||||
optionsReset: "Click to restore default options (page will be reloaded)",
|
||||
editHeightmapButton: "Click to open Heightmap customization menu. Shortcut: Shift + H",
|
||||
editBiomesButton: "Click to open Biomes Editor. Shortcut: Shift + B",
|
||||
editStatesButton: "Click to open States Editor. Shortcut: Shift + S",
|
||||
editProvincesButton: "Click to open Provinces Editor. Shortcut: Shift + P",
|
||||
editDiplomacyButton: "Click to open Diplomatical relationships Editor. Shortcut: Shift + D",
|
||||
editCulturesButton: "Click to open Cultures Editor. Shortcut: Shift + C",
|
||||
editNamesBaseButton: "Click to open Namesbase Editor. Shortcut: Shift + N",
|
||||
editZonesButton: "Click to open Zones Editor. Shortcut: Shift + Z",
|
||||
editReligions: "Click to open Religions Editor. Shortcut: Shift + R",
|
||||
editUnitsButton: "Click to open Units Editor. Shortcut: Shift + Q",
|
||||
editNotesButton: "Click to open Notes Editor. Shortcut: Shift + O",
|
||||
overviewBurgsButton: "Click to open Burgs Overview. Shortcut: Shift + T",
|
||||
overviewRiversButton: "Click to open Rivers Overview. Shortcut: Shift + V",
|
||||
overviewMilitaryButton: "Click to open Military Forces Overview. Shortcut: Shift + M",
|
||||
overviewCellsButton: "Click to open Cell details view. Shortcut: Shift + E",
|
||||
regenerateStateLabels: "Click to update state labels placement based on current borders",
|
||||
regenerateReliefIcons: "Click to regenerate all relief icons based on current cell biome and elevation",
|
||||
regenerateRoutes: "Click to regenerate all routes",
|
||||
regenerateRivers: "Click to regenerate all rivers (restore default state)",
|
||||
regeneratePopulation: "Click to recalculate rural and urban population",
|
||||
regenerateBurgs: "Click to regenerate all burgs and routes. States will remain as they are",
|
||||
regenerateStates: "Click to select new capitals and regenerate states. Burgs will remain as they are",
|
||||
regenerateProvinces: "Click to regenerate provinces. States will remain as they are",
|
||||
regenerateReligions: "Click to regenerate religions",
|
||||
regenerateMarkers: "Click to regenerate markers. Hold Ctrl and click to set markers number multiplier",
|
||||
regenerateZones: "Click to regenerate zones. Hold Ctrl and click to set zones number multiplier",
|
||||
addBurgTool: "Click on map to place a burg. Hold Shift to add multiple. Shortcut: Shift + 1",
|
||||
addLabel: "Click on map to place label. Hold Shift to add multiple. Shortcut: Shift + 2",
|
||||
addRiver: "Click on map to place a river. Hold Shift to add multiple. Shortcut: Shift + 3",
|
||||
addRoute: "Click on map to place a route. Shortcut: Shift + 4",
|
||||
addMarker: "Click on map to place a marker. Hold Shift to add multiple. Shortcut: Shift + 5",
|
||||
paintBrushes: "Display brushes panel",
|
||||
applyTemplate: "Open template editor",
|
||||
convertImage: "Open Image Converter",
|
||||
heightmapPreview: "Render heightmap data as a small monochrome image",
|
||||
heightmap3DView: "Preview heightmap in 3D scene",
|
||||
NeedToAdd!: "Click to see supporters names",
|
||||
newMapButton: "Generate a new map based on options. Shortcut: F2",
|
||||
saveButton: "Select format to save map",
|
||||
loadButton: "Load fully functional map in a .map format",
|
||||
zoomReset: "Reset map zoom. Shortcut: 0",
|
||||
finalizeHeightmap: "Finalize the heightmap and exit the edit mode",
|
||||
NeedToAdd!: "Length of Meridian. Almost half of the equator length",
|
||||
meridianLength: "Length of Meridian in pixels",
|
||||
meridianLengthFriendly: "Length of Meridian is friendly units (depends on user configuration)",
|
||||
meridianLengthEarth: "Fantasy world Meridian length relative to real-world Earth (20k km)",
|
||||
labelGroupShow: "Show the group selection",
|
||||
labelGroupHide: "Hide the group selection",
|
||||
labelGroupSelect: "Select a group for this label",
|
||||
labelGroupInput: "Provide a name for the new group",
|
||||
labelGroupNew: "Create new group for this label",
|
||||
labelGroupRemove: "Remove the Group with all labels",
|
||||
labelTextShow: "Show the edit label text section",
|
||||
labelTextHide: "Hide the edit label text section",
|
||||
labelText: "Type to change the label. Enter "|" to move to a new line",
|
||||
labelTextRandom: "Generate random name",
|
||||
labelEditStyle: "Edit label group style in Style Editor",
|
||||
labelSizeShow: "Show the font size section",
|
||||
labelSizeHide: "Hide the font size section",
|
||||
labelStartOffset: "Set starting offset for the particular label",
|
||||
labelRelativeSize: "Set relative size for the particular label",
|
||||
labelAlign: "Turn text path into a straight line",
|
||||
labelLegend: "Edit free text notes (legend) for this label",
|
||||
labelRemoveSingle: "Remove the label. Shortcut: Delete",
|
||||
riverNameShow: "Show river name section",
|
||||
riverNameHide: "Hide the river name section",
|
||||
riverName: "Change river proper name",
|
||||
riverType: "Change river type name",
|
||||
riverNameCulture: "Generate culture-specific name for the river",
|
||||
riverNameRandom: "Generate random name for the river",
|
||||
riverWidthShow: "Show river width and widening change section",
|
||||
riverWidthHide: "Hide the river width and widening change section",
|
||||
riverWidthInput: "Change river width",
|
||||
riverIncrement: "Change river bed increment (widening speed)",
|
||||
riverEditStyle: "Edit style for all rivers in Style Editor",
|
||||
riverLength: "River length in selected units",
|
||||
riverNew: "Create new river clicking on map",
|
||||
riverLegend: "Edit free text notes (legend) for the river",
|
||||
riverRemove: "Remove river. Shortcut: Delete",
|
||||
routeGroupsShow: "Show the group selection",
|
||||
routeGroupsHide: "Hide the group section",
|
||||
routeGroup: "Select a group for this route",
|
||||
routeGroupName: "Provide a name for the new group",
|
||||
routeGroupAdd: "Create new group for this route",
|
||||
routeGroupRemove: "Remove all routes of this group",
|
||||
routeEditStyle: "Edit route group style in Style Editor",
|
||||
routeLength: "Route length in selected units",
|
||||
routeSplit: "Click on a control point to split the route",
|
||||
routeLegend: "Edit free text notes (legend) for the route",
|
||||
routeNew: "Create new route clicking on map",
|
||||
routeRemove: "Remove route. Shortcut: Delete",
|
||||
lakeGroupsShow: "Show the group selection",
|
||||
lakeGroupsHide: "Hide the group section",
|
||||
lakeGroup: "Select a group for this lake",
|
||||
lakeGroupName: "Provide a name for the new group",
|
||||
lakeGroupAdd: "Create new group for this lake",
|
||||
lakeGroupRemove: "Remove the group",
|
||||
lakeEditStyle: "Edit lake group style in Style Editor",
|
||||
lakeArea: "Lake area in selected units",
|
||||
lakeLegend: "Edit free text notes (legend) for the lake",
|
||||
coastlineGroupsShow: "Show the group selection",
|
||||
coastlineGroupsHide: "Hide the group section",
|
||||
coastlineGroup: "Select a group for this coastline",
|
||||
coastlineGroupName: "Provide a name for the new group",
|
||||
coastlineGroupAdd: "Create new group for this coastline",
|
||||
coastlineGroupRemove: "Remove the group",
|
||||
coastlineEditStyle: "Edit coastline group style in Style Editor",
|
||||
coastlineArea: "Lake area in selected units",
|
||||
reliefIndividual: "Edit individual selected icon",
|
||||
reliefBulkAdd: "Place icons in a bulk",
|
||||
reliefBulkRemove: "Remove icons in a bulk",
|
||||
reliefEditStyle: "Edit Relief Icons style in Style Editor",
|
||||
reliefCopy: "Copy selected relief icon",
|
||||
reliefMoveFront: "Move selected relief icon to front",
|
||||
reliefMoveBack: "Move selected relief icon back",
|
||||
reliefRemove: "Remove selected relief icon. Shortcut: Delete",
|
||||
burgName: "Type to rename the burg",
|
||||
burgNameReCulture: "Generate culture-specific name for the burg",
|
||||
burgNameReRandom: "Generate random name for the burg",
|
||||
burgPopulation: "Set burg population",
|
||||
burgCapital: "Shows whether the burg is a state capital. Click to toggle",
|
||||
burgPort: "Shows whether the burg is a port. Click to toggle",
|
||||
burgCitadel: "Shows whether the burg has a citadel (castle). Click to toggle",
|
||||
burgWalls: "Shows whether the burg is walled. Click to toggle",
|
||||
burgPlaza: "Shows whether the burg is a trade center (has big marketplace). Click to toggle",
|
||||
burgTemple: "Shows whether the burg is a religious center. Click to toggle",
|
||||
burgShanty: "Shows whether the burg has a shanty town. Click to toggle",
|
||||
burgGroupShow: "Show group change section",
|
||||
burgGroupHide: "Hide group change section",
|
||||
burgSelectGroup: "Select a group for this burg",
|
||||
burgInputGroup: "Create new Group for the Burg",
|
||||
burgAddGroup: "Create new group for the burg",
|
||||
burgRemoveGroup: "Remove selected burg group",
|
||||
burgStyleShow: "Show style edit section",
|
||||
burgStyleHide: "Hide style edit section",
|
||||
burgEditLabelStyle: "Edit label style for burg group in Style Editor",
|
||||
burgEditIconStyle: "Edit icon style for burg group in Style Editor",
|
||||
burgEditAnchorStyle: "Edit port icon (anchor) style for burg group in Style Editor",
|
||||
burgSeeInMFCG: "Open burg in the Medieval Fantasy City Generator by Watabou. Ctrl + click to change the seed",
|
||||
burgOpenCOA: "Open burg's COA in the Iron Arachne Heraldry Generator. Ctrl + click to change the seed",
|
||||
burgRelocate: "Relocate burg",
|
||||
burglLegend: "Edit free text notes (legend) for this burg",
|
||||
burgRemove: "Remove non-capital burg. Shortcut: Delete",
|
||||
markerGroup: "Change marker type",
|
||||
markerSelectGroup: "Select type for the selected marker",
|
||||
markerInputGroup: "Create new type for selected marker",
|
||||
markerAddGroup: "Create new markers type",
|
||||
markerRemoveGroup: "Remove all markers of that type",
|
||||
markerIcon: "Change marker icon and edit positioning",
|
||||
NeedToAdd!: "Change marker icon size",
|
||||
markerIconSize: "Change marker icon size",
|
||||
NeedToAdd!: "Change marker horizontal shift",
|
||||
markerIconShiftX: "Change icon horizontal shift",
|
||||
NeedToAdd!: "Change marker vertical shift",
|
||||
markerIconShiftY: "Change vertical shift",
|
||||
NeedToAdd!: "Paste custom unicode symbol to use as icon",
|
||||
markerIconCustom: "Paste custom unicode symbol to use as icon",
|
||||
markerStyle: "Change marker size and colors",
|
||||
NeedToAdd!: "Change marker base (pin) style",
|
||||
markerSize: "Change marker size",
|
||||
markerBaseStroke: "Change pin stroke color",
|
||||
markerBaseFill: "Change pin fill color",
|
||||
NeedToAdd!: "Change marker icon style",
|
||||
markerIconStrokeWidth: "Change icon stroke width",
|
||||
markerIconStroke: "Change icon stroke color. Ensure icon stroke width is non-zero",
|
||||
markerIconFill: "Change icon fill color",
|
||||
markerToggleBubble: "Toggle pin (bubble) display",
|
||||
markerLegendButton: "Edit place legend (free text notes)",
|
||||
markerAdd: "Add additional marker of that type",
|
||||
markerRemove: "Remove the marker. Shortcut: Delete",
|
||||
undo: "Undo the latest action (Ctrl + Z)",
|
||||
redo: "Redo the action (Ctrl + Y)",
|
||||
rescaleShow: "Show rescaler slider",
|
||||
rescaleCondShow: "Rescaler: change height if condition is fulfilled",
|
||||
smoothHeights: "Smooth all heights a bit",
|
||||
disruptHeights: "Disrupt (randomize) heights a bit",
|
||||
brushClear: "Set height for all cells to 0 (erase the map)",
|
||||
rescaleHide: "Hide rescaler slider",
|
||||
rescaler: "Change height for all cells",
|
||||
rescaleCondHide: "Hide rescaler",
|
||||
rescaleExecute: "Click to perform an operation",
|
||||
templateHill: "Hill: small blob",
|
||||
templatePit: "Pit: round depression",
|
||||
templateRange: "Range: elongated elevation",
|
||||
templateTrough: "Trough: elongated depression",
|
||||
templateStrait: "Strait: centered vertical or horizontal depression",
|
||||
templateAdd: "Add or subtract value from all heights in range",
|
||||
templateMultiply: "Multiply all heights in range by factor",
|
||||
templateSmooth: "Smooth the map replacing cell heights by an average values of its neighbors",
|
||||
NeedToAdd!: "Click to skip the step",
|
||||
NeedToAdd!: "Remove the step",
|
||||
NeedToAdd!: "Drag to reorder",
|
||||
NeedToAdd!: "Placement range percentage along Y axis (minY-maxY)",
|
||||
NeedToAdd!: "Placement range percentage along X axis (minX-maxX)",
|
||||
NeedToAdd!: "Blob maximum height, use hyphen to get a random number in range",
|
||||
NeedToAdd!: "Blobs to add, use hyphen to get a random number in range",
|
||||
templateRun: "Apply current template",
|
||||
templateUndo: "Undo the latest action",
|
||||
templateRedo: "Redo the action",
|
||||
templateSave: "Download the template as a text file",
|
||||
templateLoad: "Open previously downloaded template",
|
||||
templateCA: "Find or share custom template on Cartography Assets portal",
|
||||
templateTutorial: "Open Template Editor Tutorial",
|
||||
convertImageLoad: "Load image to convert",
|
||||
convertAutoLum: "Auto-assign colors based on liminosity (good to monochrome images)",
|
||||
convertAutoHue: "Auto-assign colors based on hue (good to colored images)",
|
||||
convertColorsButton: "Set number of colors",
|
||||
convertComplete: "Complete the assignment. All unassigned colors will be considered as ocean",
|
||||
NeedToAdd!: "Click to sort by biome name",
|
||||
NeedToAdd!: "Click to sort by biome habitability",
|
||||
NeedToAdd!: "Click to sort by biome cells number",
|
||||
NeedToAdd!: "Click to sort by biome area",
|
||||
NeedToAdd!: "Click to sort by biome population",
|
||||
biomesEditorRefresh: "Refresh the Editor",
|
||||
biomesEditStyle: "Edit biomes style in Style Editor",
|
||||
biomesLegend: "Toggle Legend box",
|
||||
biomesPercentage: "Toggle percentage / absolute values views",
|
||||
biomesManually: "Manually re-assign biomes to not follow the default moisture/temperature pattern",
|
||||
biomesManuallyApply: "Apply current assignment",
|
||||
biomesManuallyCancel: "Cancel assignment",
|
||||
biomesAdd: "Add a custom biome",
|
||||
biomesRestore: "Restore the defaults and re-define biomes based on current moisture and temperature",
|
||||
biomesRegenerateReliefIcons: "Regenerate relief icons based on current biomes and elevation",
|
||||
biomesExport: "Save biomes-related data as a text file (.csv)",
|
||||
NeedToAdd!: "Click to sort by state name",
|
||||
NeedToAdd!: "Click to sort by state form name",
|
||||
NeedToAdd!: "Click to sort by capital name",
|
||||
NeedToAdd!: "Click to sort by state dominant culture",
|
||||
NeedToAdd!: "Click to sort by state burgs count",
|
||||
NeedToAdd!: "Click to sort by state area",
|
||||
NeedToAdd!: "Click to sort by state population",
|
||||
NeedToAdd!: "Click to sort by state type",
|
||||
NeedToAdd!: "Click to sort by state expansion value",
|
||||
NeedToAdd!: "Click to sort by state cells count",
|
||||
statesEditorRefresh: "Refresh the Editor",
|
||||
statesEditStyle: "Edit states style in Style Editor",
|
||||
statesLegend: "Toggle Legend box",
|
||||
statesPercentage: "Toggle percentage / absolute values views",
|
||||
statesChart: "Show states bubble chart",
|
||||
statesRegenerate: "Show the regeneration menu and more data",
|
||||
statesRegenerateBack: "Hide the regeneration menu",
|
||||
statesRandomize: "Randomize states Expansion value and re-calculate states and provinces",
|
||||
statesRecalculate: "Recalculate states based on current values of growth-related attributes",
|
||||
statesManually: "Manually re-assign states",
|
||||
statesManuallyApply: "Apply assignment",
|
||||
statesManuallyCancel: "Cancel assignment",
|
||||
statesAdd: "Add a new state. Hold Shift to add multiple",
|
||||
statesExport: "Save state-related data as a text file (.csv)",
|
||||
NeedToAdd!: "State short name",
|
||||
stateNameEditorShort: "Type to change the short name",
|
||||
stateNameEditorShortCulture: "Generate culture-specific name",
|
||||
stateNameEditorShortRandom: "Generate random name",
|
||||
NeedToAdd!: "State form name",
|
||||
stateNameEditorCustomForm: "Create custom state form name",
|
||||
stateNameEditorAddForm: "Click to add custom state form name to the list",
|
||||
NeedToAdd!: "State full name",
|
||||
stateNameEditorFull: "Type to change the full name",
|
||||
stateNameEditorFullRegenerate: "Click to re-generate full name",
|
||||
NeedToAdd!: "Click to sort by province name",
|
||||
NeedToAdd!: "Click to sort by province form name",
|
||||
NeedToAdd!: "Click to sort by province capital",
|
||||
NeedToAdd!: "Click to sort by province owner",
|
||||
NeedToAdd!: "Click to sort by province area",
|
||||
NeedToAdd!: "Click to sort by province population",
|
||||
provincesEditorRefresh: "Refresh the Editor",
|
||||
provincesEditStyle: "Edit provinces style in Style Editor",
|
||||
provincesPercentage: "Toggle percentage / absolute values views",
|
||||
provincesChart: "Show provinces chart",
|
||||
provincesToggleLabels: "Toggle province labels",
|
||||
provincesExport: "Save provinces-related data as a text file (.csv)",
|
||||
provincesManually: "Manually re-assign provinces",
|
||||
provincesManuallyApply: "Apply assignment",
|
||||
provincesManuallyCancel: "Cancel assignment",
|
||||
provincesAdd: "Add a new province. Hold Shift to add multiple",
|
||||
provincesRemoveAll: "Remove all provinces. States will remain as they are",
|
||||
NeedToAdd!: "Click to sort by state name",
|
||||
NeedToAdd!: "Click to sort by diplomatical relations",
|
||||
NeedToAdd!: "Ally means states formed a defensive pact and will protect each other in case of third party aggression",
|
||||
NeedToAdd!: "State is friendly to anouther state when they share some common interests",
|
||||
NeedToAdd!: "Neutral means states relations are neither positive nor negative",
|
||||
NeedToAdd!: "Suspicion means shate has a cautious distrust of another state",
|
||||
NeedToAdd!: "Enemies are states at war with each other",
|
||||
NeedToAdd!: "Relations are unknown if states do not have enought information about each other",
|
||||
NeedToAdd!: "Rivalry is a state of competing for dominance in the region",
|
||||
NeedToAdd!: "Vassal is a state having obligation to its suzerain",
|
||||
NeedToAdd!: "Suzerain is a state having some control over its vassals",
|
||||
diplomacyEditorRefresh: "Refresh the Editor",
|
||||
diplomacyEditStyle: "Edit states (including diplomacy view) style in Style Editor",
|
||||
diplomacyRegenerate: "Regenerate diplomatical relations",
|
||||
diplomacyHistory: "Show relations history",
|
||||
diplomacyMatrix: "Show relations matrix",
|
||||
diplomacyExport: "Save state relations matrix as a text file (.csv)",
|
||||
NeedToAdd!: "Province short name",
|
||||
provinceNameEditorShort: "Type to change the short name",
|
||||
provinceNameEditorShortCulture: "Generate culture-specific name",
|
||||
provinceNameEditorShortRandom: "Generate random name",
|
||||
NeedToAdd!: "Province form name",
|
||||
provinceNameEditorCustomForm: "Create custom province form name",
|
||||
provinceNameEditorAddForm: "Click to add custom province form name to the list",
|
||||
NeedToAdd!: "Province full name",
|
||||
provinceNameEditorFull: "Type to change the full name",
|
||||
provinceNameEditorFullRegenerate: "Click to re-generate full name",
|
||||
NeedToAdd!: "Click to sort by culture name",
|
||||
NeedToAdd!: "Click to sort by culture cells count",
|
||||
NeedToAdd!: "Click to sort by expansionism",
|
||||
NeedToAdd!: "Click to sort by type",
|
||||
NeedToAdd!: "Click to sort by culture area",
|
||||
NeedToAdd!: "Click to sort by culture population",
|
||||
NeedToAdd!: "Click to sort by culture namesbase",
|
||||
culturesEditorRefresh: "Refresh the Editor",
|
||||
culturesEditStyle: "Edit cultures style in Style Editor",
|
||||
culturesLegend: "Toggle Legend box",
|
||||
culturesPercentage: "Toggle percentage / absolute values display mode",
|
||||
culturesHeirarchy: "Show cultures hierarchy tree",
|
||||
culturesManually: "Manually re-assign cultures",
|
||||
culturesManuallyApply: "Apply assignment",
|
||||
culturesManuallyCancel: "Cancel assignment",
|
||||
culturesEditNamesBase: "Edit a database used for names generation",
|
||||
culturesAdd: "Add a new culture. Hold Shift to add multiple",
|
||||
culturesExport: "Download cultures-related data",
|
||||
culturesRecalculate: "Recalculate cultures based on current values of growth-related attributes",
|
||||
namesbaseSelect: "Select base to edit",
|
||||
namesbaseTextarea: "Names data: a comma separated list of source names used for names generation",
|
||||
namesbaseName: "Type to change a base name",
|
||||
namesbaseMin: "Recommended minimum name length",
|
||||
namesbaseMax: "Recommended maximum name length",
|
||||
namesbaseDouble: "Populate with letters that can used twice in a row",
|
||||
namesbaseMulti: "Multi-word names rate. 1 - allow all multi-word names, 0 - all names should spelled as a single word",
|
||||
namesbaseExamples: "Examples. Click to re-generate",
|
||||
namesbaseUpdateExamples: "Re-generate examples based on provided data",
|
||||
namesbaseAdd: "Add new namesbase",
|
||||
namesbaseDefault: "Restore default namesbase",
|
||||
namesbaseDownload: "Download namesbase to PC",
|
||||
namesbaseUpload: "Upload a namesbase from PC",
|
||||
namesbaseCA: "Find or share custom namesbase on Cartography Assets portal",
|
||||
NeedToAdd!: "Zone description",
|
||||
NeedToAdd!: "Zone cells count",
|
||||
NeedToAdd!: "Zone area",
|
||||
NeedToAdd!: "Zone population",
|
||||
zonesEditorRefresh: "Refresh the Editor",
|
||||
zonesEditStyle: "Edit zones style in Style Editor",
|
||||
zonesLegend: "Toggle Legend box",
|
||||
zonesPercentage: "Toggle percentage / absolute values views",
|
||||
zonesManually: "Re-assign zones",
|
||||
zonesManuallyApply: "Apply assignment",
|
||||
zonesManuallyCancel: "Cancel assignment",
|
||||
zonesRemove: "Click to toggle the removal mode on brush dragging. Shortcut: ctrl",
|
||||
zonesAdd: "Add a new zone layer",
|
||||
zonesExport: "Download zones-related data",
|
||||
notesSelect: "Select object",
|
||||
notesName: "Type to change object name",
|
||||
notesText: "Type object description",
|
||||
notesFocus: "Focus on selected object",
|
||||
notesDownload: "Download notes to PC",
|
||||
notesUpload: "Upload notes from PC",
|
||||
notesRemove: "Remove this note",
|
||||
NeedToAdd!: "Click to sort by religion name",
|
||||
NeedToAdd!: "Click to sort by religion type",
|
||||
NeedToAdd!: "Click to sort by religion form",
|
||||
NeedToAdd!: "Click to sort by supreme deity",
|
||||
NeedToAdd!: "Click to sort by religion area",
|
||||
NeedToAdd!: "Click to sort by number of believers (religion area population)",
|
||||
religionsEditorRefresh: "Refresh the Editor",
|
||||
religionsEditStyle: "Edit religions style in Style Editor",
|
||||
religionsLegend: "Toggle Legend box",
|
||||
religionsPercentage: "Toggle percentage / absolute values display mode",
|
||||
religionsHeirarchy: "Show religions hierarchy tree",
|
||||
religionsExtinct: "Show/hide extinct religions (religions without cells)",
|
||||
religionsManually: "Manually re-assign religions",
|
||||
religionsManuallyApply: "Apply assignment",
|
||||
religionsManuallyCancel: "Cancel assignment",
|
||||
religionsAdd: "Add a new religion. Hold Shift to add multiple",
|
||||
religionsExport: "Download religions-related data",
|
||||
addLinearRuler: "Click to place a linear measurer (ruler)",
|
||||
addOpisometer: "Drag to measure a curve length (opisometer)",
|
||||
addPlanimeter: "Drag to measure a polygon area (planimeter)",
|
||||
removeRulers: "Remove all rulers from the map. Click on ruler label to remove a ruler separately",
|
||||
unitsRestore: "Restore default units settings",
|
||||
NeedToAdd!: "Click to sort by burg name",
|
||||
NeedToAdd!: "Click to sort by province name",
|
||||
NeedToAdd!: "Click to sort by state name",
|
||||
NeedToAdd!: "Click to sort by culture name",
|
||||
NeedToAdd!: "Click to sort by burg population",
|
||||
NeedToAdd!: "Click to sort by burg type",
|
||||
burgsOverviewRefresh: "Refresh the Editor",
|
||||
burgsChart: "Show burgs bubble chart",
|
||||
regenerateBurgNames: "Regenerate burg names based on assigned culture",
|
||||
addNewBurg: "Add a new burg. Hold Shift to add multiple",
|
||||
burgsExport: "Save burgs-related data as a text file (.csv)",
|
||||
burgNamesImport: "Rename burgs in bulk",
|
||||
burgsRemoveAll: "Remove all burgs except for capitals. To remove a capital remove its state first",
|
||||
NeedToAdd!: "Click to sort by river name",
|
||||
NeedToAdd!: "Click to sort by river type name",
|
||||
NeedToAdd!: "Click to sort by river length",
|
||||
NeedToAdd!: "Click to sort by river basin",
|
||||
riversOverviewRefresh: "Refresh the Editor",
|
||||
addNewRiver: "Add a new river. Hold Shift to add multiple",
|
||||
riversBasinHighlight: "Toggle basin highlight mode",
|
||||
riversExport: "Save rivers-related data as a text file (.csv)",
|
||||
riversRemoveAll: "Remove all rivers",
|
||||
NeedToAdd!: "State name. Click to sort",
|
||||
militaryTotal: "Total military personnel (considering crew). Click to sort",
|
||||
NeedToAdd!: "State population. Click to sort",
|
||||
NeedToAdd!: "Military personnel rate (% of state population). Depends on war alert. Click to sort",
|
||||
NeedToAdd!: "War Alert. Modifier to military forces number, depends of political situation. Click to sort",
|
||||
militaryOverviewRefresh: "Refresh the Editor",
|
||||
militaryOptionsButton: "Military options. Click to customize units",
|
||||
militaryOverviewRecalculate: "Recalculate military forces based on current options",
|
||||
militaryExport: "Save military-related data as a text file (.csv)",
|
||||
militaryVisualize: "Show armies on the map",
|
||||
NeedToAdd!: "Unit name. If name is changed for existing unit, old unit will be replaced",
|
||||
NeedToAdd!: "Conscription percentage for rural population",
|
||||
NeedToAdd!: "Conscription percentage for urban population",
|
||||
NeedToAdd!: "Average number of people in crew",
|
||||
NeedToAdd!: "Unit type to apply special rules on forces recalculation",
|
||||
styleSaverName: "Enter style preset name",
|
||||
styleSaverTip: "Shows whether there is already a preset with this name",
|
||||
styleSaverJSON: "Style JSON is getting formed based the current settings, but can be entered manually",
|
||||
styleSaverSave: "Save current JSON as a new style preset",
|
||||
styleSaverDownload: "Download the style as a .json file (can be opened in any text editor)",
|
||||
styleSaverLoad: "Open previously downloaded style file",
|
||||
styleSaverCA: "Find or share custom style preset on Cartography Assets portal",
|
||||
NeedToAdd!: "Equirectangular projection is used: distortion is maximum on poles. Use map with aspect ratio 2:1 for best result",
|
||||
options3dUpdate: "Update the scene",
|
||||
NeedToAdd!: "Configure world and map size and climate settings",
|
||||
options3dSave: "Save screenshot of the 3d scene",
|
||||
NeedToAdd!: "Download the map as fully-functional .map file to your machine",
|
||||
NeedToAdd!: "Download the map as vector image (open in browser or Inkscape)",
|
||||
NeedToAdd!: "Download visible part of the map as .png (lossless compressed)",
|
||||
NeedToAdd!: "Download visible part of the map as .jpeg (lossy compressed) image",
|
||||
NeedToAdd!: "Download map data in GeoJSON format",
|
||||
NeedToAdd!: "Save fully-functional map to browser storage. Shortcut: F6",
|
||||
NeedToAdd!: "Load .map file from local disk",
|
||||
NeedToAdd!: "Load .map file from URL (server should allow CORS)",
|
||||
NeedToAdd!: "Load map from browser storage (if saved before)"
|
||||
};
|
||||
23
lang/lang-ru.js
Normal file
23
lang/lang-ru.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Файл перевода FMG на русский язык. Источник: lang-en.js. Версия 0.01a
|
||||
window.translation = {
|
||||
titleFull: "Генератор фэнтези карт",
|
||||
titleName: " ",
|
||||
title: "Генератор фэнтези карт",
|
||||
version: "в. ",
|
||||
loading: "ЗАГРУЗКА",
|
||||
newMap: "Новая карта!",
|
||||
layers: "Слои",
|
||||
style: "Стиль",
|
||||
options: "Опции",
|
||||
tools: "Редактор",
|
||||
about: "Справка",
|
||||
tipOptionsTrigger: "Нажмите для открытия панели меню. Клавиша Tab",
|
||||
tipRegenerate: "Нажмите, чтобы сгенерировать новую карту. Клавиша F2",
|
||||
optionsDragTrigger: "Зажмите и тяните, чтобы переместить меню",
|
||||
optionsHide: "Нажмите, чтобы скрыть меню. Клавиша: Tab (скрыть меню) Esc (скрыть меню и закрыть все окна)",
|
||||
layersTab: "Нажмите, чтобы изменить слои карты, их видимость и порядок",
|
||||
styleTab: "Нажмите, чтобы окрыть вкладку для работы над стилем",
|
||||
optionsTab: "Нажмите, чтобы изменить настройки генерации карты и пользовательского интерейса",
|
||||
toolsTab: "Нажмите, чтобы открыть вкладку инструментов",
|
||||
aboutTab: "Нажмите, чтобы посмотреть информацию о Генераторе"
|
||||
};
|
||||
55
libs/translate.js
Normal file
55
libs/translate.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// Translation module
|
||||
"use strict";
|
||||
|
||||
void function() {
|
||||
window.lang = "en"; // default language
|
||||
|
||||
if (localStorage.getItem("lang")) window.lang = localStorage.getItem("lang");
|
||||
else {
|
||||
const isSupported = ln => ["ru"].includes(ln); // list of supported languages with at least 50% support
|
||||
const browserLang = navigator.language.split("-")[0];
|
||||
if (isSupported(browserLang)) window.lang = browserLang;
|
||||
}
|
||||
|
||||
selectLanguage.value = window.lang;
|
||||
if (window.lang === "en") return; // no need to translate
|
||||
initiateTranslation();
|
||||
|
||||
async function initiateTranslation() {
|
||||
const loaded = await loadTranslation();
|
||||
if (!loaded) {
|
||||
tip(`Cannot load ${window.lang} translation, check files in lang folder`, false, "error", 4000);
|
||||
window.lang == "en"; // set to default value
|
||||
return false;
|
||||
}
|
||||
|
||||
function loadTranslation() {
|
||||
return new Promise(resolve => {
|
||||
const script = document.createElement('script');
|
||||
script.src = `lang/lang-${window.lang}.js`
|
||||
document.head.append(script);
|
||||
script.onload = () => resolve(true);
|
||||
script.onerror = () => resolve(false);
|
||||
});
|
||||
}
|
||||
|
||||
if (translation["titleFull"]) document.title = translation["titleFull"];
|
||||
|
||||
void function translateDOM() {
|
||||
const tTags = Array.from(document.getElementsByTagName("t"));
|
||||
tTags.forEach(t => {
|
||||
const id = t.dataset.t;
|
||||
const text = translation[id];
|
||||
if (!text) return;
|
||||
t.innerHTML = text;
|
||||
});
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
function translate(id, originalEn) {
|
||||
const text = translation[id];
|
||||
return text ? text : originalEn;
|
||||
}
|
||||
211
main.js
211
main.js
|
|
@ -7,7 +7,7 @@
|
|||
// See also https://github.com/Azgaar/Fantasy-Map-Generator/issues/153
|
||||
|
||||
"use strict";
|
||||
const version = "1.22"; // generator version
|
||||
const version = "1.3"; // generator version
|
||||
document.title += " v" + version;
|
||||
|
||||
// if map version is not stored, clear localStorage and show a message
|
||||
|
|
@ -58,6 +58,7 @@ let labels = viewbox.append("g").attr("id", "labels");
|
|||
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 fogging = viewbox.append("g").attr("id", "fogging-cont").attr("mask", "url(#fog)")
|
||||
.append("g").attr("id", "fogging").style("display", "none");
|
||||
|
|
@ -101,8 +102,7 @@ let grid = {}; // initial grapg based on jittered square grid and data
|
|||
let pack = {}; // packed graph and data
|
||||
let seed, mapHistory = [], elSelected, modules = {}, notes = [];
|
||||
let customization = 0; // 0 - no; 1 = heightmap draw; 2 - states draw; 3 - add state/burg; 4 - cultures draw
|
||||
let mapCoordinates = {}; // map coordinates on globe
|
||||
let winds = [225, 45, 225, 315, 135, 315]; // default wind directions
|
||||
|
||||
let biomesData = applyDefaultBiomesSystem();
|
||||
let nameBases = Names.getNameBases(); // cultures-related data
|
||||
const fonts = ["Almendra+SC", "Georgia", "Arial", "Times+New+Roman", "Comic+Sans+MS", "Lucida+Sans+Unicode", "Courier+New"]; // default web-safe fonts
|
||||
|
|
@ -114,6 +114,16 @@ const lineGen = d3.line().curve(d3.curveBasis); // d3 line generator with defaul
|
|||
let scale = 1, viewX = 0, viewY = 0;
|
||||
const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", zoomed);
|
||||
|
||||
// default options
|
||||
let options = {}; // options object
|
||||
let mapCoordinates = {}; // map coordinates on globe
|
||||
options.winds = [225, 45, 225, 315, 135, 315]; // default wind directions
|
||||
|
||||
// woldbuilding options
|
||||
options.year = rand(100, 2000); // current year
|
||||
options.era = Names.getBaseShort(P(.7) ? 1 : rand(nameBases.length)) + " Era"; // current era name, global for all cultures
|
||||
options.eraShort = options.era[0] + "E"; // short name for era
|
||||
|
||||
applyStoredOptions();
|
||||
let graphWidth = +mapWidthInput.value, graphHeight = +mapHeightInput.value; // voronoi graph extention, cannot be changed arter generation
|
||||
let svgWidth = graphWidth, svgHeight = graphHeight; // svg canvas resolution, can be changed
|
||||
|
|
@ -287,13 +297,12 @@ 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 color = ["#53679f","#fbe79f","#b5b887","#d2d082","#c8d68f","#b6d95d","#29bc56","#7dcb35","#409c43","#4b6b32","#96784b","#d5e7eb","#0b9131"];
|
||||
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 cost = [10,200,150,60,50,70,70,80,90,80,100,255,150]; // biome movement cost
|
||||
const biomesMartix = [
|
||||
// hot ↔ cold; dry ↕ wet
|
||||
const cost = [10,200,150,60,50,70,70,80,90,200,1000,5000,150]; // biome movement cost
|
||||
const biomesMartix = [ // hot ↔ cold; dry ↕ wet
|
||||
new Uint8Array([1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]),
|
||||
new Uint8Array([3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,9,9,9,9,9,10,10]),
|
||||
new Uint8Array([5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,9,9,9,9,9,10,10,10]),
|
||||
|
|
@ -314,7 +323,7 @@ function applyDefaultBiomesSystem() {
|
|||
}
|
||||
|
||||
function showWelcomeMessage() {
|
||||
const post = link("https://www.reddit.com/r/FantasyMapGenerator/comments/dlow3k/update_new_version_is_published_v_12", "Main changes:"); // announcement on Reddit
|
||||
const post = link("https://www.reddit.com/r/FantasyMapGenerator/comments/dlow3k/update_new_version_is_published_v_13", "Main changes:"); // announcement on Reddit
|
||||
const changelog = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "previous version");
|
||||
const reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit community");
|
||||
const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server");
|
||||
|
|
@ -324,13 +333,15 @@ function showWelcomeMessage() {
|
|||
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
|
||||
|
||||
<ul>${post}
|
||||
<li>3d scene and Globe view</li>
|
||||
<li>Ability to save map as JPEG image</li>
|
||||
<li>Diplomacy Editor enhancements</li>
|
||||
<li>Rivers Overview screen</li>
|
||||
<li>Military Forces generation</li>
|
||||
<li>Military Forces overview</li>
|
||||
<li>Military Units editor</li>
|
||||
<li>Regiments editor</li>
|
||||
</ul>
|
||||
|
||||
<p>Thanks for all supporters on ${patreon}!</i></p>`;
|
||||
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
||||
|
||||
<span>Thanks for all supporters on ${patreon}!</i></span>`;
|
||||
|
||||
$("#alert").dialog(
|
||||
{resizable: false, title: "Fantasy Map Generator update", width: "28em",
|
||||
|
|
@ -437,41 +448,42 @@ function invokeActiveZooming() {
|
|||
}
|
||||
}
|
||||
|
||||
// Pull request from @evyatron
|
||||
// add drag to upload logic, pull request from @evyatron
|
||||
void function addDragToUpload() {
|
||||
document.addEventListener('dragover', function(e) {
|
||||
document.addEventListener("dragover", function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$('#map-dragged').show();
|
||||
document.getElementById("mapOverlay").style.display = null;
|
||||
});
|
||||
|
||||
document.addEventListener('dragleave', function(e) {
|
||||
$('#map-dragged').hide();
|
||||
document.getElementById("mapOverlay").style.display = "none";
|
||||
});
|
||||
|
||||
document.addEventListener('drop', function(e) {
|
||||
document.addEventListener("drop", function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$('#map-dragged').hide();
|
||||
// no files or more than one
|
||||
if (e.dataTransfer.items == null || e.dataTransfer.items.length != 1) {return;}
|
||||
|
||||
const overlay = document.getElementById("mapOverlay");
|
||||
overlay.style.display = "none";
|
||||
if (e.dataTransfer.items == null || e.dataTransfer.items.length !== 1) return; // no files or more than one
|
||||
const file = e.dataTransfer.items[0].getAsFile();
|
||||
// not a .map file
|
||||
if (file.name.indexOf('.map') == -1) {
|
||||
if (file.name.indexOf('.map') == -1) { // not a .map file
|
||||
alertMessage.innerHTML = 'Please upload a <b>.map</b> file you have previously downloaded';
|
||||
$("#alert").dialog({
|
||||
resizable: false, title: "Invalid file format",
|
||||
width: "40em", buttons: {
|
||||
Close: function() { $(this).dialog("close"); }
|
||||
}, position: {my: "center", at: "center", of: "svg"}
|
||||
resizable: false, title: "Invalid file format", position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {Close: function() {$(this).dialog("close");}}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// all good - show uploading text and load the map
|
||||
$("#map-dragged > p").text("Uploading<span>.</span><span>.</span><span>.</span>");
|
||||
closeDialogs();
|
||||
uploadMap(file, function onUploadFinish() {
|
||||
$("#map-dragged > p").text("Drop to upload");
|
||||
overlay.style.display = null;
|
||||
overlay.innerHTML = "Uploading<span>.</span><span>.</span><span>.</span>";
|
||||
if (closeDialogs) closeDialogs();
|
||||
uploadMap(file, () => {
|
||||
overlay.style.display = "none";
|
||||
overlay.innerHTML = "Drop a .map file to open";
|
||||
});
|
||||
});
|
||||
}()
|
||||
|
|
@ -516,7 +528,8 @@ function generate() {
|
|||
BurgsAndStates.drawStateLabels();
|
||||
|
||||
Rivers.specify();
|
||||
//calculateMilitaryForces();
|
||||
|
||||
Military.generate();
|
||||
addMarkers();
|
||||
addZones();
|
||||
Names.getMapName();
|
||||
|
|
@ -724,13 +737,14 @@ function calculateTemperatures() {
|
|||
const tEq = +temperatureEquatorInput.value;
|
||||
const tPole = +temperaturePoleInput.value;
|
||||
const tDelta = tEq - tPole;
|
||||
const int = d3.easePolyInOut.exponent(.5); // interpolation function
|
||||
|
||||
d3.range(0, cells.i.length, grid.cellsX).forEach(function(r) {
|
||||
const y = grid.points[r][1];
|
||||
const lat = Math.abs(mapCoordinates.latN - y / graphHeight * mapCoordinates.latT);
|
||||
const initTemp = tEq - lat / 90 * tDelta;
|
||||
const lat = Math.abs(mapCoordinates.latN - y / graphHeight * mapCoordinates.latT); // [0; 90]
|
||||
const initTemp = tEq - int(lat / 90) * tDelta;
|
||||
for (let i = r; i < r+grid.cellsX; i++) {
|
||||
cells.temp[i] = initTemp - convertToFriendly(cells.h[i]);
|
||||
cells.temp[i] = Math.max(Math.min(initTemp - convertToFriendly(cells.h[i]), 127), -128);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -772,10 +786,10 @@ function generatePrecipitation() {
|
|||
const band = (Math.abs(lat) - 1) / 5 | 0;
|
||||
const latMod = lalitudeModifier[band];
|
||||
const tier = Math.abs(lat - 89) / 30 | 0; // 30d tiers from 0 to 5 from N to S
|
||||
if (winds[tier] > 40 && winds[tier] < 140) westerly.push([c, latMod, tier]);
|
||||
else if (winds[tier] > 220 && winds[tier] < 320) easterly.push([c + cellsX -1, latMod, tier]);
|
||||
if (winds[tier] > 100 && winds[tier] < 260) northerly++;
|
||||
else if (winds[tier] > 280 || winds[tier] < 80) southerly++;
|
||||
if (options.winds[tier] > 40 && options.winds[tier] < 140) westerly.push([c, latMod, tier]);
|
||||
else if (options.winds[tier] > 220 && options.winds[tier] < 320) easterly.push([c + cellsX -1, latMod, tier]);
|
||||
if (options.winds[tier] > 100 && options.winds[tier] < 260) northerly++;
|
||||
else if (options.winds[tier] > 280 || options.winds[tier] < 80) southerly++;
|
||||
});
|
||||
|
||||
// distribute winds by direction
|
||||
|
|
@ -1017,7 +1031,7 @@ function drawCoastline() {
|
|||
// Re-mark features (ocean, lakes, islands)
|
||||
function reMarkFeatures() {
|
||||
console.time("reMarkFeatures");
|
||||
const cells = pack.cells, features = pack.features = [0];
|
||||
const cells = pack.cells, features = pack.features = [0], temp = grid.cells.temp;
|
||||
cells.f = new Uint16Array(cells.i.length); // cell feature number
|
||||
cells.t = new Int16Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast;
|
||||
cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length);// cell haven (opposite water cell);
|
||||
|
|
@ -1027,6 +1041,7 @@ function reMarkFeatures() {
|
|||
const start = queue[0]; // first cell
|
||||
cells.f[start] = i; // assign feature number
|
||||
const land = cells.h[start] >= 20;
|
||||
//const frozen = !land && temp[cells.g[start]] < -5; // check if water is frozen
|
||||
let border = false; // true if feature touches map border
|
||||
let cellNumber = 1; // to count cells number in a feature
|
||||
|
||||
|
|
@ -1044,9 +1059,10 @@ function reMarkFeatures() {
|
|||
if (!cells.t[e] && cells.t[q] === 1) cells.t[e] = 2;
|
||||
else if (!cells.t[q] && cells.t[e] === 1) cells.t[q] = 2;
|
||||
}
|
||||
if (land === eLand && cells.f[e] === 0) {
|
||||
cells.f[e] = i;
|
||||
if (!cells.f[e] && land === eLand) {
|
||||
//if (!land && frozen !== temp[cells.g[e]] < -5) return;
|
||||
queue.push(e);
|
||||
cells.f[e] = i;
|
||||
cellNumber++;
|
||||
}
|
||||
});
|
||||
|
|
@ -1054,15 +1070,14 @@ function reMarkFeatures() {
|
|||
|
||||
const type = land ? "island" : border ? "ocean" : "lake";
|
||||
let group;
|
||||
if (type === "lake") group = defineLakeGroup(start, cellNumber);
|
||||
else if (type === "ocean") group = "ocean";
|
||||
if (type === "lake") group = defineLakeGroup(start, cellNumber, temp[cells.g[start]]);
|
||||
else if (type === "ocean") group = defineOceanGroup(cellNumber);
|
||||
else if (type === "island") group = defineIslandGroup(start, cellNumber);
|
||||
features.push({i, land, border, type, cells: cellNumber, firstCell: start, group});
|
||||
queue[0] = cells.f.findIndex(f => !f); // find unmarked cell
|
||||
}
|
||||
|
||||
function defineLakeGroup(cell, number) {
|
||||
const temp = grid.cells.temp[cells.g[cell]];
|
||||
function defineLakeGroup(cell, number, temp) {
|
||||
if (temp > 24) return "salt";
|
||||
if (temp < -3) return "frozen";
|
||||
const height = d3.max(cells.c[cell].map(c => cells.h[c]));
|
||||
|
|
@ -1071,6 +1086,12 @@ function reMarkFeatures() {
|
|||
return "freshwater";
|
||||
}
|
||||
|
||||
function defineOceanGroup(number) {
|
||||
if (number > grid.cells.i.length / 25) return "ocean";
|
||||
if (number > grid.cells.i.length / 100) return "sea";
|
||||
return "gulf";
|
||||
}
|
||||
|
||||
function defineIslandGroup(cell, number) {
|
||||
if (cell && features[cells.f[cell-1]].type === "lake") return "lake_island";
|
||||
if (number > grid.cells.i.length / 10) return "continent";
|
||||
|
|
@ -1105,12 +1126,13 @@ function defineBiomes() {
|
|||
|
||||
for (const i of cells.i) {
|
||||
if (f[cells.f[i]].group === "freshwater") cells.h[i] = 19; // de-elevate lakes
|
||||
if (cells.h[i] < 20) continue; // water cells have biome 0
|
||||
const temp = grid.cells.temp[cells.g[i]]; // temperature
|
||||
|
||||
if (cells.h[i] < 20 && temp > -6) continue; // liquid water cells have biome 0
|
||||
let moist = grid.cells.prec[cells.g[i]];
|
||||
if (cells.r[i]) moist += Math.max(cells.fl[i] / 20, 2);
|
||||
const n = cells.c[i].filter(isLand).map(c => grid.cells.prec[cells.g[c]]).concat([moist]);
|
||||
moist = rn(4 + d3.mean(n));
|
||||
const temp = grid.cells.temp[cells.g[i]]; // flux from precipitation
|
||||
cells.biome[i] = getBiomeId(moist, temp, cells.h[i]);
|
||||
}
|
||||
|
||||
|
|
@ -1119,6 +1141,7 @@ function defineBiomes() {
|
|||
|
||||
function getBiomeId(moisture, temperature, height) {
|
||||
if (temperature < -5) return 11; // permafrost biome
|
||||
if (height < 20) return 0; // liquid water cells have marine biome
|
||||
if (moisture > 40 && height < 25 || moisture > 24 && height > 24) return 12; // wetland biome
|
||||
const m = Math.min(moisture / 5 | 0, 4); // moisture band from 0 to 4
|
||||
const t = Math.min(Math.max(20 - temperature, 0), 25); // temparature band from 0 to 25
|
||||
|
|
@ -1163,88 +1186,11 @@ function rankCells() {
|
|||
console.timeEnd('rankCells');
|
||||
}
|
||||
|
||||
// calculate army and fleet based on state cells polulation
|
||||
function calculateMilitaryForces() {
|
||||
const cells = pack.cells, states = pack.states;
|
||||
const valid = states.filter(s => s.i && !s.removed); // valid states
|
||||
valid.forEach(s => s.military = {infantry:0, cavalry:0, archers:0, reserve:0, fleet:0});
|
||||
|
||||
for (const i of cells.i) {
|
||||
const s = states[cells.state[i]]; // cell state
|
||||
if (!s.i || s.removed) continue;
|
||||
|
||||
let m = cells.pop[i] / 100; // basic army is 1% of rural population
|
||||
if (cells.culture[i] !== s.culture) m = s.form === "Union" ? m / 1.2 : m / 2; // non-dominant culture
|
||||
if (cells.religion[i] !== cells.religion[s.center]) m = s.form === "Theocracy" ? m / 2.2 : m / 1.4; // non-dominant religion
|
||||
if (cells.f[i] !== cells.f[s.center]) m = s.type === "Naval" ? m / 1.2 : m / 1.8; // different landmass
|
||||
|
||||
let infantry = m * .5; // basic infantry is 50% of army
|
||||
let archers = m * .25; // basic archers is 25% of army
|
||||
let cavalry = m * .25; // basic cavalry is 25% of army
|
||||
|
||||
if ([1, 2, 3, 4].includes(cells.biome[i])) {cavalry *= 3; infantry /= 5; archers /= 2;} else // "nomadic" biomes have lots of cavalry
|
||||
if ([7, 8, 9, 12].includes(cells.biome[i])) {cavalry /= 2.5; infantry *= 1.2; archers *= 1.2;} // "wet" biomes have reduced number of cavalry
|
||||
|
||||
s.military.infantry += infantry;
|
||||
s.military.archers += archers;
|
||||
s.military.cavalry += cavalry;
|
||||
s.military.reserve += m * 3 + cells.pop[i] * .02; // reserve is ~5% of population
|
||||
}
|
||||
|
||||
for (const b of pack.burgs) {
|
||||
if (!b.i || b.removed || !b.state) continue;
|
||||
const s = states[b.state]; // burg state
|
||||
|
||||
let m = b.population / 50; // basic army is 2% of urban population
|
||||
if (b.capital) m *= 2; // capital has household troops
|
||||
if (b.culture !== s.culture) m = s.form === "Union" ? m / 1.2 : m / 2; // non-dominant culture
|
||||
if (cells.religion[b.cell] !== cells.religion[s.center]) m = s.form === "Theocracy" ? m / 2.2 : m / 1.4; // non-dominant religion
|
||||
if (cells.f[b.cell] !== cells.f[s.center]) m = s.type === "Naval" ? m / 1.2 : m / 1.8; // different landmass
|
||||
|
||||
let infantry = m * .6; // basic infantry is 60% of army
|
||||
let archers = m * .3; // basic archers is 3% of army
|
||||
let cavalry = m * .1; // basic cavalry is 10% of army
|
||||
|
||||
const biome = cells.biome[b.cell]; // burg biome
|
||||
if ([1, 2, 3, 4].includes(biome)) {cavalry *= 3; infantry /= 2;} else // "nomadic" biomes have lots of cavalry
|
||||
if ([7, 8, 9, 12].includes(biome)) {cavalry /= 4; infantry *= 1.2; archers *= 1.4;} // "wet" biomes have reduced number of cavalry
|
||||
|
||||
s.military.infantry += infantry;
|
||||
s.military.archers += archers;
|
||||
s.military.cavalry += cavalry;
|
||||
s.military.reserve += m * 2 + b.population * .01; // reserve is ~5% of population
|
||||
|
||||
if (!b.port) continue; // only ports have fleet
|
||||
let ships = b.capital ? b.population / 3 : b.population / 5; // ~1 ship per 5 population points
|
||||
if (s.type === "Naval") ships *= 1.8; // "naval" states have more ships
|
||||
s.military.fleet += ~~ships + +P(ships % 1);
|
||||
}
|
||||
|
||||
const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion
|
||||
const area = d3.sum(valid.map(s => s.area)); // total area
|
||||
const rate = {x:0, Ally:-.2, Friendly:-.1, Neutral:0, Suspicion:.1, Enemy:1, Unknown:0, Rival:.5, Vassal:.5, Suzerain:-.5};
|
||||
|
||||
valid.forEach(s => {
|
||||
const m = s.military, d = s.diplomacy;
|
||||
const expansionRate = Math.min(Math.max((s.expansionism / expn) / (s.area / area), .25), 4); // how much state expansionism is relized
|
||||
const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? .8 : d.some(d => d === "Suspicion") ? .5 : .1; // peacefulness
|
||||
const neighborsRate = Math.min(Math.max(s.neighbors.map(n => n ? pack.states[n].diplomacy[s.i] : "Suspicion").reduce((s, r) => s += rate[r], .5), .3), 3); // neighbors rate
|
||||
m.alert = rn(expansionRate * diplomacyRate * neighborsRate, 2); // war alert rate (army modifier)
|
||||
|
||||
m.infantry = rn(m.infantry * m.alert, 3);
|
||||
m.cavalry = rn(m.cavalry * m.alert, 3);
|
||||
m.archers = rn(m.archers * m.alert, 3);
|
||||
m.reserve = rn(m.reserve, 3);
|
||||
});
|
||||
|
||||
console.table(valid.map(s=>[s.name, s.military.alert, s.military.infantry, s.military.archers, s.military.cavalry, s.military.reserve, rn(s.military.reserve/(s.urban+s.rural)*100,2)+"%", s.military.fleet]));
|
||||
}
|
||||
|
||||
// generate some markers
|
||||
function addMarkers(number = 1) {
|
||||
if (!number) return;
|
||||
console.time("addMarkers");
|
||||
const cells = pack.cells;
|
||||
const cells = pack.cells, states = pack.states;
|
||||
|
||||
void function addVolcanoes() {
|
||||
let mounts = Array.from(cells.i).filter(i => cells.h[i] > 70).sort((a, b) => cells.h[b] - cells.h[a]);
|
||||
|
|
@ -1306,7 +1252,7 @@ function addMarkers(number = 1) {
|
|||
.attr("data-size", 1).attr("width", 30).attr("height", 30);
|
||||
const resource = rw(resources);
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const name = `${burg.name} - ${resource} mining town`;
|
||||
const name = `${burg.name} — ${resource} mining town`;
|
||||
const population = rn(burg.population * populationRate.value * urbanization.value);
|
||||
const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`;
|
||||
notes.push({id, name, legend});
|
||||
|
|
@ -1412,9 +1358,8 @@ function addMarkers(number = 1) {
|
|||
}()
|
||||
|
||||
void function addBattlefields() {
|
||||
let battlefields = Array.from(cells.i).filter(i => cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25);
|
||||
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);
|
||||
const era = Names.getCulture(0, 3, 7, "", 0) + " Era";
|
||||
if (count) addMarker("battlefield", "⚔", 50, 50, 20);
|
||||
|
||||
while (count) {
|
||||
|
|
@ -1426,9 +1371,11 @@ function addMarkers(number = 1) {
|
|||
.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 campaign = ra(states[cells.state[cell]].campaigns);
|
||||
const date = generateDate(campaign.start, campaign.end);
|
||||
const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
|
||||
const date = new Date(rand(100, 1000),rand(12),rand(31)).toLocaleDateString("en", {year:'numeric', month:'long', day:'numeric'}) + " " + era;
|
||||
notes.push({id, name, legend:`A historical battlefield spot. \r\nDate: ${date}`});
|
||||
const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`;
|
||||
notes.push({id, name, legend});
|
||||
count--;
|
||||
}
|
||||
}()
|
||||
|
|
@ -1800,7 +1747,7 @@ const regenerateMap = debounce(function() {
|
|||
|
||||
// clear the map
|
||||
function undraw() {
|
||||
viewbox.selectAll("path, circle, polygon, line, text, use, #zones > g, #ruler > g").remove();
|
||||
viewbox.selectAll("path, circle, polygon, line, text, use, #zones > g, #armies > g, #ruler > g").remove();
|
||||
defs.selectAll("path, clipPath").remove();
|
||||
notes = [];
|
||||
unfog();
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
collectStatistics();
|
||||
assignColors();
|
||||
|
||||
generateCampaigns();
|
||||
generateDiplomacy();
|
||||
Routes.draw(capitalRoutes, townRoutes, oceanRoutes);
|
||||
drawBurgs();
|
||||
|
|
@ -136,24 +137,60 @@
|
|||
}
|
||||
}
|
||||
|
||||
// define burg coordinates and define details
|
||||
// define burg coordinates, port status and define details
|
||||
const specifyBurgs = function() {
|
||||
console.time("specifyBurgs");
|
||||
const cells = pack.cells, vertices = pack.vertices;
|
||||
const cells = pack.cells, vertices = pack.vertices, features = pack.features;
|
||||
|
||||
// separate arctic seas for correct searoutes generation
|
||||
void function checkAccessibility() {
|
||||
const oceanCells = cells.i.filter(i => cells.h[i] < 20 && features[cells.f[i]].type === "ocean");
|
||||
const marked = [];
|
||||
let firstCell = oceanCells.find(i => !marked[i]);
|
||||
|
||||
while (firstCell !== undefined) {
|
||||
const queue = [firstCell];
|
||||
const f = features[cells.f[firstCell]]; // old feature
|
||||
const i = last(features).i+1; // new feature id to assign
|
||||
const biome = cells.biome[firstCell];
|
||||
marked[firstCell] = 1;
|
||||
let cellNumber = 1;
|
||||
|
||||
while (queue.length) {
|
||||
for (const c of cells.c[queue.pop()]) {
|
||||
if (cells.biome[c] !== biome || cells.h[c] >= 20) continue;
|
||||
if (marked[c]) continue;
|
||||
queue.push(c);
|
||||
cells.f[c] = i;
|
||||
marked[c] = 1;
|
||||
cellNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
const group = biome ? "frozen " + f.group : f.group;
|
||||
features.push({i, parent:f.i, land:false, border:f.border, type:"ocean", cells: cellNumber, firstCell, group});
|
||||
firstCell = oceanCells.find(i => !marked[i]);
|
||||
}
|
||||
}()
|
||||
|
||||
for (const b of pack.burgs) {
|
||||
if (!b.i) continue;
|
||||
const i = b.cell;
|
||||
|
||||
// asign port status: capital with any harbor and towns with good harbors
|
||||
const port = (b.capital && cells.harbor[i]) || cells.harbor[i] === 1;
|
||||
b.port = port ? cells.f[cells.haven[i]] : 0; // port is defined by feature id it lays on
|
||||
// asign port status
|
||||
const haven = cells.haven[i];
|
||||
if (haven && cells.biome[haven] === 0) {
|
||||
const f = cells.f[haven]; // water body id
|
||||
// port is a capital with any harbor OR town with good harbor
|
||||
const port = features[f].cells > 1 && ((b.capital && cells.harbor[i]) || cells.harbor[i] === 1);
|
||||
b.port = port ? f : 0; // port is defined by water body id it lays on
|
||||
} else b.port = 0;
|
||||
|
||||
// define burg population (keep urbanization at about 10% rate)
|
||||
b.population = rn(Math.max((cells.s[i] + cells.road[i]) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
|
||||
if (b.capital) b.population = rn(b.population * 1.3, 3); // increase capital population
|
||||
|
||||
if (port) {
|
||||
if (b.port) {
|
||||
b.population = b.population * 1.3; // increase port population
|
||||
const e = cells.v[i].filter(v => vertices.c[v].some(c => c === cells.haven[i])); // vertices of common edge
|
||||
b.x = rn((vertices.p[e[0]][0] + vertices.p[e[1]][0]) / 2, 2);
|
||||
|
|
@ -164,7 +201,7 @@
|
|||
b.population = rn(b.population * gauss(2,3,.6,20,3), 3);
|
||||
|
||||
// shift burgs on rivers semi-randomly and just a bit
|
||||
if (!port && cells.r[i]) {
|
||||
if (!b.port && cells.r[i]) {
|
||||
const shift = Math.min(cells.fl[i]/150, 1);
|
||||
if (i%2) b.x = rn(b.x + shift, 2); else b.x = rn(b.x - shift, 2);
|
||||
if (cells.r[i]%2) b.y = rn(b.y + shift, 2); else b.y = rn(b.y - shift, 2);
|
||||
|
|
@ -172,12 +209,11 @@
|
|||
}
|
||||
|
||||
// de-assign port status if it's the only one on feature
|
||||
for (const f of pack.features) {
|
||||
if (!f.i || f.land) continue;
|
||||
const onFeature = pack.burgs.filter(b => b.port === f.i);
|
||||
if (onFeature.length === 1) {
|
||||
onFeature[0].port = 0;
|
||||
}
|
||||
const ports = pack.burgs.filter(b => !b.removed && b.port > 0);
|
||||
for (const f of features) {
|
||||
if (!f.i || f.land || f.border) continue;
|
||||
const featurePorts = ports.filter(b => b.port === f.i);
|
||||
if (featurePorts.length === 1) featurePorts[0].port = 0;
|
||||
}
|
||||
|
||||
console.timeEnd("specifyBurgs");
|
||||
|
|
@ -268,16 +304,19 @@
|
|||
while (queue.length) {
|
||||
const next = queue.dequeue(), n = next.e, p = next.p, s = next.s, b = next.b;
|
||||
const type = states[s].type;
|
||||
const culture = states[s].culture;
|
||||
|
||||
cells.c[n].forEach(function(e) {
|
||||
if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells
|
||||
|
||||
const cultureCost = states[s].culture === cells.culture[e] ? -9 : 700;
|
||||
const cultureCost = culture === cells.culture[e] ? -9 : 100;
|
||||
const populationCost = cells.s[e] ? 20 - cells.s[e] : 2500;
|
||||
const biomeCost = getBiomeCost(b, cells.biome[e], type);
|
||||
const heightCost = getHeightCost(pack.features[cells.f[e]], cells.h[e], type);
|
||||
const riverCost = getRiverCost(cells.r[e], e, type);
|
||||
const typeCost = getTypeCost(cells.t[e], type);
|
||||
const totalCost = p + (10 + cultureCost + biomeCost + heightCost + riverCost + typeCost) / states[s].expansionism;
|
||||
const cellCost = Math.max(cultureCost + populationCost + biomeCost + heightCost + riverCost + typeCost, 0);
|
||||
const totalCost = p + 10 + cellCost / states[s].expansionism;
|
||||
|
||||
if (totalCost > neutral) return;
|
||||
|
||||
|
|
@ -319,7 +358,7 @@
|
|||
function getTypeCost(t, type) {
|
||||
if (t === 1) return type === "Naval" || type === "Lake" ? 0 : type === "Nomadic" ? 60 : 20; // penalty for coastline
|
||||
if (t === 2) return type === "Naval" || type === "Nomadic" ? 30 : 0; // low penalty for land level 2 for Navals and nomads
|
||||
if (t !== -1) return type === "Naval" || type === "Lake" ? 100 : 0; // penalty for mainland for navals
|
||||
if (t !== -1) return type === "Naval" || type === "Lake" ? 100 : 0; // penalty for mainland for navals
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -589,6 +628,21 @@
|
|||
console.timeEnd("assignColors");
|
||||
}
|
||||
|
||||
// generate historical conflicts of each state
|
||||
const generateCampaigns = function() {
|
||||
const wars = {"War":4, "Conflict":2, "Campaign":4, "Invasion":2, "Rebellion":2, "Conquest":2, "Intervention":1, "Expedition":1, "Crusade":1};
|
||||
|
||||
pack.states.forEach(s => {
|
||||
if (!s.i || s.removed) return;
|
||||
const n = s.neighbors.length ? s.neighbors : [0];
|
||||
s.campaigns = n.map(i => {
|
||||
const name = i && P(.8) ? pack.states[i].name : Names.getCultureShort(s.culture);
|
||||
const start = gauss(options.year-100, 150, 1, options.year-6), end = start + gauss(4, 5, 1, options.year - start - 1);
|
||||
return {name:getAdjective(name) + " " + rw(wars), start, end};
|
||||
}).sort((a, b) => a.start - b.start);
|
||||
});
|
||||
}
|
||||
|
||||
// generate Diplomatic Relationships
|
||||
const generateDiplomacy = function() {
|
||||
console.time("generateDiplomacy");
|
||||
|
|
@ -666,6 +720,9 @@
|
|||
|
||||
// start a war
|
||||
const war = [`${an}-${trimVowels(dn)}ian War`,`${an} declared a war on its rival ${dn}`];
|
||||
const start = options.year - gauss(2, 2, 0, 5);
|
||||
states[attacker].campaigns.push({name: `${trimVowels(dn)}ian War`, start, end:options.year});
|
||||
states[defender].campaigns.push({name: `${trimVowels(an)}ian War`, start, end:options.year});
|
||||
|
||||
// attacker vassals join the war
|
||||
ad.forEach((r, d) => {if (r === "Suzerain") {
|
||||
|
|
@ -997,6 +1054,6 @@
|
|||
|
||||
return {generate, expandStates, normalizeStates, assignColors,
|
||||
drawBurgs, specifyBurgs, defineBurgFeatures, drawStateLabels, collectStatistics,
|
||||
generateDiplomacy, defineStateForms, getFullName, generateProvinces};
|
||||
generateCampaigns, generateDiplomacy, defineStateForms, getFullName, generateProvinces};
|
||||
|
||||
})));
|
||||
|
|
|
|||
299
modules/military-generator.js
Normal file
299
modules/military-generator.js
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global.Military = factory());
|
||||
}(this, (function () {'use strict';
|
||||
|
||||
let cells, p, states;
|
||||
|
||||
const generate = function() {
|
||||
console.time("generateMilitaryForces");
|
||||
cells = pack.cells, p = cells.p, states = pack.states;
|
||||
const valid = states.filter(s => s.i && !s.removed); // valid states
|
||||
if (!options.military) options.military = getDefaultOptions();
|
||||
|
||||
const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion
|
||||
const area = d3.sum(valid.map(s => s.area)); // total area
|
||||
const rate = {x:0, Ally:-.2, Friendly:-.1, Neutral:0, Suspicion:.1, Enemy:1, Unknown:0, Rival:.5, Vassal:.5, Suzerain:-.5};
|
||||
|
||||
const stateModifier = {
|
||||
"melee": {"Nomadic":.5, "Highland":1.2, "Lake":1, "Naval":.7, "Hunting":1.2, "River":1.1},
|
||||
"ranged": {"Nomadic":.9, "Highland":1.3, "Lake":1, "Naval":.8, "Hunting":2, "River":.8},
|
||||
"mounted": {"Nomadic":2.3, "Highland":.6, "Lake":.7, "Naval":.3, "Hunting":.7, "River":.8},
|
||||
"machinery":{"Nomadic":.8, "Highland":1.4, "Lake":1.1, "Naval":1.4, "Hunting":.4, "River":1.1},
|
||||
"naval": {"Nomadic":.5, "Highland":.5, "Lake":1.2, "Naval":1.8, "Hunting":.7, "River":1.2}
|
||||
};
|
||||
|
||||
valid.forEach(s => {
|
||||
const temp = s.temp = {}, d = s.diplomacy;
|
||||
const expansionRate = Math.min(Math.max((s.expansionism / expn) / (s.area / area), .25), 4); // how much state expansionism is realized
|
||||
const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? .8 : d.some(d => d === "Suspicion") ? .5 : .1; // peacefulness
|
||||
const neighborsRate = Math.min(Math.max(s.neighbors.map(n => n ? pack.states[n].diplomacy[s.i] : "Suspicion").reduce((s, r) => s += rate[r], .5), .3), 3); // neighbors rate
|
||||
s.alert = Math.min(Math.max(rn(expansionRate * diplomacyRate * neighborsRate, 2), .1), 5); // war alert rate (army modifier)
|
||||
temp.platoons = [];
|
||||
|
||||
// apply overall state modifiers for unit types based on state features
|
||||
for (const unit of options.military) {
|
||||
if (!stateModifier[unit.type]) continue;
|
||||
let modifier = stateModifier[unit.type][s.type] || 1;
|
||||
if (unit.type === "mounted" && s.form === "Horde") modifier *= 2; else
|
||||
if (unit.type === "naval" && s.form === "Republic") modifier *= 1.2;
|
||||
temp[unit.name] = modifier * s.alert;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (!cells.pop[i]) continue;
|
||||
const s = states[cells.state[i]]; // cell state
|
||||
if (!s.i || s.removed) continue;
|
||||
|
||||
let m = cells.pop[i] / 100; // basic rural army in percentages
|
||||
if (cells.culture[i] !== s.culture) m = s.form === "Union" ? m / 1.2 : m / 2; // non-dominant culture
|
||||
if (cells.religion[i] !== cells.religion[s.center]) m = s.form === "Theocracy" ? m / 2.2 : m / 1.4; // non-dominant religion
|
||||
if (cells.f[i] !== cells.f[s.center]) m = s.type === "Naval" ? m / 1.2 : m / 1.8; // different landmass
|
||||
|
||||
const nomadic = [1, 2, 3, 4].includes(cells.biome[i]);
|
||||
const wetland = [7, 8, 9, 12].includes(cells.biome[i]);
|
||||
const highland = cells.h[i] >= 70;
|
||||
|
||||
for (const u of options.military) {
|
||||
const perc = +u.rural;
|
||||
if (isNaN(perc) || perc <= 0) continue;
|
||||
|
||||
let army = m * perc; // basic army for rural cell
|
||||
if (nomadic) { // "nomadic" biomes special rules
|
||||
if (u.type === "melee") army /= 5; else
|
||||
if (u.type === "ranged") army /= 2; else
|
||||
if (u.type === "mounted") army *= 3;
|
||||
}
|
||||
|
||||
if (wetland) { // "wet" biomes special rules
|
||||
if (u.type === "melee") army *= 1.2; else
|
||||
if (u.type === "ranged") army *= 1.4; else
|
||||
if (u.type === "mounted") army /= 3;
|
||||
}
|
||||
|
||||
if (highland) { // highlands special rules
|
||||
if (u.type === "melee") army *= 1.2; else
|
||||
if (u.type === "ranged") army *= 1.6; else
|
||||
if (u.type === "mounted") army /= 3;
|
||||
}
|
||||
|
||||
const t = rn(army * s.temp[u.name] * populationRate.value);
|
||||
if (!t) continue;
|
||||
let x = p[i][0], y = p[i][1], n = 0;
|
||||
if (u.type === "naval") {let haven = cells.haven[i]; x = p[haven][0], y = p[haven][1]; n = 1}; // place naval to sea
|
||||
s.temp.platoons.push({cell: i, a:t, t, x, y, u:u.name, n, s:u.separate});
|
||||
}
|
||||
}
|
||||
|
||||
for (const b of pack.burgs) {
|
||||
if (!b.i || b.removed || !b.state || !b.population) continue;
|
||||
const s = states[b.state]; // burg state
|
||||
|
||||
let m = b.population * urbanization.value / 100; // basic urban army in percentages
|
||||
if (b.capital) m *= 1.2; // capital has household troops
|
||||
if (b.culture !== s.culture) m = s.form === "Union" ? m / 1.2 : m / 2; // non-dominant culture
|
||||
if (cells.religion[b.cell] !== cells.religion[s.center]) m = s.form === "Theocracy" ? m / 2.2 : m / 1.4; // non-dominant religion
|
||||
if (cells.f[b.cell] !== cells.f[s.center]) m = s.type === "Naval" ? m / 1.2 : m / 1.8; // different landmass
|
||||
|
||||
const biome = cells.biome[b.cell]; // burg biome
|
||||
const nomadic = [1, 2, 3, 4].includes(biome);
|
||||
const wetland = [7, 8, 9, 12].includes(biome);
|
||||
const highland = cells.h[b.cell] >= 70;
|
||||
|
||||
for (const u of options.military) {
|
||||
const perc = +u.urban;
|
||||
if (isNaN(perc) || perc <= 0) continue;
|
||||
let army = m * perc; // basic army for rural cell
|
||||
|
||||
if (u.type === "naval" && !b.port) continue; // only ports produce naval units
|
||||
|
||||
if (nomadic) { // "nomadic" biomes special rules
|
||||
if (u.type === "melee") army /= 3; else
|
||||
if (u.type === "machinery") army /= 2; else
|
||||
if (u.type === "mounted") army *= 3;
|
||||
}
|
||||
|
||||
if (wetland) { // "wet" biomes special rules
|
||||
if (u.type === "melee") army *= 1.2; else
|
||||
if (u.type === "ranged") army *= 1.4; else
|
||||
if (u.type === "machinery") army *= 1.2; else
|
||||
if (u.type === "mounted") army /= 4;
|
||||
}
|
||||
|
||||
if (highland) { // highlands special rules
|
||||
if (u.type === "ranged") army *= 2; else
|
||||
if (u.type === "naval") army /= 3; else
|
||||
if (u.type === "mounted") army /= 3;
|
||||
}
|
||||
|
||||
const t = rn(army * s.temp[u.name] * populationRate.value);
|
||||
if (!t) continue;
|
||||
let x = p[b.cell][0], y = p[b.cell][1], n = 0;
|
||||
if (u.type === "naval") {let haven = cells.haven[b.cell]; x = p[haven][0], y = p[haven][1]; n = 1}; // place naval to sea
|
||||
s.temp.platoons.push({cell: b.cell, a:t, t, x, y, u:u.name, n, s:u.separate});
|
||||
}
|
||||
}
|
||||
|
||||
void function removeExistingRegiments() {
|
||||
armies.selectAll("g > g").each(function() {
|
||||
const index = notes.findIndex(n => n.id === this.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
});
|
||||
armies.selectAll("g").remove();
|
||||
}()
|
||||
|
||||
const expected = 3 * populationRate.value; // expected regiment size
|
||||
const mergeable = (n, s) => (!n.s && !s.s) || n.u === s.u;
|
||||
// get regiments for each state
|
||||
valid.forEach(s => {
|
||||
s.military = createRegiments(s.temp.platoons, s);
|
||||
delete s.temp; // do not store temp data
|
||||
drawRegiments(s.military, s.i);
|
||||
});
|
||||
|
||||
function createRegiments(nodes, s) {
|
||||
if (!nodes.length) return [];
|
||||
nodes.sort((a,b) => a.a - b.a);
|
||||
const tree = d3.quadtree(nodes, d => d.x, d => d.y);
|
||||
nodes.forEach(n => {
|
||||
tree.remove(n);
|
||||
const overlap = tree.find(n.x, n.y, 20);
|
||||
if (overlap && overlap.t && mergeable(n, overlap)) {merge(n, overlap); return;}
|
||||
if (n.t > expected) return;
|
||||
const r = (expected - n.t) / (n.s?40:20); // search radius
|
||||
const candidates = tree.findAll(n.x, n.y, r);
|
||||
for (const c of candidates) {
|
||||
if (c.t < expected && mergeable(n, c)) {merge(n, c); break;}
|
||||
}
|
||||
});
|
||||
|
||||
// add n0 to n1's ultimate parent
|
||||
function merge(n0, n1) {
|
||||
if (!n1.childen) n1.childen = [n0]; else n1.childen.push(n0);
|
||||
if (n0.childen) n0.childen.forEach(n => n1.childen.push(n));
|
||||
n1.t += n0.t;
|
||||
n0.t = 0;
|
||||
}
|
||||
|
||||
// parse regiments data to easy-readable json
|
||||
const regiments = nodes.filter(n => n.t).sort((a,b) => b.t - a.t).map((r, i) => {
|
||||
const u = {}; u[r.u] = r.a;
|
||||
(r.childen||[]).forEach(n => u[n.u] = u[n.u] ? u[n.u] += n.a : n.a);
|
||||
return {i, a:r.t, cell:r.cell, x:r.x, y:r.y, bx:r.x, by:r.y, u, n:r.n, name};
|
||||
});
|
||||
|
||||
// generate name for regiments
|
||||
regiments.forEach(r => {
|
||||
r.name = getName(r, regiments);
|
||||
r.icon = getEmblem(r);
|
||||
generateNote(r, s);
|
||||
});
|
||||
|
||||
return regiments;
|
||||
}
|
||||
|
||||
console.timeEnd("generateMilitaryForces");
|
||||
}
|
||||
|
||||
const getDefaultOptions = function() {
|
||||
return [
|
||||
{name:"infantry", rural:.25, urban:.2, crew:1, type:"melee", separate:0},
|
||||
{name:"archers", rural:.12, urban:.2, crew:1, type:"ranged", separate:0},
|
||||
{name:"cavalry", rural:.12, urban:.03, crew:3, type:"mounted", separate:0},
|
||||
{name:"artillery", rural:0, urban:.03, crew:8, type:"machinery", separate:0},
|
||||
{name:"fleet", rural:0, urban:.015, crew:100, type:"naval", separate:1}
|
||||
];
|
||||
}
|
||||
|
||||
const drawRegiments = function(regiments, s) {
|
||||
const size = +armies.attr("box-size");
|
||||
const w = d => d.n ? size * 4 : size * 6;
|
||||
const h = size * 2;
|
||||
const x = d => rn(d.x - w(d) / 2, 2);
|
||||
const y = d => rn(d.y - size, 2);
|
||||
|
||||
const baseColor = pack.states[s].color[0] === "#" ? pack.states[s].color : "#999";
|
||||
const darkerColor = d3.color(baseColor).darker().hex();
|
||||
const army = armies.append("g").attr("id", "army"+s).attr("fill", baseColor);
|
||||
|
||||
const g = army.selectAll("g").data(regiments).enter().append("g")
|
||||
.attr("id", d => "regiment"+s+"-"+d.i).attr("data-name", d => d.name).attr("data-state", s).attr("data-id", d => d.i);
|
||||
g.append("rect").attr("x", d => x(d)).attr("y", d => y(d)).attr("width", d => w(d)).attr("height", h);
|
||||
g.append("text").attr("x", d => d.x).attr("y", d => d.y).text(d => getTotal(d));
|
||||
g.append("rect").attr("fill", darkerColor).attr("x", d => x(d)-h).attr("y", d => y(d)).attr("width", h).attr("height", h);
|
||||
g.append("text").attr("class", "regimentIcon").attr("x", d => x(d)-size).attr("y", d => d.y).text(d => d.icon);
|
||||
}
|
||||
|
||||
const drawRegiment = function(reg, s) {
|
||||
const size = +armies.attr("box-size");
|
||||
const w = reg.n ? size * 4 : size * 6;
|
||||
const h = size * 2;
|
||||
const x1 = rn(reg.x - w / 2, 2);
|
||||
const y1 = rn(reg.y - size, 2);
|
||||
|
||||
const army = armies.select("g#army"+s);
|
||||
const darkerColor = d3.color(army.attr("fill")).darker().hex();
|
||||
|
||||
const g = army.append("g").attr("id", "regiment"+s+"-"+reg.i).attr("data-name", reg.name).attr("data-state", s).attr("data-id", reg.i);
|
||||
g.append("rect").attr("x", x1).attr("y", y1).attr("width", w).attr("height", h);
|
||||
g.append("text").attr("x", reg.x).attr("y", reg.y).text(getTotal(reg));
|
||||
g.append("rect").attr("fill", darkerColor).attr("x", x1-h).attr("y", y1).attr("width", h).attr("height", h);
|
||||
g.append("text").attr("class", "regimentIcon").attr("x", x1-size).attr("y", reg.y).text(reg.icon);
|
||||
}
|
||||
|
||||
// utilize si function to make regiment total text fit regiment box
|
||||
const getTotal = reg => reg.a > (reg.n ? 999 : 99999) ? si(reg.a) : reg.a;
|
||||
|
||||
const getName = function(r, regiments) {
|
||||
const proper = r.n ? null :
|
||||
cells.province[r.cell] && pack.provinces[cells.province[r.cell]] ? pack.provinces[cells.province[r.cell]].name :
|
||||
cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]] ? pack.burgs[cells.burg[r.cell]].name : null
|
||||
const number = nth(regiments.filter(reg => reg.n === r.n && reg.i < r.i).length+1);
|
||||
const form = r.n ? "Fleet" : "Regiment";
|
||||
return `${number}${proper?` (${proper}) `:` `}${form}`;
|
||||
}
|
||||
|
||||
// get default regiment emblem
|
||||
const getEmblem = function(r) {
|
||||
if (r.n) return "🌊";
|
||||
if (!Object.values(r.u).length) return "🛡️";
|
||||
const mainUnit = Object.entries(r.u).sort((a,b) => b[1]-a[1])[0][0];
|
||||
const type = options.military.find(u => u.name === mainUnit).type;
|
||||
if (type === "ranged") return "🏹";
|
||||
if (type === "mounted") return "🐴";
|
||||
if (type === "machinery") return "💣";
|
||||
else return "⚔️";
|
||||
}
|
||||
|
||||
const generateNote = function(r, s) {
|
||||
const base = cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]] ? pack.burgs[cells.burg[r.cell]].name :
|
||||
cells.province[r.cell] && pack.provinces[cells.province[r.cell]] ? pack.provinces[cells.province[r.cell]].fullName : null;
|
||||
const station = base ? `${r.name} is ${r.n ? "based" : "stationed"} in ${base}. ` : "";
|
||||
|
||||
const composition = r.a ? Object.keys(r.u).map(t => `— ${t}: ${r.u[t]}`).join("\r\n") : null;
|
||||
const troops = composition ? `\r\n\r\nRegiment composition:\r\n${composition}.` : "";
|
||||
|
||||
const campaign = s.campaigns ? ra(s.campaigns) : null;
|
||||
const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year-100, 150, 1, options.year-6);
|
||||
const conflict = campaign ? ` during the ${campaign.name}` : "";
|
||||
const legend = `Regiment was formed in ${year} ${options.era}${conflict}. ${station}${troops}`;
|
||||
notes.push({id:`regiment${s.i}-${r.i}`, name:`${r.icon} ${r.name}`, legend});
|
||||
}
|
||||
|
||||
// const updateNote = function(r, s) {
|
||||
// const id = `regiment${s}-${r.i}`;
|
||||
// const note = notes.find(n => n.id === id);
|
||||
// if (!note) return;
|
||||
|
||||
// const oldComposition = note.legend.split("composition:\r\n")[1]||"".split(".")[0];
|
||||
// if (!oldComposition) return;
|
||||
// const newComposition = Object.keys(r.u).map(t => `— ${t}: ${r.u[t]}`).join("\r\n") + ".";
|
||||
// note.legend = note.legend.replace(oldComposition, newComposition);
|
||||
// }
|
||||
|
||||
return {generate, getDefaultOptions, getName, generateNote, drawRegiments, drawRegiment, getTotal, getEmblem};
|
||||
|
||||
})));
|
||||
|
|
@ -76,6 +76,9 @@
|
|||
}
|
||||
|
||||
// parse word to get a final name
|
||||
const l = last(w); // last letter
|
||||
if (l === "'" || l === " ") w = w.slice(0,-1); // not allow apostrophe and space at the end
|
||||
|
||||
let name = [...w].reduce(function(r, c, i, d) {
|
||||
if (c === d[i+1] && !dupl.includes(c)) return r; // duplication is not allowed
|
||||
if (!r.length) return c.toUpperCase();
|
||||
|
|
@ -83,8 +86,7 @@
|
|||
if (r.slice(-1) === " ") return r + c.toUpperCase(); // capitalize letter after space
|
||||
if (r.slice(-1) === "-") return r + c.toUpperCase(); // capitalize letter after hyphen
|
||||
if (c === "a" && d[i+1] === "e") return r; // "ae" => "e"
|
||||
if (c === " " && i+1 === d.length) return r;
|
||||
if (i+2 < d.length && !vowel(c) && !vowel(d[i+1]) && !vowel(d[i+2])) return r; // remove consonant before 2 consonants
|
||||
if (i+1 < d.length && !vowel(c) && !vowel(d[i-1]) && !vowel(d[i+1])) return r; // remove consonant between 2 consonants
|
||||
if (i+2 < d.length && c === d[i+1] && c === d[i+2]) return r; // remove tree same letters in a row
|
||||
return r + c;
|
||||
}, "");
|
||||
|
|
@ -96,6 +98,7 @@
|
|||
console.error("Name is too short! Random name to be selected");
|
||||
name = ra(nameBases[base].b.split(","));
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
|
@ -115,8 +118,7 @@
|
|||
// generate short name for base
|
||||
const getBaseShort = function(base) {
|
||||
if (nameBases[base] === undefined) {
|
||||
tip(`Namebase for culture ${pack.cultures[culture].name} does not exist.
|
||||
Please upload custom namebases of change the base in Cultures Editor`, false, "error");
|
||||
tip(`Namebase ${base} does not exist. Please upload custom namebases of change the base in Cultures Editor`, false, "error");
|
||||
base = 1;
|
||||
}
|
||||
const min = nameBases[base].min-1;
|
||||
|
|
|
|||
|
|
@ -117,8 +117,9 @@
|
|||
const increment = rn(.8 + Math.random() * .6, 1); // river bed widening modifier
|
||||
const [path, length] = getPath(riverEnhanced, width, increment);
|
||||
riverPaths.push([r, path, width, increment]);
|
||||
const parent = riverSegments[0].parent || 0;
|
||||
pack.rivers.push({i:r, parent, length, source:riverSegments[0].cell, mouth:last(riverSegments).cell});
|
||||
const source = riverSegments[0], mouth = riverSegments[riverSegments.length-2];
|
||||
const parent = source.parent || 0;
|
||||
pack.rivers.push({i:r, parent, length, source:source.cell, mouth:mouth.cell});
|
||||
} else {
|
||||
// remove too short rivers
|
||||
riverSegments.filter(s => cells.r[s.cell] === r).forEach(s => cells.r[s.cell] = 0);
|
||||
|
|
@ -258,6 +259,7 @@
|
|||
for (const r of pack.rivers) {
|
||||
r.basin = getBasin(r.i, r.parent);
|
||||
r.name = getName(r.mouth);
|
||||
//debug.append("circle").attr("cx", pack.cells.p[r.mouth][0]).attr("cy", pack.cells.p[r.mouth][1]).attr("r", 2);
|
||||
const small = r.length < smallLength;
|
||||
r.type = r.parent && !(r.i%6) ? small ? "Branch" : "Fork" : small ? rw(smallType) : "River";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,43 +64,49 @@
|
|||
const cells = pack.cells, allPorts = pack.burgs.filter(b => b.port > 0 && !b.removed);
|
||||
if (allPorts.length < 2) return [];
|
||||
const bodies = new Set(allPorts.map(b => b.port)); // features with ports
|
||||
let from = [], exit = null, path = [], paths = []; // array to store path segments
|
||||
let paths = []; // array to store path segments
|
||||
|
||||
bodies.forEach(function(f) {
|
||||
const ports = allPorts.filter(b => b.port === f);
|
||||
if (ports.length < 2) return;
|
||||
const first = ports[0].cell;
|
||||
const farthest = ports[d3.scan(ports, (a, b) => ((b.y - ports[0].y) ** 2 + (b.x - ports[0].x) ** 2) - ((a.y - ports[0].y) ** 2 + (a.x - ports[0].x) ** 2))].cell;
|
||||
|
||||
// directly connect first port with the farthest one on the same island to remove gap
|
||||
if (pack.features[f].type !== "lake") {
|
||||
void function() {
|
||||
if (!pack.features[f] || pack.features[f].type === "lake") return;
|
||||
const portsOnIsland = ports.filter(b => cells.f[b.cell] === cells.f[first]);
|
||||
if (portsOnIsland.length > 3) {
|
||||
const opposite = ports[d3.scan(portsOnIsland, (a, b) => ((b.y - ports[0].y) ** 2 + (b.x - ports[0].x) ** 2) - ((a.y - ports[0].y) ** 2 + (a.x - ports[0].x) ** 2))].cell;
|
||||
//debug.append("circle").attr("r", 1).attr("fill", "blue").attr("cx", pack.cells.p[first][0]).attr("cy", pack.cells.p[first][1])
|
||||
//debug.append("circle").attr("r", 1).attr("fill", "green").attr("cx", pack.cells.p[opposite][0]).attr("cy", pack.cells.p[opposite][1])
|
||||
[from, exit] = findOceanPath(opposite, first);
|
||||
from[first] = cells.haven[first];
|
||||
path = restorePath(opposite, first, "ocean", from);
|
||||
paths = paths.concat(path);
|
||||
}
|
||||
}
|
||||
if (portsOnIsland.length < 4) return;
|
||||
const opposite = ports[d3.scan(portsOnIsland, (a, b) => ((b.y - ports[0].y) ** 2 + (b.x - ports[0].x) ** 2) - ((a.y - ports[0].y) ** 2 + (a.x - ports[0].x) ** 2))].cell;
|
||||
//debug.append("circle").attr("cx", pack.cells.p[opposite][0]).attr("cy", pack.cells.p[opposite][1]).attr("r", 1);
|
||||
//debug.append("circle").attr("cx", pack.cells.p[first][0]).attr("cy", pack.cells.p[first][1]).attr("fill", "red").attr("r", 1);
|
||||
const [from, exit, passable] = findOceanPath(opposite, first);
|
||||
if (!passable) return;
|
||||
from[first] = cells.haven[first];
|
||||
const path = restorePath(opposite, first, "ocean", from);
|
||||
paths = paths.concat(path);
|
||||
}()
|
||||
|
||||
// directly connect first port with the farthest one
|
||||
const farthest = ports[d3.scan(ports, (a, b) => ((b.y - ports[0].y) ** 2 + (b.x - ports[0].x) ** 2) - ((a.y - ports[0].y) ** 2 + (a.x - ports[0].x) ** 2))].cell;
|
||||
[from, exit] = findOceanPath(farthest, first);
|
||||
from[first] = cells.haven[first];
|
||||
path = restorePath(farthest, first, "ocean", from);
|
||||
paths = paths.concat(path);
|
||||
void function() {
|
||||
const [from, exit, passable] = findOceanPath(farthest, first);
|
||||
if (!passable) return;
|
||||
from[first] = cells.haven[first];
|
||||
const path = restorePath(farthest, first, "ocean", from);
|
||||
paths = paths.concat(path);
|
||||
}()
|
||||
|
||||
// indirectly connect first port with all other ports
|
||||
if (ports.length < 3) return;
|
||||
for (const p of ports) {
|
||||
if (p.cell === first || p.cell === farthest) continue;
|
||||
[from, exit] = findOceanPath(p.cell, first, true);
|
||||
//from[exit] = cells.haven[exit];
|
||||
const path = restorePath(p.cell, exit, "ocean", from);
|
||||
paths = paths.concat(path);
|
||||
}
|
||||
void function() {
|
||||
if (ports.length < 3) return;
|
||||
for (const p of ports) {
|
||||
if (p.cell === first || p.cell === farthest) continue;
|
||||
const [from, exit, passable] = findOceanPath(p.cell, first, true);
|
||||
if (!passable) continue;
|
||||
const path = restorePath(p.cell, exit, "ocean", from);
|
||||
paths = paths.concat(path);
|
||||
}
|
||||
}()
|
||||
|
||||
});
|
||||
|
||||
|
|
@ -173,9 +179,11 @@
|
|||
for (const c of cells.c[n]) {
|
||||
if (cells.h[c] < 20) continue; // ignore water cells
|
||||
const stateChangeCost = cells.state && cells.state[c] !== cells.state[n] ? 400 : 0; // trails tend to lay within the same state
|
||||
const habitedCost = Math.max(100 - biomesData.habitability[cells.biome[c]], 0); // routes tend to lay within populated areas
|
||||
const habitability = biomesData.habitability[cells.biome[c]];
|
||||
const habitedCost = habitability ? Math.max(100 - habitability, 0) : 400; // routes tend to lay within populated areas
|
||||
const heightChangeCost = Math.abs(cells.h[c] - cells.h[n]) * 10; // routes tend to avoid elevation changes
|
||||
const cellCoast = 10 + stateChangeCost + habitedCost + heightChangeCost;
|
||||
const heightCost = cells.h[c] > 80 ? cells.h[c] : 0; // routes tend to avoid mountainous areas
|
||||
const cellCoast = 10 + stateChangeCost + habitedCost + heightChangeCost + heightCost;
|
||||
const totalCost = p + (cells.road[c] || cells.burg[c] ? cellCoast / 3 : cellCoast);
|
||||
|
||||
if (from[c] || totalCost >= cost[c]) continue;
|
||||
|
|
@ -234,22 +242,21 @@
|
|||
|
||||
while (queue.length) {
|
||||
const next = queue.dequeue(), n = next.e, p = next.p;
|
||||
if (toRoute && n !== start && cells.road[n]) return [from, n];
|
||||
if (toRoute && n !== start && cells.road[n]) return [from, n, true];
|
||||
|
||||
for (const c of cells.c[n]) {
|
||||
if (c === exit) {from[c] = n; return [from, exit, true];}
|
||||
if (cells.h[c] >= 20) continue; // ignore land cells
|
||||
const dist2 = (cells.p[c][1] - cells.p[n][1]) ** 2 + (cells.p[c][0] - cells.p[n][0]) ** 2;
|
||||
const totalCost = p + (cells.road[c] ? 1 + dist2 / 2 : dist2 + (cells.t[c] ? 1 : 100));
|
||||
|
||||
if (from[c] || totalCost >= cost[c]) continue;
|
||||
from[c] = n;
|
||||
if (c === exit) return [from, exit];
|
||||
cost[c] = totalCost;
|
||||
from[c] = n, cost[c] = totalCost;
|
||||
queue.queue({e: c, p: totalCost});
|
||||
}
|
||||
|
||||
}
|
||||
return [from, exit];
|
||||
return [from, exit, false];
|
||||
}
|
||||
|
||||
})));
|
||||
|
|
@ -226,12 +226,12 @@ 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].join("|");
|
||||
const options = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value,
|
||||
const settings = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value,
|
||||
heightUnit.value, heightExponentInput.value, temperatureScale.value,
|
||||
barSize.value, barLabel.value, barBackOpacity.value, barBackColor.value,
|
||||
barPosX.value, barPosY.value, populationRate.value, urbanization.value,
|
||||
mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value,
|
||||
temperaturePoleOutput.value, precOutput.value, JSON.stringify(winds),
|
||||
temperaturePoleOutput.value, precOutput.value, JSON.stringify(options),
|
||||
mapName.value].join("|");
|
||||
const coords = JSON.stringify(mapCoordinates);
|
||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
|
||||
|
|
@ -242,7 +242,7 @@ function getMapData() {
|
|||
// set transform values to default
|
||||
cloneEl.setAttribute("width", graphWidth);
|
||||
cloneEl.setAttribute("height", graphHeight);
|
||||
cloneEl.querySelector("#viewbox").setAttribute("transform", null);
|
||||
cloneEl.querySelector("#viewbox").removeAttribute("transform");
|
||||
const svg_xml = (new XMLSerializer()).serializeToString(cloneEl);
|
||||
|
||||
const gridGeneral = JSON.stringify({spacing:grid.spacing, cellsX:grid.cellsX, cellsY:grid.cellsY, boundary:grid.boundary, points:grid.points, features:grid.features});
|
||||
|
|
@ -265,7 +265,7 @@ function getMapData() {
|
|||
const pop = Array.from(pack.cells.pop).map(p => rn(p, 4));
|
||||
|
||||
// data format as below
|
||||
const data = [params, options, coords, biomes, notesData, svg_xml,
|
||||
const data = [params, settings, coords, biomes, notesData, svg_xml,
|
||||
gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp,
|
||||
features, cultures, states, burgs,
|
||||
pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl,
|
||||
|
|
@ -505,6 +505,7 @@ function uploadMap(file, callback) {
|
|||
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = function(fileLoadedEvent) {
|
||||
if (callback) callback();
|
||||
const dataLoaded = fileLoadedEvent.target.result;
|
||||
const data = dataLoaded.split("\r\n");
|
||||
|
||||
|
|
@ -532,7 +533,6 @@ function uploadMap(file, callback) {
|
|||
};
|
||||
|
||||
fileReader.readAsText(file, "UTF-8");
|
||||
if (callback) callback();
|
||||
}
|
||||
|
||||
function parseLoadedData(data) {
|
||||
|
|
@ -554,29 +554,29 @@ function parseLoadedData(data) {
|
|||
|
||||
console.group("Loaded Map " + seed);
|
||||
|
||||
void function parseOptions() {
|
||||
const options = data[1].split("|");
|
||||
if (options[0]) applyOption(distanceUnitInput, options[0]);
|
||||
if (options[1]) distanceScaleInput.value = distanceScaleOutput.value = options[1];
|
||||
if (options[2]) areaUnit.value = options[2];
|
||||
if (options[3]) applyOption(heightUnit, options[3]);
|
||||
if (options[4]) heightExponentInput.value = heightExponentOutput.value = options[4];
|
||||
if (options[5]) temperatureScale.value = options[5];
|
||||
if (options[6]) barSize.value = barSizeOutput.value = options[6];
|
||||
if (options[7] !== undefined) barLabel.value = options[7];
|
||||
if (options[8] !== undefined) barBackOpacity.value = options[8];
|
||||
if (options[9]) barBackColor.value = options[9];
|
||||
if (options[10]) barPosX.value = options[10];
|
||||
if (options[11]) barPosY.value = options[11];
|
||||
if (options[12]) populationRate.value = populationRateOutput.value = options[12];
|
||||
if (options[13]) urbanization.value = urbanizationOutput.value = options[13];
|
||||
if (options[14]) mapSizeInput.value = mapSizeOutput.value = Math.max(Math.min(options[14], 100), 1);
|
||||
if (options[15]) latitudeInput.value = latitudeOutput.value = Math.max(Math.min(options[15], 100), 0);
|
||||
if (options[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = options[16];
|
||||
if (options[17]) temperaturePoleInput.value = temperaturePoleOutput.value = options[17];
|
||||
if (options[18]) precInput.value = precOutput.value = options[18];
|
||||
if (options[19]) winds = JSON.parse(options[19]);
|
||||
if (options[20]) mapName.value = options[20];
|
||||
void function parseSettings() {
|
||||
const settings = data[1].split("|");
|
||||
if (settings[0]) applyOption(distanceUnitInput, settings[0]);
|
||||
if (settings[1]) distanceScaleInput.value = distanceScaleOutput.value = settings[1];
|
||||
if (settings[2]) areaUnit.value = settings[2];
|
||||
if (settings[3]) applyOption(heightUnit, settings[3]);
|
||||
if (settings[4]) heightExponentInput.value = heightExponentOutput.value = settings[4];
|
||||
if (settings[5]) temperatureScale.value = settings[5];
|
||||
if (settings[6]) barSize.value = barSizeOutput.value = settings[6];
|
||||
if (settings[7] !== undefined) barLabel.value = settings[7];
|
||||
if (settings[8] !== undefined) barBackOpacity.value = settings[8];
|
||||
if (settings[9]) barBackColor.value = settings[9];
|
||||
if (settings[10]) barPosX.value = settings[10];
|
||||
if (settings[11]) barPosY.value = settings[11];
|
||||
if (settings[12]) populationRate.value = populationRateOutput.value = settings[12];
|
||||
if (settings[13]) urbanization.value = urbanizationOutput.value = settings[13];
|
||||
if (settings[14]) mapSizeInput.value = mapSizeOutput.value = Math.max(Math.min(settings[14], 100), 1);
|
||||
if (settings[15]) latitudeInput.value = latitudeOutput.value = Math.max(Math.min(settings[15], 100), 0);
|
||||
if (settings[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = settings[16];
|
||||
if (settings[17]) temperaturePoleInput.value = temperaturePoleOutput.value = settings[17];
|
||||
if (settings[18]) precInput.value = precOutput.value = settings[18];
|
||||
if (settings[19]) options = JSON.parse(settings[19]);
|
||||
if (settings[20]) mapName.value = settings[20];
|
||||
}()
|
||||
|
||||
void function parseConfiguration() {
|
||||
|
|
@ -645,6 +645,7 @@ function parseLoadedData(data) {
|
|||
icons = viewbox.select("#icons");
|
||||
burgIcons = icons.select("#burgIcons");
|
||||
anchors = icons.select("#anchors");
|
||||
armies = viewbox.select("#armies");
|
||||
markers = viewbox.select("#markers");
|
||||
ruler = viewbox.select("#ruler");
|
||||
fogging = viewbox.select("#fogging");
|
||||
|
|
@ -721,6 +722,7 @@ function parseLoadedData(data) {
|
|||
if (prec.selectAll("circle").size()) turnButtonOn("togglePrec"); else turnButtonOff("togglePrec");
|
||||
if (labels.style("display") !== "none") turnButtonOn("toggleLabels"); else turnButtonOff("toggleLabels");
|
||||
if (icons.style("display") !== "none") turnButtonOn("toggleIcons"); else turnButtonOff("toggleIcons");
|
||||
if (armies.selectAll("*").size() && armies.style("display") !== "none") turnButtonOn("toggleMilitary"); else turnButtonOff("toggleMilitary");
|
||||
if (markers.selectAll("*").size() && markers.style("display") !== "none") turnButtonOn("toggleMarkers"); else turnButtonOff("toggleMarkers");
|
||||
if (ruler.style("display") !== "none") turnButtonOn("toggleRulers"); else turnButtonOff("toggleRulers");
|
||||
if (scaleBar.style("display") !== "none") turnButtonOn("toggleScaleBar"); else turnButtonOff("toggleScaleBar");
|
||||
|
|
@ -774,6 +776,7 @@ function parseLoadedData(data) {
|
|||
// 1.0 adds state relations, provinces, forms and full names
|
||||
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", .6);
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.generateCampaigns();
|
||||
BurgsAndStates.generateDiplomacy();
|
||||
BurgsAndStates.defineStateForms();
|
||||
drawStates();
|
||||
|
|
@ -905,7 +908,6 @@ function parseLoadedData(data) {
|
|||
});
|
||||
|
||||
// v 1.21 added rivers data to pack
|
||||
|
||||
pack.rivers = []; // rivers data
|
||||
rivers.selectAll("path").each(function() {
|
||||
const i = +this.id.slice(5);
|
||||
|
|
@ -931,6 +933,82 @@ function parseLoadedData(data) {
|
|||
BurgsAndStates.collectStatistics();
|
||||
}
|
||||
|
||||
if (version < 1.3) {
|
||||
// v 1.3 added global options object
|
||||
const winds = options.slice(); // previostly wnd was saved in settings[19]
|
||||
const year = rand(100, 2000);
|
||||
const era = Names.getBaseShort(P(.7) ? 1 : rand(nameBases.length)) + " Era";
|
||||
const eraShort = era[0] + "E";
|
||||
const military = Military.getDefaultOptions();
|
||||
options = {winds, year, era, eraShort, military};
|
||||
|
||||
// v 1.3 added campaings data for all states
|
||||
BurgsAndStates.generateCampaigns();
|
||||
|
||||
// v 1.3 added militry layer
|
||||
svg.select("defs").append("style").text(armiesStyle()); // add armies style
|
||||
armies = viewbox.insert("g", "#icons").attr("id", "armies");
|
||||
armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", .3);
|
||||
turnButtonOn("toggleMilitary");
|
||||
Military.generate();
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
void function checkDataIntegrity() {
|
||||
const cells = pack.cells;
|
||||
|
||||
const invalidStates = [...new Set(cells.state)].filter(s => !pack.states[s] || pack.states[s].removed);
|
||||
invalidStates.forEach(s => {
|
||||
const invalidCells = cells.i.filter(i => cells.state[i] === s);
|
||||
invalidCells.forEach(i => cells.state[i] = 0);
|
||||
console.error("Data Integrity Check. Invalid state", s, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidProvinces = [...new Set(cells.province)].filter(p => p && (!pack.provinces[p] || pack.provinces[p].removed));
|
||||
invalidProvinces.forEach(p => {
|
||||
const invalidCells = cells.i.filter(i => cells.province[i] === p);
|
||||
invalidCells.forEach(i => cells.province[i] = 0);
|
||||
console.error("Data Integrity Check. Invalid province", p, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidCultures = [...new Set(cells.culture)].filter(c => !pack.cultures[c] || pack.cultures[c].removed);
|
||||
invalidCultures.forEach(c => {
|
||||
const invalidCells = cells.i.filter(i => cells.culture[i] === c);
|
||||
invalidCells.forEach(i => cells.province[i] = 0);
|
||||
console.error("Data Integrity Check. Invalid culture", c, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidReligions = [...new Set(cells.religion)].filter(r => !pack.religions[r] || pack.religions[r].removed);
|
||||
invalidReligions.forEach(r => {
|
||||
const invalidCells = cells.i.filter(i => cells.religion[i] === r);
|
||||
invalidCells.forEach(i => cells.religion[i] = 0);
|
||||
console.error("Data Integrity Check. Invalid religion", c, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidFeatures = [...new Set(cells.f)].filter(f => f && !pack.features[f]);
|
||||
invalidFeatures.forEach(f => {
|
||||
const invalidCells = cells.i.filter(i => cells.f[i] === f);
|
||||
// No fix as for now
|
||||
console.error("Data Integrity Check. Invalid feature", f, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
const invalidBurgs = [...new Set(cells.burg)].filter(b => b && (!pack.burgs[b] || pack.burgs[b].removed));
|
||||
invalidBurgs.forEach(b => {
|
||||
const invalidCells = cells.i.filter(i => cells.burg[i] === b);
|
||||
invalidCells.forEach(i => cells.burg[i] = 0);
|
||||
console.error("Data Integrity Check. Invalid burg", b, "is assigned to cells", invalidCells);
|
||||
});
|
||||
|
||||
pack.burgs.forEach(b => {
|
||||
if (!b.i || b.removed) return;
|
||||
if (b.port < 0) {console.error("Data Integrity Check. Burg", b.i, "has invalid port value", b.port); b.port = 0;}
|
||||
if (b.cell < cells.i.length) return;
|
||||
console.error("Data Integrity Check. Burg", b.i, "is linked to invalid cell", b.cell);
|
||||
b.cell = findCell(b.x, b.y);
|
||||
cells.i.filter(i => cells.burg[i] === b.i).forEach(i => cells.burg[i] = 0);
|
||||
cells.burg[b.cell] = b.i;
|
||||
});
|
||||
}()
|
||||
|
||||
changeMapSize();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
// set default options
|
||||
const options = {scale: 50, lightness: .7, shadow: .5, sun: {x: 100, y: 600, z: 1000}, rotateMesh: 0, rotateGlobe: .5,
|
||||
skyColor: "#9ecef5", waterColor: "#53679f", extendedWater: 0, resolution: 2};
|
||||
skyColor: "#9ecef5", waterColor: "#466eab", extendedWater: 0, resolution: 2};
|
||||
|
||||
// set variables
|
||||
let Renderer, scene, camera, controls, animationFrame, material, texture,
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ function editBiomes() {
|
|||
<div data-tip="Biome area" class="biomeArea hide">${si(area) + unit}</div>
|
||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="biomePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Open Wikipedia articale about the biome" class="icon-info-circled pointer hide"></span>
|
||||
<span data-tip="Open Wikipedia article about the biome" class="icon-info-circled pointer hide"></span>
|
||||
${i>12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ''}
|
||||
</div>`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ function overviewBurgs() {
|
|||
|
||||
function getCultureOptions(culture) {
|
||||
let options = "";
|
||||
pack.cultures.forEach(c => options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`);
|
||||
pack.cultures.filter(c => !c.removed).forEach(c => options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`);
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +303,7 @@ function overviewBurgs() {
|
|||
burgHighlightOff(ev);
|
||||
if (!document.getElementById("burgsInfo")) return;
|
||||
burgsInfo.innerHTML = "‍";
|
||||
d3.select(ev.target).transition().attr("stroke", "null");
|
||||
d3.select(ev.target).transition().attr("stroke", null);
|
||||
tip("");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -298,18 +298,27 @@ function editCultures() {
|
|||
function cultureRemove() {
|
||||
if (customization === 4) return;
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
cults.select("#culture"+culture).remove();
|
||||
debug.select("#cultureCenter"+culture).remove();
|
||||
|
||||
pack.burgs.filter(b => b.culture == culture).forEach(b => b.culture = 0);
|
||||
pack.states.forEach((s, i) => {if(s.culture === culture) s.culture = 0;});
|
||||
pack.cells.culture.forEach((c, i) => {if(c === culture) pack.cells.culture[i] = 0;});
|
||||
pack.cultures[culture].removed = true;
|
||||
|
||||
const origin = pack.cultures[culture].origin;
|
||||
pack.cultures.forEach(c => {if(c.origin === culture) c.origin = origin;});
|
||||
|
||||
refreshCulturesEditor();
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the culture? <br>This action cannot be reverted";
|
||||
$("#alert").dialog({resizable: false, title: "Remove culture",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
cults.select("#culture"+culture).remove();
|
||||
debug.select("#cultureCenter"+culture).remove();
|
||||
|
||||
pack.burgs.filter(b => b.culture == culture).forEach(b => b.culture = 0);
|
||||
pack.states.forEach((s, i) => {if(s.culture === culture) s.culture = 0;});
|
||||
pack.cells.culture.forEach((c, i) => {if(c === culture) pack.cells.culture[i] = 0;});
|
||||
pack.cultures[culture].removed = true;
|
||||
|
||||
const origin = pack.cultures[culture].origin;
|
||||
pack.cultures.forEach(c => {if(c.origin === culture) c.origin = origin;});
|
||||
refreshCulturesEditor();
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function drawCultureCenters() {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ function editDiplomacy() {
|
|||
const tipChange = `${tip}. Click to change relations to ${selName}`;
|
||||
|
||||
lines += `<div class="states" data-id=${s.i} data-name="${s.fullName}" data-relations="${relation}">
|
||||
<span data-tip="${tipSelect}" class="icon-right-open"></span>
|
||||
<div data-tip="${tipSelect}" style="width:12em">${s.fullName}</div>
|
||||
<svg data-tip="${tipChange}" width=".9em" height=".9em" style="margin-bottom:-1px" class="changeRelations">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect pointer" style="pointer-events: none"></rect>
|
||||
|
|
@ -91,30 +92,20 @@ function editDiplomacy() {
|
|||
if (!layerIsOn("toggleStates")) return;
|
||||
const state = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
const path = regions.select("#state"+state).attr("d");
|
||||
debug.append("path").attr("class", "highlight").attr("d", path)
|
||||
const d = regions.select("#state"+state).attr("d");
|
||||
|
||||
const path = debug.append("path").attr("class", "highlight").attr("d", d)
|
||||
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)").call(transition);
|
||||
}
|
||||
.attr("filter", "url(#blur1)");
|
||||
|
||||
function transition(path) {
|
||||
const duration = (path.node().getTotalLength() + 5000) / 2;
|
||||
path.transition().duration(duration).attrTween("stroke-dasharray", tweenDash);
|
||||
}
|
||||
|
||||
function tweenDash() {
|
||||
const l = this.getTotalLength();
|
||||
const l = path.node().getTotalLength(), dur = (l + 5000) / 2;
|
||||
const i = d3.interpolateString("0," + l, l + "," + l);
|
||||
return t => i(t);
|
||||
}
|
||||
|
||||
function removePath(path) {
|
||||
path.transition().duration(1000).attr("opacity", 0).remove();
|
||||
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)});
|
||||
}
|
||||
|
||||
function stateHighlightOff() {
|
||||
debug.selectAll(".highlight").each(function(el) {
|
||||
d3.select(this).call(removePath);
|
||||
function stateHighlightOff(event) {
|
||||
debug.selectAll(".highlight").each(function() {
|
||||
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function restoreDefaultEvents() {
|
|||
function clicked() {
|
||||
const el = d3.event.target;
|
||||
if (!el || !el.parentElement || !el.parentElement.parentElement) return;
|
||||
const parent = el.parentElement, grand = parent.parentElement;
|
||||
const parent = el.parentElement, grand = parent.parentElement, great = grand.parentElement;
|
||||
const p = d3.mouse(this);
|
||||
const i = findCell(p[0], p[1]);
|
||||
|
||||
|
|
@ -27,8 +27,9 @@ function clicked() {
|
|||
else if (grand.id === "burgLabels") editBurg();
|
||||
else if (grand.id === "burgIcons") editBurg();
|
||||
else if (parent.id === "terrain") editReliefIcon();
|
||||
else if (parent.id === "markers") editMarker();
|
||||
else if (parent.id === "markers") editMarker();
|
||||
else if (grand.id === "coastline") editCoastline();
|
||||
else if (great.id === "armies") editRegiment();
|
||||
else if (pack.cells.t[i] === 1) {
|
||||
const node = document.getElementById("island_"+pack.cells.f[i]);
|
||||
editCoastline(node);
|
||||
|
|
@ -171,9 +172,10 @@ function removeBurg(id) {
|
|||
if (label) label.remove();
|
||||
if (icon) icon.remove();
|
||||
if (anchor) anchor.remove();
|
||||
pack.burgs[id].removed = true;
|
||||
const cell = pack.burgs[id].cell;
|
||||
pack.cells.burg[cell] = 0;
|
||||
|
||||
const cells = pack.cells, burg = pack.burgs[id];
|
||||
burg.removed = true;
|
||||
cells.burg[burg.cell] = 0;
|
||||
}
|
||||
|
||||
function toggleCapital(burg) {
|
||||
|
|
|
|||
|
|
@ -39,10 +39,14 @@ function clearMainTip() {
|
|||
tooltip.innerHTML = "";
|
||||
}
|
||||
|
||||
// show tip at the bottom of the screen, consider possible translation
|
||||
function showDataTip(e) {
|
||||
if (!e.target) return;
|
||||
if (e.target.dataset.tip) {tip(e.target.dataset.tip); return;};
|
||||
if (e.target.parentNode.dataset.tip) tip(e.target.parentNode.dataset.tip);
|
||||
let dataTip = e.target.dataset.tip;
|
||||
if (!dataTip && e.target.parentNode.dataset.tip) dataTip = e.target.parentNode.dataset.tip;
|
||||
if (!dataTip) return;
|
||||
//const tooltip = lang === "en" ? dataTip : translate(e.target.dataset.t || e.target.parentNode.dataset.t, dataTip);
|
||||
tip(dataTip);
|
||||
}
|
||||
|
||||
function moved() {
|
||||
|
|
@ -84,6 +88,7 @@ function showMapTooltip(point, e, i, g) {
|
|||
const land = pack.cells.h[i] >= 20;
|
||||
|
||||
// specific elements
|
||||
if (group === "armies") {tip(e.target.parentNode.dataset.name + ". Click to edit"); return;}
|
||||
if (group === "rivers") {tip(getRiverName(e.target.id) + "Click to edit"); return;}
|
||||
if (group === "routes") {tip("Click to edit the Route"); return;}
|
||||
if (group === "terrain") {tip("Click to edit the Relief Icon"); return;}
|
||||
|
|
@ -132,14 +137,15 @@ function updateCellInfo(point, i, g) {
|
|||
const cells = pack.cells;
|
||||
const x = infoX.innerHTML = rn(point[0]);
|
||||
const y = infoY.innerHTML = rn(point[1]);
|
||||
const f = cells.f[i];
|
||||
infoLat.innerHTML = toDMS(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT, "lat");
|
||||
infoLon.innerHTML = toDMS(mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT, "lon");
|
||||
|
||||
infoCell.innerHTML = i;
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
infoArea.innerHTML = cells.area[i] ? si(cells.area[i] * distanceScaleInput.value ** 2) + unit : "n/a";
|
||||
const h = pack.cells.h[i] < 20 ? grid.cells.h[pack.cells.g[i]] : pack.cells.h[i];
|
||||
infoHeight.innerHTML = getFriendlyHeight(point) + " (" + h + ")";
|
||||
infoEvelation.innerHTML = getElevation(pack.features[f], pack.cells.h[i]);
|
||||
infoDepth.innerHTML = getDepth(pack.features[f], pack.cells.h[i], point);
|
||||
infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]);
|
||||
infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : "n/a";
|
||||
infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : "no";
|
||||
|
|
@ -149,7 +155,6 @@ function updateCellInfo(point, i, g) {
|
|||
infoReligion.innerHTML = cells.religion[i] ? `${pack.religions[cells.religion[i]].name} (${cells.religion[i]})` : "no";
|
||||
infoPopulation.innerHTML = getFriendlyPopulation(i);
|
||||
infoBurg.innerHTML = cells.burg[i] ? pack.burgs[cells.burg[i]].name + " (" + cells.burg[i] + ")" : "no";
|
||||
const f = cells.f[i];
|
||||
infoFeature.innerHTML = f ? pack.features[f].group + " (" + f + ")" : "n/a";
|
||||
infoBiome.innerHTML = biomesData.name[cells.biome[i]];
|
||||
}
|
||||
|
|
@ -164,6 +169,26 @@ function toDMS(coord, c) {
|
|||
return degrees + "° " + minutes + "′ " + seconds + "″ " + cardinal;
|
||||
}
|
||||
|
||||
// get surface elevation
|
||||
function getElevation(f, h) {
|
||||
if (f.land) return getHeight(h) + " (" + h + ")"; // land: usual height
|
||||
if (f.border) return "0 " + heightUnit.value; // ocean: 0
|
||||
|
||||
// lake: lowest coast height - 1
|
||||
const lakeCells = Array.from(pack.cells.i.filter(i => pack.cells.f[i] === f.i));
|
||||
const heights = lakeCells.map(i => pack.cells.c[i].map(c => pack.cells.h[c])).flat().filter(h => h > 19);
|
||||
const elevation = (d3.min(heights)||20) - 1;
|
||||
return getHeight(elevation) + " (" + elevation + ")";
|
||||
}
|
||||
|
||||
// get water depth
|
||||
function getDepth(f, h, p) {
|
||||
if (f.land) return "0 " + heightUnit.value; // land: 0
|
||||
if (!f.border) return getHeight(h, "abs"); // lake: pack abs height
|
||||
const gridH = grid.cells.h[findGridCell(p[0], p[1])];
|
||||
return getHeight(gridH, "abs"); // ocean: grig height
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) height value from map data
|
||||
function getFriendlyHeight(p) {
|
||||
const packH = pack.cells.h[findCell(p[0], p[1])];
|
||||
|
|
@ -172,7 +197,7 @@ function getFriendlyHeight(p) {
|
|||
return getHeight(h);
|
||||
}
|
||||
|
||||
function getHeight(h) {
|
||||
function getHeight(h, abs) {
|
||||
const unit = heightUnit.value;
|
||||
let unitRatio = 3.281; // default calculations are in feet
|
||||
if (unit === "m") unitRatio = 1; // if meter
|
||||
|
|
@ -182,6 +207,7 @@ function getHeight(h) {
|
|||
if (h >= 20) height = Math.pow(h - 18, +heightExponentInput.value);
|
||||
else if (h < 20 && h > 0) height = (h - 20) / h * 50;
|
||||
|
||||
if (abs) height = Math.abs(height);
|
||||
return rn(height * unitRatio) + " " + unit;
|
||||
}
|
||||
|
||||
|
|
@ -216,7 +242,7 @@ document.querySelectorAll("[data-locked]").forEach(function(e) {
|
|||
else tip("Click to lock the option and always use the current value on new map generation");
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
|
||||
e.addEventListener("click", function(event) {
|
||||
const id = (this.id).slice(5);
|
||||
if (this.className === "icon-lock") unlock(id);
|
||||
|
|
@ -341,7 +367,7 @@ document.addEventListener("keyup", event => {
|
|||
else if (shift && key === 79) editNotes(); // Shift + "O" to edit Notes
|
||||
else if (shift && key === 84) overviewBurgs(); // Shift + "T" to open Burgs overview
|
||||
else if (shift && key === 86) overviewRivers(); // Shift + "V" to open Rivers overview
|
||||
//else if (shift && key === 77) overviewMilitary(); // Shift + "M" to open Military overview
|
||||
else if (shift && key === 77) overviewMilitary(); // Shift + "M" to open Military overview
|
||||
else if (shift && key === 69) viewCellDetails(); // Shift + "E" to open Cell Details
|
||||
|
||||
else if (shift && key === 49) toggleAddBurg(); // Shift + "1" to click to add Burg
|
||||
|
|
@ -377,7 +403,8 @@ document.addEventListener("keyup", event => {
|
|||
else if (key === 65) togglePrec(); // "A" to toggle Precipitation layer
|
||||
else if (key === 76) toggleLabels(); // "L" to toggle Labels layer
|
||||
else if (key === 73) toggleIcons(); // "I" to toggle Icons layer
|
||||
else if (key === 77) toggleMarkers(); // "M" to toggle Markers layer
|
||||
else if (key === 77) toggleMilitary(); // "M" to toggle Military layer
|
||||
else if (key === 75) toggleMarkers(); // "K" to toggle Markers layer
|
||||
else if (key === 187) toggleRulers(); // Equal (=) to toggle Rulers
|
||||
else if (key === 189) toggleScaleBar(); // Minus (-) to toggle Scale bar
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
function editHeightmap() {
|
||||
void function selectEditMode() {
|
||||
alertMessage.innerHTML = `<p>Heightmap is a core element on which all other data (rivers, burgs, states etc) is based.
|
||||
So the best edit approach is to <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion.</p>
|
||||
alertMessage.innerHTML = `<span>Heightmap is a core element on which all other data (rivers, burgs, states etc) is based.
|
||||
So the best edit approach is to <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion.</span>
|
||||
<p>You can also <i>keep</i> all the data, but you won't be able to change the coastline.</p>
|
||||
<p>If you need to change the coastline and keep the data, you may try the <i>risk</i> edit option.
|
||||
The data will be restored as much as possible, but the coastline change can cause unexpected fluctuations and errors.</p>
|
||||
|
|
@ -128,8 +128,7 @@ function editHeightmap() {
|
|||
|
||||
customization = 0;
|
||||
customizationMenu.style.display = "none";
|
||||
if (options.querySelector(".tab > button.active").id === "toolsTab")
|
||||
toolsContent.style.display = "block";
|
||||
if (document.getElementById("options").querySelector(".tab > button.active").id === "toolsTab") toolsContent.style.display = "block";
|
||||
layersPreset.disabled = false;
|
||||
exitCustomization.style.display = "none"; // hide finalize button
|
||||
restoreDefaultEvents();
|
||||
|
|
@ -195,6 +194,7 @@ function editHeightmap() {
|
|||
BurgsAndStates.drawStateLabels();
|
||||
|
||||
Rivers.specify();
|
||||
Military.generate();
|
||||
addMarkers();
|
||||
addZones();
|
||||
console.timeEnd("regenerateErasedData");
|
||||
|
|
@ -307,10 +307,11 @@ function editHeightmap() {
|
|||
|
||||
for (const i of pack.cells.i) {
|
||||
const g = pack.cells.g[i];
|
||||
if (pack.features[pack.cells.f[i]].group === "freshwater") pack.cells.h[i] = 19; // de-elevate lakes
|
||||
const land = pack.cells.h[i] >= 20;
|
||||
|
||||
// check biome
|
||||
if (land && !biome[g]) pack.cells.biome[i] = getBiomeId(grid.cells.prec[g], grid.cells.temp[g]);
|
||||
if (!biome[g]) pack.cells.biome[i] = getBiomeId(grid.cells.prec[g], grid.cells.temp[g]);
|
||||
else if (!land && biome[g]) pack.cells.biome[i] = 0;
|
||||
else pack.cells.biome[i] = biome[g];
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ function getDefaultPresets() {
|
|||
"heightmap": ["toggleHeight", "toggleRivers"],
|
||||
"physical": ["toggleCoordinates", "toggleHeight", "toggleRivers", "toggleScaleBar"],
|
||||
"poi": ["toggleBorders", "toggleHeight", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||
"military": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleMilitary", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"],
|
||||
"landmass": ["toggleScaleBar"]
|
||||
}
|
||||
}
|
||||
|
|
@ -334,9 +335,9 @@ function drawBiomes() {
|
|||
const cells = pack.cells, vertices = pack.vertices, n = cells.i.length;
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
const paths = new Array(biomesData.i.length).fill("");
|
||||
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (!cells.biome[i]) continue; // no need to mark water
|
||||
if (!cells.biome[i]) continue; // no need to mark marine biome (liquid water)
|
||||
if (used[i]) continue; // already marked
|
||||
const b = cells.biome[i];
|
||||
const onborder = cells.c[i].some(n => cells.biome[n] !== b);
|
||||
|
|
@ -399,7 +400,7 @@ function drawPrec() {
|
|||
const data = cells.i.filter(i => cells.h[i] >= 20 && cells.prec[i]);
|
||||
prec.selectAll("circle").data(data).enter().append("circle")
|
||||
.attr("cx", d => p[d][0]).attr("cy", d => p[d][1]).attr("r", 0)
|
||||
.transition(show).attr("r", d => rn(Math.max(Math.sqrt(cells.prec[d] * .5), .8),2));
|
||||
.transition(show).attr("r", d => rn(Math.max(Math.sqrt(cells.prec[d] * .5), .8),2));
|
||||
}
|
||||
|
||||
function togglePopulation(event) {
|
||||
|
|
@ -1058,6 +1059,18 @@ function toggleRoutes(event) {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleMilitary() {
|
||||
if (!layerIsOn("toggleMilitary")) {
|
||||
turnButtonOn("toggleMilitary");
|
||||
$('#armies').fadeIn();
|
||||
if (event && isCtrlClick(event)) editStyle("armies");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {editStyle("armies"); return;}
|
||||
$('#armies').fadeOut();
|
||||
turnButtonOff("toggleMilitary");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMarkers(event) {
|
||||
if (!layerIsOn("toggleMarkers")) {
|
||||
turnButtonOn("toggleMarkers");
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ function editMarker() {
|
|||
}
|
||||
|
||||
function drawIconsList() {
|
||||
let icons = [
|
||||
const icons = [
|
||||
// emoticons in FF:
|
||||
["2693", "⚓", "Anchor"],
|
||||
["26EA", "⛪", "Church"],
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ function overviewMilitary() {
|
|||
closeDialogs("#militaryOverview, .stable");
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders();
|
||||
if (!layerIsOn("toggleMilitary")) toggleMilitary();
|
||||
|
||||
const body = document.getElementById("militaryBody");
|
||||
militaryOverviewAddLines();
|
||||
addLines();
|
||||
$("#militaryOverview").dialog();
|
||||
|
||||
if (modules.overviewMilitary) return;
|
||||
modules.overviewMilitary = true;
|
||||
updateHeaders();
|
||||
|
||||
$("#militaryOverview").dialog({
|
||||
title: "Military Overview", resizable: false, width: fitContent(),
|
||||
|
|
@ -18,94 +20,249 @@ function overviewMilitary() {
|
|||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("militaryOverviewRefresh").addEventListener("click", militaryOverviewAddLines);
|
||||
document.getElementById("militaryOverviewRefresh").addEventListener("click", addLines);
|
||||
document.getElementById("militaryPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("militaryOptionsButton").addEventListener("click", militaryCustomize);
|
||||
document.getElementById("militaryRegimentsList").addEventListener("click", () => overviewRegiments(-1));
|
||||
document.getElementById("militaryOverviewRecalculate").addEventListener("click", militaryRecalculate);
|
||||
document.getElementById("militaryExport").addEventListener("click", downloadMilitaryData);
|
||||
|
||||
// add line for each river
|
||||
function militaryOverviewAddLines() {
|
||||
body.innerHTML = "";
|
||||
let lines = "", militaryTotal = 0;
|
||||
body.addEventListener("change", function(ev) {
|
||||
const el = ev.target, line = el.parentNode, state = +line.dataset.id;
|
||||
changeAlert(state, line, +el.value);
|
||||
});
|
||||
|
||||
body.addEventListener("click", function(ev) {
|
||||
const el = ev.target, line = el.parentNode, state = +line.dataset.id;
|
||||
if (el.tagName === "SPAN") overviewRegiments(state);
|
||||
});
|
||||
|
||||
// update military types in header and tooltips
|
||||
function updateHeaders() {
|
||||
const header = document.getElementById("militaryHeader");
|
||||
header.querySelectorAll(".removable").forEach(el => el.remove());
|
||||
const insert = html => document.getElementById("militaryTotal").insertAdjacentHTML("beforebegin", html);
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
insert(`<div data-tip="State ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`);
|
||||
}
|
||||
header.querySelectorAll(".removable").forEach(function(e) {
|
||||
e.addEventListener("click", function() {sortLines(this);});
|
||||
});
|
||||
}
|
||||
|
||||
// add line for each state
|
||||
function addLines() {
|
||||
body.innerHTML = "";
|
||||
let lines = "";
|
||||
const states = pack.states.filter(s => s.i && !s.removed);
|
||||
const popRate = +populationRate.value;
|
||||
|
||||
for (const s of states) {
|
||||
const total = (s.military.infantry + s.military.cavalry + s.military.archers + s.military.fleet / 10);
|
||||
const rate = total / (s.rural + s.urban * urbanization.value) * 100;
|
||||
militaryTotal += total;
|
||||
const population = rn((s.rural + s.urban * urbanization.value) * populationRate.value);
|
||||
const getForces = u => s.military.reduce((s, r) => s+(r.u[u.name]||0), 0);
|
||||
const total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0);
|
||||
const rate = total / population * 100;
|
||||
|
||||
lines += `<div class="states" data-id=${s.i} data-state="${s.name}" data-infantry="${s.military.infantry}"
|
||||
data-archers="${s.military.archers}" data-cavalry="${s.military.cavalry}" data-reserve="${s.military.reserve}"
|
||||
data-fleet="${s.military.fleet}" data-rate="${rate}" data-total="${total}">
|
||||
<svg data-tip="State color" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
|
||||
<input data-tip="State name" class="stateName" value="${s.name}" readonly>
|
||||
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
|
||||
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" ");
|
||||
|
||||
<input data-tip="State infantry number" type="number" class="militaryArmy" min=0 step=1 value="${rn(s.military.infantry * popRate)}">
|
||||
<input data-tip="State archers number" type="number" class="militaryArmy" min=0 step=1 value="${rn(s.military.archers * popRate)}">
|
||||
<input data-tip="State cavalry number" type="number" class="militaryArmy" min=0 step=1 value="${rn(s.military.cavalry * popRate)}">
|
||||
<input data-tip="Number of ships in state navy" class="militaryFleet" type="number" min=0 step=1 value="${s.military.fleet}">
|
||||
|
||||
<div data-tip="Total military personnel (including ships crew)">${si(total * popRate)}</div>
|
||||
<div data-tip="Armed forces personnel (% of state population). Depends on diplomatic situation">${rn(rate, 2)}%</div>
|
||||
<div data-tip="State manpower (reserve)">${si(s.military.reserve * popRate)}</div>
|
||||
lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
|
||||
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
|
||||
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
|
||||
${lineData}
|
||||
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>
|
||||
<div data-type="population" data-tip="State population">${si(population)}</div>
|
||||
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div>
|
||||
<input data-tip="War Alert. Editable modifier to military forces number, depends of political situation" style="width:4.1em" type="number" min=0 step=.01 value="${rn(s.alert, 2)}">
|
||||
<span data-tip="Show regiments list" class="icon-list-bullet pointer"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.insertAdjacentHTML("beforeend", lines);
|
||||
|
||||
// update footer
|
||||
militaryFooterStates.innerHTML = states.length;
|
||||
militaryFooterAverage.innerHTML = si(militaryTotal / states.length * popRate);
|
||||
updateFooter();
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev)));
|
||||
|
||||
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
|
||||
applySorting(militaryHeader);
|
||||
}
|
||||
|
||||
function changeAlert(state, line, alert) {
|
||||
const s = pack.states[state];
|
||||
const dif = s.alert || alert ? alert / s.alert : 0; // modifier
|
||||
s.alert = line.dataset.alert = alert;
|
||||
|
||||
s.military.forEach(r => {
|
||||
Object.keys(r.u).forEach(u => r.u[u] = rn(r.u[u] * dif)); // change units value
|
||||
r.a = d3.sum(Object.values(r.u)); // change total
|
||||
armies.select(`g>g#regiment${s.i}-${r.i}>text`).text(Military.getTotal(r)); // change icon text
|
||||
});
|
||||
|
||||
const getForces = u => s.military.reduce((s, r) => s+(r.u[u.name]||0), 0);
|
||||
options.military.forEach(u => line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u));
|
||||
|
||||
const population = rn((s.rural + s.urban * urbanization.value) * populationRate.value);
|
||||
const total = line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0);
|
||||
const rate = line.dataset.rate = total / population * 100;
|
||||
line.querySelector("div[data-type='total']").innerHTML = si(total);
|
||||
line.querySelector("div[data-type='rate']").innerHTML = rn(rate, 2) + "%";
|
||||
|
||||
updateFooter();
|
||||
}
|
||||
|
||||
function updateFooter() {
|
||||
const lines = Array.from(body.querySelectorAll(":scope > div"));
|
||||
const statesNumber = militaryFooterStates.innerHTML = pack.states.filter(s => s.i && !s.removed).length;
|
||||
const total = d3.sum(lines.map(el => el.dataset.total));
|
||||
militaryFooterForcesTotal.innerHTML = si(total);
|
||||
militaryFooterForces.innerHTML = si(total / statesNumber);
|
||||
militaryFooterRate.innerHTML = rn(d3.sum(lines.map(el => el.dataset.rate)) / statesNumber, 2) + "%";
|
||||
militaryFooterAlert.innerHTML = rn(d3.sum(lines.map(el => el.dataset.alert)) / statesNumber, 2);
|
||||
}
|
||||
|
||||
function stateHighlightOn(event) {
|
||||
if (!layerIsOn("toggleStates")) return;
|
||||
const state = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
const path = regions.select("#state"+state).attr("d");
|
||||
debug.append("path").attr("class", "highlight").attr("d", path)
|
||||
armies.select("#army"+state).transition().duration(2000).style("fill", "#ff0000");
|
||||
|
||||
if (!layerIsOn("toggleStates")) return;
|
||||
const d = regions.select("#state"+state).attr("d");
|
||||
|
||||
const path = debug.append("path").attr("class", "highlight").attr("d", d)
|
||||
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)").call(transition);
|
||||
}
|
||||
.attr("filter", "url(#blur1)");
|
||||
|
||||
function transition(path) {
|
||||
const duration = (path.node().getTotalLength() + 5000) / 2;
|
||||
path.transition().duration(duration).attrTween("stroke-dasharray", tweenDash);
|
||||
}
|
||||
|
||||
function tweenDash() {
|
||||
const l = this.getTotalLength();
|
||||
const l = path.node().getTotalLength(), dur = (l + 5000) / 2;
|
||||
const i = d3.interpolateString("0," + l, l + "," + l);
|
||||
return t => i(t);
|
||||
}
|
||||
|
||||
function removePath(path) {
|
||||
path.transition().duration(1000).attr("opacity", 0).remove();
|
||||
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)});
|
||||
}
|
||||
|
||||
function stateHighlightOff() {
|
||||
debug.selectAll(".highlight").each(function(el) {
|
||||
d3.select(this).call(removePath);
|
||||
function stateHighlightOff(event) {
|
||||
debug.selectAll(".highlight").each(function() {
|
||||
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
|
||||
});
|
||||
|
||||
const state = +event.target.dataset.id;
|
||||
armies.select("#army"+state).transition().duration(1000).style("fill", null);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === "absolute") {
|
||||
body.dataset.type = "percentage";
|
||||
const lines = body.querySelectorAll(":scope > div");
|
||||
const array = Array.from(lines), cache = [];
|
||||
|
||||
const total = function(type) {
|
||||
if (cache[type]) cache[type];
|
||||
cache[type] = d3.sum(array.map(el => +el.dataset[type]));
|
||||
return cache[type];
|
||||
}
|
||||
|
||||
lines.forEach(function(el) {
|
||||
el.querySelectorAll("div").forEach(function(div) {
|
||||
const type = div.dataset.type;
|
||||
if (type === "rate") return;
|
||||
div.textContent = total(type) ? rn(+el.dataset[type] / total(type) * 100) + "%" : "0%";
|
||||
});
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
addLines();
|
||||
}
|
||||
}
|
||||
|
||||
function militaryCustomize() {
|
||||
const types = ["default", "melee", "ranged", "mounted", "machinery", "naval"];
|
||||
const table = document.getElementById("militaryOptions").querySelector("tbody");
|
||||
removeUnitLines();
|
||||
options.military.map(u => addUnitLine(u));
|
||||
|
||||
$("#militaryOptions").dialog({
|
||||
title: "Edit Military Units", resizable: false, width: fitContent(),
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Apply: function() {applyMilitaryOptions(); $(this).dialog("close");},
|
||||
Add: () => addUnitLine({name: "custom"+rand(1000), rural: .2, urban: .5, crew: 1, type: "default"}),
|
||||
Restore: restoreDefaultUnits,
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}, open: function() {
|
||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
|
||||
buttons[0].addEventListener("mousemove", () => tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>"));
|
||||
buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table"));
|
||||
buttons[2].addEventListener("mousemove", () => tip("Restore default military units and settings"));
|
||||
buttons[3].addEventListener("mousemove", () => tip("Close the window without saving the changes"));
|
||||
}
|
||||
});
|
||||
|
||||
function removeUnitLines() {
|
||||
table.querySelectorAll("tr").forEach(el => el.remove());
|
||||
}
|
||||
|
||||
function addUnitLine(u) {
|
||||
const row = `<tr>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${u.name}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${u.rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${u.urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew" type="number" min=1 step=1 value="${u.crew}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${types.map(t => `<option ${u.type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ")}</select></td>
|
||||
<td data-tip="Check if unit is separate and can be stacked only with units of the same type">
|
||||
<input id="${u.name}Separate" type="checkbox" class="checkbox" checked=${u.separate}>
|
||||
<label for="${u.name}Separate" class="checkbox-label"></label>
|
||||
</td>
|
||||
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>
|
||||
</tr>`;
|
||||
table.insertAdjacentHTML("beforeend", row);
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
removeUnitLines();
|
||||
Military.getDefaultOptions().map(u => addUnitLine(u));
|
||||
}
|
||||
|
||||
function applyMilitaryOptions() {
|
||||
options.military = Array.from(table.querySelectorAll("tr")).map(r => {
|
||||
const [name, rural, urban, crew, type, separate] = Array.from(r.querySelectorAll("input, select")).map(d => d.value||d.checked);
|
||||
return {name:name.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, '_'), rural:+rural||0, urban:+urban||0, crew:+crew||0, type, separate:+separate||0};
|
||||
});
|
||||
localStorage.setItem("military", JSON.stringify(options.military));
|
||||
Military.generate();
|
||||
updateHeaders();
|
||||
addLines();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function militaryRecalculate() {
|
||||
alertMessage.innerHTML = "Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated";
|
||||
$("#alert").dialog({resizable: false, title: "Remove regiment",
|
||||
buttons: {
|
||||
Recalculate: function() {
|
||||
$(this).dialog("close");
|
||||
Military.generate();
|
||||
addLines();
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function downloadMilitaryData() {
|
||||
let data = "Id,River,Type,Length,Basin\n"; // headers
|
||||
const units = options.military.map(u => u.name);
|
||||
let data = "Id,State,"+units.map(u => capitalize(u)).join(",")+",Total,Population,Rate,War Alert\n"; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function(el) {
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
data += el.dataset.type + ",";
|
||||
data += el.querySelector(".biomeArea").innerHTML + ",";
|
||||
data += el.dataset.basin + "\n";
|
||||
data += el.dataset.state + ",";
|
||||
data += units.map(u => el.dataset[u]).join(",") + ",";
|
||||
data += el.dataset.total + ",";
|
||||
data += el.dataset.population + ",";
|
||||
data += rn(el.dataset.rate,2) + "%,";
|
||||
data += el.dataset.alert + "\n";
|
||||
});
|
||||
|
||||
const name = getFileName("Military") + ".csv";
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ function showOptions(event) {
|
|||
}
|
||||
|
||||
regenerate.style.display = "none";
|
||||
options.style.display = "block";
|
||||
document.getElementById("options").style.display = "block";
|
||||
optionsTrigger.style.display = "none";
|
||||
|
||||
if (event) event.stopPropagation();
|
||||
|
|
@ -28,21 +28,21 @@ function showOptions(event) {
|
|||
|
||||
// Hide options pane on trigger click
|
||||
function hideOptions(event) {
|
||||
options.style.display = "none";
|
||||
document.getElementById("options").style.display = "none";
|
||||
optionsTrigger.style.display = "block";
|
||||
if (event) event.stopPropagation();
|
||||
}
|
||||
|
||||
// To toggle options on hotkey press
|
||||
function toggleOptions(event) {
|
||||
if (options.style.display === "none") showOptions(event);
|
||||
if (document.getElementById("options").style.display === "none") showOptions(event);
|
||||
else hideOptions(event);
|
||||
}
|
||||
|
||||
// Toggle "New Map!" pane on hover
|
||||
optionsTrigger.addEventListener("mouseenter", function() {
|
||||
if (optionsTrigger.classList.contains("glow")) return;
|
||||
if (options.style.display === "none") regenerate.style.display = "block";
|
||||
if (document.getElementById("options").style.display === "none") regenerate.style.display = "block";
|
||||
});
|
||||
|
||||
collapsible.addEventListener("mouseleave", function() {
|
||||
|
|
@ -50,15 +50,15 @@ collapsible.addEventListener("mouseleave", function() {
|
|||
});
|
||||
|
||||
// Activate options tab on click
|
||||
options.querySelector("div.tab").addEventListener("click", function(event) {
|
||||
document.getElementById("options").querySelector("div.tab").addEventListener("click", function(event) {
|
||||
if (event.target.tagName !== "BUTTON") return;
|
||||
const id = event.target.id;
|
||||
const active = options.querySelector(".tab > button.active");
|
||||
const active = document.getElementById("options").querySelector(".tab > button.active");
|
||||
if (active && id === active.id) return; // already active tab is clicked
|
||||
|
||||
if (active) active.classList.remove("active");
|
||||
document.getElementById(id).classList.add("active");
|
||||
options.querySelectorAll(".tabcontent").forEach(e => e.style.display = "none");
|
||||
document.getElementById("options").querySelectorAll(".tabcontent").forEach(e => e.style.display = "none");
|
||||
|
||||
if (id === "layersTab") layersContent.style.display = "block"; else
|
||||
if (id === "styleTab") styleContent.style.display = "block"; else
|
||||
|
|
@ -69,7 +69,7 @@ options.querySelector("div.tab").addEventListener("click", function(event) {
|
|||
if (id === "aboutTab") aboutContent.style.display = "block";
|
||||
});
|
||||
|
||||
options.querySelectorAll("i.collapsible").forEach(el => el.addEventListener("click", collapse));
|
||||
document.getElementById("options").querySelectorAll("i.collapsible").forEach(el => el.addEventListener("click", collapse));
|
||||
function collapse(e) {
|
||||
const trigger = e.target;
|
||||
const section = trigger.parentElement.nextElementSibling;
|
||||
|
|
@ -309,7 +309,8 @@ function applyStoredOptions() {
|
|||
if(stored.slice(0,5) === "style") applyOption(stylePreset, stored, stored.slice(5));
|
||||
}
|
||||
|
||||
if (localStorage.getItem("winds")) winds = localStorage.getItem("winds").split(",").map(w => +w);
|
||||
if (localStorage.getItem("winds")) options.winds = localStorage.getItem("winds").split(",").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"));
|
||||
|
|
@ -335,10 +336,10 @@ function randomizeOptions() {
|
|||
// 'Options' settings
|
||||
if (randomize || !locked("template")) randomizeHeightmapTemplate();
|
||||
if (randomize || !locked("regions")) regionsInput.value = regionsOutput.value = gauss(15, 3, 2, 30);
|
||||
if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(40, 20, 20, 100);
|
||||
if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(20, 10, 20, 100);
|
||||
if (randomize || !locked("manors")) {manorsInput.value = 1000; manorsOutput.value = "auto";}
|
||||
if (randomize || !locked("religions")) religionsInput.value = religionsOutput.value = gauss(5, 2, 2, 10);
|
||||
if (randomize || !locked("power")) powerInput.value = powerOutput.value = gauss(3, 2, 0, 10);
|
||||
if (randomize || !locked("power")) powerInput.value = powerOutput.value = gauss(4, 2, 0, 10, 2);
|
||||
if (randomize || !locked("neutral")) neutralInput.value = neutralOutput.value = rn(1 + Math.random(), 1);
|
||||
if (randomize || !locked("cultures")) culturesInput.value = culturesOutput.value = gauss(12, 3, 5, 30);
|
||||
if (randomize || !locked("culturesSet")) randomizeCultureSet();
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ function editProvinces() {
|
|||
const el = ev.target, cl = el.classList, line = el.parentNode, p = +line.dataset.id;
|
||||
if (cl.contains("fillRect")) changeFill(el); else
|
||||
if (cl.contains("name")) editProvinceName(p); else
|
||||
if (cl.contains("icon-fleur")) provinceOpenCOA(ev, p); else
|
||||
if (cl.contains("icon-coa")) provinceOpenCOA(ev, p); else
|
||||
if (cl.contains("icon-star-empty")) capitalZoomIn(p); else
|
||||
if (cl.contains("icon-flag-empty")) declareProvinceIndependence(p); else
|
||||
if (cl.contains("icon-flag-empty")) triggerIndependencePromps(p); else
|
||||
if (cl.contains("culturePopulation")) changePopulation(p); else
|
||||
if (cl.contains("icon-pin")) focusOn(p, cl); else
|
||||
if (cl.contains("icon-trash-empty")) removeProvince(p);
|
||||
|
|
@ -116,7 +116,7 @@ function editProvinces() {
|
|||
lines += `<div class="states" data-id=${p.i} data-name=${p.name} data-form=${p.formName} data-color="${p.color}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}>
|
||||
<svg data-tip="Province fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${p.color}" class="fillRect pointer"></svg>
|
||||
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly>
|
||||
<span data-tip="Click to open province COA in the Iron Arachne Heraldry Generator. Ctrl + click to change the seed" class="icon-fleur pointer hide"></span>
|
||||
<span data-tip="Click to open province COA in the Iron Arachne Heraldry Generator. Ctrl + click to change the seed" class="icon-coa pointer hide"></span>
|
||||
<input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly>
|
||||
<span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${p.burg?'':'placeholder'}"></span>
|
||||
<select data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital" class="cultureBase hide ${p.burgs.length?'':'placeholder'}">${p.burgs.length ? getCapitalOptions(p.burgs, p.burg) : ''}</select>
|
||||
|
|
@ -211,6 +211,19 @@ function editProvinces() {
|
|||
zoomTo(x, y, 8, 2000);
|
||||
}
|
||||
|
||||
function triggerIndependencePromps(p) {
|
||||
alertMessage.innerHTML = "Are you sure you want to declare province independence? <br>It will turn province into a new state";
|
||||
$("#alert").dialog({resizable: false, title: "Declare independence",
|
||||
buttons: {
|
||||
Declare: function() {
|
||||
declareProvinceIndependence(p);
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function declareProvinceIndependence(p) {
|
||||
const states = pack.states, provinces = pack.provinces, cells = pack.cells;
|
||||
const oldState = pack.provinces[p].state;
|
||||
|
|
@ -352,17 +365,29 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function removeProvince(p) {
|
||||
pack.cells.province.forEach((province, i) => {if(province === p) pack.cells.province[i] = 0;});
|
||||
const state = pack.provinces[p].state;
|
||||
if (pack.states[state].provinces.includes(p)) pack.states[state].provinces.splice(pack.states[state].provinces.indexOf(p), 1);
|
||||
pack.provinces[p].removed = true;
|
||||
unfocus(p);
|
||||
|
||||
const g = provs.select("#provincesBody");
|
||||
g.select("#province"+p).remove();
|
||||
g.select("#province-gap"+p).remove();
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders();
|
||||
refreshProvincesEditor();
|
||||
alertMessage.innerHTML = `Are you sure you want to remove the province? <br>This action cannot be reverted`;
|
||||
$("#alert").dialog({resizable: false, title: "Remove province",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
pack.cells.province.forEach((province, i) => {if(province === p) pack.cells.province[i] = 0;});
|
||||
const state = pack.provinces[p].state;
|
||||
if (pack.states[state].provinces.includes(p)) pack.states[state].provinces.splice(pack.states[state].provinces.indexOf(p), 1);
|
||||
pack.provinces[p].removed = true;
|
||||
unfocus(p);
|
||||
|
||||
const g = provs.select("#provincesBody");
|
||||
g.select("#province"+p).remove();
|
||||
g.select("#province-gap"+p).remove();
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders();
|
||||
refreshProvincesEditor();
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
function editProvinceName(province) {
|
||||
|
|
@ -804,7 +829,7 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function removeAllProvinces() {
|
||||
alertMessage.innerHTML = `Are you sure you want to remove all provinces?`;
|
||||
alertMessage.innerHTML = `Are you sure you want to remove all provinces? <br>This action cannot be reverted`;
|
||||
$("#alert").dialog({resizable: false, title: "Remove all provinces",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
|
|
|
|||
335
modules/ui/regiment-editor.js
Normal file
335
modules/ui/regiment-editor.js
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
"use strict";
|
||||
function editRegiment(selector) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
if (!layerIsOn("toggleMilitary")) toggleMilitary();
|
||||
|
||||
armies.selectAll(":scope > g").classed("draggable", true);
|
||||
armies.selectAll(":scope > g > g").call(d3.drag().on("drag", dragRegiment));
|
||||
elSelected = selector ? document.querySelector(selector) : d3.event.target.parentElement; // select g element
|
||||
if (!pack.states[elSelected.dataset.state]) return;
|
||||
if (!regiment()) return;
|
||||
updateRegimentData(regiment());
|
||||
drawBase();
|
||||
|
||||
$("#regimentEditor").dialog({
|
||||
title: "Edit Regiment", resizable: false, close: closeEditor,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "#map"},
|
||||
close: closeEditor
|
||||
});
|
||||
|
||||
if (modules.editRegiment) return;
|
||||
modules.editRegiment = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
|
||||
document.getElementById("regimentType").addEventListener("click", changeType);
|
||||
document.getElementById("regimentName").addEventListener("change", changeName);
|
||||
document.getElementById("regimentEmblem").addEventListener("input", changeEmblem);
|
||||
document.getElementById("regimentEmblemSelect").addEventListener("click", selectEmblem);
|
||||
document.getElementById("regimentRegenerateLegend").addEventListener("click", regenerateLegend);
|
||||
document.getElementById("regimentLegend").addEventListener("click", editLegend);
|
||||
document.getElementById("regimentSplit").addEventListener("click", splitRegiment);
|
||||
document.getElementById("regimentAdd").addEventListener("click", toggleAdd);
|
||||
document.getElementById("regimentAttach").addEventListener("click", toggleAttach);
|
||||
document.getElementById("regimentRemove").addEventListener("click", removeRegiment);
|
||||
|
||||
// get regiment data element
|
||||
function regiment() {
|
||||
return pack.states[elSelected.dataset.state].military.find(r => r.i == elSelected.dataset.id);
|
||||
}
|
||||
|
||||
function updateRegimentData(regiment) {
|
||||
document.getElementById("regimentType").className = regiment.n ? "icon-anchor" :"icon-users";
|
||||
document.getElementById("regimentName").value = regiment.name;
|
||||
document.getElementById("regimentEmblem").value = regiment.icon;
|
||||
const composition = document.getElementById("regimentComposition");
|
||||
|
||||
composition.innerHTML = options.military.map(u => {
|
||||
return `<div data-tip="${capitalize(u.name)} number. Input to change">
|
||||
<div class="label">${capitalize(u.name)}:</div>
|
||||
<input data-u="${u.name}" type="number" min=0 step=1 value="${(regiment.u[u.name]||0)}">
|
||||
<i>${u.type}</i></div>`
|
||||
}).join("");
|
||||
|
||||
composition.querySelectorAll("input").forEach(el => el.addEventListener("change", changeUnit));
|
||||
}
|
||||
|
||||
function drawBase() {
|
||||
const reg = regiment();
|
||||
const clr = pack.states[elSelected.dataset.state].color;
|
||||
const base = viewbox.insert("g", "g#armies").attr("id", "regimentBase").attr("stroke-width", .3).attr("stroke", "#000").attr("cursor", "move");
|
||||
base.on("mouseenter", () => {tip("Regiment base. Drag to re-base the regiment", true);}).on("mouseleave", () => {tip('', true);});
|
||||
|
||||
base.append("line").attr("x1", reg.bx).attr("y1", reg.by).attr("x2", reg.x).attr("y2", reg.y).attr("class", "dragLine");
|
||||
base.append("circle").attr("cx", reg.bx).attr("cy", reg.by).attr("r", 2).attr("fill", clr).call(d3.drag().on("drag", dragBase));
|
||||
}
|
||||
|
||||
function changeType() {
|
||||
const reg = regiment();
|
||||
reg.n = +!reg.n;
|
||||
document.getElementById("regimentType").className = reg.n ? "icon-anchor" :"icon-users";
|
||||
|
||||
const size = +armies.attr("box-size");
|
||||
const baseRect = elSelected.querySelectorAll("rect")[0];
|
||||
const iconRect = elSelected.querySelectorAll("rect")[1];
|
||||
const icon = elSelected.querySelector(".regimentIcon");
|
||||
const x = reg.n ? reg.x-size*2 : reg.x-size*3;
|
||||
baseRect.setAttribute("x", x);
|
||||
baseRect.setAttribute("width", reg.n ? size*4 : size*6);
|
||||
iconRect.setAttribute("x", x - size*2);
|
||||
icon.setAttribute("x", x - size);
|
||||
elSelected.querySelector("text").innerHTML = Military.getTotal(reg);
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
elSelected.dataset.name = regiment().name = this.value;
|
||||
}
|
||||
|
||||
function restoreName() {
|
||||
const reg = regiment(), regs = pack.states[elSelected.dataset.state].military;
|
||||
const name = Military.getName(reg, regs);
|
||||
elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name;
|
||||
}
|
||||
|
||||
function changeEmblem() {
|
||||
const emblem = document.getElementById("regimentEmblem").value;
|
||||
regiment().icon = elSelected.querySelector(".regimentIcon").innerHTML = emblem;
|
||||
}
|
||||
|
||||
function selectEmblem() {
|
||||
const emblems = ["⚔️","🏹","🐴","💣","🌊","🎯","⚓","🔮","📯","🛡️","👑",
|
||||
"☠️","🎆","🗡️","⛏️","🔥","🐾","🎪","🏰","⚜️","⛓️","❤️","📜","🔱","🌈","🌠","💥","☀️","🍀",
|
||||
"🔰","🕸️","⚗️","☣️","☢️","🎖️","⚕️","☸️","✡️","🚩","🏳️","🏴","🌈","💪","✊","👊","🤜","🤝","🙏","🧙","💂","🤴","🧛","🧟","🧞","🧝",
|
||||
"🦄","🐲","🐉","🐎","🦓","🐺","🦊","🐱","🐈","🦁","🐯","🐅","🐆","🐕","🦌","🐵","🐒","🦍",
|
||||
"🦅","🕊️","🐓","🦇","🐦","🦉","🐮","🐄","🐂","🐃","🐷","🐖","🐗","🐏","🐑","🐐","🐫","🦒","🐘","🦏",
|
||||
"🐭","🐁","🐀","🐹","🐰","🐇","🦔","🐸","🐊","🐢","🦎","🐍","🐳","🐬","🦈","🐙","🦑","🐌","🦋","🐜","🐝","🐞","🦗","🕷️","🦂","🦀"];
|
||||
|
||||
alertMessage.innerHTML = "";
|
||||
const container = document.createElement("div");
|
||||
container.style.userSelect = "none";
|
||||
container.style.cursor = "pointer";
|
||||
container.style.fontSize = "2em";
|
||||
container.style.width = "47vw";
|
||||
container.innerHTML = emblems.map(i => `<span>${i}</span>`).join("");
|
||||
container.addEventListener("mouseover", e => showTip(e), false);
|
||||
container.addEventListener("click", e => clickEmblem(e), false);
|
||||
alertMessage.appendChild(container);
|
||||
|
||||
$("#alert").dialog({
|
||||
resizable: false, width: fitContent(), title: "Select emblem",
|
||||
buttons: {Close: function() {$(this).dialog("close");}}
|
||||
});
|
||||
|
||||
function showTip(e) {
|
||||
if (e.target.tagName !== "SPAN") return;
|
||||
tip(`Click to select ${e.target.innerHTML} emblem`);
|
||||
}
|
||||
|
||||
function clickEmblem(e) {
|
||||
if (e.target.tagName !== "SPAN") return;
|
||||
document.getElementById("regimentEmblem").value = e.target.innerHTML;
|
||||
changeEmblem();
|
||||
}
|
||||
}
|
||||
|
||||
function changeUnit() {
|
||||
const u = this.dataset.u;
|
||||
const reg = regiment();
|
||||
reg.u[u] = (+this.value)||0;
|
||||
reg.a = d3.sum(Object.values(reg.u));
|
||||
elSelected.querySelector("text").innerHTML = Military.getTotal(reg);
|
||||
if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click();
|
||||
if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function splitRegiment() {
|
||||
const reg = regiment(), u1 = reg.u;
|
||||
const state = elSelected.dataset.state, military = pack.states[state].military;
|
||||
const i = last(military).i + 1, u2 = Object.assign({}, u1); // u clone
|
||||
|
||||
Object.keys(u2).forEach(u => u2[u] = Math.floor(u2[u]/2)); // halved new reg
|
||||
const a = d3.sum(Object.values(u2)); // new reg total
|
||||
if (!a) {tip("Not enough forces to split", false, "error"); return}; // nothing to add
|
||||
|
||||
// update old regiment
|
||||
Object.keys(u1).forEach(u => u1[u] = Math.ceil(u1[u]/2)); // halved old reg
|
||||
reg.a = d3.sum(Object.values(u1)); // old reg total
|
||||
regimentComposition.querySelectorAll("input").forEach(el => el.value = reg.u[el.dataset.u]||0);
|
||||
elSelected.querySelector("text").innerHTML = Military.getTotal(reg);
|
||||
|
||||
// create new regiment
|
||||
const shift = +armies.attr("box-size") * 2;
|
||||
const y = function(x, y) {do {y+=shift} while (military.find(r => r.x === x && r.y === y)); return y;}
|
||||
const newReg = {a, cell:reg.cell, i, n:reg.n, u:u2, x:reg.x, y:y(reg.x, reg.y), bx:reg.bx, by:reg.by, icon: reg.icon};
|
||||
newReg.name = Military.getName(newReg, military);
|
||||
military.push(newReg);
|
||||
Military.generateNote(newReg, pack.states[state]); // add legend
|
||||
Military.drawRegiment(newReg, state); // draw new reg below
|
||||
|
||||
if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function toggleAdd() {
|
||||
document.getElementById("regimentAdd").classList.toggle("pressed");
|
||||
if (document.getElementById("regimentAdd").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", addRegimentOnClick);
|
||||
tip("Click on map to create new regiment or fleet", true);
|
||||
} else {
|
||||
clearMainTip();
|
||||
viewbox.on("click", clicked).style("cursor", "default");
|
||||
}
|
||||
}
|
||||
|
||||
function addRegimentOnClick() {
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
const x = pack.cells.p[cell][0], y = pack.cells.p[cell][1];
|
||||
const state = elSelected.dataset.state, military = pack.states[state].military;
|
||||
const i = military.length ? last(military).i + 1 : 0;
|
||||
const n = +(pack.cells.h[cell] < 20); // naval or land
|
||||
const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, icon:"🛡️"};
|
||||
reg.name = Military.getName(reg, military);
|
||||
military.push(reg);
|
||||
Military.generateNote(reg, pack.states[state]); // add legend
|
||||
Military.drawRegiment(reg, state);
|
||||
if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click();
|
||||
toggleAdd();
|
||||
}
|
||||
|
||||
function toggleAttach() {
|
||||
document.getElementById("regimentAttach").classList.toggle("pressed");
|
||||
if (document.getElementById("regimentAttach").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", attachRegimentOnClick);
|
||||
tip("Click on another regiment to unite both regiments. The current regiment will be removed", true);
|
||||
armies.selectAll(":scope > g").classed("draggable", false);
|
||||
} else {
|
||||
clearMainTip();
|
||||
armies.selectAll(":scope > g").classed("draggable", true);
|
||||
viewbox.on("click", clicked).style("cursor", "default");
|
||||
}
|
||||
}
|
||||
|
||||
function attachRegimentOnClick() {
|
||||
const target = d3.event.target, regSelected = target.parentElement, army = regSelected.parentElement;
|
||||
const oldState = +elSelected.dataset.state, newState = +regSelected.dataset.state;
|
||||
|
||||
if (army.parentElement.id !== "armies") {tip("Please click on a regiment", false, "error"); return;}
|
||||
if (regSelected === elSelected) {tip("Cannot attach regiment to itself. Please click on another regiment", false, "error"); return;}
|
||||
|
||||
const reg = regiment(); // reg to be attached
|
||||
const sel = pack.states[newState].military.find(r => r.i == regSelected.dataset.id); // reg to attach to
|
||||
|
||||
for (const unit of options.military) {
|
||||
const u = unit.name;
|
||||
if (reg.u[u]) sel.u[u] ? sel.u[u] += reg.u[u] : sel.u[u] = reg.u[u];
|
||||
}
|
||||
sel.a = d3.sum(Object.values(sel.u)); // reg total
|
||||
regSelected.querySelector("text").innerHTML = Military.getTotal(sel); // update selected reg total text
|
||||
|
||||
// remove attached regiment
|
||||
const military = pack.states[oldState].military;
|
||||
military.splice(military.indexOf(reg), 1);
|
||||
const index = notes.findIndex(n => n.id === elSelected.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
elSelected.remove();
|
||||
|
||||
if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click();
|
||||
$("#regimentEditor").dialog("close");
|
||||
editRegiment("#"+regSelected.id);
|
||||
}
|
||||
|
||||
function regenerateLegend() {
|
||||
const index = notes.findIndex(n => n.id === elSelected.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
|
||||
const s = pack.states[elSelected.dataset.state];
|
||||
Military.generateNote(regiment(), s);
|
||||
}
|
||||
|
||||
function editLegend() {
|
||||
editNotes(elSelected.id, regiment().name);
|
||||
}
|
||||
|
||||
function removeRegiment() {
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the regiment?";
|
||||
$("#alert").dialog({resizable: false, title: "Remove regiment",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
const military = pack.states[elSelected.dataset.state].military;
|
||||
const regIndex = military.indexOf(regiment());
|
||||
if (regIndex === -1) return;
|
||||
military.splice(regIndex, 1);
|
||||
|
||||
const index = notes.findIndex(n => n.id === elSelected.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
elSelected.remove();
|
||||
|
||||
if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click();
|
||||
if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click();
|
||||
$("#regimentEditor").dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function dragRegiment() {
|
||||
d3.select(this).raise();
|
||||
d3.select(this.parentNode).raise();
|
||||
|
||||
const reg = pack.states[this.dataset.state].military.find(r => r.i == this.dataset.id);
|
||||
const size = +armies.attr("box-size");
|
||||
const w = reg.n ? size * 4 : size * 6;
|
||||
const h = size * 2;
|
||||
const x1 = x => rn(x - w / 2, 2);
|
||||
const y1 = y => rn(y - size, 2);
|
||||
|
||||
const baseRect = this.querySelectorAll("rect")[0];
|
||||
const text = this.querySelector("text");
|
||||
const iconRect = this.querySelectorAll("rect")[1];
|
||||
const icon = this.querySelector(".regimentIcon");
|
||||
|
||||
const self = elSelected === this;
|
||||
const baseLine = viewbox.select("g#regimentBase > line");
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const x = reg.x = d3.event.x, y = reg.y = d3.event.y;
|
||||
|
||||
baseRect.setAttribute("x", x1(x));
|
||||
baseRect.setAttribute("y", y1(y));
|
||||
text.setAttribute("x", x);
|
||||
text.setAttribute("y", y);
|
||||
iconRect.setAttribute("x", x1(x)-h);
|
||||
iconRect.setAttribute("y", y1(y));
|
||||
icon.setAttribute("x", x1(x)-size);
|
||||
icon.setAttribute("y", y);
|
||||
if (self) baseLine.attr("x2", x).attr("y2", y);
|
||||
});
|
||||
}
|
||||
|
||||
function dragBase() {
|
||||
const baseLine = viewbox.select("g#regimentBase > line");
|
||||
const reg = regiment();
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
this.setAttribute("cx", d3.event.x);
|
||||
this.setAttribute("cy", d3.event.y);
|
||||
baseLine.attr("x1", d3.event.x).attr("y1", d3.event.y);
|
||||
});
|
||||
|
||||
d3.event.on("end", function() {reg.bx = d3.event.x; reg.by = d3.event.y;});
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
armies.selectAll(":scope > g").classed("draggable", false);
|
||||
armies.selectAll("g>g").call(d3.drag().on("drag", null));
|
||||
viewbox.select("g#regimentBase").remove();
|
||||
document.getElementById("regimentAdd").classList.remove("pressed");
|
||||
document.getElementById("regimentAttach").classList.remove("pressed");
|
||||
restoreDefaultEvents();
|
||||
elSelected = null;
|
||||
}
|
||||
|
||||
}
|
||||
180
modules/ui/regiments-overview.js
Normal file
180
modules/ui/regiments-overview.js
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
"use strict";
|
||||
function overviewRegiments(state) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
if (!layerIsOn("toggleMilitary")) toggleMilitary();
|
||||
|
||||
const body = document.getElementById("regimentsBody");
|
||||
updateFilter(state);
|
||||
addLines();
|
||||
$("#regimentsOverview").dialog();
|
||||
|
||||
if (modules.overviewRegiments) return;
|
||||
modules.overviewRegiments = true;
|
||||
updateHeaders();
|
||||
|
||||
$("#regimentsOverview").dialog({
|
||||
title: "Regiments Overview", resizable: false, width: fitContent(),
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("regimentsOverviewRefresh").addEventListener("click", addLines);
|
||||
document.getElementById("regimentsPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("regimentsAddNew").addEventListener("click", toggleAdd);
|
||||
document.getElementById("regimentsExport").addEventListener("click", downloadRegimentsData);
|
||||
document.getElementById("regimentsFilter").addEventListener("change", addLines);
|
||||
|
||||
// update military types in header and tooltips
|
||||
function updateHeaders() {
|
||||
const header = document.getElementById("regimentsHeader");
|
||||
header.querySelectorAll(".removable").forEach(el => el.remove());
|
||||
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
insert(`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`);
|
||||
}
|
||||
header.querySelectorAll(".removable").forEach(function(e) {
|
||||
e.addEventListener("click", function() {sortLines(this);});
|
||||
});
|
||||
}
|
||||
|
||||
// add line for each state
|
||||
function addLines() {
|
||||
const state = +regimentsFilter.value;
|
||||
body.innerHTML = "";
|
||||
let lines = "";
|
||||
const regiments = [];
|
||||
|
||||
for (const s of pack.states) {
|
||||
if (!s.i || s.removed || !s.military.length) continue;
|
||||
if (state !== -1 && s.i !== state) continue; // specific state is selected
|
||||
|
||||
for (const r of s.military) {
|
||||
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name]||0}`).join(" ");
|
||||
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name]||0}</div>`).join(" ");
|
||||
|
||||
lines += `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
|
||||
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
|
||||
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
|
||||
<span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span>
|
||||
<input data-tip="Regiment's name" style="width:13em" value="${r.name}" readonly>
|
||||
${lineData}
|
||||
<div data-type="total" data-tip="Total military personnel (not considering crew)" style="font-weight: bold">${r.a}</div>
|
||||
<span data-tip="Edit regiment" onclick="editRegiment('#regiment${s.i}-${r.i}')" class="icon-pencil pointer"></span>
|
||||
</div>`;
|
||||
|
||||
regiments.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
lines += `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
|
||||
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div>
|
||||
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name]||0)))}</div>`).join(" ")}
|
||||
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
|
||||
</div>`;
|
||||
|
||||
body.insertAdjacentHTML("beforeend", lines);
|
||||
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
|
||||
applySorting(regimentsHeader);
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => regimentHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => regimentHighlightOff(ev)));
|
||||
}
|
||||
|
||||
function updateFilter(state) {
|
||||
const filter = document.getElementById("regimentsFilter");
|
||||
filter.options.length = 0; // remove all options
|
||||
filter.options.add(new Option(`all`, -1, false, state === -1));
|
||||
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name) ? 1 : -1);
|
||||
statesSorted.forEach(s => filter.options.add(new Option(s.name, s.i, false, s.i == state)));
|
||||
}
|
||||
|
||||
function regimentHighlightOn(event) {
|
||||
const state = +event.target.dataset.s;
|
||||
const id = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
armies.select(`g > g#regiment${state}-${id}`).transition().duration(2000).style("fill", "#ff0000");
|
||||
}
|
||||
|
||||
function regimentHighlightOff(event) {
|
||||
const state = +event.target.dataset.s;
|
||||
const id = +event.target.dataset.id;
|
||||
armies.select(`g > g#regiment${state}-${id}`).transition().duration(1000).style("fill", null);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === "absolute") {
|
||||
body.dataset.type = "percentage";
|
||||
const lines = body.querySelectorAll(":scope > div:not(.totalLine)");
|
||||
const array = Array.from(lines), cache = [];
|
||||
|
||||
const total = function(type) {
|
||||
if (cache[type]) cache[type];
|
||||
cache[type] = d3.sum(array.map(el => +el.dataset[type]));
|
||||
return cache[type];
|
||||
}
|
||||
|
||||
lines.forEach(function(el) {
|
||||
el.querySelectorAll("div").forEach(function(div) {
|
||||
const type = div.dataset.type;
|
||||
if (type === "rate") return;
|
||||
div.textContent = total(type) ? rn(+el.dataset[type] / total(type) * 100) + "%" : "0%";
|
||||
});
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
addLines();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAdd() {
|
||||
document.getElementById("regimentsAddNew").classList.toggle("pressed");
|
||||
if (document.getElementById("regimentsAddNew").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", addRegimentOnClick);
|
||||
tip("Click on map to create new regiment or fleet", true);
|
||||
if (regimentAdd.offsetParent) regimentAdd.classList.add("pressed");
|
||||
} else {
|
||||
clearMainTip();
|
||||
viewbox.on("click", clicked).style("cursor", "default");
|
||||
addLines();
|
||||
if (regimentAdd.offsetParent) regimentAdd.classList.remove("pressed");
|
||||
}
|
||||
}
|
||||
|
||||
function addRegimentOnClick() {
|
||||
const state = +regimentsFilter.value;
|
||||
if (state === -1) {tip("Please select state from the list", false, "error"); return;}
|
||||
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
const x = pack.cells.p[cell][0], y = pack.cells.p[cell][1];
|
||||
const military = pack.states[state].military;
|
||||
const i = military.length ? last(military).i + 1 : 0;
|
||||
const n = +(pack.cells.h[cell] < 20); // naval or land
|
||||
const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, icon:"🛡️"};
|
||||
reg.name = Military.getName(reg, military);
|
||||
military.push(reg);
|
||||
Military.generateNote(reg, pack.states[state]); // add legend
|
||||
Military.drawRegiment(reg, state);
|
||||
toggleAdd();
|
||||
}
|
||||
|
||||
function downloadRegimentsData() {
|
||||
const units = options.military.map(u => u.name);
|
||||
let data = "State,Id,Name,"+units.map(u => capitalize(u)).join(",")+",Total\n"; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function(el) {
|
||||
data += el.dataset.state + ",";
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
data += units.map(u => el.dataset[u]).join(",") + ",";
|
||||
data += el.dataset.total + "\n";
|
||||
});
|
||||
|
||||
const name = getFileName("Regiments") + ".csv";
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -293,15 +293,26 @@ function editReligions() {
|
|||
function religionRemove() {
|
||||
if (customization) return;
|
||||
const religion = +this.parentNode.dataset.id;
|
||||
relig.select("#religion"+religion).remove();
|
||||
debug.select("#religionsCenter"+religion).remove();
|
||||
|
||||
pack.cells.religion.forEach((r, i) => {if(r === religion) pack.cells.religion[i] = 0;});
|
||||
pack.religions[religion].removed = true;
|
||||
const origin = pack.religions[religion].origin;
|
||||
pack.religions.forEach(r => {if(r.origin === religion) r.origin = origin;});
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the religion? <br>This action cannot be reverted";
|
||||
$("#alert").dialog({resizable: false, title: "Remove religion",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
relig.select("#religion"+religion).remove();
|
||||
relig.select("#religion-gap"+religion).remove();
|
||||
debug.select("#religionsCenter"+religion).remove();
|
||||
|
||||
refreshReligionsEditor();
|
||||
pack.cells.religion.forEach((r, i) => {if(r === religion) pack.cells.religion[i] = 0;});
|
||||
pack.religions[religion].removed = true;
|
||||
const origin = pack.religions[religion].origin;
|
||||
pack.religions.forEach(r => {if(r.origin === religion) r.origin = origin;});
|
||||
|
||||
refreshReligionsEditor();
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function drawReligionCenters() {
|
||||
|
|
|
|||
|
|
@ -42,11 +42,11 @@ function editStates() {
|
|||
const el = ev.target, cl = el.classList, line = el.parentNode, state = +line.dataset.id;
|
||||
if (cl.contains("fillRect")) stateChangeFill(el); else
|
||||
if (cl.contains("name")) editStateName(state); else
|
||||
if (cl.contains("icon-fleur")) stateOpenCOA(ev, state); else
|
||||
if (cl.contains("icon-coa")) stateOpenCOA(ev, state); else
|
||||
if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state); else
|
||||
if (cl.contains("culturePopulation")) changePopulation(state); else
|
||||
if (cl.contains("icon-pin")) focusOnState(state, cl); else
|
||||
if (cl.contains("icon-trash-empty")) stateRemove(state);
|
||||
if (cl.contains("icon-trash-empty")) stateRemovePrompt(state);
|
||||
});
|
||||
|
||||
body.addEventListener("input", function(ev) {
|
||||
|
|
@ -90,7 +90,7 @@ function editStates() {
|
|||
data-population=${population} data-burgs=${s.burgs} data-color="" data-form="" data-capital="" data-culture="" data-type="" data-expansionism="">
|
||||
<svg width="9" height="9" class="placeholder"></svg>
|
||||
<input data-tip="Neutral lands name. Click to change" class="stateName name pointer italic" value="${s.name}" readonly>
|
||||
<span class="icon-fleur placeholder hide"></span>
|
||||
<span class="icon-coa placeholder hide"></span>
|
||||
<input class="stateForm placeholder" value="none">
|
||||
<span class="icon-star-empty placeholder hide"></span>
|
||||
<input class="stateCapital placeholder hide">
|
||||
|
|
@ -114,7 +114,7 @@ function editStates() {
|
|||
data-area=${area} data-population=${population} data-burgs=${s.burgs} data-culture=${pack.cultures[s.culture].name} data-type=${s.type} data-expansionism=${s.expansionism}>
|
||||
<svg data-tip="State fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect pointer"></svg>
|
||||
<input data-tip="State name. Click to change" class="stateName name pointer" value="${s.name}" readonly>
|
||||
<span data-tip="Click to open state COA in the Iron Arachne Heraldry Generator. Ctrl + click to change the seed" class="icon-fleur pointer hide"></span>
|
||||
<span data-tip="Click to open state COA in the Iron Arachne Heraldry Generator. Ctrl + click to change the seed" class="icon-coa pointer hide"></span>
|
||||
<input data-tip="State form name. Click to change" class="stateForm name pointer" value="${s.formName}" readonly>
|
||||
<span data-tip="State capital. Click to zoom into view" class="icon-star-empty pointer hide"></span>
|
||||
<input data-tip="Capital name. Click and type to rename" class="stateCapital hide" value="${capital}" autocorrect="off" spellcheck="false"/>
|
||||
|
|
@ -173,30 +173,20 @@ function editStates() {
|
|||
if (!layerIsOn("toggleStates")) return;
|
||||
const state = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
const path = statesBody.select("#state"+state).attr("d");
|
||||
debug.append("path").attr("class", "highlight").attr("d", path)
|
||||
const d = regions.select("#state"+state).attr("d");
|
||||
|
||||
const path = debug.append("path").attr("class", "highlight").attr("d", d)
|
||||
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)").call(transition);
|
||||
}
|
||||
.attr("filter", "url(#blur1)");
|
||||
|
||||
function transition(path) {
|
||||
const duration = (path.node().getTotalLength() + 5000) / 2;
|
||||
path.transition().duration(duration).attrTween("stroke-dasharray", tweenDash);
|
||||
}
|
||||
|
||||
function tweenDash() {
|
||||
const l = this.getTotalLength();
|
||||
const l = path.node().getTotalLength(), dur = (l + 5000) / 2;
|
||||
const i = d3.interpolateString("0," + l, l + "," + l);
|
||||
return t => i(t);
|
||||
}
|
||||
|
||||
function removePath(path) {
|
||||
path.transition().duration(1000).attr("opacity", 0).remove();
|
||||
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)});
|
||||
}
|
||||
|
||||
function stateHighlightOff() {
|
||||
debug.selectAll(".highlight").each(function(el) {
|
||||
d3.select(this).call(removePath);
|
||||
function stateHighlightOff(event) {
|
||||
debug.selectAll(".highlight").each(function() {
|
||||
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -431,8 +421,22 @@ function editStates() {
|
|||
if (!defs.selectAll("#fog path").size()) fogging.style("display", "none"); // all items are de-focused
|
||||
}
|
||||
|
||||
function stateRemove(state) {
|
||||
function stateRemovePrompt(state) {
|
||||
if (customization) return;
|
||||
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the state? <br>This action cannot be reverted";
|
||||
$("#alert").dialog({resizable: false, title: "Remove state",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
stateRemove(state);
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function stateRemove(state) {
|
||||
statesBody.select("#state"+state).remove();
|
||||
statesBody.select("#state-gap"+state).remove();
|
||||
statesHalo.select("#state-border"+state).remove();
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -64,6 +64,7 @@ function processFeatureRegeneration(event, button) {
|
|||
if (button === "regenerateStates") regenerateStates(); else
|
||||
if (button === "regenerateProvinces") regenerateProvinces(); else
|
||||
if (button === "regenerateReligions") regenerateReligions(); else
|
||||
if (button === "regenerateMilitary") regenerateMilitary(); else
|
||||
if (button === "regenerateMarkers") regenerateMarkers(event); else
|
||||
if (button === "regenerateZones") regenerateZones(event);
|
||||
}
|
||||
|
|
@ -135,6 +136,7 @@ function regenerateBurgs() {
|
|||
moveBurgToGroup(burg, "cities");
|
||||
});
|
||||
|
||||
pack.features.forEach(f => {if (f.port) f.port = 0}); // reset features ports counter
|
||||
BurgsAndStates.specifyBurgs();
|
||||
BurgsAndStates.defineBurgFeatures();
|
||||
BurgsAndStates.drawBurgs();
|
||||
|
|
@ -215,15 +217,18 @@ function regenerateStates() {
|
|||
BurgsAndStates.normalizeStates();
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.assignColors();
|
||||
BurgsAndStates.generateCampaigns();
|
||||
BurgsAndStates.generateDiplomacy();
|
||||
BurgsAndStates.defineStateForms();
|
||||
BurgsAndStates.generateProvinces(true);
|
||||
if (!layerIsOn("toggleStates")) toggleStates(); else drawStates();
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders();
|
||||
BurgsAndStates.drawStateLabels();
|
||||
Military.generate();
|
||||
|
||||
if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click();
|
||||
if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click();
|
||||
if (document.getElementById("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateProvinces() {
|
||||
|
|
@ -238,6 +243,12 @@ function regenerateReligions() {
|
|||
if (!layerIsOn("toggleReligions")) toggleReligions(); else drawReligions();
|
||||
}
|
||||
|
||||
function regenerateMilitary() {
|
||||
Military.generate();
|
||||
if (!layerIsOn("toggleMilitary")) toggleMilitary();
|
||||
if (document.getElementById("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateMarkers(event) {
|
||||
let number = gauss(1, .5, .3, 5, 2);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ function editWorld() {
|
|||
"Southern": () => applyPreset(33, 75),
|
||||
"Restore Winds": restoreDefaultWinds
|
||||
}, open: function() {
|
||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button")
|
||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
|
||||
buttons[0].addEventListener("mousemove", () => tip("Click to set map size to cover the whole World"));
|
||||
buttons[1].addEventListener("mousemove", () => tip("Click to set map size to cover the Northern latitudes"));
|
||||
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"));
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
const globe = d3.select("#globe");
|
||||
|
|
@ -56,6 +56,7 @@ function editWorld() {
|
|||
if (layerIsOn("togglePrec")) drawPrec();
|
||||
if (layerIsOn("toggleBiomes")) drawBiomes();
|
||||
if (layerIsOn("toggleCoordinates")) drawCoordinates();
|
||||
if (document.getElementById("canvas3d")) setTimeout(ThreeD.update(), 500);
|
||||
}
|
||||
|
||||
function updateGlobePosition() {
|
||||
|
|
@ -100,26 +101,26 @@ function editWorld() {
|
|||
function updateWindDirections() {
|
||||
globe.select("#globeWindArrows").selectAll("path").each(function(d, i) {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
this.setAttribute("transform", `rotate(${winds[i]} ${tr[1]} ${tr[2]})`);
|
||||
this.setAttribute("transform", `rotate(${options.winds[i]} ${tr[1]} ${tr[2]})`);
|
||||
});
|
||||
}
|
||||
|
||||
function changeWind() {
|
||||
const arrow = d3.event.target.nextElementSibling;
|
||||
const tier = +arrow.dataset.tier;
|
||||
winds[tier] = (winds[tier] + 45) % 360;
|
||||
options.winds[tier] = (options.winds[tier] + 45) % 360;
|
||||
const tr = parseTransform(arrow.getAttribute("transform"));
|
||||
arrow.setAttribute("transform", `rotate(${winds[tier]} ${tr[1]} ${tr[2]})`);
|
||||
localStorage.setItem("winds", winds);
|
||||
arrow.setAttribute("transform", `rotate(${options.winds[tier]} ${tr[1]} ${tr[2]})`);
|
||||
localStorage.setItem("winds", options.winds);
|
||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0);
|
||||
if (mapTiers.includes(tier)) updateWorld();
|
||||
}
|
||||
|
||||
|
||||
function restoreDefaultWinds() {
|
||||
const defaultWinds = [225, 45, 225, 315, 135, 315];
|
||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0);
|
||||
const update = mapTiers.some(t => winds[t] != defaultWinds[t]);
|
||||
winds = defaultWinds;
|
||||
const update = mapTiers.some(t => options.winds[t] != defaultWinds[t]);
|
||||
options.winds = defaultWinds;
|
||||
updateWindDirections();
|
||||
if (update) updateWorld();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,6 +232,11 @@ function gauss(expected = 100, deviation = 30, min = 0, max = 300, round = 0) {
|
|||
return rn(Math.max(Math.min(d3.randomNormal(expected, deviation)(), max), min), round);
|
||||
}
|
||||
|
||||
// get integer from float as floor + P(fractional)
|
||||
function Pint(float) {
|
||||
return ~~float + +P(float % 1);
|
||||
}
|
||||
|
||||
// round value to d decimals
|
||||
function rn(v, d = 0) {
|
||||
const m = Math.pow(10, d);
|
||||
|
|
@ -245,11 +250,11 @@ function round(s, d = 1) {
|
|||
|
||||
// corvent number to short string with SI postfix
|
||||
function si(n) {
|
||||
if (n >= 1e9) {return rn(n / 1e9, 1) + "B";}
|
||||
if (n >= 1e8) {return rn(n / 1e6) + "M";}
|
||||
if (n >= 1e6) {return rn(n / 1e6, 1) + "M";}
|
||||
if (n >= 1e4) {return rn(n / 1e3) + "K";}
|
||||
if (n >= 1e3) {return rn(n / 1e3, 1) + "K";}
|
||||
if (n >= 1e9) return rn(n / 1e9, 1) + "B";
|
||||
if (n >= 1e8) return rn(n / 1e6) + "M";
|
||||
if (n >= 1e6) return rn(n / 1e6, 1) + "M";
|
||||
if (n >= 1e4) return rn(n / 1e3) + "K";
|
||||
if (n >= 1e3) return rn(n / 1e3, 1) + "K";
|
||||
return rn(n);
|
||||
}
|
||||
|
||||
|
|
@ -403,6 +408,9 @@ function getAdjective(string) {
|
|||
return trimVowels(string) + "ian";
|
||||
}
|
||||
|
||||
// get ordinal out of integer: 1 => 1st
|
||||
const nth = n => n+(["st","nd","rd"][((n+90)%100-10)%10-1]||"th");
|
||||
|
||||
// split string into 2 almost equal parts not breaking words
|
||||
function splitInTwo(str) {
|
||||
const half = str.length / 2;
|
||||
|
|
@ -596,5 +604,9 @@ function isCtrlClick(event) {
|
|||
return event.ctrlKey || event.metaKey;
|
||||
}
|
||||
|
||||
function generateDate(from = 100, to = 1000) {
|
||||
return new Date(rand(from, to),rand(12),rand(31)).toLocaleDateString("en", {year:'numeric', month:'long', day:'numeric'});
|
||||
}
|
||||
|
||||
// localStorageDB
|
||||
!function(){function e(t,o){return n?void(n.transaction("s").objectStore("s").get(t).onsuccess=function(e){var t=e.target.result&&e.target.result.v||null;o(t)}):void setTimeout(function(){e(t,o)},100)}var t=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB;if(!t)return void console.error("indexDB not supported");var n,o={k:"",v:""},r=t.open("d2",1);r.onsuccess=function(e){n=this.result},r.onerror=function(e){console.error("indexedDB request error"),console.log(e)},r.onupgradeneeded=function(e){n=null;var t=e.target.result.createObjectStore("s",{keyPath:"k"});t.transaction.oncomplete=function(e){n=e.target.db}},window.ldb={get:e,set:function(e,t){o.k=e,o.v=t,n.transaction("s","readwrite").objectStore("s").put(o)}}}();
|
||||
Loading…
Add table
Add a link
Reference in a new issue