Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e4fc40e
to do: add the actual siege
Palladium103 Apr 23, 2026
cf652e4
can attack any burg
Palladium103 May 1, 2026
6bc65a5
can now only siege if the burg has castle or wall
Palladium103 May 1, 2026
7527e60
attempt to make it harder to siege a burg with castle
Palladium103 May 1, 2026
808f23a
naval regiment cannot siege non naval burg
Palladium103 May 1, 2026
938dddf
burgAttack renamed to regimentBurgAttack
Palladium103 May 1, 2026
c227c33
Update public/modules/ui/regiment-editor.js
Palladium103 May 1, 2026
ee4a1f2
Update public/modules/ui/battle-screen.js
Palladium103 May 1, 2026
2f30628
Update public/modules/ui/battle-screen.js
Palladium103 May 1, 2026
b4a39b6
removed state attribute and fixed names
Palladium103 May 1, 2026
7057710
Update public/modules/ui/regiment-editor.js
Palladium103 May 1, 2026
823565e
.
Palladium103 May 1, 2026
8073565
Merge branch 'master' of https://github.com/Algoatrithm/Fantasy-Map-G…
Palladium103 May 1, 2026
57d6fcd
Merge branch 'master' into master
Palladium103 May 1, 2026
5cbd4e4
helpful guide
Palladium103 May 1, 2026
0f4447f
Merge branch 'master' of https://github.com/Algoatrithm/Fantasy-Map-G…
Palladium103 May 1, 2026
81a53ba
Refactor: changed ById to ensureEl
Palladium103 May 1, 2026
f2588f0
fix: error when all regiment of a state is removed
Palladium103 May 1, 2026
577935c
fix: last regiment that is depleted causes error when a foreign regim…
Palladium103 May 1, 2026
cb0098f
refactor: removed debug lines
Palladium103 May 1, 2026
434e462
refactor: inline boolean eval
Palladium103 May 1, 2026
f334e7d
refactor: discarded third param in Battle class
Palladium103 May 1, 2026
dcde024
refactor: removed parenthesis, renamed hasCitadel
Palladium103 May 1, 2026
75f6c68
refactor: removed brackets
Palladium103 May 1, 2026
aa6616f
adjustment: bombardmen melee adjusted from 0.1 to 0.3
Palladium103 May 1, 2026
5d84beb
fix: changed byId to ensureEl
Palladium103 May 2, 2026
b6270fd
.
Palladium103 May 2, 2026
70f1326
improvement: added another guard clause
Palladium103 May 7, 2026
230bdac
refactor: stricter conversion
Palladium103 May 7, 2026
a340a03
refactor: grammar corrected
Palladium103 May 7, 2026
c39fdf4
refactor: whitespace after inline brackets removed
Palladium103 May 7, 2026
e6034cf
refactor: grammer corrected
Palladium103 May 7, 2026
6728e57
fix: operator precedence
Palladium103 May 7, 2026
b7bb7fb
refactor: removed copy pasted code
Palladium103 May 7, 2026
3e2158e
refactor: guard clause ordering
Palladium103 May 7, 2026
a8b5d43
Merge branch 'master' into allow-burg-attack
Palladium103 May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 46 additions & 46 deletions public/modules/ui/battle-screen.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Battle {
title: this.name,
resizable: false,
width: fitContent(),
position: {my: "center", at: "center", of: "#map"},
position: { my: "center", at: "center", of: "#map" },
close: () => Battle.prototype.context.cancelResults()
});

