mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
v1.4.05
This commit is contained in:
parent
8152ccbe9c
commit
5304306044
8 changed files with 300 additions and 26 deletions
10
icons.css
10
icons.css
|
|
@ -264,4 +264,12 @@
|
|||
width: .6em;
|
||||
font-family: monospace;
|
||||
}
|
||||
.icon-die:before {content:'🎲';}
|
||||
.icon-die:before {content:'🎲';}
|
||||
|
||||
.icon-button-plus:before {content:'➕'; padding-right: .4em;}
|
||||
.icon-button-die:before {content:'🎲'; padding-right: .4em;}
|
||||
.icon-button-power:before {content:'💪'; padding-right: .6em;}
|
||||
.icon-button-skirmish:before {content:'🎯'; padding-right: .4em;}
|
||||
.icon-button-melee:before {content:'🗡'; padding-right: .4em;}
|
||||
.icon-button-pursue:before {content:'🐎'; padding-right: .4em;}
|
||||
.icon-button-retreat:before {content:'🏳️'; padding-right: .4em;}
|
||||
|
|
|
|||
19
index.css
19
index.css
|
|
@ -942,6 +942,25 @@ body button.noicon {
|
|||
stroke: #2c0808;
|
||||
}
|
||||
|
||||
#battleBody > table {
|
||||
padding: .2em .6em .2em .6em;
|
||||
border: 1px solid #ccc;
|
||||
margin: 0 0 .4em 0;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
max-height: 34vh;
|
||||
}
|
||||
|
||||
#battleBody > table .regiment {
|
||||
width: 13em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr.battleCasualties, tr.battleSurvivors {
|
||||
font-style: italic;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
.drag-trigger {
|
||||
border-left: 1em solid transparent;
|
||||
border-right: 1em solid #000;
|
||||
|
|
|
|||
30
index.html
30
index.html
|
|
@ -2445,15 +2445,30 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="battleScreen" class="dialog" style="display: none">
|
||||
<div id="battleScreen" class="dialog stable" style="display: none">
|
||||
<div id="battleBody">
|
||||
<div style="font-size:1.2em; font-weight: bold"><div style="width: 1.2em; display: inline-block">⚔</div><span>Attackers</span></div>
|
||||
<table id="battleAttackers" style="padding: .2em .6em .2em .6em; border: 1px solid #ccc; margin: .0 0 .4em 0"><thead><tr></tr></thead><tbody></tbody></table>
|
||||
<div style="font-size:1.2em; font-weight: bold"><div style="width: 1.2em; display: inline-block">🛡</div><span>Defenders</span></div>
|
||||
<table id="battleDefenders" style="padding: .2em .6em .2em .6em; border: 1px solid #ccc; margin: .0 0 .4em 0"><thead><tr></tr></thead><tbody></tbody></table>
|
||||
<div style="font-size:1.2em; font-weight: bold">
|
||||
<div style="width: 1.2em; display: inline-block">⚔</div><span>Attackers</span>
|
||||
<div style="float: right; font-size: .7em">
|
||||
<span data-tip="Attackers power considering phase and randon factor" style="padding: .2em" class="icon-button-power">43</span>
|
||||
<button data-tip="Battle phase. Click to change" class="icon-button-pursue"></button>
|
||||
<button data-tip="Random factor for attackers. Click to re-roll" style="padding: .1em .2em" class="icon-button-die">12</button>
|
||||
</div>
|
||||
</div>
|
||||
<table id="battleAttackers"><thead><tr></tr></thead><tbody></tbody></table>
|
||||
<div style="font-size:1.2em; font-weight: bold">
|
||||
<div style="width: 1.2em; display: inline-block">🛡</div><span>Defenders</span>
|
||||
<div style="float: right; font-size: .7em">
|
||||
<span data-tip="Defenders power considering phase and randon factor" style="padding: .2em" class="icon-button-power">65</span>
|
||||
<button data-tip="Battle phase. Click to change" class="icon-button-skirmish"></button>
|
||||
<button data-tip="Random factor for defenders. Click to re-roll" style="padding: .1em .2em" class="icon-button-die">05</button>
|
||||
</div>
|
||||
</div>
|
||||
<table id="battleDefenders"><thead><tr></tr></thead><tbody></tbody></table>
|
||||
</div>
|
||||
|
||||
<div id="battleBottom">
|
||||
<button id="battleAddRegiment" data-tip="Add regiment to the battle" class="icon-user-plus"></button>
|
||||
<button id="battleRoll" data-tip="Roll dice to randomize sides power and battle phase" class="icon-die"></button>
|
||||
<button id="battleNext" data-tip="Calculate and apply phase results" class="icon-play"></button>
|
||||
<button id="battleApply" data-tip="Apply battle results" class="icon-check"></button>
|
||||
|
|
@ -3360,8 +3375,8 @@
|
|||
<tr>
|
||||
<th data-tip="Unit icon">Icon</th>
|
||||
<th data-tip="Unit name. If name is changed for existing unit, old unit will be replaced">Unit name</th>
|
||||
<th data-tip="Conscription percentage for rural population">Rural %</th>
|
||||
<th data-tip="Conscription percentage for urban population">Urban %</th>
|
||||
<th data-tip="Conscription percentage for rural population">Rural</th>
|
||||
<th data-tip="Conscription percentage for urban population">Urban</th>
|
||||
<th data-tip="Average number of people in crew (used for total personnel calculation)">Crew</th>
|
||||
<th data-tip="Unit military power (used for battle simulation)">Power</th>
|
||||
<th data-tip="Unit type to apply special rules on forces recalculation">Type</th>
|
||||
|
|
@ -3608,4 +3623,5 @@
|
|||
<script defer src="modules/ui/3d.js"></script>
|
||||
<script defer src="libs/quantize.min.js"></script>
|
||||
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
||||
<script defer src="libs/publicstorage.js"></script>
|
||||
</body>
|
||||
|
|
|
|||
188
libs/publicstorage.js
Normal file
188
libs/publicstorage.js
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
// https://github.com/Highbrainer/jeevaneo-js-publicstorage. MIT
|
||||
const IFRAME_ROOT_URL = "https://publicstorage.neocities.org/shared-iframe.html";
|
||||
|
||||
class PublicStorageAccess {
|
||||
|
||||
constructor ({debug=false}={}) {
|
||||
this.uid = this.uniqueId();
|
||||
this.debug=debug;
|
||||
}
|
||||
|
||||
uniqueId() {
|
||||
function chr4(){
|
||||
return Math.random().toString(16).slice(-4);
|
||||
}
|
||||
return chr4() + chr4() +
|
||||
'-' + chr4() +
|
||||
'-' + chr4() +
|
||||
'-' + chr4() +
|
||||
'-' + chr4() + chr4() + chr4();
|
||||
}
|
||||
|
||||
_debug(msg) {
|
||||
if(this.debug) {
|
||||
if(console && console.debug) {
|
||||
console.debug(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareIFrame() {
|
||||
const that = this;
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id=that.uid;
|
||||
iframe.src=IFRAME_ROOT_URL + "?uid=init-"+that.uid;
|
||||
iframe.style.cssText="display:none;";
|
||||
return new Promise(function(resolve, reject) {
|
||||
window.addEventListener('message', function mafunc(tkn) {
|
||||
|
||||
if (IFRAME_ROOT_URL.indexOf(tkn.origin)<0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const packet = JSON.parse(tkn.data);
|
||||
|
||||
if(!(packet.frameId === "init-" + that.uid)) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
|
||||
if(packet.ready) {
|
||||
resolve(iframe);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(tkn.data);
|
||||
}
|
||||
window.removeEventListener('message', mafunc);
|
||||
});
|
||||
onLoadThen().then(() => {
|
||||
document.getElementsByTagName("body")[0].appendChild(iframe);
|
||||
});
|
||||
|
||||
setTimeout(()=>reject(`Request ${that.uid} TIMEOUTED!`), 20000);
|
||||
});
|
||||
}
|
||||
|
||||
access(access, prop, value = null, level = "local") {
|
||||
|
||||
if(!(access === "get" || access === "set" || access === "delete")) {
|
||||
throw new Error("access can only be 'set', 'get' or 'delete' - not '" + access + "'");
|
||||
}
|
||||
|
||||
if (!prop) {
|
||||
throw new Error("Prop name is mandatory");
|
||||
}
|
||||
|
||||
if(!(level === "local" || level === "session")) {
|
||||
throw new Error("level can only be 'session' or 'local' - not '" + access + "'");
|
||||
}
|
||||
|
||||
const that = this;
|
||||
|
||||
const promise = new Promise(function(resolve, reject) {
|
||||
that.prepareIFrame().then(iframe => {
|
||||
window.addEventListener('message', function mafunc(tkn) {
|
||||
if (IFRAME_ROOT_URL.indexOf(tkn.origin)<0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var packet = JSON.parse(tkn.data);
|
||||
|
||||
if(!(packet.uid === that.uid)) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
resolve(packet.body);
|
||||
} catch (e) {
|
||||
reject(tkn.data);
|
||||
}
|
||||
iframe.parentNode.removeChild(iframe);
|
||||
window.removeEventListener('message', mafunc);
|
||||
});
|
||||
|
||||
const request = {uid:that.uid, access:access, prop:prop, value:value, level:level};
|
||||
iframe.contentWindow.postMessage(JSON.stringify(request), '*');
|
||||
setTimeout(()=>reject("TIMEOUTED!"), 20000);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function __createDebugIFrame() {
|
||||
onLoadThen().then(function(){
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src=IFRAME_ROOT_URL + "?for-debug-only";
|
||||
iframe.style.cssText="display:none;";
|
||||
document.getElementsByTagName("body")[0].appendChild(iframe);
|
||||
});
|
||||
}
|
||||
|
||||
class PublicStorage {
|
||||
|
||||
constructor({debug=false}={}) {
|
||||
if(debug) {
|
||||
__createDebugIFrame();
|
||||
}
|
||||
}
|
||||
|
||||
sessionGet(prop) {
|
||||
return new PublicStorageAccess().access("get", prop, null, "session");
|
||||
}
|
||||
sessionSet(prop, value) {
|
||||
return new PublicStorageAccess().access("set", prop, value, "session");
|
||||
}
|
||||
sessionUnset(prop) {
|
||||
return new PublicStorageAccess().access("delete", prop, null, "session");
|
||||
}
|
||||
localGet(prop) {
|
||||
return new PublicStorageAccess().access("get", prop, null, "local");
|
||||
}
|
||||
localSet(prop, value) {
|
||||
return new PublicStorageAccess().access("set", prop, value, "local");
|
||||
}
|
||||
localUnset(prop) {
|
||||
return new PublicStorageAccess().access("delete", prop, null, "local");
|
||||
}
|
||||
get(prop) {
|
||||
return this.localGet(prop);
|
||||
}
|
||||
set(prop, value) {
|
||||
return this.localSet(prop, value);
|
||||
}
|
||||
unset(prop) {
|
||||
return this.localUnset(prop);
|
||||
}
|
||||
}
|
||||
|
||||
const publicstorage = new PublicStorage();
|
||||
|
||||
function onLoadThen() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (window) {
|
||||
if(document.getElementsByTagName('BODY')[0]) {
|
||||
resolve();
|
||||
} else {
|
||||
registerOnLoad(function unregisterme() {
|
||||
resolve();
|
||||
window.removeEventListener('load', unregisterme);
|
||||
});
|
||||
}
|
||||
}
|
||||
setTimeout(function() {reject(new Error("Timeout waiting for onLoad!"));}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
function registerOnLoad(lambda) {
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('load', lambda);
|
||||
} else if (window.attachEvent) {
|
||||
window.attachEvent('onload', lambda);
|
||||
}
|
||||
}
|
||||
|
||||
onLoadThen().then(() => window.publicstorage = publicstorage).catch(e=> console.error(e));
|
||||
//export {onLoadThen, PublicStorage, publicstorage as default}
|
||||
// module.exports = onLoadThen();
|
||||
|
|
@ -293,7 +293,15 @@ async function saveMap() {
|
|||
document.body.appendChild(link);
|
||||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||
window.URL.revokeObjectURL(URL);
|
||||
|
||||
// send saved files count and size to server for usage analysis (for the future Cloud storage)
|
||||
publicstorage.get("fmg").then(fmg => {
|
||||
if (!fmg) return;
|
||||
fmg.size = (fmg.size * fmg.maps + blob.size) / (fmg.maps + 1);
|
||||
fmg.maps += 1;
|
||||
publicstorage.set("fmg", fmg).then(fmg => console.log(fmg));
|
||||
});
|
||||
}
|
||||
|
||||
// Send .map file to server [test function]
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ function showBattleScreen(attacker, defender) {
|
|||
modules.showBattleScreen = true;
|
||||
|
||||
// add listeners
|
||||
//document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
|
||||
document.getElementById("battleAddRegiment").addEventListener("click", addSide);
|
||||
|
||||
function addHeaders() {
|
||||
document.getElementById("battleScreen").querySelectorAll("th").forEach(el => el.remove());
|
||||
const attackers = battleAttackers.querySelector("tr");
|
||||
const defenders = battleDefenders.querySelector("tr");
|
||||
let headers = "<th></th>";
|
||||
let headers = "<th></th><th></th>";
|
||||
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
|
|
@ -38,12 +38,55 @@ function showBattleScreen(attacker, defender) {
|
|||
}
|
||||
|
||||
function addRegiment(div, regiment) {
|
||||
let line = `<tr><th>${regiment.name}</th>`;
|
||||
const state = ra(pack.states), supply = rand(1000) + " " + distanceUnitInput.value;
|
||||
const color = state.color[0] === "#" ? state.color : "#999";
|
||||
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em;">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect"></rect>
|
||||
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
|
||||
const body = `<tbody id="battle${state.i}-${regiment.i}">`;
|
||||
|
||||
let initial = `<tr class="battleInitial"><td>${icon}</td><td class="regiment">${regiment.name.slice(0,25)}</td>`;
|
||||
let casualties = `<tr class="battleCasualties"><td></td><td>${state.fullName}</td>`;
|
||||
let survivors = `<tr class="battleSurvivors"><td></td><td>Supply line length: ${supply}</td>`;
|
||||
|
||||
for (const u of options.military) {
|
||||
line += `<th>${regiment.u[u.name]||0}</th>`;
|
||||
initial += `<td style="width: 2.5em; text-align: center">${regiment.u[u.name]||0}</td>`;
|
||||
casualties += `<td style="width: 2.5em; text-align: center; color: red">0</td>`;
|
||||
survivors += `<td style="width: 2.5em; text-align: center; color: green">${regiment.u[u.name]||0}</td>`;
|
||||
}
|
||||
|
||||
initial += `<td style="width: 2.5em; text-align: center">${regiment.a||0}</td></tr>`;
|
||||
casualties += `<td style="width: 2.5em; text-align: center; color: red">0</td></tr>`;
|
||||
survivors += `<td style="width: 2.5em; text-align: center; color: green">${regiment.a||0}</td></tr>`;
|
||||
|
||||
div.innerHTML += body + initial + casualties + survivors + "</tbody>";
|
||||
}
|
||||
|
||||
function addSide() {
|
||||
const states = pack.states.filter(s => s.i && !s.removed);
|
||||
const stateOptions = states.map(s => `<option value=${s.i}>${s.fullName}</option>`).join("");
|
||||
const regiments = states[0].military.map(r => `<option value=${r.i}>${r.icon} ${r.name} (${r.a})</option>`).join("");
|
||||
alertMessage.innerHTML = `<select id="addSideSide" data-tip="Select side"><option>Attackers</option><option>Defenders</option></select>
|
||||
<select id="addSideState" data-tip="Select state">${stateOptions}</select><br>
|
||||
<select id="addSideRegiment" data-tip="Select regiment">${regiments}</select>`;
|
||||
$("#alert").dialog({resizable: false, title: "Add regiment to the battle",
|
||||
buttons: {
|
||||
Add: function() {
|
||||
$(this).dialog("close");
|
||||
const div = document.getElementById("addSideSide").selectedIndex ? battleDefenders : battleAttackers;
|
||||
const state = pack.states.find(s => s.i == document.getElementById("addSideState").value);
|
||||
const regiment = state.military.find(r => r.i == document.getElementById("addSideRegiment").value);
|
||||
addRegiment(div, regiment);
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("addSideState").onchange = function () {
|
||||
const state = pack.states.find(s => s.i == this.value);
|
||||
const regiments = state.military.map(r => `<option value=${r.i}>${r.icon} ${r.name} (${r.a})</option>`).join("");
|
||||
document.getElementById("addSideRegiment").innerHTML = regiments;
|
||||
}
|
||||
line += `<th>${regiment.a||0}</th></tr>`;
|
||||
div.querySelector("tbody").insertAdjacentHTML("beforebegin", line);
|
||||
}
|
||||
|
||||
function closeBattleScreen() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
"use strict";
|
||||
|
||||
function showEPForRoute(node) {
|
||||
const points = [];
|
||||
debug.select("#controlPoints").selectAll("circle").each(function() {
|
||||
|
|
@ -34,7 +33,7 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
position: {my: "left top", at: "left+20 bottom-240", of: window, collision: "fit"}
|
||||
});
|
||||
|
||||
// Azgaar asked if we can prevent river graphs from showing rivers as flowing uphill
|
||||
// prevent river graphs from showing rivers as flowing uphill
|
||||
var slope = 0;
|
||||
if (isRiver) {
|
||||
if (pack.cells.h[data[0]] < pack.cells.h[data[data.length-1]]) {
|
||||
|
|
@ -73,10 +72,6 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
j++;
|
||||
}
|
||||
|
||||
const heightDiff = ma-mi+1;
|
||||
|
||||
|
||||
// const w = document.getElementById("elevationProfile").clientWidth - 32;
|
||||
const w = window.innerWidth-280;
|
||||
h = 100;
|
||||
|
||||
|
|
@ -88,10 +83,7 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
chart.append("defs").append("marker").attr("id", "arrowhead").attr("orient", "auto").attr("markerWidth", "2").attr("markerHeight", "4").attr("refX", "0.1").attr("refY", "2").append("path").attr("d", "M0,0 V4 L2,2 Z");
|
||||
|
||||
// main graph line
|
||||
var lineFunc = d3.line()
|
||||
.x(function(d) { return d.x * w / points.length + xOffset})
|
||||
.y(function(d) { return h-d.y + yOffset });
|
||||
|
||||
var lineFunc = d3.line().x(d => d.x * w / points.length + xOffset).y(d => h-d.y + yOffset);
|
||||
chart.append("path").attr("d", lineFunc(points)).attr("stroke", "purple").attr("fill", "none").attr("id", "elevationLine");
|
||||
|
||||
// y-axis labels for starting and ending heights
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ function editRegiment(selector) {
|
|||
.style("dominant-baseline", "central").style("text-anchor", "middle")
|
||||
.transition(attack).attr("font-size", 1000).attr("opacity", 0).remove();
|
||||
|
||||
tip("", false);
|
||||
clearMainTip();
|
||||
$("#regimentEditor").dialog("close");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue