Merge branch 'zone-types' into zoneFilter

This commit is contained in:
Azgaar 2022-02-06 23:08:10 +03:00 committed by GitHub
commit be5fd52545
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 5605 additions and 1374 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
.bat .bat
.vscode .vscode
.idea
.idea/Fantasy-Map-Generator.iml

74
components/fill-box.js Normal file
View file

@ -0,0 +1,74 @@
// fill-box cannot use Shadow DOM as it needs access to svg hatches
// append stylesheet
{
const style = `
fill-box:not([disabled]) {
cursor: pointer;
}
fill-box > svg {
vertical-align: middle;
pointer-events: none;
}
fill-box > svg > rect {
stroke: #666666;
stroke-width: 2;
}`;
const styleElement = document.createElement("style");
styleElement.setAttribute("type", "text/css");
styleElement.innerHTML = style;
document.head.appendChild(styleElement);
}
{
const template = document.createElement("template");
template.innerHTML = `
<svg>
<rect x="0" y="0" width="100%" height="100%">
</svg>
`;
class FillBox extends HTMLElement {
constructor() {
super();
this.appendChild(template.content.cloneNode(true));
this.querySelector("rect")?.setAttribute("fill", this.fill);
this.querySelector("svg")?.setAttribute("width", this.size);
this.querySelector("svg")?.setAttribute("height", this.size);
}
static showTip() {
tip(this.tip);
}
connectedCallback() {
this.addEventListener("mousemove", this.constructor.showTip);
}
disconnectedCallback() {
this.removeEventListener("mousemove", this.constructor.showTip);
}
get fill() {
return this.getAttribute("fill") || "#333";
}
set fill(newFill) {
this.setAttribute("fill", newFill);
this.querySelector("rect")?.setAttribute("fill", newFill);
}
get size() {
return this.getAttribute("size") || "1em";
}
get tip() {
return this.dataset.tip || "Fill style. Click to change";
}
}
customElements.define("fill-box", FillBox);
}

View file

@ -29,9 +29,13 @@ input:read-only {
cursor: default; cursor: default;
} }
input[type="radio"] {
vertical-align: bottom;
cursor: pointer;
}
textarea { textarea {
padding: 2px; padding: 3px;
text-indent: 1px;
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
} }
@ -834,75 +838,79 @@ fieldset {
} }
.matrix-table { .matrix-table {
width: 100%; max-height: 80vh;
font-size: smaller; max-width: 85vw;
text-align: center; scrollbar-width: thin;
border-collapse: collapse; overflow: auto;
} }
table.matrix-table th, .matrix-table > table {
table.matrix-table td { text-align: center;
border-collapse: collapse;
font-size: smaller;
}
.matrix-table > table th,
.matrix-table > table td {
border: 1px solid var(--dark-solid); border: 1px solid var(--dark-solid);
height: 2em; height: 2em;
padding: 0.2em; padding: 0.2em;
position: relative; position: relative;
} }
table.matrix-table th { .matrix-table > table th {
background-color: #302a2a; background-color: #302a2a;
color: #ffffff; color: #ffffff;
} }
table.matrix-table tr:hover th { .matrix-table > table td:hover {
background: #3e3636;
}
table.matrix-table td:hover {
outline: 2px solid var(--dark-solid); outline: 2px solid var(--dark-solid);
outline-offset: -1px; outline-offset: -1px;
z-index: 1; z-index: 1;
cursor: pointer;
} }
table.matrix-table td.Ally { .matrix-table > table td.Ally {
background-color: #73ec73; background-color: #73ec73;
color: #000000; color: #000000;
} }
table.matrix-table td.Friendly { .matrix-table > table td.Friendly {
background-color: #d4f8aa; background-color: #d4f8aa;
} }
table.matrix-table td.Neutral { .matrix-table > table td.Neutral {
background-color: #d8d9d3; background-color: #d8d9d3;
} }
table.matrix-table td.Suspicion { .matrix-table > table td.Suspicion {
background-color: #eeafaa; background-color: #eeafaa;
} }
table.matrix-table td.Enemy { .matrix-table > table td.Enemy {
background-color: #ffa39c; background-color: #ffa39c;
color: #af0d23; color: #af0d23;
} }
table.matrix-table td.Unknown { .matrix-table > table td.Unknown {
background-color: #c1bfbf; background-color: #c1bfbf;
} }
table.matrix-table td.Rival { .matrix-table > table td.Rival {
background-color: #bd845c; background-color: #bd845c;
} }
table.matrix-table td.Vassal { .matrix-table > table td.Vassal {
background-color: #87cefa; background-color: #87cefa;
} }
table.matrix-table td.Suzerain { .matrix-table > table td.Suzerain {
background-color: #8f8fe1; background-color: #8f8fe1;
} }
table.matrix-table td.x { .matrix-table > table td.x {
background-color: #d4ca94; background-color: #d4ca94;
cursor: initial;
} }
#sizeOutput { #sizeOutput {
@ -1310,19 +1318,23 @@ div.slider .ui-slider-handle {
} }
#alertMessage::-webkit-scrollbar, #alertMessage::-webkit-scrollbar,
.table::-webkit-scrollbar { .table::-webkit-scrollbar,
.matrix-table::-webkit-scrollbar {
width: 6px; width: 6px;
height: 6px;
background-color: transparent; background-color: transparent;
} }
#alertMessage::-webkit-scrollbar-thumb, #alertMessage::-webkit-scrollbar-thumb,
.table::-webkit-scrollbar-thumb { .table::-webkit-scrollbar-thumb,
.matrix-table::-webkit-scrollbar-thumb {
background-color: #aaa; background-color: #aaa;
border-radius: 6px; border-radius: 6px;
} }
#alertMessage::-webkit-scrollbar-thumb:hover, #alertMessage::-webkit-scrollbar-thumb:hover,
.table::-webkit-scrollbar-thumb:hover { .table::-webkit-scrollbar-thumb:hover,
.matrix-table::-webkit-scrollbar-thumb:hover {
background: #666; background: #666;
} }
@ -1504,15 +1516,14 @@ div.states > .riverType {
width: 5em; width: 5em;
} }
div.states > .coaIcon { .coaIcon {
stroke-width: 3; stroke-width: 3;
width: 1.4em; width: 1.4em;
height: 1.4em; height: 1.4em;
margin: -0.3em 0; margin: -0.3em 0;
cursor: pointer;
} }
div.states > .coaIcon > use { .coaIcon > use {
pointer-events: none; pointer-events: none;
} }
@ -1520,6 +1531,11 @@ div.states > .coaIcon > use {
cursor: pointer; cursor: pointer;
} }
.changeRelations > * {
pointer-events: none;
cursor: pointer;
}
#diplomacySelect { #diplomacySelect {
width: 5em; width: 5em;
margin: 0.1em 0 0 -0.3em; margin: 0.1em 0 0 -0.3em;
@ -1657,11 +1673,6 @@ div.states > div.biomeArea {
width: 5em; width: 5em;
} }
rect.fillRect {
stroke: #666666;
stroke-width: 2;
}
#militaryHeader > div, #militaryHeader > div,
#regimentsHeader > div { #regimentsHeader > div {
width: 5.2em; width: 5.2em;
@ -2000,7 +2011,7 @@ div.textual fieldset {
div.textual span, div.textual span,
.textual legend { .textual legend {
font-size: 0.8em; font-size: 0.9em;
font-weight: bold; font-weight: bold;
} }
@ -2119,8 +2130,9 @@ svg.button {
text-decoration: underline; text-decoration: underline;
} }
#info-line { .info-line {
font-size: 0.9em; font-size: 0.9em;
font-style: italic;
color: gray; color: gray;
user-select: none; user-select: none;
} }
@ -2264,10 +2276,10 @@ svg.button {
left: 50%; left: 50%;
top: 50%; top: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
max-width: 22em; max-width: 23em;
background-color: #fff;
padding: 1.2em; padding: 1.2em;
border: solid 1px #000; background-color: var(--bg-dialogs);
border: solid 1px var(--dark-solid);
font-size: 1.2em; font-size: 1.2em;
z-index: 1000; z-index: 1000;
} }
@ -2298,7 +2310,7 @@ svg.button {
.epgrid line { .epgrid line {
stroke: lightgrey; stroke: lightgrey;
stroke-opacity: 0.7; stroke-opacity: 0.5;
shape-rendering: crispEdges; shape-rendering: crispEdges;
} }

View file

@ -19,7 +19,7 @@
body {margin: 0; font-size: 10px; overflow: hidden;} body {margin: 0; font-size: 10px; overflow: hidden;}
#map {position: absolute;} #map {position: absolute;}
#initial {fill: none; stroke: black; pointer-events: none;} #initial {fill: none; stroke: black; pointer-events: none;}
#init-rose {animation: 20s infinite spin; opacity: .7; transform-origin: center;} #init-rose {opacity: .7; transform-origin: center; opacity: .7; animation: 20s infinite spin;}
@keyframes spin {0% {transform: rotate(0deg);} 100% {transform: rotate(359deg);}} @keyframes spin {0% {transform: rotate(0deg);} 100% {transform: rotate(359deg);}}
#loading {opacity:1; font-size: 11px; color:#fff5da; text-align:center; text-shadow:0px 1px 4px #4c3a35; width:80%; max-width:600px; position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); pointer-events:none;} #loading {opacity:1; font-size: 11px; color:#fff5da; text-align:center; text-shadow:0px 1px 4px #4c3a35; width:80%; max-width:600px; position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); pointer-events:none;}
#loading-text {font-size: 1.8em; margin: 0.2em 0 0 1em;} #loading-text {font-size: 1.8em; margin: 0.2em 0 0 1em;}
@ -148,55 +148,6 @@
</filter> </filter>
</g> </g>
<g id="hatching">
<pattern id="hatch0" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch2" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch3" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch4" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch5" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch6" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="1" style="fill: black"/>
</pattern>
<pattern id="hatch7" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" transform="rotate(-45 0 0)" style="stroke:black; stroke-width:1.5" />
</pattern>
<pattern id="hatch8" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch9" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch10" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch11" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="1" x2="0" y2="3" style="stroke:black; stroke-width:1.5" />
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5" />
</pattern>
<pattern id="hatch13" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5" />
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5" />
</pattern>
</g>
<g id="deftemp"> <g id="deftemp">
<mask id="land"></mask> <mask id="land"></mask>
<mask id="water"> <mask id="water">
@ -226,7 +177,7 @@
<div id="loading"> <div id="loading">
<div id="titleName"><t data-t="titleName">Azgaar's</t></div> <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="title"><t data-t="title">Fantasy Map Generator</t></div>
<div id="version"><t data-t="version">v. </t>1.71</div> <div id="version"><t data-t="version">v. </t>1.72</div>
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p> <p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
</div> </div>
@ -309,17 +260,9 @@
<div id="styleContent" class="tabcontent"> <div id="styleContent" class="tabcontent">
<p data-tip="Select a style preset. State labels may required regeneration if font is changed" style="display: inline-block">Style preset:</p> <p data-tip="Select a style preset. State labels may required regeneration if font is changed" style="display: inline-block">Style preset:</p>
<select data-tip="Select a style preset" id="stylePreset" onchange="changeStylePreset(this.value)" style="width:45%"> <select data-tip="Select a style preset" id="stylePreset" onchange="requestStylePresetChange(this.value)" style="width:45%; text-transform: capitalize;"></select>
<option value="styleDefault" data-system=1 selected>Default</option>
<option value="styleAncient" data-system=1>Ancient</option>
<option value="styleGloom" data-system=1>Gloom</option>
<option value="styleClean" data-system=1>Clean</option>
<option value="styleLight" data-system=1>Light</option>
<option value="styleWatercolor" data-system=1>Watercolor</option>
<option value="styleMonochrome" data-system=1>Monochrome</option>
</select>
<button id="addStyleButton" data-tip="Click to save current style as a new preset" class="icon-plus sideButton" style="display: inline-block" onclick="addStylePreset()"></button> <button id="addStyleButton" data-tip="Click to save current style as a new preset" class="icon-plus sideButton" style="display: inline-block" onclick="addStylePreset()"></button>
<button id="removeStyleButton" data-tip="Click to remove current custom style preset" class="icon-minus sideButton" style="display: none" onclick="removeStylePreset()"></button> <button id="removeStyleButton" data-tip="Click to remove current custom style preset" class="icon-minus sideButton" style="display: none" onclick="requestRemoveStylePreset()"></button>
<p data-tip="Select an element to edit its style" style="display: inline-block;">Select element:</p> <p data-tip="Select an element to edit its style" style="display: inline-block;">Select element:</p>
<select data-tip="Select an element to edit its style (list is ordered alphabetically)" id="styleElementSelect" style="width:42%"> <select data-tip="Select an element to edit its style (list is ordered alphabetically)" id="styleElementSelect" style="width:42%">
@ -947,7 +890,7 @@
<tr data-tip="Map seed number. Seed produces the same map only if canvas size and options are the same"> <tr data-tip="Map seed number. Seed produces the same map only if canvas size and options are the same">
<td> <td>
<i data-tip="Show seed history to apply a previous seed" id="optionsMapHistory" class="icon-history"></i> <i data-tip="Show seed history to apply a previous seed" id="optionsMapHistory" class="icon-hourglass-1"></i>
</td> </td>
<td>Map seed</td> <td>Map seed</td>
<td> <td>
@ -1062,7 +1005,7 @@
<input id="regionsInput" data-stored="regions" type="range" min=0 max=99 value=13> <input id="regionsInput" data-stored="regions" type="range" min=0 max=99 value=13>
</td> </td>
<td> <td>
<input id="regionsOutput" data-stored="regions" type="number" min=0 max=99 value=13> <input id="regionsOutput" data-stored="regions" type="number" min=0 max=999 value=13>
</td> </td>
</tr> </tr>
@ -1446,9 +1389,21 @@
</div> </div>
<div id="aboutContent" class="tabcontent"> <div id="aboutContent" class="tabcontent">
<p><a href="https://github.com/Azgaar/Fantasy-Map-Generator" target="_blank">Fantasy Map Generator</a> is a free <a href="https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE" target="_blank">open source</a> tool which procedurally generates fantasy maps. You may use auto-generated maps as they are, edit them or even create a new map from scratch. Check out the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial" target="_blank">quick start tutorial</a>, <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A" target="_blank">Q&A</a> and <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys" target="_blank">hotkeys</a> for guidance.</p> <p>
<p>Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share maps.</p> <a href="https://github.com/Azgaar/Fantasy-Map-Generator" target="_blank">Fantasy Map Generator</a> is a free <a href="https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE" target="_blank">open source</a> tool which procedurally generates fantasy maps.
<p>The project is under active development. Creator and main maintainer: Azgaar. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>. For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:azgaar.fmg@yandex.by" target="_blank">email</a>.</p> You may use auto-generated maps as they are, edit them or even create a new map from scratch.
Check out the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial" target="_blank">Quick start</a>, <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A" target="_blank">Q&A</a>, <a href="https://youtube.com/playlist?list=PLtgiuDC8iVR2gIG8zMTRn7T_L0arl9h1C" target="_blank">Video tutorial</a>, and <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys" target="_blank">hotkeys</a> for guidance.
</p>
<p>
Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share maps.
</p>
<p>
The project is under active development. Creator and main maintainer: Azgaar. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>.
For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:azgaar.fmg@yandex.by" target="_blank">email</a>.
</p>
<div style="background-color: #e85b46; padding: .4em; width: max-content; margin: .6em auto 0 auto; border: 1px solid #943838"> <div style="background-color: #e85b46; padding: .4em; width: max-content; margin: .6em auto 0 auto; border: 1px solid #943838">
<a href="https://www.patreon.com/azgaar" target="_blank" style="color: white; text-decoration: none; font-family: sans-serif"> <a href="https://www.patreon.com/azgaar" target="_blank" style="color: white; text-decoration: none; font-family: sans-serif">
<div> <div>
@ -1461,7 +1416,10 @@
</div> </div>
</a> </a>
</div> </div>
<p>Special thanks to <a data-tip="Click to see list of supporters" onclick="showSupporters()">all supporters</a> on Patreon!</p>
<p>
Special thanks to <a data-tip="Click to see list of supporters" onclick="showSupporters()">all supporters</a> on Patreon!
</p>
<ul class="share-buttons"> <ul class="share-buttons">
<li><a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator%2F&quote=" data-tip="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="images/Facebook.png" /></a></li> <li><a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator%2F&quote=" data-tip="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="images/Facebook.png" /></a></li>
@ -1784,7 +1742,7 @@
<span data-tip="Set curve profile">Curve: <span data-tip="Set curve profile">Curve:
<select id="epCurve"> <select id="epCurve">
<option>Linear</option> <option>Linear</option>
<option selected>Basis spline</option> <option selected>Basis spline</option>
<option>Bundle</option> <option>Bundle</option>
<option>Cubic Catmull-Rom</option> <option>Cubic Catmull-Rom</option>
<option>Monotone X</option> <option>Monotone X</option>
@ -1975,69 +1933,73 @@
<div id="burgBody" style="padding-bottom: .3em"> <div id="burgBody" style="padding-bottom: .3em">
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<svg data-tip="Burg emblem. Click to edit" class="pointer" viewBox="0 0 200 200" width="13em" height="13em"><use id="burgEmblem"></use></svg> <svg data-tip="Burg emblem. Click to edit" class="pointer" viewBox="0 0 200 200" width="13em" height="13em"><use id="burgEmblem"></use></svg>
<div> <div style="display: grid; grid-auto-rows: minmax(1.6em, auto)">
<div id="burgProvinceAndState" style="font-style: italic; max-width: 16em"></div> <div id="burgProvinceAndState" style="font-style: italic; max-width: 16em"></div>
<div> <div>
<div class="label">Name:</div> <div class="label">Name:</div>
<input id="burgName" data-tip="Type to rename the burg" autocorrect="off" spellcheck="false" style="width: 8em"> <input id="burgName" data-tip="Type to rename the burg" autocorrect="off" spellcheck="false" style="width: 8em">
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span> <span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
<span id="burgNameReRandom" data-tip="Generate random name for the burg" class="icon-globe pointer"></span> <span id="burgNameReRandom" data-tip="Generate random name for the burg" class="icon-globe pointer"></span>
</div> </div>
<div data-tip="Select burg type. Type slightly affects emblem generation"> <div data-tip="Select burg type. Type slightly affects emblem generation">
<div class="label">Type:</div> <div class="label">Type:</div>
<select id="burgType" style="width: 8em"> <select id="burgType" style="width: 8em">
<option value="Generic">Generic</option> <option value="Generic">Generic</option>
<option value="River">River</option> <option value="River">River</option>
<option value="Lake">Lake</option> <option value="Lake">Lake</option>
<option value="Naval">Naval</option> <option value="Naval">Naval</option>
<option value="Nomadic">Nomadic</option> <option value="Nomadic">Nomadic</option>
<option value="Hunting">Hunting</option> <option value="Hunting">Hunting</option>
<option value="Highland">Highland</option> <option value="Highland">Highland</option>
</select> </select>
</div> </div>
<div data-tip="Select dominant culture"> <div data-tip="Select dominant culture">
<div class="label">Culture:</div> <div class="label">Culture:</div>
<select id="burgCulture" style="width: 8em"></select> <select id="burgCulture" style="width: 8em"></select>
<span id="burgNameReCulture" data-tip="Generate culture-specific name for the burg" class="icon-book pointer"></span> <span id="burgNameReCulture" data-tip="Generate culture-specific name for the burg" class="icon-book pointer"></span>
</div> </div>
<div data-tip="Set burg population"> <div data-tip="Set burg population">
<div class="label">Population:</div> <div class="label">Population:</div>
<input id="burgPopulation" type="number" min=0 step=1 style="width: 8em"> <input id="burgPopulation" type="number" min=0 step=1 style="width: 8em">
</div> </div>
<div> <div>
<div class="label">Features:</div> <div class="label">Features:</div>
<span id="burgCapital" data-tip="Shows whether the burg is a state capital. Click to toggle" data-feature="capital" class="burgFeature icon-star"></span> <span id="burgCapital" data-tip="Shows whether the burg is a state capital. Click to toggle" data-feature="capital" class="burgFeature icon-star"></span>
<span id="burgPort" data-tip="Shows whether the burg is a port. Click to toggle" data-feature="port" class="burgFeature icon-anchor"></span> <span id="burgPort" data-tip="Shows whether the burg is a port. Click to toggle" data-feature="port" class="burgFeature icon-anchor"></span>
<span id="burgCitadel" data-tip="Shows whether the burg has a citadel (castle). Click to toggle" data-feature="citadel" class="burgFeature icon-chess-rook" style="font-size: 1.1em"></span> <span id="burgCitadel" data-tip="Shows whether the burg has a citadel (castle). Click to toggle" data-feature="citadel" class="burgFeature icon-chess-rook" style="font-size: 1.1em"></span>
<span id="burgWalls" data-tip="Shows whether the burg is walled. Click to toggle" data-feature="walls" class="burgFeature icon-fort-awesome"></span> <span id="burgWalls" data-tip="Shows whether the burg is walled. Click to toggle" data-feature="walls" class="burgFeature icon-fort-awesome"></span>
<span id="burgPlaza" data-tip="Shows whether the burg is a trade center (has big marketplace). Click to toggle" data-feature="plaza" class="burgFeature icon-store" style="font-size: 1em"></span> <span id="burgPlaza" data-tip="Shows whether the burg is a trade center (has big marketplace). Click to toggle" data-feature="plaza" class="burgFeature icon-store" style="font-size: 1em"></span>
<span id="burgTemple" data-tip="Shows whether the burg is a religious center. Click to toggle" data-feature="temple" class="burgFeature icon-chess-bishop" style="font-size: 1.1em; margin-left: 3px"></span> <span id="burgTemple" data-tip="Shows whether the burg is a religious center. Click to toggle" data-feature="temple" class="burgFeature icon-chess-bishop" style="font-size: 1.1em; margin-left: 3px"></span>
<span id="burgShanty" data-tip="Shows whether the burg has a shanty town. Click to toggle" data-feature="shanty" class="burgFeature icon-campground" style="font-size: 1em"></span> <span id="burgShanty" data-tip="Shows whether the burg has a shanty town. Click to toggle" data-feature="shanty" class="burgFeature icon-campground" style="font-size: 1em"></span>
</div> </div>
<div data-tip="Burg mean annual temperature and real-world city for comparison"> <div data-tip="Burg mean annual temperature and real-world city for comparison">
<div class="label">Temperature:</div> <div class="label">Temperature:</div>
<span id="burgTemperature"></span>, like in <span id="burgTemperature"></span>, like in
<span id="burgTemperatureLikeIn"></span> <span id="burgTemperatureLikeIn"></span>
</div> <i id="burgTemperatureGraph" data-tip="Show temperature graph for the burg" class="icon-chart-area pointer"></i>
</div>
<div data-tip="Burg height above mean sea level"> <div data-tip="Burg height above mean sea level">
<div class="label">Elevation:</div> <div class="label">Elevation:</div>
<span id="burgElevation"></span> above sea level <span id="burgElevation"></span> above sea level
</div> </div>
</div> </div>
</div> </div>
<div id="mfcgPreviewSection" data-tip="Burg preview in the Medieval Fantasy City Generator. Default seed is a combination of map seed and burg id" style="display: flex; flex-direction: column"> <div id="mfcgPreviewSection" data-tip="Burg preview in the Medieval Fantasy City Generator. Default seed is a combination of map seed and burg id" style="display: flex; flex-direction: column">
<div> <div>
See in <a id="mfcgLink" target="_blank">City Generator by Watabou</a>. See in <a id="mfcgLink" target="_blank">City Generator by Watabou</a>.
Seed: <input id="mfcgBurgSeed" style="width: 10em" type="number" min=1 max="1e13" step="1" /> <div id="mfcgBurgSeedSection">
<i id="regenerateMFCGBurgSeed" data-tip="Randomize Medieval Fantasy City Generator burg seed" class="icon-arrows-cw pointer" style="margin-left: .1em"></i> Seed: <input id="mfcgBurgSeed" style="width: 10em" type="number" min=1 max="1e13" step="1" />
<i id="regenerateMFCGBurgSeed" data-tip="Randomize Medieval Fantasy City Generator burg seed" class="icon-arrows-cw pointer" style="margin-left: .1em"></i>
</div>
<i id="addCustomMFCGBurgLink" data-tip="Provide custom link to the burg map" class="icon-pencil pointer" style="margin-left: .1em"></i>
</div> </div>
<iframe id="mfcgPreview" sandbox="allow-scripts allow-same-origin"></iframe> <iframe id="mfcgPreview" sandbox="allow-scripts allow-same-origin"></iframe>
</div> </div>
@ -2618,8 +2580,10 @@
<option value="Sultanate">Sultanate</option> <option value="Sultanate">Sultanate</option>
<option value="Tsardom">Tsardom</option> <option value="Tsardom">Tsardom</option>
<option value="Ulus">Ulus</option> <option value="Ulus">Ulus</option>
<option value="Viceroyalty">Viceroyalty</option>
</optgroup> </optgroup>
<optgroup label="Republic"> <optgroup label="Republic">
<option value="Chancellery">Chancellery</option>
<option value="City-state">City-state</option> <option value="City-state">City-state</option>
<option value="Diarchy">Diarchy</option> <option value="Diarchy">Diarchy</option>
<option value="Federation">Federation</option> <option value="Federation">Federation</option>
@ -2657,8 +2621,10 @@
<option value="Divine Kingdom">Divine Kingdom</option> <option value="Divine Kingdom">Divine Kingdom</option>
<option value="Divine Empire">Divine Empire</option> <option value="Divine Empire">Divine Empire</option>
<option value="Eparchy">Eparchy</option> <option value="Eparchy">Eparchy</option>
<option value="Exarchate">Exarchate</option>
<option value="Holy State">Holy State</option> <option value="Holy State">Holy State</option>
<option value="Imamah">Imamah</option> <option value="Imamah">Imamah</option>
<option value="Patriarchate">Patriarchate</option>
<option value="Theocracy">Theocracy</option> <option value="Theocracy">Theocracy</option>
</optgroup> </optgroup>
<optgroup label="Anarchy"> <optgroup label="Anarchy">
@ -2735,33 +2701,27 @@
<div id="diplomacyEditor" class="dialog stable" style="display: none"> <div id="diplomacyEditor" class="dialog stable" style="display: none">
<div id="diplomacyHeader" class="header"> <div id="diplomacyHeader" class="header">
<div style="left:.2em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="name">State&nbsp;</div> <div style="left:.2em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="name">State&nbsp;</div>
<div style="left:13.4em" data-tip="Click to sort by diplomatical relations" class="sortable alphabetically" data-sortby="relations">Relations&nbsp;</div> <div style="left:14.4em" data-tip="Click to sort by diplomatical relations" class="sortable alphabetically" data-sortby="relations">Relations&nbsp;</div>
</div> </div>
<div id="diplomacyBodySection" class="table"></div> <div id="diplomacyBodySection" class="table"></div>
<div class="info-line">Click on state name to see relations.<br>Click on relations name to change it</div>
<div id="diplomacySelect">
<div data-tip="Ally means states formed a defensive pact and will protect each other in case of third party aggression">Ally</div>
<div data-tip="State is friendly to anouther state when they share some common interests">Friendly</div>
<div data-tip="Neutral means states relations are neither positive nor negative">Neutral</div>
<div data-tip="Suspicion means state has a cautious distrust of another state">Suspicion</div>
<div data-tip="Enemies are states at war with each other">Enemy</div>
<div data-tip="Relations are unknown if states do not have enough information about each other">Unknown</div>
<div data-tip="Rivalry is a state of competing for dominance in the region">Rival</div>
<div data-tip="Vassal is a state having obligation to its suzerain">Vassal</div>
<div data-tip="Suzerain is a state having some control over its vassals">Suzerain</div>
</div>
<div id="diplomacyBottom" style="margin-top: .1em"> <div id="diplomacyBottom" style="margin-top: .1em">
<button id="diplomacyEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button> <button id="diplomacyEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="diplomacyEditStyle" data-tip="Edit states (including diplomacy view) style in Style Editor" class="icon-adjust"></button> <button id="diplomacyEditStyle" data-tip="Edit states (including diplomacy view) style in Style Editor" class="icon-adjust"></button>
<button id="diplomacyRegenerate" data-tip="Regenerate diplomatical relations" class="icon-retweet"></button> <button id="diplomacyRegenerate" data-tip="Regenerate diplomatical relations" class="icon-retweet"></button>
<button id="diplomacyReset" data-tip="Reset diplomatical relations of selected state to Neutral" class="icon-eraser"></button>
<button id="diplomacyHistory" data-tip="Show relations history" class="icon-hourglass-1"></button> <button id="diplomacyHistory" data-tip="Show relations history" class="icon-hourglass-1"></button>
<button id="diplomacyMatrix" data-tip="Show relations matrix" class="icon-list-bullet"></button> <button id="diplomacyShowMatrix" data-tip="Show relations matrix" class="icon-list-bullet"></button>
<button id="diplomacyExport" data-tip="Save state relations matrix as a text file (.csv)" class="icon-download"></button> <button id="diplomacyExport" data-tip="Save state relations matrix as a text file (.csv)" class="icon-download"></button>
</div> </div>
</div> </div>
<div id="diplomacyMatrix" class="dialog" style="display: none">
<div id="diplomacyMatrixBody" class="matrix-table"></div>
</div>
<div id="provinceNameEditor" class="dialog" data-province="0" style="display: none"> <div id="provinceNameEditor" class="dialog" data-province="0" style="display: none">
<div> <div>
<div data-tip="Province short name" class="label">Short name:</div> <div data-tip="Province short name" class="label">Short name:</div>
@ -2779,6 +2739,8 @@
<option value="Autonomy">Autonomy</option> <option value="Autonomy">Autonomy</option>
<option value="Barony">Barony</option> <option value="Barony">Barony</option>
<option value="Canton">Canton</option> <option value="Canton">Canton</option>
<option value="Captaincy">Captaincy</option>
<option value="Chiefdom">Chiefdom</option>
<option value="Clan">Clan</option> <option value="Clan">Clan</option>
<option value="Colony">Colony</option> <option value="Colony">Colony</option>
<option value="Council">Council</option> <option value="Council">Council</option>
@ -2786,6 +2748,7 @@
<option value="Deanery">Deanery</option> <option value="Deanery">Deanery</option>
<option value="Department">Department</option> <option value="Department">Department</option>
<option value="Dependency">Dependency</option> <option value="Dependency">Dependency</option>
<option value="Diaconate">Diaconate</option>
<option value="District">District</option> <option value="District">District</option>
<option value="Earldom">Earldom</option> <option value="Earldom">Earldom</option>
<option value="Governorate">Governorate</option> <option value="Governorate">Governorate</option>
@ -2795,6 +2758,7 @@
<option value="Landgrave">Landgrave</option> <option value="Landgrave">Landgrave</option>
<option value="Mandate">Mandate</option> <option value="Mandate">Mandate</option>
<option value="Margrave">Margrave</option> <option value="Margrave">Margrave</option>
<option value="Municipality">Municipality</option>
<option value="Occupation zone">Occupation zone</option> <option value="Occupation zone">Occupation zone</option>
<option value="Parish">Parish</option> <option value="Parish">Parish</option>
<option value="Prefecture">Prefecture</option> <option value="Prefecture">Prefecture</option>
@ -2802,6 +2766,7 @@
<option value="Region">Region</option> <option value="Region">Region</option>
<option value="Republic">Republic</option> <option value="Republic">Republic</option>
<option value="Reservation">Reservation</option> <option value="Reservation">Reservation</option>
<option value="Seneschalty">Seneschalty</option>
<option value="Shire">Shire</option> <option value="Shire">Shire</option>
<option value="State">State</option> <option value="State">State</option>
<option value="Territory">Territory</option> <option value="Territory">Territory</option>
@ -2869,21 +2834,22 @@
<div id="namesbaseBasesTop"> <div id="namesbaseBasesTop">
<span>Select base: </span> <span>Select base: </span>
<select id="namesbaseSelect" data-tip="Select base to edit" style="width: 12em" value="0"></select> <select id="namesbaseSelect" data-tip="Select base to edit" style="width: 12em" value="0"></select>
<span style="margin-left: 2px">Names data: </span>
</div> </div>
<div id="namesbaseBody"> <div id="namesbaseBody" style="margin-block: 2px">
<span>Names data:</span><br> <textarea id="namesbaseTextarea" data-base="0" rows=13 data-tip="Names data: a comma separated list of source names used for names generation" placeholder="Provide a names data: a comma separated list of source names" autocorrect="off" spellcheck="false"></textarea>
<textarea id="namesbaseTextarea" data-base="0" rows=12 data-tip="Names data: a comma separated list of source names used for names generation" placeholder="Provide a names data: a comma separated list of source names" autocorrect="off" spellcheck="false"></textarea>
<br>
<div> <div>
<span>Name: </span> <span>Name: </span>
<input id="namesbaseName" data-tip="Type to change a base name" placeholder="Base name" autocorrect="off" spellcheck="false" style="width:12em"/> <input id="namesbaseName" data-tip="Type to change a base name" placeholder="Base name" autocorrect="off" spellcheck="false" style="width:12em"/>
<span>Length: </span> <span>Length: </span>
<input id="namesbaseMin" data-tip="Recommended minimum name length" type="number" min=2 max=100> <input id="namesbaseMin" data-tip="Recommended minimum name length" type="number" min=2 max=100>
<input id="namesbaseMax" data-tip="Recommended maximum name length" type="number" min=2 value=10> <input id="namesbaseMax" data-tip="Recommended maximum name length" type="number" min=2 value=10>
<span>Double: </span> <span>Doubled: </span>
<input id="namesbaseDouble" data-tip="Populate with letters that can used twice in a row (geminates)" autocorrect="off" spellcheck="false" style="width:10em"> <input id="namesbaseDouble" data-tip="Populate with letters that can used twice in a row (geminates)" autocorrect="off" spellcheck="false" style="width:10em">
</div> </div>
<fieldset> <fieldset>
<legend>Generated examples: </legend> <legend>Generated examples: </legend>
<div id="namesbaseExamples" data-tip="Examples. Click to re-generate"></div> <div id="namesbaseExamples" data-tip="Examples. Click to re-generate"></div>
@ -2892,12 +2858,12 @@
<div id="namesbaseBottom"> <div id="namesbaseBottom">
<button id="namesbaseUpdateExamples" data-tip="Re-generate examples based on provided data" class="icon-arrows-cw"></button> <button id="namesbaseUpdateExamples" data-tip="Re-generate examples based on provided data" class="icon-arrows-cw"></button>
<button id="namesbaseAnalize" data-tip="Analyze namesbase to get a validity and quality overview" class="icon-flask"></button>
<button id="namesbaseAdd" data-tip="Add new namesbase" class="icon-plus"></button> <button id="namesbaseAdd" data-tip="Add new namesbase" class="icon-plus"></button>
<button id="namesbaseDefault" data-tip="Restore default namesbase" class="icon-cancel"></button> <button id="namesbaseDefault" data-tip="Restore default namesbase" class="icon-cancel"></button>
<button id="namesbaseDownload" data-tip="Download namesbase to PC" class="icon-download"></button> <button id="namesbaseDownload" data-tip="Download namesbase to PC" class="icon-download"></button>
<button id="namesbaseUpload" data-tip="Upload a namesbase from PC" class="icon-upload"></button> <button id="namesbaseUpload" data-tip="Upload a namesbase from PC" class="icon-upload"></button>
<button id="namesbaseCA" data-tip="Find or share custom namesbase on Cartography Assets portal" class="icon-drafting-compass" onclick="openURL('https://cartographyassets.com/asset-category/specific-assets/azgaars-generator/namebases/')"></button> <button id="namesbaseCA" data-tip="Find or share custom namesbase on Cartography Assets portal" class="icon-drafting-compass"></button>
<button id="namesbaseAnalyze" data-tip="Analyze namesbase to get a validity and quality overview" class="icon-flask"></button>
<button id="namesbaseSpeak" data-tip="Speak the examples. You can change voice and language in options" class="icon-voice"></button> <button id="namesbaseSpeak" data-tip="Speak the examples. You can change voice and language in options" class="icon-voice"></button>
</div> </div>
</div> </div>
@ -3310,6 +3276,7 @@
<button id="addNewBurg" data-tip="Add a new burg. Hold Shift to add multiple" class="icon-plus"></button> <button id="addNewBurg" data-tip="Add a new burg. Hold Shift to add multiple" class="icon-plus"></button>
<button id="burgsExport" data-tip="Save burgs-related data as a text file (.csv)" class="icon-download"></button> <button id="burgsExport" data-tip="Save burgs-related data as a text file (.csv)" class="icon-download"></button>
<button id="burgNamesImport" data-tip="Rename burgs in bulk" class="icon-upload"></button> <button id="burgNamesImport" data-tip="Rename burgs in bulk" class="icon-upload"></button>
<button id="burgsLockAll" data-tip="Lock or unlock all burgs" class="icon-lock"></button>
<button id="burgsRemoveAll" data-tip="Remove all unlocked burgs except for capitals. To remove a capital remove its state first" class="icon-trash"></button> <button id="burgsRemoveAll" data-tip="Remove all unlocked burgs except for capitals. To remove a capital remove its state first" class="icon-trash"></button>
</div> </div>
</div> </div>
@ -3609,7 +3576,19 @@
</div> </div>
<p>GeoJSON format is used in GIS tools such as QGIS. Check out <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export" target="_blank">wiki-page</a> for guidance.</p> <p>GeoJSON format is used in GIS tools such as QGIS. Check out <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export" target="_blank">wiki-page</a> for guidance.</p>
<p>Generator uses pop-up window to download files. Please ensure your browser does not block popups.</p> <p>Generator uses pop-up window to download files. Please ensure your browser does not block popups.</p>
<p>It's also possible to export map to <i>Foundry VTT</i>, see <a href="https://github.com/Ethck/azgaar-foundry" target="_blank">the module.</a></p>
<div style="margin: 1em 0 .3em; font-weight: bold">Export To JSON</div>
<div>
<button onclick="exportToJson('Full')" data-tip="Download full data as in JSON format">full</button>
<button onclick="exportToJson('Minimal')" data-tip="Download minimal data as in JSON format">minimal</button>
<button onclick="exportToJson('PackCells')" data-tip="Download map metadata and pack cells data as in JSON format">pack cells</button>
<button onclick="exportToJson('GridCells')" data-tip="Download map metadata and grid cells data as in JSON format">grid cells</button>
</div>
<p>Export in JSON format can be used as an API replacement.</p>
<div>
<p>It's also possible to export map to <i>Foundry VTT</i>, see <a href="https://github.com/Ethck/azgaar-foundry" target="_blank">the module.</a></p>
</div>
</div> </div>
<div id="saveMapData" style="display: none" class="dialog"> <div id="saveMapData" style="display: none" class="dialog">
@ -3662,8 +3641,7 @@
<div data-tip="Image scale relative to image size (e.g. 5x)" style="margin-bottom: .3em"> <div data-tip="Image scale relative to image size (e.g. 5x)" style="margin-bottom: .3em">
<div class="label">Scale:</div> <div class="label">Scale:</div>
<input id="tileScaleInput" data-stored="tileScale" type="range" min=1 max=4 value=1 style="width: 10em"> <input id="tileScaleInput" data-stored="tileScale" type="range" min=1 max=4 value=1 style="width: 10em">
<input id="tileScaleOutput" data-stored="tileScale" type="number" min=1 value=1 <input id="tileScaleOutput" data-stored="tileScale" type="number" min=1 value=1 >
>
</div> </div>
<div data-tip="Calculated size of image if combined" style="margin-bottom: .3em"> <div data-tip="Calculated size of image if combined" style="margin-bottom: .3em">
<div class="label">Total size:</div> <div class="label">Total size:</div>
@ -3679,7 +3657,7 @@
<div id="prompt" style="display: none" class="dialog"> <div id="prompt" style="display: none" class="dialog">
<form id="promptForm"> <form id="promptForm">
<div id="promptText"></div> <div id="promptText"></div>
<input id="promptInput" type="number" step=.01 placeholder="type value" autocomplete="off" required> <input id="promptInput" type="number" step=.01 placeholder="type value" autocomplete="off">
<button type="submit">Confirm</button> <button type="submit">Confirm</button>
<button type="button" id="promptCancel" formnovalidate>Cancel</button> <button type="button" id="promptCancel" formnovalidate>Cancel</button>
</form> </form>
@ -4355,6 +4333,153 @@
<path d="M 43.4,0 36.2,12.5 43.4,25 M 21.7,12.5 H 36.2 Z M 0,0 H 14.5 L 21.7,12.5 14.5,25 H 0"/> <path d="M 43.4,0 36.2,12.5 43.4,25 M 21.7,12.5 H 36.2 Z M 0,0 H 14.5 L 21.7,12.5 14.5,25 H 0"/>
</pattern> </pattern>
</g> </g>
<g id="defs-hatching">
<pattern id="hatch0" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch2" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch3" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch4" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch5" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch6" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="1" style="fill: black"/>
</pattern>
<pattern id="hatch7" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" transform="rotate(-45 0 0)" style="stroke:black; stroke-width:1.5" />
</pattern>
<pattern id="hatch8" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch9" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch10" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch11" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="1" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch13" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch14" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch15" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch16" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch17" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch18" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch19" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch20" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch21" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch22" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="2" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch23" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch24" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="2" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch25" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch26" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch27" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch28" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="2" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch29" patternTransform="rotate(30 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="1" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch30" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch31" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch32" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="0.5" style="fill: black"/>
</pattern>
<pattern id="hatch33" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="1.5" style="fill: black"/>
</pattern>
<pattern id="hatch34" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="3" cy="3" r="1" style="fill: black"/>
<circle cx="1" cy="1" r="1" style="fill: black"/>
</pattern>
<pattern id="hatch35" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="3" cy="3" r="1.5" style="fill: black"/>
<circle cx="1" cy="1" r="1.5" style="fill: black"/>
</pattern>
<pattern id="hatch36" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" transform="rotate(-45 0 0)" style="stroke:black; stroke-width:1.5" />
</pattern>
<pattern id="hatch37" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" transform="rotate(-45 0 0)" style="stroke:black; stroke-width:1.5" />
</pattern>
<pattern id="hatch38" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="4" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch39" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="4" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch40" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="3" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch41" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="3" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
</g>
</defs> </defs>
</svg> </svg>
@ -4395,18 +4520,20 @@
<script src="modules/fonts.js"></script> <script src="modules/fonts.js"></script>
<script src="modules/ui/layers.js"></script> <script src="modules/ui/layers.js"></script>
<script src="modules/ui/measurers.js"></script> <script src="modules/ui/measurers.js"></script>
<script src="modules/ui/stylePresets.js"></script>
<!-- <script src="libs/umami.js"></script> --> <!-- <script src="libs/umami.js"></script> -->
<script defer src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script> <script src="modules/ui/general.js"></script>
<script defer src="modules/ui/general.js"></script> <script src="modules/ui/options.js"></script>
<script defer src="modules/ui/options.js"></script> <script src="main.js"></script>
<script defer src="modules/ui/style.js"></script>
<script defer src="modules/load.js"></script> <script defer src="modules/load.js"></script>
<script defer src="modules/cloud.js"></script> <script defer src="modules/cloud.js"></script>
<script defer src="main.js"></script> <script defer src="modules/export-json.js"></script>
<script defer src="modules/save.js"></script> <script defer src="modules/save.js"></script>
<script defer src="modules/export.js"></script> <script defer src="modules/export.js"></script>
<script defer src="modules/relief-icons.js"></script> <script defer src="modules/relief-icons.js"></script>
<script defer src="modules/ui/style.js"></script>
<script defer src="modules/ui/tools.js"></script> <script defer src="modules/ui/tools.js"></script>
<script defer src="modules/ui/world-configurator.js"></script> <script defer src="modules/ui/world-configurator.js"></script>
<script defer src="modules/ui/editors.js"></script> <script defer src="modules/ui/editors.js"></script>
@ -4417,6 +4544,7 @@
<script defer src="modules/ui/cultures-editor.js"></script> <script defer src="modules/ui/cultures-editor.js"></script>
<script defer src="modules/ui/namesbase-editor.js"></script> <script defer src="modules/ui/namesbase-editor.js"></script>
<script defer src="modules/ui/elevation-profile.js"></script> <script defer src="modules/ui/elevation-profile.js"></script>
<script defer src="modules/ui/temperature-graph.js"></script>
<script defer src="modules/ui/routes-editor.js"></script> <script defer src="modules/ui/routes-editor.js"></script>
<script defer src="modules/ui/ice-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/lakes-editor.js"></script>
@ -4426,7 +4554,6 @@
<script defer src="modules/ui/rivers-creator.js"></script> <script defer src="modules/ui/rivers-creator.js"></script>
<script defer src="modules/ui/relief-editor.js"></script> <script defer src="modules/ui/relief-editor.js"></script>
<script defer src="modules/ui/religions-editor.js"></script> <script defer src="modules/ui/religions-editor.js"></script>
<script defer src="modules/ui/markers-editor.js"></script>
<script defer src="modules/ui/burg-editor.js"></script> <script defer src="modules/ui/burg-editor.js"></script>
<script defer src="modules/ui/units-editor.js"></script> <script defer src="modules/ui/units-editor.js"></script>
<script defer src="modules/ui/notes-editor.js"></script> <script defer src="modules/ui/notes-editor.js"></script>
@ -4439,13 +4566,18 @@
<script defer src="modules/ui/markers-overview.js"></script> <script defer src="modules/ui/markers-overview.js"></script>
<script defer src="modules/ui/regiment-editor.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/battle-screen.js"></script>
<script defer src="modules/coa-renderer.js"></script>
<script defer src="modules/ui/emblems-editor.js"></script> <script defer src="modules/ui/emblems-editor.js"></script>
<script defer src="modules/ui/markers-editor.js"></script>
<script defer src="modules/ui/3d.js"></script> <script defer src="modules/ui/3d.js"></script>
<script defer src="modules/ui/hotkeys.js"></script> <script defer src="modules/ui/hotkeys.js"></script>
<script defer src="modules/coa-renderer.js"></script>
<script defer src="libs/rgbquant.min.js"></script> <script defer src="libs/rgbquant.min.js"></script>
<script defer src="libs/jquery.ui.touch-punch.min.js"></script> <script defer src="libs/jquery.ui.touch-punch.min.js"></script>
<script defer src="libs/pell.min.js"></script> <script defer src="libs/pell.min.js"></script>
<script defer src="libs/jszip.min.js"></script> <script defer src="libs/jszip.min.js"></script>
<script defer src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
<!-- Web Components -->
<script defer src="components/fill-box.js"></script>
</body> </body>
</html> </html>

92
main.js
View file

@ -1,11 +1,11 @@
// Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2021. MIT License // Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2022. MIT License
// https://github.com/Azgaar/Fantasy-Map-Generator // https://github.com/Azgaar/Fantasy-Map-Generator
"use strict"; "use strict";
const version = "1.71"; // generator version const version = "1.73"; // generator version
document.title += " v" + version; document.title += " v" + version;
// Switches to disable/enable logging features // switches to disable/enable logging features
const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1"; const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
const DEBUG = localStorage.getItem("debug"); const DEBUG = localStorage.getItem("debug");
const INFO = DEBUG || !PRODUCTION; const INFO = DEBUG || !PRODUCTION;
@ -109,7 +109,7 @@ scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", ()
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend()); legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
// main data variables // main data variables
let grid = {}; // initial grapg based on jittered square grid and data let grid = {}; // initial graph based on jittered square grid and data
let pack = {}; // packed graph and data let pack = {}; // packed graph and data
let seed; let seed;
let mapId; let mapId;
@ -161,7 +161,7 @@ let urbanDensity = +document.getElementById("urbanDensityInput").value;
applyStoredOptions(); applyStoredOptions();
// voronoi graph extention, cannot be changed arter generation // voronoi graph extension, cannot be changed after generation
let graphWidth = +mapWidthInput.value; let graphWidth = +mapWidthInput.value;
let graphHeight = +mapHeightInput.value; let graphHeight = +mapHeightInput.value;
@ -173,14 +173,37 @@ landmass.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr
oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight); oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight); oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
// remove loading screen if (!location.hostname) {
d3.select("#loading").transition().duration(4000).style("opacity", 0).remove(); const wiki = "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Run-FMG-locally";
d3.select("#initial").transition().duration(4000).attr("opacity", 0).remove(); alertMessage.innerHTML = `Fantasy Map Generator cannot run serverless.
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1); Follow the <a href="${wiki}" target="_blank">instructions</a> on how you can easily run a local web-server`;
d3.select("#tooltip").transition().duration(4000).style("opacity", 1);
$("#alert").dialog({
resizable: false,
title: "Loading error",
width: "28em",
position: {my: "center center-4em", at: "center", of: "svg"},
buttons: {
OK: function () {
$(this).dialog("close");
}
}
});
d3.select("#loading-text").transition().duration(1000).style("opacity", 0);
d3.select("#init-rose").transition().duration(4000).style("opacity", 0);
} else {
checkLoadParameters();
// remove loading screen
d3.select("#loading").transition().duration(4000).style("opacity", 0).remove();
d3.select("#initial").transition().duration(4000).attr("opacity", 0).remove();
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1);
d3.select("#tooltip").transition().duration(4000).style("opacity", 1);
}
// decide which map should be loaded or generated on page load // decide which map should be loaded or generated on page load
void (function checkLoadParameters() { function checkLoadParameters() {
const url = new URL(window.location.href); const url = new URL(window.location.href);
const params = url.searchParams; const params = url.searchParams;
@ -225,10 +248,10 @@ void (function checkLoadParameters() {
WARN && console.warn("Generate random map"); WARN && console.warn("Generate random map");
generateMapOnLoad(); generateMapOnLoad();
})(); }
function generateMapOnLoad() { async function generateMapOnLoad() {
applyStyleOnLoad(); // apply default or previously selected style await applyStyleOnLoad(); // apply previously selected default or custom style
generate(); // generate map generate(); // generate map
focusOn(); // based on searchParams focus on point, cell or burg from MFCG focusOn(); // based on searchParams focus on point, cell or burg from MFCG
applyPreset(); // apply saved layers preset applyPreset(); // apply saved layers preset
@ -412,20 +435,18 @@ function showWelcomeMessage() {
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>. alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated. This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
<ul>Main changes: <ul><b>Latest changes:</b>
<li>Ability to limit military units by biome, state, culture and religion</li> <li>Color picker: new hatchings</li>
<li>New marker types</li> <li>New style presets: Cyberpunk and Atlas</li>
<li>New markers editor</li> <li>Burg temperature graph</li>
<li>Markers overview screen</li> <li>4 new textures</li>
<li>Markers regeneration menu</li> <li>Province capture logic rework</li>
<li>Burg editor update</li> <li>Button to release all provinces</li>
<li>Editable theme color</li> <li>Limit military units by biome, state, culture and religion</li>
<li>Add font dialog</li>
<li>Save to Dropbox</li>
</ul> </ul>
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p> <p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
<span>Thanks for all supporters on <a href="https://www.patreon.com/azgaar" target="_blank">Patreon</a>!</i></span>`; <span><i>Thanks for all supporters on ${patreon}!</i></span>`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
@ -493,7 +514,7 @@ function invokeActiveZooming() {
coastline.select("#sea_island").attr("filter", filter); coastline.select("#sea_island").attr("filter", filter);
} }
// rescale lables on zoom // rescale labels on zoom
if (labels.style("display") !== "none") { if (labels.style("display") !== "none") {
labels.selectAll("g").each(function () { labels.selectAll("g").each(function () {
if (this.id === "burgLabels") return; if (this.id === "burgLabels") return;
@ -668,7 +689,7 @@ function generate() {
const parsedError = parseError(error); const parsedError = parseError(error);
clearMainTip(); clearMainTip();
alertMessage.innerHTML = `An error is occured on map generation. Please retry. alertMessage.innerHTML = `An error has occurred on map generation. Please retry.
<br>If error is critical, clear the stored data and try again. <br>If error is critical, clear the stored data and try again.
<p id="errorBox">${parsedError}</p>`; <p id="errorBox">${parsedError}</p>`;
$("#alert").dialog({ $("#alert").dialog({
@ -980,7 +1001,10 @@ function generatePrecipitation() {
prec.selectAll("*").remove(); prec.selectAll("*").remove();
const {cells, cellsX, cellsY} = grid; const {cells, cellsX, cellsY} = grid;
cells.prec = new Uint8Array(cells.i.length); // precipitation array cells.prec = new Uint8Array(cells.i.length); // precipitation array
const modifier = precInput.value / 100; // user's input
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
const precInputModifier = precInput.value / 100;
const modifier = cellsNumberModifier * precInputModifier;
const westerly = []; const westerly = [];
const easterly = []; const easterly = [];
@ -996,14 +1020,14 @@ function generatePrecipitation() {
// x2 = 60-70 latitude: wet summer (rising zone), dry winter (sinking zone) // x2 = 60-70 latitude: wet summer (rising zone), dry winter (sinking zone)
// x1 = 70-85 latitude: dry all year (sinking zone) // x1 = 70-85 latitude: dry all year (sinking zone)
// x0.5 = 85-90 latitude: dry all year (sinking zone) // x0.5 = 85-90 latitude: dry all year (sinking zone)
const lalitudeModifier = [4, 2, 2, 2, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 1, 1, 1, 0.5]; const latitudeModifier = [4, 2, 2, 2, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 1, 1, 1, 0.5];
const MAX_PASSABLE_ELEVATION = 85; const MAX_PASSABLE_ELEVATION = 85;
// define wind directions based on cells latitude and prevailing winds there // define wind directions based on cells latitude and prevailing winds there
d3.range(0, cells.i.length, cellsX).forEach(function (c, i) { d3.range(0, cells.i.length, cellsX).forEach(function (c, i) {
const lat = mapCoordinates.latN - (i / cellsY) * mapCoordinates.latT; const lat = mapCoordinates.latN - (i / cellsY) * mapCoordinates.latT;
const latBand = ((Math.abs(lat) - 1) / 5) | 0; const latBand = ((Math.abs(lat) - 1) / 5) | 0;
const latMod = lalitudeModifier[latBand]; const latMod = latitudeModifier[latBand];
const windTier = (Math.abs(lat - 89) / 30) | 0; // 30d tiers from 0 to 5 from N to S const windTier = (Math.abs(lat - 89) / 30) | 0; // 30d tiers from 0 to 5 from N to S
const {isWest, isEast, isNorth, isSouth} = getWindDirections(windTier); const {isWest, isEast, isNorth, isSouth} = getWindDirections(windTier);
@ -1020,14 +1044,14 @@ function generatePrecipitation() {
const vertT = southerly + northerly; const vertT = southerly + northerly;
if (northerly) { if (northerly) {
const bandN = ((Math.abs(mapCoordinates.latN) - 1) / 5) | 0; const bandN = ((Math.abs(mapCoordinates.latN) - 1) / 5) | 0;
const latModN = mapCoordinates.latT > 60 ? d3.mean(lalitudeModifier) : lalitudeModifier[bandN]; const latModN = mapCoordinates.latT > 60 ? d3.mean(latitudeModifier) : latitudeModifier[bandN];
const maxPrecN = (northerly / vertT) * 60 * modifier * latModN; const maxPrecN = (northerly / vertT) * 60 * modifier * latModN;
passWind(d3.range(0, cellsX, 1), maxPrecN, cellsX, cellsY); passWind(d3.range(0, cellsX, 1), maxPrecN, cellsX, cellsY);
} }
if (southerly) { if (southerly) {
const bandS = ((Math.abs(mapCoordinates.latS) - 1) / 5) | 0; const bandS = ((Math.abs(mapCoordinates.latS) - 1) / 5) | 0;
const latModS = mapCoordinates.latT > 60 ? d3.mean(lalitudeModifier) : lalitudeModifier[bandS]; const latModS = mapCoordinates.latT > 60 ? d3.mean(latitudeModifier) : latitudeModifier[bandS];
const maxPrecS = (southerly / vertT) * 60 * modifier * latModS; const maxPrecS = (southerly / vertT) * 60 * modifier * latModS;
passWind(d3.range(cells.i.length - cellsX, cells.i.length, 1), maxPrecS, -cellsX, cellsY); passWind(d3.range(cells.i.length - cellsX, cells.i.length, 1), maxPrecS, -cellsX, cellsY);
} }
@ -1181,7 +1205,7 @@ function reGraph() {
TIME && console.timeEnd("reGraph"); TIME && console.timeEnd("reGraph");
} }
// Detect and draw the coasline // Detect and draw the coastline
function drawCoastline() { function drawCoastline() {
TIME && console.time("drawCoastline"); TIME && console.time("drawCoastline");
reMarkFeatures(); reMarkFeatures();
@ -1190,7 +1214,7 @@ function drawCoastline() {
vertices = pack.vertices, vertices = pack.vertices,
n = cells.i.length, n = cells.i.length,
features = pack.features; features = pack.features;
const used = new Uint8Array(features.length); // store conneted features const used = new Uint8Array(features.length); // store connected features
const largestLand = d3.scan( const largestLand = d3.scan(
features.map(f => (f.land ? f.cells : 0)), features.map(f => (f.land ? f.cells : 0)),
(a, b) => b - a (a, b) => b - a

View file

@ -31,7 +31,7 @@ window.BurgsAndStates = (function () {
function placeCapitals() { function placeCapitals() {
TIME && console.time("placeCapitals"); TIME && console.time("placeCapitals");
let count = +regionsInput.value; let count = +regionsOutput.value;
let burgs = [0]; let burgs = [0];
const rand = () => 0.5 + Math.random() * 0.5; const rand = () => 0.5 + Math.random() * 0.5;
@ -240,7 +240,7 @@ window.BurgsAndStates = (function () {
b.citadel = b.capital || (pop > 50 && P(0.75)) || P(0.5) ? 1 : 0; b.citadel = b.capital || (pop > 50 && P(0.75)) || P(0.5) ? 1 : 0;
b.plaza = pop > 50 || (pop > 30 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.25) ? 1 : 0; b.plaza = pop > 50 || (pop > 30 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.25) ? 1 : 0;
b.walls = b.capital || pop > 30 || (pop > 20 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.2) ? 1 : 0; b.walls = b.capital || pop > 30 || (pop > 20 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.2) ? 1 : 0;
b.shanty = pop > 30 || (pop > 20 && P(0.75)) || (b.walls && P(0.75)) ? 1 : 0; b.shanty = pop > 60 || (pop > 40 && P(0.75)) || (pop > 20 && b.walls && P(0.4)) ? 1 : 0;
const religion = cells.religion[b.cell]; const religion = cells.religion[b.cell];
const theocracy = pack.states[b.state].form === "Theocracy"; const theocracy = pack.states[b.state].form === "Theocracy";
b.temple = (religion && theocracy) || pop > 50 || (pop > 35 && P(0.75)) || (pop > 20 && P(0.5)) ? 1 : 0; b.temple = (religion && theocracy) || pop > 50 || (pop > 35 && P(0.75)) || (pop > 20 && P(0.5)) ? 1 : 0;
@ -726,7 +726,7 @@ window.BurgsAndStates = (function () {
TIME && console.time("assignColors"); TIME && console.time("assignColors");
const colors = ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f"]; // d3.schemeSet2; const colors = ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f"]; // d3.schemeSet2;
// assin basic color using greedy coloring algorithm // assign basic color using greedy coloring algorithm
pack.states.forEach(s => { pack.states.forEach(s => {
if (!s.i || s.removed) return; if (!s.i || s.removed) return;
const neibs = s.neighbors; const neibs = s.neighbors;
@ -962,12 +962,12 @@ window.BurgsAndStates = (function () {
const republic = { const republic = {
Republic: 75, Republic: 75,
Federation: 4, Federation: 4,
Oligarchy: 2, "Trade Company": 4,
"Most Serene Republic": 2, "Most Serene Republic": 2,
Oligarchy: 2,
Tetrarchy: 1, Tetrarchy: 1,
Triumvirate: 1, Triumvirate: 1,
Diarchy: 1, Diarchy: 1,
"Trade Company": 4,
Junta: 1 Junta: 1
}; // weighted random }; // 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 const union = {Union: 3, League: 4, Confederation: 1, "United Kingdom": 1, "United Republic": 1, "United Provinces": 2, Commonwealth: 1, Heptarchy: 1}; // weighted random
@ -997,7 +997,7 @@ window.BurgsAndStates = (function () {
const form = monarchy[tier]; const form = monarchy[tier];
// Default name depends on exponent tier, some culture bases have special names for tiers // Default name depends on exponent tier, some culture bases have special names for tiers
if (s.diplomacy) { if (s.diplomacy) {
if (form === "Duchy" && s.neighbors.length > 1 && rand(6) < s.neighbors.length && s.diplomacy.includes("Vassal")) return "Marches"; // some vassal dutchies on borderland if (form === "Duchy" && s.neighbors.length > 1 && rand(6) < s.neighbors.length && s.diplomacy.includes("Vassal")) return "Marches"; // some vassal duchies on borderland
if (base === 1 && P(0.3) && s.diplomacy.includes("Vassal")) return "Dominion"; // English vassals if (base === 1 && P(0.3) && s.diplomacy.includes("Vassal")) return "Dominion"; // English vassals
if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals
} }
@ -1037,7 +1037,12 @@ window.BurgsAndStates = (function () {
if (tier < 2 && P(0.5)) return "Diocese"; if (tier < 2 && P(0.5)) return "Diocese";
if (tier < 2 && P(0.5)) return "Bishopric"; if (tier < 2 && P(0.5)) return "Bishopric";
} }
if (tier < 2 && P(0.9) && [7, 5].includes(base)) return "Eparchy"; // Greek, Ruthenian if (P(0.9) && [7, 5].includes(base)) {
// Greek, Ruthenian
if (tier < 2) return "Eparchy";
if (tier === 2) return "Exarchate";
if (tier > 2) return "Patriarchate";
}
if (P(0.9) && [21, 16].includes(base)) return "Imamah"; // Nigerian, Turkish if (P(0.9) && [21, 16].includes(base)) return "Imamah"; // Nigerian, Turkish
if (tier > 2 && P(0.8) && [18, 17, 28].includes(base)) return "Caliphate"; // Arabic, Berber, Swahili if (tier > 2 && P(0.8) && [18, 17, 28].includes(base)) return "Caliphate"; // Arabic, Berber, Swahili
return rw(theocracy); return rw(theocracy);
@ -1093,7 +1098,7 @@ window.BurgsAndStates = (function () {
const max = percentage == 100 ? 1000 : gauss(20, 5, 5, 100) * percentage ** 0.5; // max growth const max = percentage == 100 ? 1000 : gauss(20, 5, 5, 100) * percentage ** 0.5; // max growth
const forms = { const forms = {
Monarchy: {County: 11, Earldom: 3, Shire: 1, Landgrave: 1, Margrave: 1, Barony: 1}, Monarchy: {County: 22, Earldom: 6, Shire: 2, Landgrave: 2, Margrave: 2, Barony: 2, Captaincy: 1, Seneschalty: 1},
Republic: {Province: 6, Department: 2, Governorate: 2, District: 1, Canton: 1, Prefecture: 1}, Republic: {Province: 6, Department: 2, Governorate: 2, District: 1, Canton: 1, Prefecture: 1},
Theocracy: {Parish: 3, Deanery: 1}, Theocracy: {Parish: 3, Deanery: 1},
Union: {Province: 1, State: 1, Canton: 1, Republic: 1, County: 1, Council: 1}, Union: {Province: 1, State: 1, Canton: 1, Republic: 1, County: 1, Council: 1},

View file

@ -976,7 +976,7 @@ window.COA = (function () {
if (emblemShape.value === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield; if (emblemShape.value === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield;
if (pack.cultures[culture].shield) return pack.cultures[culture].shield; if (pack.cultures[culture].shield) return pack.cultures[culture].shield;
console.error("Shield shape is not defined on culture level", pack.cultures[culture]); ERROR && console.error("Shield shape is not defined on culture level", pack.cultures[culture]);
return "heater"; return "heater";
}; };

View file

@ -1938,7 +1938,9 @@ window.COArenderer = (function () {
g.setAttribute("id", charge + "_" + id); g.setAttribute("id", charge + "_" + id);
return g.outerHTML; return g.outerHTML;
}) })
.catch(err => console.error(err)); .catch(err => {
ERROR && console.error(err);
});
return fetched; return fetched;
} }

209
modules/export-json.js Normal file
View file

@ -0,0 +1,209 @@
function exportToJson(type) {
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
closeDialogs("#alert");
const typeMap = {
Full: getFullDataJson,
Minimal: getMinimalDataJson,
PackCells: getPackCellsDataJson,
GridCells: getGridCellsDataJson,
};
const mapData = typeMap[type]();
const blob = new Blob([mapData], {type: "application/json"});
const URL = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.download = getFileName(type) + ".json";
link.href = URL;
link.click();
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
window.URL.revokeObjectURL(URL);
}
function getMapInfo() {
const info = {
version,
description: "Azgaar's Fantasy Map Generator output: azgaar.github.io/Fantasy-map-generator",
exportedAt: new Date().toISOString(),
mapName: mapName.value,
seed,
mapId
};
return info;
}
function getSettings() {
const settings = {
distanceUnit: distanceUnitInput.value,
distanceScale: distanceScaleInput.value,
areaUnit: areaUnit.value,
heightUnit: heightUnit.value,
heightExponent: heightExponentInput.value,
temperatureScale: temperatureScale.value,
barSize: barSizeInput.value,
barLabel: barLabel.value,
barBackOpacity: barBackOpacity.value,
barBackColor: barBackColor.value,
barPosX: barPosX.value,
barPosY: barPosY.value,
populationRate: populationRate,
urbanization: urbanization,
mapSize: mapSizeOutput.value,
latitudeO: latitudeOutput.value,
temperatureEquator: temperatureEquatorOutput.value,
temperaturePole: temperaturePoleOutput.value,
prec: precOutput.value,
options: options,
mapName: mapName.value,
hideLabels: hideLabels.checked,
stylePreset: stylePreset.value,
rescaleLabels: rescaleLabels.checked,
urbanDensity: urbanDensity
};
return settings;
}
function getPackCellsData() {
const cellConverted = {
i: Array.from(pack.cells.i),
v: pack.cells.v,
c: pack.cells.c,
p: pack.cells.p,
g: Array.from(pack.cells.g),
h: Array.from(pack.cells.h),
area: Array.from(pack.cells.area),
f: Array.from(pack.cells.f),
t: Array.from(pack.cells.t),
haven: Array.from(pack.cells.haven),
harbor: Array.from(pack.cells.harbor),
fl: Array.from(pack.cells.fl),
r: Array.from(pack.cells.r),
conf: Array.from(pack.cells.conf),
biome: Array.from(pack.cells.biome),
s: Array.from(pack.cells.s),
pop: Array.from(pack.cells.pop),
culture: Array.from(pack.cells.culture),
burg: Array.from(pack.cells.burg),
road: Array.from(pack.cells.road),
crossroad: Array.from(pack.cells.crossroad),
state: Array.from(pack.cells.state),
religion: Array.from(pack.cells.religion),
province: Array.from(pack.cells.province)
};
const cellObjArr = [];
{
cellConverted.i.forEach(value => {
const cellobj = {
i: value,
v: cellConverted.v[value],
c: cellConverted.c[value],
p: cellConverted.p[value],
g: cellConverted.g[value],
h: cellConverted.h[value],
area: cellConverted.area[value],
f: cellConverted.f[value],
t: cellConverted.t[value],
haven: cellConverted.haven[value],
harbor: cellConverted.harbor[value],
fl: cellConverted.fl[value],
r: cellConverted.r[value],
conf: cellConverted.conf[value],
biome: cellConverted.biome[value],
s: cellConverted.s[value],
pop: cellConverted.pop[value],
culture: cellConverted.culture[value],
burg: cellConverted.burg[value],
road: cellConverted.road[value],
crossroad: cellConverted.crossroad[value],
state: cellConverted.state[value],
religion: cellConverted.religion[value],
province: cellConverted.province[value]
};
cellObjArr.push(cellobj);
});
}
const cellsData = {
cells: cellObjArr,
features: pack.features,
cultures: pack.cultures,
burgs: pack.burgs,
states: pack.states,
provinces: pack.provinces,
religions: pack.religions,
rivers: pack.rivers,
markers: pack.markers
};
return cellsData;
}
//data only containing graphical appearance
function getGridCellsData(){
const gridData = {
spacing: grid.spacing,
cellsY: grid.cellsY,
cellsX: grid.cellsX,
points: grid.points,
boundary: grid.boundary
}
return gridData
}
function getFullDataJson() {
TIME && console.time("getFullDataJson");
const info = getMapInfo();
const settings = getSettings();
const cells = getPackCellsData();
const exportData = {info, settings, coords: mapCoordinates, cells, biomes: biomesData, notes, nameBases};
TIME && console.timeEnd("getFullDataJson");
return JSON.stringify(exportData);
}
// data excluding cells
function getMinimalDataJson() {
TIME && console.time("getMinimalDataJson");
const info = getMapInfo();
const settings = getSettings();
const packData = {
features: pack.features,
cultures: pack.cultures,
burgs: pack.burgs,
states: pack.states,
provinces: pack.provinces,
religions: pack.religions,
rivers: pack.rivers,
markers: pack.markers
};
const exportData = {info, settings, coords: mapCoordinates, pack: packData, biomes: biomesData, notes, nameBases};
TIME && console.timeEnd("getMinimalDataJson");
return JSON.stringify(exportData);
}
function getPackCellsDataJson() {
TIME && console.time("getCellsDataJson");
const info = getMapInfo();
const cells = getPackCellsData();
const exportData = {info, cells};
TIME && console.timeEnd("getCellsDataJson");
return JSON.stringify(exportData);
}
function getGridCellsDataJson() {
TIME && console.time("getGridCellsDataJson");
const info = getMapInfo();
const gridCells = getGridCellsData()
const exportData = {info,gridCells};
TIME && console.log("getGridCellsDataJson");
return JSON.stringify(exportData);
}

View file

@ -262,19 +262,19 @@ async function getMapURL(type, options = {}) {
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true)); if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
} }
if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching")?.remove(); // remove unused hatching group
if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths
// add armies style // add armies style
if (cloneEl.getElementById("armies")) if (cloneEl.getElementById("armies")) {
cloneEl.insertAdjacentHTML( cloneEl.insertAdjacentHTML(
"afterbegin", "afterbegin",
"<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>" "<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>"
); );
}
// add xlink: for href to support svg1.1 // add xlink: for href to support svg 1.1
if (type === "svg") { if (type === "svg") {
cloneEl.querySelectorAll("[href]").forEach(el => { cloneEl.querySelectorAll("[href]").forEach(el => {
const href = el.getAttribute("href"); const href = el.getAttribute("href");
@ -283,6 +283,16 @@ async function getMapURL(type, options = {}) {
}); });
} }
// add hatchings
const hatchingUsers = cloneEl.querySelectorAll(`[fill^='url(#hatch']`);
const hatchingFills = unique(Array.from(hatchingUsers).map(el => el.getAttribute("fill")));
const hatchingIds = hatchingFills.map(fill => fill.slice(5, -1));
for (const hatchingId of hatchingIds) {
const hatching = svgDefs.getElementById(hatchingId);
if (hatching) cloneDefs.appendChild(hatching.cloneNode(true));
}
// load fonts
const usedFonts = getUsedFonts(cloneEl); const usedFonts = getUsedFonts(cloneEl);
const fontsToLoad = usedFonts.filter(font => font.src); const fontsToLoad = usedFonts.filter(font => font.src);
if (fontsToLoad.length) { if (fontsToLoad.length) {
@ -405,8 +415,8 @@ function saveGeoJSON_Cells() {
json.features.push(feature); json.features.push(feature);
}); });
const name = getFileName("Cells") + ".geojson"; const fileName = getFileName("Cells") + ".geojson";
downloadFile(JSON.stringify(json), name, "application/json"); downloadFile(JSON.stringify(json), fileName, "application/json");
} }
function saveGeoJSON_Routes() { function saveGeoJSON_Routes() {
@ -421,30 +431,25 @@ function saveGeoJSON_Routes() {
json.features.push(feature); json.features.push(feature);
}); });
const name = getFileName("Routes") + ".geojson"; const fileName = getFileName("Routes") + ".geojson";
downloadFile(JSON.stringify(json), name, "application/json"); downloadFile(JSON.stringify(json), fileName, "application/json");
} }
function saveGeoJSON_Rivers() { function saveGeoJSON_Rivers() {
const json = {type: "FeatureCollection", features: []}; const json = {type: "FeatureCollection", features: []};
rivers.selectAll("path").each(function () { rivers.selectAll("path").each(function () {
const coordinates = getRiverPoints(this); const river = pack.rivers.find(r => r.i === +this.id.slice(5));
const id = this.id; if (!river) return;
const width = +this.dataset.increment;
const increment = +this.dataset.increment;
const river = pack.rivers.find(r => r.i === +id.slice(5));
const name = river ? river.name : "";
const type = river ? river.type : "";
const i = river ? river.i : "";
const basin = river ? river.basin : "";
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, i, basin, name, type, width, increment}}; const coordinates = getRiverPoints(this);
const properties = {...river, id: this.id};
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties};
json.features.push(feature); json.features.push(feature);
}); });
const name = getFileName("Rivers") + ".geojson"; const fileName = getFileName("Rivers") + ".geojson";
downloadFile(JSON.stringify(json), name, "application/json"); downloadFile(JSON.stringify(json), fileName, "application/json");
} }
function saveGeoJSON_Markers() { function saveGeoJSON_Markers() {

View file

@ -14,12 +14,14 @@ const fonts = [
{ {
family: "Almendra SC", family: "Almendra SC",
src: "url(https://fonts.gstatic.com/s/almendrasc/v13/Iure6Yx284eebowr7hbyTaZOrLQ.woff2)", src: "url(https://fonts.gstatic.com/s/almendrasc/v13/Iure6Yx284eebowr7hbyTaZOrLQ.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD" unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
}, },
{ {
family: "Amatic SC", family: "Amatic SC",
src: "url(https://fonts.gstatic.com/s/amaticsc/v11/TUZ3zwprpvBS1izr_vOMscGKfrUC.woff2)", src: "url(https://fonts.gstatic.com/s/amaticsc/v11/TUZ3zwprpvBS1izr_vOMscGKfrUC.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD" unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
}, },
{ {
family: "Architects Daughter", family: "Architects Daughter",
@ -34,7 +36,8 @@ const fonts = [
{ {
family: "Caesar Dressing", family: "Caesar Dressing",
src: "url(https://fonts.gstatic.com/s/caesardressing/v6/yYLx0hLa3vawqtwdswbotmK4vrRHdrz7.woff2)", src: "url(https://fonts.gstatic.com/s/caesardressing/v6/yYLx0hLa3vawqtwdswbotmK4vrRHdrz7.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD" unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
}, },
{ {
family: "Cinzel", family: "Cinzel",
@ -49,7 +52,8 @@ const fonts = [
{ {
family: "Fredericka the Great", family: "Fredericka the Great",
src: "url(https://fonts.gstatic.com/s/frederickathegreat/v6/9Bt33CxNwt7aOctW2xjbCstzwVKsIBVV--Sjxbc.woff2)", src: "url(https://fonts.gstatic.com/s/frederickathegreat/v6/9Bt33CxNwt7aOctW2xjbCstzwVKsIBVV--Sjxbc.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD" unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
}, },
{ {
family: "Gloria Hallelujah", family: "Gloria Hallelujah",
@ -74,12 +78,14 @@ const fonts = [
{ {
family: "MedievalSharp", family: "MedievalSharp",
src: "url(https://fonts.gstatic.com/s/medievalsharp/v9/EvOJzAlL3oU5AQl2mP5KdgptMqhwMg.woff2)", src: "url(https://fonts.gstatic.com/s/medievalsharp/v9/EvOJzAlL3oU5AQl2mP5KdgptMqhwMg.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD" unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
}, },
{ {
family: "Metamorphous", family: "Metamorphous",
src: "url(https://fonts.gstatic.com/s/metamorphous/v7/Wnz8HA03aAXcC39ZEX5y133EOyqs.woff2)", src: "url(https://fonts.gstatic.com/s/metamorphous/v7/Wnz8HA03aAXcC39ZEX5y133EOyqs.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD" unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
}, },
{ {
family: "Montez", family: "Montez",
@ -89,7 +95,8 @@ const fonts = [
{ {
family: "Nova Script", family: "Nova Script",
src: "url(https://fonts.gstatic.com/s/novascript/v10/7Au7p_IpkSWSTWaFWkumvlQKGFw.woff2)", src: "url(https://fonts.gstatic.com/s/novascript/v10/7Au7p_IpkSWSTWaFWkumvlQKGFw.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD" unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
}, },
{ {
family: "Orbitron", family: "Orbitron",
@ -109,12 +116,14 @@ const fonts = [
{ {
family: "Uncial Antiqua", family: "Uncial Antiqua",
src: "url(https://fonts.gstatic.com/s/uncialantiqua/v5/N0bM2S5WOex4OUbESzoESK-i-MfWQZQ.woff2)", src: "url(https://fonts.gstatic.com/s/uncialantiqua/v5/N0bM2S5WOex4OUbESzoESK-i-MfWQZQ.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD" unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
}, },
{ {
family: "Underdog", family: "Underdog",
src: "url(https://fonts.gstatic.com/s/underdog/v6/CHygV-jCElj7diMroWSlWV8.woff2)", src: "url(https://fonts.gstatic.com/s/underdog/v6/CHygV-jCElj7diMroWSlWV8.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD" unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
}, },
{ {
family: "Yellowtail", family: "Yellowtail",
@ -243,7 +252,7 @@ async function addGoogleFont(family) {
}) })
.catch(err => { .catch(err => {
tip(`Failed to load Google font ${family}`, true, "error", 4000); tip(`Failed to load Google font ${family}`, true, "error", 4000);
console.error(err); ERROR && console.error(err);
}); });
} }

View file

@ -145,7 +145,7 @@ function parseLoadedResult(result) {
const mapVersion = parseFloat(mapData[0].split("|")[0] || mapData[0]); const mapVersion = parseFloat(mapData[0].split("|")[0] || mapData[0]);
return [mapData, mapVersion]; return [mapData, mapVersion];
} catch (error) { } catch (error) {
console.error(error); ERROR && console.error(error);
return [null, null]; return [null, null];
} }
} }
@ -190,7 +190,6 @@ function parseLoadedData(data) {
if (customizationMenu.offsetParent) styleTab.click(); if (customizationMenu.offsetParent) styleTab.click();
const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons
const hatching = document.getElementById("hatching").cloneNode(true); // save hatching
void (function parseParameters() { void (function parseParameters() {
const params = data[0].split("|"); const params = data[0].split("|");
@ -426,16 +425,16 @@ function parseLoadedData(data) {
void (function resolveVersionConflicts() { void (function resolveVersionConflicts() {
const version = parseFloat(data[0].split("|")[0]); const version = parseFloat(data[0].split("|")[0]);
if (version < 0.9) { if (version < 0.9) {
// 0.9 has additional relief icons to be included into older maps // v0.9 has additional relief icons to be included into older maps
document.getElementById("defs-relief").innerHTML = reliefIcons; document.getElementById("defs-relief").innerHTML = reliefIcons;
} }
if (version < 1) { if (version < 1) {
// 1.0 adds a new religions layer // v1.0 adds a new religions layer
relig = viewbox.insert("g", "#terrain").attr("id", "relig"); relig = viewbox.insert("g", "#terrain").attr("id", "relig");
Religions.generate(); Religions.generate();
// 1.0 adds a legend box // v1.0 adds a legend box
legend = svg.append("g").attr("id", "legend"); legend = svg.append("g").attr("id", "legend");
legend legend
.attr("font-family", "Almendra SC") .attr("font-family", "Almendra SC")
@ -448,7 +447,7 @@ function parseLoadedData(data) {
.attr("stroke-dasharray", "0 4 10 4") .attr("stroke-dasharray", "0 4 10 4")
.attr("stroke-linecap", "round"); .attr("stroke-linecap", "round");
// 1.0 separated drawBorders fron drawStates() // v1.0 separated drawBorders fron drawStates()
stateBorders = borders.append("g").attr("id", "stateBorders"); stateBorders = borders.append("g").attr("id", "stateBorders");
provinceBorders = borders.append("g").attr("id", "provinceBorders"); provinceBorders = borders.append("g").attr("id", "provinceBorders");
borders borders
@ -461,7 +460,7 @@ function parseLoadedData(data) {
stateBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt"); stateBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt");
provinceBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 0.5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt"); provinceBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 0.5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt");
// 1.0 adds state relations, provinces, forms and full names // v1.0 adds state relations, provinces, forms and full names
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", 0.6); provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", 0.6);
BurgsAndStates.collectStatistics(); BurgsAndStates.collectStatistics();
BurgsAndStates.generateCampaigns(); BurgsAndStates.generateCampaigns();
@ -473,10 +472,7 @@ function parseLoadedData(data) {
if (!layerIsOn("toggleBorders")) $("#borders").fadeOut(); if (!layerIsOn("toggleBorders")) $("#borders").fadeOut();
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove(); if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
// 1.0 adds hatching // v1.0 adds zones layer
document.getElementsByTagName("defs")[0].appendChild(hatching);
// 1.0 adds zones layer
zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none"); zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none");
zones.attr("opacity", 0.6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt"); zones.attr("opacity", 0.6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt");
addZones(); addZones();
@ -485,35 +481,35 @@ function parseLoadedData(data) {
turnButtonOn("toggleMarkers"); turnButtonOn("toggleMarkers");
} }
// 1.0 add fogging layer (state focus) // v1.0 add fogging layer (state focus)
fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("display", "none"); fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("display", "none");
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%"); fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
defs.append("mask").attr("id", "fog").append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%").attr("fill", "white"); defs.append("mask").attr("id", "fog").append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%").attr("fill", "white");
// 1.0 changes states opacity bask to regions level // v1.0 changes states opacity bask to regions level
if (statesBody.attr("opacity")) { if (statesBody.attr("opacity")) {
regions.attr("opacity", statesBody.attr("opacity")); regions.attr("opacity", statesBody.attr("opacity"));
statesBody.attr("opacity", null); statesBody.attr("opacity", null);
} }
// 1.0 changed labels to multi-lined // v1.0 changed labels to multi-lined
labels.selectAll("textPath").each(function () { labels.selectAll("textPath").each(function () {
const text = this.textContent; const text = this.textContent;
const shift = this.getComputedTextLength() / -1.5; const shift = this.getComputedTextLength() / -1.5;
this.innerHTML = `<tspan x="${shift}">${text}</tspan>`; this.innerHTML = `<tspan x="${shift}">${text}</tspan>`;
}); });
// 1.0 added new biome - Wetland // v1.0 added new biome - Wetland
biomesData.name.push("Wetland"); biomesData.name.push("Wetland");
biomesData.color.push("#0b9131"); biomesData.color.push("#0b9131");
biomesData.habitability.push(12); biomesData.habitability.push(12);
} }
if (version < 1.1) { if (version < 1.1) {
// v 1.0 initial code had a bug with religion layer id // v1.0 initial code had a bug with religion layer id
if (!relig.size()) relig = viewbox.insert("g", "#terrain").attr("id", "relig"); if (!relig.size()) relig = viewbox.insert("g", "#terrain").attr("id", "relig");
// v 1.0 initially has Sympathy status then relaced with Friendly // v1.0 initially has Sympathy status then relaced with Friendly
for (const s of pack.states) { for (const s of pack.states) {
if (!s.diplomacy) continue; if (!s.diplomacy) continue;
s.diplomacy = s.diplomacy.map(r => (r === "Sympathy" ? "Friendly" : r)); s.diplomacy = s.diplomacy.map(r => (r === "Sympathy" ? "Friendly" : r));
@ -522,7 +518,7 @@ function parseLoadedData(data) {
// labels should be toggled via style attribute, so remove display attribute // labels should be toggled via style attribute, so remove display attribute
labels.attr("display", null); labels.attr("display", null);
// v 1.0 added religions heirarchy tree // v1.0 added religions heirarchy tree
if (pack.religions[1] && !pack.religions[1].code) { if (pack.religions[1] && !pack.religions[1].code) {
pack.religions pack.religions
.filter(r => r.i) .filter(r => r.i)
@ -542,7 +538,7 @@ function parseLoadedData(data) {
lakes.select("#salt").attr("opacity", 0.5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", 0.7).attr("filter", null); lakes.select("#salt").attr("opacity", 0.5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", 0.7).attr("filter", null);
} }
// v 1.1 added new lake and coast groups // v1.1 added new lake and coast groups
if (!document.getElementById("sinkhole")) { if (!document.getElementById("sinkhole")) {
lakes.append("g").attr("id", "sinkhole"); lakes.append("g").attr("id", "sinkhole");
lakes.append("g").attr("id", "frozen"); lakes.append("g").attr("id", "frozen");
@ -557,7 +553,7 @@ function parseLoadedData(data) {
coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", 0.35).attr("filter", null); coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", 0.35).attr("filter", null);
} }
// v 1.1 features stores more data // v1.1 features stores more data
defs.select("#land").selectAll("path").remove(); defs.select("#land").selectAll("path").remove();
defs.select("#water").selectAll("path").remove(); defs.select("#water").selectAll("path").remove();
coastline.selectAll("path").remove(); coastline.selectAll("path").remove();
@ -566,13 +562,13 @@ function parseLoadedData(data) {
} }
if (version < 1.11) { if (version < 1.11) {
// v 1.11 added new attributes // v1.11 added new attributes
terrs.attr("scheme", "bright").attr("terracing", 0).attr("skip", 5).attr("relax", 0).attr("curve", 0); terrs.attr("scheme", "bright").attr("terracing", 0).attr("skip", 5).attr("relax", 0).attr("curve", 0);
svg.select("#oceanic > *").attr("id", "oceanicPattern"); svg.select("#oceanic > *").attr("id", "oceanicPattern");
oceanLayers.attr("layers", "-6,-3,-1"); oceanLayers.attr("layers", "-6,-3,-1");
gridOverlay.attr("type", "pointyHex").attr("size", 10); gridOverlay.attr("type", "pointyHex").attr("size", 10);
// v 1.11 added cultures heirarchy tree // v1.11 added cultures heirarchy tree
if (pack.cultures[1] && !pack.cultures[1].code) { if (pack.cultures[1] && !pack.cultures[1].code) {
pack.cultures pack.cultures
.filter(c => c.i) .filter(c => c.i)
@ -582,17 +578,17 @@ function parseLoadedData(data) {
}); });
} }
// v 1.11 had an issue with fogging being displayed on load // v1.11 had an issue with fogging being displayed on load
unfog(); unfog();
// v 1.2 added new terrain attributes // v1.2 added new terrain attributes
if (!terrain.attr("set")) terrain.attr("set", "simple"); if (!terrain.attr("set")) terrain.attr("set", "simple");
if (!terrain.attr("size")) terrain.attr("size", 1); if (!terrain.attr("size")) terrain.attr("size", 1);
if (!terrain.attr("density")) terrain.attr("density", 0.4); if (!terrain.attr("density")) terrain.attr("density", 0.4);
} }
if (version < 1.21) { if (version < 1.21) {
// v 1.11 replaced "display" attribute by "display" style // v1.11 replaced "display" attribute by "display" style
viewbox.selectAll("g").each(function () { viewbox.selectAll("g").each(function () {
if (this.hasAttribute("display")) { if (this.hasAttribute("display")) {
this.removeAttribute("display"); this.removeAttribute("display");
@ -600,7 +596,7 @@ function parseLoadedData(data) {
} }
}); });
// v 1.21 added rivers data to pack // v1.21 added rivers data to pack
pack.rivers = []; // rivers data pack.rivers = []; // rivers data
rivers.selectAll("path").each(function () { rivers.selectAll("path").each(function () {
const i = +this.id.slice(5); const i = +this.id.slice(5);
@ -616,12 +612,12 @@ function parseLoadedData(data) {
} }
if (version < 1.22) { if (version < 1.22) {
// v 1.22 changed state neighbors from Set object to array // v1.22 changed state neighbors from Set object to array
BurgsAndStates.collectStatistics(); BurgsAndStates.collectStatistics();
} }
if (version < 1.3) { if (version < 1.3) {
// v 1.3 added global options object // v1.3 added global options object
const winds = options.slice(); // previostly wind was saved in settings[19] const winds = options.slice(); // previostly wind was saved in settings[19]
const year = rand(100, 2000); const year = rand(100, 2000);
const era = Names.getBaseShort(P(0.7) ? 1 : rand(nameBases.length)) + " Era"; const era = Names.getBaseShort(P(0.7) ? 1 : rand(nameBases.length)) + " Era";
@ -629,10 +625,10 @@ function parseLoadedData(data) {
const military = Military.getDefaultOptions(); const military = Military.getDefaultOptions();
options = {winds, year, era, eraShort, military}; options = {winds, year, era, eraShort, military};
// v 1.3 added campaings data for all states // v1.3 added campaings data for all states
BurgsAndStates.generateCampaigns(); BurgsAndStates.generateCampaigns();
// v 1.3 added militry layer // v1.3 added militry layer
armies = viewbox.insert("g", "#icons").attr("id", "armies"); armies = viewbox.insert("g", "#icons").attr("id", "armies");
armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", 0.3); armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", 0.3);
turnButtonOn("toggleMilitary"); turnButtonOn("toggleMilitary");
@ -640,18 +636,18 @@ function parseLoadedData(data) {
} }
if (version < 1.4) { if (version < 1.4) {
// v 1.35 added dry lakes // v1.35 added dry lakes
if (!lakes.select("#dry").size()) { if (!lakes.select("#dry").size()) {
lakes.append("g").attr("id", "dry"); lakes.append("g").attr("id", "dry");
lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", 0.7).attr("filter", null); lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", 0.7).attr("filter", null);
} }
// v 1.4 added ice layer // v1.4 added ice layer
ice = viewbox.insert("g", "#coastline").attr("id", "ice").style("display", "none"); 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)"); ice.attr("opacity", null).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)");
drawIce(); drawIce();
// v 1.4 added icon and power attributes for units // v1.4 added icon and power attributes for units
for (const unit of options.military) { for (const unit of options.military) {
if (!unit.icon) unit.icon = getUnitIcon(unit.type); if (!unit.icon) unit.icon = getUnitIcon(unit.type);
if (!unit.power) unit.power = unit.crew; if (!unit.power) unit.power = unit.crew;
@ -668,7 +664,7 @@ function parseLoadedData(data) {
else return "⚔️"; else return "⚔️";
} }
// 1.4 added state reference for regiments // v1.4 added state reference for regiments
pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => (r.state = s.i))); pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => (r.state = s.i)));
} }
@ -679,19 +675,19 @@ function parseLoadedData(data) {
localStorage.removeItem("styleAncient"); localStorage.removeItem("styleAncient");
localStorage.removeItem("styleMonochrome"); localStorage.removeItem("styleMonochrome");
// v 1.5 cultures has shield attribute // v1.5 cultures has shield attribute
pack.cultures.forEach(culture => { pack.cultures.forEach(culture => {
if (culture.removed) return; if (culture.removed) return;
culture.shield = Cultures.getRandomShield(); culture.shield = Cultures.getRandomShield();
}); });
// v 1.5 added burg type value // v1.5 added burg type value
pack.burgs.forEach(burg => { pack.burgs.forEach(burg => {
if (!burg.i || burg.removed) return; if (!burg.i || burg.removed) return;
burg.type = BurgsAndStates.getType(burg.cell, burg.port); burg.type = BurgsAndStates.getType(burg.cell, burg.port);
}); });
// v 1.5 added emblems // v1.5 added emblems
defs.append("g").attr("id", "defs-emblems"); defs.append("g").attr("id", "defs-emblems");
emblems = viewbox.insert("g", "#population").attr("id", "emblems").style("display", "none"); emblems = viewbox.insert("g", "#population").attr("id", "emblems").style("display", "none");
emblems.append("g").attr("id", "burgEmblems"); emblems.append("g").attr("id", "burgEmblems");
@ -700,7 +696,7 @@ function parseLoadedData(data) {
regenerateEmblems(); regenerateEmblems();
toggleEmblems(); toggleEmblems();
// v 1.5 changed releif icons data // v1.5 changed releif icons data
terrain.selectAll("use").each(function () { terrain.selectAll("use").each(function () {
const type = this.getAttribute("data-type") || this.getAttribute("xlink:href"); const type = this.getAttribute("data-type") || this.getAttribute("xlink:href");
this.removeAttribute("xlink:href"); this.removeAttribute("xlink:href");
@ -711,7 +707,7 @@ function parseLoadedData(data) {
} }
if (version < 1.6) { if (version < 1.6) {
// v 1.6 changed rivers data // v1.6 changed rivers data
for (const river of pack.rivers) { for (const river of pack.rivers) {
const el = document.getElementById("river" + river.i); const el = document.getElementById("river" + river.i);
if (el) { if (el) {
@ -726,7 +722,7 @@ function parseLoadedData(data) {
} }
} }
// v 1.6 changed lakes data // v1.6 changed lakes data
for (const f of pack.features) { for (const f of pack.features) {
if (f.type !== "lake") continue; if (f.type !== "lake") continue;
if (f.evaporation) continue; if (f.evaporation) continue;
@ -743,7 +739,7 @@ function parseLoadedData(data) {
} }
if (version < 1.61) { if (version < 1.61) {
// v 1.61 changed rulers data // v1.61 changed rulers data
ruler.style("display", null); ruler.style("display", null);
rulers = new Rulers(); rulers = new Rulers();
@ -797,12 +793,12 @@ function parseLoadedData(data) {
} }
if (version < 1.62) { if (version < 1.62) {
// v 1.62 changed grid data // v1.62 changed grid data
gridOverlay.attr("size", null); gridOverlay.attr("size", null);
} }
if (version < 1.63) { if (version < 1.63) {
// v.1.63 changed ocean pattern opacity element // v1.63 changed ocean pattern opacity element
const oceanPattern = document.getElementById("oceanPattern"); const oceanPattern = document.getElementById("oceanPattern");
if (oceanPattern) oceanPattern.removeAttribute("opacity"); if (oceanPattern) oceanPattern.removeAttribute("opacity");
const oceanicPattern = document.getElementById("oceanicPattern"); const oceanicPattern = document.getElementById("oceanicPattern");
@ -816,7 +812,7 @@ function parseLoadedData(data) {
} }
if (version < 1.64) { if (version < 1.64) {
// v.1.64 change states style // v1.64 change states style
const opacity = regions.attr("opacity"); const opacity = regions.attr("opacity");
const filter = regions.attr("filter"); const filter = regions.attr("filter");
statesBody.attr("opacity", opacity).attr("filter", filter); statesBody.attr("opacity", opacity).attr("filter", filter);
@ -825,9 +821,10 @@ function parseLoadedData(data) {
} }
if (version < 1.65) { if (version < 1.65) {
// v 1.65 changed rivers data // v1.65 changed rivers data
d3.select("#rivers").attr("style", null); // remove style to unhide layer d3.select("#rivers").attr("style", null); // remove style to unhide layer
const {cells, rivers} = pack; const {cells, rivers} = pack;
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
for (const river of rivers) { for (const river of rivers) {
const node = document.getElementById("river" + river.i); const node = document.getElementById("river" + river.i);
@ -856,7 +853,7 @@ function parseLoadedData(data) {
river.points = riverPoints; river.points = riverPoints;
} }
river.widthFactor = 1; river.widthFactor = defaultWidthFactor;
cells.i.forEach(i => { cells.i.forEach(i => {
const riverInWater = cells.r[i] && cells.h[i] < 20; const riverInWater = cells.r[i] && cells.h[i] < 20;
@ -872,7 +869,7 @@ function parseLoadedData(data) {
} }
if (version < 1.7) { if (version < 1.7) {
// v 1.7 changed markers data // v1.7 changed markers data
const defs = document.getElementById("defs-markers"); const defs = document.getElementById("defs-markers");
const markersGroup = document.getElementById("markers"); const markersGroup = document.getElementById("markers");
@ -928,6 +925,22 @@ function parseLoadedData(data) {
if (layerIsOn("markers")) drawMarkers(); if (layerIsOn("markers")) drawMarkers();
} }
} }
if (version < 1.72) {
// v1.72 renamed custom style presets
const storedStyles = Object.keys(localStorage).filter(key => key.startsWith("style"));
storedStyles.forEach(styleName => {
const style = localStorage.getItem(styleName);
const newStyleName = styleName.replace(/^style/, customPresetPrefix);
localStorage.setItem(newStyleName, style);
localStorage.removeItem(styleName);
});
}
if (version < 1.73) {
// v1.73 moved the hatching patterns out of the user's SVG
document.getElementById("hatching")?.remove();
}
})(); })();
void (function checkDataIntegrity() { void (function checkDataIntegrity() {
@ -1013,6 +1026,31 @@ function parseLoadedData(data) {
ERROR && console.error("Data Integrity Check. Province", p.i, "is linked to removed state", p.state); ERROR && console.error("Data Integrity Check. Province", p.i, "is linked to removed state", p.state);
p.removed = true; // remove incorrect province p.removed = true; // remove incorrect province
}); });
{
const markerIds = [];
let nextId = last(pack.markers)?.i + 1 || 0;
pack.markers.forEach(marker => {
if (markerIds[marker.i]) {
ERROR && console.error("Data Integrity Check. Marker", marker.i, "has non-unique id. Changing to", nextId);
const domElements = document.querySelectorAll("#marker" + marker.i);
if (domElements[1]) domElements[1].id = "marker" + nextId; // rename 2nd dom element
const noteElements = notes.filter(note => note.id === "marker" + marker.i);
if (noteElements[1]) noteElements[1].id = "marker" + nextId; // rename 2nd note
marker.i = nextId;
nextId += 1;
} else {
markerIds[marker.i] = true;
}
});
// sort markers by index
pack.markers.sort((a, b) => a.i - b.i);
}
})(); })();
changeMapSize(); changeMapSize();

View file

@ -97,7 +97,7 @@ window.Markers = (function () {
} }
function addMarker({cell, type, icon, dx, dy, px}) { function addMarker({cell, type, icon, dx, dy, px}) {
const i = pack.markers.length; const i = last(pack.markers)?.i + 1 || 0;
const [x, y] = getMarkerCoordinates(cell); const [x, y] = getMarkerCoordinates(cell);
const marker = {i, icon, type, x, y, cell}; const marker = {i, icon, type, x, y, cell};
if (dx) marker.dx = dx; if (dx) marker.dx = dx;

View file

@ -30,63 +30,59 @@ window.Religions = (function () {
const base = { const base = {
number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"], number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"],
being: [ being: [
"God",
"Goddess",
"Lord",
"Lady",
"Deity",
"Creator",
"Maker",
"Overlord",
"Ruler",
"Chief",
"Master",
"Spirit",
"Ancestor", "Ancestor",
"Ancient",
"Brother",
"Chief",
"Council",
"Creator",
"Deity",
"Elder",
"Father", "Father",
"Forebear", "Forebear",
"Forefather", "Forefather",
"Mother",
"Brother",
"Sister",
"Elder",
"Numen",
"Ancient",
"Virgin",
"Giver", "Giver",
"Council", "God",
"Goddess",
"Guardian", "Guardian",
"Reaper" "Lady",
"Lord",
"Maker",
"Master",
"Mother",
"Numen",
"Overlord",
"Reaper",
"Ruler",
"Sister",
"Spirit",
"Virgin"
], ],
animal: [ animal: [
"Dragon",
"Wyvern",
"Phoenix",
"Unicorn",
"Sphinx",
"Centaur",
"Pegasus",
"Kraken",
"Basilisk",
"Chimera",
"Cyclope",
"Antelope", "Antelope",
"Ape", "Ape",
"Badger", "Badger",
"Basilisk",
"Bear", "Bear",
"Beaver", "Beaver",
"Bison", "Bison",
"Boar", "Boar",
"Buffalo", "Buffalo",
"Camel",
"Cat", "Cat",
"Centaur",
"Chimera",
"Cobra", "Cobra",
"Crane", "Crane",
"Crocodile", "Crocodile",
"Crow", "Crow",
"Cyclope",
"Deer", "Deer",
"Dog", "Dog",
"Dragon",
"Eagle", "Eagle",
"Elk", "Elk",
"Falcon",
"Fox", "Fox",
"Goat", "Goat",
"Goose", "Goose",
@ -94,10 +90,12 @@ window.Religions = (function () {
"Hawk", "Hawk",
"Heron", "Heron",
"Horse", "Horse",
"Hound",
"Hyena", "Hyena",
"Ibis", "Ibis",
"Jackal", "Jackal",
"Jaguar", "Jaguar",
"Kraken",
"Lark", "Lark",
"Leopard", "Leopard",
"Lion", "Lion",
@ -107,177 +105,179 @@ window.Religions = (function () {
"Mule", "Mule",
"Narwhal", "Narwhal",
"Owl", "Owl",
"Ox",
"Panther", "Panther",
"Pegasus",
"Phoenix",
"Rat", "Rat",
"Raven", "Raven",
"Rook", "Rook",
"Scorpion", "Scorpion",
"Serpent",
"Shark", "Shark",
"Sheep", "Sheep",
"Snake", "Snake",
"Sphinx",
"Spider", "Spider",
"Swan", "Swan",
"Tiger", "Tiger",
"Turtle", "Turtle",
"Unicorn",
"Viper", "Viper",
"Vulture", "Vulture",
"Walrus", "Walrus",
"Wolf", "Wolf",
"Wolverine", "Wolverine",
"Worm", "Worm",
"Camel", "Wyvern"
"Falcon",
"Hound",
"Ox",
"Serpent"
], ],
adjective: [ adjective: [
"New",
"Good",
"High",
"Old",
"Great",
"Big",
"Young",
"Major",
"Strong",
"Happy",
"Last",
"Main",
"Huge",
"Far",
"Beautiful",
"Wild",
"Fair",
"Prime",
"Crazy",
"Ancient",
"Proud",
"Secret",
"Lucky",
"Sad",
"Silent",
"Latter",
"Severe",
"Fat",
"Holy",
"Pure",
"Aggressive", "Aggressive",
"Honest", "Almighty",
"Giant", "Ancient",
"Mad", "Beautiful",
"Pregnant", "Benevolent",
"Distant", "Big",
"Lost",
"Broken",
"Blind", "Blind",
"Friendly", "Blond",
"Unknown", "Bloody",
"Sleeping", "Brave",
"Slumbering", "Broken",
"Loud", "Brutal",
"Hungry", "Burning",
"Wise", "Calm",
"Worried", "Cheerful",
"Sacred", "Crazy",
"Magical", "Cruel",
"Superior",
"Patient",
"Dead", "Dead",
"Deadly", "Deadly",
"Peaceful",
"Grateful",
"Frozen",
"Evil",
"Scary",
"Burning",
"Divine",
"Bloody",
"Dying",
"Waking",
"Brutal",
"Unhappy",
"Calm",
"Cruel",
"Favorable",
"Blond",
"Explicit",
"Disturbing",
"Devastating", "Devastating",
"Brave", "Distant",
"Sunny", "Disturbing",
"Troubled", "Divine",
"Flying", "Dying",
"Sustainable",
"Marine",
"Fatal",
"Inherent",
"Selected",
"Naval",
"Cheerful",
"Almighty",
"Benevolent",
"Eternal", "Eternal",
"Evil",
"Explicit",
"Fair",
"Far",
"Fat",
"Fatal",
"Favorable",
"Flying",
"Friendly",
"Frozen",
"Giant",
"Good",
"Grateful",
"Great",
"Happy",
"High",
"Holy",
"Honest",
"Huge",
"Hungry",
"Immutable", "Immutable",
"Infallible" "Infallible",
"Inherent",
"Last",
"Latter",
"Lost",
"Loud",
"Lucky",
"Mad",
"Magical",
"Main",
"Major",
"Marine",
"Naval",
"New",
"Old",
"Patient",
"Peaceful",
"Pregnant",
"Prime",
"Proud",
"Pure",
"Sacred",
"Sad",
"Scary",
"Secret",
"Selected",
"Severe",
"Silent",
"Sleeping",
"Slumbering",
"Strong",
"Sunny",
"Superior",
"Sustainable",
"Troubled",
"Unhappy",
"Unknown",
"Waking",
"Wild",
"Wise",
"Worried",
"Young"
], ],
genitive: [ genitive: [
"Day",
"Life",
"Death",
"Night",
"Home",
"Fog",
"Snow",
"Winter",
"Summer",
"Cold", "Cold",
"Springs", "Day",
"Gates", "Death",
"Nature",
"Thunder",
"Lightning",
"War",
"Ice",
"Frost",
"Fire",
"Doom", "Doom",
"Fate", "Fate",
"Pain", "Fire",
"Fog",
"Frost",
"Gates",
"Heaven", "Heaven",
"Home",
"Ice",
"Justice", "Justice",
"Life",
"Light", "Light",
"Lightning",
"Love", "Love",
"Nature",
"Night",
"Pain",
"Snow",
"Springs",
"Summer",
"Thunder",
"Time", "Time",
"Victory" "Victory",
"War",
"Winter"
], ],
theGenitive: [ theGenitive: [
"World",
"Word",
"South",
"West",
"North",
"East",
"Sun",
"Moon",
"Peak",
"Fall",
"Dawn",
"Eclipse",
"Abyss", "Abyss",
"Blood", "Blood",
"Tree", "Dawn",
"Earth", "Earth",
"East",
"Eclipse",
"Fall",
"Harvest", "Harvest",
"Moon",
"North",
"Peak",
"Rainbow", "Rainbow",
"Sea", "Sea",
"Sky", "Sky",
"South",
"Stars", "Stars",
"Storm", "Storm",
"Sun",
"Tree",
"Underworld", "Underworld",
"Wild" "West",
"Wild",
"Word",
"World"
], ],
color: ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"] color: ["Amber", "Black", "Blue", "Bright", "Brown", "Dark", "Golden", "Green", "Grey", "Light", "Orange", "Pink", "Purple", "Red", "White", "Yellow"]
}; };
const forms = { const forms = {
@ -308,10 +308,10 @@ window.Religions = (function () {
Monotheism: {Religion: 1, Church: 1}, Monotheism: {Religion: 1, Church: 1},
"Non-theism": {Beliefs: 3, Spirits: 1}, "Non-theism": {Beliefs: 3, Spirits: 1},
Cult: {Cult: 4, Sect: 4, Worship: 1, Orden: 1, Coterie: 1, Arcanum: 1}, Cult: {Cult: 4, Sect: 4, Arcanum: 1, Coterie: 1, Order: 1, Worship: 1},
"Dark Cult": {Cult: 2, Sect: 2, Occultism: 1, Idols: 1, Coven: 1, Circle: 1, Blasphemy: 1}, "Dark Cult": {Cult: 2, Sect: 2, Blasphemy: 1, Circle: 1, Coven: 1, Idols: 1, Occultism: 1},
Heresy: {Heresy: 3, Sect: 2, Schism: 1, Dissenters: 1, Circle: 1, Brotherhood: 1, Society: 1, Iconoclasm: 1, Dissent: 1, Apostates: 1} Heresy: {Heresy: 3, Sect: 2, Apostates: 1, Brotherhood: 1, Circle: 1, Dissent: 1, Dissenters: 1, Iconoclasm: 1, Schism: 1, Society: 1}
}; };
const generate = function () { const generate = function () {

View file

@ -23,22 +23,27 @@ window.Rivers = (function () {
resolveDepressions(h); resolveDepressions(h);
drainWater(); drainWater();
defineRivers(); defineRivers();
calculateConfluenceFlux(); calculateConfluenceFlux();
Lakes.cleanupLakeData(); Lakes.cleanupLakeData();
if (allowErosion) cells.h = Uint8Array.from(h); // apply changed heights as basic one if (allowErosion) {
cells.h = Uint8Array.from(h); // apply gradient
downcutRivers(); // downcut river beds
}
TIME && console.timeEnd("generateRivers"); TIME && console.timeEnd("generateRivers");
function drainWater() { function drainWater() {
const MIN_FLUX_TO_FORM_RIVER = 30; const MIN_FLUX_TO_FORM_RIVER = 30;
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
const prec = grid.cells.prec; const prec = grid.cells.prec;
const area = pack.cells.area;
const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]); const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]);
const lakeOutCells = Lakes.setClimateData(h); const lakeOutCells = Lakes.setClimateData(h);
land.forEach(function (i) { land.forEach(function (i) {
cells.fl[i] += (prec[cells.g[i]] * area[i]) / 100; // add flux from precipitation cells.fl[i] += prec[cells.g[i]] / cellsNumberModifier; // add flux from precipitation
// create lake outlet if lake is not in deep depression and flux > evaporation // create lake outlet if lake is not in deep depression and flux > evaporation
const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : []; const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : [];
@ -90,6 +95,15 @@ window.Rivers = (function () {
// cells is depressed // cells is depressed
if (h[i] <= h[min]) return; if (h[i] <= h[min]) return;
// debug
// .append("line")
// .attr("x1", pack.cells.p[i][0])
// .attr("y1", pack.cells.p[i][1])
// .attr("x2", pack.cells.p[min][0])
// .attr("y2", pack.cells.p[min][1])
// .attr("stroke", "#333")
// .attr("stroke-width", 0.2);
if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) { if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) {
// flux is too small to operate as a river // flux is too small to operate as a river
if (h[min] >= 20) cells.fl[min] += cells.fl[i]; if (h[min] >= 20) cells.fl[min] += cells.fl[i];
@ -149,6 +163,9 @@ window.Rivers = (function () {
cells.conf = new Uint16Array(cells.i.length); cells.conf = new Uint16Array(cells.i.length);
pack.rivers = []; pack.rivers = [];
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
const mainStemWidthFactor = defaultWidthFactor * 1.2;
for (const key in riversData) { for (const key in riversData) {
const riverCells = riversData[key]; const riverCells = riversData[key];
if (riverCells.length < 3) continue; // exclude tiny rivers if (riverCells.length < 3) continue; // exclude tiny rivers
@ -166,7 +183,7 @@ window.Rivers = (function () {
const mouth = riverCells[riverCells.length - 2]; const mouth = riverCells[riverCells.length - 2];
const parent = riverParents[key] || 0; const parent = riverParents[key] || 0;
const widthFactor = !parent || parent === riverId ? 1.2 : 1; const widthFactor = !parent || parent === riverId ? mainStemWidthFactor : defaultWidthFactor;
const meanderedPoints = addMeandering(riverCells); const meanderedPoints = addMeandering(riverCells);
const discharge = cells.fl[mouth]; // m3 in second const discharge = cells.fl[mouth]; // m3 in second
const length = getApproximateLength(meanderedPoints); const length = getApproximateLength(meanderedPoints);
@ -176,6 +193,22 @@ window.Rivers = (function () {
} }
} }
function downcutRivers() {
const MAX_DOWNCUT = 5;
for (const i of pack.cells.i) {
if (cells.h[i] < 35) continue; // don't donwcut lowlands
if (!cells.fl[i]) continue;
const higherCells = cells.c[i].filter(c => cells.h[c] > cells.h[i]);
const higherFlux = higherCells.reduce((acc, c) => acc + cells.fl[c], 0) / higherCells.length;
if (!higherFlux) continue;
const downcut = Math.floor(cells.fl[i] / higherFlux);
if (downcut) cells.h[i] -= Math.min(downcut, MAX_DOWNCUT);
}
}
function calculateConfluenceFlux() { function calculateConfluenceFlux() {
for (const i of cells.i) { for (const i of cells.i) {
if (!cells.conf[i]) continue; if (!cells.conf[i]) continue;
@ -344,14 +377,14 @@ window.Rivers = (function () {
const LENGTH_PROGRESSION = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / LENGTH_FACTOR); const LENGTH_PROGRESSION = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / LENGTH_FACTOR);
const MAX_PROGRESSION = last(LENGTH_PROGRESSION); const MAX_PROGRESSION = last(LENGTH_PROGRESSION);
const getOffset = (flux, pointNumber, widthFactor = 1, startingWidth = 0) => { const getOffset = (flux, pointNumber, widthFactor, startingWidth = 0) => {
const fluxWidth = Math.min(flux ** 0.9 / FLUX_FACTOR, MAX_FLUX_WIDTH); const fluxWidth = Math.min(flux ** 0.9 / FLUX_FACTOR, MAX_FLUX_WIDTH);
const lengthWidth = pointNumber * STEP_WIDTH + (LENGTH_PROGRESSION[pointNumber] || MAX_PROGRESSION); const lengthWidth = pointNumber * STEP_WIDTH + (LENGTH_PROGRESSION[pointNumber] || MAX_PROGRESSION);
return widthFactor * (lengthWidth + fluxWidth) + startingWidth; return widthFactor * (lengthWidth + fluxWidth) + startingWidth;
}; };
// build polygon from a list of points and calculated offset (width) // build polygon from a list of points and calculated offset (width)
const getRiverPath = function (points, widthFactor = 1, startingWidth = 0) { const getRiverPath = function (points, widthFactor, startingWidth = 0) {
const riverPointsLeft = []; const riverPointsLeft = [];
const riverPointsRight = []; const riverPointsRight = [];
@ -444,5 +477,20 @@ window.Rivers = (function () {
return getBasin(parent); return getBasin(parent);
}; };
return {generate, alterHeights, resolveDepressions, addMeandering, getRiverPath, specify, getName, getType, getBasin, getWidth, getOffset, getApproximateLength, getRiverPoints, remove}; return {
generate,
alterHeights,
resolveDepressions,
addMeandering,
getRiverPath,
specify,
getName,
getType,
getBasin,
getWidth,
getOffset,
getApproximateLength,
getRiverPoints,
remove
};
})(); })();

View file

@ -145,7 +145,7 @@ async function saveToDropbox() {
await Cloud.providers.dropbox.save(filename, mapData); await Cloud.providers.dropbox.save(filename, mapData);
tip("Map is saved to your Dropbox", true, "success", 8000); tip("Map is saved to your Dropbox", true, "success", 8000);
} catch (msg) { } catch (msg) {
console.error(msg); ERROR && console.error(msg);
tip("Cannot save .map to your Dropbox", true, "error", 8000); tip("Cannot save .map to your Dropbox", true, "error", 8000);
} }
} }

View file

@ -141,8 +141,8 @@ class Battle {
const state = pack.states[regiment.state]; const state = pack.states[regiment.state];
const distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScaleInput.value) | 0; // distance between regiment and its base const distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScaleInput.value) | 0; // distance between regiment and its base
const color = state.color[0] === "#" ? state.color : "#999"; const color = state.color[0] === "#" ? state.color : "#999";
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em"> const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em; stroke: #333">
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect"></rect> <rect x="0" y="0" width="100%" height="100%" fill="${color}"></rect>
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`; <text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
const body = `<tbody id="battle${state.i}-${regiment.i}">`; const body = `<tbody id="battle${state.i}-${regiment.i}">`;
@ -183,7 +183,7 @@ class Battle {
dist = added ? "0 " + distanceUnitInput.value : distance(r); dist = added ? "0 " + distanceUnitInput.value : distance(r);
return `<div ${added ? "class='inactive'" : ""} data-s=${s.i} data-i=${r.i} data-state=${s.name} data-regiment=${r.name} return `<div ${added ? "class='inactive'" : ""} data-s=${s.i} data-i=${r.i} data-state=${s.name} data-regiment=${r.name}
data-total=${r.a} data-distance=${dist} data-tip="Click to select regiment"> data-total=${r.a} data-distance=${dist} data-tip="Click to select regiment">
<svg width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg> <svg width=".9em" height=".9em" style="margin-bottom:-1px; stroke: #333"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" ></svg>
<div style="width:6em">${s.name.slice(0, 11)}</div> <div style="width:6em">${s.name.slice(0, 11)}</div>
<div style="width:1.2em">${r.icon}</div> <div style="width:1.2em">${r.icon}</div>
<div style="width:13em">${r.name.slice(0, 24)}</div> <div style="width:13em">${r.name.slice(0, 24)}</div>

View file

@ -37,9 +37,9 @@ function editBiomes() {
document.getElementById("biomesExport").addEventListener("click", downloadBiomesData); document.getElementById("biomesExport").addEventListener("click", downloadBiomesData);
body.addEventListener("click", function (ev) { body.addEventListener("click", function (ev) {
const el = ev.target, const el = ev.target;
cl = el.classList; const cl = el.classList;
if (cl.contains("fillRect")) biomeChangeColor(el); if (el.tagName === "FILL-BOX") biomeChangeColor(el);
else if (cl.contains("icon-info-circled")) openWiki(el); else if (cl.contains("icon-info-circled")) openWiki(el);
else if (cl.contains("icon-trash-empty")) removeCustomBiome(el); else if (cl.contains("icon-trash-empty")) removeCustomBiome(el);
if (customization === 6) selectBiomeOnLineClick(el); if (customization === 6) selectBiomeOnLineClick(el);
@ -94,9 +94,7 @@ function editBiomes() {
lines += `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability="${b.habitability[i]}" lines += `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability="${b.habitability[i]}"
data-cells=${b.cells[i]} data-area=${area} data-population=${population} data-color=${b.color[i]}> data-cells=${b.cells[i]} data-area=${area} data-population=${population} data-color=${b.color[i]}>
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${ <fill-box fill="${b.color[i]}"></fill-box>
b.color[i]
}" class="fillRect pointer"></svg>
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false"> <input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
<span data-tip="Biome habitability percent" class="hide">%</span> <span data-tip="Biome habitability percent" class="hide">%</span>
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 class="biomeHabitability hide" value=${ <input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 class="biomeHabitability hide" value=${
@ -158,15 +156,15 @@ function editBiomes() {
function biomeChangeColor(el) { function biomeChangeColor(el) {
const currentFill = el.getAttribute("fill"); const currentFill = el.getAttribute("fill");
const biome = +el.parentNode.parentNode.dataset.id; const biome = +el.parentNode.dataset.id;
const callback = function (fill) { const callback = newFill => {
el.setAttribute("fill", fill); el.fill = newFill;
biomesData.color[biome] = fill; biomesData.color[biome] = newFill;
biomes biomes
.select("#biome" + biome) .select("#biome" + biome)
.attr("fill", fill) .attr("fill", newFill)
.attr("stroke", fill); .attr("stroke", newFill);
}; };
openPicker(currentFill, callback); openPicker(currentFill, callback);
@ -270,7 +268,7 @@ function editBiomes() {
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
const line = `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability=${b.habitability[i]} data-cells=0 data-area=0 data-population=0 data-color=${b.color[i]}> const line = `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability=${b.habitability[i]} data-cells=0 data-area=0 data-population=0 data-color=${b.color[i]}>
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${b.color[i]}" class="fillRect pointer"></svg> <fill-box fill="${b.color[i]}"></fill-box>
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false"> <input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
<span data-tip="Biome habitability percent" class="hide">%</span> <span data-tip="Biome habitability percent" class="hide">%</span>
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 step=1 class="biomeHabitability hide" value=${b.habitability[i]}> <input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 step=1 class="biomeHabitability hide" value=${b.habitability[i]}>

View file

@ -37,6 +37,7 @@ function editBurg(id) {
burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature)); burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature));
document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed); document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed);
document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed); document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed);
document.getElementById("addCustomMFCGBurgLink").addEventListener("click", addCustomMfcgLink);
document.getElementById("burgStyleShow").addEventListener("click", showStyleSection); document.getElementById("burgStyleShow").addEventListener("click", showStyleSection);
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection); document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection);
@ -51,6 +52,7 @@ function editBurg(id) {
document.getElementById("burglLegend").addEventListener("click", editBurgLegend); document.getElementById("burglLegend").addEventListener("click", editBurgLegend);
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton); document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton);
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg); document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
document.getElementById("burgTemperatureGraph").addEventListener("click", showTemperatureGraph);
function updateBurgValues() { function updateBurgValues() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
@ -112,7 +114,13 @@ function editBurg(id) {
if (options.showMFCGMap) { if (options.showMFCGMap) {
document.getElementById("mfcgPreviewSection").style.display = "block"; document.getElementById("mfcgPreviewSection").style.display = "block";
updateMFCGFrame(b); updateMFCGFrame(b);
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
if (b.link) {
document.getElementById("mfcgBurgSeedSection").style.display = "none";
} else {
document.getElementById("mfcgBurgSeedSection").style.display = "inline-block";
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
}
} else { } else {
document.getElementById("mfcgPreviewSection").style.display = "none"; document.getElementById("mfcgPreviewSection").style.display = "none";
} }
@ -347,22 +355,25 @@ function editBurg(id) {
function toggleFeature() { function toggleFeature() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
const b = pack.burgs[id]; const burg = pack.burgs[id];
const feature = this.dataset.feature; const feature = this.dataset.feature;
const turnOn = this.classList.contains("inactive"); const turnOn = this.classList.contains("inactive");
if (feature === "port") togglePort(id); if (feature === "port") togglePort(id);
else if (feature === "capital") toggleCapital(id); else if (feature === "capital") toggleCapital(id);
else b[feature] = +turnOn; else burg[feature] = +turnOn;
if (b[feature]) this.classList.remove("inactive"); if (burg[feature]) this.classList.remove("inactive");
else if (!b[feature]) this.classList.add("inactive"); else if (!burg[feature]) this.classList.add("inactive");
if (b.port) document.getElementById("burgEditAnchorStyle").style.display = "inline-block"; if (burg.port) document.getElementById("burgEditAnchorStyle").style.display = "inline-block";
else document.getElementById("burgEditAnchorStyle").style.display = "none"; else document.getElementById("burgEditAnchorStyle").style.display = "none";
updateMFCGFrame(burg);
} }
function toggleBurgLockButton() { function toggleBurgLockButton() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
toggleBurgLock(id); const burg = pack.burgs[id];
burg.lock = !burg.lock;
updateBurgLockIcon(); updateBurgLockIcon();
} }
@ -426,6 +437,17 @@ function editBurg(id) {
document.getElementById("mfcgBurgSeed").value = burgSeed; document.getElementById("mfcgBurgSeed").value = burgSeed;
} }
function addCustomMfcgLink() {
const id = +elSelected.attr("data-id");
const burg = pack.burgs[id];
const message = "Enter custom link to the burg map. It can be a link to Medieval Fantasy City Generator or other tool. Keep empty to use MFCG seed";
prompt(message, {default: burg.link || "", required: false}, link => {
if (link) burg.link = link;
else delete burg.link;
updateMFCGFrame(burg);
});
}
function openEmblemEdit() { function openEmblemEdit() {
const id = +elSelected.attr("data-id"), const id = +elSelected.attr("data-id"),
burg = pack.burgs[id]; burg = pack.burgs[id];
@ -522,6 +544,11 @@ function editBurg(id) {
editNotes("burg" + id, name); editNotes("burg" + id, name);
} }
function showTemperatureGraph() {
const id = elSelected.attr("data-id");
showBurgTemperatureGraph(id);
}
function removeSelectedBurg() { function removeSelectedBurg() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
if (pack.burgs[id].capital) { if (pack.burgs[id].capital) {

View file

@ -7,6 +7,7 @@ function overviewBurgs() {
const body = document.getElementById("burgsBody"); const body = document.getElementById("burgsBody");
updateFilter(); updateFilter();
updateLockAllIcon();
burgsOverviewAddLines(); burgsOverviewAddLines();
$("#burgsOverview").dialog(); $("#burgsOverview").dialog();
@ -33,6 +34,7 @@ function overviewBurgs() {
document.getElementById("burgsListToLoad").addEventListener("change", function () { document.getElementById("burgsListToLoad").addEventListener("change", function () {
uploadFile(this, importBurgNames); uploadFile(this, importBurgNames);
}); });
document.getElementById("burgsLockAll").addEventListener("click", toggleLockAll);
document.getElementById("burgsRemoveAll").addEventListener("click", triggerAllBurgsRemove); document.getElementById("burgsRemoveAll").addEventListener("click", triggerAllBurgsRemove);
document.getElementById("burgsInvertLock").addEventListener("click", invertLock); document.getElementById("burgsInvertLock").addEventListener("click", invertLock);
@ -87,7 +89,7 @@ function overviewBurgs() {
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false"> <input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false">
<input data-tip="Burg province" class="burgState" value="${province}" disabled> <input data-tip="Burg province" class="burgState" value="${province}" disabled>
<input data-tip="Burg state" class="burgState" value="${state}" disabled> <input data-tip="Burg state" class="burgState" value="${state}" disabled>
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions( <select data-tip="Dominant culture. Click to change burg culture (to change cell culture use Cultures Editor)" class="stateCulture">${getCultureOptions(
b.culture b.culture
)}</select> )}</select>
<span data-tip="Burg population" class="icon-male"></span> <span data-tip="Burg population" class="icon-male"></span>
@ -195,8 +197,11 @@ function overviewBurgs() {
} }
function toggleBurgLockStatus() { function toggleBurgLockStatus() {
const burg = +this.parentNode.dataset.id; const burgId = +this.parentNode.dataset.id;
toggleBurgLock(burg);
const burg = pack.burgs[burgId];
burg.lock = !burg.lock;
if (this.classList.contains("icon-lock")) { if (this.classList.contains("icon-lock")) {
this.classList.remove("icon-lock"); this.classList.remove("icon-lock");
this.classList.add("icon-lock-open"); this.classList.add("icon-lock-open");
@ -478,9 +483,9 @@ function overviewBurgs() {
} }
function renameBurgsInBulk() { function renameBurgsInBulk() {
const message = `Download burgs list as a text file, make changes and re-upload the file. alertMessage.innerHTML = `Download burgs list as a text file, make changes and re-upload the file.
Make sure the file is a plain text document with each name on its own line (the dilimiter is CRLF).
If you do not want to change the name, just leave it as is`; If you do not want to change the name, just leave it as is`;
alertMessage.innerHTML = message;
$("#alert").dialog({ $("#alert").dialog({
title: "Burgs bulk renaming", title: "Burgs bulk renaming",
@ -562,4 +567,21 @@ function overviewBurgs() {
pack.burgs = pack.burgs.map(burg => ({...burg, lock: !burg.lock})); pack.burgs = pack.burgs.map(burg => ({...burg, lock: !burg.lock}));
burgsOverviewAddLines(); burgsOverviewAddLines();
} }
function toggleLockAll() {
const activeBurgs = pack.burgs.filter(b => b.i && !b.removed);
const allLocked = activeBurgs.every(burg => burg.lock);
pack.burgs.forEach(burg => {
burg.lock = !allLocked;
});
burgsOverviewAddLines();
document.getElementById("burgsLockAll").className = allLocked ? "icon-lock" : "icon-lock-open";
}
function updateLockAllIcon() {
const allLocked = pack.burgs.every(({lock, i, removed}) => lock || !i || removed);
document.getElementById("burgsLockAll").className = allLocked ? "icon-lock-open" : "icon-lock";
}
} }

View file

@ -108,9 +108,7 @@ function editCultures() {
lines += `<div class="states cultures" data-id=${c.i} data-name="${c.name}" data-color="${c.color}" data-cells=${c.cells} lines += `<div class="states cultures" data-id=${c.i} data-name="${c.name}" data-color="${c.color}" data-cells=${c.cells}
data-area=${area} data-population=${population} data-base=${c.base} data-type=${c.type} data-expansionism=${c.expansionism} data-emblems="${c.shield}"> data-area=${area} data-population=${population} data-base=${c.base} data-type=${c.type} data-expansionism=${c.expansionism} data-emblems="${c.shield}">
<svg data-tip="Culture fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"> <fill-box fill="${c.color}"></fill-box>
<rect x="0" y="0" width="100%" height="100%" fill="${c.color}" class="fillRect pointer">
</svg>
<input data-tip="Culture name. Click and type to change" class="cultureName" value="${c.name}" autocorrect="off" spellcheck="false"> <input data-tip="Culture name. Click and type to change" class="cultureName" value="${c.name}" autocorrect="off" spellcheck="false">
<span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span> <span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span>
<span data-tip="Cells count" class="icon-check-empty hide"></span> <span data-tip="Cells count" class="icon-check-empty hide"></span>
@ -148,7 +146,7 @@ function editCultures() {
body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseenter", ev => cultureHighlightOn(ev))); body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseenter", ev => cultureHighlightOn(ev)));
body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseleave", ev => cultureHighlightOff(ev))); body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseleave", ev => cultureHighlightOff(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectCultureOnLineClick)); body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectCultureOnLineClick));
body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", cultureChangeColor)); body.querySelectorAll("fill-box").forEach(el => el.addEventListener("click", cultureChangeColor));
body.querySelectorAll("div > input.cultureName").forEach(el => el.addEventListener("input", cultureChangeName)); body.querySelectorAll("div > input.cultureName").forEach(el => el.addEventListener("input", cultureChangeName));
body.querySelectorAll("div > span.icon-cw").forEach(el => el.addEventListener("click", cultureRegenerateName)); body.querySelectorAll("div > span.icon-cw").forEach(el => el.addEventListener("click", cultureRegenerateName));
body.querySelectorAll("div > input.statePower").forEach(el => el.addEventListener("input", cultureChangeExpansionism)); body.querySelectorAll("div > input.statePower").forEach(el => el.addEventListener("input", cultureChangeExpansionism));
@ -248,16 +246,16 @@ function editCultures() {
function cultureChangeColor() { function cultureChangeColor() {
const el = this; const el = this;
const currentFill = el.getAttribute("fill"); const currentFill = el.getAttribute("fill");
const culture = +el.parentNode.parentNode.dataset.id; const culture = +el.parentNode.dataset.id;
const callback = function (fill) { const callback = newFill => {
el.setAttribute("fill", fill); el.fill = newFill;
pack.cultures[culture].color = fill; pack.cultures[culture].color = newFill;
cults cults
.select("#culture" + culture) .select("#culture" + culture)
.attr("fill", fill) .attr("fill", newFill)
.attr("stroke", fill); .attr("stroke", newFill);
debug.select("#cultureCenter" + culture).attr("fill", fill); debug.select("#cultureCenter" + culture).attr("fill", newFill);
}; };
openPicker(currentFill, callback); openPicker(currentFill, callback);

View file

@ -1,10 +1,9 @@
"use strict"; "use strict";
function editDiplomacy() { function editDiplomacy() {
if (customization) return; if (customization) return;
if (pack.states.filter(s => s.i && !s.removed).length < 2) { if (pack.states.filter(s => s.i && !s.removed).length < 2) return tip("There should be at least 2 states to edit the diplomacy", false, "error");
tip("There should be at least 2 states to edit the diplomacy", false, "error");
return; const body = document.getElementById("diplomacyBodySection");
}
closeDialogs("#diplomacyEditor, .stable"); closeDialogs("#diplomacyEditor, .stable");
if (!layerIsOn("toggleStates")) toggleStates(); if (!layerIsOn("toggleStates")) toggleStates();
@ -14,21 +13,29 @@ function editDiplomacy() {
if (layerIsOn("toggleBiomes")) toggleBiomes(); if (layerIsOn("toggleBiomes")) toggleBiomes();
if (layerIsOn("toggleReligions")) toggleReligions(); if (layerIsOn("toggleReligions")) toggleReligions();
const body = document.getElementById("diplomacyBodySection"); const relations = {
const statuses = ["Ally", "Friendly", "Neutral", "Suspicion", "Enemy", "Unknown", "Rival", "Vassal", "Suzerain"]; Ally: {inText: "is an ally of", color: "#00b300", tip: "Allies formed a defensive pact and protect each other in case of third party aggression"},
const description = [" is an ally of ", " is friendly to ", " is neutral to ", " is suspicious of ", Friendly: {inText: "is friendly to", color: "#d4f8aa", tip: "State is friendly to anouther state when they share some common interests"},
" is at war with ", " does not know about ", " is a rival of ", " is a vassal of ", " is suzerain to "]; Neutral: {inText: "is neutral to", color: "#edeee8", tip: "Neutral means states relations are neither positive nor negative"},
const colors = ["#00b300", "#d4f8aa", "#edeee8", "#eeafaa", "#e64b40", "#a9a9a9", "#ad5a1f", "#87CEFA", "#00008B"]; Suspicion: {inText: "is suspicious of", color: "#eeafaa", tip: "Suspicion means state has a cautious distrust of another state"},
refreshDiplomacyEditor(); Enemy: {inText: "is at war with", color: "#e64b40", tip: "Enemies are states at war with each other"},
Unknown: {inText: "does not know about", color: "#a9a9a9", tip: "Relations are unknown if states do not have enough information about each other"},
Rival: {inText: "is a rival of", color: "#ad5a1f", tip: "Rivalry is a state of competing for dominance in the region"},
Vassal: {inText: "is a vassal of", color: "#87CEFA", tip: "Vassal is a state having obligation to its suzerain"},
Suzerain: {inText: "is suzerain to", color: "#00008B", tip: "Suzerain is a state having some control over its vassals"}
};
tip("Click on a state to see its diplomatic relations", false, "warning"); refreshDiplomacyEditor();
viewbox.style("cursor", "crosshair").on("click", selectStateOnMapClick); viewbox.style("cursor", "crosshair").on("click", selectStateOnMapClick);
if (modules.editDiplomacy) return; if (modules.editDiplomacy) return;
modules.editDiplomacy = true; modules.editDiplomacy = true;
$("#diplomacyEditor").dialog({ $("#diplomacyEditor").dialog({
title: "Diplomacy Editor", resizable: false, width: fitContent(), close: closeDiplomacyEditor, title: "Diplomacy Editor",
resizable: false,
width: fitContent(),
close: closeDiplomacyEditor,
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
}); });
@ -36,10 +43,30 @@ function editDiplomacy() {
document.getElementById("diplomacyEditorRefresh").addEventListener("click", refreshDiplomacyEditor); document.getElementById("diplomacyEditorRefresh").addEventListener("click", refreshDiplomacyEditor);
document.getElementById("diplomacyEditStyle").addEventListener("click", () => editStyle("regions")); document.getElementById("diplomacyEditStyle").addEventListener("click", () => editStyle("regions"));
document.getElementById("diplomacyRegenerate").addEventListener("click", regenerateRelations); document.getElementById("diplomacyRegenerate").addEventListener("click", regenerateRelations);
document.getElementById("diplomacyMatrix").addEventListener("click", showRelationsMatrix); document.getElementById("diplomacyReset").addEventListener("click", resetRelations);
document.getElementById("diplomacyShowMatrix").addEventListener("click", showRelationsMatrix);
document.getElementById("diplomacyHistory").addEventListener("click", showRelationsHistory); document.getElementById("diplomacyHistory").addEventListener("click", showRelationsHistory);
document.getElementById("diplomacyExport").addEventListener("click", downloadDiplomacyData); document.getElementById("diplomacyExport").addEventListener("click", downloadDiplomacyData);
document.getElementById("diplomacySelect").addEventListener("mouseup", diplomacyChangeRelations);
body.addEventListener("click", function (ev) {
const el = ev.target;
if (el.parentElement.classList.contains("Self")) return;
if (el.classList.contains("changeRelations")) {
const line = el.parentElement;
const subjectId = +line.dataset.id;
const objectId = +body.querySelector("div.Self").dataset.id;
const currentRelation = line.dataset.relations;
selectRelation(subjectId, objectId, currentRelation);
return;
}
// select state of clicked line
body.querySelector("div.Self").classList.remove("Self");
el.parentElement.classList.add("Self");
refreshDiplomacyEditor();
});
function refreshDiplomacyEditor() { function refreshDiplomacyEditor() {
diplomacyEditorAddLines(); diplomacyEditorAddLines();
@ -50,33 +77,34 @@ function editDiplomacy() {
function diplomacyEditorAddLines() { function diplomacyEditorAddLines() {
const states = pack.states; const states = pack.states;
const selectedLine = body.querySelector("div.Self"); const selectedLine = body.querySelector("div.Self");
const sel = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i; const selectedId = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
const selName = states[sel].fullName; const selectedName = states[selectedId].name;
diplomacySelect.style.display = "none";
COArenderer.trigger("stateCOA"+sel, states[sel].coa); COArenderer.trigger("stateCOA" + selectedId, states[selectedId].coa);
let lines = `<div class="states Self" data-id=${sel} data-tip="List below shows relations to ${selName}"> let lines = `<div class="states Self" data-id=${selectedId} data-tip="List below shows relations to ${selectedName}">
<div style="width: max-content">${selName}</div> <div style="width: max-content">${states[selectedId].fullName}</div>
<svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${sel}"></use></svg> <svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${selectedId}"></use></svg>
</div>`; </div>`;
for (const s of states) { for (const state of states) {
if (!s.i || s.removed || s.i === sel) continue; if (!state.i || state.removed || state.i === selectedId) continue;
const relation = s.diplomacy[sel]; const relation = state.diplomacy[selectedId];
const index = statuses.indexOf(relation); const {color, inText} = relations[relation];
const color = colors[index];
const tip = s.fullName + description[index] + selName;
const tipSelect = `${tip}. Click to see relations to ${s.name}`;
const tipChange = `${tip}. Click to change relations to ${selName}`;
COArenderer.trigger("stateCOA"+s.i, s.coa);
lines += `<div class="states" data-id=${s.i} data-name="${s.fullName}" data-relations="${relation}"> const tip = `${state.name} ${inText} ${selectedName}`;
<svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg> const tipSelect = `${tip}. Click to see relations to ${state.name}`;
<div data-tip="${tipSelect}" style="width:12em">${s.fullName}</div> const tipChange = `Click to change relations. ${tip}`;
<svg data-tip="${tipChange}" width=".9em" height=".9em" style="margin-bottom:-1px" class="changeRelations">
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect pointer" style="pointer-events: none"></rect> const name = state.fullName.length < 23 ? state.fullName : state.name;
</svg> COArenderer.trigger("stateCOA" + state.i, state.coa);
<input data-tip="${tipChange}" class="changeRelations diplomacyRelations" value="${relation}" readonly/>
lines += `<div class="states" data-id=${state.i} data-name="${name}" data-relations="${relation}">
<svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${state.i}"></use></svg>
<div data-tip="${tipSelect}" style="width: 12em">${name}</div>
<div data-tip="${tipChange}" class="changeRelations" style="width: 6em">
<fill-box fill="${color}" size=".9em"></fill-box>
${relation}
</div>
</div>`; </div>`;
} }
body.innerHTML = lines; body.innerHTML = lines;
@ -84,8 +112,6 @@ function editDiplomacy() {
// add listeners // add listeners
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev))); body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev))); body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectStateOnLineClick));
body.querySelectorAll(".changeRelations").forEach(el => el.addEventListener("click", toggleDiplomacySelect));
applySorting(diplomacyHeader); applySorting(diplomacyHeader);
$("#diplomacyEditor").dialog(); $("#diplomacyEditor").dialog();
@ -95,19 +121,31 @@ function editDiplomacy() {
if (!layerIsOn("toggleStates")) return; if (!layerIsOn("toggleStates")) return;
const state = +event.target.dataset.id; const state = +event.target.dataset.id;
if (customization || !state) return; if (customization || !state) return;
const d = regions.select("#state"+state).attr("d"); const d = regions.select("#state" + state).attr("d");
const path = debug.append("path").attr("class", "highlight").attr("d", d) const path = debug
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1) .append("path")
.attr("class", "highlight")
.attr("d", d)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 1)
.attr("opacity", 1)
.attr("filter", "url(#blur1)"); .attr("filter", "url(#blur1)");
const l = path.node().getTotalLength(), dur = (l + 5000) / 2; const l = path.node().getTotalLength(),
dur = (l + 5000) / 2;
const i = d3.interpolateString("0," + l, l + "," + l); const i = d3.interpolateString("0," + l, l + "," + l);
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)}); path
.transition()
.duration(dur)
.attrTween("stroke-dasharray", function () {
return t => i(t);
});
} }
function stateHighlightOff(event) { function stateHighlightOff(event) {
debug.selectAll(".highlight").each(function() { debug.selectAll(".highlight").each(function () {
d3.select(this).transition().duration(1000).attr("opacity", 0).remove(); d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
}); });
} }
@ -118,22 +156,17 @@ function editDiplomacy() {
if (!sel) return; if (!sel) return;
if (!layerIsOn("toggleStates")) toggleStates(); if (!layerIsOn("toggleStates")) toggleStates();
statesBody.selectAll("path").each(function() { statesBody.selectAll("path").each(function () {
if (this.id.slice(0, 9) === "state-gap") return; // exclude state gap element if (this.id.slice(0, 9) === "state-gap") return; // exclude state gap element
const id = +this.id.slice(5); // state id const id = +this.id.slice(5); // state id
const index = statuses.indexOf(pack.states[id].diplomacy[sel]); // status index
const clr = index !== -1 ? colors[index] : "#4682b4"; // Self (bluish)
this.setAttribute("fill", clr);
statesBody.select("#state-gap"+id).attr("stroke", clr);
statesHalo.select("#state-border"+id).attr("stroke", d3.color(clr).darker().hex());
});
}
function selectStateOnLineClick() { const relation = pack.states[id].diplomacy[sel];
if (this.classList.contains("Self")) return; const color = relations[relation]?.color || "#4682b4";
body.querySelector("div.Self").classList.remove("Self");
this.classList.add("Self"); this.setAttribute("fill", color);
refreshDiplomacyEditor(); statesBody.select("#state-gap" + id).attr("stroke", color);
statesHalo.select("#state-border" + id).attr("stroke", d3.color(color).darker().hex());
});
} }
function selectStateOnMapClick() { function selectStateOnMapClick() {
@ -145,42 +178,61 @@ function editDiplomacy() {
if (+selectedLine.dataset.id === state) return; if (+selectedLine.dataset.id === state) return;
selectedLine.classList.remove("Self"); selectedLine.classList.remove("Self");
body.querySelector("div[data-id='"+state+"']").classList.add("Self"); body.querySelector("div[data-id='" + state + "']").classList.add("Self");
refreshDiplomacyEditor(); refreshDiplomacyEditor();
} }
function toggleDiplomacySelect(event) { function selectRelation(subjectId, objectId, currentRelation) {
event.stopPropagation(); const states = pack.states;
const select = document.getElementById("diplomacySelect");
const show = select.style.display === "none"; const subject = states[subjectId];
if (!show) {select.style.display = "none"; return;} const header = `<div style="margin-bottom: 0.3em"><svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${subject.i}"></use></svg> <b>${subject.fullName}</b></div>`;
select.style.display = "block";
const input = event.target.closest("div").querySelector("input"); const options = Object.entries(relations)
select.style.left = input.getBoundingClientRect().left + "px"; .map(
select.style.top = input.getBoundingClientRect().bottom + "px"; ([relation, {color, inText, tip}]) =>
body.dataset.state = event.target.closest("div.states").dataset.id; `<div style="margin-block: 0.2em" data-tip="${tip}"><label class="pointer">
<input type="radio" name="relationSelect" value="${relation}" ${currentRelation === relation && "checked"} >
<fill-box fill="${color}" size=".8em"></fill-box>
${inText}
</label></div>`
)
.join("");
const object = states[objectId];
const footer = `<div style="margin-top: 0.7em"><svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${object.i}"></use></svg> <b>${object.fullName}</b></div>`;
alertMessage.innerHTML = `<div style="overflow: hidden">${header} ${options} ${footer}</div>`;
$("#alert").dialog({
width: fitContent(),
title: `Change relations`,
buttons: {
Apply: function () {
const newRelation = document.querySelector('input[name="relationSelect"]:checked')?.value;
changeRelation(subjectId, objectId, currentRelation, newRelation);
$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
}
});
} }
function diplomacyChangeRelations(event) { function changeRelation(subjectId, objectId, oldRelation, newRelation) {
event.stopPropagation(); if (newRelation === oldRelation) return;
diplomacySelect.style.display = "none"; const states = pack.states;
const subject = +body.dataset.state; const chronicle = states[0].diplomacy;
const rel = event.target.innerHTML;
const states = pack.states, chronicle = states[0].diplomacy; const subjectName = states[subjectId].name;
const selectedLine = body.querySelector("div.Self"); const objectName = states[objectId].name;
const object = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
if (!object) return;
const objectName = states[object].name; // object of relations change
const subjectName = states[subject].name; // subject of relations change - actor
const oldRel = states[subject].diplomacy[object]; states[subjectId].diplomacy[objectId] = newRelation;
if (rel === oldRel) return; states[objectId].diplomacy[subjectId] = newRelation === "Vassal" ? "Suzerain" : newRelation === "Suzerain" ? "Vassal" : newRelation;
states[subject].diplomacy[object] = rel;
states[object].diplomacy[subject] = rel === "Vassal" ? "Suzerain" : rel === "Suzerain" ? "Vassal" : rel;
// update relation history // update relation history
const change = () => [`Relations change`, `${subjectName}-${getAdjective(objectName)} relations changed to ${rel.toLowerCase()}`]; const change = () => [`Relations change`, `${subjectName}-${getAdjective(objectName)} relations changed to ${newRelation.toLowerCase()}`];
const ally = () => [`Defence pact`, `${subjectName} entered into defensive pact with ${objectName}`]; const ally = () => [`Defence pact`, `${subjectName} entered into defensive pact with ${objectName}`];
const vassal = () => [`Vassalization`, `${subjectName} became a vassal of ${objectName}`]; const vassal = () => [`Vassalization`, `${subjectName} became a vassal of ${objectName}`];
const suzerain = () => [`Vassalization`, `${subjectName} vassalized ${objectName}`]; const suzerain = () => [`Vassalization`, `${subjectName} vassalized ${objectName}`];
@ -189,24 +241,33 @@ function editDiplomacy() {
const war = () => [`War declaration`, `${subjectName} declared a war on its enemy ${objectName}`]; const war = () => [`War declaration`, `${subjectName} declared a war on its enemy ${objectName}`];
const peace = () => { const peace = () => {
const treaty = `${subjectName} and ${objectName} agreed to cease fire and signed a peace treaty`; const treaty = `${subjectName} and ${objectName} agreed to cease fire and signed a peace treaty`;
const changed = rel === "Ally" ? ally() const changed =
: rel === "Vassal" ? vassal() newRelation === "Ally"
: rel === "Suzerain" ? suzerain() ? ally()
: rel === "Unknown" ? unknown() : newRelation === "Vassal"
: change(); ? vassal()
: newRelation === "Suzerain"
? suzerain()
: newRelation === "Unknown"
? unknown()
: change();
return [`War termination`, treaty, changed[1]]; return [`War termination`, treaty, changed[1]];
} };
if (oldRel === "Enemy") chronicle.push(peace()); else if (oldRelation === "Enemy") chronicle.push(peace());
if (rel === "Enemy") chronicle.push(war()); else else if (newRelation === "Enemy") chronicle.push(war());
if (rel === "Vassal") chronicle.push(vassal()); else else if (newRelation === "Vassal") chronicle.push(vassal());
if (rel === "Suzerain") chronicle.push(suzerain()); else else if (newRelation === "Suzerain") chronicle.push(suzerain());
if (rel === "Ally") chronicle.push(ally()); else else if (newRelation === "Ally") chronicle.push(ally());
if (rel === "Unknown") chronicle.push(unknown()); else else if (newRelation === "Unknown") chronicle.push(unknown());
if (rel === "Rival") chronicle.push(rival()); else else if (newRelation === "Rival") chronicle.push(rival());
chronicle.push(change()); else chronicle.push(change());
refreshDiplomacyEditor(); refreshDiplomacyEditor();
if (diplomacyMatrix.offsetParent) {
document.getElementById("diplomacyMatrixBody").innerHTML = "";
showRelationsMatrix();
}
} }
function regenerateRelations() { function regenerateRelations() {
@ -214,28 +275,52 @@ function editDiplomacy() {
refreshDiplomacyEditor(); refreshDiplomacyEditor();
} }
function resetRelations() {
const selectedId = +body.querySelector("div.Self")?.dataset?.id;
if (!selectedId) return;
const states = pack.states;
states[selectedId].diplomacy.forEach((relations, index) => {
if (relations !== "x") {
states[selectedId].diplomacy[index] = "Neutral";
states[index].diplomacy[selectedId] = "Neutral";
}
});
refreshDiplomacyEditor();
}
function showRelationsHistory() { function showRelationsHistory() {
const chronicle = pack.states[0].diplomacy; const chronicle = pack.states[0].diplomacy;
if (!chronicle.length) {tip("Relations history is blank", false, "error"); return;} if (!chronicle.length) return tip("Relations history is blank", false, "error");
let message = `<div autocorrect="off" spellcheck="false">`; let message = `<div autocorrect="off" spellcheck="false">`;
chronicle.forEach((e, d) => { chronicle.forEach((entry, d) => {
message += `<div>`; message += `<div>`;
e.forEach((l, i) => message += `<div contenteditable="true" data-id="${d}-${i}"${i ? "" : " style='font-weight:bold'"}>${l}</div>`); entry.forEach((l, i) => {
message += `<div contenteditable="true" data-id="${d}-${i}"${i ? "" : " style='font-weight:bold'"}>${l}</div>`;
});
message += `&#8205;</div>`; message += `&#8205;</div>`;
}); });
alertMessage.innerHTML = message + `</div><i id="info-line">Type to edit. Press Enter to add a new line, empty the element to remove it</i>`; alertMessage.innerHTML = message + `</div><div class="info-line">Type to edit. Press Enter to add a new line, empty the element to remove it</div>`;
alertMessage.querySelectorAll("div[contenteditable='true']").forEach(el => el.addEventListener("input", changeReliationsHistory)); alertMessage.querySelectorAll("div[contenteditable='true']").forEach(el => el.addEventListener("input", changeReliationsHistory));
$("#alert").dialog({title: "Relations history", position: {my: "center", at: "center", of: "svg"}, $("#alert").dialog({
title: "Relations history",
position: {my: "center", at: "center", of: "svg"},
buttons: { buttons: {
Save: function() { Save: function () {
const data = this.querySelector("div").innerText.split("\n").join("\r\n"); const data = this.querySelector("div").innerText.split("\n").join("\r\n");
const name = getFileName("Relations history") + ".txt"; const name = getFileName("Relations history") + ".txt";
downloadFile(data, name); downloadFile(data, name);
}, },
Clear: function() {pack.states[0].diplomacy = []; $(this).dialog("close");}, Clear: function () {
Close: function() {$(this).dialog("close");} pack.states[0].diplomacy = [];
$(this).dialog("close");
},
Close: function () {
$(this).dialog("close");
}
} }
}); });
} }
@ -251,22 +336,48 @@ function editDiplomacy() {
function showRelationsMatrix() { function showRelationsMatrix() {
const states = pack.states.filter(s => s.i && !s.removed); const states = pack.states.filter(s => s.i && !s.removed);
const valid = states.map(s => s.i); const valid = states.map(state => state.i);
const diplomacyMatrixBody = document.getElementById("diplomacyMatrixBody");
let message = `<table class="matrix-table"><tr><th data-tip='&#8205;'></th>`; let table = `<table><thead><tr><th data-tip='&#8205;'></th>`;
message += states.map(s => `<th data-tip='See relations to ${s.fullName}'>${s.name}</th>`).join("") + `</tr>`; // headers table += states.map(state => `<th data-tip='Relations to ${state.fullName}'>${state.name}</th>`).join("") + `</tr>`;
states.forEach(s => { table += `<tbody>`;
message += `<tr><th data-tip='See relations of ${s.fullName}'>${s.name}</th>` + s.diplomacy
.filter((v, i) => valid.includes(i)).map((r, i) => { states.forEach(state => {
const desc = description[statuses.indexOf(r)]; table +=
const tip = desc ? s.fullName + desc + pack.states[valid[i]].fullName : '&#8205;'; `<tr data-id=${state.i}><th data-tip='Relations of ${state.fullName}'>${state.name}</th>` +
return `<td data-tip='${tip}' class='${r}'>${r}</td>` state.diplomacy
}).join("") + "</tr>"; .filter((v, i) => valid.includes(i))
.map((relation, index) => {
const relationObj = relations[relation];
if (!relationObj) return `<td class='${relation}'>${relation}</td>`;
const objectState = pack.states[valid[index]];
const tip = `${state.fullName} ${relationObj.inText} ${objectState.fullName}`;
return `<td data-id=${objectState.i} data-tip='${tip}' class='${relation}'>${relation}</td>`;
})
.join("") +
"</tr>";
}); });
message += `</table>`;
alertMessage.innerHTML = message;
$("#alert").dialog({title: "Relations matrix", width: fitContent(), position: {my: "center", at: "center", of: "svg"}, buttons: {}}); table += `</tbody></table>`;
diplomacyMatrixBody.innerHTML = table;
const tableEl = diplomacyMatrixBody.querySelector("table");
tableEl.addEventListener("click", function (event) {
const el = event.target;
if (el.tagName !== "TD") return;
const currentRelation = el.innerText;
if (!relations[currentRelation]) return;
const subjectId = +el.closest("tr")?.dataset?.id;
const objectId = +el?.dataset?.id;
selectRelation(subjectId, objectId, currentRelation);
});
$("#diplomacyMatrix").dialog({title: "Relations matrix", position: {my: "center", at: "center", of: "svg"}, buttons: {}});
} }
function downloadDiplomacyData() { function downloadDiplomacyData() {
@ -288,7 +399,8 @@ function editDiplomacy() {
clearMainTip(); clearMainTip();
const selected = body.querySelector("div.Self"); const selected = body.querySelector("div.Self");
if (selected) selected.classList.remove("Self"); if (selected) selected.classList.remove("Self");
if (layerIsOn("toggleStates")) drawStates(); else toggleStates(); if (layerIsOn("toggleStates")) drawStates();
else toggleStates();
debug.selectAll(".highlight").remove(); debug.selectAll(".highlight").remove();
} }
} }

View file

@ -265,41 +265,48 @@ function getBurgSeed(burg) {
} }
function getMFCGlink(burg) { function getMFCGlink(burg) {
if (burg.link) return burg.link;
const {cells} = pack; const {cells} = pack;
const {name, population, cell} = burg; const {i, name, population: burgPopulation, cell} = burg;
const burgSeed = getBurgSeed(burg); const seed = getBurgSeed(burg);
const sizeRaw = 2.13 * Math.pow((population * populationRate) / urbanDensity, 0.385);
const sizeRaw = 2.13 * Math.pow((burgPopulation * populationRate) / urbanDensity, 0.385);
const size = minmax(Math.ceil(sizeRaw), 6, 100); const size = minmax(Math.ceil(sizeRaw), 6, 100);
const people = rn(population * populationRate * urbanization); const population = rn(burgPopulation * populationRate * urbanization);
const river = cells.r[cell] ? 1 : 0;
const coast = Number(burg.port > 0);
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : null;
const biome = cells.biome[cell];
const arableBiomes = river ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8];
const farms = +arableBiomes.includes(biome);
const citadel = +burg.citadel;
const urban_castle = +(citadel && each(2)(i));
const hub = +cells.road[cell] > 50; const hub = +cells.road[cell] > 50;
const river = cells.r[cell] ? 1 : 0;
const coast = +burg.port;
const citadel = +burg.citadel;
const walls = +burg.walls; const walls = +burg.walls;
const plaza = +burg.plaza; const plaza = +burg.plaza;
const temple = +burg.temple; const temple = +burg.temple;
const shanty = +burg.shanty; const shantytown = +burg.shanty;
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : "";
function getSeaDirections(i) { function getSeaDirections(i) {
const p1 = cells.p[i]; const p1 = cells.p[i];
const p2 = cells.p[cells.haven[i]]; const p2 = cells.p[cells.haven[i]];
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90; let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
if (deg < 0) deg += 360; if (deg < 0) deg += 360;
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east return rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
return "&sea=" + norm;
} }
const baseURL = "https://watabou.github.io/city-generator/?random=0&continuous=0"; const parameters = {name, population, size, seed, river, coast, farms, citadel, urban_castle, hub, plaza, temple, walls, shantytown, gates: -1};
const url = `${baseURL}&name=${name}&population=${people}&size=${size}&seed=${burgSeed}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`; const url = new URL("https://watabou.github.io/city-generator");
return url; url.search = new URLSearchParams(parameters);
} if (sea) url.searchParams.append("sea", sea);
function toggleBurgLock(burg) { return url.toString();
const b = pack.burgs[burg];
b.lock = b.lock ? 0 : 1;
} }
// draw legend box // draw legend box
@ -418,10 +425,10 @@ function clearLegend() {
function createPicker() { function createPicker() {
const pos = () => tip("Drag to change the picker position"); const pos = () => tip("Drag to change the picker position");
const cl = () => tip("Click to close the picker"); const cl = () => tip("Click to close the picker");
const closePicker = () => contaiter.style("display", "none"); const closePicker = () => container.style("display", "none");
const contaiter = d3.select("body").append("svg").attr("id", "pickerContainer").attr("width", "100%").attr("height", "100%"); const container = d3.select("body").append("svg").attr("id", "pickerContainer").attr("width", "100%").attr("height", "100%");
contaiter container
.append("rect") .append("rect")
.attr("x", 0) .attr("x", 0)
.attr("y", 0) .attr("y", 0)
@ -430,7 +437,7 @@ function createPicker() {
.attr("opacity", 0.2) .attr("opacity", 0.2)
.on("mousemove", cl) .on("mousemove", cl)
.on("click", closePicker); .on("click", closePicker);
const picker = contaiter const picker = container
.append("g") .append("g")
.attr("id", "picker") .attr("id", "picker")
.call( .call(
@ -487,7 +494,7 @@ function createPicker() {
const colors = picker.append("g").attr("id", "pickerColors").attr("stroke", "#333333"); const colors = picker.append("g").attr("id", "pickerColors").attr("stroke", "#333333");
const hatches = picker.append("g").attr("id", "pickerHatches").attr("stroke", "#333333"); const hatches = picker.append("g").attr("id", "pickerHatches").attr("stroke", "#333333");
const hatching = d3.selectAll("g#hatching > pattern"); const hatching = d3.selectAll("g#defs-hatching > pattern");
const number = hatching.size(); const number = hatching.size();
const clr = d3.range(number).map(i => d3.hsl((i / number) * 360, 0.7, 0.7).hex()); const clr = d3.range(number).map(i => d3.hsl((i / number) * 360, 0.7, 0.7).hex());
@ -497,8 +504,8 @@ function createPicker() {
.attr("id", "picker_" + d) .attr("id", "picker_" + d)
.attr("fill", d) .attr("fill", d)
.attr("class", i ? "" : "selected") .attr("class", i ? "" : "selected")
.attr("x", i * 22 + 4) .attr("x", (i % 14) * 22 + 4)
.attr("y", 40) .attr("y", 40 + Math.floor(i / 14)*20)
.attr("width", 16) .attr("width", 16)
.attr("height", 16); .attr("height", 16);
}); });
@ -508,20 +515,20 @@ function createPicker() {
.append("rect") .append("rect")
.attr("id", "picker_" + this.id) .attr("id", "picker_" + this.id)
.attr("fill", "url(#" + this.id + ")") .attr("fill", "url(#" + this.id + ")")
.attr("x", i * 22 + 4) .attr("x", (i % 14) * 22 + 4)
.attr("y", 61) .attr("y", Math.floor(i / 14)*20 + 20 + (number * 2))
.attr("width", 16) .attr("width", 16)
.attr("height", 16); .attr("height", 16)
}); });
colors colors
.selectAll("rect") .selectAll("rect")
.on("click", pickerFillClicked) .on("click", pickerFillClicked)
.on("mousemove", () => tip("Click to fill with the color")); .on("mouseover", () => tip("Click to fill with the color"));
hatches hatches
.selectAll("rect") .selectAll("rect")
.on("click", pickerFillClicked) .on("click", pickerFillClicked)
.on("mousemove", () => tip("Click to fill with the hatching")); .on("mouseover", function() { tip("Click to fill with the hatching " + this.id) });
// append box // append box
const bbox = picker.node().getBBox(); const bbox = picker.node().getBBox();
@ -537,10 +544,10 @@ function createPicker() {
.attr("fill", "#ffffff") .attr("fill", "#ffffff")
.attr("stroke", "#5d4651") .attr("stroke", "#5d4651")
.on("mousemove", pos); .on("mousemove", pos);
picker.insert("text", ":first-child").attr("x", 291).attr("y", -10).attr("id", "pickerCloseText").text("✕"); picker.insert("text", ":first-child").attr("x", width-20).attr("y", -10).attr("id", "pickerCloseText").text("✕");
picker picker
.insert("rect", ":first-child") .insert("rect", ":first-child")
.attr("x", 288) .attr("x", width-23)
.attr("y", -21) .attr("y", -21)
.attr("id", "pickerCloseRect") .attr("id", "pickerCloseRect")
.attr("width", 14) .attr("width", 14)

View file

@ -460,32 +460,31 @@ function showInfo() {
const Discord = link("https://discordapp.com/invite/X7E84HU", "Discord"); const Discord = link("https://discordapp.com/invite/X7E84HU", "Discord");
const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit"); const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit");
const Patreon = link("https://www.patreon.com/azgaar", "Patreon"); const Patreon = link("https://www.patreon.com/azgaar", "Patreon");
const Trello = link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Trello");
const Armoria = link("https://azgaar.github.io/Armoria", "Armoria"); const Armoria = link("https://azgaar.github.io/Armoria", "Armoria");
const QuickStart = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial", "Quick start tutorial"); const QuickStart = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial", "Quick start tutorial");
const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page"); const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page");
const VideoTutorial = link("https://youtube.com/playlist?list=PLtgiuDC8iVR2gIG8zMTRn7T_L0arl9h1C", "Video tutorial");
alertMessage.innerHTML = ` alertMessage.innerHTML = `
<b>Fantasy Map Generator</b> (FMG) is an open-source application, it means the code is published an anyone can use it. <b>Fantasy Map Generator</b> (FMG) is a free open-source application.
In case of FMG is also means that you own all created maps and can use them as you wish, you can even sell them. It means that you own all created maps and can use them as you wish.
<p>The development is supported by community, you can donate on ${Patreon}. <p>The development is community-backed, you can donate on ${Patreon}.
You can also help creating overviews, tutorials and spreding the word about the Generator.</p> You can also help creating overviews, tutorials and spreding the word about the Generator.</p>
<p>The best way to get help is to contact the community on ${Discord} and ${Reddit}. <p>The best way to get help is to contact the community on ${Discord} and ${Reddit}.
Before asking questions, please check out the ${QuickStart} and the ${QAA}.</p> Before asking questions, please check out the ${QuickStart}, the ${QAA}, and ${VideoTutorial}.</p>
<p>Track the development process on ${Trello}.</p> <p>Check out our another project: ${Armoria} heraldry generator and editor.</p>
<p>Check out our new project: ${Armoria}, heraldry generator and editor.</p>
<b>Links:</b>
<ul style="columns:2"> <ul style="columns:2">
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator", "GitHub repository")}</li> <li>${link("https://github.com/Azgaar/Fantasy-Map-Generator", "GitHub repository")}</li>
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE", "License")}</li> <li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE", "License")}</li>
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "Changelog")}</li> <li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "Changelog")}</li>
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys", "Hotkeys")}</li> <li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys", "Hotkeys")}</li>
<li>${link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Devboard")}</li>
<li><a href="mailto:azgaar.fmg@yandex.by" target="_blank">Contact Azgaar</a></li>
</ul>`; </ul>`;
$("#alert").dialog({ $("#alert").dialog({

View file

@ -470,23 +470,26 @@ function togglePrec(event) {
function drawPrec() { function drawPrec() {
prec.selectAll("circle").remove(); prec.selectAll("circle").remove();
const cells = grid.cells, const {cells, points} = grid;
p = grid.points;
prec.style("display", "block"); prec.style("display", "block");
const show = d3.transition().duration(800).ease(d3.easeSinIn); const show = d3.transition().duration(800).ease(d3.easeSinIn);
prec.selectAll("text").attr("opacity", 0).transition(show).attr("opacity", 1); prec.selectAll("text").attr("opacity", 0).transition(show).attr("opacity", 1);
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
const data = cells.i.filter(i => cells.h[i] >= 20 && cells.prec[i]); const data = cells.i.filter(i => cells.h[i] >= 20 && cells.prec[i]);
const getRadius = prec => rn(Math.sqrt(prec / 4) / cellsNumberModifier, 2);
prec prec
.selectAll("circle") .selectAll("circle")
.data(data) .data(data)
.enter() .enter()
.append("circle") .append("circle")
.attr("cx", d => p[d][0]) .attr("cx", d => points[d][0])
.attr("cy", d => p[d][1]) .attr("cy", d => points[d][1])
.attr("r", 0) .attr("r", 0)
.transition(show) .transition(show)
.attr("r", d => rn(Math.max(Math.sqrt(cells.prec[d] * 0.5), 0.8), 2)); .attr("r", d => getRadius(cells.prec[d]));
} }
function togglePopulation(event) { function togglePopulation(event) {

View file

@ -75,12 +75,9 @@ function overviewMilitary() {
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" "); const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" "); const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" ");
lines += `<div class="states" data-id=${s.i} data-state="${ lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}"
s.name data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}"> <fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
s.color
}" class="fillRect"></svg>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly> <input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
${lineData} ${lineData}
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div> <div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>

View file

@ -17,18 +17,24 @@ function editNamesbase() {
document.getElementById("namesbaseMax").addEventListener("input", updateBaseMax); document.getElementById("namesbaseMax").addEventListener("input", updateBaseMax);
document.getElementById("namesbaseDouble").addEventListener("input", updateBaseDublication); document.getElementById("namesbaseDouble").addEventListener("input", updateBaseDublication);
document.getElementById("namesbaseAdd").addEventListener("click", namesbaseAdd); document.getElementById("namesbaseAdd").addEventListener("click", namesbaseAdd);
document.getElementById("namesbaseAnalize").addEventListener("click", analizeNamesbase); document.getElementById("namesbaseAnalyze").addEventListener("click", analyzeNamesbase);
document.getElementById("namesbaseDefault").addEventListener("click", namesbaseRestoreDefault); document.getElementById("namesbaseDefault").addEventListener("click", namesbaseRestoreDefault);
document.getElementById("namesbaseDownload").addEventListener("click", namesbaseDownload); document.getElementById("namesbaseDownload").addEventListener("click", namesbaseDownload);
document.getElementById("namesbaseUpload").addEventListener("click", () => namesbaseToLoad.click()); document.getElementById("namesbaseUpload").addEventListener("click", () => document.getElementById("namesbaseToLoad").click());
document.getElementById("namesbaseToLoad").addEventListener("change", function() {uploadFile(this, namesbaseUpload)}); document.getElementById("namesbaseToLoad").addEventListener("change", function () {
uploadFile(this, namesbaseUpload);
});
document.getElementById("namesbaseCA").addEventListener("click", () => {
openURL("https://cartographyassets.com/asset-category/specific-assets/azgaars-generator/namebases/");
});
document.getElementById("namesbaseSpeak").addEventListener("click", () => speak(namesbaseExamples.textContent)); document.getElementById("namesbaseSpeak").addEventListener("click", () => speak(namesbaseExamples.textContent));
createBasesList(); createBasesList();
updateInputs(); updateInputs();
$("#namesbaseEditor").dialog({ $("#namesbaseEditor").dialog({
title: "Namesbase Editor", width: "42.5em", title: "Namesbase Editor",
width: "auto",
position: {my: "center", at: "center", of: "svg"} position: {my: "center", at: "center", of: "svg"}
}); });
@ -40,7 +46,10 @@ function editNamesbase() {
function updateInputs() { function updateInputs() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById("namesbaseSelect").value;
if (!nameBases[base]) {tip(`Namesbase ${base} is not defined`, false, "error"); return;} if (!nameBases[base]) {
tip(`Namesbase ${base} is not defined`, false, "error");
return;
}
document.getElementById("namesbaseTextarea").value = nameBases[base].b; document.getElementById("namesbaseTextarea").value = nameBases[base].b;
document.getElementById("namesbaseName").value = nameBases[base].name; document.getElementById("namesbaseName").value = nameBases[base].name;
document.getElementById("namesbaseMin").value = nameBases[base].min; document.getElementById("namesbaseMin").value = nameBases[base].min;
@ -52,7 +61,7 @@ function editNamesbase() {
function updateExamples() { function updateExamples() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById("namesbaseSelect").value;
let examples = ""; let examples = "";
for (let i=0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const example = Names.getBase(base); const example = Names.getBase(base);
if (example === undefined) { if (example === undefined) {
examples = "Cannot generate examples. Please verify the data"; examples = "Cannot generate examples. Please verify the data";
@ -84,13 +93,19 @@ function editNamesbase() {
function updateBaseMin() { function updateBaseMin() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById("namesbaseSelect").value;
if (+this.value > nameBases[base].max) {tip("Minimal length cannot be greater than maximal", false, "error"); return;} if (+this.value > nameBases[base].max) {
tip("Minimal length cannot be greater than maximal", false, "error");
return;
}
nameBases[base].min = +this.value; nameBases[base].min = +this.value;
} }
function updateBaseMax() { function updateBaseMax() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById("namesbaseSelect").value;
if (+this.value < nameBases[base].min) {tip("Maximal length should be greater than minimal", false, "error"); return;} if (+this.value < nameBases[base].min) {
tip("Maximal length should be greater than minimal", false, "error");
return;
}
nameBases[base].max = +this.value; nameBases[base].max = +this.value;
} }
@ -99,59 +114,70 @@ function editNamesbase() {
nameBases[base].d = this.value; nameBases[base].d = this.value;
} }
function analizeNamesbase() { function analyzeNamesbase() {
const string = document.getElementById("namesbaseTextarea").value; const namesSourceString = document.getElementById("namesbaseTextarea").value;
if (!string) {tip("Names data field should not be empty", false, "error"); return;} const namesArray = namesSourceString.toLowerCase().split(",");
const base = string.toLowerCase(); const length = namesArray.length;
const array = base.split(","); if (!namesSourceString || !length) return tip("Names data should not be empty", false, "error");
const l = array.length;
if (!l) {tip("Names data should not be empty", false, "error"); return;}
const wordsLength = array.map(n => n.length); const chain = Names.calculateChain(namesSourceString);
const multi = rn(d3.mean(array.map(n => (n.match(/ /i)||[]).length)) * 100, 2); const variety = rn(d3.mean(Object.values(chain).map(keyValue => keyValue.length)));
const geminate = array.map(name => name.match(/[^\w\s]|(.)(?=\1)/g)||[]).flat();
const doubled = ([...new Set(geminate)].filter(l => geminate.filter(d => d === l).length > 3)||["none"]).join("");
const chain = Names.calculateChain(string);
const depth = rn(d3.mean(Object.keys(chain).map(key => chain[key].filter(c => c !== " ").length)));
const nonLatin = (string.match(/[^\u0000-\u007f]/g)||["none"]).join("");
const lengthStat = const wordsLength = namesArray.map(n => n.length);
l < 30 ? "<span style='color:red'>[not enough]</span>" :
l < 150 ? "<span style='color:darkred'>[low]</span>" :
l < 150 ? "<span style='color:orange'>[low]</span>" :
l < 400 ? "<span style='color:green'>[good]</span>" :
l < 600 ? "<span style='color:orange'>[overmuch]</span>" :
"<span style='color:darkred'>[overmuch]</span>";
const rangeStat = const nonLatin = namesSourceString.match(/[^\u0000-\u007f]/g);
l < 10 ? "<span style='color:red'>[low]</span>" : const nonBasicLatinChars = nonLatin
l < 15 ? "<span style='color:darkred'>[low]</span>" : ? unique(
l < 20 ? "<span style='color:orange'>[low]</span>" : namesSourceString
"<span style='color:green'>[good]</span>"; .match(/[^\u0000-\u007f]/g)
.join("")
.toLowerCase()
).join("")
: "none";
const depthStat = const geminate = namesArray.map(name => name.match(/[^\w\s]|(.)(?=\1)/g) || []).flat();
l < 15 ? "<span style='color:red'>[low]</span>" : const doubled = unique(geminate).filter(char => geminate.filter(doudledChar => doudledChar === char).length > 3) || ["none"];
l < 20 ? "<span style='color:darkred'>[low]</span>" :
l < 25 ? "<span style='color:orange'>[low]</span>" : const duplicates = unique(namesArray.filter((e, i, a) => a.indexOf(e) !== i)).join(", ") || "none";
"<span style='color:green'>[good]</span>"; const multiwordRate = d3.mean(namesArray.map(n => +n.includes(" ")));
const getLengthQuality = () => {
if (length < 30) return "<span data-tip='Namesbase contains < 30 names - not enough to generate reasonable data' style='color:red'>[not enough]</span>";
if (length < 100) return "<span data-tip='Namesbase contains < 100 names - not enough to generate good names' style='color:darkred'>[low]</span>";
if (length <= 400) return "<span data-tip='Namesbase contains a reasonable number of samples' style='color:green'>[good]</span>";
return "<span data-tip='Namesbase contains > 400 names. That is too much, try to reduce it to ~300 names' style='color:darkred'>[overmuch]</span>";
};
const getVarietyLevel = () => {
if (variety < 15) return "<span data-tip='Namesbase average variety < 15 - generated names will be too repetitive' style='color:red'>[low]</span>";
if (variety < 30) return "<span data-tip='Namesbase average variety < 30 - names can be too repetitive' style='color:orange'>[mean]</span>";
return "<span data-tip='Namesbase variety is good' style='color:green'>[good]</span>";
};
alertMessage.innerHTML = `<div style="line-height: 1.6em; max-width: 20em"> alertMessage.innerHTML = `<div style="line-height: 1.6em; max-width: 20em">
<div>Namesbase length: ${l} ${lengthStat}</div> <div data-tip="Number of names provided">Namesbase length: ${length} ${getLengthQuality()}</div>
<div>Namesbase range: ${Object.keys(chain).length-1} ${rangeStat}</div> <div data-tip="Average number of generation variants for each key in the chain">Namesbase variety: ${variety} ${getVarietyLevel()}</div>
<div>Namesbase depth: ${depth} ${depthStat}</div>
<div>Non-basic chars: ${nonLatin}</div>
<hr> <hr>
<div>Min name length: ${d3.min(wordsLength)}</div> <div data-tip="The shortest name length">Min name length: ${d3.min(wordsLength)}</div>
<div>Max name length: ${d3.max(wordsLength)}</div> <div data-tip="The longest name length">Max name length: ${d3.max(wordsLength)}</div>
<div>Mean name length: ${rn(d3.mean(wordsLength), 1)}</div> <div data-tip="Average name length">Mean name length: ${rn(d3.mean(wordsLength), 1)}</div>
<div>Median name length: ${d3.median(wordsLength)}</div> <div data-tip="Common name length">Median name length: ${d3.median(wordsLength)}</div>
<div>Doubled chars: ${doubled}</div> <hr>
<div>Multi-word names: ${multi}%</div> <div data-tip="Characters outside of Basic Latin have bad font support">Non-basic chars: ${nonBasicLatinChars}</div>
<div data-tip="Characters that are frequently (more than 3 times) doubled">Doubled chars: ${doubled.join("")}</div>
<div data-tip="Names used more than one time">Duplicates: ${duplicates}</div>
<div data-tip="Percentage of names containing space character">Multi-word names: ${rn(multiwordRate * 100, 2)}%</div>
</div>`; </div>`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, title: "Data Analysis", resizable: false,
title: "Data Analysis",
position: {my: "left top-30", at: "right+10 top", of: "#namesbaseEditor"}, position: {my: "left top-30", at: "right+10 top", of: "#namesbaseEditor"},
buttons: {OK: function() {$(this).dialog("close");}} buttons: {
OK: function () {
$(this).dialog("close");
}
}
}); });
} }
@ -171,35 +197,42 @@ function editNamesbase() {
function namesbaseRestoreDefault() { function namesbaseRestoreDefault() {
alertMessage.innerHTML = `Are you sure you want to restore default namesbase?`; alertMessage.innerHTML = `Are you sure you want to restore default namesbase?`;
$("#alert").dialog({resizable: false, title: "Restore default data", $("#alert").dialog({
resizable: false,
title: "Restore default data",
buttons: { buttons: {
Restore: function() { Restore: function () {
$(this).dialog("close"); $(this).dialog("close");
Names.clearChains(); Names.clearChains();
nameBases = Names.getNameBases(); nameBases = Names.getNameBases();
createBasesList(); createBasesList();
updateInputs(); updateInputs();
}, },
Cancel: function() {$(this).dialog("close");} Cancel: function () {
$(this).dialog("close");
}
} }
}); });
} }
function namesbaseDownload() { function namesbaseDownload() {
const data = nameBases.map((b,i) => `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${b.b}`).join("\r\n"); const data = nameBases.map((b, i) => `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${b.b}`).join("\r\n");
const name = getFileName("Namesbase") + ".txt"; const name = getFileName("Namesbase") + ".txt";
downloadFile(data, name); downloadFile(data, name);
} }
function namesbaseUpload(dataLoaded) { function namesbaseUpload(dataLoaded) {
const data = dataLoaded.split("\r\n"); const data = dataLoaded.split("\r\n");
if (!data || !data[0]) {tip("Cannot load a namesbase. Please check the data format", false, "error"); return;} if (!data || !data[0]) {
tip("Cannot load a namesbase. Please check the data format", false, "error");
return;
}
Names.clearChains(); Names.clearChains();
nameBases = []; nameBases = [];
data.forEach(d => { data.forEach(d => {
const e = d.split("|"); const e = d.split("|");
nameBases.push({name:e[0], min:e[1], max:e[2], d:e[3], m:e[4], b:e[5]}); nameBases.push({name: e[0], min: e[1], max: e[2], d: e[3], m: e[4], b: e[5]});
}); });
createBasesList(); createBasesList();

View file

@ -102,7 +102,9 @@ function showSupporters() {
Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel, Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel,
Mike Conley,Xavier privé,Hope You're Well,Mark Sprietsma,Robert Landry,Nick Mowry,steve hall,Markell,Josh Wren,Neutrix,BLRageQuit,Rocky, Mike Conley,Xavier privé,Hope You're Well,Mark Sprietsma,Robert Landry,Nick Mowry,steve hall,Markell,Josh Wren,Neutrix,BLRageQuit,Rocky,
Dario Spadavecchia,Bas Kroot,John Patrick Callahan Jr,Alexandra Vesey,D,Exp1nt,james,Braxton Istace,w,Rurikid,AntiBlock,Redsauz,BigE0021, Dario Spadavecchia,Bas Kroot,John Patrick Callahan Jr,Alexandra Vesey,D,Exp1nt,james,Braxton Istace,w,Rurikid,AntiBlock,Redsauz,BigE0021,
Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya,www15o,Jan Bundesmann,Angelique Badger`; Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya,www15o,Jan Bundesmann,Angelique Badger,Joshua Xiong,Moist mongol,
Frank Fewkes,jason baldrick,Game Master Pro,Andrew Kircher,Preston Mitchell,Chris Kohut,Emarandzeb,Trentin Bergeron,Damon Gallaty,Pleaseworkforonce,
Jordan,William Markus,Sidr Dim`;
const array = supporters const array = supporters
.replace(/(?:\r\n|\r|\n)/g, "") .replace(/(?:\r\n|\r|\n)/g, "")
@ -287,18 +289,16 @@ function generateMapWithSeed(source) {
} }
function showSeedHistoryDialog() { function showSeedHistoryDialog() {
const alert = mapHistory const lines = mapHistory.map((h, i) => {
.map(function (h, i) { const created = new Date(h.created).toLocaleTimeString();
const created = new Date(h.created).toLocaleTimeString(); const button = `<i data-tip="Click to generate a map with this seed" onclick="restoreSeed(${i})" class="icon-history optionsSeedRestore"></i>`;
const button = `<i data-tip"Click to generate a map with this seed" onclick="restoreSeed(${i})" class="icon-history optionsSeedRestore"></i>`; return `<li>Seed: ${h.seed} ${button}. Size: ${h.width}x${h.height}. Template: ${h.template}. Created: ${created}</li>`;
return `<div>${i + 1}. Seed: ${h.seed} ${button}. Size: ${h.width}x${h.height}. Template: ${h.template}. Created: ${created}</div>`; });
}) alertMessage.innerHTML = `<ol style="margin: 0; padding-left: 1.5em">${lines.join("")}</ol>`;
.join("");
alertMessage.innerHTML = alert;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Seed history", title: "Seed history",
width: fitContent(),
position: {my: "center", at: "center", of: "svg"} position: {my: "center", at: "center", of: "svg"}
}); });
} }

View file

@ -44,7 +44,8 @@ function editProvinces() {
cl = el.classList, cl = el.classList,
line = el.parentNode, line = el.parentNode,
p = +line.dataset.id; p = +line.dataset.id;
if (cl.contains("fillRect")) changeFill(el);
if (el.tagName === "FILL-BOX") changeFill(el);
else if (cl.contains("name")) editProvinceName(p); else if (cl.contains("name")) editProvinceName(p);
else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]); else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]);
else if (cl.contains("icon-star-empty")) capitalZoomIn(p); else if (cl.contains("icon-star-empty")) capitalZoomIn(p);
@ -133,11 +134,9 @@ function editProvinces() {
lines += `<div class="states" data-id=${p.i} data-name="${p.name}" data-form="${p.formName}" data-color="${ lines += `<div class="states" data-id=${p.i} data-name="${p.name}" data-form="${p.formName}" data-color="${
p.color p.color
}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}> }" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}>
<svg data-tip="Province fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${ <fill-box fill="${p.color}"></fill-box>
p.color
}" class="fillRect pointer"></svg>
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly> <input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly>
<svg data-tip="Click to show and edit province emblem" class="coaIcon hide" viewBox="0 0 200 200"><use href="#provinceCOA${p.i}"></use></svg> <svg data-tip="Click to show and edit province emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#provinceCOA${p.i}"></use></svg>
<input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly> <input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly>
<span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${p.burg ? "" : "placeholder"}"></span> <span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${p.burg ? "" : "placeholder"}"></span>
<select data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital" class="cultureBase hide ${ <select data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital" class="cultureBase hide ${
@ -215,14 +214,14 @@ function editProvinces() {
function changeFill(el) { function changeFill(el) {
const currentFill = el.getAttribute("fill"); const currentFill = el.getAttribute("fill");
const p = +el.parentNode.parentNode.dataset.id; const p = +el.parentNode.dataset.id;
const callback = function (fill) { const callback = newFill => {
el.setAttribute("fill", fill); el.fill = newFill;
pack.provinces[p].color = fill; pack.provinces[p].color = newFill;
const g = provs.select("#provincesBody"); const g = provs.select("#provincesBody");
g.select("#province" + p).attr("fill", fill); g.select("#province" + p).attr("fill", newFill);
g.select("#province-gap" + p).attr("stroke", fill); g.select("#province-gap" + p).attr("stroke", newFill);
}; };
openPicker(currentFill, callback); openPicker(currentFill, callback);

View file

@ -14,7 +14,9 @@ function overviewRegiments(state) {
updateHeaders(); updateHeaders();
$("#regimentsOverview").dialog({ $("#regimentsOverview").dialog({
title: "Regiments Overview", resizable: false, width: fitContent(), title: "Regiments Overview",
resizable: false,
width: fitContent(),
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
}); });
@ -31,11 +33,13 @@ function overviewRegiments(state) {
header.querySelectorAll(".removable").forEach(el => el.remove()); header.querySelectorAll(".removable").forEach(el => el.remove());
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html); const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
for (const u of options.military) { for (const u of options.military) {
const label = capitalize(u.name.replace(/_/g, ' ')); const label = capitalize(u.name.replace(/_/g, " "));
insert(`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label}&nbsp;</div>`); insert(`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label}&nbsp;</div>`);
} }
header.querySelectorAll(".removable").forEach(function(e) { header.querySelectorAll(".removable").forEach(function (e) {
e.addEventListener("click", function() {sortLines(this);}); e.addEventListener("click", function () {
sortLines(this);
});
}); });
} }
@ -51,11 +55,13 @@ function overviewRegiments(state) {
if (state !== -1 && s.i !== state) continue; // specific state is selected if (state !== -1 && s.i !== state) continue; // specific state is selected
for (const r of s.military) { for (const r of s.military) {
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name]||0}`).join(" "); const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name] || 0}`).join(" ");
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name]||0}</div>`).join(" "); const lineData = options.military
.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name] || 0}</div>`)
.join(" ");
lines += `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}"> lines += `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg> <fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly> <input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
<span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span> <span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span>
<input data-tip="Regiment's name" style="width:13em" value="${r.name}" readonly> <input data-tip="Regiment's name" style="width:13em" value="${r.name}" readonly>
@ -70,12 +76,15 @@ function overviewRegiments(state) {
lines += `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments"> lines += `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div> <div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div>
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name]||0)))}</div>`).join(" ")} ${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}</div>`).join(" ")}
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div> <div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
</div>`; </div>`;
body.insertAdjacentHTML("beforeend", lines); body.insertAdjacentHTML("beforeend", lines);
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} if (body.dataset.type === "percentage") {
body.dataset.type = "absolute";
togglePercentageMode();
}
applySorting(regimentsHeader); applySorting(regimentsHeader);
// add listeners // add listeners
@ -87,7 +96,7 @@ function overviewRegiments(state) {
const filter = document.getElementById("regimentsFilter"); const filter = document.getElementById("regimentsFilter");
filter.options.length = 0; // remove all options filter.options.length = 0; // remove all options
filter.options.add(new Option(`all`, -1, false, state === -1)); filter.options.add(new Option(`all`, -1, false, state === -1));
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name) ? 1 : -1); const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
statesSorted.forEach(s => filter.options.add(new Option(s.name, s.i, false, s.i == state))); statesSorted.forEach(s => filter.options.add(new Option(s.name, s.i, false, s.i == state)));
} }
@ -108,19 +117,20 @@ function overviewRegiments(state) {
if (body.dataset.type === "absolute") { if (body.dataset.type === "absolute") {
body.dataset.type = "percentage"; body.dataset.type = "percentage";
const lines = body.querySelectorAll(":scope > div:not(.totalLine)"); const lines = body.querySelectorAll(":scope > div:not(.totalLine)");
const array = Array.from(lines), cache = []; const array = Array.from(lines),
cache = [];
const total = function(type) { const total = function (type) {
if (cache[type]) cache[type]; if (cache[type]) cache[type];
cache[type] = d3.sum(array.map(el => +el.dataset[type])); cache[type] = d3.sum(array.map(el => +el.dataset[type]));
return cache[type]; return cache[type];
} };
lines.forEach(function(el) { lines.forEach(function (el) {
el.querySelectorAll("div").forEach(function(div) { el.querySelectorAll("div").forEach(function (div) {
const type = div.dataset.type; const type = div.dataset.type;
if (type === "rate") return; if (type === "rate") return;
div.textContent = total(type) ? rn(+el.dataset[type] / total(type) * 100) + "%" : "0%"; div.textContent = total(type) ? rn((+el.dataset[type] / total(type)) * 100) + "%" : "0%";
}); });
}); });
} else { } else {
@ -145,15 +155,19 @@ function overviewRegiments(state) {
function addRegimentOnClick() { function addRegimentOnClick() {
const state = +regimentsFilter.value; const state = +regimentsFilter.value;
if (state === -1) {tip("Please select state from the list", false, "error"); return;} if (state === -1) {
tip("Please select state from the list", false, "error");
return;
}
const point = d3.mouse(this); const point = d3.mouse(this);
const cell = findCell(point[0], point[1]); const cell = findCell(point[0], point[1]);
const x = pack.cells.p[cell][0], y = pack.cells.p[cell][1]; const x = pack.cells.p[cell][0],
y = pack.cells.p[cell][1];
const military = pack.states[state].military; const military = pack.states[state].military;
const i = military.length ? last(military).i + 1 : 0; const i = military.length ? last(military).i + 1 : 0;
const n = +(pack.cells.h[cell] < 20); // naval or land const n = +(pack.cells.h[cell] < 20); // naval or land
const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, state, icon:"🛡️"}; const reg = {a: 0, cell, i, n, u: {}, x, y, bx: x, by: y, state, icon: "🛡️"};
reg.name = Military.getName(reg, military); reg.name = Military.getName(reg, military);
military.push(reg); military.push(reg);
Military.generateNote(reg, pack.states[state]); // add legend Military.generateNote(reg, pack.states[state]); // add legend
@ -163,9 +177,9 @@ function overviewRegiments(state) {
function downloadRegimentsData() { function downloadRegimentsData() {
const units = options.military.map(u => u.name); const units = options.military.map(u => u.name);
let data = "State,Id,Name,"+units.map(u => capitalize(u)).join(",")+",Total\n"; // headers let data = "State,Id,Name," + units.map(u => capitalize(u)).join(",") + ",Total\n"; // headers
body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function(el) { body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function (el) {
data += el.dataset.state + ","; data += el.dataset.state + ",";
data += el.dataset.id + ","; data += el.dataset.id + ",";
data += el.dataset.name + ","; data += el.dataset.name + ",";
@ -176,5 +190,4 @@ function overviewRegiments(state) {
const name = getFileName("Regiments") + ".csv"; const name = getFileName("Regiments") + ".csv";
downloadFile(data, name); downloadFile(data, name);
} }
} }

View file

@ -79,7 +79,7 @@ function editReligions() {
if (r.i) { if (r.i) {
lines += `<div class="states religions" data-id=${r.i} data-name="${r.name}" data-color="${r.color}" data-area=${area} lines += `<div class="states religions" data-id=${r.i} data-name="${r.name}" data-color="${r.color}" data-area=${area}
data-population=${population} data-type=${r.type} data-form=${r.form} data-deity="${r.deity ? r.deity : ""}" data-expansionism=${r.expansionism}> data-population=${population} data-type=${r.type} data-form=${r.form} data-deity="${r.deity ? r.deity : ""}" data-expansionism=${r.expansionism}>
<svg data-tip="Religion fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${r.color}" class="fillRect pointer"></svg> <fill-box fill="${r.color}"></fill-box>
<input data-tip="Religion name. Click and type to change" class="religionName" value="${r.name}" autocorrect="off" spellcheck="false"> <input data-tip="Religion name. Click and type to change" class="religionName" value="${r.name}" autocorrect="off" spellcheck="false">
<select data-tip="Religion type" class="religionType">${getTypeOptions(r.type)}</select> <select data-tip="Religion type" class="religionType">${getTypeOptions(r.type)}</select>
<input data-tip="Religion form" class="religionForm hide" value="${r.form}" autocorrect="off" spellcheck="false"> <input data-tip="Religion form" class="religionForm hide" value="${r.form}" autocorrect="off" spellcheck="false">
@ -93,7 +93,9 @@ function editReligions() {
</div>`; </div>`;
} else { } else {
// No religion (neutral) line // No religion (neutral) line
lines += `<div class="states" data-id=${r.i} data-name="${r.name}" data-color="" data-area=${area} data-population=${population} data-type="" data-form="" data-deity="" data-expansionism=""> lines += `<div class="states" data-id=${r.i} data-name="${
r.name
}" data-color="" data-area=${area} data-population=${population} data-type="" data-form="" data-deity="" data-expansionism="">
<svg width="9" height="9" class="placeholder"></svg> <svg width="9" height="9" class="placeholder"></svg>
<input data-tip="Religion name. Click and type to change" class="religionName italic" value="${r.name}" autocorrect="off" spellcheck="false"> <input data-tip="Religion name. Click and type to change" class="religionName italic" value="${r.name}" autocorrect="off" spellcheck="false">
<select data-tip="Religion type" class="religionType placeholder">${getTypeOptions(r.type)}</select> <select data-tip="Religion type" class="religionType placeholder">${getTypeOptions(r.type)}</select>
@ -124,7 +126,7 @@ function editReligions() {
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseenter", ev => religionHighlightOn(ev))); body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseenter", ev => religionHighlightOn(ev)));
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseleave", ev => religionHighlightOff(ev))); body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseleave", ev => religionHighlightOff(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectReligionOnLineClick)); body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectReligionOnLineClick));
body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", religionChangeColor)); body.querySelectorAll("fill-box").forEach(el => el.addEventListener("click", religionChangeColor));
body.querySelectorAll("div > input.religionName").forEach(el => el.addEventListener("input", religionChangeName)); body.querySelectorAll("div > input.religionName").forEach(el => el.addEventListener("input", religionChangeName));
body.querySelectorAll("div > select.religionType").forEach(el => el.addEventListener("change", religionChangeType)); body.querySelectorAll("div > select.religionType").forEach(el => el.addEventListener("change", religionChangeType));
body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm)); body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm));
@ -215,13 +217,13 @@ function editReligions() {
function religionChangeColor() { function religionChangeColor() {
const el = this; const el = this;
const currentFill = el.getAttribute("fill"); const currentFill = el.getAttribute("fill");
const religion = +el.parentNode.parentNode.dataset.id; const religion = +el.parentNode.dataset.id;
const callback = function (fill) { const callback = newFill => {
el.setAttribute("fill", fill); el.fill = newFill;
pack.religions[religion].color = fill; pack.religions[religion].color = newFill;
relig.select("#religion" + religion).attr("fill", fill); relig.select("#religion" + religion).attr("fill", newFill);
debug.select("#religionsCenter" + religion).attr("fill", fill); debug.select("#religionsCenter" + religion).attr("fill", newFill);
}; };
openPicker(currentFill, callback); openPicker(currentFill, callback);
@ -459,7 +461,13 @@ function editReligions() {
// prepare svg // prepare svg
alertMessage.innerHTML = "<div id='religionInfo' class='chartInfo'>&#8205;</div>"; alertMessage.innerHTML = "<div id='religionInfo' class='chartInfo'>&#8205;</div>";
const svg = d3.select("#alertMessage").insert("svg", "#religionInfo").attr("id", "hierarchy").attr("width", width).attr("height", height).style("text-anchor", "middle"); const svg = d3
.select("#alertMessage")
.insert("svg", "#religionInfo")
.attr("id", "hierarchy")
.attr("width", width)
.attr("height", height)
.style("text-anchor", "middle");
const graph = svg.append("g").attr("transform", `translate(10, -45)`); const graph = svg.append("g").attr("transform", `translate(10, -45)`);
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa"); const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa");
const nodes = graph.append("g"); const nodes = graph.append("g");
@ -473,7 +481,24 @@ function editReligions() {
.enter() .enter()
.append("path") .append("path")
.attr("d", d => { .attr("d", d => {
return "M" + d.source.x + "," + d.source.y + "C" + d.source.x + "," + (d.source.y * 3 + d.target.y) / 4 + " " + d.target.x + "," + (d.source.y * 2 + d.target.y) / 3 + " " + d.target.x + "," + d.target.y; return (
"M" +
d.source.x +
"," +
d.source.y +
"C" +
d.source.x +
"," +
(d.source.y * 3 + d.target.y) / 4 +
" " +
d.target.x +
"," +
(d.source.y * 2 + d.target.y) / 3 +
" " +
d.target.x +
"," +
d.target.y
);
}); });
const node = nodes const node = nodes
@ -578,7 +603,11 @@ function editReligions() {
$("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); $("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
tip("Click on religion to select, drag the circle to change religion", true); tip("Click on religion to select, drag the circle to change religion", true);
viewbox.style("cursor", "crosshair").on("click", selectReligionOnMapClick).call(d3.drag().on("start", dragReligionBrush)).on("touchmove mousemove", moveReligionBrush); viewbox
.style("cursor", "crosshair")
.on("click", selectReligionOnMapClick)
.call(d3.drag().on("start", dragReligionBrush))
.on("touchmove mousemove", moveReligionBrush);
body.querySelector("div").classList.add("selected"); body.querySelector("div").classList.add("selected");
} }

View file

@ -89,7 +89,8 @@ function createRiver() {
const source = riverCells[0]; const source = riverCells[0];
const mouth = parent === riverId ? last(riverCells) : riverCells[riverCells.length - 2]; const mouth = parent === riverId ? last(riverCells) : riverCells[riverCells.length - 2];
const sourceWidth = 0.05; const sourceWidth = 0.05;
const widthFactor = 1.2; const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
const widthFactor = 1.2 * defaultWidthFactor;
const meanderedPoints = addMeandering(riverCells); const meanderedPoints = addMeandering(riverCells);

View file

@ -45,7 +45,7 @@ function editStates() {
cl = el.classList, cl = el.classList,
line = el.parentNode, line = el.parentNode,
state = +line.dataset.id; state = +line.dataset.id;
if (cl.contains("fillRect")) stateChangeFill(el); if (el.tagName === "FILL-BOX") stateChangeFill(el);
else if (cl.contains("name")) editStateName(state); else if (cl.contains("name")) editStateName(state);
else if (cl.contains("coaIcon")) editEmblem("state", "stateCOA" + state, pack.states[state]); else if (cl.contains("coaIcon")) editEmblem("state", "stateCOA" + state, pack.states[state]);
else if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state); else if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state);
@ -102,7 +102,7 @@ function editStates() {
// Neutral line // Neutral line
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-cells=${s.cells} data-area=${area} lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-cells=${s.cells} data-area=${area}
data-population=${population} data-burgs=${s.burgs} data-color="" data-form="" data-capital="" data-culture="" data-type="" data-expansionism=""> data-population=${population} data-burgs=${s.burgs} data-color="" data-form="" data-capital="" data-culture="" data-type="" data-expansionism="">
<svg width="9" height="9" class="placeholder"></svg> <svg width="1em" height="1em" class="placeholder"></svg>
<input data-tip="Neutral lands name. Click to change" class="stateName name pointer italic" value="${s.name}" readonly> <input data-tip="Neutral lands name. Click to change" class="stateName name pointer italic" value="${s.name}" readonly>
<svg class="coaIcon placeholder hide"></svg> <svg class="coaIcon placeholder hide"></svg>
<input class="stateForm placeholder" value="none"> <input class="stateForm placeholder" value="none">
@ -126,17 +126,12 @@ function editStates() {
const capital = pack.burgs[s.capital].name; const capital = pack.burgs[s.capital].name;
COArenderer.trigger("stateCOA" + s.i, s.coa); COArenderer.trigger("stateCOA" + s.i, s.coa);
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-form="${s.formName}" data-capital="${capital}" data-color="${ lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-form="${s.formName}" data-capital="${capital}"
s.color data-color="${s.color}" data-cells=${s.cells} data-area=${area} data-population=${population} data-burgs=${s.burgs}
}" data-cells=${s.cells} data-culture=${pack.cultures[s.culture].name} data-type=${s.type} data-expansionism=${s.expansionism}>
data-area=${area} data-population=${population} data-burgs=${s.burgs} data-culture=${pack.cultures[s.culture].name} data-type=${ <fill-box fill="${s.color}"></fill-box>
s.type
} data-expansionism=${s.expansionism}>
<svg data-tip="State fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
s.color
}" class="fillRect pointer"></svg>
<input data-tip="State name. Click to change" class="stateName name pointer" value="${s.name}" readonly> <input data-tip="State name. Click to change" class="stateName name pointer" value="${s.name}" readonly>
<svg data-tip="Click to show and edit state emblem" class="coaIcon hide" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg> <svg data-tip="Click to show and edit state emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg>
<input data-tip="State form name. Click to change" class="stateForm name pointer" value="${s.formName}" readonly> <input data-tip="State form name. Click to change" class="stateForm name pointer" value="${s.formName}" readonly>
<span data-tip="State capital. Click to zoom into view" class="icon-star-empty pointer hide"></span> <span data-tip="State capital. Click to zoom into view" class="icon-star-empty pointer hide"></span>
<input data-tip="Capital name. Click and type to rename" class="stateCapital hide" value="${capital}" autocorrect="off" spellcheck="false"/> <input data-tip="Capital name. Click and type to rename" class="stateCapital hide" value="${capital}" autocorrect="off" spellcheck="false"/>
@ -149,9 +144,8 @@ function editStates() {
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div> <div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
<select data-tip="State type. Defines growth model. Click to change" class="cultureType ${hidden} show hide">${getTypeOptions(s.type)}</select> <select data-tip="State type. Defines growth model. Click to change" class="cultureType ${hidden} show hide">${getTypeOptions(s.type)}</select>
<span data-tip="State expansionism" class="icon-resize-full ${hidden} show hide"></span> <span data-tip="State expansionism" class="icon-resize-full ${hidden} show hide"></span>
<input data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value" class="statePower ${hidden} show hide" type="number" min=0 max=99 step=.1 value=${ <input data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value"
s.expansionism class="statePower ${hidden} show hide" type="number" min=0 max=99 step=.1 value=${s.expansionism}>
}>
<span data-tip="Cells count" class="icon-check-empty ${hidden} show hide"></span> <span data-tip="Cells count" class="icon-check-empty ${hidden} show hide"></span>
<div data-tip="Cells count" class="stateCells ${hidden} show hide">${s.cells}</div> <div data-tip="Cells count" class="stateCells ${hidden} show hide">${s.cells}</div>
<span data-tip="Toggle state focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span> <span data-tip="Toggle state focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span>
@ -237,18 +231,18 @@ function editStates() {
function stateChangeFill(el) { function stateChangeFill(el) {
const currentFill = el.getAttribute("fill"); const currentFill = el.getAttribute("fill");
const state = +el.parentNode.parentNode.dataset.id; const state = +el.parentNode.dataset.id;
const callback = function (fill) { const callback = function (newFill) {
el.setAttribute("fill", fill); el.fill = newFill;
pack.states[state].color = fill; pack.states[state].color = newFill;
statesBody.select("#state" + state).attr("fill", fill); statesBody.select("#state" + state).attr("fill", newFill);
statesBody.select("#state-gap" + state).attr("stroke", fill); statesBody.select("#state-gap" + state).attr("stroke", newFill);
const halo = d3.color(fill) ? d3.color(fill).darker().hex() : "#666666"; const halo = d3.color(newFill) ? d3.color(newFill).darker().hex() : "#666666";
statesHalo.select("#state-border" + state).attr("stroke", halo); statesHalo.select("#state-border" + state).attr("stroke", halo);
// recolor regiments // recolor regiments
const solidColor = fill[0] === "#" ? fill : "#999"; const solidColor = newFill[0] === "#" ? newFill : "#999";
const darkerColor = d3.color(solidColor).darker().hex(); const darkerColor = d3.color(solidColor).darker().hex();
armies.select("#army" + state).attr("fill", solidColor); armies.select("#army" + state).attr("fill", solidColor);
armies armies

File diff suppressed because one or more lines are too long

311
modules/ui/stylePresets.js Normal file
View file

@ -0,0 +1,311 @@
// UI module to control the style presets
"use strict";
const systemPresets = ["default", "ancient", "gloom", "light", "watercolor", "clean", "atlas", "cyberpunk", "monochrome"];
const customPresetPrefix = "fmgStyle_";
// add style presets to list
{
const systemOptions = systemPresets.map(styleName => `<option value="${styleName}">${styleName}</option>`);
const storedStyles = Object.keys(localStorage).filter(key => key.startsWith(customPresetPrefix));
const customOptions = storedStyles.map(styleName => `<option value="${styleName}">${styleName.replace(customPresetPrefix, "")} [custom]</option>`);
const options = systemOptions.join("") + customOptions.join("");
document.getElementById("stylePreset").innerHTML = options;
}
async function applyStyleOnLoad() {
const desiredPreset = localStorage.getItem("presetStyle") || "default";
const styleData = await getStylePreset(desiredPreset);
const [appliedPreset, style] = styleData;
applyStyle(style);
updateMapFilter();
stylePreset.value = stylePreset.dataset.old = appliedPreset;
setPresetRemoveButtonVisibiliy();
}
async function getStylePreset(desiredPreset) {
let presetToLoad = desiredPreset;
const isCustom = !systemPresets.includes(desiredPreset);
if (isCustom) {
const storedStyleJSON = localStorage.getItem(desiredPreset);
if (!storedStyleJSON) {
ERROR && console.error(`Custom style ${desiredPreset} in not found in localStorage. Applying default style`);
presetToLoad = "default";
} else {
const isValid = JSON.isValid(storedStyleJSON);
if (isValid) return [desiredPreset, JSON.parse(storedStyleJSON)];
ERROR && console.error(`Custom style ${desiredPreset} stored in localStorage is not valid. Applying default style`);
presetToLoad = "default";
}
}
const style = await fetchSystemPreset(presetToLoad);
return [presetToLoad, style];
}
async function fetchSystemPreset(preset) {
const style = await fetch(`./styles/${preset}.json`)
.then(res => res.json())
.catch(err => {
ERROR && console.error("Error on loading style preset", preset, err);
return null;
});
if (!style) throw new Error("Cannot fetch style preset", preset);
return style;
}
function applyStyle(style) {
for (const selector in style) {
const el = document.querySelector(selector);
if (!el) continue;
for (const attribute in style[selector]) {
const value = style[selector][attribute];
if (value === "null" || value === null) {
el.removeAttribute(attribute);
continue;
}
if (attribute === "text-shadow") {
el.style[attribute] = value;
} else {
el.setAttribute(attribute, value);
}
}
}
}
function requestStylePresetChange(preset) {
const isConfirmed = sessionStorage.getItem("styleChangeConfirmed");
if (isConfirmed) {
changeStyle(preset);
return;
}
confirmationDialog({
title: "Change style preset",
message: "Are you sure you want to change the style preset? All unsaved style changes will be lost",
confirm: "Change",
onConfirm: () => {
sessionStorage.setItem("styleChangeConfirmed", true);
changeStyle(preset);
},
onCancel: () => {
stylePreset.value = stylePreset.dataset.old;
}
});
}
async function changeStyle(desiredPreset) {
const styleData = await getStylePreset(desiredPreset);
const [appliedPreset, style] = styleData;
localStorage.setItem("presetStyle", appliedPreset);
applyStyleWithUiRefresh(style);
}
function applyStyleWithUiRefresh(style) {
applyStyle(style);
updateElements();
selectStyleElement(); // re-select element to trigger values update
updateMapFilter();
stylePreset.dataset.old = stylePreset.value;
invokeActiveZooming();
setPresetRemoveButtonVisibiliy();
}
function addStylePreset() {
$("#styleSaver").dialog({title: "Style Saver", width: "26em", position: {my: "center", at: "center", of: "svg"}});
const styleName = stylePreset.value.replace(customPresetPrefix, "");
document.getElementById("styleSaverName").value = styleName;
styleSaverJSON.value = JSON.stringify(collectStyleData(), null, 2);
checkName();
if (modules.saveStyle) return;
modules.saveStyle = true;
// add listeners
document.getElementById("styleSaverName").addEventListener("input", checkName);
document.getElementById("styleSaverSave").addEventListener("click", saveStyle);
document.getElementById("styleSaverDownload").addEventListener("click", styleDownload);
document.getElementById("styleSaverLoad").addEventListener("click", () => styleToLoad.click());
document.getElementById("styleToLoad").addEventListener("change", loadStyleFile);
function collectStyleData() {
const style = {};
const attributes = {
"#map": ["background-color", "filter", "data-filter"],
"#armies": ["font-size", "box-size", "stroke", "stroke-width", "fill-opacity", "filter"],
"#biomes": ["opacity", "filter", "mask"],
"#stateBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#provinceBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#cells": ["opacity", "stroke", "stroke-width", "filter", "mask"],
"#gridOverlay": ["opacity", "scale", "dx", "dy", "type", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "transform", "filter", "mask"],
"#coordinates": ["opacity", "data-size", "font-size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#compass": ["opacity", "transform", "filter", "mask", "shape-rendering"],
"#rose": ["transform"],
"#relig": ["opacity", "stroke", "stroke-width", "filter"],
"#cults": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#landmass": ["opacity", "fill", "filter"],
"#markers": ["opacity", "rescale", "filter"],
"#prec": ["opacity", "stroke", "stroke-width", "fill", "filter"],
"#population": ["opacity", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#rural": ["stroke"],
"#urban": ["stroke"],
"#freshwater": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#salt": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#sinkhole": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#frozen": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#lava": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#dry": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#sea_island": ["opacity", "stroke", "stroke-width", "filter", "auto-filter"],
"#lake_island": ["opacity", "stroke", "stroke-width", "filter"],
"#terrain": ["opacity", "set", "size", "density", "filter", "mask"],
"#rivers": ["opacity", "filter", "fill"],
"#ruler": ["opacity", "filter"],
"#roads": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#trails": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#searoutes": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#statesBody": ["opacity", "filter"],
"#statesHalo": ["opacity", "data-width", "stroke-width", "filter"],
"#provs": ["opacity", "fill", "font-size", "font-family", "filter"],
"#temperature": ["opacity", "font-size", "fill", "fill-opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#ice": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#emblems": ["opacity", "stroke-width", "filter"],
"#texture": ["opacity", "filter", "mask"],
"#textureImage": ["x", "y"],
"#zones": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#oceanLayers": ["filter", "layers"],
"#oceanBase": ["fill"],
"#oceanicPattern": ["href", "opacity"],
"#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
"#legend": ["data-size", "font-size", "font-family", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "data-x", "data-y", "data-columns"],
"#legendBox": ["fill", "fill-opacity"],
"#burgLabels > #cities": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
"#burgIcons > #cities": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"],
"#anchors > #cities": ["opacity", "fill", "size", "stroke", "stroke-width"],
"#burgLabels > #towns": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
"#burgIcons > #towns": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"],
"#anchors > #towns": ["opacity", "fill", "size", "stroke", "stroke-width"],
"#labels > #states": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
"#labels > #addedLabels": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
"#fogging": ["opacity", "fill", "filter"]
};
for (const selector in attributes) {
const el = document.querySelector(selector);
if (!el) continue;
style[selector] = {};
for (const attr of attributes[selector]) {
let value = el.style[attr] || el.getAttribute(attr);
if (attr === "font-size" && el.hasAttribute("data-size")) value = el.getAttribute("data-size");
style[selector][attr] = parseValue(value);
}
}
function parseValue(value) {
if (value === "null" || value === null) return null;
if (value === "") return "";
if (!isNaN(+value)) return +value;
return value;
}
return style;
}
function checkName() {
const styleName = customPresetPrefix + styleSaverName.value;
const isSystem = systemPresets.includes(styleName) || systemPresets.includes(styleSaverName.value);
if (isSystem) return (styleSaverTip.innerHTML = "default");
const isExisting = Array.from(stylePreset.options).some(option => option.value == styleName);
if (isExisting) return (styleSaverTip.innerHTML = "existing");
styleSaverTip.innerHTML = "new";
}
function saveStyle() {
const styleJSON = styleSaverJSON.value;
const desiredName = styleSaverName.value;
if (!styleJSON) return tip("Please provide a style JSON", false, "error");
if (!JSON.isValid(styleJSON)) return tip("JSON string is not valid, please check the format", false, "error");
if (!desiredName) return tip("Please provide a preset name", false, "error");
if (styleSaverTip.innerHTML === "default") return tip("You cannot overwrite default preset, please change the name", false, "error");
const presetName = customPresetPrefix + desiredName;
applyOption(stylePreset, presetName, desiredName + " [custom]");
localStorage.setItem("presetStyle", presetName);
localStorage.setItem(presetName, styleJSON);
applyStyleWithUiRefresh(JSON.parse(styleJSON));
tip("Style preset is saved and applied", false, "success", 4000);
$("#styleSaver").dialog("close");
}
function styleDownload() {
const styleJSON = styleSaverJSON.value;
const styleName = styleSaverName.value;
if (!styleJSON) return tip("Please provide a style JSON", false, "error");
if (!JSON.isValid(styleJSON)) return tip("JSON string is not valid, please check the format", false, "error");
if (!styleName) return tip("Please provide a preset name", false, "error");
downloadFile(styleJSON, styleName + ".json", "application/json");
}
function loadStyleFile() {
const fileName = this.files[0]?.name.replace(/\.[^.]*$/, "");
uploadFile(this, styleUpload);
function styleUpload(dataLoaded) {
if (!dataLoaded) return tip("Cannot load the file. Please check the data format", false, "error");
const isValid = JSON.isValid(dataLoaded);
if (!isValid) return tip("Loaded data is not a valid JSON, please check the format", false, "error");
styleSaverJSON.value = JSON.stringify(JSON.parse(dataLoaded), null, 2);
styleSaverName.value = fileName;
checkName();
tip("Style preset is uploaded", false, "success", 4000);
}
}
}
function requestRemoveStylePreset() {
const isDefault = systemPresets.includes(stylePreset.value);
if (isDefault) return tip("Cannot remove system preset", false, "error");
confirmationDialog({
title: "Remove style preset",
message: "Are you sure you want to remove the style preset? This action cannot be undone.",
confirm: "Remove",
onConfirm: removeStylePreset
});
}
function removeStylePreset() {
localStorage.removeItem("presetStyle");
localStorage.removeItem(stylePreset.value);
stylePreset.selectedOptions[0].remove();
changeStyle("default");
}
function updateMapFilter() {
const filter = svg.attr("data-filter");
mapFilters.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed"));
if (!filter) return;
mapFilters.querySelector("#" + filter).classList.add("pressed");
}
function setPresetRemoveButtonVisibiliy() {
const isDefault = systemPresets.includes(stylePreset.value);
removeStyleButton.style.display = isDefault ? "none" : "inline-block";
}

View file

@ -0,0 +1,161 @@
"use strict";
function showBurgTemperatureGraph(id) {
const b = pack.burgs[id];
const lat = mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT;
const burgTemp = grid.cells.temp[pack.cells.g[b.cell]];
const prec = grid.cells.prec[pack.cells.g[b.cell]];
// prettier-ignore
const weights = [
[
[10.782752257744338, 2.7100404240962126], [-2.8226802110591462, 51.62920138583541], [-6.6250956268643835, 4.427939197315455], [-59.64690518541339, 41.89084162654791], [-1.3302059550553835, -3.6964487738450913],
[-2.5844898544535497, 0.09879268612455298], [-5.58528252533573, -0.23426224364501905], [26.94531337690372, 20.898158905988907], [3.816397481634785, -0.19045424064580757], [-4.835697931609101, -10.748232783636434]
],
[
[-2.478952081870123, 0.6405800134306895, -7.136785640930911, -0.2186529024764509, 3.6568435212735424, 31.446026153530838, -19.91005187482281, 0.2543395274783306, -7.036924569659988, -0.7721371621651565],
[-197.10583739743538, 6.889921141533474, 0.5058941504631129, 7.7667203434606416, -53.74180550086929, -15.717331715167001, -61.32068414155791, -2.259728220978728, 35.84049189540032, 94.6157364730977],
[-5.312011591880851, -0.09923148954215096, -1.7132477487917586, -22.55559652066422, 0.4806107280554336, -26.5583974109492, 2.0558257347014863, 25.815645234787432, -18.569029876991156, -2.6792003366730035],
[20.706518520569514, 18.344297403881875, 99.52244671131733, -58.53124969563653, -60.74384321042212, -80.57540534651835, 7.884792406540866, -144.33871131678563, 80.134199744324, 20.50745285622448],
[-52.88299538575159, -15.782505343805528, 16.63316001054924, 88.09475330556671, -17.619552086641818, -19.943999528182427, -120.46286026828177, 19.354752020806302, 43.49422099308949, 28.733924806541363],
[-2.4621368711159897, -1.2074759925679757, -1.5133898639835084, 2.173715352424188, -5.988707597991683, 3.0234147182203843, 3.3284199340000797, -1.8805161326360575, 5.151910934121654, -1.2540553911612116]
],
[
[-0.3357437479474717, 0.01430651794222215, -0.7927524256670906, 0.2121636229648523, 1.0587803023358318, -3.759288325505095],
[-1.1988028704442968, 1.3768997508052783, -3.8480086358278816, 0.5289387340947143, 0.5769459339961177, -1.2528318145750772],
[1.0074966649240946, 1.155301164699459, -2.974254371052421, 0.47408176553219467, 0.5939042688615264, -0.7631976947131744]
]
];
// From (-∞, ∞) to ~[-1, 1]
const In1 = [(Math.abs(lat) - 26.950680212887473) / 48.378128506956, (prec - 12.229929140832644) / 29.94402033696607];
let lastIn = In1;
let lstOut = [];
for (let levelN = 0; levelN < weights.length; levelN++) {
const layerN = weights[levelN];
for (let i = 0; i < layerN.length; i++) {
lstOut[i] = 0;
for (let j = 0; j < layerN[i].length; j++) {
lstOut[i] = lstOut[i] + lastIn[j] * layerN[i][j];
}
// sigmoid
lstOut[i] = 1 / (1 + Math.exp(-lstOut[i]));
}
lastIn = lstOut.slice(0);
}
// Standard deviation for average temperature for the year from [0, 1] to [min, max]
const yearSig = lstOut[0] * 62.9466411977018 + 0.28613807855649165;
// Standard deviation for the difference between the minimum and maximum temperatures for the year
const yearDelTmpSig = lstOut[1] * 13.541688670361175 + 0.1414213562373084 > yearSig ? yearSig : lstOut[1] * 13.541688670361175 + 0.1414213562373084;
// Expected value for the difference between the minimum and maximum temperatures for the year
const yearDelTmpMu = lstOut[2] * 15.266666666666667 + 0.6416666666666663;
// Temperature change shape
const delT = yearDelTmpMu / 2 + (0.5 * yearDelTmpSig) / 2;
const minT = burgTemp - Math.max(yearSig + delT, 15);
const maxT = burgTemp + (burgTemp - minT);
const chartWidth = Math.max(window.innerWidth / 2, 580);
const chartHeight = 300;
// drawing starting point from top-left (y = 0) of SVG
const xOffset = 60;
const yOffset = 10;
const year = new Date().getFullYear(); // use current year
const startDate = new Date(year, 0, 1);
const endDate = new Date(year, 11, 31);
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const xscale = d3.scaleTime().domain([startDate, endDate]).range([0, chartWidth]);
const yscale = d3.scaleLinear().domain([minT, maxT]).range([chartHeight, 0]);
const tempMean = [];
const tempMin = [];
const tempMax = [];
months.forEach((month, index) => {
const rate = index / 11;
let formTmp = Math.cos(rate * 2 * Math.PI) / 2;
if (lat > 0) formTmp = -formTmp;
const x = rate * chartWidth + xOffset;
const tempAverage = formTmp * yearSig + burgTemp;
const tempDelta = yearDelTmpMu / 2 + (formTmp * yearDelTmpSig) / 2;
tempMean.push([x, yscale(tempAverage) + yOffset]);
tempMin.push([x, yscale(tempAverage - tempDelta) + yOffset]);
tempMax.push([x, yscale(tempAverage + tempDelta) + yOffset]);
});
drawGraph();
$("#alert").dialog({title: "Annual temperature in " + b.name, width: "auto", position: {my: "center", at: "center", of: "svg"}});
function drawGraph() {
alertMessage.innerHTML = "";
const getCurve = data => round(d3.line().curve(d3.curveBasis)(data), 2);
const legendSize = 60;
const chart = d3
.select("#alertMessage")
.append("svg")
.attr("width", chartWidth + 120)
.attr("height", chartHeight + yOffset + legendSize);
const legend = chart.append("g");
const legendY = chartHeight + yOffset + legendSize * 0.8;
const legendX = n => (chartWidth * n) / 4;
const legendTextX = n => legendX(n) + 10;
legend.append("circle").attr("cx", legendX(1)).attr("cy", legendY).attr("r", 4).style("fill", "red");
legend.append("text").attr("x", legendTextX(1)).attr("y", legendY).attr("alignment-baseline", "central").text("Day temperature");
legend.append("circle").attr("cx", legendX(2)).attr("cy", legendY).attr("r", 4).style("fill", "orange");
legend.append("text").attr("x", legendTextX(2)).attr("y", legendY).attr("alignment-baseline", "central").text("Mean temperature");
legend.append("circle").attr("cx", legendX(3)).attr("cy", legendY).attr("r", 4).style("fill", "blue");
legend.append("text").attr("x", legendTextX(3)).attr("y", legendY).attr("alignment-baseline", "central").text("Night temperature");
const xGrid = d3.axisBottom(xscale).ticks().tickSize(-chartHeight);
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth);
const grid = chart.append("g").attr("class", "epgrid").attr("stroke-dasharray", "4 1");
grid.append("g").attr("transform", `translate(${xOffset}, ${chartHeight + yOffset})`).call(xGrid); // prettier-ignore
grid.append("g").attr("transform", `translate(${xOffset}, ${yOffset})`).call(yGrid);
grid.selectAll("text").remove();
// add zero degree line
if (minT < 0 && maxT > 0) {
grid
.append("line")
.attr("x1", xOffset)
.attr("y1", yscale(0) + yOffset)
.attr("x2", chartWidth + xOffset)
.attr("y2", yscale(0) + yOffset)
.attr("stroke", "gray");
}
const xAxis = d3.axisBottom(xscale).ticks().tickFormat(d3.timeFormat("%B"));
const yAxis = d3.axisLeft(yscale).ticks(5).tickFormat(convertTemperature);
const axis = chart.append("g");
axis
.append("g")
.attr("transform", `translate(${xOffset}, ${chartHeight + yOffset})`)
.call(xAxis);
axis.append("g").attr("transform", `translate(${xOffset}, ${yOffset})`).call(yAxis);
axis.select("path.domain").attr("d", `M0.5,0.5 H${chartWidth + 0.5}`);
const curves = chart.append("g").attr("fill", "none").style("stroke-width", 2.5);
curves.append("path").attr("d", getCurve(tempMean)).attr("data-type", "daily").attr("stroke", "orange").on("mousemove", printVal);
curves.append("path").attr("d", getCurve(tempMin)).attr("data-type", "night").attr("stroke", "blue").on("mousemove", printVal);
curves.append("path").attr("d", getCurve(tempMax)).attr("data-type", "day").attr("stroke", "red").on("mousemove", printVal);
function printVal() {
const [x, y] = d3.mouse(this);
const type = this.getAttribute("data-type");
const temp = convertTemperature(yscale.invert(y - yOffset));
const month = months[rn(((x - xOffset) / chartWidth) * 12)] || months[0];
tip(`Average ${type} temperature in ${month}: ${temp}`);
}
}
}

View file

@ -143,7 +143,7 @@ function regenerateStates() {
const localSeed = Math.floor(Math.random() * 1e9); // new random seed const localSeed = Math.floor(Math.random() * 1e9); // new random seed
Math.random = aleaPRNG(localSeed); Math.random = aleaPRNG(localSeed);
const statesCount = +regionsInput.value; const statesCount = +regionsOutput.value;
const burgs = pack.burgs.filter(b => b.i && !b.removed); const burgs = pack.burgs.filter(b => b.i && !b.removed);
if (!burgs.length) return tip("There are no any burgs to generate states. Please create burgs first", false, "error"); if (!burgs.length) return tip("There are no any burgs to generate states. Please create burgs first", false, "error");
if (burgs.length < statesCount) tip(`Not enough burgs to generate ${statesCount} states. Will generate only ${burgs.length} states`, false, "warn"); if (burgs.length < statesCount) tip(`Not enough burgs to generate ${statesCount} states. Will generate only ${burgs.length} states`, false, "warn");
@ -624,7 +624,9 @@ function addRiverOnClick() {
const source = riverCells[0]; const source = riverCells[0];
const mouth = riverCells[riverCells.length - 2]; const mouth = riverCells[riverCells.length - 2];
const widthFactor = river?.widthFactor || (!parent || parent === riverId ? 1.2 : 1);
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
const widthFactor = river?.widthFactor || (!parent || parent === riverId ? defaultWidthFactor * 1.2 : defaultWidthFactor);
const meanderedPoints = addMeandering(riverCells); const meanderedPoints = addMeandering(riverCells);
const discharge = cells.fl[mouth]; // m3 in second const discharge = cells.fl[mouth]; // m3 in second

View file

@ -37,26 +37,12 @@ function editZones() {
const el = ev.target, const el = ev.target,
cl = el.classList, cl = el.classList,
zone = el.parentNode.dataset.id; zone = el.parentNode.dataset.id;
if (cl.contains("culturePopulation")) { if (el.tagName === "FILL-BOX") changeFill(el);
changePopulation(zone); else if (cl.contains("culturePopulation")) changePopulation(zone);
return; else if (cl.contains("icon-trash-empty")) zoneRemove(zone);
} else if (cl.contains("icon-eye")) toggleVisibility(el);
if (cl.contains("icon-trash-empty")) { else if (cl.contains("icon-pin")) toggleFog(zone, cl);
zoneRemove(zone);
return;
}
if (cl.contains("icon-eye")) {
toggleVisibility(el);
return;
}
if (cl.contains("icon-pin")) {
toggleFog(zone, cl);
return;
}
if (cl.contains("fillRect")) {
changeFill(el);
return;
}
if (customization) selectZone(el); if (customization) selectZone(el);
}); });
@ -131,8 +117,9 @@ function editZones() {
const inactive = this.style.display === "none"; const inactive = this.style.display === "none";
const focused = defs.select("#fog #focus" + this.id).size(); const focused = defs.select("#fog #focus" + this.id).size();
lines += `<div class="states" data-id="${this.id}" data-fill="${fill}" data-description="${description}" data-cells=${c.length} data-area=${area} data-population=${population}> lines += `<div class="states" data-id="${this.id}" data-fill="${fill}" data-description="${description}"
<svg data-tip="Zone fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${fill}" class="fillRect pointer"></svg> data-cells=${c.length} data-area=${area} data-population=${population}>
<fill-box fill="${fill}"></fill-box>
<input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false"> <input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false">
<span data-tip="Cells count" class="icon-check-empty hide"></span> <span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="stateCells hide">${c.length}</div> <div data-tip="Cells count" class="stateCells hide">${c.length}</div>
@ -337,9 +324,9 @@ function editZones() {
function changeFill(el) { function changeFill(el) {
const fill = el.getAttribute("fill"); const fill = el.getAttribute("fill");
const callback = function (fill) { const callback = newFill => {
el.setAttribute("fill", fill); el.fill = newFill;
document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute("fill", fill); document.getElementById(el.parentNode.dataset.id).setAttribute("fill", newFill);
}; };
openPicker(fill, callback); openPicker(fill, callback);
@ -411,12 +398,12 @@ function editZones() {
function addZonesLayer() { function addZonesLayer() {
const id = getNextId("zone"); const id = getNextId("zone");
const description = "Unknown zone"; const description = "Unknown zone";
const fill = "url(#hatch" + (id.slice(4) % 14) + ")"; const fill = "url(#hatch" + (id.slice(4) % 42) + ")";
zones.append("g").attr("id", id).attr("data-description", description).attr("data-cells", "").attr("fill", fill).attr("data-type", zoneTypes[0]); zones.append("g").attr("id", id).attr("data-description", description).attr("data-cells", "").attr("fill", fill);
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
const line = `<div class="states" data-id="${id}" data-fill="${fill}" data-description="${description}" data-cells=0 data-area=0 data-population=0> const line = `<div class="states" data-id="${id}" data-fill="${fill}" data-description="${description}" data-cells=0 data-area=0 data-population=0>
<svg data-tip="Zone fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${fill}" class="fillRect pointer"></svg> <fill-box fill="${fill}"></fill-box>
<input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false"> <input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false">
<span data-tip="Cells count" class="icon-check-empty hide"></span> <span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="stateCells hide">0</div> <div data-tip="Cells count" class="stateCells hide">0</div>

View file

@ -1,3 +0,0 @@
start chrome.exe http://localhost:8000/
@echo off
php -S localhost:8000

389
styles/ancient.json Normal file
View file

@ -0,0 +1,389 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 8,
"box-size": 4,
"stroke": "#000",
"stroke-width": 0.2,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": null,
"filter": null,
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 0.8,
"stroke": "#56566d",
"stroke-width": 1,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 0.8,
"stroke": "#56566d",
"stroke-width": 0.2,
"stroke-dasharray": 1,
"stroke-linecap": "butt",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": 0,
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#d4d4d4",
"stroke-width": 1,
"stroke-dasharray": 5,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.5,
"transform": null,
"filter": "url(#filter-sepia)",
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": "translate(80 80) scale(.25)"
},
"#relig": {
"opacity": 0.7,
"stroke": "#404040",
"stroke-width": 0.7,
"filter": null
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#e3dfce",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": ""
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 0.6,
"fill": "#c8d6e0",
"stroke": "#968d6e",
"stroke-width": 0.7,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#339482",
"stroke": "#836a34",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#c3d6df",
"stroke": "#b29062",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#a04e18",
"stroke": "#835520",
"stroke-width": 2,
"filter": "url(#paper)"
},
"#dry": {
"opacity": 0.7,
"fill": "#c6b795",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.5,
"stroke": "#1f3846",
"stroke-width": 0.7,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": 1,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": "",
"fill": "#a69b7d"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.7,
"stroke": "#8d502a",
"stroke-width": 1,
"stroke-dasharray": 3,
"stroke-linecap": "inherit",
"filter": "",
"mask": null
},
"#trails": {
"opacity": 0.7,
"stroke": "#924217",
"stroke-width": 0.5,
"stroke-dasharray": "1 2",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#b16925",
"stroke-width": 0.8,
"stroke-dasharray": "1 2",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.2,
"filter": "url(#filter-sepia)"
},
"#statesHalo": {
"opacity": 0.4,
"data-width": 10,
"stroke-width": 10,
"filter": "blur(6px)"
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"font-size": 10,
"font-family": "Georgia",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.35,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 3,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.8,
"stroke-width": 0.8,
"filter": "url(#dropShadow05)"
},
"#texture": {
"opacity": 0.6,
"filter": "",
"mask": ""
},
"#textureImage": {
"x": 0,
"y": 0
},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": "",
"layers": "-6,-4,-2"
},
"#oceanBase": {
"fill": "#c99f64"
},
"#oceanicPattern": {
"href": "./images/kiwiroo.png",
"opacity": 0.4
},
"#terrs": {
"opacity": null,
"scheme": "bright",
"terracing": 0,
"skip": 2,
"relax": 1,
"curve": 0,
"filter": "url(#blur3)",
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 12,
"font-size": 12,
"font-family": "Great Vibes"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#fdfab9",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#6f4e1f",
"stroke-width": 0.3,
"stroke-dasharray": ".3 .4",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 5,
"font-size": 5,
"font-family": "Great Vibes"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#fef4d8",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#72472c",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 22,
"font-size": 22,
"font-family": "Great Vibes",
"filter": "url(#filter-sepia)"
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Times New Roman",
"filter": "url(#filter-sepia)"
},
"#fogging": {
"opacity": 0.98,
"fill": "#30426f",
"filter": null
}
}

385
styles/atlas.json Normal file
View file

@ -0,0 +1,385 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 6,
"box-size": 3,
"stroke": "#000",
"stroke-width": 0.3,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": null,
"filter": null,
"mask": "u rl(#land)"
},
"#stateBorders": {
"opacity": 1,
"stroke": "#000000",
"stroke-width": 1.01,
"stroke-dasharray": 0,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 0.8,
"stroke": "#000000",
"stroke-width": 0.69,
"stroke-dasharray": 0,
"stroke-linecap": "round",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 1,
"scale": 7.99,
"dx": -2,
"dy": 3,
"type": "square",
"stroke": "#000000",
"stroke-width": 0.05,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#d4d4d4",
"stroke-width": 1,
"stroke-dasharray": 5,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.8,
"transform": null,
"filter": null,
"mask": "u rl(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": "translate(80 80) scale(.25)"
},
"#relig": {
"opacity": 0.7,
"stroke": "#777777",
"stroke-width": 0,
"filter": null
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#eef6fb",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": "u rl(#dropShadow01)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 1,
"fill": "#cae3f7",
"stroke": "#0089ca",
"stroke-width": 1.01,
"filter": null
},
"#salt": {
"opacity": 1,
"fill": "#cae3f7",
"stroke": "#0089ca",
"stroke-width": 1.01,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#cae3f7",
"stroke": "#0089ca",
"stroke-width": 1.01,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "u rl(#crumpled)"
},
"#dry": {
"opacity": 1,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 1,
"stroke": "#028ac9",
"stroke-width": 1.01,
"filter": "",
"auto-filter": 0
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": null,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#0089ca"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 1,
"stroke": "#ff2c2c",
"stroke-width": 1.05,
"stroke-dasharray": 0,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 1,
"stroke": "#9f5122",
"stroke-width": 0.43,
"stroke-dasharray": 0,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 1,
"stroke": "#0089ca",
"stroke-width": 0.45,
"stroke-dasharray": "1 2",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.49,
"filter": null
},
"#statesHalo": {
"opacity": 0.4,
"data-width": 10,
"stroke-width": 10,
"filter": "blur(5px)"
},
"#provs": {
"opacity": 1,
"fill": "#000000",
"font-size": 10,
"font-family": "Times New Roman",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "14px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": ""
},
"#ice": {
"opacity": 0.9,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1,
"filter": "u rl(#dropShadow05)"
},
"#emblems": {
"opacity": 0.9,
"stroke-width": 1,
"filter": null
},
"#texture": {
"opacity": null,
"filter": null,
"mask": "u rl(#land)"
},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": "",
"layers": "none"
},
"#oceanBase": {
"fill": "#b4d2f3"
},
"#oceanicPattern": {
"href": "",
"opacity": 1
},
"#terrs": {
"opacity": null,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 0,
"curve": 0,
"filter": null,
"mask": "u rl(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#000000",
"text-shadow": "white 0px 0px 4px",
"data-size": 4,
"font-size": 4,
"font-family": "Questrial"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#000000",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#000000",
"stroke-width": 0.24,
"stroke-dasharray": "",
"stroke-linecap": "round"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#000000",
"text-shadow": "white 0px 0px 4px",
"data-size": 3,
"font-size": 3,
"font-family": "Questrial"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#000000",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#000000",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "round"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 0.7,
"fill": "#000000",
"stroke": "#000000",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 16,
"font-size": 16,
"font-family": "Times New Roman",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#000000",
"stroke": "#000000",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Questrial",
"filter": null
},
"#fogging": {
"opacity": 0.98,
"fill": "#30426f",
"filter": null
}
}

388
styles/clean.json Normal file
View file

@ -0,0 +1,388 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 6,
"box-size": 3,
"stroke": "#000",
"stroke-width": 0,
"opacity": 1,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": 0.5,
"filter": "url(#blur7)",
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 0.8,
"stroke": "#414141",
"stroke-width": 0.7,
"stroke-dasharray": 0,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 0.8,
"stroke": "#414141",
"stroke-width": 0.45,
"stroke-dasharray": 1,
"stroke-linecap": "butt",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.09,
"filter": null,
"mask": "url(#land)"
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": "0",
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#414141",
"stroke-width": 0.45,
"stroke-dasharray": 3,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.8,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": null
},
"#relig": {
"opacity": 0.7,
"stroke": "#404040",
"stroke-width": 0.7,
"filter": null
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#eeedeb",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": null,
"filter": "url(#dropShadow01)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0,
"fill": "#0080ff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 2.58,
"stroke-dasharray": 0,
"stroke-linecap": "butt",
"filter": "url(#blur3)"
},
"#rural": {
"stroke": "#ff0000"
},
"#urban": {
"stroke": "#800000"
},
"#freshwater": {
"opacity": 0.5,
"fill": "#aadaff",
"stroke": "#5f799d",
"stroke-width": 0,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 0.7,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.6,
"stroke": "#595959",
"stroke-width": 0.4,
"filter": null,
"auto-filter": 0
},
"#lake_island": {
"opacity": 0,
"stroke": "#7c8eaf",
"stroke-width": 0,
"filter": null
},
"#terrain": {
"opacity": 1,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#aadaff"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.9,
"stroke": "#f6d068",
"stroke-width": 0.7,
"stroke-dasharray": 0,
"stroke-linecap": "inherit",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 1,
"stroke": "#ffffff",
"stroke-width": 0.25,
"stroke-dasharray": "",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#4f82c6",
"stroke-width": 0.45,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null,
"mask": "url(#water)"
},
"#statesBody": {
"opacity": 0.3,
"filter": null
},
"#statesHalo": {
"opacity": 0.5,
"data-width": 1,
"stroke-width": 1,
"filter": null
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"data-size": 10,
"font-size": 10,
"font-family": "Georgia",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.9,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1,
"filter": "url(#dropShadow01)"
},
"#emblems": {
"opacity": 1,
"stroke-width": 1,
"filter": null
},
"#texture": {
"opacity": null,
"filter": null,
"mask": "url(#land)"
},
"#textureImage": {},
"#zones": {
"opacity": 0.7,
"stroke": "#ff6262",
"stroke-width": 0,
"stroke-dasharray": "",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": null,
"layers": "none"
},
"#oceanBase": {
"fill": "#aadaff"
},
"#oceanicPattern": {
"href": "",
"opacity": 0.2
},
"#terrs": {
"opacity": 0.5,
"scheme": "bright",
"terracing": 0,
"skip": 5,
"relax": 0,
"curve": 0,
"filter": null,
"mask": "url(#land)"
},
"#legend": {
"data-size": 12.74,
"font-size": 12.74,
"font-family": "Arial",
"stroke": "#909090",
"stroke-width": 1.13,
"stroke-dasharray": 0,
"stroke-linecap": "round",
"data-x": 98.39,
"data-y": 12.67,
"data-columns": null
},
"#legendBox": {},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#414141",
"text-shadow": "white 0 0 4px",
"data-size": 7,
"font-size": 7,
"font-family": "Arial"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 0.24,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#303030",
"stroke-width": 1.7
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#414141",
"data-size": 3,
"font-size": 3,
"font-family": "Arial"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#3e3e4b",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.06
},
"#labels > #states": {
"opacity": 1,
"fill": "#292929",
"stroke": "#303030",
"stroke-width": 0,
"text-shadow": "white 0 0 2px",
"data-size": 10,
"font-size": 10,
"font-family": "Arial",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#414141",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0 0 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Arial",
"filter": null
},
"#fogging": {
"opacity": 1,
"fill": "#ffffff",
"filter": null
}
}

385
styles/cyberpunk.json Normal file
View file

@ -0,0 +1,385 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 8,
"box-size": 4,
"stroke": "#000000",
"stroke-width": 0.6,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": 0.7,
"filter": "",
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 1,
"stroke": "#ffffff",
"stroke-width": 1,
"stroke-dasharray": 3,
"stroke-linecap": "round",
"filter": ""
},
"#provinceBorders": {
"opacity": 0.5,
"stroke": "#ffffff",
"stroke-width": 0.3,
"stroke-dasharray": 1,
"stroke-linecap": "round",
"filter": ""
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": 0,
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 14,
"font-size": 14,
"stroke": "#4a4a4a",
"stroke-width": 1,
"stroke-dasharray": 6,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.9,
"transform": null,
"filter": null,
"mask": "",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": null
},
"#relig": {
"opacity": 0.5,
"stroke": "#404040",
"stroke-width": 2,
"filter": "url(#splotch)"
},
"#cults": {
"opacity": 0.35,
"stroke": "#777777",
"stroke-width": 2,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": "url(#splotch)"
},
"#landmass": {
"opacity": 1,
"fill": "#04011e",
"filter": null
},
"#markers": {
"opacity": 0.8,
"rescale": 1,
"filter": "url(#dropShadow05)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "square",
"filter": null
},
"#rural": {
"stroke": "#5294ff"
},
"#urban": {
"stroke": "#5cdeff"
},
"#freshwater": {
"opacity": 0.9,
"fill": "#381579",
"stroke": "#47228c",
"stroke-width": 3,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 0.7,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.6,
"stroke": "#1f3846",
"stroke-width": 0.7,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": 0.9,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#6738bc"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 1,
"stroke": "#c44ac0",
"stroke-width": 0.9,
"stroke-dasharray": "2 3",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 1,
"stroke": "#df2654",
"stroke-width": 0.2,
"stroke-dasharray": ".5 1",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#a890c6",
"stroke-width": 0.6,
"stroke-dasharray": "1.2 2.4",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0,
"filter": null
},
"#statesHalo": {
"opacity": 1,
"data-width": 13,
"stroke-width": 13,
"filter": "blur(8.25px)"
},
"#provs": {
"opacity": 0.2,
"fill": "#933e3e",
"font-size": 8,
"font-family": "Orbitron",
"filter": ""
},
"#temperature": {
"opacity": 0.8,
"font-size": "22px",
"fill": "#551282",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 3,
"stroke-dasharray": 2,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.3,
"fill": "#919191",
"stroke": "#949494",
"stroke-width": 0,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.75,
"stroke-width": 0.5,
"filter": ""
},
"#texture": {
"opacity": 0.14,
"filter": null,
"mask": "url(#water)"
},
"#zones": {
"opacity": 0.7,
"stroke": "#ffffff",
"stroke-width": 0.3,
"stroke-dasharray": null,
"stroke-linecap": "inherit",
"filter": "url(#dropShadow05)",
"mask": null
},
"#oceanLayers": {
"filter": "",
"layers": "-6,-3,-1"
},
"#oceanBase": {
"fill": "#05001f"
},
"#oceanicPattern": {
"href": "",
"opacity": 0.15
},
"#terrs": {
"opacity": 1,
"scheme": "monochrome",
"terracing": 6,
"skip": 0,
"relax": 2,
"curve": 0,
"filter": "",
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#ffffff",
"text-shadow": "white 0px 0px 4px",
"data-size": 8,
"font-size": 8,
"font-family": "Orbitron"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 2,
"stroke": "#444444",
"stroke-width": 0.25,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 4,
"stroke": "#3e3e4b",
"stroke-width": 1
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#ffffff",
"text-shadow": "white 0px 0px 4px",
"data-size": 3,
"font-size": 3,
"font-family": "Orbitron"
},
"#burgIcons > #towns": {
"opacity": 0.95,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.8,
"stroke": "#3e3e4b",
"stroke-width": 0.2,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1.6,
"stroke": "#3e3e4b",
"stroke-width": 1
},
"#labels > #states": {
"opacity": 1,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Orbitron",
"filter": ""
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Almendra SC",
"filter": null
},
"#fogging": {
"opacity": 0.98,
"fill": "#1b1423",
"filter": null
}
}

385
styles/default.json Normal file
View file

@ -0,0 +1,385 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 6,
"box-size": 3,
"stroke": "#000",
"stroke-width": 0.3,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": null,
"filter": null,
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 0.8,
"stroke": "#56566d",
"stroke-width": 1,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 0.8,
"stroke": "#56566d",
"stroke-width": 0.5,
"stroke-dasharray": "0 2",
"stroke-linecap": "round",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": 0,
"type": "pointyHex",
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#d4d4d4",
"stroke-width": 1,
"stroke-dasharray": 5,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.8,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": null
},
"#relig": {
"opacity": 0.7,
"stroke": "#777777",
"stroke-width": 0,
"filter": null
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#eef6fb",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": "url(#dropShadow01)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 0.5,
"fill": "#a6c1fd",
"stroke": "#5f799d",
"stroke-width": 0.7,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 1,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.5,
"stroke": "#1f3846",
"stroke-width": 0.7,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": null,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#5d97bb"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.9,
"stroke": "#d06324",
"stroke-width": 0.7,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 0.9,
"stroke": "#d06324",
"stroke-width": 0.25,
"stroke-dasharray": ".8 1.6",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#ffffff",
"stroke-width": 0.45,
"stroke-dasharray": "1 2",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.4,
"filter": null
},
"#statesHalo": {
"opacity": 0.4,
"data-width": 10,
"stroke-width": 10,
"filter": "blur(5px)"
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"font-size": 10,
"font-family": "Georgia",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.9,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.9,
"stroke-width": 1,
"filter": null
},
"#texture": {
"opacity": null,
"filter": null,
"mask": "url(#land)"
},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": null,
"layers": "-6,-3,-1"
},
"#oceanBase": {
"fill": "#466eab"
},
"#oceanicPattern": {
"href": "./images/pattern1.png",
"opacity": 0.2
},
"#terrs": {
"opacity": null,
"scheme": "bright",
"terracing": 0,
"skip": 5,
"relax": 0,
"curve": 0,
"filter": null,
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 7,
"font-size": 7,
"font-family": "Almendra SC"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 0.24,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 4,
"font-size": 4,
"font-family": "Almendra SC"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#3e3e4b",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 22,
"font-size": 22,
"font-family": "Almendra SC",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Almendra SC",
"filter": null
},
"#fogging": {
"opacity": 0.98,
"fill": "#30426f",
"filter": null
}
}

391
styles/gloom.json Normal file
View file

@ -0,0 +1,391 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 6,
"box-size": 3,
"stroke": "#000",
"stroke-width": 0.3,
"opacity": 1,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": null,
"filter": "url(#blur5)",
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 1,
"stroke": "#56566d",
"stroke-width": 1,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 1,
"stroke": "#56566d",
"stroke-width": 0.3,
"stroke-dasharray": ".7 1",
"stroke-linecap": "butt",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": "0",
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 14,
"font-size": 14,
"stroke": "#4a4a4a",
"stroke-width": 1,
"stroke-dasharray": 6,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.6,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": "translate(100 100) scale(0.3)"
},
"#relig": {
"opacity": 0.7,
"stroke": "#404040",
"stroke-width": 1,
"filter": null
},
"#cults": {
"opacity": 0.7,
"stroke": "#777777",
"stroke-width": 1.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#e0e0e0",
"filter": null
},
"#markers": {
"opacity": 0.8,
"rescale": 1,
"filter": "url(#dropShadow05)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000aa"
},
"#urban": {
"stroke": "#9d0000"
},
"#freshwater": {
"opacity": 0.5,
"fill": "#a6c1fd",
"stroke": "#5f799d",
"stroke-width": 0.7,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 0.7,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.6,
"stroke": "#1f3846",
"stroke-width": 0.7,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": 0.9,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#779582"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 1,
"stroke": "#8b4418",
"stroke-width": 0.9,
"stroke-dasharray": "2 3",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 1,
"stroke": "#844017",
"stroke-width": 0.2,
"stroke-dasharray": ".5 1",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#5e1865",
"stroke-width": 0.6,
"stroke-dasharray": "1.2 2.4",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.4,
"filter": null
},
"#statesHalo": {
"opacity": 0.5,
"data-width": 12,
"stroke-width": 12,
"filter": "blur(10px)"
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"data-size": 10,
"font-size": 10,
"font-family": "Georgia",
"filter": null
},
"#temperature": {
"opacity": 1,
"font-size": "11px",
"fill": "#62001b",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 2,
"stroke-dasharray": 2,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.9,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.6,
"stroke-width": 0.5,
"filter": null
},
"#texture": {
"opacity": null,
"filter": null,
"mask": "url(#land)"
},
"#textureImage": {
"x": 0,
"y": 0
},
"#zones": {
"opacity": 0.5,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": "url(#dropShadow01)",
"mask": null
},
"#oceanLayers": {
"filter": null,
"layers": "-6,-4,-2"
},
"#oceanBase": {
"fill": "#4e6964"
},
"#oceanicPattern": {
"href": "./images/pattern3.png",
"opacity": 0.2
},
"#terrs": {
"opacity": 1,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": 1,
"filter": "url(#filter-grayscale)",
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#legendBox": {},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0 0 4px",
"data-size": 7,
"font-size": 7,
"font-family": "Bitter"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 2,
"stroke": "#444444",
"stroke-width": 0.25,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 0.8,
"fill": "#ffffff",
"size": 4,
"stroke": "#3e3e4b",
"stroke-width": 1
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"data-size": 3,
"font-size": 3,
"font-family": "Bitter"
},
"#burgIcons > #towns": {
"opacity": 0.95,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.8,
"stroke": "#3e3e4b",
"stroke-width": 0.2,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1.6,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#4e4e4e",
"stroke": "#b5b5b5",
"stroke-width": 0,
"text-shadow": "white 0 0 4px",
"data-size": 22,
"font-size": 22,
"font-family": "Almendra SC",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0 0 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Almendra SC",
"filter": null
},
"#fogging": {
"opacity": 0.98,
"fill": "#1b1423",
"filter": null
}
}

385
styles/light.json Normal file
View file

@ -0,0 +1,385 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 8,
"box-size": 4,
"stroke": "#000",
"stroke-width": 0.02,
"fill-opacity": 0.8,
"filter": null
},
"#biomes": {
"opacity": 0.5,
"filter": null,
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 0.8,
"stroke": "#4c483e",
"stroke-width": 1,
"stroke-dasharray": 2,
"stroke-linecap": "square",
"filter": null
},
"#provinceBorders": {
"opacity": 0.8,
"stroke": "#56566d",
"stroke-width": 0.2,
"stroke-dasharray": 1,
"stroke-linecap": "butt",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.5,
"scale": 1,
"dx": 0,
"dy": 0,
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 1,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 0.7,
"data-size": 15,
"font-size": 15,
"stroke": "#734d37",
"stroke-width": 1.5,
"stroke-dasharray": 5,
"stroke-linecap": "square",
"filter": null,
"mask": ""
},
"#compass": {
"opacity": 0.6,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": null
},
"#relig": {
"opacity": 0.5,
"stroke": null,
"stroke-width": 0,
"filter": null
},
"#cults": {
"opacity": 0.5,
"stroke": "#777777",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#f9f2ea",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": null
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#2554ef",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 1,
"fill": "#98cdc4",
"stroke": "#719892",
"stroke-width": 0.46,
"filter": "url(#dropShadow05)"
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 1,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 1,
"stroke": "#5e5e5e",
"stroke-width": 0.4,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": 0.6,
"set": "colored",
"size": 1,
"density": 0.3,
"filter": null,
"mask": ""
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#6d94ba"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.9,
"stroke": "#3c1d0b",
"stroke-width": 1.37,
"stroke-dasharray": 2,
"stroke-linecap": "inherit",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 0.9,
"stroke": "#95481a",
"stroke-width": 0.88,
"stroke-dasharray": ".8 1.6",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#ffffff",
"stroke-width": 0.45,
"stroke-dasharray": "1 2",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.2,
"filter": null
},
"#statesHalo": {
"opacity": 0.3,
"data-width": 25,
"stroke-width": 25,
"filter": "blur(5px)"
},
"#provs": {
"opacity": 0.4,
"fill": "#000000",
"font-size": 5,
"font-family": "IM Fell English",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.5,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1.5,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.9,
"stroke-width": 1,
"filter": null
},
"#texture": {
"opacity": 0.39,
"filter": null,
"mask": ""
},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": "url(#dropShadow05)",
"layers": "-6,-3,-1"
},
"#oceanBase": {
"fill": "#8dc1c8"
},
"#oceanicPattern": {
"href": "./images/pattern1.png",
"opacity": 0.2
},
"#terrs": {
"opacity": 0.4,
"scheme": "light",
"terracing": 10,
"skip": 5,
"relax": 0,
"curve": 0,
"filter": "url(#turbulence)",
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 54.73,
"data-y": 62.98,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#3a3a3a",
"text-shadow": "white 0px 0px 4px",
"data-size": 8,
"font-size": 8,
"font-family": "IM Fell English"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 3,
"stroke": "#3e3e4b",
"stroke-width": 0.4,
"stroke-dasharray": "0.5 0.25",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 5.5,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 4,
"font-size": 4,
"font-family": "IM Fell English"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 1.2,
"stroke": "#3e3e4b",
"stroke-width": 0.2,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 2.2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#3e3e3e",
"stroke": "#000000",
"stroke-width": 0.3,
"text-shadow": "white 0px 0px 6px",
"data-size": 14,
"font-size": 14,
"font-family": "IM Fell English",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#f24706",
"stroke": "#701b05",
"stroke-width": 0.1,
"text-shadow": "white 0px 0px 4px",
"data-size": 6,
"font-size": 6,
"font-family": "IM Fell English",
"filter": null
},
"#fogging": {
"opacity": 1,
"fill": "#30426f",
"filter": null
}
}

381
styles/monochrome.json Normal file
View file

@ -0,0 +1,381 @@
{
"#map": {
"background-color": "#000000",
"filter": "url(#filter-grayscale)",
"data-filter": "grayscale"
},
"#armies": {
"font-size": 6,
"box-size": 3,
"stroke": "#000",
"stroke-width": 0.3,
"opacity": 1,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": null,
"filter": "url(#blur5)",
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 1,
"stroke": "#56566d",
"stroke-width": 1,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 1,
"stroke": "#56566d",
"stroke-width": 0.4,
"stroke-dasharray": 1,
"stroke-linecap": "butt",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": "0",
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#d4d4d4",
"stroke-width": 1,
"stroke-dasharray": 5,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.8,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": null
},
"#relig": {
"opacity": 0.7,
"stroke": "#404040",
"stroke-width": 0.7,
"filter": null
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#000000",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": "url(#dropShadow01)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 1,
"fill": "#000000",
"stroke": "#515151",
"stroke-width": 0,
"filter": null
},
"#salt": {
"opacity": 1,
"fill": "#000000",
"stroke": "#484848",
"stroke-width": 0,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#000000",
"stroke": "#5f5f5f",
"stroke-width": 0.5,
"filter": null
},
"#frozen": {
"opacity": 1,
"fill": "#000000",
"stroke": "#6f6f6f",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 1,
"fill": "#000000",
"stroke": "#5d5d5d",
"stroke-width": 0,
"filter": null
},
"#sea_island": {
"opacity": 1,
"stroke": "#1f3846",
"stroke-width": 0,
"filter": null,
"auto-filter": 0
},
"#lake_island": {
"opacity": 0,
"stroke": "#7c8eaf",
"stroke-width": 0,
"filter": null
},
"#terrain": {
"opacity": null,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": 0.2,
"filter": "url(#blur1)",
"fill": "#000000"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.9,
"stroke": "#d06324",
"stroke-width": 0.7,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 0.9,
"stroke": "#d06324",
"stroke-width": 0.25,
"stroke-dasharray": ".8 1.6",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#ffffff",
"stroke-width": 0.45,
"stroke-dasharray": "1 2",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.4,
"filter": null
},
"#statesHalo": {
"opacity": 0.4,
"data-width": 10,
"stroke-width": 10,
"filter": "blur(5px)"
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"data-size": 10,
"font-size": 10,
"font-family": "Georgia",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.9,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1,
"filter": "url(#dropShadow05)"
},
"#texture": {
"opacity": 1,
"filter": null,
"mask": "url(#land)"
},
"#emblems": {
"opacity": 0.5,
"stroke-width": 0.5,
"filter": null
},
"#textureImage": {},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": null,
"layers": "none"
},
"#oceanBase": {
"fill": "#000000"
},
"#oceanicPattern": {
"href": "",
"opacity": 0.2
},
"#terrs": {
"opacity": 1,
"scheme": "monochrome",
"terracing": 0,
"skip": 5,
"relax": 0,
"curve": 0,
"filter": "url(#blur3)",
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#legendBox": {},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0 0 4px",
"data-size": 7,
"font-size": 7,
"font-family": "Almendra SC"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 0.24,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"data-size": 4,
"font-size": 4,
"font-family": "Almendra SC"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#3e3e4b",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0 0 4px",
"data-size": 22,
"font-size": 22,
"font-family": "Almendra SC",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0 0 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Almendra SC",
"filter": null
},
"#fogging": {
"opacity": 0.98,
"fill": "#30426f",
"filter": null
}
}

385
styles/watercolor.json Normal file
View file

@ -0,0 +1,385 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 8,
"box-size": 4,
"stroke": "#000",
"stroke-width": 0.2,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": 0.6,
"filter": null,
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 0.6,
"stroke": "#56566d",
"stroke-width": 1,
"stroke-dasharray": 3,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 0.5,
"stroke": "#56566d",
"stroke-width": 0.5,
"stroke-dasharray": "0 2",
"stroke-linecap": "round",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": 0,
"type": "pointyHex",
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#d4d4d4",
"stroke-width": 1,
"stroke-dasharray": 5,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.8,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": "translate(80 80) scale(.25)"
},
"#relig": {
"opacity": 0.7,
"stroke": "#777777",
"stroke-width": 0,
"filter": "url(#bluredSplotch)"
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": "url(#splotch)"
},
"#landmass": {
"opacity": 1,
"fill": "#eef6fb",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": "url(#dropShadow01)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 0.5,
"fill": "#a6c1fd",
"stroke": "#5f799d",
"stroke-width": 0.7,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 1,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.5,
"stroke": "#1f3846",
"stroke-width": 0.7,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": 1,
"set": "gray",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#2e89c2"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.9,
"stroke": "#969696",
"stroke-width": 0.7,
"stroke-dasharray": "",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 0.9,
"stroke": "#969696",
"stroke-width": 0.4,
"stroke-dasharray": "",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.9,
"stroke": "#969696",
"stroke-width": 0.7,
"stroke-dasharray": "",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.05,
"filter": null
},
"#statesHalo": {
"opacity": 0.4,
"data-width": 8,
"stroke-width": 8,
"filter": "blur(2px)"
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"font-size": 4,
"font-family": "Comfortaa",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.7,
"fill": "#dfe8ec",
"stroke": "#000000",
"stroke-width": 0,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.95,
"stroke-width": 1,
"filter": null
},
"#texture": {
"opacity": 0.2,
"filter": null,
"mask": "url(#land)"
},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": null,
"layers": "-6,-4,-2"
},
"#oceanBase": {
"fill": "#2d788b"
},
"#oceanicPattern": {
"href": "./images/kiwiroo.png",
"opacity": 0.5
},
"#terrs": {
"opacity": 0.5,
"scheme": "light",
"terracing": 0,
"skip": 5,
"relax": 1,
"curve": 0,
"filter": null,
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#043449",
"text-shadow": "white 0px 0px 2px",
"data-size": 5,
"font-size": 5,
"font-family": "Comfortaa"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 0.24,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 3,
"font-size": 3,
"font-family": "Comfortaa"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#3e3e4b",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 0.15,
"text-shadow": "black 1px 1px 2px",
"data-size": 20,
"font-size": 20,
"font-family": "Gloria Hallelujah",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Comfortaa",
"filter": null
},
"#fogging": {
"opacity": 0.97,
"fill": "#8398ce",
"filter": null
}
}

View file

@ -154,18 +154,23 @@ void (function () {
const prompt = document.getElementById("prompt"); const prompt = document.getElementById("prompt");
const form = prompt.querySelector("#promptForm"); const form = prompt.querySelector("#promptForm");
window.prompt = function (promptText = "Please provide an input", options = {default: 1, step: 0.01, min: 0, max: 100}, callback) { const defaultText = "Please provide an input";
if (options.default === undefined) { const defaultOptions = {default: 1, step: 0.01, min: 0, max: 100, required: true};
ERROR && console.error("Prompt: options object does not have default value defined");
return; window.prompt = function (promptText = defaultText, options = defaultOptions, callback) {
} if (options.default === undefined) return ERROR && console.error("Prompt: options object does not have default value defined");
const input = prompt.querySelector("#promptInput"); const input = prompt.querySelector("#promptInput");
prompt.querySelector("#promptText").innerHTML = promptText; prompt.querySelector("#promptText").innerHTML = promptText;
const type = typeof options.default === "number" ? "number" : "text"; const type = typeof options.default === "number" ? "number" : "text";
input.type = type; input.type = type;
if (options.step !== undefined) input.step = options.step; if (options.step !== undefined) input.step = options.step;
if (options.min !== undefined) input.min = options.min; if (options.min !== undefined) input.min = options.min;
if (options.max !== undefined) input.max = options.max; if (options.max !== undefined) input.max = options.max;
input.required = options.required === false ? false : true;
input.placeholder = "type a " + type; input.placeholder = "type a " + type;
input.value = options.default; input.value = options.default;
prompt.style.display = "block"; prompt.style.display = "block";
@ -173,9 +178,9 @@ void (function () {
form.addEventListener( form.addEventListener(
"submit", "submit",
event => { event => {
event.preventDefault();
prompt.style.display = "none"; prompt.style.display = "none";
const v = type === "number" ? +input.value : input.value; const v = type === "number" ? +input.value : input.value;
event.preventDefault();
if (callback) callback(v); if (callback) callback(v);
}, },
{once: true} {once: true}
@ -183,7 +188,9 @@ void (function () {
}; };
const cancel = prompt.querySelector("#promptCancel"); const cancel = prompt.querySelector("#promptCancel");
cancel.addEventListener("click", () => (prompt.style.display = "none")); cancel.addEventListener("click", () => {
prompt.style.display = "none";
});
})(); })();
// indexedDB; ldb object // indexedDB; ldb object

View file

@ -5,7 +5,7 @@
function convertTemperature(temp) { function convertTemperature(temp) {
switch (temperatureScale.value) { switch (temperatureScale.value) {
case "°C": case "°C":
return temp + "°C"; return rn(temp) + "°C";
case "°F": case "°F":
return rn((temp * 9) / 5 + 32) + "°F"; return rn((temp * 9) / 5 + 32) + "°F";
case "K": case "K":
@ -21,7 +21,7 @@ function convertTemperature(temp) {
case "°Rø": case "°Rø":
return rn((temp * 21) / 40 + 7.5) + "°Rø"; return rn((temp * 21) / 40 + 7.5) + "°Rø";
default: default:
return temp + "°C"; return rn(temp) + "°C";
} }
} }