diff --git a/public/modules/ui/battle-screen.js b/public/modules/ui/battle-screen.js index 5b441a7b4..dff70f42b 100644 --- a/public/modules/ui/battle-screen.js +++ b/public/modules/ui/battle-screen.js @@ -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() }); @@ -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"; }; @@ -156,9 +156,9 @@ class Battle { ${iconHtml}`; const body = ``; - let initial = `${icon}${regiment.name.slice(0, 24)}`; + let initial = `${icon}${regiment.name.slice(0, 24)}`; let casualties = `${state.fullName.slice( 0, 26 @@ -168,18 +168,19 @@ class Battle { for (const u of options.military) { initial += `${ regiment.u[u.name] || 0 - }`; + }`; casualties += `0`; survivors += `${ regiment.u[u.name] || 0 - }`; + }`; } - initial += `${regiment.a || 0}`; + initial += `${ + regiment.a || 0}`; casualties += `0`; survivors += `${ regiment.a || 0 - }`; + }`; const div = side === "attackers" ? battleAttackers : battleDefenders; div.innerHTML += body + initial + casualties + survivors + ""; @@ -204,13 +205,11 @@ class Battle { const s = pack.states[r.state], added = isAdded(r), dist = added ? "0 " + distanceUnitInput.value : distance(r); - return `
- +
${s.name.slice(0, 11)}
${r.icon}
${r.name.slice(0, 24)}
@@ -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"), @@ -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) { @@ -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) { @@ -314,6 +313,7 @@ class Battle { } calculateStrength(side) { + const hasCitadel = pack.burgs[pack.cells.burg[this.cell]].citadel; const scheme = { // field battle phases skirmish: { @@ -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 @@ -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) { @@ -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) { diff --git a/public/modules/ui/regiment-editor.js b/public/modules/ui/regiment-editor.js index 3f9253519..9f7d8410d 100644 --- a/public/modules/ui/regiment-editor.js +++ b/public/modules/ui/regiment-editor.js @@ -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); @@ -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); @@ -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; + } + + // 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, @@ -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; diff --git a/src/index.html b/src/index.html index 11dbf51e0..7d55e344e 100644 --- a/src/index.html +++ b/src/index.html @@ -3756,6 +3756,7 @@
+