Expand Down Expand Up @@ -73,7 +73,7 @@ class Battle {
if (attacker.n && defender.n) return "naval"; // attacker and defender are navals
if (typesA.every(t => t === "aviation") && typesD.every(t => t === "aviation")) return "air"; // if attackers and defender have only aviation units
if (attacker.n && !defender.n && typesA.some(t => t !== "naval")) return "landing"; // if attacked is naval with non-naval units and defender is not naval
if (!defender.n && pack.burgs[pack.cells.burg[this.cell]].walls) return "siege"; // defender is in walled town
if (!defender.n && (pack.burgs[pack.cells.burg[this.cell]].walls || pack.burgs[pack.cells.burg[this.cell]].citadel)) return "siege"; // defender is in walled town
if (P(0.1) && [5, 6, 7, 8, 9, 12].includes(pack.cells.biome[this.cell])) return "ambush"; // 20% if defenders are in forest or marshes
return "field";
};
Expand Down Expand Up @@ -156,9 +156,9 @@ class Battle {
<rect x="0" y="0" width="100%" height="100%" fill="${color}"></rect>${iconHtml}</svg>`;
const body = `<tbody id="battle${state.i}-${regiment.i}">`;

let initial = `<tr class="battleInitial"><td>${icon}</td><td class="regiment" data-tip="${
regiment.name
}">${regiment.name.slice(0, 24)}</td>`;
let initial = `<tr class="battleInitial"><td>${icon}</td><td
class="regiment" data-tip="${regiment.name
}">${regiment.name.slice(0, 24)}</td>`;
let casualties = `<tr class="battleCasualties"><td></td><td data-tip="${state.fullName}">${state.fullName.slice(
0,
26
Expand All @@ -168,18 +168,19 @@ class Battle {
for (const u of options.military) {
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${
regiment.u[u.name] || 0
}</td>`;
}</td>`;
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td>`;
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${
regiment.u[u.name] || 0
}</td>`;
}</td>`;
}

initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.a || 0}</td></tr>`;
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${
regiment.a || 0}</td></tr>`;
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td></tr>`;
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${
regiment.a || 0
}</td></tr>`;
}</td></tr>`;

const div = side === "attackers" ? battleAttackers : battleDefenders;
div.innerHTML += body + initial + casualties + survivors + "</tbody>";
Expand All @@ -204,13 +205,11 @@ class Battle {
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}
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; stroke: #333"><rect x="0" y="0" width="100%" height="100%" fill="${
s.color
}" ></svg>
<svg width=".9em" height=".9em" style="margin-bottom:-1px; stroke: #333"><rect x="0" y="0" width="100%" height="100%" fill="${s.color
}" ></svg>
Comment thread
Palladium103 marked this conversation as resolved.
<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>
Expand All @@ -224,7 +223,7 @@ class Battle {
resizable: false,
width: fitContent(),
title: "Add regiment to the battle",
position: {my: "left center", at: "right+10 center", of: "#battleScreen"},
position: { my: "left center", at: "right+10 center", of: "#battleScreen" },
close: addSideClosed,
buttons: {
"Add to attackers": () => addSideClicked("attackers"),
Expand Down Expand Up @@ -290,7 +289,7 @@ class Battle {

changeName(ev) {
this.name = ev.target.value;
$("#battleScreen").dialog({title: this.name});
$("#battleScreen").dialog({ title: this.name });
}

generateName(type) {
Expand All @@ -300,7 +299,7 @@ class Battle {
: Names.getBase(rand(nameBases.length - 1));
ensureEl("battleNamePlace").value = this.place = place;
ensureEl("battleNameFull").value = this.name = this.defineName();
$("#battleScreen").dialog({title: this.name});
$("#battleScreen").dialog({ title: this.name });
}

getJoinedForces(regiments) {
Expand All @@ -314,6 +313,7 @@ class Battle {
}

calculateStrength(side) {
const hasCitadel = pack.burgs[pack.cells.burg[this.cell]].citadel;
const scheme = {
// field battle phases
skirmish: {
Expand Down Expand Up @@ -365,42 +365,42 @@ class Battle {

// siege phases
blockade: {
melee: 0.25,
ranged: 0.25,
mounted: 0.2,
machinery: 0.5,
naval: 0.2,
armored: 0.1,
aviation: 0.25,
magical: 0.25
melee: hasCitadel ? 0.08 : 0.25,
ranged: hasCitadel ? 0.20 : 0.25,
mounted: hasCitadel ? 0.03 : 0.2,
machinery: hasCitadel ? 0.8 : 0.5,
naval: hasCitadel ? 0.06 : 0.2,
armored: hasCitadel ? 0.09 : 0.1,
aviation: hasCitadel ? 0.25 : 0.25,
magical: hasCitadel ? 0.025 : 0.25
}, // no active actions
sheltering: {
melee: 0.3,
ranged: 0.5,
mounted: 0.2,
machinery: 0.5,
naval: 0.2,
armored: 0.1,
aviation: 0.25,
magical: 0.25
melee: hasCitadel ? 0.4 : 0.3,
ranged: hasCitadel ? 0.8 : 0.5,
mounted: hasCitadel ? 0.08 : 0.2,
machinery: hasCitadel ? 0.6 : 0.5,
naval: hasCitadel ? 0.01 : 0.2,
armored: hasCitadel ? 0.1 : 0.1,
aviation: hasCitadel ? 0.1 : 0.25,
magical: hasCitadel ? 0.25 : 0.25
}, // no active actions
sortie: {melee: 2, ranged: 0.5, mounted: 1.2, machinery: 0.2, naval: 0.1, armored: 0.5, aviation: 1, magical: 1}, // melee excel
bombardment: {
melee: 0.2,
ranged: 0.5,
mounted: 0.2,
machinery: 3,
naval: 1,
melee: hasCitadel ? 0.3 : 0.2,
ranged: hasCitadel ? 0.6 : 0.5,
mounted: hasCitadel ? 0.25 : 0.2,
machinery: hasCitadel ? 0.4 : 3,
naval: hasCitadel ? 0.1 : 1,
armored: 0.5,
aviation: 1,
magical: 1
}, // machinery excel
storming: {
melee: 1,
ranged: 0.6,
mounted: 0.5,
machinery: 1,
naval: 0.1,
melee: hasCitadel ? 0.8 : 1,
ranged: hasCitadel ? 0.5 : 0.6,
mounted: hasCitadel ? 0.3 : 0.5,
machinery: hasCitadel ? 0.5 : 1,
naval: hasCitadel ? 0.01 : 0.1,
armored: 0.1,
aviation: 0.5,
magical: 0.5
Expand Down Expand Up @@ -782,7 +782,7 @@ class Battle {
button.style.opacity = 0.5;
div.style.display = "block";

document.getElementsByTagName("body")[0].on("click", hideSection, {once: true});
document.getElementsByTagName("body")[0].on("click", hideSection, { once: true });
}

changeType(ev) {
Expand All @@ -793,7 +793,7 @@ class Battle {
this.calculateStrength("attackers");
this.calculateStrength("defenders");
this.name = this.defineName();
$("#battleScreen").dialog({title: this.name});
$("#battleScreen").dialog({ title: this.name });
}

changePhase(ev, side) {
Expand Down
114 changes: 114 additions & 0 deletions public/modules/ui/regiment-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function editRegiment(selector) {
ensureEl("regimentName").addEventListener("change", changeName);
ensureEl("regimentEmblemChange").addEventListener("click", changeEmblem);
ensureEl("regimentAttack").addEventListener("click", toggleAttack);
ensureEl("regimentBurgAttack").addEventListener("click", toggleBurgAttack);
ensureEl("regimentRegenerateLegend").addEventListener("click", regenerateLegend);
ensureEl("regimentLegend").addEventListener("click", editLegend);
ensureEl("regimentSplit").addEventListener("click", splitRegiment);
Expand Down Expand Up @@ -254,6 +255,7 @@ function editRegiment(selector) {
}

function toggleAttack() {
ensureEl("regimentBurgAttack").classList.remove("pressed");
ensureEl("regimentAttack").classList.toggle("pressed");
if (ensureEl("regimentAttack").classList.contains("pressed")) {
viewbox.style("cursor", "crosshair").on("click", attackRegimentOnClick);
Expand All @@ -266,6 +268,117 @@ function editRegiment(selector) {
}
}

function toggleBurgAttack() {
ensureEl("regimentAttack").classList.remove("pressed");
ensureEl("regimentBurgAttack").classList.toggle("pressed");
if (ensureEl("regimentBurgAttack").classList.contains("pressed")) {
viewbox.style("cursor", "crosshair").on("click", attackBurgOnClick);
tip("Click on another burg to initiate a siege", true);
armies.selectAll(":scope > g").classed("draggable", false);
} else {
clearMainTip();
armies.selectAll(":scope > g").classed("draggable", true);
viewbox.on("click", clicked).style("cursor", "default");
}
}

function attackBurgOnClick() {
const target = d3.event.target,
burgSelected = target.parentElement,
burg = burgSelected.parentElement;
const burgId = target.dataset.id;

if (pack.states[pack.burgs[burgId].state].military.length == 0) {
tip("Burg's state has no military", false, "error");
return;
}
if (String(burg.id) != "burgIcons") {
tip("Please click on a burg to attack", false, "error");
return;
}
if (getRegiment().state == pack.burgs[burgId].state) {
tip("Cannot attack fraternal burg", false, "error");
return;
}
if (pack.burgs[burgId].walls != 1 && pack.burgs[burgId].citadel != 1) {
tip("Please click on a burg with walls or a citadel to attack", false, "error");
return;
}
if (pack.burgs[burgId].type != "Naval" && getRegiment().n) {
tip("Burg is too far from sea, regiment cannot engage. Please choose a burg near the shoreline.", false, "error");
return;
}


const attacker = getRegiment();
const burgStateId = Number(pack.burgs[burgId].state);
const burgStateRegiments = pack.states[burgStateId].military;
const defendingBurg = target;
const dx = +defendingBurg.getAttribute("x");
const dy = +defendingBurg.getAttribute("y");
const ax = +attacker.getAttribute("x");
const ay = +attacker.getAttribute("y");

let closestIndex = -1;
let closestDistance = Infinity;

burgStateRegiments.forEach((el, i) => {
if (el.t === 0 || !el.a) return;

const distX = el.x - dx;
const distY = el.y - dy;
const dist = Math.sqrt(distX * distX + distY * distY);

if (dist < closestDistance) {
closestDistance = dist;
closestIndex = i;
}
});

if (closestIndex == -1 && pack.states[pack.burgs[burgId].state].military.length == 1)
closestIndex = 0;
if (closestIndex == -1)
return;
const defendingRegiment = pack.states[pack.burgs[burgId].state].military[closestIndex];
if (!attacker.a || !defendingRegiment.a) {
tip("Regiment has no troops to battle", false, "error");
return;
}
Comment thread
Palladium103 marked this conversation as resolved.

// save initial position to temp attribute
(attacker.px = attacker.x), (attacker.py = attacker.y);
(defendingRegiment.px = defendingRegiment.x), (defendingRegiment.py = defendingRegiment.y);

// move attacker to burg
moveRegiment(attacker, dx, dy - 8);
// Move defender to burg
moveRegiment(defendingRegiment, ax, ay);
// draw battle icon
const attack = d3
.transition()
.delay(300)
.duration(700)
.ease(d3.easeSinInOut)
.on("end", () => new Battle(attacker, defendingRegiment));
svg
.append("text")
.attr("text-rendering", "optimizeSpeed")
.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.2)
.remove();

clearMainTip();
$("#regimentEditor").dialog("close");
}

function attackRegimentOnClick() {
const target = d3.event.target,
regSelected = target.parentElement,
Expand Down Expand Up @@ -487,6 +600,7 @@ function editRegiment(selector) {
armies.selectAll("g>g").call(d3.drag().on("drag", null));
ensureEl("regimentAdd").classList.remove("pressed");
ensureEl("regimentAttack").classList.remove("pressed");
ensureEl("regimentBurgAttack").classList.remove("pressed");
ensureEl("regimentAttach").classList.remove("pressed");
restoreDefaultEvents();
elSelected = null;
Expand Down
3 changes: 2 additions & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3756,6 +3756,7 @@

<div id="regimentBottom">
<button id="regimentAttack" data-tip="Attack foreign regiment" class="icon-target"></button>
<button id="regimentBurgAttack" data-tip="Attack foreign burg" class="icon-flag"></button>
<button id="regimentAdd" data-tip="Create a new regiment or fleet" class="icon-user-plus"></button>
<button id="regimentSplit" data-tip="Split regiment into 2 separate ones" class="icon-half"></button>
<button
Expand Down Expand Up @@ -5947,7 +5948,7 @@
</div>

<div style="margin-top: 0.5em">
<b>External images</b>
<b>External images (Links that end with .png, .jpg, or .svg)</b>
<div style="font-style: italic">
<span>Paste link to the image here: </span>
<input id="imageInput" style="width: 20em" />
Expand Down
2 changes: 1 addition & 1 deletion src/modules/military-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ class MilitaryModule {
return `${number}${proper ? ` (${proper}) ` : ` `}${form}`;
}

// utilize si function to make regiment total text fit regiment box
// utilize this function to make regiment total text fit regiment box
getTotal(reg: MilitaryRegiment) {
return reg.a > (reg.n ? 999 : 99999) ? si(reg.a) : reg.a;
}
Expand Down