mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51:24 +01:00
v1.4.06
This commit is contained in:
parent
5304306044
commit
beb2d0ad7c
14 changed files with 469 additions and 108 deletions
|
|
@ -190,7 +190,7 @@
|
|||
const regiments = nodes.filter(n => n.t).sort((a,b) => b.t - a.t).map((r, i) => {
|
||||
const u = {}; u[r.u] = r.a;
|
||||
(r.childen||[]).forEach(n => u[n.u] = u[n.u] ? u[n.u] += n.a : n.a);
|
||||
return {i, a:r.t, cell:r.cell, x:r.x, y:r.y, bx:r.x, by:r.y, u, n:r.n, name};
|
||||
return {i, a:r.t, cell:r.cell, x:r.x, y:r.y, bx:r.x, by:r.y, u, n:r.n, name, state: s.i};
|
||||
});
|
||||
|
||||
// generate name for regiments
|
||||
|
|
@ -210,9 +210,9 @@
|
|||
return [
|
||||
{icon: "⚔️", name:"infantry", rural:.25, urban:.2, crew:1, power:1, type:"melee", separate:0},
|
||||
{icon: "🏹", name:"archers", rural:.12, urban:.2, crew:1, power:1, type:"ranged", separate:0},
|
||||
{icon: "🐴", name:"cavalry", rural:.12, urban:.03, crew:3, power:4, type:"mounted", separate:0},
|
||||
{icon: "💣", name:"artillery", rural:0, urban:.03, crew:8, power:12, type:"machinery", separate:0},
|
||||
{icon: "🌊", name:"fleet", rural:0, urban:.015, crew:100, power:50, type:"naval", separate:1}
|
||||
{icon: "🐴", name:"cavalry", rural:.12, urban:.03, crew:2, power:2, type:"mounted", separate:0},
|
||||
{icon: "💣", name:"artillery", rural:0, urban:.03, crew:8, power:12, type:"machinery", separate:0},
|
||||
{icon: "🌊", name:"fleet", rural:0, urban:.015, crew:100, power:50, type:"naval", separate:1}
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -256,6 +256,26 @@
|
|||
g.append("text").attr("class", "regimentIcon").attr("x", x1-size).attr("y", reg.y).text(reg.icon);
|
||||
}
|
||||
|
||||
// move one regiment to another
|
||||
const moveRegiment = function(reg, x, y) {
|
||||
const el = armies.select("g#army"+reg.state).select("g#regiment"+reg.state+"-"+reg.i);
|
||||
if (!el.size()) return;
|
||||
|
||||
const duration = Math.hypot(reg.x - x, reg.y - y) * 8;
|
||||
reg.x = x; reg.y = y;
|
||||
const size = +armies.attr("box-size");
|
||||
const w = reg.n ? size * 4 : size * 6;
|
||||
const h = size * 2;
|
||||
const x1 = x => rn(x - w / 2, 2);
|
||||
const y1 = y => rn(y - size, 2);
|
||||
|
||||
const move = d3.transition().duration(duration).ease(d3.easeSinInOut);
|
||||
el.select("rect").transition(move).attr("x", x1(x)).attr("y", y1(y));
|
||||
el.select("text").transition(move).attr("x", x).attr("y", y);
|
||||
el.selectAll("rect:nth-of-type(2)").transition(move).attr("x", x1(x)-h).attr("y", y1(y));
|
||||
el.select(".regimentIcon").transition(move).attr("x", x1(x)-size).attr("y", y);
|
||||
}
|
||||
|
||||
// utilize si function to make regiment total text fit regiment box
|
||||
const getTotal = reg => reg.a > (reg.n ? 999 : 99999) ? si(reg.a) : reg.a;
|
||||
|
||||
|
|
@ -304,6 +324,6 @@
|
|||
// note.legend = note.legend.replace(oldComposition, newComposition);
|
||||
// }
|
||||
|
||||
return {generate, getDefaultOptions, getName, generateNote, drawRegiments, drawRegiment, getTotal, getEmblem};
|
||||
return {generate, getDefaultOptions, getName, generateNote, drawRegiments, drawRegiment, moveRegiment, getTotal, getEmblem};
|
||||
|
||||
})));
|
||||
|
|
@ -33,15 +33,15 @@
|
|||
const relaxed = chain.filter((v, i) => !(i%relax) || vertices.c[v].some(c => c >= pointsN));
|
||||
if (relaxed.length < 4) continue;
|
||||
const points = clipPoly(relaxed.map(v => vertices.p[v]), 1);
|
||||
const inside = d3.polygonContains(points, grid.points[i]);
|
||||
chains.push([t, points, inside]);
|
||||
//const inside = d3.polygonContains(points, grid.points[i]);
|
||||
chains.push([t, points]); //chains.push([t, points, inside]);
|
||||
}
|
||||
|
||||
const bbox = `M0,0h${graphWidth}v${graphHeight}h${-graphWidth}Z`;
|
||||
//const bbox = `M0,0h${graphWidth}v${graphHeight}h${-graphWidth}Z`;
|
||||
for (const t of limits) {
|
||||
const layer = chains.filter(c => c[0] === t);
|
||||
let path = layer.map(c => round(lineGen(c[1]))).join("");
|
||||
if (layer.every(c => !c[2])) path = bbox + path; // add outer ring if all segments are outside (works not for all cases)
|
||||
//if (layer.every(c => !c[2])) path = bbox + path; // add outer ring if all segments are outside (works not for all cases)
|
||||
if (path) oceanLayers.append("path").attr("d", path).attr("fill", "#ecf2f9").style("opacity", opacity);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1018,6 +1018,9 @@ function parseLoadedData(data) {
|
|||
if (type === "magical") return "🔮";
|
||||
else return "⚔️";
|
||||
}
|
||||
|
||||
// 1.4 added state reference for regiments
|
||||
pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => r.state = s.i));
|
||||
}
|
||||
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -1,53 +1,83 @@
|
|||
"use strict";
|
||||
function showBattleScreen(attacker, defender) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
class Battle {
|
||||
|
||||
const battle = {name:"Battle", attackers:[attacker], defenders:[defender]};
|
||||
const battleAttackers = document.getElementById("battleAttackers");
|
||||
const battleDefenders = document.getElementById("battleDefenders");
|
||||
addHeaders();
|
||||
addRegiment(battleAttackers, attacker);
|
||||
addRegiment(battleDefenders, defender);
|
||||
constructor(attacker, defender) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
customization = 13; // enter customization to avoid unwanted dialog closing
|
||||
|
||||
$("#battleScreen").dialog({
|
||||
title: battle.name, resizable: false, width: fitContent(), close: closeBattleScreen,
|
||||
position: {my: "center", at: "center", of: "#map"}
|
||||
});
|
||||
Battle.prototype.context = this; // store context
|
||||
this.x = defender.x;
|
||||
this.y = defender.y;
|
||||
this.name = this.getBattleName();
|
||||
this.iteration = 0;
|
||||
this.attackers = {regiments:[], distances:[], morale:100};
|
||||
this.defenders = {regiments:[], distances:[], morale:100};
|
||||
|
||||
if (modules.showBattleScreen) return;
|
||||
modules.showBattleScreen = true;
|
||||
this.addHeaders();
|
||||
this.addRegiment("attackers", attacker);
|
||||
this.addRegiment("defenders", defender);
|
||||
this.randomize();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
this.getInitialMorale();
|
||||
|
||||
// add listeners
|
||||
document.getElementById("battleAddRegiment").addEventListener("click", addSide);
|
||||
$("#battleScreen").dialog({
|
||||
title: this.name, resizable: false, width: fitContent(), close: this.closeBattleScreen,
|
||||
position: {my: "center", at: "center", of: "#map"}
|
||||
});
|
||||
|
||||
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><th></th>";
|
||||
if (modules.Battle) return;
|
||||
modules.Battle = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("battleAddRegiment").addEventListener("click", this.addSide);
|
||||
document.getElementById("battleRoll").addEventListener("click", () => Battle.prototype.context.randomize());
|
||||
document.getElementById("battleRun").addEventListener("click", () => Battle.prototype.context.run());
|
||||
document.getElementById("battleApply").addEventListener("click", () => Battle.prototype.context.applyResults());
|
||||
document.getElementById("battleCancel").addEventListener("click", () => Battle.prototype.context.cancelResults());
|
||||
|
||||
document.getElementById("battlePhase_attackers").addEventListener("click", ev => this.toggleChangePhase(ev, "attackers"));
|
||||
document.getElementById("battlePhase_attackers").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "attackers"));
|
||||
document.getElementById("battlePhase_defenders").addEventListener("click", ev => this.toggleChangePhase(ev, "defenders"));
|
||||
document.getElementById("battlePhase_defenders").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "defenders"));
|
||||
document.getElementById("battleDie_attackers").addEventListener("click", () => Battle.prototype.context.rollDie("attackers"));
|
||||
document.getElementById("battleDie_defenders").addEventListener("click", () => Battle.prototype.context.rollDie("defenders"));
|
||||
}
|
||||
|
||||
getBattleName() {
|
||||
const cell = findCell(this.x, this.y);
|
||||
const burg = pack.cells.burg[cell] ? pack.burgs[pack.cells.burg[cell]].name : null;
|
||||
return burg ? burg + " Battle" : Names.getCulture(pack.cells.culture[cell]) + " Battle"
|
||||
}
|
||||
|
||||
addHeaders() {
|
||||
let headers = "<thead><tr><th></th><th></th>";
|
||||
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
headers += `<th data-tip="${label}">${u.icon}</th>`;
|
||||
}
|
||||
|
||||
headers += "<th>Total</th>";
|
||||
attackers.insertAdjacentHTML("beforebegin", headers);
|
||||
defenders.insertAdjacentHTML("beforebegin", headers);
|
||||
headers += "<th data-tip='Total military''>Total</th></tr></thead>";
|
||||
battleAttackers.innerHTML = battleDefenders.innerHTML = headers;
|
||||
}
|
||||
|
||||
function addRegiment(div, regiment) {
|
||||
const state = ra(pack.states), supply = rand(1000) + " " + distanceUnitInput.value;
|
||||
addRegiment(side, regiment) {
|
||||
regiment.casualties = Object.keys(regiment.u).reduce((a,b) => (a[b]=0,a), {});
|
||||
regiment.survivors = Object.assign({}, regiment.u);
|
||||
|
||||
const state = pack.states[regiment.state];
|
||||
const distance = Math.hypot(this.y-regiment.by, this.x-regiment.bx) * distanceScaleInput.value | 0; // distance between regiment and its base
|
||||
const color = state.color[0] === "#" ? state.color : "#999";
|
||||
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em;">
|
||||
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em">
|
||||
<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>`;
|
||||
let initial = `<tr class="battleInitial"><td>${icon}</td><td class="regiment">${regiment.name.slice(0, 24)}</td>`;
|
||||
let casualties = `<tr class="battleCasualties"><td></td><td>${state.fullName.slice(0, 26)}</td>`;
|
||||
let survivors = `<tr class="battleSurvivors"><td></td><td>Distance to base: ${distance} ${distanceUnitInput.value}</td>`;
|
||||
|
||||
for (const u of options.military) {
|
||||
initial += `<td style="width: 2.5em; text-align: center">${regiment.u[u.name]||0}</td>`;
|
||||
|
|
@ -59,38 +89,274 @@ function showBattleScreen(attacker, defender) {
|
|||
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>`;
|
||||
|
||||
const div = side === "attackers" ? battleAttackers : battleDefenders;
|
||||
div.innerHTML += body + initial + casualties + survivors + "</tbody>";
|
||||
this[side].regiments.push(regiment);
|
||||
this[side].distances.push(distance);
|
||||
}
|
||||
|
||||
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",
|
||||
addSide() {
|
||||
const body = document.getElementById("regimentSelectorBody");
|
||||
const context = Battle.prototype.context;
|
||||
const regiments = pack.states.filter(s => s.military && !s.removed).map(s => s.military).flat();
|
||||
const distance = reg => rn(Math.hypot(context.y-reg.y, context.x-reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const isAdded = reg => context.defenders.regiments.some(r => r === reg) || context.attackers.regiments.some(r => r === reg);
|
||||
|
||||
body.innerHTML = regiments.map(r => {
|
||||
const s = pack.states[r.state], added = isAdded(r), dist = added ? "0 " + distanceUnitInput.value : distance(r);
|
||||
return `<div ${added ? "class='inactive'" : ""} data-s=${s.i} data-i=${r.i} data-state=${s.name} data-regiment=${r.name}
|
||||
data-total=${r.a} data-distance=${dist} data-tip="Click to select regiment">
|
||||
<svg width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
|
||||
<div style="width:6em">${s.name.slice(0, 11)}</div>
|
||||
<div style="width:1.2em">${r.icon}</div>
|
||||
<div style="width:13em">${r.name.slice(0, 24)}</div>
|
||||
<div style="width:4em">${r.a}</div>
|
||||
<div style="width:4em">${dist}</div>
|
||||
</div>`;
|
||||
}).join("");
|
||||
|
||||
$("#regimentSelectorScreen").dialog({
|
||||
resizable: false, width: fitContent(), title: "Add regiment to the battle",
|
||||
position: {my: "left center", at: "right+10 center", of: "#battleScreen"}, close: addSideClosed,
|
||||
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");}
|
||||
"Add to attackers": () => addSideClicked("attackers"),
|
||||
"Add to defenders": () => addSideClicked("defenders"),
|
||||
Cancel: () => $("#regimentSelectorScreen").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;
|
||||
applySorting(regimentSelectorHeader);
|
||||
body.addEventListener("click", selectLine);
|
||||
|
||||
function selectLine(ev) {
|
||||
if (ev.target.className === "inactive") {tip("Regiment is already in the battle", false, "error"); return};
|
||||
ev.target.classList.toggle("selected");
|
||||
}
|
||||
|
||||
function addSideClicked(side) {
|
||||
const selected = body.querySelectorAll(".selected");
|
||||
if (!selected.length) {tip("Please select a regiment first", false, "error"); return}
|
||||
|
||||
$("#regimentSelectorScreen").dialog("close");
|
||||
selected.forEach(line => {
|
||||
const state = pack.states[line.dataset.s];
|
||||
const regiment = state.military.find(r => r.i == +line.dataset.i);
|
||||
Battle.prototype.addRegiment.call(context, side, regiment);
|
||||
Battle.prototype.calculateStrength.call(context, side);
|
||||
Battle.prototype.getInitialMorale.call(context);
|
||||
|
||||
// move regiment
|
||||
const defenders = context.defenders.regiments, attackers = context.attackers.regiments;
|
||||
const shift = side === "attackers" ? attackers.length * -8 : (defenders.length-1) * 8;
|
||||
regiment.px = regiment.x;
|
||||
regiment.py = regiment.y;
|
||||
Military.moveRegiment(regiment, defenders[0].x, defenders[0].y + shift);
|
||||
});
|
||||
}
|
||||
|
||||
function addSideClosed() {
|
||||
body.innerHTML = "";
|
||||
body.removeEventListener("click", selectLine);
|
||||
}
|
||||
}
|
||||
|
||||
function closeBattleScreen() {
|
||||
getJoinedForces(regiments) {
|
||||
return regiments.reduce((a, b) => {
|
||||
for (let k in b.survivors) {
|
||||
if (!b.survivors.hasOwnProperty(k)) continue;
|
||||
a[k] = (a[k] || 0) + b.survivors[k];
|
||||
}
|
||||
return a;
|
||||
}, {});
|
||||
}
|
||||
|
||||
calculateStrength(side) {
|
||||
const scheme = {
|
||||
"skirmish": {"melee":.2, "ranged":2.4, "mounted":.1, "machinery":3, "naval":1, "armored":.2, "aviation":1.8, "magical":1.8}, // ranged excel
|
||||
"melee": {"melee":2, "ranged":1.2, "mounted":1.5, "machinery":.5, "naval":.2, "armored":2, "aviation":.8, "magical":.8}, // melee excel
|
||||
"pursue": {"melee":1, "ranged":1, "mounted":4, "machinery":.05, "naval":1, "armored":1, "aviation":1.5, "magical":.6}, // mounted excel
|
||||
"retreat": {"melee":.1, "ranged":.01, "mounted":.5, "machinery":.01, "naval":.2, "armored":.1, "aviation":.8, "magical":.05} // mounted excel
|
||||
};
|
||||
|
||||
const forces = this.getJoinedForces(this[side].regiments);
|
||||
const phase = this[side].phase;
|
||||
const adjuster = populationRate.value / 10; // population adjuster, by default 100
|
||||
this[side].power = d3.sum(options.military.map(u => (forces[u.name] || 0) * u.power * scheme[phase][u.type])) / adjuster;
|
||||
const UIvalue = this[side].power ? Math.max(this[side].power|0, 1) : 0;
|
||||
document.getElementById("battlePower_"+side).innerHTML = UIvalue;
|
||||
}
|
||||
|
||||
getInitialMorale() {
|
||||
const powerFee = diff => Math.min(Math.max(100 - diff ** 1.5 * 10 + 10, 50), 100);
|
||||
const distanceFee = dist => Math.min(d3.mean(dist) / 50, 15);
|
||||
const powerDiff = this.defenders.power / this.attackers.power;
|
||||
this.attackers.morale = powerFee(powerDiff) - distanceFee(this.attackers.distances);
|
||||
this.defenders.morale = powerFee(1 / powerDiff) - distanceFee(this.defenders.distances);
|
||||
this.updateMorale("attackers");
|
||||
this.updateMorale("defenders");
|
||||
}
|
||||
|
||||
updateMorale(side) {
|
||||
const morale = document.getElementById("battleMorale_"+side);
|
||||
morale.dataset.tip = morale.dataset.tip.replace(morale.value, "");
|
||||
morale.value = this[side].morale | 0;
|
||||
morale.dataset.tip += morale.value;
|
||||
}
|
||||
|
||||
randomize() {
|
||||
this.rollDie("attackers");
|
||||
this.rollDie("defenders");
|
||||
this.selectPhase();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
}
|
||||
|
||||
rollDie(side) {
|
||||
const el = document.getElementById("battleDie_"+side);
|
||||
const prev = +el.innerHTML;
|
||||
do {el.innerHTML = rand(1, 6)} while (el.innerHTML === prev)
|
||||
this[side].die = +el.innerHTML;
|
||||
}
|
||||
|
||||
selectPhase() {
|
||||
const phase = this.getPhase();
|
||||
this.attackers.phase = phase[0];
|
||||
this.defenders.phase = phase[1];
|
||||
document.getElementById("battlePhase_attackers").className = "icon-button-" + this.attackers.phase;
|
||||
document.getElementById("battlePhase_defenders").className = "icon-button-" + this.defenders.phase;
|
||||
}
|
||||
|
||||
getPhase() {
|
||||
const i = this.iteration;
|
||||
const prev = [this.attackers.phase || "skirmish", this.defenders.phase || "skirmish"]; // previous phase
|
||||
const morale = [this.attackers.morale, this.defenders.morale];
|
||||
|
||||
if (P(1 - morale[0] / 25)) return ["retreat", "pursue"];
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"];
|
||||
|
||||
if (prev[0] === "skirmish" && prev[1] === "skirmish") {
|
||||
const forces = this.getJoinedForces(this.attackers.regiments.concat(this.defenders.regiments));
|
||||
const total = d3.sum(Object.values(forces)); // total forces
|
||||
const ranged = d3.sum(options.military.filter(u => u.type === "ranged").map(u => u.name).map(u => forces[u])) / total;
|
||||
if (ranged && (P(ranged) || P(.8-i/10))) return ["skirmish", "skirmish"];
|
||||
}
|
||||
|
||||
return ["melee", "melee"]; // default option
|
||||
}
|
||||
|
||||
run() {
|
||||
// validations
|
||||
if (!this.attackers.power) {tip("Attackers army destroyed", false, "warn"); return}
|
||||
if (!this.defenders.power) {tip("Defenders army destroyed", false, "warn"); return}
|
||||
|
||||
// calculate casualties
|
||||
const attack = this.attackers.power * (this.attackers.die / 10 + .4);
|
||||
const defence = this.defenders.power * (this.defenders.die / 10 + .4);
|
||||
const phase = {"skirmish":.1, "melee":.2, "pursue":.3, "retreat":.3}; // casualties modifier for phase
|
||||
const casualties = Math.random() * phase[this.attackers.phase]; // total casualties, ~10% per iteration
|
||||
const casualtiesA = casualties * defence / (attack + defence); // attackers casualties, ~5% per iteration
|
||||
const casualtiesD = casualties * attack / (attack + defence); // defenders casualties, ~5% per iteration
|
||||
|
||||
this.calculateCasualties("attackers", casualtiesA);
|
||||
this.calculateCasualties("defenders", casualtiesD);
|
||||
|
||||
// change morale
|
||||
this.attackers.morale = Math.max(this.attackers.morale - casualtiesA * 100, 0);
|
||||
this.defenders.morale = Math.max(this.defenders.morale - casualtiesD * 100, 0);
|
||||
|
||||
// update table values
|
||||
this.updateTable("attackers");
|
||||
this.updateTable("defenders");
|
||||
|
||||
// prepare for next iteration
|
||||
this.iteration += 1;
|
||||
this.selectPhase();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
}
|
||||
|
||||
calculateCasualties(side, casualties) {
|
||||
for (const r of this[side].regiments) {
|
||||
for (const unit in r.u) {
|
||||
const rand = .8 + Math.random() * .4;
|
||||
const died = Math.min(Pint(r.u[unit] * casualties * rand), r.survivors[unit]);
|
||||
r.casualties[unit] -= died;
|
||||
r.survivors[unit] -= died;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateTable(side) {
|
||||
for (const r of this[side].regiments) {
|
||||
const tbody = document.getElementById("battle" + r.state + "-" + r.i);
|
||||
const battleCasualties = tbody.querySelector(".battleCasualties");
|
||||
const battleSurvivors = tbody.querySelector(".battleSurvivors");
|
||||
|
||||
let index = 3; // index to find table element easily
|
||||
for (const u of options.military) {
|
||||
battleCasualties.querySelector(`td:nth-child(${index})`).innerHTML = r.casualties[u.name] || 0;
|
||||
battleSurvivors.querySelector(`td:nth-child(${index})`).innerHTML = r.survivors[u.name] || 0;
|
||||
index++;
|
||||
}
|
||||
|
||||
battleCasualties.querySelector(`td:nth-child(${index})`).innerHTML = d3.sum(Object.values(r.casualties));
|
||||
battleSurvivors.querySelector(`td:nth-child(${index})`).innerHTML = d3.sum(Object.values(r.survivors));
|
||||
}
|
||||
this.updateMorale(side);
|
||||
}
|
||||
|
||||
toggleChangePhase(ev, side) {
|
||||
ev.stopPropagation();
|
||||
const button = document.getElementById("battlePhase_"+side);
|
||||
const div = button.nextElementSibling;
|
||||
button.style.opacity = .5;
|
||||
div.style.display = "block";
|
||||
|
||||
const hideSection = function() {button.style.opacity = 1; div.style.display = "none"}
|
||||
document.getElementsByTagName("body")[0].addEventListener("click", hideSection, {once: true});
|
||||
}
|
||||
|
||||
changePhase(ev, side) {
|
||||
if (ev.target.tagName !== "BUTTON") return;
|
||||
const phase = this[side].phase = ev.target.dataset.phase;
|
||||
const button = document.getElementById("battlePhase_"+side);
|
||||
button.className = "icon-button-" + phase;
|
||||
this.calculateStrength(side);
|
||||
}
|
||||
|
||||
applyResults() {
|
||||
this.attackers.regiments.concat(this.defenders.regiments).forEach(r => {
|
||||
r.u = Object.assign({}, r.survivors);
|
||||
r.a = d3.sum(Object.values(r.u)); // reg total
|
||||
armies.select(`g#regiment${r.state}-${r.i} > text`).text(Military.getTotal(r)); // update reg box
|
||||
Military.moveRegiment(r, r.x + rand(30) - 15, r.y + rand(30) - 15);
|
||||
});
|
||||
|
||||
$("#battleScreen").dialog("close");
|
||||
}
|
||||
|
||||
cancelResults() {
|
||||
// move regiments back to initial positions
|
||||
this.attackers.regiments.concat(this.defenders.regiments).forEach(r => {
|
||||
Military.moveRegiment(r, r.px, r.py);
|
||||
});
|
||||
|
||||
$("#battleScreen").dialog("close");
|
||||
}
|
||||
|
||||
closeBattleScreen() {
|
||||
battleAttackers.innerHTML = battleDefenders.innerHTML = ""; // clean DOM
|
||||
customization = 0; // exit edit mode
|
||||
|
||||
// clean temp data
|
||||
const context = Battle.prototype.context;
|
||||
context.attackers.regiments.concat(context.defenders.regiments).forEach(r => {
|
||||
delete r.px;
|
||||
delete r.py;
|
||||
delete r.casualties;
|
||||
delete r.survivors;
|
||||
});
|
||||
delete Battle.prototype.context;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -377,7 +377,7 @@ function createPicker() {
|
|||
const height = bbox.height + 9;
|
||||
|
||||
picker.insert("rect", ":first-child").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", "#ffffff").attr("stroke", "#5d4651").on("mousemove", pos);
|
||||
picker.insert("text", ":first-child").attr("x", 291).attr("y", -11).attr("id", "pickerCloseText").text("✖");
|
||||
picker.insert("text", ":first-child").attr("x", 291).attr("y", -10).attr("id", "pickerCloseText").text("✕");
|
||||
picker.insert("rect", ":first-child").attr("x", 288).attr("y", -21).attr("id", "pickerCloseRect").attr("width", 14).attr("height", 14).on("mousemove", cl).on("click", closePicker);
|
||||
picker.insert("text", ":first-child").attr("x", 12).attr("y", -10).attr("id", "pickerLabel").text("Color Picker").on("mousemove", pos);
|
||||
picker.insert("rect", ":first-child").attr("x", 0).attr("y", -30).attr("width", width).attr("height", 30).attr("id", "pickerHeader").on("mousemove", pos);
|
||||
|
|
|
|||
|
|
@ -1167,7 +1167,7 @@ function editHeightmap() {
|
|||
|
||||
function setConvertColorsNumber() {
|
||||
prompt(`Please provide a desired number of colors. <br>An actual number depends on color scheme and may vary from desired`,
|
||||
{default:convertColors.value, step:1, min:3, max:255}, number => {
|
||||
{default:+convertColors.value, step:1, min:3, max:255}, number => {
|
||||
convertColors.value = number;
|
||||
heightsFromImage(number);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ document.getElementById("options").querySelector("div.tab").addEventListener("cl
|
|||
|
||||
// show popup with a list of Patreon supportes (updated manually, to be replaced with API call)
|
||||
function showSupporters() {
|
||||
const supporters = "Aaron Meyer, Ahmad Amerih, AstralJacks, aymeric, Billy Dean Goehring, Branndon Edwards, Chase Mayers, Curt Flood, cyninge, Dino Princip, E.M. White, es, Fondue, Fritjof Olsson, Gatsu, Johan Fröberg, Jonathan Moore, Joseph Miranda, Kate, KC138, Luke Nelson, Markus Finster, Massimo Vella, Mikey, Nathan Mitchell, Paavi1, Pat, Ryan Westcott, Sasquatch, Shawn Spencer, Sizz_TV, Timothée CALLET, UTG community, Vlad Tomash, Wil Sisney, William Merriott, Xariun, Gun Metal Games, Scott Marner, Spencer Sherman, Valerii Matskevych, Alloyed Clavicle, Stewart Walsh, Ruthlyn Mollett (Javan), Benjamin Mair-Pratt, Diagonath, Alexander Thomas, Ashley Wilson-Savoury, William Henry, Preston Brooks, JOSHUA QUALTIERI, Hilton Williams, Katharina Haase, Hisham Bedri, Ian arless, Karnat, Bird, Kevin, Jessica Thomas, Steve Hyatt, Logicspren, Alfred García, Jonathan Killstring, John Ackley, Invad3r233, Norbert Žigmund, Jennifer, PoliticsBuff, _gfx_, Maggie, Connor McMartin, Jared McDaris, BlastWind, Franc Casanova Ferrer, Dead & Devil, Michael Carmody, Valerie Elise, naikibens220, Jordon Phillips, William Pucs, The Dungeon Masters, Brady R Rathbun, J, Shadow, Matthew Tiffany, Huw Williams, Joseph Hamilton, FlippantFeline, Tamashi Toh, kms, Stephen Herron, MidnightMoon, Whakomatic x, Barished, Aaron bateson, Brice Moss, Diklyquill, PatronUser, Michael Greiner, Steven Bennett, Jacob Harrington, Miguel C., Reya C., Giant Monster Games, Noirbard, Brian Drennen, Ben Craigie, Alex Smolin, Endwords";
|
||||
const supporters = "Aaron Meyer, Ahmad Amerih, AstralJacks, aymeric, Billy Dean Goehring, Branndon Edwards, Chase Mayers, Curt Flood, cyninge, Dino Princip, E.M. White, es, Fondue, Fritjof Olsson, Gatsu, Johan Fröberg, Jonathan Moore, Joseph Miranda, Kate, KC138, Luke Nelson, Markus Finster, Massimo Vella, Mikey, Nathan Mitchell, Paavi1, Pat, Ryan Westcott, Sasquatch, Shawn Spencer, Sizz_TV, Timothée CALLET, UTG community, Vlad Tomash, Wil Sisney, William Merriott, Xariun, Gun Metal Games, Scott Marner, Spencer Sherman, Valerii Matskevych, Alloyed Clavicle, Stewart Walsh, Ruthlyn Mollett (Javan), Benjamin Mair-Pratt, Diagonath, Alexander Thomas, Ashley Wilson-Savoury, William Henry, Preston Brooks, JOSHUA QUALTIERI, Hilton Williams, Katharina Haase, Hisham Bedri, Ian arless, Karnat, Bird, Kevin, Jessica Thomas, Steve Hyatt, Logicspren, Alfred García, Jonathan Killstring, John Ackley, Invad3r233, Norbert Žigmund, Jennifer, PoliticsBuff, _gfx_, Maggie, Connor McMartin, Jared McDaris, BlastWind, Franc Casanova Ferrer, Dead & Devil, Michael Carmody, Valerie Elise, naikibens220, Jordon Phillips, William Pucs, The Dungeon Masters, Brady R Rathbun, J, Shadow, Matthew Tiffany, Huw Williams, Joseph Hamilton, FlippantFeline, Tamashi Toh, kms, Stephen Herron, MidnightMoon, Whakomatic x, Barished, Aaron bateson, Brice Moss, Diklyquill, PatronUser, Michael Greiner, Steven Bennett, Jacob Harrington, Miguel C., Reya C., Giant Monster Games, Noirbard, Brian Drennen, Ben Craigie, Alex Smolin, Endwords, Joshua E Goodwin, SirTobit , Allen S. Rout, Allen Bull Bear, Pippa Mitchell, R K, G0atfather, Ryan Lege, Caner Oleas Pekgönenç, Bradley Edwards, Tertiary , Austin Miller, Jesse Holmes";
|
||||
alertMessage.innerHTML = "<ul style='column-count: 3; column-gap: 2em'>" + supporters.split(", ").sort().map(n => `<li>${n}</li>`).join("") + "</ul>";
|
||||
$("#alert").dialog({resizable: false, title: "Patreon Supporters", width: "30vw", position: {my: "center", at: "center", of: "svg"}});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ function editProvinces() {
|
|||
document.getElementById("provincesManuallyApply").addEventListener("click", applyProvincesManualAssignent);
|
||||
document.getElementById("provincesManuallyCancel").addEventListener("click", () => exitProvincesManualAssignment());
|
||||
document.getElementById("provincesAdd").addEventListener("click", enterAddProvinceMode);
|
||||
document.getElementById("provincesRecolor").addEventListener("click", recolorProvinces);
|
||||
|
||||
body.addEventListener("click", function(ev) {
|
||||
if (customization) return;
|
||||
|
|
@ -808,6 +809,20 @@ function editProvinces() {
|
|||
if (provincesAdd.classList.contains("pressed")) provincesAdd.classList.remove("pressed");
|
||||
}
|
||||
|
||||
function recolorProvinces() {
|
||||
const state = +document.getElementById("provincesFilterState").value;
|
||||
|
||||
pack.provinces.forEach(p => {
|
||||
if (!p || p.removed) return;
|
||||
if (state !== -1 && p.state !== state) return;
|
||||
const stateColor = pack.states[p.state].color;
|
||||
const rndColor = getRandomColor();
|
||||
p.color = stateColor[0] === "#" ? d3.color(d3.interpolate(stateColor, rndColor)(.2)).hex() : rndColor;
|
||||
});
|
||||
|
||||
if (!layerIsOn("toggleProvinces")) toggleProvinces(); else drawProvinces();
|
||||
}
|
||||
|
||||
function downloadProvincesData() {
|
||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||
let data = "Id,Province,Form,State,Color,Capital,Area "+unit+",Total Population,Rural Population,Urban Population\n"; // headers
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ function editRegiment(selector) {
|
|||
|
||||
function splitRegiment() {
|
||||
const reg = regiment(), u1 = reg.u;
|
||||
const state = elSelected.dataset.state, military = pack.states[state].military;
|
||||
const state = +elSelected.dataset.state, military = pack.states[state].military;
|
||||
const i = last(military).i + 1, u2 = Object.assign({}, u1); // u clone
|
||||
|
||||
Object.keys(u2).forEach(u => u2[u] = Math.floor(u2[u]/2)); // halved new reg
|
||||
|
|
@ -129,7 +129,7 @@ function editRegiment(selector) {
|
|||
// create new regiment
|
||||
const shift = +armies.attr("box-size") * 2;
|
||||
const y = function(x, y) {do {y+=shift} while (military.find(r => r.x === x && r.y === y)); return y;}
|
||||
const newReg = {a, cell:reg.cell, i, n:reg.n, u:u2, x:reg.x, y:y(reg.x, reg.y), bx:reg.bx, by:reg.by, icon: reg.icon};
|
||||
const newReg = {a, cell:reg.cell, i, n:reg.n, u:u2, x:reg.x, y:y(reg.x, reg.y), bx:reg.bx, by:reg.by, state, icon: reg.icon};
|
||||
newReg.name = Military.getName(newReg, military);
|
||||
military.push(newReg);
|
||||
Military.generateNote(newReg, pack.states[state]); // add legend
|
||||
|
|
@ -153,10 +153,10 @@ function editRegiment(selector) {
|
|||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
const x = pack.cells.p[cell][0], y = pack.cells.p[cell][1];
|
||||
const state = elSelected.dataset.state, military = pack.states[state].military;
|
||||
const state = +elSelected.dataset.state, military = pack.states[state].military;
|
||||
const i = military.length ? last(military).i + 1 : 0;
|
||||
const n = +(pack.cells.h[cell] < 20); // naval or land
|
||||
const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, icon:"🛡️"};
|
||||
const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, state, icon:"🛡️"};
|
||||
reg.name = Military.getName(reg, military);
|
||||
military.push(reg);
|
||||
Military.generateNote(reg, pack.states[state]); // add legend
|
||||
|
|
@ -190,30 +190,19 @@ function editRegiment(selector) {
|
|||
const defender = pack.states[regSelected.dataset.state].military.find(r => r.i == regSelected.dataset.id);
|
||||
if (!attacker.a || !defender.a) {tip("Regiment has no troops to battle", false, "error"); return;}
|
||||
|
||||
// move attacked to defender
|
||||
const duration = Math.hypot(attacker.x - defender.x, attacker.y - defender.y) * 6;
|
||||
const x = attacker.x = defender.x;
|
||||
const y = attacker.y = defender.y + 8;
|
||||
// save initial position to temp attribute
|
||||
attacker.px = attacker.x, attacker.py = attacker.y;
|
||||
defender.px = defender.x, defender.py = defender.y;
|
||||
|
||||
const size = +armies.attr("box-size");
|
||||
const w = attacker.n ? size * 4 : size * 6;
|
||||
const h = size * 2;
|
||||
const x1 = x => rn(x - w / 2, 2);
|
||||
const y1 = y => rn(y - size, 2);
|
||||
// move attacker to defender
|
||||
Military.moveRegiment(attacker, defender.x, defender.y-8);
|
||||
|
||||
const move = d3.transition().duration(duration).ease(d3.easeSinInOut);
|
||||
const attack = d3.transition().delay(duration).duration(800).ease(d3.easeSinInOut).on("end", () => showBattleScreen(attacker, defender));
|
||||
|
||||
d3.select(elSelected.querySelector("rect")).transition(move).attr("x", x1(x)).attr("y", y1(y));
|
||||
d3.select(elSelected.querySelector("text")).transition(move).attr("x", x).attr("y", y);
|
||||
d3.select(elSelected.querySelectorAll("rect")[1]).transition(move).attr("x", x1(x)-h).attr("y", y1(y));
|
||||
d3.select(elSelected.querySelector(".regimentIcon")).transition(move).attr("x", x1(x)-size).attr("y", y);
|
||||
|
||||
// battle icon
|
||||
// draw battle icon
|
||||
const attack = d3.transition().delay(300).duration(700).ease(d3.easeSinInOut).on("end", () => new Battle(attacker, defender));
|
||||
svg.append("text").attr("x", window.innerWidth/2).attr("y", window.innerHeight/2)
|
||||
.text("⚔️").attr("font-size", 0).attr("opacity", 1)
|
||||
.style("dominant-baseline", "central").style("text-anchor", "middle")
|
||||
.transition(attack).attr("font-size", 1000).attr("opacity", 0).remove();
|
||||
.transition(attack).attr("font-size", 1000).attr("opacity", .2).remove();
|
||||
|
||||
clearMainTip();
|
||||
$("#regimentEditor").dialog("close");
|
||||
|
|
|
|||
|
|
@ -930,5 +930,6 @@ function editStates() {
|
|||
if (customization === 2) exitStatesManualAssignment("close");
|
||||
if (customization === 3) exitAddStateMode();
|
||||
debug.selectAll(".highlight").remove();
|
||||
body.innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ function gauss(expected = 100, deviation = 30, min = 0, max = 300, round = 0) {
|
|||
return rn(Math.max(Math.min(d3.randomNormal(expected, deviation)(), max), min), round);
|
||||
}
|
||||
|
||||
// get integer from float as floor + P(fractional)
|
||||
/** This is a description of the foo function. */
|
||||
function Pint(float) {
|
||||
return ~~float + +P(float % 1);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue