mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-23 12:31:24 +01:00
Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into dev-submaps
This commit is contained in:
commit
b58674dddd
23 changed files with 850 additions and 522 deletions
83
index.css
83
index.css
|
|
@ -29,16 +29,19 @@ input:read-only {
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
vertical-align: bottom;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
textarea {
|
||||
padding: 2px;
|
||||
text-indent: 1px;
|
||||
padding: 3px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
|
@ -835,75 +838,79 @@ fieldset {
|
|||
}
|
||||
|
||||
.matrix-table {
|
||||
width: 100%;
|
||||
font-size: smaller;
|
||||
text-align: center;
|
||||
border-collapse: collapse;
|
||||
max-height: 80vh;
|
||||
max-width: 85vw;
|
||||
scrollbar-width: thin;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
table.matrix-table th,
|
||||
table.matrix-table td {
|
||||
.matrix-table > table {
|
||||
text-align: center;
|
||||
border-collapse: collapse;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.matrix-table > table th,
|
||||
.matrix-table > table td {
|
||||
border: 1px solid var(--dark-solid);
|
||||
height: 2em;
|
||||
padding: 0.2em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
table.matrix-table th {
|
||||
.matrix-table > table th {
|
||||
background-color: #302a2a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
table.matrix-table tr:hover th {
|
||||
background: #3e3636;
|
||||
}
|
||||
|
||||
table.matrix-table td:hover {
|
||||
.matrix-table > table td:hover {
|
||||
outline: 2px solid var(--dark-solid);
|
||||
outline-offset: -1px;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
table.matrix-table td.Ally {
|
||||
.matrix-table > table td.Ally {
|
||||
background-color: #73ec73;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
table.matrix-table td.Friendly {
|
||||
.matrix-table > table td.Friendly {
|
||||
background-color: #d4f8aa;
|
||||
}
|
||||
|
||||
table.matrix-table td.Neutral {
|
||||
.matrix-table > table td.Neutral {
|
||||
background-color: #d8d9d3;
|
||||
}
|
||||
|
||||
table.matrix-table td.Suspicion {
|
||||
.matrix-table > table td.Suspicion {
|
||||
background-color: #eeafaa;
|
||||
}
|
||||
|
||||
table.matrix-table td.Enemy {
|
||||
.matrix-table > table td.Enemy {
|
||||
background-color: #ffa39c;
|
||||
color: #af0d23;
|
||||
}
|
||||
|
||||
table.matrix-table td.Unknown {
|
||||
.matrix-table > table td.Unknown {
|
||||
background-color: #c1bfbf;
|
||||
}
|
||||
|
||||
table.matrix-table td.Rival {
|
||||
.matrix-table > table td.Rival {
|
||||
background-color: #bd845c;
|
||||
}
|
||||
|
||||
table.matrix-table td.Vassal {
|
||||
.matrix-table > table td.Vassal {
|
||||
background-color: #87cefa;
|
||||
}
|
||||
|
||||
table.matrix-table td.Suzerain {
|
||||
.matrix-table > table td.Suzerain {
|
||||
background-color: #8f8fe1;
|
||||
}
|
||||
|
||||
table.matrix-table td.x {
|
||||
.matrix-table > table td.x {
|
||||
background-color: #d4ca94;
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
#sizeOutput {
|
||||
|
|
@ -1311,19 +1318,23 @@ div.slider .ui-slider-handle {
|
|||
}
|
||||
|
||||
#alertMessage::-webkit-scrollbar,
|
||||
.table::-webkit-scrollbar {
|
||||
.table::-webkit-scrollbar,
|
||||
.matrix-table::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#alertMessage::-webkit-scrollbar-thumb,
|
||||
.table::-webkit-scrollbar-thumb {
|
||||
.table::-webkit-scrollbar-thumb,
|
||||
.matrix-table::-webkit-scrollbar-thumb {
|
||||
background-color: #aaa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
#alertMessage::-webkit-scrollbar-thumb:hover,
|
||||
.table::-webkit-scrollbar-thumb:hover {
|
||||
.table::-webkit-scrollbar-thumb:hover,
|
||||
.matrix-table::-webkit-scrollbar-thumb:hover {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
|
|
@ -1505,15 +1516,14 @@ div.states > .riverType {
|
|||
width: 5em;
|
||||
}
|
||||
|
||||
div.states > .coaIcon {
|
||||
.coaIcon {
|
||||
stroke-width: 3;
|
||||
width: 1.4em;
|
||||
height: 1.4em;
|
||||
margin: -0.3em 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.states > .coaIcon > use {
|
||||
.coaIcon > use {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
|
@ -2001,7 +2011,7 @@ div.textual fieldset {
|
|||
|
||||
div.textual span,
|
||||
.textual legend {
|
||||
font-size: 0.8em;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
|
@ -2120,8 +2130,9 @@ svg.button {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#info-line {
|
||||
.info-line {
|
||||
font-size: 0.9em;
|
||||
font-style: italic;
|
||||
color: gray;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
@ -2265,10 +2276,10 @@ svg.button {
|
|||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: 22em;
|
||||
background-color: #fff;
|
||||
max-width: 23em;
|
||||
padding: 1.2em;
|
||||
border: solid 1px #000;
|
||||
background-color: var(--bg-dialogs);
|
||||
border: solid 1px var(--dark-solid);
|
||||
font-size: 1.2em;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
|
|
|||
89
index.html
89
index.html
|
|
@ -947,7 +947,7 @@
|
|||
|
||||
<tr data-tip="Map seed number. Seed produces the same map only if canvas size and options are the same">
|
||||
<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>Map seed</td>
|
||||
<td>
|
||||
|
|
@ -1062,7 +1062,7 @@
|
|||
<input id="regionsInput" data-stored="regions" type="range" min=0 max=99 value=13>
|
||||
</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>
|
||||
</tr>
|
||||
|
||||
|
|
@ -1451,9 +1451,21 @@
|
|||
</div>
|
||||
|
||||
<div id="aboutContent" class="tabcontent">
|
||||
<p><a href="https://github.com/Azgaar/Fantasy-Map-Generator" target="_blank">Fantasy Map Generator</a> is a free <a href="https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE" target="_blank">open source</a> tool which procedurally generates fantasy maps. You may use auto-generated maps as they are, edit them or even create a new map from scratch. Check out the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial" target="_blank">quick start tutorial</a>, <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A" target="_blank">Q&A</a> and <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys" target="_blank">hotkeys</a> for guidance.</p>
|
||||
<p>Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share 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>
|
||||
<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</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">
|
||||
<a href="https://www.patreon.com/azgaar" target="_blank" style="color: white; text-decoration: none; font-family: sans-serif">
|
||||
<div>
|
||||
|
|
@ -1466,7 +1478,10 @@
|
|||
</div>
|
||||
</a>
|
||||
</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">
|
||||
<li><a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator%2F"e=" data-tip="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="images/Facebook.png" /></a></li>
|
||||
|
|
@ -2038,11 +2053,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mfcgPreviewSection" data-tip="Burg preview in the Medieval Fantasy City Generator. Default seed is a conbimation of map seed and burg id" style="display: flex; flex-direction: column">
|
||||
<div 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>
|
||||
See in <a id="mfcgLink" target="_blank">City Generator by Watabou</a>.
|
||||
Seed: <input id="mfcgBurgSeed" style="width: 10em" type="number" min=1 max="1e13" step="1" />
|
||||
<i id="regenerateMFCGBurgSeed" data-tip="Randomize Medieval Fantasy City Generator burg seed" class="icon-arrows-cw pointer" style="margin-left: .1em"></i>
|
||||
<div id="mfcgBurgSeedSection">
|
||||
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>
|
||||
<iframe id="mfcgPreview" sandbox="allow-scripts allow-same-origin"></iframe>
|
||||
</div>
|
||||
|
|
@ -2623,8 +2641,10 @@
|
|||
<option value="Sultanate">Sultanate</option>
|
||||
<option value="Tsardom">Tsardom</option>
|
||||
<option value="Ulus">Ulus</option>
|
||||
<option value="Viceroyalty">Viceroyalty</option>
|
||||
</optgroup>
|
||||
<optgroup label="Republic">
|
||||
<option value="Chancellery">Chancellery</option>
|
||||
<option value="City-state">City-state</option>
|
||||
<option value="Diarchy">Diarchy</option>
|
||||
<option value="Federation">Federation</option>
|
||||
|
|
@ -2662,8 +2682,10 @@
|
|||
<option value="Divine Kingdom">Divine Kingdom</option>
|
||||
<option value="Divine Empire">Divine Empire</option>
|
||||
<option value="Eparchy">Eparchy</option>
|
||||
<option value="Exarchate">Exarchate</option>
|
||||
<option value="Holy State">Holy State</option>
|
||||
<option value="Imamah">Imamah</option>
|
||||
<option value="Patriarchate">Patriarchate</option>
|
||||
<option value="Theocracy">Theocracy</option>
|
||||
</optgroup>
|
||||
<optgroup label="Anarchy">
|
||||
|
|
@ -2740,33 +2762,27 @@
|
|||
<div id="diplomacyEditor" class="dialog stable" style="display: none">
|
||||
<div id="diplomacyHeader" class="header">
|
||||
<div style="left:.2em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="name">State </div>
|
||||
<div style="left:13.4em" data-tip="Click to sort by diplomatical relations" class="sortable alphabetically" data-sortby="relations">Relations </div>
|
||||
<div style="left:14.4em" data-tip="Click to sort by diplomatical relations" class="sortable alphabetically" data-sortby="relations">Relations </div>
|
||||
</div>
|
||||
|
||||
<div id="diplomacyBodySection" class="table"></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 class="info-line">Click on state name to see relations.<br>Click on relations name to change it</div>
|
||||
|
||||
<div id="diplomacyBottom" style="margin-top: .1em">
|
||||
<button id="diplomacyEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
|
||||
<button id="diplomacyEditStyle" data-tip="Edit states (including diplomacy view) style in Style Editor" class="icon-adjust"></button>
|
||||
<button id="diplomacyRegenerate" data-tip="Regenerate diplomatical relations" class="icon-retweet"></button>
|
||||
<button id="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="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>
|
||||
</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>
|
||||
<div data-tip="Province short name" class="label">Short name:</div>
|
||||
|
|
@ -2784,6 +2800,8 @@
|
|||
<option value="Autonomy">Autonomy</option>
|
||||
<option value="Barony">Barony</option>
|
||||
<option value="Canton">Canton</option>
|
||||
<option value="Captaincy">Captaincy</option>
|
||||
<option value="Chiefdom">Chiefdom</option>
|
||||
<option value="Clan">Clan</option>
|
||||
<option value="Colony">Colony</option>
|
||||
<option value="Council">Council</option>
|
||||
|
|
@ -2791,6 +2809,7 @@
|
|||
<option value="Deanery">Deanery</option>
|
||||
<option value="Department">Department</option>
|
||||
<option value="Dependency">Dependency</option>
|
||||
<option value="Diaconate">Diaconate</option>
|
||||
<option value="District">District</option>
|
||||
<option value="Earldom">Earldom</option>
|
||||
<option value="Governorate">Governorate</option>
|
||||
|
|
@ -2800,6 +2819,7 @@
|
|||
<option value="Landgrave">Landgrave</option>
|
||||
<option value="Mandate">Mandate</option>
|
||||
<option value="Margrave">Margrave</option>
|
||||
<option value="Municipality">Municipality</option>
|
||||
<option value="Occupation zone">Occupation zone</option>
|
||||
<option value="Parish">Parish</option>
|
||||
<option value="Prefecture">Prefecture</option>
|
||||
|
|
@ -2807,6 +2827,7 @@
|
|||
<option value="Region">Region</option>
|
||||
<option value="Republic">Republic</option>
|
||||
<option value="Reservation">Reservation</option>
|
||||
<option value="Seneschalty">Seneschalty</option>
|
||||
<option value="Shire">Shire</option>
|
||||
<option value="State">State</option>
|
||||
<option value="Territory">Territory</option>
|
||||
|
|
@ -2874,21 +2895,22 @@
|
|||
<div id="namesbaseBasesTop">
|
||||
<span>Select base: </span>
|
||||
<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 id="namesbaseBody">
|
||||
<span>Names data:</span><br>
|
||||
<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 id="namesbaseBody" style="margin-block: 2px">
|
||||
<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>
|
||||
|
||||
<div>
|
||||
<span>Name: </span>
|
||||
<input id="namesbaseName" data-tip="Type to change a base name" placeholder="Base name" autocorrect="off" spellcheck="false" style="width:12em"/>
|
||||
<span>Length: </span>
|
||||
<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>
|
||||
<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">
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Generated examples: </legend>
|
||||
<div id="namesbaseExamples" data-tip="Examples. Click to re-generate"></div>
|
||||
|
|
@ -2897,12 +2919,12 @@
|
|||
|
||||
<div id="namesbaseBottom">
|
||||
<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="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="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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -3289,6 +3311,7 @@
|
|||
<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="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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -3640,8 +3663,8 @@
|
|||
</div>
|
||||
<div data-tip="Image scale relative to image size (e.g. 5x)" style="margin-bottom: .3em">
|
||||
<div class="label">Scale:</div>
|
||||
<input id="tileScaleInput" data-stored="tileScale" type="range" min=1 max=4 value=1 style="width: 11em">
|
||||
<input id="tileScaleOutput" data-stored="tileScale" type="number" min=1 value=1>
|
||||
<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 >
|
||||
</div>
|
||||
<div data-tip="Calculated size of image if combined" style="margin-bottom: .3em">
|
||||
<div class="label">Total size:</div>
|
||||
|
|
@ -3695,7 +3718,7 @@
|
|||
<div id="prompt" style="display: none" class="dialog">
|
||||
<form id="promptForm">
|
||||
<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="button" id="promptCancel" formnovalidate>Cancel</button>
|
||||
</form>
|
||||
|
|
|
|||
37
main.js
37
main.js
|
|
@ -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());
|
||||
|
||||
// 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 seed;
|
||||
let mapId;
|
||||
|
|
@ -161,7 +161,7 @@ let urbanDensity = +document.getElementById("urbanDensityInput").value;
|
|||
|
||||
applyStoredOptions();
|
||||
|
||||
// voronoi graph extention, cannot be changed arter generation
|
||||
// voronoi graph extension, cannot be changed after generation
|
||||
let graphWidth = +mapWidthInput.value;
|
||||
let graphHeight = +mapHeightInput.value;
|
||||
|
||||
|
|
@ -493,7 +493,7 @@ function invokeActiveZooming() {
|
|||
coastline.select("#sea_island").attr("filter", filter);
|
||||
}
|
||||
|
||||
// rescale lables on zoom
|
||||
// rescale labels on zoom
|
||||
if (labels.style("display") !== "none") {
|
||||
labels.selectAll("g").each(function () {
|
||||
if (this.id === "burgLabels") return;
|
||||
|
|
@ -668,7 +668,7 @@ function generate() {
|
|||
const parsedError = parseError(error);
|
||||
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.
|
||||
<p id="errorBox">${parsedError}</p>`;
|
||||
$("#alert").dialog({
|
||||
|
|
@ -909,7 +909,7 @@ function defineMapSize() {
|
|||
const template = document.getElementById("templateInput").value; // heightmap template
|
||||
const part = grid.features.some(f => f.land && f.border); // if land goes over map borders
|
||||
const max = part ? 80 : 100; // max size
|
||||
const lat = () => gauss(P(0.5) ? 40 : 60, 15, 25, 75); // latiture shift
|
||||
const lat = () => gauss(P(0.5) ? 40 : 60, 15, 25, 75); // latitude shift
|
||||
|
||||
if (!part) {
|
||||
if (template === "Pangea") return [100, 50];
|
||||
|
|
@ -981,7 +981,10 @@ function generatePrecipitation() {
|
|||
prec.selectAll("*").remove();
|
||||
const {cells, cellsX, cellsY} = grid;
|
||||
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 easterly = [];
|
||||
|
|
@ -997,14 +1000,14 @@ function generatePrecipitation() {
|
|||
// x2 = 60-70 latitude: wet summer (rising zone), dry winter (sinking zone)
|
||||
// x1 = 70-85 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;
|
||||
|
||||
// define wind directions based on cells latitude and prevailing winds there
|
||||
d3.range(0, cells.i.length, cellsX).forEach(function (c, i) {
|
||||
const lat = mapCoordinates.latN - (i / cellsY) * mapCoordinates.latT;
|
||||
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 {isWest, isEast, isNorth, isSouth} = getWindDirections(windTier);
|
||||
|
||||
|
|
@ -1021,14 +1024,14 @@ function generatePrecipitation() {
|
|||
const vertT = southerly + northerly;
|
||||
if (northerly) {
|
||||
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;
|
||||
passWind(d3.range(0, cellsX, 1), maxPrecN, cellsX, cellsY);
|
||||
}
|
||||
|
||||
if (southerly) {
|
||||
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;
|
||||
passWind(d3.range(cells.i.length - cellsX, cells.i.length, 1), maxPrecS, -cellsX, cellsY);
|
||||
}
|
||||
|
|
@ -1054,7 +1057,7 @@ function generatePrecipitation() {
|
|||
}
|
||||
|
||||
let humidity = maxPrec - cells.h[first]; // initial water amount
|
||||
if (humidity <= 0) continue; // if first cell in row is too elevated cosdired wind dry
|
||||
if (humidity <= 0) continue; // if first cell in row is too elevated consider wind dry
|
||||
|
||||
for (let s = 0, current = first; s < steps; s++, current += next) {
|
||||
if (cells.temp[current] < -5) continue; // no flux in permafrost
|
||||
|
|
@ -1182,7 +1185,7 @@ function reGraph() {
|
|||
TIME && console.timeEnd("reGraph");
|
||||
}
|
||||
|
||||
// Detect and draw the coasline
|
||||
// Detect and draw the coastline
|
||||
function drawCoastline() {
|
||||
TIME && console.time("drawCoastline");
|
||||
reMarkFeatures();
|
||||
|
|
@ -1191,7 +1194,7 @@ function drawCoastline() {
|
|||
vertices = pack.vertices,
|
||||
n = cells.i.length,
|
||||
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(
|
||||
features.map(f => (f.land ? f.cells : 0)),
|
||||
(a, b) => b - a
|
||||
|
|
@ -1395,6 +1398,12 @@ function reMarkFeatures() {
|
|||
TIME && console.timeEnd("reMarkFeatures");
|
||||
}
|
||||
|
||||
function isWetLand(moisture, temperature, height) {
|
||||
if (moisture > 40 && temperature > -2 && height < 25) return true; //near coast
|
||||
if (moisture > 24 && temperature > -2 && height > 24 && height < 60) return true; //off coast
|
||||
return false;
|
||||
}
|
||||
|
||||
// assign biome id for each cell
|
||||
function defineBiomes() {
|
||||
TIME && console.time("defineBiomes");
|
||||
|
|
@ -1427,7 +1436,7 @@ function defineBiomes() {
|
|||
function getBiomeId(moisture, temperature, height) {
|
||||
if (height < 20) return 0; // marine biome: all water cells
|
||||
if (temperature < -5) return 11; // permafrost biome
|
||||
if (moisture > 40 && temperature > -2 && (height < 25 || (moisture > 24 && height > 24 && height < 60))) return 12; // wetland biome
|
||||
if (isWetLand(moisture, temperature, height)) return 12; // wetland biome
|
||||
|
||||
const moistureBand = Math.min((moisture / 5) | 0, 4); // [0-4]
|
||||
const temperatureBand = Math.min(Math.max(20 - temperature, 0), 25); // [0-25]
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ window.BurgsAndStates = (function () {
|
|||
|
||||
function placeCapitals() {
|
||||
TIME && console.time("placeCapitals");
|
||||
let count = +regionsInput.value;
|
||||
let count = +regionsOutput.value;
|
||||
let burgs = [0];
|
||||
|
||||
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.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.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 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;
|
||||
|
|
@ -726,7 +726,7 @@ window.BurgsAndStates = (function () {
|
|||
TIME && console.time("assignColors");
|
||||
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 => {
|
||||
if (!s.i || s.removed) return;
|
||||
const neibs = s.neighbors;
|
||||
|
|
@ -962,12 +962,12 @@ window.BurgsAndStates = (function () {
|
|||
const republic = {
|
||||
Republic: 75,
|
||||
Federation: 4,
|
||||
Oligarchy: 2,
|
||||
"Trade Company": 4,
|
||||
"Most Serene Republic": 2,
|
||||
Oligarchy: 2,
|
||||
Tetrarchy: 1,
|
||||
Triumvirate: 1,
|
||||
Diarchy: 1,
|
||||
"Trade Company": 4,
|
||||
Junta: 1
|
||||
}; // weighted random
|
||||
const union = {Union: 3, League: 4, Confederation: 1, "United Kingdom": 1, "United Republic": 1, "United Provinces": 2, Commonwealth: 1, Heptarchy: 1}; // weighted random
|
||||
|
|
@ -997,7 +997,7 @@ window.BurgsAndStates = (function () {
|
|||
const form = monarchy[tier];
|
||||
// Default name depends on exponent tier, some culture bases have special names for tiers
|
||||
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 (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 "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 (tier > 2 && P(0.8) && [18, 17, 28].includes(base)) return "Caliphate"; // Arabic, Berber, Swahili
|
||||
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 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},
|
||||
Theocracy: {Parish: 3, Deanery: 1},
|
||||
Union: {Province: 1, State: 1, Canton: 1, Republic: 1, County: 1, Council: 1},
|
||||
|
|
|
|||
|
|
@ -405,8 +405,8 @@ function saveGeoJSON_Cells() {
|
|||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName("Cells") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), name, "application/json");
|
||||
const fileName = getFileName("Cells") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||
}
|
||||
|
||||
function saveGeoJSON_Routes() {
|
||||
|
|
@ -421,30 +421,25 @@ function saveGeoJSON_Routes() {
|
|||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName("Routes") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), name, "application/json");
|
||||
const fileName = getFileName("Routes") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||
}
|
||||
|
||||
function saveGeoJSON_Rivers() {
|
||||
const json = {type: "FeatureCollection", features: []};
|
||||
|
||||
rivers.selectAll("path").each(function () {
|
||||
const coordinates = getRiverPoints(this);
|
||||
const id = this.id;
|
||||
const width = +this.dataset.increment;
|
||||
const increment = +this.dataset.increment;
|
||||
const river = pack.rivers.find(r => r.i === +id.slice(5));
|
||||
const name = river ? river.name : "";
|
||||
const type = river ? river.type : "";
|
||||
const i = river ? river.i : "";
|
||||
const basin = river ? river.basin : "";
|
||||
const river = pack.rivers.find(r => r.i === +this.id.slice(5));
|
||||
if (!river) return;
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
const name = getFileName("Rivers") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), name, "application/json");
|
||||
const fileName = getFileName("Rivers") + ".geojson";
|
||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||
}
|
||||
|
||||
function saveGeoJSON_Markers() {
|
||||
|
|
|
|||
|
|
@ -828,6 +828,7 @@ function parseLoadedData(data) {
|
|||
// v 1.65 changed rivers data
|
||||
d3.select("#rivers").attr("style", null); // remove style to unhide layer
|
||||
const {cells, rivers} = pack;
|
||||
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
|
||||
|
||||
for (const river of rivers) {
|
||||
const node = document.getElementById("river" + river.i);
|
||||
|
|
@ -856,7 +857,7 @@ function parseLoadedData(data) {
|
|||
river.points = riverPoints;
|
||||
}
|
||||
|
||||
river.widthFactor = 1;
|
||||
river.widthFactor = defaultWidthFactor;
|
||||
|
||||
cells.i.forEach(i => {
|
||||
const riverInWater = cells.r[i] && cells.h[i] < 20;
|
||||
|
|
@ -1013,6 +1014,31 @@ function parseLoadedData(data) {
|
|||
ERROR && console.error("Data Integrity Check. Province", p.i, "is linked to removed state", p.state);
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ window.Markers = (function () {
|
|||
}
|
||||
|
||||
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 marker = {i, icon, type, x, y, cell};
|
||||
if (dx) marker.dx = dx;
|
||||
|
|
|
|||
|
|
@ -30,63 +30,59 @@ window.Religions = (function () {
|
|||
const base = {
|
||||
number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"],
|
||||
being: [
|
||||
"God",
|
||||
"Goddess",
|
||||
"Lord",
|
||||
"Lady",
|
||||
"Deity",
|
||||
"Creator",
|
||||
"Maker",
|
||||
"Overlord",
|
||||
"Ruler",
|
||||
"Chief",
|
||||
"Master",
|
||||
"Spirit",
|
||||
"Ancestor",
|
||||
"Ancient",
|
||||
"Brother",
|
||||
"Chief",
|
||||
"Council",
|
||||
"Creator",
|
||||
"Deity",
|
||||
"Elder",
|
||||
"Father",
|
||||
"Forebear",
|
||||
"Forefather",
|
||||
"Mother",
|
||||
"Brother",
|
||||
"Sister",
|
||||
"Elder",
|
||||
"Numen",
|
||||
"Ancient",
|
||||
"Virgin",
|
||||
"Giver",
|
||||
"Council",
|
||||
"God",
|
||||
"Goddess",
|
||||
"Guardian",
|
||||
"Reaper"
|
||||
"Lady",
|
||||
"Lord",
|
||||
"Maker",
|
||||
"Master",
|
||||
"Mother",
|
||||
"Numen",
|
||||
"Overlord",
|
||||
"Reaper",
|
||||
"Ruler",
|
||||
"Sister",
|
||||
"Spirit",
|
||||
"Virgin"
|
||||
],
|
||||
animal: [
|
||||
"Dragon",
|
||||
"Wyvern",
|
||||
"Phoenix",
|
||||
"Unicorn",
|
||||
"Sphinx",
|
||||
"Centaur",
|
||||
"Pegasus",
|
||||
"Kraken",
|
||||
"Basilisk",
|
||||
"Chimera",
|
||||
"Cyclope",
|
||||
"Antelope",
|
||||
"Ape",
|
||||
"Badger",
|
||||
"Basilisk",
|
||||
"Bear",
|
||||
"Beaver",
|
||||
"Bison",
|
||||
"Boar",
|
||||
"Buffalo",
|
||||
"Camel",
|
||||
"Cat",
|
||||
"Centaur",
|
||||
"Chimera",
|
||||
"Cobra",
|
||||
"Crane",
|
||||
"Crocodile",
|
||||
"Crow",
|
||||
"Cyclope",
|
||||
"Deer",
|
||||
"Dog",
|
||||
"Dragon",
|
||||
"Eagle",
|
||||
"Elk",
|
||||
"Falcon",
|
||||
"Fox",
|
||||
"Goat",
|
||||
"Goose",
|
||||
|
|
@ -94,10 +90,12 @@ window.Religions = (function () {
|
|||
"Hawk",
|
||||
"Heron",
|
||||
"Horse",
|
||||
"Hound",
|
||||
"Hyena",
|
||||
"Ibis",
|
||||
"Jackal",
|
||||
"Jaguar",
|
||||
"Kraken",
|
||||
"Lark",
|
||||
"Leopard",
|
||||
"Lion",
|
||||
|
|
@ -107,177 +105,179 @@ window.Religions = (function () {
|
|||
"Mule",
|
||||
"Narwhal",
|
||||
"Owl",
|
||||
"Ox",
|
||||
"Panther",
|
||||
"Pegasus",
|
||||
"Phoenix",
|
||||
"Rat",
|
||||
"Raven",
|
||||
"Rook",
|
||||
"Scorpion",
|
||||
"Serpent",
|
||||
"Shark",
|
||||
"Sheep",
|
||||
"Snake",
|
||||
"Sphinx",
|
||||
"Spider",
|
||||
"Swan",
|
||||
"Tiger",
|
||||
"Turtle",
|
||||
"Unicorn",
|
||||
"Viper",
|
||||
"Vulture",
|
||||
"Walrus",
|
||||
"Wolf",
|
||||
"Wolverine",
|
||||
"Worm",
|
||||
"Camel",
|
||||
"Falcon",
|
||||
"Hound",
|
||||
"Ox",
|
||||
"Serpent"
|
||||
"Wyvern"
|
||||
],
|
||||
adjective: [
|
||||
"New",
|
||||
"Good",
|
||||
"High",
|
||||
"Old",
|
||||
"Great",
|
||||
"Big",
|
||||
"Young",
|
||||
"Major",
|
||||
"Strong",
|
||||
"Happy",
|
||||
"Last",
|
||||
"Main",
|
||||
"Huge",
|
||||
"Far",
|
||||
"Beautiful",
|
||||
"Wild",
|
||||
"Fair",
|
||||
"Prime",
|
||||
"Crazy",
|
||||
"Ancient",
|
||||
"Proud",
|
||||
"Secret",
|
||||
"Lucky",
|
||||
"Sad",
|
||||
"Silent",
|
||||
"Latter",
|
||||
"Severe",
|
||||
"Fat",
|
||||
"Holy",
|
||||
"Pure",
|
||||
"Aggressive",
|
||||
"Honest",
|
||||
"Giant",
|
||||
"Mad",
|
||||
"Pregnant",
|
||||
"Distant",
|
||||
"Lost",
|
||||
"Broken",
|
||||
"Almighty",
|
||||
"Ancient",
|
||||
"Beautiful",
|
||||
"Benevolent",
|
||||
"Big",
|
||||
"Blind",
|
||||
"Friendly",
|
||||
"Unknown",
|
||||
"Sleeping",
|
||||
"Slumbering",
|
||||
"Loud",
|
||||
"Hungry",
|
||||
"Wise",
|
||||
"Worried",
|
||||
"Sacred",
|
||||
"Magical",
|
||||
"Superior",
|
||||
"Patient",
|
||||
"Blond",
|
||||
"Bloody",
|
||||
"Brave",
|
||||
"Broken",
|
||||
"Brutal",
|
||||
"Burning",
|
||||
"Calm",
|
||||
"Cheerful",
|
||||
"Crazy",
|
||||
"Cruel",
|
||||
"Dead",
|
||||
"Deadly",
|
||||
"Peaceful",
|
||||
"Grateful",
|
||||
"Frozen",
|
||||
"Evil",
|
||||
"Scary",
|
||||
"Burning",
|
||||
"Divine",
|
||||
"Bloody",
|
||||
"Dying",
|
||||
"Waking",
|
||||
"Brutal",
|
||||
"Unhappy",
|
||||
"Calm",
|
||||
"Cruel",
|
||||
"Favorable",
|
||||
"Blond",
|
||||
"Explicit",
|
||||
"Disturbing",
|
||||
"Devastating",
|
||||
"Brave",
|
||||
"Sunny",
|
||||
"Troubled",
|
||||
"Flying",
|
||||
"Sustainable",
|
||||
"Marine",
|
||||
"Fatal",
|
||||
"Inherent",
|
||||
"Selected",
|
||||
"Naval",
|
||||
"Cheerful",
|
||||
"Almighty",
|
||||
"Benevolent",
|
||||
"Distant",
|
||||
"Disturbing",
|
||||
"Divine",
|
||||
"Dying",
|
||||
"Eternal",
|
||||
"Evil",
|
||||
"Explicit",
|
||||
"Fair",
|
||||
"Far",
|
||||
"Fat",
|
||||
"Fatal",
|
||||
"Favorable",
|
||||
"Flying",
|
||||
"Friendly",
|
||||
"Frozen",
|
||||
"Giant",
|
||||
"Good",
|
||||
"Grateful",
|
||||
"Great",
|
||||
"Happy",
|
||||
"High",
|
||||
"Holy",
|
||||
"Honest",
|
||||
"Huge",
|
||||
"Hungry",
|
||||
"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: [
|
||||
"Day",
|
||||
"Life",
|
||||
"Death",
|
||||
"Night",
|
||||
"Home",
|
||||
"Fog",
|
||||
"Snow",
|
||||
"Winter",
|
||||
"Summer",
|
||||
"Cold",
|
||||
"Springs",
|
||||
"Gates",
|
||||
"Nature",
|
||||
"Thunder",
|
||||
"Lightning",
|
||||
"War",
|
||||
"Ice",
|
||||
"Frost",
|
||||
"Fire",
|
||||
"Day",
|
||||
"Death",
|
||||
"Doom",
|
||||
"Fate",
|
||||
"Pain",
|
||||
"Fire",
|
||||
"Fog",
|
||||
"Frost",
|
||||
"Gates",
|
||||
"Heaven",
|
||||
"Home",
|
||||
"Ice",
|
||||
"Justice",
|
||||
"Life",
|
||||
"Light",
|
||||
"Lightning",
|
||||
"Love",
|
||||
"Nature",
|
||||
"Night",
|
||||
"Pain",
|
||||
"Snow",
|
||||
"Springs",
|
||||
"Summer",
|
||||
"Thunder",
|
||||
"Time",
|
||||
"Victory"
|
||||
"Victory",
|
||||
"War",
|
||||
"Winter"
|
||||
],
|
||||
theGenitive: [
|
||||
"World",
|
||||
"Word",
|
||||
"South",
|
||||
"West",
|
||||
"North",
|
||||
"East",
|
||||
"Sun",
|
||||
"Moon",
|
||||
"Peak",
|
||||
"Fall",
|
||||
"Dawn",
|
||||
"Eclipse",
|
||||
"Abyss",
|
||||
"Blood",
|
||||
"Tree",
|
||||
"Dawn",
|
||||
"Earth",
|
||||
"East",
|
||||
"Eclipse",
|
||||
"Fall",
|
||||
"Harvest",
|
||||
"Moon",
|
||||
"North",
|
||||
"Peak",
|
||||
"Rainbow",
|
||||
"Sea",
|
||||
"Sky",
|
||||
"South",
|
||||
"Stars",
|
||||
"Storm",
|
||||
"Sun",
|
||||
"Tree",
|
||||
"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 = {
|
||||
|
|
@ -308,10 +308,10 @@ window.Religions = (function () {
|
|||
Monotheism: {Religion: 1, Church: 1},
|
||||
"Non-theism": {Beliefs: 3, Spirits: 1},
|
||||
|
||||
Cult: {Cult: 4, Sect: 4, Worship: 1, Orden: 1, Coterie: 1, Arcanum: 1},
|
||||
"Dark Cult": {Cult: 2, Sect: 2, Occultism: 1, Idols: 1, Coven: 1, Circle: 1, Blasphemy: 1},
|
||||
Cult: {Cult: 4, Sect: 4, Arcanum: 1, Coterie: 1, Order: 1, Worship: 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 () {
|
||||
|
|
|
|||
|
|
@ -23,10 +23,14 @@ window.Rivers = (function () {
|
|||
resolveDepressions(h);
|
||||
drainWater();
|
||||
defineRivers();
|
||||
|
||||
calculateConfluenceFlux();
|
||||
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");
|
||||
|
||||
|
|
@ -34,6 +38,8 @@ window.Rivers = (function () {
|
|||
const pixel2 = distanceScale * distanceScale
|
||||
//const MIN_FLUX_TO_FORM_RIVER = 10 * distanceScale;
|
||||
const MIN_FLUX_TO_FORM_RIVER = 30;
|
||||
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
|
||||
|
||||
const prec = grid.cells.prec;
|
||||
// const area = c => pack.cells.area[c] * pixel2;
|
||||
const area = pack.cells.area;
|
||||
|
|
@ -41,7 +47,7 @@ window.Rivers = (function () {
|
|||
const lakeOutCells = Lakes.setClimateData(h);
|
||||
|
||||
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
|
||||
const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : [];
|
||||
|
|
@ -93,6 +99,15 @@ window.Rivers = (function () {
|
|||
// cells is depressed
|
||||
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) {
|
||||
// flux is too small to operate as a river
|
||||
if (h[min] >= 20) cells.fl[min] += cells.fl[i];
|
||||
|
|
@ -152,6 +167,9 @@ window.Rivers = (function () {
|
|||
cells.conf = new Uint16Array(cells.i.length);
|
||||
pack.rivers = [];
|
||||
|
||||
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
|
||||
const mainStemWidthFactor = defaultWidthFactor * 1.2;
|
||||
|
||||
for (const key in riversData) {
|
||||
const riverCells = riversData[key];
|
||||
if (riverCells.length < 3) continue; // exclude tiny rivers
|
||||
|
|
@ -169,7 +187,7 @@ window.Rivers = (function () {
|
|||
const mouth = riverCells[riverCells.length - 2];
|
||||
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 discharge = cells.fl[mouth]; // m3 in second
|
||||
const length = getApproximateLength(meanderedPoints);
|
||||
|
|
@ -179,6 +197,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() {
|
||||
for (const i of cells.i) {
|
||||
if (!cells.conf[i]) continue;
|
||||
|
|
@ -347,14 +381,14 @@ window.Rivers = (function () {
|
|||
const LENGTH_PROGRESSION = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / LENGTH_FACTOR);
|
||||
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 lengthWidth = pointNumber * STEP_WIDTH + (LENGTH_PROGRESSION[pointNumber] || MAX_PROGRESSION);
|
||||
return widthFactor * (lengthWidth + fluxWidth) + startingWidth;
|
||||
};
|
||||
|
||||
// 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 riverPointsRight = [];
|
||||
|
||||
|
|
@ -447,5 +481,20 @@ window.Rivers = (function () {
|
|||
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
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ function editBurg(id) {
|
|||
burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature));
|
||||
document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed);
|
||||
document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed);
|
||||
document.getElementById("addCustomMFCGBurgLink").addEventListener("click", addCustomMfcgLink);
|
||||
|
||||
document.getElementById("burgStyleShow").addEventListener("click", showStyleSection);
|
||||
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection);
|
||||
|
|
@ -112,7 +113,13 @@ function editBurg(id) {
|
|||
if (options.showMFCGMap) {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "block";
|
||||
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 {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "none";
|
||||
}
|
||||
|
|
@ -347,22 +354,25 @@ function editBurg(id) {
|
|||
|
||||
function toggleFeature() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const b = pack.burgs[id];
|
||||
const burg = pack.burgs[id];
|
||||
const feature = this.dataset.feature;
|
||||
const turnOn = this.classList.contains("inactive");
|
||||
if (feature === "port") togglePort(id);
|
||||
else if (feature === "capital") toggleCapital(id);
|
||||
else b[feature] = +turnOn;
|
||||
if (b[feature]) this.classList.remove("inactive");
|
||||
else if (!b[feature]) this.classList.add("inactive");
|
||||
else burg[feature] = +turnOn;
|
||||
if (burg[feature]) this.classList.remove("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";
|
||||
updateMFCGFrame(burg);
|
||||
}
|
||||
|
||||
function toggleBurgLockButton() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
toggleBurgLock(id);
|
||||
const burg = pack.burgs[id];
|
||||
burg.lock = !burg.lock;
|
||||
|
||||
updateBurgLockIcon();
|
||||
}
|
||||
|
||||
|
|
@ -405,7 +415,7 @@ function editBurg(id) {
|
|||
|
||||
function updateMFCGFrame(burg) {
|
||||
const mfcgURL = getMFCGlink(burg);
|
||||
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL);
|
||||
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL + "&preview=1");
|
||||
document.getElementById("mfcgLink").setAttribute("href", mfcgURL);
|
||||
}
|
||||
|
||||
|
|
@ -426,6 +436,17 @@ function editBurg(id) {
|
|||
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() {
|
||||
const id = +elSelected.attr("data-id"),
|
||||
burg = pack.burgs[id];
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ function overviewBurgs() {
|
|||
|
||||
const body = document.getElementById("burgsBody");
|
||||
updateFilter();
|
||||
updateLockAllIcon();
|
||||
burgsOverviewAddLines();
|
||||
$("#burgsOverview").dialog();
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ function overviewBurgs() {
|
|||
document.getElementById("burgsListToLoad").addEventListener("change", function () {
|
||||
uploadFile(this, importBurgNames);
|
||||
});
|
||||
document.getElementById("burgsLockAll").addEventListener("click", toggleLockAll);
|
||||
document.getElementById("burgsRemoveAll").addEventListener("click", triggerAllBurgsRemove);
|
||||
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 province" class="burgState" value="${province}" disabled>
|
||||
<input data-tip="Burg state" class="burgState" value="${state}" disabled>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell culture use Cultures Editor)" class="stateCulture">${getCultureOptions(
|
||||
b.culture
|
||||
)}</select>
|
||||
<span data-tip="Burg population" class="icon-male"></span>
|
||||
|
|
@ -195,8 +197,11 @@ function overviewBurgs() {
|
|||
}
|
||||
|
||||
function toggleBurgLockStatus() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
toggleBurgLock(burg);
|
||||
const burgId = +this.parentNode.dataset.id;
|
||||
|
||||
const burg = pack.burgs[burgId];
|
||||
burg.lock = !burg.lock;
|
||||
|
||||
if (this.classList.contains("icon-lock")) {
|
||||
this.classList.remove("icon-lock");
|
||||
this.classList.add("icon-lock-open");
|
||||
|
|
@ -478,9 +483,9 @@ function overviewBurgs() {
|
|||
}
|
||||
|
||||
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`;
|
||||
alertMessage.innerHTML = message;
|
||||
|
||||
$("#alert").dialog({
|
||||
title: "Burgs bulk renaming",
|
||||
|
|
@ -562,4 +567,21 @@ function overviewBurgs() {
|
|||
pack.burgs = pack.burgs.map(burg => ({...burg, lock: !burg.lock}));
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
"use strict";
|
||||
function editDiplomacy() {
|
||||
if (customization) return;
|
||||
if (pack.states.filter(s => s.i && !s.removed).length < 2) {
|
||||
tip("There should be at least 2 states to edit the diplomacy", false, "error");
|
||||
return;
|
||||
}
|
||||
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");
|
||||
|
||||
const body = document.getElementById("diplomacyBodySection");
|
||||
|
||||
closeDialogs("#diplomacyEditor, .stable");
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
|
|
@ -14,21 +13,29 @@ function editDiplomacy() {
|
|||
if (layerIsOn("toggleBiomes")) toggleBiomes();
|
||||
if (layerIsOn("toggleReligions")) toggleReligions();
|
||||
|
||||
const body = document.getElementById("diplomacyBodySection");
|
||||
const statuses = ["Ally", "Friendly", "Neutral", "Suspicion", "Enemy", "Unknown", "Rival", "Vassal", "Suzerain"];
|
||||
const description = [" is an ally of ", " is friendly to ", " is neutral to ", " is suspicious of ",
|
||||
" is at war with ", " does not know about ", " is a rival of ", " is a vassal of ", " is suzerain to "];
|
||||
const colors = ["#00b300", "#d4f8aa", "#edeee8", "#eeafaa", "#e64b40", "#a9a9a9", "#ad5a1f", "#87CEFA", "#00008B"];
|
||||
refreshDiplomacyEditor();
|
||||
const relations = {
|
||||
Ally: {inText: "is an ally of", color: "#00b300", tip: "Allies formed a defensive pact and protect each other in case of third party aggression"},
|
||||
Friendly: {inText: "is friendly to", color: "#d4f8aa", tip: "State is friendly to anouther state when they share some common interests"},
|
||||
Neutral: {inText: "is neutral to", color: "#edeee8", tip: "Neutral means states relations are neither positive nor negative"},
|
||||
Suspicion: {inText: "is suspicious of", color: "#eeafaa", tip: "Suspicion means state has a cautious distrust of another state"},
|
||||
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);
|
||||
|
||||
if (modules.editDiplomacy) return;
|
||||
modules.editDiplomacy = true;
|
||||
|
||||
$("#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"}
|
||||
});
|
||||
|
||||
|
|
@ -36,10 +43,30 @@ function editDiplomacy() {
|
|||
document.getElementById("diplomacyEditorRefresh").addEventListener("click", refreshDiplomacyEditor);
|
||||
document.getElementById("diplomacyEditStyle").addEventListener("click", () => editStyle("regions"));
|
||||
document.getElementById("diplomacyRegenerate").addEventListener("click", regenerateRelations);
|
||||
document.getElementById("diplomacyMatrix").addEventListener("click", showRelationsMatrix);
|
||||
document.getElementById("diplomacyReset").addEventListener("click", resetRelations);
|
||||
document.getElementById("diplomacyShowMatrix").addEventListener("click", showRelationsMatrix);
|
||||
document.getElementById("diplomacyHistory").addEventListener("click", showRelationsHistory);
|
||||
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() {
|
||||
diplomacyEditorAddLines();
|
||||
|
|
@ -50,33 +77,36 @@ function editDiplomacy() {
|
|||
function diplomacyEditorAddLines() {
|
||||
const states = pack.states;
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const sel = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
|
||||
const selName = states[sel].fullName;
|
||||
diplomacySelect.style.display = "none";
|
||||
const selectedId = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
|
||||
const selectedName = states[selectedId].name;
|
||||
|
||||
COArenderer.trigger("stateCOA"+sel, states[sel].coa);
|
||||
let lines = `<div class="states Self" data-id=${sel} data-tip="List below shows relations to ${selName}">
|
||||
<div style="width: max-content">${selName}</div>
|
||||
<svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${sel}"></use></svg>
|
||||
COArenderer.trigger("stateCOA" + selectedId, states[selectedId].coa);
|
||||
let lines = `<div class="states Self" data-id=${selectedId} data-tip="List below shows relations to ${selectedName}">
|
||||
<div style="width: max-content">${states[selectedId].fullName}</div>
|
||||
<svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${selectedId}"></use></svg>
|
||||
</div>`;
|
||||
|
||||
for (const s of states) {
|
||||
if (!s.i || s.removed || s.i === sel) continue;
|
||||
const relation = s.diplomacy[sel];
|
||||
const index = statuses.indexOf(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);
|
||||
for (const state of states) {
|
||||
if (!state.i || state.removed || state.i === selectedId) continue;
|
||||
const relation = state.diplomacy[selectedId];
|
||||
const {color, inText} = relations[relation];
|
||||
|
||||
lines += `<div class="states" data-id=${s.i} data-name="${s.fullName}" data-relations="${relation}">
|
||||
<svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg>
|
||||
<div data-tip="${tipSelect}" style="width:12em">${s.fullName}</div>
|
||||
<svg data-tip="${tipChange}" width=".9em" height=".9em" style="margin-bottom:-1px" class="changeRelations">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect pointer" style="pointer-events: none"></rect>
|
||||
</svg>
|
||||
<input data-tip="${tipChange}" class="changeRelations diplomacyRelations" value="${relation}" readonly/>
|
||||
const tip = `${state.name} ${inText} ${selectedName}`;
|
||||
const tipSelect = `${tip}. Click to see relations to ${state.name}`;
|
||||
const tipChange = `Click to change relations. ${tip}`;
|
||||
|
||||
const name = state.fullName.length < 23 ? state.fullName : state.name;
|
||||
COArenderer.trigger("stateCOA" + state.i, state.coa);
|
||||
|
||||
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 pointer" style="width: 6em">
|
||||
<svg width=".9em" height=".9em" style="margin-bottom:-1px">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect"></rect>
|
||||
</svg>
|
||||
${relation}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
body.innerHTML = lines;
|
||||
|
|
@ -84,8 +114,6 @@ function editDiplomacy() {
|
|||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectStateOnLineClick));
|
||||
body.querySelectorAll(".changeRelations").forEach(el => el.addEventListener("click", toggleDiplomacySelect));
|
||||
|
||||
applySorting(diplomacyHeader);
|
||||
$("#diplomacyEditor").dialog();
|
||||
|
|
@ -95,19 +123,31 @@ function editDiplomacy() {
|
|||
if (!layerIsOn("toggleStates")) return;
|
||||
const state = +event.target.dataset.id;
|
||||
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)
|
||||
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
|
||||
const path = debug
|
||||
.append("path")
|
||||
.attr("class", "highlight")
|
||||
.attr("d", d)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)");
|
||||
|
||||
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);
|
||||
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) {
|
||||
debug.selectAll(".highlight").each(function() {
|
||||
debug.selectAll(".highlight").each(function () {
|
||||
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
|
||||
});
|
||||
}
|
||||
|
|
@ -118,22 +158,17 @@ function editDiplomacy() {
|
|||
if (!sel) return;
|
||||
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
|
||||
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() {
|
||||
if (this.classList.contains("Self")) return;
|
||||
body.querySelector("div.Self").classList.remove("Self");
|
||||
this.classList.add("Self");
|
||||
refreshDiplomacyEditor();
|
||||
const relation = pack.states[id].diplomacy[sel];
|
||||
const color = relations[relation]?.color || "#4682b4";
|
||||
|
||||
this.setAttribute("fill", color);
|
||||
statesBody.select("#state-gap" + id).attr("stroke", color);
|
||||
statesHalo.select("#state-border" + id).attr("stroke", d3.color(color).darker().hex());
|
||||
});
|
||||
}
|
||||
|
||||
function selectStateOnMapClick() {
|
||||
|
|
@ -145,42 +180,63 @@ function editDiplomacy() {
|
|||
if (+selectedLine.dataset.id === state) return;
|
||||
|
||||
selectedLine.classList.remove("Self");
|
||||
body.querySelector("div[data-id='"+state+"']").classList.add("Self");
|
||||
body.querySelector("div[data-id='" + state + "']").classList.add("Self");
|
||||
refreshDiplomacyEditor();
|
||||
}
|
||||
|
||||
function toggleDiplomacySelect(event) {
|
||||
event.stopPropagation();
|
||||
const select = document.getElementById("diplomacySelect");
|
||||
const show = select.style.display === "none";
|
||||
if (!show) {select.style.display = "none"; return;}
|
||||
select.style.display = "block";
|
||||
const input = event.target.closest("div").querySelector("input");
|
||||
select.style.left = input.getBoundingClientRect().left + "px";
|
||||
select.style.top = input.getBoundingClientRect().bottom + "px";
|
||||
body.dataset.state = event.target.closest("div.states").dataset.id;
|
||||
function selectRelation(subjectId, objectId, currentRelation) {
|
||||
const states = pack.states;
|
||||
|
||||
const subject = states[subjectId];
|
||||
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>`;
|
||||
|
||||
const options = Object.entries(relations)
|
||||
.map(
|
||||
([relation, {color, inText, tip}]) =>
|
||||
`<div style="margin-block: 0.2em" data-tip="${tip}"><label class="pointer">
|
||||
<input type="radio" name="relationSelect" value="${relation}" ${currentRelation === relation && "checked"} >
|
||||
<svg width=".9em" height=".9em" style="margin-bottom:-1px">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect" />
|
||||
</svg>
|
||||
${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) {
|
||||
event.stopPropagation();
|
||||
diplomacySelect.style.display = "none";
|
||||
const subject = +body.dataset.state;
|
||||
const rel = event.target.innerHTML;
|
||||
function changeRelation(subjectId, objectId, oldRelation, newRelation) {
|
||||
if (newRelation === oldRelation) return;
|
||||
const states = pack.states;
|
||||
const chronicle = states[0].diplomacy;
|
||||
|
||||
const states = pack.states, chronicle = states[0].diplomacy;
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
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 subjectName = states[subjectId].name;
|
||||
const objectName = states[objectId].name;
|
||||
|
||||
const oldRel = states[subject].diplomacy[object];
|
||||
if (rel === oldRel) return;
|
||||
states[subject].diplomacy[object] = rel;
|
||||
states[object].diplomacy[subject] = rel === "Vassal" ? "Suzerain" : rel === "Suzerain" ? "Vassal" : rel;
|
||||
states[subjectId].diplomacy[objectId] = newRelation;
|
||||
states[objectId].diplomacy[subjectId] = newRelation === "Vassal" ? "Suzerain" : newRelation === "Suzerain" ? "Vassal" : newRelation;
|
||||
|
||||
// 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 vassal = () => [`Vassalization`, `${subjectName} became a vassal of ${objectName}`];
|
||||
const suzerain = () => [`Vassalization`, `${subjectName} vassalized ${objectName}`];
|
||||
|
|
@ -189,24 +245,33 @@ function editDiplomacy() {
|
|||
const war = () => [`War declaration`, `${subjectName} declared a war on its enemy ${objectName}`];
|
||||
const peace = () => {
|
||||
const treaty = `${subjectName} and ${objectName} agreed to cease fire and signed a peace treaty`;
|
||||
const changed = rel === "Ally" ? ally()
|
||||
: rel === "Vassal" ? vassal()
|
||||
: rel === "Suzerain" ? suzerain()
|
||||
: rel === "Unknown" ? unknown()
|
||||
: change();
|
||||
const changed =
|
||||
newRelation === "Ally"
|
||||
? ally()
|
||||
: newRelation === "Vassal"
|
||||
? vassal()
|
||||
: newRelation === "Suzerain"
|
||||
? suzerain()
|
||||
: newRelation === "Unknown"
|
||||
? unknown()
|
||||
: change();
|
||||
return [`War termination`, treaty, changed[1]];
|
||||
}
|
||||
};
|
||||
|
||||
if (oldRel === "Enemy") chronicle.push(peace()); else
|
||||
if (rel === "Enemy") chronicle.push(war()); else
|
||||
if (rel === "Vassal") chronicle.push(vassal()); else
|
||||
if (rel === "Suzerain") chronicle.push(suzerain()); else
|
||||
if (rel === "Ally") chronicle.push(ally()); else
|
||||
if (rel === "Unknown") chronicle.push(unknown()); else
|
||||
if (rel === "Rival") chronicle.push(rival()); else
|
||||
chronicle.push(change());
|
||||
if (oldRelation === "Enemy") chronicle.push(peace());
|
||||
else if (newRelation === "Enemy") chronicle.push(war());
|
||||
else if (newRelation === "Vassal") chronicle.push(vassal());
|
||||
else if (newRelation === "Suzerain") chronicle.push(suzerain());
|
||||
else if (newRelation === "Ally") chronicle.push(ally());
|
||||
else if (newRelation === "Unknown") chronicle.push(unknown());
|
||||
else if (newRelation === "Rival") chronicle.push(rival());
|
||||
else chronicle.push(change());
|
||||
|
||||
refreshDiplomacyEditor();
|
||||
if (diplomacyMatrix.offsetParent) {
|
||||
document.getElementById("diplomacyMatrixBody").innerHTML = "";
|
||||
showRelationsMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
function regenerateRelations() {
|
||||
|
|
@ -214,28 +279,52 @@ function editDiplomacy() {
|
|||
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() {
|
||||
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">`;
|
||||
chronicle.forEach((e, d) => {
|
||||
chronicle.forEach((entry, d) => {
|
||||
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 += `‍</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));
|
||||
|
||||
$("#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: {
|
||||
Save: function() {
|
||||
Save: function () {
|
||||
const data = this.querySelector("div").innerText.split("\n").join("\r\n");
|
||||
const name = getFileName("Relations history") + ".txt";
|
||||
downloadFile(data, name);
|
||||
},
|
||||
Clear: function() {pack.states[0].diplomacy = []; $(this).dialog("close");},
|
||||
Close: function() {$(this).dialog("close");}
|
||||
Clear: function () {
|
||||
pack.states[0].diplomacy = [];
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Close: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -251,22 +340,48 @@ function editDiplomacy() {
|
|||
|
||||
function showRelationsMatrix() {
|
||||
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='‍'></th>`;
|
||||
message += states.map(s => `<th data-tip='See relations to ${s.fullName}'>${s.name}</th>`).join("") + `</tr>`; // headers
|
||||
states.forEach(s => {
|
||||
message += `<tr><th data-tip='See relations of ${s.fullName}'>${s.name}</th>` + s.diplomacy
|
||||
.filter((v, i) => valid.includes(i)).map((r, i) => {
|
||||
const desc = description[statuses.indexOf(r)];
|
||||
const tip = desc ? s.fullName + desc + pack.states[valid[i]].fullName : '‍';
|
||||
return `<td data-tip='${tip}' class='${r}'>${r}</td>`
|
||||
}).join("") + "</tr>";
|
||||
let table = `<table><thead><tr><th data-tip='‍'></th>`;
|
||||
table += states.map(state => `<th data-tip='Relations to ${state.fullName}'>${state.name}</th>`).join("") + `</tr>`;
|
||||
table += `<tbody>`;
|
||||
|
||||
states.forEach(state => {
|
||||
table +=
|
||||
`<tr data-id=${state.i}><th data-tip='Relations of ${state.fullName}'>${state.name}</th>` +
|
||||
state.diplomacy
|
||||
.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() {
|
||||
|
|
@ -288,7 +403,8 @@ function editDiplomacy() {
|
|||
clearMainTip();
|
||||
const selected = body.querySelector("div.Self");
|
||||
if (selected) selected.classList.remove("Self");
|
||||
if (layerIsOn("toggleStates")) drawStates(); else toggleStates();
|
||||
if (layerIsOn("toggleStates")) drawStates();
|
||||
else toggleStates();
|
||||
debug.selectAll(".highlight").remove();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,41 +265,48 @@ function getBurgSeed(burg) {
|
|||
}
|
||||
|
||||
function getMFCGlink(burg) {
|
||||
if (burg.link) return burg.link;
|
||||
|
||||
const {cells} = pack;
|
||||
const {name, population, cell} = burg;
|
||||
const burgSeed = getBurgSeed(burg);
|
||||
const sizeRaw = 2.13 * Math.pow((population * populationRate) / urbanDensity, 0.385);
|
||||
const {i, name, population: burgPopulation, cell} = burg;
|
||||
const seed = getBurgSeed(burg);
|
||||
|
||||
const sizeRaw = 2.13 * Math.pow((burgPopulation * populationRate) / urbanDensity, 0.385);
|
||||
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 river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
const shantytown = +burg.shanty;
|
||||
|
||||
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : "";
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return "&sea=" + norm;
|
||||
return rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
}
|
||||
|
||||
const baseURL = "https://watabou.github.io/city-generator/?random=0&continuous=0";
|
||||
const url = `${baseURL}&name=${name}&population=${people}&size=${size}&seed=${burgSeed}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
return url;
|
||||
}
|
||||
const parameters = {name, population, size, seed, river, coast, farms, citadel, urban_castle, hub, plaza, temple, walls, shantytown, gates: -1};
|
||||
const url = new URL("https://watabou.github.io/city-generator");
|
||||
url.search = new URLSearchParams(parameters);
|
||||
if (sea) url.searchParams.append("sea", sea);
|
||||
|
||||
function toggleBurgLock(burg) {
|
||||
const b = pack.burgs[burg];
|
||||
b.lock = b.lock ? 0 : 1;
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
// draw legend box
|
||||
|
|
|
|||
|
|
@ -460,32 +460,31 @@ function showInfo() {
|
|||
const Discord = link("https://discordapp.com/invite/X7E84HU", "Discord");
|
||||
const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit");
|
||||
const Patreon = link("https://www.patreon.com/azgaar", "Patreon");
|
||||
const Trello = link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Trello");
|
||||
const 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 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 = `
|
||||
<b>Fantasy Map Generator</b> (FMG) is an open-source application, it means the code is published an anyone can use it.
|
||||
In case of FMG is also means that you own all created maps and can use them as you wish, you can even sell them.
|
||||
<b>Fantasy Map Generator</b> (FMG) is a free open-source application.
|
||||
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>
|
||||
|
||||
<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">
|
||||
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator", "GitHub repository")}</li>
|
||||
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE", "License")}</li>
|
||||
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "Changelog")}</li>
|
||||
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys", "Hotkeys")}</li>
|
||||
<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>`;
|
||||
|
||||
$("#alert").dialog({
|
||||
|
|
|
|||
|
|
@ -470,23 +470,26 @@ function togglePrec(event) {
|
|||
|
||||
function drawPrec() {
|
||||
prec.selectAll("circle").remove();
|
||||
const cells = grid.cells,
|
||||
p = grid.points;
|
||||
const {cells, points} = grid;
|
||||
|
||||
prec.style("display", "block");
|
||||
const show = d3.transition().duration(800).ease(d3.easeSinIn);
|
||||
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 getRadius = prec => rn(Math.sqrt(prec / 4) / cellsNumberModifier, 2);
|
||||
|
||||
prec
|
||||
.selectAll("circle")
|
||||
.data(data)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("cx", d => p[d][0])
|
||||
.attr("cy", d => p[d][1])
|
||||
.attr("cx", d => points[d][0])
|
||||
.attr("cy", d => points[d][1])
|
||||
.attr("r", 0)
|
||||
.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) {
|
||||
|
|
|
|||
|
|
@ -17,18 +17,24 @@ function editNamesbase() {
|
|||
document.getElementById("namesbaseMax").addEventListener("input", updateBaseMax);
|
||||
document.getElementById("namesbaseDouble").addEventListener("input", updateBaseDublication);
|
||||
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("namesbaseDownload").addEventListener("click", namesbaseDownload);
|
||||
document.getElementById("namesbaseUpload").addEventListener("click", () => namesbaseToLoad.click());
|
||||
document.getElementById("namesbaseToLoad").addEventListener("change", function() {uploadFile(this, namesbaseUpload)});
|
||||
document.getElementById("namesbaseUpload").addEventListener("click", () => document.getElementById("namesbaseToLoad").click());
|
||||
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));
|
||||
|
||||
createBasesList();
|
||||
updateInputs();
|
||||
|
||||
$("#namesbaseEditor").dialog({
|
||||
title: "Namesbase Editor", width: "42.5em",
|
||||
title: "Namesbase Editor",
|
||||
width: "auto",
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
});
|
||||
|
||||
|
|
@ -40,7 +46,10 @@ function editNamesbase() {
|
|||
|
||||
function updateInputs() {
|
||||
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("namesbaseName").value = nameBases[base].name;
|
||||
document.getElementById("namesbaseMin").value = nameBases[base].min;
|
||||
|
|
@ -52,7 +61,7 @@ function editNamesbase() {
|
|||
function updateExamples() {
|
||||
const base = +document.getElementById("namesbaseSelect").value;
|
||||
let examples = "";
|
||||
for (let i=0; i < 10; i++) {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const example = Names.getBase(base);
|
||||
if (example === undefined) {
|
||||
examples = "Cannot generate examples. Please verify the data";
|
||||
|
|
@ -84,13 +93,19 @@ function editNamesbase() {
|
|||
|
||||
function updateBaseMin() {
|
||||
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;
|
||||
}
|
||||
|
||||
function updateBaseMax() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -99,59 +114,70 @@ function editNamesbase() {
|
|||
nameBases[base].d = this.value;
|
||||
}
|
||||
|
||||
function analizeNamesbase() {
|
||||
const string = document.getElementById("namesbaseTextarea").value;
|
||||
if (!string) {tip("Names data field should not be empty", false, "error"); return;}
|
||||
const base = string.toLowerCase();
|
||||
const array = base.split(",");
|
||||
const l = array.length;
|
||||
if (!l) {tip("Names data should not be empty", false, "error"); return;}
|
||||
function analyzeNamesbase() {
|
||||
const namesSourceString = document.getElementById("namesbaseTextarea").value;
|
||||
const namesArray = namesSourceString.toLowerCase().split(",");
|
||||
const length = namesArray.length;
|
||||
if (!namesSourceString || !length) return tip("Names data should not be empty", false, "error");
|
||||
|
||||
const wordsLength = array.map(n => n.length);
|
||||
const multi = rn(d3.mean(array.map(n => (n.match(/ /i)||[]).length)) * 100, 2);
|
||||
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 chain = Names.calculateChain(namesSourceString);
|
||||
const variety = rn(d3.mean(Object.values(chain).map(keyValue => keyValue.length)));
|
||||
|
||||
const lengthStat =
|
||||
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 wordsLength = namesArray.map(n => n.length);
|
||||
|
||||
const rangeStat =
|
||||
l < 10 ? "<span style='color:red'>[low]</span>" :
|
||||
l < 15 ? "<span style='color:darkred'>[low]</span>" :
|
||||
l < 20 ? "<span style='color:orange'>[low]</span>" :
|
||||
"<span style='color:green'>[good]</span>";
|
||||
const nonLatin = namesSourceString.match(/[^\u0000-\u007f]/g);
|
||||
const nonBasicLatinChars = nonLatin
|
||||
? unique(
|
||||
namesSourceString
|
||||
.match(/[^\u0000-\u007f]/g)
|
||||
.join("")
|
||||
.toLowerCase()
|
||||
).join("")
|
||||
: "none";
|
||||
|
||||
const depthStat =
|
||||
l < 15 ? "<span style='color:red'>[low]</span>" :
|
||||
l < 20 ? "<span style='color:darkred'>[low]</span>" :
|
||||
l < 25 ? "<span style='color:orange'>[low]</span>" :
|
||||
"<span style='color:green'>[good]</span>";
|
||||
const geminate = namesArray.map(name => name.match(/[^\w\s]|(.)(?=\1)/g) || []).flat();
|
||||
const doubled = unique(geminate).filter(char => geminate.filter(doudledChar => doudledChar === char).length > 3) || ["none"];
|
||||
|
||||
const duplicates = unique(namesArray.filter((e, i, a) => a.indexOf(e) !== i)).join(", ") || "none";
|
||||
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">
|
||||
<div>Namesbase length: ${l} ${lengthStat}</div>
|
||||
<div>Namesbase range: ${Object.keys(chain).length-1} ${rangeStat}</div>
|
||||
<div>Namesbase depth: ${depth} ${depthStat}</div>
|
||||
<div>Non-basic chars: ${nonLatin}</div>
|
||||
<div data-tip="Number of names provided">Namesbase length: ${length} ${getLengthQuality()}</div>
|
||||
<div data-tip="Average number of generation variants for each key in the chain">Namesbase variety: ${variety} ${getVarietyLevel()}</div>
|
||||
<hr>
|
||||
<div>Min name length: ${d3.min(wordsLength)}</div>
|
||||
<div>Max name length: ${d3.max(wordsLength)}</div>
|
||||
<div>Mean name length: ${rn(d3.mean(wordsLength), 1)}</div>
|
||||
<div>Median name length: ${d3.median(wordsLength)}</div>
|
||||
<div>Doubled chars: ${doubled}</div>
|
||||
<div>Multi-word names: ${multi}%</div>
|
||||
<div data-tip="The shortest name length">Min name length: ${d3.min(wordsLength)}</div>
|
||||
<div data-tip="The longest name length">Max name length: ${d3.max(wordsLength)}</div>
|
||||
<div data-tip="Average name length">Mean name length: ${rn(d3.mean(wordsLength), 1)}</div>
|
||||
<div data-tip="Common name length">Median name length: ${d3.median(wordsLength)}</div>
|
||||
<hr>
|
||||
<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>`;
|
||||
|
||||
$("#alert").dialog({
|
||||
resizable: false, title: "Data Analysis",
|
||||
resizable: false,
|
||||
title: "Data Analysis",
|
||||
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() {
|
||||
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: {
|
||||
Restore: function() {
|
||||
Restore: function () {
|
||||
$(this).dialog("close");
|
||||
Names.clearChains();
|
||||
nameBases = Names.getNameBases();
|
||||
createBasesList();
|
||||
updateInputs();
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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";
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function namesbaseUpload(dataLoaded) {
|
||||
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();
|
||||
nameBases = [];
|
||||
data.forEach(d => {
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -102,7 +102,8 @@ 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,
|
||||
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,
|
||||
Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya`;
|
||||
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`;
|
||||
|
||||
const array = supporters
|
||||
.replace(/(?:\r\n|\r|\n)/g, "")
|
||||
|
|
@ -287,18 +288,16 @@ function generateMapWithSeed(source) {
|
|||
}
|
||||
|
||||
function showSeedHistoryDialog() {
|
||||
const alert = mapHistory
|
||||
.map(function (h, i) {
|
||||
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>`;
|
||||
return `<div>${i + 1}. Seed: ${h.seed} ${button}. Size: ${h.width}x${h.height}. Template: ${h.template}. Created: ${created}</div>`;
|
||||
})
|
||||
.join("");
|
||||
alertMessage.innerHTML = alert;
|
||||
const lines = mapHistory.map((h, i) => {
|
||||
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>`;
|
||||
return `<li>Seed: ${h.seed} ${button}. Size: ${h.width}x${h.height}. Template: ${h.template}. Created: ${created}</li>`;
|
||||
});
|
||||
alertMessage.innerHTML = `<ol style="margin: 0; padding-left: 1.5em">${lines.join("")}</ol>`;
|
||||
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Seed history",
|
||||
width: fitContent(),
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ function editProvinces() {
|
|||
p.color
|
||||
}" class="fillRect pointer"></svg>
|
||||
<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>
|
||||
<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 ${
|
||||
|
|
|
|||
|
|
@ -89,7 +89,8 @@ function createRiver() {
|
|||
const source = riverCells[0];
|
||||
const mouth = parent === riverId ? last(riverCells) : riverCells[riverCells.length - 2];
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ function editStates() {
|
|||
s.color
|
||||
}" class="fillRect pointer"></svg>
|
||||
<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>
|
||||
<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"/>
|
||||
|
|
|
|||
|
|
@ -831,7 +831,7 @@ function applyDefaultStyle() {
|
|||
landmass.attr("opacity", 1).attr("fill", "#eef6fb").attr("filter", null);
|
||||
markers.attr("opacity", null).attr("rescale", 1).attr("filter", "url(#dropShadow01)");
|
||||
|
||||
prec.attr("opacity", null).attr("stroke", "#000000").attr("stroke-width", 0.1).attr("fill", "#003dff").attr("filter", null);
|
||||
prec.attr("opacity", null).attr("stroke", "#000000").attr("stroke-width", 0).attr("fill", "#003dff").attr("filter", null);
|
||||
population.attr("opacity", null).attr("stroke-width", 1.6).attr("stroke-dasharray", null).attr("stroke-linecap", "butt").attr("filter", null);
|
||||
population.select("#rural").attr("stroke", "#0000ff");
|
||||
population.select("#urban").attr("stroke", "#ff0000");
|
||||
|
|
@ -938,7 +938,7 @@ function applyDefaultStyle() {
|
|||
.attr("stroke-linecap", "round");
|
||||
legend.select("#legendBox").attr("fill", "#ffffff").attr("fill-opacity", 0.8);
|
||||
|
||||
const citiesSize = Math.max(rn(8 - regionsInput.value / 20), 3);
|
||||
const citiesSize = Math.max(rn(8 - regionsOutput.value / 20), 3);
|
||||
burgLabels
|
||||
.select("#cities")
|
||||
.attr("fill", "#3e3e4b")
|
||||
|
|
@ -979,7 +979,7 @@ function applyDefaultStyle() {
|
|||
.attr("stroke-linecap", "butt");
|
||||
anchors.select("#towns").attr("opacity", 1).attr("fill", "#ffffff").attr("stroke", "#3e3e4b").attr("stroke-width", 1.2).attr("size", 1);
|
||||
|
||||
const stateLabelSize = Math.max(rn(24 - regionsInput.value / 6), 6);
|
||||
const stateLabelSize = Math.max(rn(24 - regionsOutput.value / 6), 6);
|
||||
labels
|
||||
.select("#states")
|
||||
.attr("fill", "#3e3e4b")
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ function regenerateStates() {
|
|||
const localSeed = Math.floor(Math.random() * 1e9); // new random seed
|
||||
Math.random = aleaPRNG(localSeed);
|
||||
|
||||
const statesCount = +regionsInput.value;
|
||||
const statesCount = +regionsOutput.value;
|
||||
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 < 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 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 discharge = cells.fl[mouth]; // m3 in second
|
||||
|
|
@ -739,7 +741,7 @@ function configMarkersGeneration() {
|
|||
const inputId = `markerIconInput${index}`;
|
||||
return `<tr>
|
||||
<td><input value="${type}" /></td>
|
||||
<td>
|
||||
<td style="position: relative">
|
||||
<input id="${inputId}" style="width: 5em" value="${icon}" />
|
||||
<i class="icon-edit pointer" style="position: absolute; margin:.4em 0 0 -1.4em; font-size:.85em"></i>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -154,18 +154,23 @@ void (function () {
|
|||
const prompt = document.getElementById("prompt");
|
||||
const form = prompt.querySelector("#promptForm");
|
||||
|
||||
window.prompt = function (promptText = "Please provide an input", options = {default: 1, step: 0.01, min: 0, max: 100}, callback) {
|
||||
if (options.default === undefined) {
|
||||
ERROR && console.error("Prompt: options object does not have default value defined");
|
||||
return;
|
||||
}
|
||||
const defaultText = "Please provide an input";
|
||||
const defaultOptions = {default: 1, step: 0.01, min: 0, max: 100, required: true};
|
||||
|
||||
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");
|
||||
prompt.querySelector("#promptText").innerHTML = promptText;
|
||||
|
||||
const type = typeof options.default === "number" ? "number" : "text";
|
||||
input.type = type;
|
||||
|
||||
if (options.step !== undefined) input.step = options.step;
|
||||
if (options.min !== undefined) input.min = options.min;
|
||||
if (options.max !== undefined) input.max = options.max;
|
||||
|
||||
input.required = options.required === false ? false : true;
|
||||
input.placeholder = "type a " + type;
|
||||
input.value = options.default;
|
||||
prompt.style.display = "block";
|
||||
|
|
@ -173,9 +178,9 @@ void (function () {
|
|||
form.addEventListener(
|
||||
"submit",
|
||||
event => {
|
||||
event.preventDefault();
|
||||
prompt.style.display = "none";
|
||||
const v = type === "number" ? +input.value : input.value;
|
||||
event.preventDefault();
|
||||
if (callback) callback(v);
|
||||
},
|
||||
{once: true}
|
||||
|
|
@ -183,7 +188,9 @@ void (function () {
|
|||
};
|
||||
|
||||
const cancel = prompt.querySelector("#promptCancel");
|
||||
cancel.addEventListener("click", () => (prompt.style.display = "none"));
|
||||
cancel.addEventListener("click", () => {
|
||||
prompt.style.display = "none";
|
||||
});
|
||||
})();
|
||||
|
||||
// indexedDB; ldb object
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue