diff --git a/index.html b/index.html
index 21ede5d..de4def4 100644
--- a/index.html
+++ b/index.html
@@ -1910,9 +1910,10 @@ Gamestate.drawMapText = function() {
if (areaOnMap && textNode) {
if(country.isCrater) { textNode.innerHTML = ""; return; }
- let text = `${country.army}`;
let iconHtml = "";
+
+ // 1. Check for Command Silo
if(this.nukesEnabled && country.isSilo) {
let isLaunchSite = (this.activeNuke && this.activeNuke.launchSilo === country.name);
let siloColor = isLaunchSite ? "#ff3333" : "#ffcc00";
@@ -1920,17 +1921,28 @@ Gamestate.drawMapText = function() {
iconHtml += `☢ `;
}
+ // 2. Check for Multiple Commanders
if(this.commandersEnabled) {
this.players.forEach(p => {
- if (p.alive && !p.isNeutral && p.commander && p.commander.loc === country.name) {
- iconHtml += `★ `;
+ // FIX: Added "hp > 0" so dead commanders don't leave a ghost star!
+ if (p.alive && !p.isNeutral && p.commander && p.commander.hp > 0 && p.commander.loc === country.name) {
+ // Added dx="2" to give multiple stars a tiny bit of breathing room
+ iconHtml += `★`;
}
});
}
- if (iconHtml !== "") { text += `${iconHtml}`; }
+ // 3. Perfect Stacking Output
+ // We grab the exact X coordinate of the text to force the stars to center directly over the number
+ let xCoord = textNode.getAttribute("x");
- textNode.innerHTML = text;
+ if (iconHtml !== "") {
+ // Stack the icons up (-12), then drop the army number back down (16)
+ textNode.innerHTML = `${iconHtml}${country.army}`;
+ } else {
+ // If no icons, just reset the army number to normal
+ textNode.innerHTML = `${country.army}`;
+ }
}
});
}
@@ -2799,13 +2811,17 @@ Gamestate.maneuver = async function(e){
let country = this.countries.find(c => c.name === e.target.id);
if(!country || country.isCrater) return;
- if(this.stage === "Commander Phase") {
+if(this.stage === "Commander Phase") {
if(!this.player.commander || this.player.commander.ap <= 0) return;
let cmdrLoc = this.countries.find(c => c.name === this.player.commander.loc);
if(!cmdrLoc) return;
- if(cmdrLoc.neighbours.includes(country.name)) {
- let enemyCmdr = this.players.find(p => p !== this.player && p.alive && !p.isNeutral && p.commander && p.commander.loc === country.name);
+ let isNeighbor = cmdrLoc.neighbours.includes(country.name);
+ let isCurrentLoc = (cmdrLoc.name === country.name);
+
+ if(isNeighbor || isCurrentLoc) {
+ // Look for an enemy commander on the clicked territory
+ let enemyCmdr = this.players.find(p => p !== this.player && p.alive && !p.isNeutral && p.commander && p.commander.hp > 0 && p.commander.loc === country.name);
if(enemyCmdr) {
if (this.player.commander.hasFought) {
@@ -2815,13 +2831,21 @@ Gamestate.maneuver = async function(e){
}
this.player.commander.ap -= 1;
- await this.logAction(`[ REGICIDE DUEL ] Commander initiated direct combat with ${enemyCmdr.name}'s Commander!`, true);
- // 3. Combat Math (Safe Home Advantage: 5-15 DMG Max)
+ // If we attacked from a neighbor, step into the territory to face them!
+ if (isNeighbor) {
+ this.player.commander.loc = country.name;
+ this.player.commander.siegeTurns = 0;
+ }
+
+ await this.logAction(`[ REGICIDE DUEL ] Commander initiated direct combat with ${enemyCmdr.name}'s Commander at ${formatTerritoryName(country.name)}!`, true);
+
+ // Combat Math (Safe Home Advantage: 5-15 DMG Max)
let rawDmgToEnemy = Math.floor(Math.random() * 16) + 10;
let rawDmgToSelf = Math.floor(Math.random() * 11) + 10;
- if (cmdrLoc.owner === this.player.name) { rawDmgToSelf = Math.floor(Math.random() * 11) + 5; }
+ // Check if the territory the duel is happening on belongs to you
+ if (country.owner === this.player.name) { rawDmgToSelf = Math.floor(Math.random() * 11) + 5; }
let cappedDmgToEnemy = Math.min(25, rawDmgToEnemy);
let cappedDmgToSelf = Math.min(25, rawDmgToSelf);
@@ -2838,7 +2862,8 @@ Gamestate.maneuver = async function(e){
if(this.player.commander.hp <= 0) await this.killCommander(this.player);
this.updateInfo();
- } else {
+ } else if (isNeighbor) {
+ // NO ENEMY COMMANDER - JUST MOVE
this.player.commander.loc = country.name; this.player.commander.ap -= 1;
this.player.commander.siegeTurns = 0; // Reset subversion timer on move
@@ -3346,10 +3371,15 @@ Gamestate.aiManeuver = function(i){
let player = this.players[i];
let owned = this.countries.filter(c => c.owner === player.name);
+ // Helper to check how many commanders are on a territory
+ let getCmdrCount = (locName) => {
+ return this.players.filter(p => p.alive && p.commander && p.commander.hp > 0 && p.commander.loc === locName).length;
+ };
+
// --- COMMANDER AI LOGIC ---
if(this.commandersEnabled && player.commander && player.commander.hp > 0) {
- // ---> NEW: FOOLPROOF RESET: Ensure the Commander wakes up ready for action! <---
+ // FOOLPROOF RESET: Wake up!
player.commander.ap = 2;
player.commander.hasFought = false;
player.commander.hasBeenAmbushed = false;
@@ -3364,20 +3394,28 @@ Gamestate.aiManeuver = function(i){
let neighbors = cmdrLoc.neighbours.map(n => this.countries.find(x=>x.name===n)).filter(c => c !== undefined);
let friendlyNeighbors = neighbors.filter(c => c.owner === player.name);
+ // Collect enemy commanders in adjacent territories OR on the SAME territory!
let enemyCmdrs = [];
- neighbors.forEach(n => {
- let eCmdr = this.players.find(p => p !== player && p.alive && !p.isNeutral && p.commander && p.commander.hp > 0 && p.commander.loc === n.name);
- if (eCmdr) enemyCmdrs.push(eCmdr);
+ this.players.forEach(p => {
+ if (p !== player && p.alive && !p.isNeutral && p.commander && p.commander.hp > 0) {
+ if (p.commander.loc === cmdrLoc.name || cmdrLoc.neighbours.includes(p.commander.loc)) {
+ enemyCmdrs.push(p);
+ }
+ }
});
enemyCmdrs.sort((a,b) => a.commander.hp - b.commander.hp);
- // PRIORITY 4: RETREAT (HP < 35%)
- if (player.commander.hp < 35) {
+ // PRIORITY 4: RETREAT (HP < 50% - Retreating sooner!)
+ if (player.commander.hp < 50) {
if (friendlyNeighbors.length > 0) {
- let safeSpots = friendlyNeighbors.filter(c => !c.neighbours.some(n => {
- let nc = this.countries.find(x=>x.name===n); return nc && nc.owner !== player.name;
- }));
- if (safeSpots.length === 0) safeSpots = friendlyNeighbors;
+ // Find a safe spot that isn't already crowded
+ let safeSpots = friendlyNeighbors.filter(c =>
+ !c.neighbours.some(n => { let nc = this.countries.find(x=>x.name===n); return nc && nc.owner !== player.name; }) &&
+ getCmdrCount(c.name) < 2
+ );
+ if (safeSpots.length === 0) safeSpots = friendlyNeighbors.filter(c => getCmdrCount(c.name) < 2);
+ if (safeSpots.length === 0) safeSpots = friendlyNeighbors; // Desperate fallback
+
let silo = safeSpots.find(c => this.nukesEnabled && c.isSilo);
safeSpots.sort((a,b) => b.army - a.army);
let retreatTarget = silo ? silo : safeSpots[0];
@@ -3390,8 +3428,10 @@ Gamestate.aiManeuver = function(i){
continue;
}
} else if (neighbors.length > 0) {
- // DESPERATE ESCAPE: Completely surrounded! Run anywhere!
- let escapeRoute = neighbors[Math.floor(Math.random() * neighbors.length)];
+ // DESPERATE ESCAPE: Run anywhere to survive, but avoid crowding!
+ let escapeOptions = neighbors.filter(c => getCmdrCount(c.name) < 2);
+ if (escapeOptions.length === 0) escapeOptions = neighbors;
+ let escapeRoute = escapeOptions[Math.floor(Math.random() * escapeOptions.length)];
player.commander.loc = escapeRoute.name;
player.commander.ap -= 1; movedOrAction = true;
player.commander.siegeTurns = 0;
@@ -3400,34 +3440,46 @@ Gamestate.aiManeuver = function(i){
}
}
- // PRIORITIES 1 & 3: ATTACK ENEMY COMMANDERS
+ // PRIORITIES 1 & 3: DUEL ENEMY COMMANDERS
if (!movedOrAction && !player.commander.hasFought) {
let targetToDuel = null;
for (let target of enemyCmdrs) {
let targetLoc = this.countries.find(c => c.name === target.commander.loc);
+ // Don't dive into heavily crowded territories
+ if (target.commander.loc !== cmdrLoc.name && getCmdrCount(target.commander.loc) >= 2) continue;
+
let isHomeDefense = (cmdrLoc.owner === player.name || (targetLoc && targetLoc.owner === player.name));
- let isStranded = (cmdrLoc.owner !== player.name); // Be aggressive if deep in enemy lines!
- let isHealthy = (player.commander.hp >= 50); // Lowered threshold for more action
- let isTargetWeak = (player.commander.hp > target.commander.hp + 10);
+ let isHealthy = (player.commander.hp >= 65); // Need solid HP to hunt
+ let isTargetWeak = (player.commander.hp > target.commander.hp + 20);
- if (isHomeDefense || isStranded || isHealthy || isTargetWeak) {
+ if (isHomeDefense || isHealthy || isTargetWeak) {
targetToDuel = target; break;
}
}
if (targetToDuel) {
- player.commander.ap -= 1; movedOrAction = true;
+ // Move to their territory if not already there
+ if (player.commander.loc !== targetToDuel.commander.loc) {
+ player.commander.loc = targetToDuel.commander.loc;
+ player.commander.ap -= 1;
+ player.commander.siegeTurns = 0;
+ } else {
+ player.commander.ap -= 1; // Uses 1 AP to fight on the same tile
+ }
+ movedOrAction = true;
+
let rawDmgToTarget = Math.floor(Math.random() * 16) + 10;
let rawDmgToSelf = Math.floor(Math.random() * 11) + 10;
- if (cmdrLoc.owner === player.name) {
+ // BUG FIX: Strictly check the territory they are standing on AFTER they moved to attack
+ let currentLocAfterMove = this.countries.find(c => c.name === player.commander.loc);
+
+ if (currentLocAfterMove && currentLocAfterMove.owner === player.name) {
rawDmgToSelf = Math.floor(Math.random() * 11) + 5;
player.commander.hp = Math.min(100, player.commander.hp + 10);
}
-
- let targetLoc = this.countries.find(c => c.name === targetToDuel.commander.loc);
- if (targetLoc && targetLoc.owner === targetToDuel.name) {
+ if (currentLocAfterMove && currentLocAfterMove.owner === targetToDuel.name) {
rawDmgToTarget = Math.floor(Math.random() * 11) + 5;
}
@@ -3440,19 +3492,21 @@ Gamestate.aiManeuver = function(i){
targetToDuel.commander.wasAttacked = true;
player.commander.wasAttacked = true;
- if (Gamestate.logAction) Gamestate.logAction(`[ REGICIDE DUEL ] ${player.name} attacked ${targetToDuel.name}'s Commander! (Dealt ${cappedDmgToTarget} DMG, Took ${cappedDmgToSelf} DMG)`, true);
+ if (Gamestate.logAction) Gamestate.logAction(`[ REGICIDE DUEL ] ${player.name} engaged ${targetToDuel.name}'s Commander at ${formatTerritoryName(currentLocAfterMove.name)}! (Dealt ${cappedDmgToTarget} DMG, Took ${cappedDmgToSelf} DMG)`, true);
if(targetToDuel.commander.hp <= 0) this.killCommander(targetToDuel);
if(player.commander.hp <= 0) this.killCommander(player);
continue;
}
}
- // PRIORITY 2: FRONTLINE DEFENSE BUFF OR STRANDED WANDERING
+ // PRIORITY 2: TACTICAL POSITIONING (Don't stray too far)
if (!movedOrAction) {
if (friendlyNeighbors.length > 0) {
- let chokePoints = friendlyNeighbors.filter(c => c.neighbours.some(n => {
- let nc = this.countries.find(x=>x.name===n); return nc && nc.owner !== player.name && !nc.isCrater;
- }));
+ // Find a border territory (chokepoint) that isn't crowded
+ let chokePoints = friendlyNeighbors.filter(c =>
+ getCmdrCount(c.name) < 2 &&
+ c.neighbours.some(n => { let nc = this.countries.find(x=>x.name===n); return nc && nc.owner !== player.name && !nc.isCrater; })
+ );
if (chokePoints.length > 0) {
chokePoints.sort((a,b) => b.army - a.army);
if (chokePoints[0].name !== cmdrLoc.name) {
@@ -3462,21 +3516,37 @@ Gamestate.aiManeuver = function(i){
continue;
}
} else if (cmdrLoc.owner !== player.name) {
- friendlyNeighbors.sort((a,b) => b.army - a.army);
- player.commander.loc = friendlyNeighbors[0].name;
+ // Stranded? Go back to friendly land!
+ let uncrowdedFriendly = friendlyNeighbors.filter(c => getCmdrCount(c.name) < 2);
+ if(uncrowdedFriendly.length === 0) uncrowdedFriendly = friendlyNeighbors;
+ uncrowdedFriendly.sort((a,b) => b.army - a.army);
+ player.commander.loc = uncrowdedFriendly[0].name;
player.commander.ap -= 1; movedOrAction = true;
player.commander.siegeTurns = 0;
}
} else {
- // ---> NEW: COMMANDER IS STRANDED AND NO ONE IS AROUND TO FIGHT! <---
- // 30% chance to roam randomly (so they don't look frozen), 70% chance to sit still and incite a rebellion!
- if (neighbors.length > 0 && Math.random() < 0.30) {
- let wanderTarget = neighbors[Math.floor(Math.random() * neighbors.length)];
- player.commander.loc = wanderTarget.name;
+ // STRANDED TACTICS: Try to path TOWARDS home instead of randomly wandering.
+ let pathHome = neighbors.find(n => {
+ let nc = this.countries.find(x=>x.name===n);
+ return nc && nc.neighbours.some(nn => { let nnc = this.countries.find(x=>x.name===nn); return nnc && nnc.owner === player.name; });
+ });
+
+ if (pathHome && getCmdrCount(pathHome) < 2) {
+ player.commander.loc = pathHome;
player.commander.ap -= 1; movedOrAction = true;
player.commander.siegeTurns = 0;
- if (Gamestate.logAction) Gamestate.logAction(`VIP MOVEMENT: ${player.name}'s stranded Commander is roaming through ${formatTerritoryName(wanderTarget.name)}.`);
+ if (Gamestate.logAction) Gamestate.logAction(`VIP MOVEMENT: ${player.name}'s stranded Commander is falling back towards friendly lines!`);
continue;
+ } else if (neighbors.length > 0 && Math.random() < 0.20) {
+ // Only a 20% chance to roam randomly if totally lost
+ let uncrowded = neighbors.filter(c => getCmdrCount(c.name) < 2);
+ if(uncrowded.length > 0) {
+ let wanderTarget = uncrowded[Math.floor(Math.random() * uncrowded.length)];
+ player.commander.loc = wanderTarget.name;
+ player.commander.ap -= 1; movedOrAction = true;
+ player.commander.siegeTurns = 0;
+ continue;
+ }
}
}
}