mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
v1.4.01
This commit is contained in:
parent
e7b4d0e39a
commit
72d124d95d
26 changed files with 713 additions and 498 deletions
|
|
@ -263,4 +263,5 @@
|
|||
margin-left: 1px;
|
||||
width: .6em;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
.icon-die:before {content:'🎲';}
|
||||
31
index.css
31
index.css
|
|
@ -60,13 +60,6 @@ textarea {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
#preview {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
left: 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#pickerContainer {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
|
|
@ -101,7 +94,7 @@ button, select, a, .pointer {
|
|||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
#lakes, #coastline, #armies {
|
||||
#lakes, #coastline, #armies, #ice {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
@ -1151,6 +1144,7 @@ div.slider .ui-slider-handle {
|
|||
|
||||
.table {
|
||||
max-height: 75vh;
|
||||
max-width: 75vw;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
@ -1657,7 +1651,6 @@ rect.fillRect {
|
|||
|
||||
#militaryOptionsTable input {
|
||||
width: 9em;
|
||||
padding-left: 3px;
|
||||
border: 1px solid #d4d4d4;
|
||||
}
|
||||
|
||||
|
|
@ -1760,24 +1753,6 @@ div.textual span, .textual legend {
|
|||
vertical-align: top;
|
||||
}
|
||||
|
||||
#markerIconTable {
|
||||
font-size: 1.6em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#markerIconTable td:hover {
|
||||
transition: .1s;
|
||||
color: #3c3ca9;
|
||||
}
|
||||
|
||||
#markerIconTable td:active {
|
||||
transform: translate(0px, 1px);
|
||||
}
|
||||
|
||||
#markerIconTable td.selected {
|
||||
outline: 1px solid #9b9b9b;
|
||||
}
|
||||
|
||||
.highlighted {
|
||||
outline-width: 2px;
|
||||
outline-style: dashed;
|
||||
|
|
@ -2025,7 +2000,7 @@ svg.button {
|
|||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: 21em;
|
||||
max-width: 22em;
|
||||
background-color: #fff;
|
||||
padding: 1.2em;
|
||||
border: solid 1px #000;
|
||||
|
|
|
|||
69
index.html
69
index.html
|
|
@ -34,11 +34,11 @@
|
|||
#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.3" as="style">
|
||||
<link rel="preload" href="icons.css?version=1.3" as="style">
|
||||
<link rel="preload" href="index.css?version=1.4" as="style">
|
||||
<link rel="preload" href="icons.css?version=1.4" as="style">
|
||||
<link rel="preload" href="libs/jquery-ui.css" as="style">
|
||||
<link rel="stylesheet" href="index.css?version=1.3">
|
||||
<link rel="stylesheet" href="icons.css?version=1.3">
|
||||
<link rel="stylesheet" href="index.css?version=1.4">
|
||||
<link rel="stylesheet" href="icons.css?version=1.4">
|
||||
<link rel="stylesheet" href="libs/jquery-ui.css">
|
||||
|
||||
</head>
|
||||
|
|
@ -899,7 +899,7 @@
|
|||
<div id="loading">
|
||||
<div id="titleName"><t data-t="titleName">Azgaar's</t></div>
|
||||
<div id="title"><t data-t="title">Fantasy Map Generator</t></div>
|
||||
<div id="version"><t data-t="version">v. </t>1.3</div>
|
||||
<div id="version"><t data-t="version">v. </t>1.4</div>
|
||||
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
|
||||
</div>
|
||||
|
||||
|
|
@ -960,6 +960,7 @@
|
|||
<li id="toggleRoutes"data-tip="Trade routes: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: U" onclick="toggleRoutes(event)">Ro<u>u</u>tes</li>
|
||||
<li id="toggleTemp" data-tip="Temperature map: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: T" class="buttonoff" onclick="toggleTemp(event)"><u>T</u>emperature</li>
|
||||
<li id="togglePopulation" data-tip="Population map: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: N" class="buttonoff" onclick="togglePopulation(event)">Populatio<u>n</u></li>
|
||||
<li id="toggleIce" data-tip="Icebergs and glaciers: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: J" class="buttonoff" onclick="toggleIce(event)">Ice</li>
|
||||
<li id="togglePrec" data-tip="Precipitation map: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: A" class="buttonoff" onclick="togglePrec(event)">Precipit<u>a</u>tion</li>
|
||||
<li id="toggleLabels" data-tip="Labels: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: L" onclick="toggleLabels(event)"><u>L</u>abels</li>
|
||||
<li id="toggleIcons" data-tip="Burg icons: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style. Shortcut: I" onclick="toggleIcons(event)"><u>I</u>cons</li>
|
||||
|
|
@ -1002,6 +1003,7 @@
|
|||
<option value="fogging">Fogging</option>
|
||||
<option value="gridOverlay">Grid</option>
|
||||
<option value="terrs">Heightmap</option>
|
||||
<option value="ice">Ice</option>
|
||||
<option value="labels">Labels</option>
|
||||
<option value="lakes">Lakes</option>
|
||||
<option value="landmass">Landmass</option>
|
||||
|
|
@ -1022,7 +1024,6 @@
|
|||
<option value="texture">Texture</option>
|
||||
<option value="compass">Wind Rose</option>
|
||||
<option value="zones">Zones</option>
|
||||
<option value="seaIce">Ice</option>
|
||||
</select>
|
||||
<!-- <button id="restoreStyle" data-tip="Click to restore default style for all elements" class="icon-ccw styleButton" onclick="askToRestoreDefaultStyle()"></button> -->
|
||||
|
||||
|
|
@ -1857,6 +1858,7 @@
|
|||
<button id="regenerateProvinces" data-tip="Click to regenerate provinces. States will remain as they are">Provinces</button>
|
||||
<button id="regenerateReligions" data-tip="Click to regenerate religions">Religions</button>
|
||||
<button id="regenerateMilitary" data-tip="Click to recalculate military forces based on current military options">Military</button>
|
||||
<button id="regenerateIce" data-tip="Click to icebergs and glaciers">Ice</button>
|
||||
<button id="regenerateMarkers" data-tip="Click to regenerate markers. Hold Ctrl and click to set markers number multiplier">Markers</button>
|
||||
<button id="regenerateZones" data-tip="Click to regenerate zones. Hold Ctrl and click to set zones number multiplier">Zones</button>
|
||||
</div>
|
||||
|
|
@ -2160,6 +2162,14 @@
|
|||
<button id="lakeLegend" data-tip="Edit free text notes (legend) for the lake" class="icon-edit"></button>
|
||||
</div>
|
||||
|
||||
<div id="iceEditor" class="dialog" style="display: none">
|
||||
<button id="iceEditStyle" data-tip="Edit style in Style Editor" class="icon-brush"></button>
|
||||
<button id="iceRandomize" data-tip="Randomize Iceberd shape" class="icon-shuffle"></button>
|
||||
<input id="iceSize" data-tip="Change Iceberg size" type="range" min=".05" max="1" step=".01">
|
||||
<button id="iceNew" data-tip="Add an Iceberg (click on map)" class="icon-plus"></button>
|
||||
<button id="iceRemove" data-tip="Remove the element. Shortcut: Delete" class="icon-trash"></button>
|
||||
</div>
|
||||
|
||||
<div id="coastlineEditor" class="dialog" style="display: none">
|
||||
<button id="coastlineGroupsShow" data-tip="Show the group selection" class="icon-tags"></button>
|
||||
<div id="coastlineGroupsSelection" style="display: none">
|
||||
|
|
@ -2373,15 +2383,13 @@
|
|||
<button id="markerIcon" data-tip="Change marker icon and edit positioning" class="icon-star"></button>
|
||||
<div id="markerIconSection" style="display: none">
|
||||
<i data-tip="Change marker icon size" class="icon-resize-full"></i>
|
||||
<input id="markerIconSize" data-tip="Change marker icon size" type="range" min=10 max=30 step=.5 value=22 style="width:12em">
|
||||
<input id="markerIconSize" data-tip="Change marker icon size" type="range" min=5 max=30 step=.5 value=22 style="width:12em"><br>
|
||||
<i data-tip="Marker Icon" class="icon-info"></i>
|
||||
<button id="markerIconSelect" data-tip="Click to select icon"></button>
|
||||
<i data-tip="Change marker horizontal shift" class="icon-resize-horizontal"></i>
|
||||
<input id="markerIconShiftX" data-tip="Change icon horizontal shift" type="number" value=50 style="width:3em">
|
||||
<i data-tip="Change marker vertical shift" class="icon-resize-vertical"></i>
|
||||
<input id="markerIconShiftY" data-tip="Change vertical shift" type="number" min=0 max=100 value=50 style="width:3em">
|
||||
<span data-tip="Paste custom unicode symbol to use as icon">Paste here:</span>
|
||||
<input id="markerIconCustom" data-tip="Paste custom unicode symbol to use as icon" style="width:3em">
|
||||
<table id="markerIconTable"></table>
|
||||
<div style="font-style: italic; color: darkgrey;">Emojis look different in different browsers. Visit <a href="https://emojipedia.org/" rel="noopener" target="_blank">Emojipedia</a> to check and find more</div>
|
||||
</div>
|
||||
|
||||
<button id="markerStyle" data-tip="Change marker size and colors" class="icon-brush"></button>
|
||||
|
|
@ -2421,6 +2429,7 @@
|
|||
</div>
|
||||
|
||||
<div id="regimentBottom">
|
||||
<button id="regimentAttack" data-tip="Attack other regiment" class="icon-target"></button>
|
||||
<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>
|
||||
|
|
@ -2430,6 +2439,22 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="battleScreen" class="dialog" style="display: none">
|
||||
<div id="battleBody">
|
||||
<div style="font-size:1.2em; font-weight: bold"><div style="width: 1.2em; display: inline-block">⚔</div><span>Attackers</span></div>
|
||||
<table id="battleAttackers" style="padding: .2em .6em .2em .6em; border: 1px solid #ccc; margin: .0 0 .4em 0"><thead><tr></tr></thead><tbody></tbody></table>
|
||||
<div style="font-size:1.2em; font-weight: bold"><div style="width: 1.2em; display: inline-block">🛡</div><span>Defenders</span></div>
|
||||
<table id="battleDefenders" style="padding: .2em .6em .2em .6em; border: 1px solid #ccc; margin: .0 0 .4em 0"><thead><tr></tr></thead><tbody></tbody></table>
|
||||
</div>
|
||||
|
||||
<div id="battleBottom">
|
||||
<button id="battleRoll" data-tip="Roll dice to randomize sides power and battle phase" class="icon-die"></button>
|
||||
<button id="battleNext" data-tip="Calculate and apply phase results" class="icon-play"></button>
|
||||
<button id="battleApply" data-tip="Apply battle results" class="icon-check"></button>
|
||||
<button id="battleCancel" data-tip="Cancel battle results and restore initial troop value" class="icon-cancel"></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">
|
||||
|
|
@ -2839,7 +2864,7 @@
|
|||
<div id="diplomacyEditor" class="dialog stable" style="display: none">
|
||||
<div id="diplomacyHeader" class="header">
|
||||
<div style="left:.2em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="name">State </div>
|
||||
<div style="left:12.4em" data-tip="Click to sort by diplomatical relations" class="sortable alphabetically" data-sortby="relations">Relations </div>
|
||||
<div style="left:13.4em" data-tip="Click to sort by diplomatical relations" class="sortable alphabetically" data-sortby="relations">Relations </div>
|
||||
</div>
|
||||
|
||||
<div id="diplomacyBodySection" class="table"></div>
|
||||
|
|
@ -3329,10 +3354,12 @@
|
|||
<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="Unit icon">Icon</th>
|
||||
<th data-tip="Unit name. If name is changed for existing unit, old unit will be replaced">Unit name</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="Average number of people in crew (used for total personnel calculation)">Crew</th>
|
||||
<th data-tip="Unit military power (used for battle simulation)">Power</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>
|
||||
|
|
@ -3382,6 +3409,15 @@
|
|||
<p><b>Burg:</b> <span id="infoBurg">n/a</span></p>
|
||||
</div>
|
||||
|
||||
<div id="iconSelector" style="display: none" class="dialog">
|
||||
<table id="iconTable" class="table pointer" style="font-size: 2em; text-align: center"></table>
|
||||
<div style="font-style: italic; font-size: 1.2em; margin: .4em 0 0 .4em">
|
||||
<span>Select from the list or paste a Unicode character here: </span>
|
||||
<input id="iconInput" style="width: 2em">
|
||||
<span>. See <a href="https://emojipedia.org" target="_blank">Emojipedia</a> for reference</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="options3d" class="dialog stable" style="display: none">
|
||||
|
||||
<div id="options3dMesh" style="display: none">
|
||||
|
|
@ -3523,6 +3559,7 @@
|
|||
<script src="modules/religions-generator.js"></script>
|
||||
<script src="modules/military-generator.js"></script>
|
||||
<script src="libs/polylabel.min.js"></script>
|
||||
<script src="libs/lineclip.js"></script>
|
||||
<script src="libs/jquery-ui.min.js"></script>
|
||||
<script src="libs/seedrandom.min.js"></script>
|
||||
<script src="modules/ui/layers.js"></script>
|
||||
|
|
@ -3532,7 +3569,7 @@
|
|||
<script defer src="modules/ui/style.js"></script>
|
||||
<script defer src="modules/ui/measurers.js"></script>
|
||||
<script defer src="modules/save-and-load.js"></script>
|
||||
<script defer src="main.js?version=1.0"></script>
|
||||
<script defer src="main.js?version=1.4"></script>
|
||||
<script defer src="modules/relief-icons.js"></script>
|
||||
<script defer src="modules/ui/tools.js"></script>
|
||||
<script defer src="modules/ui/world-configurator.js"></script>
|
||||
|
|
@ -3543,6 +3580,7 @@
|
|||
<script defer src="modules/ui/cultures-editor.js"></script>
|
||||
<script defer src="modules/ui/namesbase-editor.js"></script>
|
||||
<script defer src="modules/ui/routes-editor.js"></script>
|
||||
<script defer src="modules/ui/ice-editor.js"></script>
|
||||
<script defer src="modules/ui/lakes-editor.js"></script>
|
||||
<script defer src="modules/ui/coastline-editor.js"></script>
|
||||
<script defer src="modules/ui/labels-editor.js"></script>
|
||||
|
|
@ -3560,6 +3598,7 @@
|
|||
<script defer src="modules/ui/military-overview.js"></script>
|
||||
<script defer src="modules/ui/regiments-overview.js"></script>
|
||||
<script defer src="modules/ui/regiment-editor.js"></script>
|
||||
<script defer src="modules/ui/battle-screen.js"></script>
|
||||
<script defer src="modules/ui/editors.js"></script>
|
||||
<script defer src="modules/ui/3d.js"></script>
|
||||
<script defer src="libs/quantize.min.js"></script>
|
||||
|
|
|
|||
2
libs/jquery-ui.min.js
vendored
2
libs/jquery-ui.min.js
vendored
File diff suppressed because one or more lines are too long
102
libs/lineclip.js
Normal file
102
libs/lineclip.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
'use strict';
|
||||
// lineclip by mourner, https://github.com/mapbox/lineclip
|
||||
// Cohen-Sutherland line clippign algorithm, adapted to efficiently
|
||||
// handle polylines rather than just segments
|
||||
function lineclip(points, bbox, result) {
|
||||
var len = points.length,
|
||||
codeA = bitCode(points[0], bbox),
|
||||
part = [],
|
||||
i, a, b, codeB, lastCode;
|
||||
if (!result) result = [];
|
||||
|
||||
for (i = 1; i < len; i++) {
|
||||
a = points[i - 1];
|
||||
b = points[i];
|
||||
codeB = lastCode = bitCode(b, bbox);
|
||||
|
||||
while (true) {
|
||||
if (!(codeA | codeB)) { // accept
|
||||
part.push(a);
|
||||
|
||||
if (codeB !== lastCode) { // segment went outside
|
||||
part.push(b);
|
||||
if (i < len - 1) { // start a new line
|
||||
result.push(part);
|
||||
part = [];
|
||||
}
|
||||
} else if (i === len - 1) {
|
||||
part.push(b);
|
||||
}
|
||||
break;
|
||||
|
||||
} else if (codeA & codeB) { // trivial reject
|
||||
break;
|
||||
} else if (codeA) { // a outside, intersect with clip edge
|
||||
a = intersect(a, b, codeA, bbox);
|
||||
codeA = bitCode(a, bbox);
|
||||
} else { // b outside
|
||||
b = intersect(a, b, codeB, bbox);
|
||||
codeB = bitCode(b, bbox);
|
||||
}
|
||||
}
|
||||
codeA = lastCode;
|
||||
}
|
||||
|
||||
if (part.length) result.push(part);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Sutherland-Hodgeman polygon clipping algorithm
|
||||
function polygonclip(points, bbox, secure = false) {
|
||||
var result, edge, prev, prevInside, inter, i, p, inside;
|
||||
|
||||
// clip against each side of the clip rectangle
|
||||
for (edge = 1; edge <= 8; edge *= 2) {
|
||||
result = [];
|
||||
prev = points[points.length - 1];
|
||||
prevInside = !(bitCode(prev, bbox) & edge);
|
||||
|
||||
for (i = 0; i < points.length; i++) {
|
||||
p = points[i];
|
||||
inside = !(bitCode(p, bbox) & edge);
|
||||
inter = inside !== prevInside; // segment goes through the clip window
|
||||
|
||||
if (secure && inter && inside && i) result.push(points[i-1]); // add previous point in secure mode (to get a correct d3 curve)
|
||||
if (inter) result.push(intersect(prev, p, edge, bbox)); // add an intersection point
|
||||
if (inside) result.push(p); // add a point if it's inside
|
||||
else if (secure && prevInside) result.push(p); // // add an outside point if in secure mode (to get a correct d3 curve)
|
||||
|
||||
prev = p;
|
||||
prevInside = inside;
|
||||
}
|
||||
points = result;
|
||||
if (!points.length) break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// intersect a segment against one of the 4 lines that make up the bbox
|
||||
function intersect(a, b, edge, bbox) {
|
||||
return edge & 8 ? [a[0] + (b[0] - a[0]) * (bbox[3] - a[1]) / (b[1] - a[1]), bbox[3]] : // top
|
||||
edge & 4 ? [a[0] + (b[0] - a[0]) * (bbox[1] - a[1]) / (b[1] - a[1]), bbox[1]] : // bottom
|
||||
edge & 2 ? [bbox[2], a[1] + (b[1] - a[1]) * (bbox[2] - a[0]) / (b[0] - a[0])] : // right
|
||||
edge & 1 ? [bbox[0], a[1] + (b[1] - a[1]) * (bbox[0] - a[0]) / (b[0] - a[0])] : null; // left
|
||||
}
|
||||
|
||||
// bit code reflects the point position relative to the bbox:
|
||||
// left mid right
|
||||
// top 1001 1000 1010
|
||||
// mid 0001 0000 0010
|
||||
// bottom 0101 0100 0110
|
||||
function bitCode(p, bbox) {
|
||||
var code = 0;
|
||||
|
||||
if (p[0] < bbox[0]) code |= 1; // left
|
||||
else if (p[0] > bbox[2]) code |= 2; // right
|
||||
|
||||
if (p[1] < bbox[1]) code |= 4; // bottom
|
||||
else if (p[1] > bbox[3]) code |= 8; // top
|
||||
|
||||
return code;
|
||||
}
|
||||
153
main.js
153
main.js
|
|
@ -7,7 +7,7 @@
|
|||
// See also https://github.com/Azgaar/Fantasy-Map-Generator/issues/153
|
||||
|
||||
"use strict";
|
||||
const version = "1.3"; // generator version
|
||||
const version = "1.4"; // generator version
|
||||
document.title += " v" + version;
|
||||
|
||||
// if map version is not stored, clear localStorage and show a message
|
||||
|
|
@ -52,6 +52,7 @@ let trails = routes.append("g").attr("id", "trails");
|
|||
let searoutes = routes.append("g").attr("id", "searoutes");
|
||||
let temperature = viewbox.append("g").attr("id", "temperature");
|
||||
let coastline = viewbox.append("g").attr("id", "coastline");
|
||||
let ice = viewbox.append("g").attr("id", "ice").style("display", "none");
|
||||
let prec = viewbox.append("g").attr("id", "prec").style("display", "none");
|
||||
let population = viewbox.append("g").attr("id", "population");
|
||||
let labels = viewbox.append("g").attr("id", "labels");
|
||||
|
|
@ -220,7 +221,7 @@ function focusOn() {
|
|||
const url = new URL(window.location.href);
|
||||
const params = url.searchParams;
|
||||
|
||||
if (params.get("from") === "MFCG") {
|
||||
if (params.get("from") === "MFCG" && document.referrer) {
|
||||
if (params.get("seed").length === 13) {
|
||||
// show back burg from MFCG
|
||||
params.set("burg", params.get("seed").slice(-4));
|
||||
|
|
@ -313,12 +314,12 @@ function applyDefaultBiomesSystem() {
|
|||
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,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]),
|
||||
const biomesMartix = [ // hot ↔ cold [>19°C; <-4°C]; 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,10]),
|
||||
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,10,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]),
|
||||
new Uint8Array([5,6,6,6,6,6,6,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,10,10,10]),
|
||||
new Uint8Array([7,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,10,10,10])
|
||||
new Uint8Array([7,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,10,10])
|
||||
];
|
||||
|
||||
// parse icons weighted array into a simple array
|
||||
|
|
@ -527,7 +528,7 @@ function generate() {
|
|||
elevateLakes();
|
||||
Rivers.generate();
|
||||
defineBiomes();
|
||||
//drawSeaIce();
|
||||
drawIce();
|
||||
|
||||
rankCells();
|
||||
Cultures.generate();
|
||||
|
|
@ -701,8 +702,9 @@ function openNearSeaLakes() {
|
|||
// define map size and position based on template and random factor
|
||||
function defineMapSize() {
|
||||
const [size, latitude] = getSizeAndLatitude();
|
||||
if (!locked("mapSize")) mapSizeOutput.value = mapSizeInput.value = size;
|
||||
if (!locked("latitude")) latitudeOutput.value = latitudeInput.value = latitude;
|
||||
const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options
|
||||
if (randomize || !locked("mapSize")) mapSizeOutput.value = mapSizeInput.value = size;
|
||||
if (randomize || !locked("latitude")) latitudeOutput.value = latitudeInput.value = latitude;
|
||||
|
||||
function getSizeAndLatitude() {
|
||||
const template = document.getElementById("templateInput").value; // heightmap template
|
||||
|
|
@ -963,7 +965,7 @@ function drawCoastline() {
|
|||
let vchain = connectVertices(start, type);
|
||||
if (features[f].type === "lake") relax(vchain, 1.2);
|
||||
used[f] = 1;
|
||||
let points = vchain.map(v => vertices.p[v]);
|
||||
let points = clipPoly(vchain.map(v => vertices.p[v]), 1);
|
||||
const area = d3.polygonArea(points); // area with lakes/islands
|
||||
if (area > 0 && features[f].type === "lake") {
|
||||
points = points.reverse();
|
||||
|
|
@ -1056,7 +1058,6 @@ 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
|
||||
|
||||
|
|
@ -1075,7 +1076,6 @@ function reMarkFeatures() {
|
|||
else if (!cells.t[q] && cells.t[e] === 1) cells.t[q] = 2;
|
||||
}
|
||||
if (!cells.f[e] && land === eLand) {
|
||||
//if (!land && frozen !== temp[cells.g[e]] < -5) return;
|
||||
queue.push(e);
|
||||
cells.f[e] = i;
|
||||
cellNumber++;
|
||||
|
|
@ -1137,28 +1137,32 @@ function elevateLakes() {
|
|||
// assign biome id for each cell
|
||||
function defineBiomes() {
|
||||
console.time("defineBiomes");
|
||||
const cells = pack.cells, f = pack.features;
|
||||
const cells = pack.cells, f = pack.features, temp = grid.cells.temp, prec = grid.cells.prec;
|
||||
cells.biome = new Uint8Array(cells.i.length); // biomes array
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (f[cells.f[i]].group === "freshwater") cells.h[i] = 19; // de-elevate lakes
|
||||
const temp = grid.cells.temp[cells.g[i]]; // temperature
|
||||
if (f[cells.f[i]].group === "freshwater") cells.h[i] = 19; // de-elevate lakes; here to save some resources
|
||||
const t = temp[cells.g[i]]; // cell temperature
|
||||
const h = cells.h[i]; // cell height
|
||||
const m = h < 20 ? 0 : calculateMoisture(i); // cell moisture
|
||||
cells.biome[i] = getBiomeId(m, t, h);
|
||||
}
|
||||
|
||||
if (cells.h[i] < 20 && temp > -6) continue; // liquid water cells have biome 0
|
||||
let moist = grid.cells.prec[cells.g[i]];
|
||||
function calculateMoisture(i) {
|
||||
let moist = 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));
|
||||
cells.biome[i] = getBiomeId(moist, temp, cells.h[i]);
|
||||
const n = cells.c[i].filter(isLand).map(c => prec[cells.g[c]]).concat([moist]);
|
||||
return rn(4 + d3.mean(n));
|
||||
}
|
||||
|
||||
console.timeEnd("defineBiomes");
|
||||
}
|
||||
|
||||
// assign biome id to a cell
|
||||
function getBiomeId(moisture, temperature, height) {
|
||||
if (temperature < -5) return 11; // permafrost biome, including sea ice
|
||||
if (height < 20) return 0; // liquid water cells have marine biome
|
||||
if (moisture > 40 && height < 25 || moisture > 24 && height > 24) return 12; // wetland biome
|
||||
if (height < 20) return 0; // marine biome: liquid water cells
|
||||
if (moisture > 40 && temperature > -2 && (height < 25 || moisture > 24 && height > 24)) return 12; // wetland biome
|
||||
const m = Math.min(moisture / 5 | 0, 4); // moisture band from 0 to 4
|
||||
const t = Math.min(Math.max(20 - temperature, 0), 25); // temparature band from 0 to 25
|
||||
return biomesData.biomesMartix[m][t];
|
||||
|
|
@ -1211,20 +1215,15 @@ function addMarkers(number = 1) {
|
|||
void function addVolcanoes() {
|
||||
let mounts = Array.from(cells.i).filter(i => cells.h[i] > 70).sort((a, b) => cells.h[b] - cells.h[a]);
|
||||
let count = mounts.length < 10 ? 0 : Math.ceil(mounts.length / 300 * number);
|
||||
if (count) addMarker("volcano", "🌋", 52, 52, 17.5);
|
||||
if (count) addMarker("volcano", "🌋", 52, 50, 13);
|
||||
|
||||
while (count && mounts.length) {
|
||||
const cell = mounts.splice(biased(0, mounts.length-1, 5), 1);
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
const id = getNextId("markerElement");
|
||||
markers.append("use").attr("id", id).attr("data-cell", cell)
|
||||
.attr("xlink:href", "#marker_volcano").attr("data-id", "#marker_volcano")
|
||||
.attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
|
||||
.attr("data-size", 1).attr("width", 30).attr("height", 30);
|
||||
const height = getFriendlyHeight([x, y]);
|
||||
const id = appendMarker(cell, "volcano");
|
||||
const proper = Names.getCulture(cells.culture[cell]);
|
||||
const name = P(.3) ? "Mount " + proper : Math.random() > .3 ? proper + " Volcano" : proper;
|
||||
notes.push({id, name, legend:`Active volcano. Height: ${height}`});
|
||||
notes.push({id, name, legend:`Active volcano. Height: ${getFriendlyHeight([x, y])}`});
|
||||
count--;
|
||||
}
|
||||
}()
|
||||
|
|
@ -1232,17 +1231,11 @@ function addMarkers(number = 1) {
|
|||
void function addHotSprings() {
|
||||
let springs = Array.from(cells.i).filter(i => cells.h[i] > 50).sort((a, b) => cells.h[b]-cells.h[a]);
|
||||
let count = springs.length < 30 ? 0 : Math.ceil(springs.length / 1000 * number);
|
||||
if (count) addMarker("hot_springs", "♨", 50, 50, 19.5);
|
||||
if (count) addMarker("hot_springs", "♨️", 50, 52, 12.5);
|
||||
|
||||
while (count && springs.length) {
|
||||
const cell = springs.splice(biased(1, springs.length-1, 3), 1);
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
const id = getNextId("markerElement");
|
||||
markers.append("use").attr("id", id)
|
||||
.attr("xlink:href", "#marker_hot_springs").attr("data-id", "#marker_hot_springs")
|
||||
.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 id = appendMarker(cell, "hot_springs");
|
||||
const proper = Names.getCulture(cells.culture[cell]);
|
||||
const temp = convertTemperature(gauss(30,15,20,100));
|
||||
notes.push({id, name: proper + " Hot Springs", legend:`A hot springs area. Temperature: ${temp}`});
|
||||
|
|
@ -1255,17 +1248,12 @@ function addMarkers(number = 1) {
|
|||
let count = !hills.length ? 0 : Math.ceil(hills.length / 7 * number);
|
||||
if (!count) return;
|
||||
|
||||
addMarker("mine", "⚒", 50, 50, 20);
|
||||
addMarker("mine", "⛏️", 48, 50, 13.5);
|
||||
const resources = {"salt":5, "gold":2, "silver":4, "copper":2, "iron":3, "lead":1, "tin":1};
|
||||
|
||||
while (count && hills.length) {
|
||||
const cell = hills.splice(Math.floor(Math.random() * hills.length), 1);
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
const id = getNextId("markerElement");
|
||||
markers.append("use").attr("id", id)
|
||||
.attr("xlink:href", "#marker_mine").attr("data-id", "#marker_mine")
|
||||
.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 id = appendMarker(cell, "mine");
|
||||
const resource = rw(resources);
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const name = `${burg.name} — ${resource} mining town`;
|
||||
|
|
@ -1285,17 +1273,11 @@ function addMarkers(number = 1) {
|
|||
.sort((a, b) => (cells.road[b] + cells.fl[b] / 10) - (cells.road[a] + cells.fl[a] / 10));
|
||||
|
||||
let count = !bridges.length ? 0 : Math.ceil(bridges.length / 12 * number);
|
||||
if (count) addMarker("bridge", "🌉", 50, 50, 16.5);
|
||||
if (count) addMarker("bridge", "🌉", 50, 50, 14);
|
||||
|
||||
while (count && bridges.length) {
|
||||
const cell = bridges.splice(0, 1);
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
const id = getNextId("markerElement");
|
||||
markers.append("use").attr("id", id)
|
||||
.attr("xlink:href", "#marker_bridge").attr("data-id", "#marker_bridge")
|
||||
.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 id = appendMarker(cell, "bridge");
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
|
||||
const riverName = river ? `${river.name} ${river.type}` : "river";
|
||||
|
|
@ -1310,7 +1292,7 @@ function addMarkers(number = 1) {
|
|||
let taverns = Array.from(cells.i).filter(i => cells.crossroad[i] && cells.h[i] >= 20 && cells.road[i] > maxRoad);
|
||||
if (!taverns.length) return;
|
||||
const count = Math.ceil(4 * number);
|
||||
addMarker("inn", "🍻", 50, 50, 17.5);
|
||||
addMarker("inn", "🍻", 50, 50, 14.5);
|
||||
|
||||
const color = ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"];
|
||||
const animal = ["Antelope", "Ape", "Badger", "Bear", "Beaver", "Bison", "Boar", "Buffalo", "Cat", "Crane", "Crocodile", "Crow", "Deer", "Dog", "Eagle", "Elk", "Fox", "Goat", "Goose", "Hare", "Hawk", "Heron", "Horse", "Hyena", "Ibis", "Jackal", "Jaguar", "Lark", "Leopard", "Lion", "Mantis", "Marten", "Moose", "Mule", "Narwhal", "Owl", "Panther", "Rat", "Raven", "Rook", "Scorpion", "Shark", "Sheep", "Snake", "Spider", "Swan", "Tiger", "Turtle", "Wolf", "Wolverine", "Camel", "Falcon", "Hound", "Ox"];
|
||||
|
|
@ -1318,14 +1300,7 @@ function addMarkers(number = 1) {
|
|||
|
||||
for (let i=0; i < taverns.length && i < count; i++) {
|
||||
const cell = taverns.splice(Math.floor(Math.random() * taverns.length), 1);
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
const id = getNextId("markerElement");
|
||||
|
||||
markers.append("use").attr("id", id)
|
||||
.attr("xlink:href", "#marker_inn").attr("data-id", "#marker_inn")
|
||||
.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 id = appendMarker(cell, "inn");
|
||||
const type = P(.3) ? "inn" : "tavern";
|
||||
const name = P(.5) ? ra(color) + " " + ra(animal) : P(.6) ? ra(adj) + " " + ra(animal) : ra(adj) + " " + capitalize(type);
|
||||
notes.push({id, name: "The " + name, legend:`A big and famous roadside ${type}`});
|
||||
|
|
@ -1340,14 +1315,7 @@ function addMarkers(number = 1) {
|
|||
|
||||
for (let i=0; i < lighthouses.length && i < count; i++) {
|
||||
const cell = lighthouses[i][0], vertex = lighthouses[i][1];
|
||||
const x = pack.vertices.p[vertex][0], y = pack.vertices.p[vertex][1];
|
||||
const id = getNextId("markerElement");
|
||||
|
||||
markers.append("use").attr("id", id)
|
||||
.attr("xlink:href", "#marker_lighthouse").attr("data-id", "#marker_lighthouse")
|
||||
.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 id = appendMarker(cell, "lighthouse");
|
||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||
notes.push({id, name: getAdjective(proper) + " Lighthouse" + name, legend:`A lighthouse to keep the navigation safe`});
|
||||
}
|
||||
|
|
@ -1360,14 +1328,7 @@ function addMarkers(number = 1) {
|
|||
|
||||
for (let i=0; i < waterfalls.length && i < count; i++) {
|
||||
const cell = waterfalls[i];
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
const id = getNextId("markerElement");
|
||||
|
||||
markers.append("use").attr("id", id)
|
||||
.attr("xlink:href", "#marker_waterfall").attr("data-id", "#marker_waterfall")
|
||||
.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 id = appendMarker(cell, "waterfall");
|
||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||
notes.push({id, name: getAdjective(proper) + " Waterfall" + name, legend:`An extremely beautiful waterfall`});
|
||||
}
|
||||
|
|
@ -1376,17 +1337,11 @@ function addMarkers(number = 1) {
|
|||
void function addBattlefields() {
|
||||
let battlefields = Array.from(cells.i).filter(i => cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25);
|
||||
let count = battlefields.length < 100 ? 0 : Math.ceil(battlefields.length / 500 * number);
|
||||
if (count) addMarker("battlefield", "⚔", 50, 50, 20);
|
||||
if (count) addMarker("battlefield", "⚔️", 50, 52, 12);
|
||||
|
||||
while (count && battlefields.length) {
|
||||
const cell = battlefields.splice(Math.floor(Math.random() * battlefields.length), 1);
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
const id = getNextId("markerElement");
|
||||
markers.append("use").attr("id", id)
|
||||
.attr("xlink:href", "#marker_battlefield").attr("data-id", "#marker_battlefield")
|
||||
.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 id = appendMarker(cell, "battlefield");
|
||||
const campaign = ra(states[cells.state[cell]].campaigns);
|
||||
const date = generateDate(campaign.start, campaign.end);
|
||||
const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
|
||||
|
|
@ -1407,6 +1362,19 @@ function addMarkers(number = 1) {
|
|||
.attr("font-size", size+"px").attr("dominant-baseline", "central").text(icon);
|
||||
}
|
||||
|
||||
function appendMarker(cell, type) {
|
||||
const x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
const id = getNextId("markerElement");
|
||||
const name = "#marker_" + type;
|
||||
|
||||
markers.append("use").attr("id", id)
|
||||
.attr("xlink:href", name).attr("data-id", name)
|
||||
.attr("data-x", x).attr("data-y", y).attr("x", x - 15).attr("y", y - 30)
|
||||
.attr("data-size", 1).attr("width", 30).attr("height", 30);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
console.timeEnd("addMarkers");
|
||||
}
|
||||
|
||||
|
|
@ -1593,14 +1561,11 @@ function addZones(number = 1) {
|
|||
}
|
||||
|
||||
function addEruption() {
|
||||
const volcanoes = [];
|
||||
markers.selectAll("use[data-id='#marker_volcano']").each(function() {
|
||||
volcanoes.push(this.dataset.cell);
|
||||
});
|
||||
if (!volcanoes.length) return;
|
||||
const volcano = document.getElementById("markers").querySelector("use[data-id='#marker_volcano']");
|
||||
if (!volcano) return;
|
||||
|
||||
const cell = +ra(volcanoes);
|
||||
const id = markers.select("use[data-cell='"+cell+"']").attr("id");
|
||||
const x = +volcano.dataset.x, y = +volcano.dataset.y, cell = findCell(x, y);
|
||||
const id = volcano.id;
|
||||
const note = notes.filter(n => n.id === id);
|
||||
|
||||
if (note[0]) note[0].legend = note[0].legend.replace("Active volcano", "Erupting volcano");
|
||||
|
|
@ -1613,7 +1578,7 @@ function addZones(number = 1) {
|
|||
cellsArray.push(q);
|
||||
if (cellsArray.length > power) break;
|
||||
cells.c[q].forEach(e => {
|
||||
if (used[e]) return;
|
||||
if (used[e] || cells.h[e] < 20) return;
|
||||
used[e] = 1;
|
||||
queue.push(e);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@
|
|||
} 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);
|
||||
b.population = rn(Math.max((cells.s[i] + cells.road[i] / 2) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
|
||||
if (b.capital) b.population = rn(b.population * 1.3, 3); // increase capital population
|
||||
|
||||
if (b.port) {
|
||||
|
|
@ -420,7 +420,7 @@
|
|||
const passableLake = features[cells.f[c]].type === "lake" && features[cells.f[c]].cells < maxLake;
|
||||
if (cells.b[c] || (cells.state[c] !== state && !passableLake)) {hull.add(cells.v[q][d]); return;}
|
||||
const nC = cells.c[c].filter(n => cells.state[n] === state);
|
||||
const intersected = intersect(nQ, nC).length
|
||||
const intersected = common(nQ, nC).length
|
||||
if (hull.size > 20 && !intersected && !passableLake) {hull.add(cells.v[q][d]); return;}
|
||||
if (used[c]) return;
|
||||
used[c] = 1;
|
||||
|
|
@ -810,7 +810,7 @@
|
|||
});
|
||||
|
||||
const monarchy = ["Duchy", "Grand Duchy", "Principality", "Kingdom", "Empire"]; // per expansionism tier
|
||||
const republic = {Republic:70, Federation:2, Oligarchy:2, Tetrarchy:1, Triumvirate:1, Diarchy:1, "Trade Company":3}; // weighted random
|
||||
const republic = {Republic:75, Federation:4, Oligarchy:2, Tetrarchy:1, Triumvirate:1, Diarchy:1, "Trade Company":4, Junta:1}; // weighted random
|
||||
const union = {Union:3, League:4, Confederation:1, "United Kingdom":1, "United Republic":1, "United Provinces":2, Commonwealth:1, Heptarchy:1}; // weighted random
|
||||
|
||||
for (const s of states) {
|
||||
|
|
@ -870,11 +870,13 @@
|
|||
if (s.form === "Union") return rw(union);
|
||||
|
||||
if (s.form === "Theocracy") {
|
||||
// default name is "Theocracy", some culture bases have special names
|
||||
if ([0, 1, 2, 3, 4, 6, 8, 9, 13, 15, 20].includes(base)) return "Diocese"; // Euporean
|
||||
if ([7, 5].includes(base)) return "Eparchy"; // Greek, Ruthenian
|
||||
if ([21, 16].includes(base)) return "Imamah"; // Nigerian, Turkish
|
||||
if ([18, 17, 28].includes(base)) return "Caliphate"; // Arabic, Berber, Swahili
|
||||
// default name is "Theocracy"
|
||||
if (P(.5) && [0, 1, 2, 3, 4, 6, 8, 9, 13, 15, 20].includes(base)) return "Diocese"; // Euporean
|
||||
if (P(.9) && [7, 5].includes(base)) return "Eparchy"; // Greek, Ruthenian
|
||||
if (P(.9) && [21, 16].includes(base)) return "Imamah"; // Nigerian, Turkish
|
||||
if (P(.8) && [18, 17, 28].includes(base)) return "Caliphate"; // Arabic, Berber, Swahili
|
||||
if (P(.02)) return "Thearchy"; // "Thearchy" in very rare case
|
||||
if (P(.05)) return "See"; // "See" in rare case
|
||||
return "Theocracy";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,11 +208,11 @@
|
|||
|
||||
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}
|
||||
{icon: "⚔️", name:"infantry", rural:.25, urban:.2, crew:1, power:1, type:"melee", separate:0},
|
||||
{icon: "🏹", name:"archers", rural:.12, urban:.2, crew:1, power:1, type:"ranged", separate:0},
|
||||
{icon: "🐴", name:"cavalry", rural:.12, urban:.03, crew:3, power:4, type:"mounted", separate:0},
|
||||
{icon: "💣", name:"artillery", rural:0, urban:.03, crew:8, power:12, type:"machinery", separate:0},
|
||||
{icon: "🌊", name:"fleet", rural:0, urban:.015, crew:100, power:50, type:"naval", separate:1}
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -271,16 +271,11 @@
|
|||
// 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 "💣";
|
||||
if (type === "armored") return "🐢";
|
||||
if (type === "aviation") return "🦅";
|
||||
if (type === "magical") return "🔮";
|
||||
else return "⚔️";
|
||||
if (!Object.values(r.u).length) return "🔰"; // regiment without troops
|
||||
if (cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]].capital) return "👑"; // "Royal" regiment based in capital
|
||||
const mainUnit = Object.entries(r.u).sort((a,b) => b[1]-a[1])[0][0]; // unit with more troops in regiment
|
||||
const unit = options.military.find(u => u.name === mainUnit);
|
||||
return unit.icon;
|
||||
}
|
||||
|
||||
const generateNote = function(r, s) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
if (outline === "none") return;
|
||||
console.time("drawOceanLayers");
|
||||
|
||||
lineGen.curve(d3.curveBasisClosed);
|
||||
cells = grid.cells, pointsN = grid.cells.i.length, vertices = grid.vertices;
|
||||
const limits = outline === "random" ? randomizeOutline() : outline.split(",").map(s => +s);
|
||||
markupOcean(limits);
|
||||
|
|
@ -29,7 +30,8 @@
|
|||
const chain = connectVertices(start, t); // vertices chain to form a path
|
||||
const relaxation = 1 + t * -2; // select only n-th point
|
||||
const relaxed = chain.filter((v, i) => i % relaxation === 0 || vertices.c[v].some(c => c >= pointsN));
|
||||
if (relaxed.length >= 3) chains.push([t, relaxed.map(v => vertices.p[v])]);
|
||||
const points = clipPoly(relaxed.map(v => vertices.p[v]), 1);
|
||||
if (relaxed.length >= 3) chains.push([t, points]);
|
||||
}
|
||||
|
||||
for (const t of limits) {
|
||||
|
|
@ -57,8 +59,8 @@
|
|||
return limits;
|
||||
}
|
||||
|
||||
// Define grid ocean cells type based on distance form land
|
||||
function markupOcean(limits) {
|
||||
// Define ocean cells type based on distance form land
|
||||
for (let t = -2; t >= limits[0]-1; t--) {
|
||||
for (let i = 0; i < pointsN; i++) {
|
||||
if (cells.t[i] !== t+1) continue;
|
||||
|
|
|
|||
|
|
@ -296,6 +296,33 @@ async function saveMap() {
|
|||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||
}
|
||||
|
||||
// Send .map file to server [test function]
|
||||
async function sendToURL(URL = "http://localhost:8000/upload.php") {
|
||||
if (customization) {tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return;}
|
||||
closeDialogs("#alert");
|
||||
|
||||
const blob = await getMapData();
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", URL, true);
|
||||
//xhr.setRequestHeader("Content-Type", "blob"); // set request header
|
||||
xhr.onload = e => console.log("onload", e.responseText);
|
||||
xhr.onreadystatechange = () => {if (xhr.readyState === 4 && xhr.status === 200) tip(`File is sent to cloud storage`, true, "success", 7000);}
|
||||
xhr.send(blob);
|
||||
}
|
||||
|
||||
// Load .map file from server [test function]
|
||||
async function loadFromCloud() {
|
||||
ldb.get("lastMap", blob => {
|
||||
if (blob) {
|
||||
loadMapPrompt(blob);
|
||||
} else {
|
||||
tip("No map stored. Save map to storage first", true, "error", 2000);
|
||||
console.error("No map stored");
|
||||
}
|
||||
});
|
||||
uploadMap(blob);
|
||||
}
|
||||
|
||||
function saveGeoJSON_Cells() {
|
||||
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
|
||||
const cells = pack.cells, v = pack.vertices;
|
||||
|
|
@ -617,6 +644,7 @@ function parseLoadedData(data) {
|
|||
texture = viewbox.select("#texture");
|
||||
terrs = viewbox.select("#terrs");
|
||||
biomes = viewbox.select("#biomes");
|
||||
ice = viewbox.select("#ice");
|
||||
cells = viewbox.select("#cells");
|
||||
gridOverlay = viewbox.select("#gridOverlay");
|
||||
coordinates = viewbox.select("#coordinates");
|
||||
|
|
@ -953,12 +981,34 @@ function parseLoadedData(data) {
|
|||
Military.generate();
|
||||
}
|
||||
|
||||
if (version < 1.35) {
|
||||
if (version < 1.4) {
|
||||
// v 1.35 added dry lakes
|
||||
if (!lakes.select("#dry").size()) {
|
||||
lakes.append("g").attr("id", "dry");
|
||||
lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", .7).attr("filter", null);
|
||||
}
|
||||
|
||||
// v 1.4 added ice layer
|
||||
ice = viewbox.insert("g", "#coastline").attr("id", "ice").style("display", "none");
|
||||
ice.attr("opacity", null).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)");
|
||||
drawIce();
|
||||
|
||||
// v 1.4 added icon and power attributes for units
|
||||
for (const unit of options.military) {
|
||||
if (!unit.icon) unit.icon = getUnitIcon(unit.type);
|
||||
if (!unit.power) unit.power = unit.crew;
|
||||
}
|
||||
|
||||
function getUnitIcon(type) {
|
||||
if (type === "naval") return "🌊";
|
||||
if (type === "ranged") return "🏹";
|
||||
if (type === "mounted") return "🐴";
|
||||
if (type === "machinery") return "💣";
|
||||
if (type === "armored") return "🐢";
|
||||
if (type === "aviation") return "🦅";
|
||||
if (type === "magical") return "🔮";
|
||||
else return "⚔️";
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
|
|
|||
50
modules/ui/battle-screen.js
Normal file
50
modules/ui/battle-screen.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"use strict";
|
||||
function showBattleScreen(attacker, defender) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
|
||||
const battle = {name:"Battle", attackers:[attacker], defenders:[defender]};
|
||||
const battleAttackers = document.getElementById("battleAttackers");
|
||||
const battleDefenders = document.getElementById("battleDefenders");
|
||||
addHeaders();
|
||||
addRegiment(battleAttackers, attacker);
|
||||
addRegiment(battleDefenders, defender);
|
||||
|
||||
$("#battleScreen").dialog({
|
||||
title: battle.name, resizable: false, width: fitContent(), close: closeBattleScreen,
|
||||
position: {my: "center", at: "center", of: "#map"}
|
||||
});
|
||||
|
||||
if (modules.showBattleScreen) return;
|
||||
modules.showBattleScreen = true;
|
||||
|
||||
// add listeners
|
||||
//document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
|
||||
|
||||
function addHeaders() {
|
||||
document.getElementById("battleScreen").querySelectorAll("th").forEach(el => el.remove());
|
||||
const attackers = battleAttackers.querySelector("tr");
|
||||
const defenders = battleDefenders.querySelector("tr");
|
||||
let headers = "<th></th>";
|
||||
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
headers += `<th data-tip="${label}">${u.icon}</th>`;
|
||||
}
|
||||
|
||||
headers += "<th>Total</th>";
|
||||
attackers.insertAdjacentHTML("beforebegin", headers);
|
||||
defenders.insertAdjacentHTML("beforebegin", headers);
|
||||
}
|
||||
|
||||
function addRegiment(div, regiment) {
|
||||
const reg = document.createElement("div");
|
||||
reg.innerHTML = regiment.name;
|
||||
div.append(reg);
|
||||
}
|
||||
|
||||
function closeBattleScreen() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -215,6 +215,8 @@ function editBiomes() {
|
|||
|
||||
function addCustomBiome() {
|
||||
const b = biomesData, i = biomesData.i.length;
|
||||
if (i > 254) {tip("Maximum number of biomes reached (255), data cleansing is required", false, "error"); return;}
|
||||
|
||||
b.i.push(i);
|
||||
b.color.push(getRandomColor());
|
||||
b.habitability.push(50);
|
||||
|
|
|
|||
|
|
@ -294,13 +294,14 @@ function editBurg(id) {
|
|||
|
||||
function openMFCG(seed) {
|
||||
if (!seed && burg.MFCGlink) {openURL(burg.MFCGlink); return;}
|
||||
const cells = pack.cells;
|
||||
const name = elSelected.text();
|
||||
const size = Math.max(Math.min(rn(burg.population), 65), 6);
|
||||
|
||||
const s = burg.MFCG || defSeed;
|
||||
const cell = burg.cell;
|
||||
const hub = +pack.cells.road[cell] > 50;
|
||||
const river = pack.cells.r[cell] ? 1 : 0;
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
|
|
@ -309,8 +310,31 @@ function editBurg(id) {
|
|||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
|
||||
const sea = coast && cells.haven[burg.cell] ? getSeaDirections(burg.cell) : "";
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180 / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
let norm = rn(normalize(deg, 0, 360) * 8) / 4;
|
||||
if (norm === 2) norm = 0;
|
||||
return "sea="+norm;
|
||||
// debug.selectAll("*").remove();
|
||||
// pack.burgs.filter(b => b.port).forEach(b => {
|
||||
// var p1 = pack.cells.p[b.cell];
|
||||
// var p2 = pack.cells.p[pack.cells.haven[b.cell]];
|
||||
// var deg = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180 / Math.PI - 90;
|
||||
// if (deg < 0) deg += 360;
|
||||
// var norm = rn(normalize(deg, 0, 360) * 8) / 4;
|
||||
// if (norm === 2) norm = 0;
|
||||
// debug.append("line").attr("x1", p1[0]).attr("y1", p1[1]).attr("x2", p2[0]).attr("y2", p2[1]).attr("stroke", "red").attr("stroke-width", .2);
|
||||
// debug.append("circle").attr("cx", b.x).attr("cy", b.y).attr("r", .4);
|
||||
// debug.append("text").attr("x", b.x+1).attr("y", b.y).attr("font-size", 2).text(rn(norm, 2));
|
||||
// });
|
||||
}
|
||||
|
||||
const site = "http://fantasycities.watabou.ru/";
|
||||
const url = `${site}?name=${name}&size=${size}&seed=${s}&hub=${hub}&random=0&continuous=0&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}`;
|
||||
const url = `${site}?name=${name}&size=${size}&seed=${s}&hub=${hub}&random=0&continuous=0&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
openURL(url);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ function clicked() {
|
|||
else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel();
|
||||
else if (grand.id === "burgLabels") editBurg();
|
||||
else if (grand.id === "burgIcons") editBurg();
|
||||
else if (parent.id === "ice") editIce();
|
||||
else if (parent.id === "terrain") editReliefIcon();
|
||||
else if (parent.id === "markers") editMarker();
|
||||
else if (grand.id === "coastline") editCoastline();
|
||||
|
|
@ -581,4 +582,43 @@ function highlightElement(element) {
|
|||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
if (scale >= 2) zoomTo(x, y, scale, 1600);
|
||||
}
|
||||
|
||||
function selectIcon(initial, callback) {
|
||||
if (!callback) return;
|
||||
$("#iconSelector").dialog();
|
||||
|
||||
const table = document.getElementById("iconTable");
|
||||
const input = document.getElementById("iconInput");
|
||||
input.value = initial;
|
||||
|
||||
if (!table.innerHTML) {
|
||||
const icons = ["⚔️","🏹","🐴","💣","🌊","🎯","⚓","🔮","📯","⚒️","🛡️","👑","⚜️",
|
||||
"☠️","🎆","🗡️","🔪","⛏️","🔥","🩸","💧","🐾","🎪","🏰","🏯","⛓️","❤️","💘","💜","📜","🔔",
|
||||
"🔱","💎","🌈","🌠","✨","💥","☀️","🌙","⚡","❄️","♨️","🎲","🚨","🌉","🗻","🌋","🧱",
|
||||
"⚖️","✂️","🎵","👗","🎻","🎨","🎭","⛲","💉","📖","📕","🎁","💍","⏳","🕸️","⚗️","☣️","☢️",
|
||||
"🔰","🎖️","🚩","🏳️","🏴","💪","✊","👊","🤜","🤝","🙏","🧙","🧙♀️","💂","🤴","🧛","🧟","🧞","🧝","👼",
|
||||
"👻","👺","👹","🦄","🐲","🐉","🐎","🦓","🐺","🦊","🐱","🐈","🦁","🐯","🐅","🐆","🐕","🦌","🐵","🐒","🦍",
|
||||
"🦅","🕊️","🐓","🦇","🦜","🐦","🦉","🐮","🐄","🐂","🐃","🐷","🐖","🐗","🐏","🐑","🐐","🐫","🦒","🐘","🦏","🐭","🐁","🐀",
|
||||
"🐹","🐰","🐇","🦔","🐸","🐊","🐢","🦎","🐍","🐳","🐬","🦈","🐠","🐙","🦑","🐌","🦋","🐜","🐝","🐞","🦗","🕷️","🦂","🦀",
|
||||
"🌳","🌲","🎄","🌴","🍂","🍁","🌵","☘️","🍀","🌿","🌱","🌾","🍄","🌽","🌸","🌹","🌻",
|
||||
"🍒","🍏","🍇","🍉","🍅","🍓","🥔","🥕","🥩","🍗","🍞","🍻","🍺","🍲","🍷"
|
||||
];
|
||||
|
||||
let row = "";
|
||||
for (let i=0; i < icons.length; i++) {
|
||||
if (i%17 === 0) row = table.insertRow(i/17|0);
|
||||
const cell = row.insertCell(i%17);
|
||||
cell.innerHTML = icons[i];
|
||||
}
|
||||
}
|
||||
|
||||
table.onclick = e => {if (e.target.tagName === "TD") {input.value = e.target.innerHTML; callback(input.value)}};
|
||||
table.onmouseover = e => {if (e.target.tagName === "TD") tip(`Click to select ${e.target.innerHTML} icon`)};
|
||||
|
||||
$("#iconSelector").dialog({width: fitContent(), title: "Select Icon",
|
||||
buttons: {
|
||||
Apply: function() {callback(input.value||"⠀"); $(this).dialog("close")},
|
||||
Close: function() {callback(initial); $(this).dialog("close")}}
|
||||
});
|
||||
}
|
||||
|
|
@ -106,6 +106,7 @@ function showMapTooltip(point, e, i, g) {
|
|||
if (group === "lakes" && !land) {tip(`${capitalize(subgroup)} lake. Click to edit`); return;}
|
||||
if (group === "coastline") {tip("Click to edit the coastline"); return;}
|
||||
if (group === "zones") {tip(path[path.length-8].dataset.description); return;}
|
||||
if (group === "ice") {tip("Click to edit the Ice"); return;}
|
||||
|
||||
// covering elements
|
||||
if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: "+ getFriendlyPrecipitation(i)); else
|
||||
|
|
@ -403,6 +404,7 @@ document.addEventListener("keyup", event => {
|
|||
else if (key === 85) toggleRoutes(); // "U" to toggle Routes layer
|
||||
else if (key === 84) toggleTemp(); // "T" to toggle Temperature layer
|
||||
else if (key === 78) togglePopulation(); // "N" to toggle Population layer
|
||||
else if (key === 74) toggleIce(); // "J" to toggle Ice layer
|
||||
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
|
||||
|
|
|
|||
|
|
@ -965,7 +965,7 @@ function editHeightmap() {
|
|||
closeDialogs("#imageConverter");
|
||||
|
||||
$("#imageConverter").dialog({
|
||||
title: "Image Converter", minHeight: "auto", width: "19.5em", resizable: false,
|
||||
title: "Image Converter", maxHeight: svgHeight*.75, minHeight: "auto", width: "19.5em", resizable: false,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg"},
|
||||
beforeClose: closeImageConverter
|
||||
});
|
||||
|
|
|
|||
105
modules/ui/ice-editor.js
Normal file
105
modules/ui/ice-editor.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
"use strict";
|
||||
function editIce() {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
if (!layerIsOn("toggleIce")) toggleIce();
|
||||
|
||||
elSelected = d3.select(d3.event.target);
|
||||
const type = elSelected.attr("type") ? "Glacier" : "Iceberg";
|
||||
document.getElementById("iceRandomize").style.display = type === "Glacier" ? "none" : "inline-block";
|
||||
document.getElementById("iceSize").style.display = type === "Glacier" ? "none" : "inline-block";
|
||||
if (type === "Iceberg") document.getElementById("iceSize").value = +elSelected.attr("size");
|
||||
ice.selectAll("*").classed("draggable", true).call(d3.drag().on("drag", dragElement));
|
||||
|
||||
$("#iceEditor").dialog({
|
||||
title: "Edit "+type, resizable: false,
|
||||
position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"},
|
||||
close: closeEditor
|
||||
});
|
||||
|
||||
if (modules.editIce) return;
|
||||
modules.editIce = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("iceEditStyle").addEventListener("click", () => editStyle("ice"));
|
||||
document.getElementById("iceRandomize").addEventListener("click", randomizeShape);
|
||||
document.getElementById("iceSize").addEventListener("input", changeSize);
|
||||
document.getElementById("iceNew").addEventListener("click", toggleAdd);
|
||||
document.getElementById("iceRemove").addEventListener("click", removeIce);
|
||||
|
||||
function randomizeShape() {
|
||||
const c = grid.points[+elSelected.attr("cell")];
|
||||
const s = +elSelected.attr("size");
|
||||
const i = ra(grid.cells.i), cn = grid.points[i];
|
||||
const poly = getGridPolygon(i).map(p => [p[0]-cn[0], p[1]-cn[1]]);
|
||||
const points = poly.map(p => [rn(c[0] + p[0] * s, 2), rn(c[1] + p[1] * s, 2)]);
|
||||
elSelected.attr("points", points);
|
||||
}
|
||||
|
||||
function changeSize() {
|
||||
const c = grid.points[+elSelected.attr("cell")];
|
||||
const s = +elSelected.attr("size");
|
||||
const flat = elSelected.attr("points").split(",").map(el => +el);
|
||||
const pairs = [];
|
||||
while (flat.length) pairs.push(flat.splice(0,2));
|
||||
const poly = pairs.map(p => [(p[0]-c[0]) / s, (p[1]-c[1]) / s]);
|
||||
const size = +this.value;
|
||||
const points = poly.map(p => [rn(c[0] + p[0] * size, 2), rn(c[1] + p[1] * size, 2)]);
|
||||
elSelected.attr("points", points).attr("size", size);
|
||||
}
|
||||
|
||||
function toggleAdd() {
|
||||
document.getElementById("iceNew").classList.toggle("pressed");
|
||||
if (document.getElementById("iceNew").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", addIcebergOnClick);
|
||||
tip("Click on map to create an iceberg. Hold Shift to add multiple", true);
|
||||
} else {
|
||||
clearMainTip();
|
||||
viewbox.on("click", clicked).style("cursor", "default");
|
||||
}
|
||||
}
|
||||
|
||||
function addIcebergOnClick() {
|
||||
const point = d3.mouse(this);
|
||||
const i = findGridCell(point[0], point[1]);
|
||||
const c = grid.points[i];
|
||||
const s = +document.getElementById("iceSize").value;
|
||||
|
||||
const points = getGridPolygon(i).map(p => [(p[0] + (c[0]-p[0]) / s)|0, (p[1] + (c[1]-p[1]) / s)|0]);
|
||||
const iceberg = ice.append("polygon").attr("points", points).attr("cell", i).attr("size", s);
|
||||
iceberg.call(d3.drag().on("drag", dragElement));
|
||||
if (d3.event.shiftKey === false) toggleAdd();
|
||||
}
|
||||
|
||||
function removeIce() {
|
||||
const type = elSelected.attr("type") ? "Glacier" : "Iceberg";
|
||||
alertMessage.innerHTML = `Are you sure you want to remove the ${type}?`;
|
||||
$("#alert").dialog({resizable: false, title: "Remove "+type,
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
elSelected.remove();
|
||||
$("#iceEditor").dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function dragElement() {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const dx = +tr[0] - d3.event.x, dy = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const x = d3.event.x, y = d3.event.y;
|
||||
this.setAttribute("transform", `translate(${(dx+x)},${(dy+y)})`);
|
||||
});
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
ice.selectAll("*").classed("draggable", false).call(d3.drag().on("drag", null));
|
||||
clearMainTip();
|
||||
iceNew.classList.remove("pressed");
|
||||
unselect();
|
||||
}
|
||||
}
|
||||
|
|
@ -32,10 +32,10 @@ function getDefaultPresets() {
|
|||
"cultural": ["toggleBorders", "toggleCultures", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||
"religions": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleReligions", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||
"provinces": ["toggleBorders", "toggleIcons", "toggleProvinces", "toggleRivers", "toggleScaleBar"],
|
||||
"biomes": ["toggleBiomes", "toggleRivers", "toggleScaleBar"],
|
||||
"biomes": ["toggleBiomes", "toggleIce", "toggleRivers", "toggleScaleBar"],
|
||||
"heightmap": ["toggleHeight", "toggleRivers"],
|
||||
"physical": ["toggleCoordinates", "toggleHeight", "toggleRivers", "toggleScaleBar"],
|
||||
"poi": ["toggleBorders", "toggleHeight", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||
"poi": ["toggleBorders", "toggleHeight", "toggleIce", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||
"military": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleMilitary", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"],
|
||||
"landmass": ["toggleScaleBar"]
|
||||
}
|
||||
|
|
@ -345,7 +345,7 @@ function drawBiomes() {
|
|||
const edgeVerticle = cells.v[i].find(v => vertices.c[v].some(i => cells.biome[i] !== b));
|
||||
const chain = connectVertices(edgeVerticle, b);
|
||||
if (chain.length < 3) continue;
|
||||
const points = chain.map(v => vertices.p[v]);
|
||||
const points = clipPoly(chain.map(v => vertices.p[v]), 1);
|
||||
paths[b] += "M" + points.join("L") + "Z";
|
||||
}
|
||||
|
||||
|
|
@ -456,38 +456,77 @@ function drawCells() {
|
|||
cells.append("path").attr("d", path);
|
||||
}
|
||||
|
||||
function drawSeaIce() {
|
||||
const seaIce = viewbox.append("g").attr("id", "seaIce").attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("filter", "url(#dropShadow05)");//.attr("opacity", .8);
|
||||
for (const i of grid.cells.i) {
|
||||
const t = grid.cells.temp[i] ;
|
||||
if (t > 2) continue;
|
||||
if (t > -5 && grid.cells.h[i] >= 20) continue;
|
||||
if (t < -5) drawpolygon(i);
|
||||
if (P(normalize(t, -5.5, 2.5))) continue; // t[-5; 2]
|
||||
const size = t < -14 ? 0 : t > -6 ? (7 + t) / 10 : (15 + t) / 100; // [0; 1], where 0 = full size, 1 = zero size
|
||||
resizePolygon(i, rn(size * (.2 + rand() * .9), 2));
|
||||
function toggleIce() {
|
||||
if (!layerIsOn("toggleIce")) {
|
||||
turnButtonOn("toggleIce");
|
||||
$('#ice').fadeIn();
|
||||
if (event && isCtrlClick(event)) editStyle("ice");
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {editStyle("ice"); return;}
|
||||
$('#ice').fadeOut();
|
||||
turnButtonOff("toggleIce");
|
||||
}
|
||||
}
|
||||
|
||||
// -9: .06
|
||||
// -8: .07
|
||||
// -7: .08
|
||||
// -6: .09
|
||||
function drawIce() {
|
||||
const cells = grid.cells, vertices = grid.vertices, n = cells.i.length, temp = cells.temp, h = cells.h;
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
Math.seedrandom(seed);
|
||||
|
||||
// -5: .2
|
||||
// -4: .3
|
||||
// -3: .4
|
||||
// -2: .5
|
||||
// -1: .6
|
||||
// 0: .7
|
||||
const shieldMin = -6; // min temp to form ice shield (glacier)
|
||||
const icebergMax = 2; // max temp to form an iceberg
|
||||
|
||||
function drawpolygon(i) {
|
||||
seaIce.append("polygon").attr("points", getGridPolygon(i));
|
||||
for (const i of grid.cells.i) {
|
||||
const t = temp[i];
|
||||
if (t > icebergMax) continue; // too warm: no ice
|
||||
if (t > shieldMin && h[i] >= 20) continue; // non-glacier land: no ice
|
||||
|
||||
if (t <= shieldMin) {
|
||||
// very cold: ice shield
|
||||
if (used[i]) continue; // already rendered
|
||||
const onborder = cells.c[i].some(n => temp[n] > shieldMin);
|
||||
if (!onborder) continue; // need to start from onborder cell
|
||||
const vertex = cells.v[i].find(v => vertices.c[v].some(i => temp[i] > shieldMin));
|
||||
const chain = connectVertices(vertex);
|
||||
if (chain.length < 3) continue;
|
||||
const points = clipPoly(chain.map(v => vertices.p[v]));
|
||||
ice.append("polygon").attr("points", points).attr("type", "iceShield");
|
||||
continue;
|
||||
}
|
||||
|
||||
// mildly cold: iceberd
|
||||
if (P(normalize(t, -7, 2.5))) continue; // t[-5; 2] cold: skip some cells
|
||||
if (grid.features[cells.f[i]].type === "lake") continue; // lake: no icebers
|
||||
let size = (6.5 + t) / 10; // iceberg size: 0 = full size, 1 = zero size
|
||||
if (cells.t[i] === -1) size *= 1.3; // coasline: smaller icebers
|
||||
size = Math.min(size * (.4 + rand() * 1.2), .95); // randomize iceberd size
|
||||
resizePolygon(i, size);
|
||||
}
|
||||
|
||||
function resizePolygon(i, s) {
|
||||
const c = grid.points[i];
|
||||
const points = getGridPolygon(i).map(p => [p[0] + (c[0]-p[0]) * s, p[1] + (c[1]-p[1]) * s]);
|
||||
seaIce.append("polygon").attr("points", points);
|
||||
const points = getGridPolygon(i).map(p => [(p[0] + (c[0]-p[0]) * s)|0, (p[1] + (c[1]-p[1]) * s)|0]);
|
||||
ice.append("polygon").attr("points", points).attr("cell", i).attr("size", rn(1-s, 2));
|
||||
}
|
||||
|
||||
// connect vertices to chain
|
||||
function connectVertices(start) {
|
||||
const chain = []; // vertices chain to form a path
|
||||
for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) {
|
||||
const prev = last(chain); // previous vertex in chain
|
||||
chain.push(current); // add current vertex to sequence
|
||||
const c = vertices.c[current]; // cells adjacent to vertex
|
||||
c.filter(c => temp[c] <= shieldMin).forEach(c => used[c] = 1);
|
||||
const c0 = c[0] >= n || temp[c[0]] > shieldMin;
|
||||
const c1 = c[1] >= n || temp[c[1]] > shieldMin;
|
||||
const c2 = c[2] >= n || temp[c[2]] > shieldMin;
|
||||
const v = vertices.v[current]; // neighboring vertices
|
||||
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||
if (current === chain[chain.length - 1]) {console.error("Next vertex is not found"); break;}
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ function editMarker() {
|
|||
document.getElementById("markerIconSize").addEventListener("input", changeIconSize);
|
||||
document.getElementById("markerIconShiftX").addEventListener("input", changeIconShiftX);
|
||||
document.getElementById("markerIconShiftY").addEventListener("input", changeIconShiftY);
|
||||
document.getElementById("markerIconCustom").addEventListener("input", applyCustomUnicodeIcon);
|
||||
document.getElementById("markerIconSelect").addEventListener("click", selectMarkerIcon);
|
||||
|
||||
document.getElementById("markerStyle").addEventListener("click", toggleStyleSection);
|
||||
document.getElementById("markerSize").addEventListener("input", changeMarkerSize);
|
||||
|
|
@ -73,13 +73,7 @@ function editMarker() {
|
|||
markerIconFill.value = icon.attr("fill");
|
||||
|
||||
markerToggleBubble.className = symbol.select("circle").attr("opacity") === "0" ? "icon-info" : "icon-info-circled";
|
||||
|
||||
const table = document.getElementById("markerIconTable");
|
||||
let selected = table.getElementsByClassName("selected");
|
||||
if (selected.length) selected[0].removeAttribute("class");
|
||||
selected = document.querySelectorAll("#markerIcon" + icon.text().codePointAt());
|
||||
if (selected.length) selected[0].className = "selected";
|
||||
markerIconCustom.value = selected.length ? "" : icon.text();
|
||||
markerIconSelect.innerHTML = icon.text();
|
||||
}
|
||||
|
||||
function toggleGroupSection() {
|
||||
|
|
@ -162,242 +156,20 @@ function editMarker() {
|
|||
if (markerIconSection.style.display === "inline-block") {
|
||||
markerEditor.querySelectorAll("button:not(#markerIcon)").forEach(b => b.style.display = "inline-block");
|
||||
markerIconSection.style.display = "none";
|
||||
markerIconSelect.style.display = "none";
|
||||
} else {
|
||||
markerEditor.querySelectorAll("button:not(#markerIcon)").forEach(b => b.style.display = "none");
|
||||
markerIconSection.style.display = "inline-block";
|
||||
if (!markerIconTable.innerHTML) drawIconsList();
|
||||
markerIconSelect.style.display = "inline-block";
|
||||
}
|
||||
}
|
||||
|
||||
function drawIconsList() {
|
||||
const icons = [
|
||||
// emoticons in FF:
|
||||
["2693", "⚓", "Anchor"],
|
||||
["26EA", "⛪", "Church"],
|
||||
["1F3EF", "🏯", "Japanese Castle"],
|
||||
["1F3F0", "🏰", "Castle"],
|
||||
["1F5FC", "🗼", "Tower"],
|
||||
["1F3E0", "🏠", "House"],
|
||||
["1F3AA", "🎪", "Tent"],
|
||||
["1F3E8", "🏨", "Hotel"],
|
||||
["1F4B0", "💰", "Money bag"],
|
||||
["1F6A8", "🚨", "Revolving Light"],
|
||||
["1F309", "🌉", "Bridge at Night"],
|
||||
["1F5FB", "🗻", "Mountain"],
|
||||
["1F30B", "🌋", "Volcano"],
|
||||
["270A", "✊", "Raised Fist"],
|
||||
["1F44A", "👊", "Oncoming Fist"],
|
||||
["1F4AA", "💪", "Flexed Biceps"],
|
||||
["1F47C", "👼", "Baby Angel"],
|
||||
["1F40E", "🐎", "Horse"],
|
||||
["1F434", "🐴", "Horse Face"],
|
||||
["1F42E", "🐮", "Cow"],
|
||||
["1F43A", "🐺", "Wolf Face"],
|
||||
["1F435", "🐵", "Monkey face"],
|
||||
["1F437", "🐷", "Pig face"],
|
||||
["1F414", "🐔", "Chicken"],
|
||||
["1F411", "🐑", "Ewe"],
|
||||
["1F42B", "🐫", "Camel"],
|
||||
["1F418", "🐘", "Elephant"],
|
||||
["1F422", "🐢", "Turtle"],
|
||||
["1F40C", "🐌", "Snail"],
|
||||
["1F40D", "🐍", "Snake"],
|
||||
["1F41D", "🐝", "Honeybee"],
|
||||
["1F41C", "🐜", "Ant"],
|
||||
["1F41B", "🐛", "Bug"],
|
||||
["1F426", "🐦", "Bird"],
|
||||
["1F438", "🐸", "Frog Face"],
|
||||
["1F433", "🐳", "Whale"],
|
||||
["1F42C", "🐬", "Dolphin"],
|
||||
["1F420", "🐟", "Fish"],
|
||||
["1F480", "💀", "Skull"],
|
||||
["1F432", "🐲", "Dragon Head"],
|
||||
["1F479", "👹", "Ogre"],
|
||||
["1F47A", "👺", "Goblin"],
|
||||
["1F47B", "👻", "Ghost"],
|
||||
["1F47E", "👾", "Alien"],
|
||||
["1F383", "🎃", "Jack-O-Lantern"],
|
||||
["1F384", "🎄", "Christmas Tree"],
|
||||
["1F334", "🌴", "Palm"],
|
||||
["1F335", "🌵", "Cactus"],
|
||||
["2618", "☘️", "Shamrock"],
|
||||
["1F340", "🍀", "Four Leaf Clover"],
|
||||
["1F341", "🍁", "Maple Leaf"],
|
||||
["1F33F", "🌿", "Herb"],
|
||||
["1F33E", "🌾", "Sheaf"],
|
||||
["1F344", "🍄", "Mushroom"],
|
||||
["1F374", "🍴", "Fork and knife"],
|
||||
["1F372", "🍲", "Food"],
|
||||
["1F35E", "🍞", "Bread"],
|
||||
["1F357", "🍗", "Poultry leg"],
|
||||
["1F347", "🍇", "Grapes"],
|
||||
["1F34F", "🍏", "Apple"],
|
||||
["1F352", "🍒", "Cherries"],
|
||||
["1F36F", "🍯", "Honey pot"],
|
||||
["1F37A", "🍺", "Beer"],
|
||||
["1F37B", "🍻", "Beers"],
|
||||
["1F377", "🍷", "Wine glass"],
|
||||
["1F3BB", "🎻", "Violin"],
|
||||
["1F3B8", "🎸", "Guitar"],
|
||||
["1F52A", "🔪", "Knife"],
|
||||
["1F52B", "🔫", "Pistol"],
|
||||
["1F4A3", "💣", "Bomb"],
|
||||
["1F4A5", "💥", "Collision"],
|
||||
["1F4A8", "💨", "Dashing away"],
|
||||
["1F301", "🌁", "Foggy"],
|
||||
["2744", "❄️", "Snowflake"],
|
||||
["26A1", "⚡", "Electricity"],
|
||||
["1F320", "🌠", "Shooting star"],
|
||||
["1F319", "🌙", "Crescent moon"],
|
||||
["1F525", "🔥", "Fire"],
|
||||
["1F4A7", "💧", "Droplet"],
|
||||
["1F30A", "🌊", "Wave"],
|
||||
["23F0", "⏰", "Alarm Clock"],
|
||||
["231B", "⌛", "Hourglass"],
|
||||
["1F3C6", "🏆", "Goblet"],
|
||||
["26F2", "⛲", "Fountain"],
|
||||
["26F5", "⛵", "Sailboat"],
|
||||
["26FA", "⛺", "Campfire"],
|
||||
["2764", "❤", "Red Heart"],
|
||||
["1F498", "💘", "Heart With Arrow"],
|
||||
["1F489", "💉", "Syringe"],
|
||||
["1F4D5", "📕", "Closed Book"],
|
||||
["1F4D6", "📚", "Books"],
|
||||
["1F381", "🎁", "Wrapped Gift"],
|
||||
["1F3AF", "🎯", "Archery"],
|
||||
["1F52E", "🔮", "Magic ball"],
|
||||
["1F3AD", "🎭", "Performing arts"],
|
||||
["1F3A8", "🎨", "Artist palette"],
|
||||
["1F457", "👗", "Dress"],
|
||||
["1F392", "🎒", "Backpack"],
|
||||
["1F451", "👑", "Crown"],
|
||||
["1F48D", "💍", "Ring"],
|
||||
["1F48E", "💎", "Gem"],
|
||||
["1F514", "🔔", "Bell"],
|
||||
["1F3B2", "🎲", "Die"],
|
||||
// black and white icons in FF:
|
||||
["26A0", "⚠", "Alert"],
|
||||
["2317", "⌗", "Hash"],
|
||||
["2318", "⌘", "POI"],
|
||||
["2307", "⌇", "Wavy"],
|
||||
["27F1", "⟱", "Downwards Quadruple"],
|
||||
["21E6", "⇦", "Left arrow"],
|
||||
["21E7", "⇧", "Top arrow"],
|
||||
["21E8", "⇨", "Right arrow"],
|
||||
["21E9", "⇩", "Left arrow"],
|
||||
["21F6", "⇶", "Three arrows"],
|
||||
["2699", "⚙", "Gear"],
|
||||
["269B", "⚛", "Atom"],
|
||||
["2680", "⚀", "Die1"],
|
||||
["2681", "⚁", "Die2"],
|
||||
["2682", "⚂", "Die3"],
|
||||
["2683", "⚃", "Die4"],
|
||||
["2684", "⚄", "Die5"],
|
||||
["2685", "⚅", "Die6"],
|
||||
["26B4", "⚴", "Pallas"],
|
||||
["26B5", "⚵", "Juno"],
|
||||
["26B6", "⚶", "Vesta"],
|
||||
["26B7", "⚷", "Chiron"],
|
||||
["26B8", "⚸", "Lilith"],
|
||||
["263F", "☿", "Mercury"],
|
||||
["2640", "♀", "Venus"],
|
||||
["2641", "♁", "Earth"],
|
||||
["2642", "♂", "Mars"],
|
||||
["2643", "♃", "Jupiter"],
|
||||
["2644", "♄", "Saturn"],
|
||||
["2645", "♅", "Uranus"],
|
||||
["2646", "♆", "Neptune"],
|
||||
["2647", "♇", "Pluto"],
|
||||
["26B3", "⚳", "Ceres"],
|
||||
["2654", "♔", "Chess king"],
|
||||
["2655", "♕", "Chess queen"],
|
||||
["2656", "♖", "Chess rook"],
|
||||
["2657", "♗", "Chess bishop"],
|
||||
["2658", "♘", "Chess knight"],
|
||||
["2659", "♙", "Chess pawn"],
|
||||
["2660", "♠", "Spade"],
|
||||
["2663", "♣", "Club"],
|
||||
["2665", "♥", "Heart"],
|
||||
["2666", "♦", "Diamond"],
|
||||
["2698", "⚘", "Flower"],
|
||||
["2625", "☥", "Ankh"],
|
||||
["2626", "☦", "Orthodox"],
|
||||
["2627", "☧", "Chi Rho"],
|
||||
["2628", "☨", "Lorraine"],
|
||||
["2629", "☩", "Jerusalem"],
|
||||
["2670", "♰", "Syriac cross"],
|
||||
["2020", "†", "Dagger"],
|
||||
["262A", "☪", "Muslim"],
|
||||
["262D", "☭", "Soviet"],
|
||||
["262E", "☮", "Peace"],
|
||||
["262F", "☯", "Yin yang"],
|
||||
["26A4", "⚤", "Heterosexuality"],
|
||||
["26A2", "⚢", "Female homosexuality"],
|
||||
["26A3", "⚣", "Male homosexuality"],
|
||||
["26A5", "⚥", "Male and female"],
|
||||
["26AD", "⚭", "Rings"],
|
||||
["2690", "⚐", "White flag"],
|
||||
["2691", "⚑", "Black flag"],
|
||||
["263C", "☼", "Sun"],
|
||||
["263E", "☾", "Moon"],
|
||||
["2668", "♨", "Hot springs"],
|
||||
["2600", "☀", "Black sun"],
|
||||
["2601", "☁", "Cloud"],
|
||||
["2602", "☂", "Umbrella"],
|
||||
["2603", "☃", "Snowman"],
|
||||
["2604", "☄", "Comet"],
|
||||
["2605", "★", "Black star"],
|
||||
["2606", "☆", "White star"],
|
||||
["269D", "⚝", "Outlined star"],
|
||||
["2618", "☘", "Shamrock"],
|
||||
["21AF", "↯", "Lightning"],
|
||||
["269C", "⚜", "FleurDeLis"],
|
||||
["2622", "☢", "Radiation"],
|
||||
["2623", "☣", "Biohazard"],
|
||||
["2620", "☠", "Skull"],
|
||||
["2638", "☸", "Dharma"],
|
||||
["2624", "☤", "Caduceus"],
|
||||
["2695", "⚕", "Aeculapius staff"],
|
||||
["269A", "⚚", "Hermes staff"],
|
||||
["2697", "⚗", "Alembic"],
|
||||
["266B", "♫", "Music"],
|
||||
["2702", "✂", "Scissors"],
|
||||
["2696", "⚖", "Scales"],
|
||||
["2692", "⚒", "Hammer and pick"],
|
||||
["2694", "⚔", "Swords"]
|
||||
];
|
||||
|
||||
const table = document.getElementById("markerIconTable");
|
||||
table.addEventListener("click", selectIcon, false);
|
||||
table.addEventListener("mouseover", hoverIcon, false);
|
||||
let row = "";
|
||||
|
||||
for (let i=0; i < icons.length; i++) {
|
||||
if (i%16 === 0) row = table.insertRow(0);
|
||||
const cell = row.insertCell(0);
|
||||
const icon = String.fromCodePoint(parseInt(icons[i][0], 16));
|
||||
cell.innerHTML = icon;
|
||||
cell.id = "markerIcon" + icon.codePointAt();
|
||||
cell.dataset.desc = icons[i][2];
|
||||
}
|
||||
}
|
||||
|
||||
function selectIcon(e) {
|
||||
if (e.target !== e.currentTarget) {
|
||||
const table = document.getElementById("markerIconTable");
|
||||
const selected = table.getElementsByClassName("selected");
|
||||
if (selected.length) selected[0].removeAttribute("class");
|
||||
e.target.className = "selected";
|
||||
function selectMarkerIcon() {
|
||||
selectIcon(this.innerHTML, v => {
|
||||
this.innerHTML = v;
|
||||
const id = elSelected.attr("data-id");
|
||||
const icon = e.target.innerHTML;
|
||||
d3.select("#defs-markers").select(id).select("text").text(icon);
|
||||
}
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
function hoverIcon(e) {
|
||||
if (e.target !== e.currentTarget) tip(e.target.innerHTML + " " + e.target.dataset.desc);
|
||||
e.stopPropagation();
|
||||
d3.select("#defs-markers").select(id).select("text").text(v);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconSize() {
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ function overviewMilitary() {
|
|||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Apply: applyMilitaryOptions,
|
||||
Add: () => addUnitLine({name: "custom"+militaryOptionsTable.rows.length, rural: .2, urban: .5, crew: 1, type: "melee"}),
|
||||
Add: () => addUnitLine({icon: "🛡️", name: "custom"+militaryOptionsTable.rows.length, rural: .2, urban: .5, crew: 1, power: 1, type: "melee"}),
|
||||
Restore: restoreDefaultUnits,
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}, open: function() {
|
||||
|
|
@ -200,19 +200,20 @@ function overviewMilitary() {
|
|||
}
|
||||
|
||||
function addUnitLine(u) {
|
||||
const row = `<tr>
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `<td><button type="button" data-tip="Click to select unit icon">${u.icon||" "}</button></td>
|
||||
<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><input data-tip="Enter average number of people in crew (used for total personnel calculation)" type="number" min=1 step=1 value="${u.crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${u.power}"></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" ${u.separate ? "checked" : ""}>
|
||||
<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);
|
||||
<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>`;
|
||||
row.querySelector("button").addEventListener("click", function(e) {selectIcon(this.innerHTML, v => this.innerHTML = v)});
|
||||
table.appendChild(row);
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
|
|
@ -230,8 +231,14 @@ function overviewMilitary() {
|
|||
|
||||
$("#militaryOptions").dialog("close");
|
||||
options.military = unitLines.map((r, i) => {
|
||||
const [name, rural, urban, crew, type, separate] = Array.from(r.querySelectorAll("input, select")).map(d => d.type === "checkbox" ? d.checked : d.value);
|
||||
return {name:names[i], rural:+rural||0, urban:+urban||0, crew:+crew||0, type, separate:+separate||0};
|
||||
const [icon, name, rural, urban, crew, power, type, separate] = Array.from(r.querySelectorAll("input, select, button")).map(d => {
|
||||
let value = d.value;
|
||||
if (d.type === "number") value = +d.value || 0;
|
||||
if (d.type === "checkbox") value = +d.checked || 0;
|
||||
if (d.type === "button") value = d.innerHTML || "⠀";
|
||||
return value;
|
||||
});
|
||||
return {icon, name:names[i], rural, urban, crew, power, type, separate};
|
||||
});
|
||||
localStorage.setItem("military", JSON.stringify(options.military));
|
||||
Military.generate();
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ function editRegiment(selector) {
|
|||
|
||||
$("#regimentEditor").dialog({
|
||||
title: "Edit Regiment", resizable: false, close: closeEditor,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "#map"},
|
||||
close: closeEditor
|
||||
position: {my: "left top", at: "left+10 top+10", of: "#map"}
|
||||
});
|
||||
|
||||
if (modules.editRegiment) return;
|
||||
|
|
@ -27,6 +26,7 @@ function editRegiment(selector) {
|
|||
document.getElementById("regimentName").addEventListener("change", changeName);
|
||||
document.getElementById("regimentEmblem").addEventListener("input", changeEmblem);
|
||||
document.getElementById("regimentEmblemSelect").addEventListener("click", selectEmblem);
|
||||
document.getElementById("regimentAttack").addEventListener("click", toggleAttack);
|
||||
document.getElementById("regimentRegenerateLegend").addEventListener("click", regenerateLegend);
|
||||
document.getElementById("regimentLegend").addEventListener("click", editLegend);
|
||||
document.getElementById("regimentSplit").addEventListener("click", splitRegiment);
|
||||
|
|
@ -92,50 +92,15 @@ function editRegiment(selector) {
|
|||
elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name;
|
||||
}
|
||||
|
||||
function selectEmblem() {
|
||||
selectIcon(regimentEmblem.value, v => {regimentEmblem.value = v; changeEmblem()});
|
||||
}
|
||||
|
||||
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: {
|
||||
Emojipedia: function() {openURL("https://emojipedia.org/");},
|
||||
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();
|
||||
|
|
@ -200,6 +165,60 @@ function editRegiment(selector) {
|
|||
toggleAdd();
|
||||
}
|
||||
|
||||
function toggleAttack() {
|
||||
document.getElementById("regimentAttack").classList.toggle("pressed");
|
||||
if (document.getElementById("regimentAttack").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", attackRegimentOnClick);
|
||||
tip("Click on another regiment to initiate battle", 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 attackRegimentOnClick() {
|
||||
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 to attack", false, "error"); return;}
|
||||
if (regSelected === elSelected) {tip("Regiment cannot attack itself", false, "error"); return;}
|
||||
if (oldState === newState) {tip("Cannot attack fraternal regiment", false, "error"); return;}
|
||||
|
||||
const attacker = regiment();
|
||||
const defender = pack.states[regSelected.dataset.state].military.find(r => r.i == regSelected.dataset.id);
|
||||
if (!attacker.a || !defender.a) {tip("Regiment has no troops to battle", false, "error"); return;}
|
||||
|
||||
// move attacked to defender
|
||||
const duration = Math.hypot(attacker.x - defender.x, attacker.y - defender.y) * 6;
|
||||
const x = attacker.x = defender.x;
|
||||
const y = attacker.y = defender.y + 8;
|
||||
|
||||
const size = +armies.attr("box-size");
|
||||
const w = attacker.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 move = d3.transition().duration(duration).ease(d3.easeSinInOut);
|
||||
const attack = d3.transition().delay(duration).duration(800).ease(d3.easeSinInOut).on("end", () => showBattleScreen(attacker, defender));
|
||||
|
||||
d3.select(elSelected.querySelector("rect")).transition(move).attr("x", x1(x)).attr("y", y1(y));
|
||||
d3.select(elSelected.querySelector("text")).transition(move).attr("x", x).attr("y", y);
|
||||
d3.select(elSelected.querySelectorAll("rect")[1]).transition(move).attr("x", x1(x)-h).attr("y", y1(y));
|
||||
d3.select(elSelected.querySelector(".regimentIcon")).transition(move).attr("x", x1(x)-size).attr("y", y);
|
||||
|
||||
// battle icon
|
||||
svg.append("text").attr("x", window.innerWidth/2).attr("y", window.innerHeight/2)
|
||||
.text("⚔️").attr("font-size", 0).attr("opacity", 1)
|
||||
.style("dominant-baseline", "central").style("text-anchor", "middle")
|
||||
.transition(attack).attr("font-size", 1000).attr("opacity", 0).remove();
|
||||
|
||||
tip("", false);
|
||||
$("#regimentEditor").dialog("close");
|
||||
}
|
||||
|
||||
function toggleAttach() {
|
||||
document.getElementById("regimentAttach").classList.toggle("pressed");
|
||||
if (document.getElementById("regimentAttach").classList.contains("pressed")) {
|
||||
|
|
@ -289,7 +308,7 @@ function editRegiment(selector) {
|
|||
const x1 = x => rn(x - w / 2, 2);
|
||||
const y1 = y => rn(y - size, 2);
|
||||
|
||||
const baseRect = this.querySelectorAll("rect")[0];
|
||||
const baseRect = this.querySelector("rect");
|
||||
const text = this.querySelector("text");
|
||||
const iconRect = this.querySelectorAll("rect")[1];
|
||||
const icon = this.querySelector(".regimentIcon");
|
||||
|
|
@ -330,6 +349,7 @@ function editRegiment(selector) {
|
|||
armies.selectAll("g>g").call(d3.drag().on("drag", null));
|
||||
viewbox.selectAll("g#regimentBase").remove();
|
||||
document.getElementById("regimentAdd").classList.remove("pressed");
|
||||
document.getElementById("regimentAttack").classList.remove("pressed");
|
||||
document.getElementById("regimentAttach").classList.remove("pressed");
|
||||
restoreDefaultEvents();
|
||||
elSelected = null;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -65,6 +65,7 @@ function processFeatureRegeneration(event, button) {
|
|||
if (button === "regenerateProvinces") regenerateProvinces(); else
|
||||
if (button === "regenerateReligions") regenerateReligions(); else
|
||||
if (button === "regenerateMilitary") regenerateMilitary(); else
|
||||
if (button === "regenerateIce") regenerateIce(); else
|
||||
if (button === "regenerateMarkers") regenerateMarkers(event); else
|
||||
if (button === "regenerateZones") regenerateZones(event);
|
||||
}
|
||||
|
|
@ -86,7 +87,7 @@ function recalculatePopulation() {
|
|||
if (!b.i || b.removed) return;
|
||||
const i = b.cell;
|
||||
|
||||
b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i]) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
|
||||
b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i] / 2) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
|
||||
if (b.capital) b.population = b.population * 1.3; // increase capital population
|
||||
if (b.port) b.population = b.population * 1.3; // increase port population
|
||||
b.population = rn(b.population * gauss(2,3,.6,20,3), 3);
|
||||
|
|
@ -249,6 +250,12 @@ function regenerateMilitary() {
|
|||
if (document.getElementById("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateIce() {
|
||||
if (!layerIsOn("toggleIce")) toggleIce();
|
||||
ice.selectAll("*").remove();
|
||||
drawIce();
|
||||
}
|
||||
|
||||
function regenerateMarkers(event) {
|
||||
if (isCtrlClick(event)) prompt("Please provide markers number multiplier", {default:1, step:.01, min:0, max:100}, v => addNumberOfMarkers(v));
|
||||
else addNumberOfMarkers(gauss(1, .5, .3, 5, 2));
|
||||
|
|
|
|||
|
|
@ -370,11 +370,16 @@ function biased(min, max, ex) {
|
|||
}
|
||||
|
||||
// return array of values common for both array a and array b
|
||||
function intersect(a, b) {
|
||||
function common(a, b) {
|
||||
const setB = new Set(b);
|
||||
return [...new Set(a)].filter(a => setB.has(a));
|
||||
}
|
||||
|
||||
// clip polygon by graph bbox
|
||||
function clipPoly(points, secure = false) {
|
||||
return polygonclip(points, [0, 0, graphWidth, graphHeight], secure);
|
||||
}
|
||||
|
||||
// check if char is vowel
|
||||
function vowel(c) {
|
||||
return "aeiouy".includes(c);
|
||||
|
|
@ -610,5 +615,5 @@ void function() {
|
|||
cancel.addEventListener("click", () => prompt.style.display = "none");
|
||||
}()
|
||||
|
||||
// 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)}}}();
|
||||
// indexedDB; ldb object
|
||||
!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("indexedDB 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)}}}();
|
||||
3
run_php_server.bat
Normal file
3
run_php_server.bat
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
start chrome.exe http://localhost:8000/
|
||||
@echo off
|
||||
php -S localhost:8000
|
||||
6
upload.php
Normal file
6
upload.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
header("Content-type: text/plain");
|
||||
echo "received:";
|
||||
var_dump(file_get_contents('php://input'));
|
||||
print_r($_POST);
|
||||
?>
|
||||
Loading…
Add table
Add a link
Reference in a new issue