diff --git a/index.html b/index.html
index f7fd378..b9a0361 100644
--- a/index.html
+++ b/index.html
@@ -89,6 +89,20 @@
cursor: auto;
}
+ /* Protect UI icons from being hijacked by the game board's Map CSS */
+ .ui-svg-icon path, .ui-svg-icon polyline {
+ stroke: currentColor !important;
+ fill: none !important;
+ }
+
+ /* Brute-force the SVG lines to turn dark when the button is hovered */
+ button#btn-save-game:hover svg path,
+ button#btn-save-game:hover svg polyline,
+ button#btn-load-game:hover svg path,
+ button#btn-load-game:hover svg polyline {
+ stroke: #1a1a1a !important;
+ }
+
/* Standard pointer for interactive elements */
button,
select,
@@ -105,6 +119,53 @@
/* No cursor property here - it will be added dynamically */
}
+ /* GOAL 1: Leaderboard Pulsing Animation */
+ @keyframes activeTurnPulse {
+ 0% { box-shadow: 0 0 5px var(--pip-color), inset 0 0 2px var(--pip-color); border-color: var(--pip-color); transform: scale(1); }
+ 100% { box-shadow: 0 0 25px var(--pip-color), inset 0 0 12px var(--pip-color); border-color: #ffffff; transform: scale(1.03); filter: brightness(1.2); }
+ }
+
+ /* Apply pulse to the highlighted card */
+ .highlight {
+ animation: activeTurnPulse 0.5s infinite alternate !important;
+ z-index: 10;
+ position: relative;
+ }
+
+
+ /* Kill the pulse instantly if Turbo is active */
+ .turbo-active .highlight {
+ animation: none !important;
+ box-shadow: 0 0 5px var(--pip-color) !important;
+ border-color: var(--pip-color) !important;
+ }
+
+ /* GOAL 2: Make the "_" and "█" Cursors Actually Blink */
+ @keyframes blinkCursorAnim {
+ 0%, 49% { opacity: 1; }
+ 50%, 100% { opacity: 0; }
+ }
+ .blinking-cursor, .terminal-cursor {
+ animation: blinkCursorAnim 1s infinite !important;
+ font-weight: bold;
+ }
+
+ /* GOAL 3: Force ALL Commander Text to Match the UI Theme */
+ #cmdr-ui-container,
+ #cmdr-ui-container span,
+ #cmdr-ui-container div,
+ #cmdr-hp-text,
+ #cmdr-ap-text {
+ color: var(--pip-color) !important;
+ border-color: var(--pip-color) !important;
+ text-shadow: 0 0 5px var(--pip-color) !important;
+ }
+
+ /* Keep the HP/AP fill bars solid (don't make them transparent text) */
+ #cmdr-hp-fill, #cmdr-ap-fill {
+ text-shadow: none !important;
+ color: #1a1a1a !important; /* Dark text inside the bar so it's readable */
+ }
/* --- Modals --- */
.overlay {
@@ -1399,16 +1460,24 @@
}
}
- /* --- Developer Menu Button Fixes --- */
- #dev-modal button {
- /* Ensures text color does not change on hover */
- color: var(--pip-color) !important;
- }
+ /* Admin/Dev Menu Unified Buttons */
+ #dev-modal button {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 10px;
+ background-color: var(--pip-dark, #1a1a1a);
+ color: var(--pip-color);
+ border: 1px solid var(--pip-color);
+ font-family: inherit;
+ font-size: 16px;
+ cursor: pointer;
+ text-transform: uppercase;
+ }
+ #dev-modal button:hover {
+ background-color: var(--pip-color);
+ color: var(--pip-dark, #1a1a1a);
+ }
- #dev-modal button:hover {
- /* Sets a semi-transparent white background on hover for readability */
- background: rgba(255, 255, 255, 0.15);
- }
#dev-modal button:active {
/* Pushes the button down slightly and darkens it for click feedback */
@@ -1870,7 +1939,14 @@
-
+
+
@@ -3228,6 +3304,8 @@
+
+
@@ -3506,7 +3584,13 @@
-
+
@@ -3778,6 +3862,7 @@
const fileInput = document.getElementById('file-load-game');
if (loadBtn && fileInput) {
+
// 1. When the visible button is clicked, trigger the hidden file input
loadBtn.addEventListener('click', (e) => {
e.preventDefault();
@@ -4031,16 +4116,35 @@
const encounterData = {
creatures: [
- { name: "Savage Dog", threat: 0.1 }, { name: "Mole Rat", threat: 0.3 }, { name: "Bloatfly", threat: 0.4 },
- { name: "Radroach", threat: 0.5 }, { name: "Yao Guai", threat: 1.0 }, { name: "Feral Ghoul", threat: 1.2 },
- { name: "Giant Ant", threat: 1.5 }, { name: "Scavenger's Dog", threat: 1.8 }, { name: "Giant Worker Ant", threat: 2.0 },
- { name: "Radscorpion", threat: 2.5 }, { name: "Giant Soldier Ant", threat: 3.0 }, { name: "Fire Ant Soldier", threat: 3.5 },
- { name: "Guard Dog", threat: 4.0 }, { name: "Fire Ant Warrior", threat: 4.5 }, { name: "Mirelurk", threat: 5.0 },
- { name: "Feral Ghoul Roamer", threat: 6.0 }, { name: "Giant Radscorpion", threat: 7.0 }, { name: "Mirelurk Hunter", threat: 8.0 },
- { name: "Vicious Dog", threat: 9.0 }, { name: "Centaur", threat: 10.0 }, { name: "Feral Ghoul Reaver", threat: 12.0 },
- { name: "Deathclaw", threat: 15.0 }, { name: "Enclave Deathclaw", threat: 20.0 }, { name: "Super Mutant", threat: 25.0 },
- { name: "Super Mutant Master", threat: 30.0 }, { name: "Super Mutant Overlord", threat: 40.0 }, { name: "Super Mutant Behemoth", threat: 50.0 }
+ { name: "Centaur", threat: 10.0, isHumanoid: true },
+ { name: "Deathclaw", threat: 15.0, isHumanoid: false },
+ { name: "Enclave Deathclaw", threat: 20.0, isHumanoid: false },
+ { name: "Feral Ghoul", threat: 1.2, isHumanoid: true },
+ { name: "Feral Ghoul Reaver", threat: 12.0, isHumanoid: true },
+ { name: "Feral Ghoul Roamer", threat: 6.0, isHumanoid: true },
+ { name: "Fire Ant Soldier", threat: 3.5, isHumanoid: false },
+ { name: "Fire Ant Warrior", threat: 4.5, isHumanoid: false },
+ { name: "Giant Ant", threat: 1.5, isHumanoid: false },
+ { name: "Giant Radscorpion", threat: 7.0, isHumanoid: false },
+ { name: "Giant Soldier Ant", threat: 3.0, isHumanoid: false },
+ { name: "Giant Worker Ant", threat: 2.0, isHumanoid: false },
+ { name: "Guard Dog", threat: 4.0, isHumanoid: false },
+ { name: "Mirelurk", threat: 5.0, isHumanoid: false },
+ { name: "Mirelurk Hunter", threat: 8.0, isHumanoid: false },
+ { name: "Mole Rat", threat: 0.3, isHumanoid: false },
+ { name: "Radroach", threat: 0.5, isHumanoid: false },
+ { name: "Radscorpion", threat: 2.5, isHumanoid: false },
+ { name: "Savage Dog", threat: 0.1, isHumanoid: false },
+ { name: "Scavenger's Dog", threat: 1.8, isHumanoid: false },
+ { name: "Super Mutant", threat: 25.0, isHumanoid: true },
+ { name: "Super Mutant Behemoth", threat: 50.0, isHumanoid: true },
+ { name: "Super Mutant Master", threat: 30.0, isHumanoid: true },
+ { name: "Super Mutant Overlord", threat: 40.0, isHumanoid: true },
+ { name: "Vicious Dog", threat: 9.0, isHumanoid: false },
+ { name: "Yao Guai", threat: 1.0, isHumanoid: false },
+ { name: "Bloatfly", threat: 0.4, isHumanoid: false }
],
+
genericLocations: ["a crashed Vertibird", "a ruined highway overpass", "an abandoned Red Rocket station", "a Pre-War police station", "a collapsed metro tunnel", "a flooded sewer system", "a ruined church", "an abandoned drive-in theater", "a Broadcasting Tower", "an Abandoned Shack"],
subLocations: ["the manager's office", "the pharmacy counter", "the main server room", "the evidence locker", "the cockpit", "the projection booth", "the sacristy", "the underground pump station", "a hidden Refrigerator", "a locked Safe"],
people: ["a Wasteland Doctor", "a scared Settler", "a shifty-eyed Mercenary", "a Ghoul scavenger", "a Brotherhood of Steel scribe", "a Fugitive Slave", "a Wounded Sheriff", "a Merchant", "Talon Company Mercs", "a Drunken Drifter", "a Lost Farmer", "a Mister Handy"],
@@ -4108,7 +4212,8 @@
const infoIncome = Array.from(document.getElementsByClassName('income'));
const areas = Array.from(document.getElementsByClassName('area'));
const bar = Array.from(document.getElementsByClassName('bar'));
- const map = document.querySelector('svg');
+ const map = document.querySelector('.area').closest('svg');
+
const vatsTooltip = document.createElement('div');
vatsTooltip.id = 'vats-tooltip';
@@ -4230,7 +4335,14 @@
if (targetCountry.isLockedDown) {
infoLines.push(`TERRITORY LOCKED DOWN`);
}
+
+ // NEW: Searching status
+ if (targetCountry.isExploring) {
+ infoLines.push(`SEARCHING... (${targetCountry.exploreTurnsLeft} TURNS LEFT)`);
+ }
+
infoLines.push(`TERRITORY: ${formatTerritoryName(targetCountry.name)}`);
+
if (targetCountry.isCrater) {
infoLines.push(`IRRADIATED CRATER (IMPASSABLE)`);
} else {
@@ -4911,6 +5023,24 @@
document.getElementById('dev-modal').style.display = 'none';
if (helpModal) helpModal.style.display = 'block';
});
+ // --- NEW DEV GOD MODE BUTTON ---
+ const godModeBtn = document.getElementById('dev-godmode');
+ if (godModeBtn) {
+ godModeBtn.addEventListener('click', (e) => {
+ Gamestate.godMode = !Gamestate.godMode;
+
+ // Dynamically update the button text!
+ e.target.textContent = Gamestate.godMode ? "TURN GM OFF" : "TURN GM ON";
+
+ if (Gamestate.showToast) {
+ Gamestate.showToast(`Dev: AI Ignore is now ${Gamestate.godMode ? 'ON' : 'OFF'}`, Gamestate.godMode ? "#ff3333" : "grey");
+ }
+ if (Gamestate.logAction) {
+ Gamestate.logAction(`[ OVERSEER ] AI Ignore Protocol: ${Gamestate.godMode ? 'ENGAGED' : 'DISABLED'}.`, true);
+ }
+ });
+ }
+
// --- FIX: RESTORES THE PATCH NOTES BUTTONS ---
@@ -4926,6 +5056,19 @@
document.getElementById('confirm-yes')?.addEventListener('click', () => { document.getElementById('confirm-restart-modal').style.display = 'none'; this.restart(); });
document.getElementById('confirm-no')?.addEventListener('click', () => { document.getElementById('confirm-restart-modal').style.display = 'none'; });
+ // Instantly toggle the sliding/pulsing animations when Turbo is clicked
+ const turboToggleBtn = document.getElementById('turbo-toggle');
+ if (turboToggleBtn) {
+ turboToggleBtn.addEventListener('change', (e) => {
+ if (e.target.checked) {
+ document.body.classList.add('turbo-active');
+ } else {
+ document.body.classList.remove('turbo-active');
+ }
+ });
+ }
+
+
document.getElementById('view-cards-btn')?.addEventListener('click', () => {
if (this.wastelandEconomyActive) {
this.showRecruitmentModal();
@@ -4943,7 +5086,15 @@
// DEV CONSOLE BUTTONS
document.getElementById('close-dev-btn')?.addEventListener('click', () => { document.getElementById('dev-modal').style.display = 'none'; });
- document.getElementById('dev-caps')?.addEventListener('click', () => { for (let i = 0; i < 10; i++) this.player.cards.push({ country: "Cheat", type: "Wild" }); this.updateInfo(); this.showToast("Dev: +10 Caps"); });
+ document.getElementById('dev-caps')?.addEventListener('click', () => {
+ if (this.wastelandEconomyActive) {
+ this.player.caps += 10;
+ } else {
+ for (let i = 0; i < 10; i++) this.player.cards.push({ country: "Cheat", type: "Wild" });
+ }
+ this.updateInfo();
+ this.showToast("Dev: +10 Caps/Cards");
+ });
document.getElementById('dev-code')?.addEventListener('click', () => { this.player.codes++; this.updateInfo(); this.showToast("Dev: +1 Launch Code"); });
document.getElementById('dev-troops')?.addEventListener('click', () => {
this.player.reserve += 100;
@@ -5701,7 +5852,7 @@ Gamestate.openDiplomacy = function (targetName) {
if (repScore >= 35) { repLabel = "IDOLIZED"; repColor = "#00ff00"; }
else if (repScore >= 10) { repLabel = "LIKED"; repColor = "#88ff88"; }
else if (repScore <= -35) { repLabel = "HATED"; repColor = "#ff0000"; }
- else if (repScore <= -10) { repLabel = "HOSTILE"; repColor = "#ff8888"; }
+ else if (repScore <= -10) { repLabel = "VILIFIED"; repColor = "#ff8888"; }
let repDisplay = document.getElementById('dip-rep-display');
repDisplay.textContent = `REP: ${repLabel}`;
repDisplay.style.color = repColor;
@@ -5716,13 +5867,32 @@ Gamestate.openDiplomacy = function (targetName) {
let theirMaxCurrency = this.wastelandEconomyActive ? target.caps : target.cards.length;
if (this.wastelandEconomyActive) {
- baseCostPerTurn = 5 + (tax * 5); // Minimum 5 Caps
- if (target.army > this.player.army) baseCostPerTurn = 10 + (tax * 5);
- if (target.army > (this.player.army * 2)) baseCostPerTurn = 15 + (tax * 5);
+ baseCostPerTurn = 5 + (tax * 5); // Base cost 5 Caps + Betrayal Tax
- if (repScore >= 35) baseCostPerTurn = Math.max(0, baseCostPerTurn - 10);
- else if (repScore >= 10) baseCostPerTurn = Math.max(0, baseCostPerTurn - 5);
+ // Army Size Modifiers
+ if (target.army > (this.player.army * 2)) {
+ baseCostPerTurn += 5;
+ } else if (target.army > this.player.army) {
+ baseCostPerTurn += 2;
+ } else if (target.army < this.player.army) {
+ baseCostPerTurn -= 2;
+ }
+
+ // Reputation Modifiers
+ if (repScore <= -35) {
+ baseCostPerTurn += 5; // Hated
+ } else if (repScore <= -10) {
+ baseCostPerTurn += 2; // Hostile
+ } else if (repScore >= 35) {
+ baseCostPerTurn -= 3; // Idolized
+ } else if (repScore >= 10) {
+ baseCostPerTurn -= 1; // Liked
+ }
+
+ // Failsafe: Truce always costs at least 1 Cap
+ baseCostPerTurn = Math.max(1, baseCostPerTurn);
} else {
+
baseCostPerTurn = 0.33 + (tax * 0.33); // Classic Cards
if (target.army > this.player.army) baseCostPerTurn = 0.66 + (tax * 0.33);
if (target.army > (this.player.army * 2)) baseCostPerTurn = 1.0 + (tax * 0.33);
@@ -5766,17 +5936,20 @@ Gamestate.openDiplomacy = function (targetName) {
reqTruceSlider.max = 0;
reqTruceSlider.value = 0;
reqTruceSlider.disabled = true;
- // --- FIX: Changed text to match your requirement. ---
- truceCostDisplay.textContent = `Requires at least ${Math.ceil(baseCostPerTurn)} ${currencyName} for a 1-turn Truce.`;
- truceCostDisplay.style.color = "#ff3333";
+ truceCostDisplay.textContent = `Requires at least ${Math.ceil(baseCostPerTurn)} ${currencyName} for a 1-day Truce.`;
+ truceCostDisplay.style.color = "var(--pip-color)";
+ truceCostDisplay.style.opacity = "0.7"; // Faded to indicate it's locked
} else {
// Unlock the slider
reqTruceSlider.max = maxAffordableTurns;
reqTruceSlider.value = 0;
reqTruceSlider.disabled = false;
- truceCostDisplay.textContent = `Cost: 0 ${currencyName}`;
- truceCostDisplay.style.color = "#ffcc00";
+ truceCostDisplay.textContent = `Cost: 0 ${currencyName} (${Math.ceil(baseCostPerTurn)}/Day)`;
+ truceCostDisplay.style.color = "var(--pip-color)";
+ truceCostDisplay.style.opacity = "1";
}
+
+
reqTruceVal.textContent = "0";
const reqCapsSlider = document.getElementById('dip-req-caps');
@@ -5790,17 +5963,36 @@ Gamestate.openDiplomacy = function (targetName) {
// 5. Update values on drag
offerCapsSlider.oninput = function () { offerCapsVal.textContent = this.value; validateProposal(); };
offerTroopsSlider.oninput = function () { offerTroopsVal.textContent = this.value; validateProposal(); };
- reqCapsSlider.oninput = function () { reqCapsVal.textContent = this.value; validateProposal(); };
- // --- FIX: Wrapped the original oninput logic in a new function to keep it clean. ---
+ reqCapsSlider.oninput = function () {
+ reqCapsVal.textContent = this.value;
+
+ // MUTUAL EXCLUSIVITY: If demanding caps, force Truce slider to 0
+ if (parseInt(this.value) > 0) {
+ reqTruceSlider.value = 0;
+ reqTruceVal.textContent = "0";
+ if (maxAffordableTurns >= 1) {
+ truceCostDisplay.textContent = `Cost: 0 ${currencyName}`;
+ }
+ }
+ validateProposal();
+ };
+
const handleTruceSliderInput = function() {
reqTruceVal.textContent = this.value;
+ // MUTUAL EXCLUSIVITY: If requesting a truce, force Demand Caps slider to 0
+ if (parseInt(this.value) > 0) {
+ reqCapsSlider.value = 0;
+ reqCapsVal.textContent = "0";
+ }
+
let truceTurnsRequested = parseInt(this.value);
let totalTruceCost = Math.ceil(baseCostPerTurn * truceTurnsRequested);
if (maxAffordableTurns >= 1) {
- truceCostDisplay.textContent = `Cost: ${totalTruceCost} ${currencyName}`;
+ // NEW: Keep the per-day rate visible while sliding
+ truceCostDisplay.textContent = `Cost: ${totalTruceCost} ${currencyName} (${Math.ceil(baseCostPerTurn)}/Day)`;
}
validateProposal();
@@ -5808,6 +6000,7 @@ Gamestate.openDiplomacy = function (targetName) {
reqTruceSlider.oninput = handleTruceSliderInput;
+
// 6. The Validation Logic
let validateProposal = () => {
let capsOffered = parseInt(offerCapsSlider.value);
@@ -5818,37 +6011,68 @@ Gamestate.openDiplomacy = function (targetName) {
let analysisEl = document.getElementById('dip-analysis');
let sendBtn = document.getElementById('dip-send');
- if (repScore <= -35 && (capsRequested > 0 || truceTurnsRequested > 0)) {
- analysisEl.innerHTML = `Refusal guaranteed. Target is hostile.`;
+ let isTerrified = (this.player.army >= target.army * 2);
+ if (repScore <= -35 && (capsRequested > 0 || truceTurnsRequested > 0) && !isTerrified) {
+ analysisEl.innerHTML = `Refusal guaranteed. Target is hostile and unyielding.`;
sendBtn.disabled = true;
return;
}
let offerValue = 0;
- let requestValue = capsRequested;
-
- if (this.wastelandEconomyActive) {
- offerValue = capsOffered + (troopsOffered * 5); // 1 Troop = 5 Caps
- } else {
- offerValue = capsOffered + (troopsOffered * 0.5);
- }
+ let requestValue = capsRequested; // Removed the Truce cost from here!
+ let softPower = 0;
let totalTruceCost = Math.ceil(baseCostPerTurn * truceTurnsRequested);
- requestValue += totalTruceCost;
+ let totalCurrencyCost = capsOffered + totalTruceCost; // Track combined Cap spending
+
+ // Failsafe: Prevent spending more than you have if combining Gifts + Truces
+ if (totalCurrencyCost > myMaxCurrency) {
+ analysisEl.innerHTML = `Insufficient funds. Combined cost exceeds your bank.`;
+ sendBtn.disabled = true;
+ return;
+ }
+
+ if (this.wastelandEconomyActive) {
+ offerValue = capsOffered + (troopsOffered * 5);
+ if (this.player.army > target.army) softPower += Math.floor((this.player.army - target.army) * 1.5);
+ if (repScore >= 35) softPower += 10;
+ } else {
+ offerValue = capsOffered + (troopsOffered * 0.5);
+ if (this.player.army > target.army) softPower += (this.player.army - target.army) * 0.15;
+ if (repScore >= 35) softPower += 1.0;
+ }
+ let effectiveOffer = offerValue + softPower;
+
+ // Auto-Pay Ceasefire Check
+ if (truceTurnsRequested > 0) {
+ if (offerValue > 0) {
+ analysisEl.innerHTML = `Purchasing Ceasefire + Generous Gift. Target will accept.`;
+ } else {
+ analysisEl.innerHTML = `Purchasing a ${truceTurnsRequested}-Turn Ceasefire for ${totalTruceCost} ${currencyName}.`;
+ }
+ sendBtn.disabled = false;
+ return;
+ }
+
// Is it a gift?
if (offerValue > 0 && requestValue === 0) {
- analysisEl.innerHTML = `Generous Gift. Significant Reputation increase expected.`;
+ analysisEl.innerHTML = `Generous Gift. Significant Reputation increase expected.`;
sendBtn.disabled = false;
return;
}
- // Is the trade fair?
- if (offerValue >= requestValue && requestValue > 0) {
- analysisEl.innerHTML = `Terms acceptable. Target likely to agree.`;
+ // Is the trade fair or forced?
+ if (effectiveOffer >= requestValue && requestValue > 0) {
+ if (offerValue < requestValue && softPower > 0) {
+ analysisEl.innerHTML = `Target will yield to your demands out of fear or respect.`;
+ } else {
+ analysisEl.innerHTML = `Terms acceptable. Target likely to agree.`;
+ }
sendBtn.disabled = false;
} else if (requestValue > 0) {
- analysisEl.innerHTML = `Terms insufficient. Offer more ${currencyName} or Troops.`;
+ // Faded opacity used here for rejections to differentiate without breaking the theme color
+ analysisEl.innerHTML = `Target refuses. You must offer a fair trade, or possess a much larger army to force compliance.`;
sendBtn.disabled = true;
} else {
analysisEl.innerHTML = `Awaiting terms...`;
@@ -5856,6 +6080,9 @@ Gamestate.openDiplomacy = function (targetName) {
}
};
+
+
+
validateProposal();
let modal = document.getElementById('diplomacy-modal');
@@ -5880,21 +6107,38 @@ Gamestate.openDiplomacy = function (targetName) {
let offerValue = 0;
let requestValue = proposal.capsRequested;
let baseCostPerTurn = 0;
+ let softPower = 0; // NEW: Intimidation / Reputation leverage
if (this.wastelandEconomyActive) {
// CAPS ECONOMY
offerValue = proposal.capsOffered + (proposal.troopsOffered * 5);
- baseCostPerTurn = 2 + (tax * 2);
- if (target.army > this.player.army) baseCostPerTurn = 4 + (tax * 2);
- if (target.army > (this.player.army * 2)) baseCostPerTurn = 6 + (tax * 2);
-
- if (repScore >= 35) baseCostPerTurn = Math.max(0, baseCostPerTurn - 4);
- else if (repScore >= 10) baseCostPerTurn = Math.max(0, baseCostPerTurn - 2);
+ if (this.player.army > target.army) {
+ softPower += Math.floor((this.player.army - target.army) * 1.5);
+ }
+ if (repScore >= 35) softPower += 10;
+
+ // Synced Math from openDiplomacy UI!
+ baseCostPerTurn = 5 + (tax * 5);
+ if (target.army > (this.player.army * 2)) baseCostPerTurn += 5;
+ else if (target.army > this.player.army) baseCostPerTurn += 2;
+ else if (target.army < this.player.army) baseCostPerTurn -= 2;
+
+ if (repScore <= -35) baseCostPerTurn += 5;
+ else if (repScore <= -10) baseCostPerTurn += 2;
+ else if (repScore >= 35) baseCostPerTurn -= 3;
+ else if (repScore >= 10) baseCostPerTurn -= 1;
+
+ baseCostPerTurn = Math.max(1, baseCostPerTurn);
} else {
// CLASSIC CARD ECONOMY
offerValue = proposal.capsOffered + (proposal.troopsOffered * 0.5);
+ if (this.player.army > target.army) {
+ softPower += (this.player.army - target.army) * 0.15;
+ }
+ if (repScore >= 35) softPower += 1.0;
+
baseCostPerTurn = 0.33 + (tax * 0.33);
if (target.army > this.player.army) baseCostPerTurn = 0.66 + (tax * 0.33);
if (target.army > (this.player.army * 2)) baseCostPerTurn = 1.0 + (tax * 0.33);
@@ -5903,25 +6147,28 @@ Gamestate.openDiplomacy = function (targetName) {
else if (repScore >= 10) baseCostPerTurn = Math.max(0, baseCostPerTurn - 0.33);
}
- // FIX: Check if truceRequested is greater than 0 (since it's a slider now)
+ let totalTruceCost = 0;
if (proposal.truceRequested > 0) {
- let totalTruceCost = Math.ceil(baseCostPerTurn * proposal.truceRequested);
- requestValue += totalTruceCost;
+ totalTruceCost = Math.ceil(baseCostPerTurn * proposal.truceRequested);
+ // We no longer add this to requestValue, as it is auto-paid!
}
- // AI Acceptance Logic
- if (offerValue >= requestValue || (offerValue > 0 && requestValue === 0)) {
+ let effectiveOffer = offerValue + softPower;
+
+ // AI Acceptance Logic (Truce purchases are automatically accepted)
+ if (proposal.truceRequested > 0 || effectiveOffer >= requestValue || (offerValue > 0 && requestValue === 0)) {
// 1 & 2. Transfer Currency
if (this.wastelandEconomyActive) {
- // Transfer Caps
- this.player.caps -= proposal.capsOffered;
- target.caps += proposal.capsOffered;
+ // Transfer Caps (Including auto-paid Truce cost)
+ this.player.caps -= (proposal.capsOffered + totalTruceCost);
+ target.caps += (proposal.capsOffered + totalTruceCost);
+
target.caps -= proposal.capsRequested;
this.player.caps += proposal.capsRequested;
} else {
- // Transfer Cards
- for (let i = 0; i < proposal.capsOffered; i++) {
+ // Transfer Cards (Including auto-paid Truce cost)
+ for (let i = 0; i < (proposal.capsOffered + totalTruceCost); i++) {
if (this.player.cards.length > 0) target.cards.push(this.player.cards.pop());
}
for (let i = 0; i < proposal.capsRequested; i++) {
@@ -5950,7 +6197,6 @@ Gamestate.openDiplomacy = function (targetName) {
}
// 4. Enact Truce
- // FIX: Enacts the truce for the exact number of turns selected on the slider
if (proposal.truceRequested > 0) {
this.diplomacy.truces.push({ f1: this.player.name, f2: target.name, turns: proposal.truceRequested });
}
@@ -5960,6 +6206,12 @@ Gamestate.openDiplomacy = function (targetName) {
let actionText = "";
let currencyName = this.wastelandEconomyActive ? "Caps" : "Cards";
+ // Was it a Truce Purchase?
+ if (proposal.truceRequested > 0) {
+ repChange = 1;
+ actionText = `[ DIPLOMACY ] You purchased a ${proposal.truceRequested}-Turn Ceasefire with ${target.name} for ${totalTruceCost} ${currencyName}.`;
+ }
+
// Was it a pure gift?
if (requestValue === 0 && offerValue > 0) {
repChange = Math.floor(offerValue * 3);
@@ -5970,6 +6222,11 @@ Gamestate.openDiplomacy = function (targetName) {
repChange = Math.floor((offerValue - requestValue) * 2);
actionText = `[ DIPLOMACY ] You concluded a highly favorable trade with ${target.name}.`;
}
+ // Was it Extortion? (NEW)
+ else if (requestValue > 0 && offerValue < requestValue) {
+ repChange = -5; // Extorting damages your reputation with them
+ actionText = `[ DIPLOMACY ] You strong-armed ${target.name} into yielding to your demands.`;
+ }
// Was it a standard, fair trade?
else {
repChange = 1;
@@ -6337,12 +6594,22 @@ Gamestate.openDiplomacy = function (targetName) {
Gamestate.handleClick = function (e) {
+ if (this.aiTurn || this.modalIsOpen) return;
- if (this.aiTurn) return;
+ // --- NEW: Spontaneous Map Encounter Trigger (3% chance per click) ---
+ // Only allowed if encounters are enabled, we are NOT mid-attack, and we aren't targeting a nuke
+ if (this.encountersEnabled && Math.random() < 0.03) {
+ if (this.stage !== "Battle" || !this.prevCountry) {
+ if (this.stage !== "Nuke Targeting" && this.stage !== "Frenzy Targeting") {
+ this.resolveCreatureEncounter();
+ return; // Stop the click action so the modal takes priority
+ }
+ }
+ }
if (this.stage === "Fortify") {
this.addArmy(e);
- } else if (this.stage === "Battle" || this.stage === "Frenzy Targeting") { // --- THIS IS THE FIX ---
+ } else if (this.stage === "Battle" || this.stage === "Frenzy Targeting") {
this.attack(e);
} else if (this.stage === "Maneuver" || this.stage === "Commander Phase") {
this.maneuver(e);
@@ -6352,6 +6619,7 @@ Gamestate.openDiplomacy = function (targetName) {
}
+
Gamestate.win = function (player) {
if (winMessage) {
winMessage.textContent = player.name;
@@ -6410,11 +6678,11 @@ Gamestate.openDiplomacy = function (targetName) {
let totalArmy = 0; this.players.forEach(player => { totalArmy += player.army });
// --- FINAL & DEFINITIVE PLAYER INFO LOGIC (REVISED) ---
- let sortedPlayers = [...this.players].sort((a, b) => b.army - a.army);
const iBobbleActive = this.bobbleheads && this.bobbleheads.find(b => b.key === 'i' && b.active);
this.players.forEach((player, i) => {
let infoBox = infoName[i] ? infoName[i].parentElement : null;
+
if (!infoBox) return;
// --- Get UI Elements (instead of creating them) ---
@@ -6426,6 +6694,13 @@ Gamestate.openDiplomacy = function (targetName) {
if (player.alive) {
infoBox.classList.remove('defeated');
+
+ // --- FIX: Force the player's card to highlight on their turn ---
+ if (player.isPlayer) {
+ if (!this.aiTurn) infoBox.classList.add('highlight');
+ else infoBox.classList.remove('highlight');
+ }
+
const fogEnabled = document.getElementById('opt-fog-of-war') && document.getElementById('opt-fog-of-war').checked;
const hasIntel = !fogEnabled || player.name === this.player.name || iBobbleActive || (this.diplomacy.reputation[player.name] && this.diplomacy.reputation[player.name][this.player.name] >= 10);
@@ -6435,7 +6710,18 @@ Gamestate.openDiplomacy = function (targetName) {
// --- Faction Name & Reputation ---
if (countryEl) {
let countryHtml = player.country;
- if (!player.isNeutral && player.name !== this.player.name) {
+
+ // NEW: Add Text Label in Alliance Mode
+ if (this.isAllianceMode && !player.isNeutral) {
+ // Find who this player is allied with
+ let ally = this.players.find(p => p.team === player.team && p.name !== player.name && !p.isNeutral);
+ if (ally) {
+ // Append it on a new line, slightly smaller and faded
+ countryHtml += ` (Allied with ${ally.name})`;
+ }
+ }
+ // Standard Reputation Display (Hidden in Alliance Mode)
+ else if (!player.isNeutral && player.name !== this.player.name) {
let rep = this.diplomacy.reputation[player.name]?.[this.player.name] || 0;
let repText = "NEUTRAL";
if (rep >= 35) { repText = "IDOLIZED"; }
@@ -6444,9 +6730,13 @@ Gamestate.openDiplomacy = function (targetName) {
else if (rep <= -10) { repText = "HOSTILE"; }
countryHtml += ` (${repText})`;
}
+
countryEl.innerHTML = countryHtml;
}
+
+
+
// Hide the old income-label
if (incomeContainer) incomeContainer.style.display = "none";
@@ -6551,10 +6841,19 @@ Gamestate.openDiplomacy = function (targetName) {
if (existingBtn) existingBtn.style.display = "none";
}
- let rank = sortedPlayers.findIndex(p => p.name === player.name);
- infoBox.style.order = rank;
+ // FIX: Order by turn (their index in the array) instead of army size
+ infoBox.style.order = i;
});
+ // NEW: Check Turbo status and apply a global class to control the sliding animations
+ let turbo = document.getElementById('turbo-toggle') && document.getElementById('turbo-toggle').checked;
+ if (turbo) {
+ document.body.classList.add('turbo-active');
+ } else {
+ document.body.classList.remove('turbo-active');
+ }
+
+
// --- The rest of the original function remains the same... ---
if (this.players.length === 6 && infoName[6] && infoName[6].parentElement) infoName[6].parentElement.style.display = "none";
let helpBtnEl = document.getElementById('help-btn'); if (helpBtnEl) helpBtnEl.style.order = "998";
@@ -6732,8 +7031,22 @@ Gamestate.openDiplomacy = function (targetName) {
if (this.commandersEnabled && this.player.commander) {
document.getElementById('cmdr-hp-text').textContent = `HP: ${this.player.commander.hp}/100`;
document.getElementById('cmdr-ap-text').textContent = `AP: ${this.player.commander.ap}/2`;
- document.getElementById('cmdr-hp-fill').style.width = `${this.player.commander.hp}%`;
+
+ let hpFill = document.getElementById('cmdr-hp-fill');
+ if (hpFill) {
+ hpFill.style.width = `${this.player.commander.hp}%`;
+
+ // Dynamic HP Color (Red if <= 30, otherwise theme color)
+ if (this.player.commander.hp <= 30) {
+ hpFill.style.backgroundColor = "#ff3333";
+ hpFill.style.boxShadow = "0 0 10px #ff3333";
+ } else {
+ hpFill.style.backgroundColor = "var(--pip-color)";
+ hpFill.style.boxShadow = "0 0 10px var(--pip-color)";
+ }
+ }
}
+
this.drawMapText(); this.lastStage = this.stage;
// --- ACTIONABLE PERK BUTTON LOGIC (v6 - Chem Frenzy Integration) ---
@@ -7022,6 +7335,36 @@ Gamestate.openDiplomacy = function (targetName) {
}
+ if (!document.getElementById('ui-polish-styles')) {
+ let style = document.createElement('style');
+ style.id = 'ui-polish-styles';
+ style.innerHTML = `
+ /* Leaderboard Pulsing Animation */
+ @keyframes activeTurnPulse {
+ 0% { box-shadow: 0 0 5px var(--pip-color), inset 0 0 2px var(--pip-color); border-color: var(--pip-color); }
+ 100% { box-shadow: 0 0 15px var(--pip-color), inset 0 0 8px var(--pip-color); border-color: #ffffff; }
+ }
+
+ /* When highlighted (and turbo is OFF), pulse the card */
+ body:not(.turbo-active) .player-panel > div.highlight {
+ animation: activeTurnPulse 1.2s infinite alternate !important;
+ z-index: 10;
+ }
+
+ /* Force Commander UI to match theme (including all text inside it) */
+ #cmdr-ui-container, #cmdr-ui-container span, #cmdr-ui-container div {
+ border-color: var(--pip-color) !important;
+ color: var(--pip-color) !important;
+ text-shadow: 0 0 5px var(--pip-color) !important;
+ }
+ /* Ensure the health bar background itself doesn't turn transparent */
+ #cmdr-hp-fill {
+ text-shadow: none !important;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
// --- Scrambled Text Intel Animation (All 2 Characters) ---
@@ -8615,22 +8958,34 @@ Gamestate.openDiplomacy = function (targetName) {
if (troopsToPlace > 0 && !turbo) await this.logAction(`${this.players[i].name} deployed ${troopsToPlace} troops to their sectors.`);
}
- let areaToFortify = ["", 0];
+ let areaToFortify = ["", -1];
this.players[i].areas.forEach(area => {
let country = this.countries.find(c => c.name === area);
if (this.players[i].reserve > 0) {
let ratio = 0;
+
+ // NEW: Massive priority boost if the territory actually borders a valid enemy!
+ let hasEnemyNeighbor = country.neighbours.some(n => {
+ let nc = this.countries.find(x => x.name === n);
+ return nc && nc.owner !== this.players[i].name && !this.areAllies(this.players[i].name, nc.owner) && !nc.isCrater;
+ });
+ if (hasEnemyNeighbor) ratio += 10.0;
+
if (this.nukesEnabled && country.isSilo) {
- ratio = 5.0;
+ ratio += 5.0;
} else {
let continent = continents.find(x => x.name === country.continent);
let count = 0;
continent.areas.forEach(x => {
if (this.players[i].areas.includes(x)) count++;
});
- ratio = count / continent.areas.length;
+ ratio += (count / continent.areas.length);
}
- if (ratio >= areaToFortify[1]) {
+
+ // Add a tiny random factor so they don't always stack the exact same territory if tied
+ ratio += Math.random() * 0.5;
+
+ if (ratio > areaToFortify[1]) {
areaToFortify = [country, ratio]
}
}
@@ -8639,6 +8994,7 @@ Gamestate.openDiplomacy = function (targetName) {
areaToFortify[0].army += this.players[i].reserve;
this.players[i].reserve = 0;
}
+
let currentAreas = [...this.players[i].areas];
for (let area of currentAreas) {
let country = this.countries.find(c => c.name === area);
@@ -8695,11 +9051,18 @@ Gamestate.openDiplomacy = function (targetName) {
let opponent = this.countries.find(c => c.name === neighbourName);
// --- FIX: The AI will consider attacking if the neighbor is an enemy, not a crater, AND NOT LOCKED DOWN.
if (opponent && opponent.owner !== this.players[i].name && !opponent.isCrater && !opponent.isLockedDown) {
+
+ // --- DEV GOD MODE: AI completely ignores Player territories ---
+ if (this.godMode && opponent.owner === this.player.name) {
+ return; // Skip adding the player to valid targets
+ }
+
possibleTargets.push(opponent);
}
});
+
// --- CORRECTED ALLY FILTERING LOGIC ---
// This will now correctly prevent the AI from attacking allies.
possibleTargets = possibleTargets.filter(poss => {
@@ -9674,80 +10037,217 @@ Gamestate.openDiplomacy = function (targetName) {
Gamestate.resolveCreatureEncounter = async function () {
- if (this.player.areas.length === 0) return;
+ if (this.player.areas.length === 0 || this.modalIsOpen) return;
const randomAreaName = this.player.areas[Math.floor(Math.random() * this.player.areas.length)];
const territory = this.countries.find(c => c.name === randomAreaName);
- if (!territory || territory.army <= 0) return;
+ if (!territory || territory.army <= 0 || territory.isExploring) return; // Don't attack exploring units
const creature = encounterData.creatures[Math.floor(Math.random() * encounterData.creatures.length)];
- const title = "Creature Sighting";
- const message = `Your scouts report a wild ${creature.name} near your garrison of ${territory.army} troops at ${formatTerritoryName(territory.name)}.`;
+ const title = "Creature Sighting";
+
+ const army = territory.army;
+ const threat = creature.threat;
+ const tName = formatTerritoryName(territory.name);
+ const cName = `${creature.name}`;
+
+ // Calculate Risk Assessment
+ let riskText, riskColor;
+ if (army >= threat * 1.5) {
+ riskText = "LOW RISK";
+ riskColor = "var(--pip-color)"; // UI Theme Color
+ } else if (army >= threat) {
+ riskText = "MODERATE RISK";
+ riskColor = "#ffcc00"; // Yellow
+ } else {
+ riskText = "HIGH RISK";
+ riskColor = "#ff3333"; // Red
+ }
+
+ // Flavor text variations
+ const flavors = [
+ `Your scouts report a wild ${cName} near your garrison of ${army} troops at ${tName}.`,
+ `A terrifying ${cName} has been spotted stalking the perimeter of ${tName}, where ${army} of your troops are stationed.`,
+ `Frantic radio chatter from ${tName} indicates a ${cName} is encroaching on your encampment of ${army}.`,
+ `A dust cloud approaches ${tName}. Your ${army} defenders brace themselves as a ${cName} emerges from the wastes.`,
+ `Sentries at ${tName} have sounded the alarm! A ${cName} is prowling dangerously close to your ${army} soldiers.`,
+ `The local wildlife is getting restless. A ${cName} is currently menacing your ${army} troops garrisoned at ${tName}.`,
+ `A lone ${cName} has wandered into the patrol zone of your ${army} troops stationed in ${tName}.`,
+ `Your detachment of ${army} at ${tName} is requesting orders regarding a hostile ${cName} spotted nearby.`,
+ `Movement detected in the ruins of ${tName}. It's a ${cName}, and it's heading straight for your ${army} troops.`,
+ `The mutated fauna of ${tName} is acting up again. A ${cName} is threatening your garrison of ${army}.`
+ ];
+
+ const randomFlavor = flavors[Math.floor(Math.random() * flavors.length)];
+
+ const message = `${randomFlavor}
[ TACTICAL ASSESSMENT: ${riskText} ]`;
+
const choices = [
- { id: "attack", text: "[Attack] Try to eliminate the threat." },
- { id: "avoid", text: "[Avoid] The risk isn't worth it." }
+ { id: "attack", text: "[Attack] Engage and eliminate the threat." },
+ { id: "avoid", text: "[Avoid] Attempt to hide and let it pass." }
];
- const onChoiceCallback = (decision) => {
- if (decision === 'attack') {
- // Log the action but DO NOT await it.
- this.logAction(`You chose to engage the ${creature.name} at ${formatTerritoryName(territory.name)}!`, true);
+ this.modalIsOpen = true;
+
+ const onChoiceCallback = (decision) => {
+ let forcedFightText = "";
+ let isFighting = true;
+
+ if (decision === 'avoid') {
+ // 80% chance to successfully hide
+ if (Math.random() < 0.80) {
+ isFighting = false;
+ this.logAction(`[ SURVIVAL ] Garrison at ${tName} successfully stayed hidden from a roaming ${creature.name}.`);
+ return `Your garrison remained hidden until the ${creature.name} moved on.`;
+ } else {
+ forcedFightText = `Your troops tried to hide, but the ${creature.name} picked up their scent and ambushed them!
`;
+ this.logAction(`[ AMBUSH ] A ${creature.name} sniffed out your hiding troops at ${tName} and forced an engagement!`, true);
+ }
+ }
+
+ if (isFighting) {
const army = territory.army;
const threat = creature.threat;
- const damagePercent = threat / (army + threat);
- let casualties = Math.floor(army * damagePercent);
-
- if (army > 1 && army - casualties < 1) {
- casualties = army - 1;
- } else if (army === 1 && casualties > 0) {
- casualties = 1;
- }
-
- if (casualties > 0) {
- territory.army -= casualties;
- this.player.army -= casualties;
- // Log the action but DO NOT await it.
- this.logAction(`Your soldiers fought bravely, but suffered ${casualties} casualties.`, true);
- return `The ${creature.name} was driven off, but you lost ${casualties} troops in the fight.`;
+
+ // Player wins ties (army >= threat)
+ if (army >= threat) {
+ let lootText = "";
+ let logText = "";
+ let roll = Math.random();
+
+ // Hostage Reward for Humanoids
+ if (creature.isHumanoid && roll < 0.40) {
+ let hostages = Math.floor(Math.random() * 3) + 1;
+ territory.army += hostages;
+ this.player.army += hostages;
+ lootText = `They rescued ${hostages} wastelanders being held captive, who have joined your ranks!`;
+ logText = `[ VICTORY ] The ${creature.name} was slain at ${tName}. Rescued ${hostages} wastelanders who joined the garrison!`;
+ }
+ // Stimpak Reward
+ else if (this.commandersEnabled && this.player.commander && this.player.commander.stimpaks < 3 && roll > 0.75) {
+ this.player.commander.stimpaks++;
+ let navInv = document.getElementById('nav-inv');
+ if (navInv) navInv.classList.add('inv-pulse');
+ lootText = `They salvaged a rare Stimpak from the creature's den!`;
+ logText = `[ VICTORY ] The ${creature.name} was slain at ${tName}. Salvaged a rare Stimpak from its den!`;
+ }
+ // Caps Reward
+ else {
+ let capsFound = Math.floor(Math.random() * 6) + 5;
+ if (this.wastelandEconomyActive) this.player.caps += capsFound;
+ lootText = `They recovered ${capsFound} Caps from the remains.`;
+ logText = `[ VICTORY ] The ${creature.name} was slain at ${tName}. Recovered ${capsFound} Caps from the area.`;
+ }
+
+ // Log the exact victory outcome
+ this.logAction(logText, true);
+ return `${forcedFightText}A decisive victory! The ${creature.name} was eliminated with no casualties.
${lootText}`;
} else {
- // Log the action but DO NOT await it.
- this.logAction(`Your garrison made short work of the ${creature.name} and suffered no losses.`);
- return `A decisive victory! The ${creature.name} was eliminated with no casualties.`;
+ // Defeat/Casualties
+ let minRemaining = Math.max(1, Math.floor(army * 0.10)); // 10% or 1 unit minimum
+ let maxCasualties = army - minRemaining;
+ let casualties = Math.min(Math.ceil(threat), maxCasualties);
+
+ if (casualties > 0) {
+ territory.army -= casualties;
+ this.player.army -= casualties;
+ // Log the exact losses
+ this.logAction(`[ CASUALTIES ] The ${creature.name} overwhelmed defenders at ${tName}! Lost ${casualties} troops before driving it off.`, true);
+ return `${forcedFightText}The ${creature.name} overwhelmed your forces! You suffered ${casualties} casualties before driving it away.`;
+ } else {
+ // Log battered survival
+ this.logAction(`[ SURVIVAL ] Defenders at ${tName} were battered by a ${creature.name} but suffered no permanent casualties.`);
+ return `${forcedFightText}The ${creature.name} battered your forces, but they managed to survive without permanent casualties.`;
+ }
}
- } else { // 'avoid'
- // Log the action but DO NOT await it.
- this.logAction(`You chose to avoid the ${creature.name}, leaving it to roam the wastes.`);
- return `You wisely avoid the creature, and your troops remain safe.`;
+ }
+ };
+
+ Gamestate.resolveRadioEncounter = async function () {
+ if (this.player.areas.length === 0 || this.modalIsOpen) return;
+
+ // Find a valid territory: not already exploring, army > 1
+ const validTerritories = this.player.areas.map(a => this.countries.find(c => c.name === a)).filter(c => c && c.army > 1 && !c.isExploring);
+ if (validTerritories.length === 0) return;
+
+ const territory = validTerritories[Math.floor(Math.random() * validTerritories.length)];
+
+ // Pick an encounter type
+ const types = ["location", "person", "container"];
+ const type = types[Math.floor(Math.random() * types.length)];
+
+ let poiName = "";
+ let actionVerb = "";
+
+ if (type === "location") {
+ poiName = encounterData.genericLocations[Math.floor(Math.random() * encounterData.genericLocations.length)];
+ actionVerb = "explore the area";
+ } else if (type === "person") {
+ poiName = encounterData.people[Math.floor(Math.random() * encounterData.people.length)];
+ actionVerb = "approach and investigate";
+ } else {
+ poiName = encounterData.containers[Math.floor(Math.random() * encounterData.containers.length)];
+ actionVerb = "attempt to secure and open it";
+ }
+
+ const title = "Radio Transmission";
+ const message = `>>> INCOMING TRANSMISSION <<<
Garrison forces at ${formatTerritoryName(territory.name)} report they have discovered ${poiName}.
Do you want to send a detachment to ${actionVerb}? This will lock the territory down for 2 turns and reduce their defenses by 15%.`;
+
+ const choices = [
+ { id: "explore", text: "[Investigate] Send the detachment." },
+ { id: "ignore", text: "[Ignore] Hold the line. Do not engage." }
+ ];
+
+ this.modalIsOpen = true;
+
+ const onChoiceCallback = (decision) => {
+ if (decision === 'explore') {
+ // Lock the troops into the exploring state
+ territory.isExploring = true;
+ territory.exploreTurnsLeft = 2;
+ territory.exploreType = type;
+ territory.explorePOI = poiName;
+
+ this.logAction(`[ EXPEDITION ] Troops at ${formatTerritoryName(territory.name)} have begun investigating ${poiName}. They will report back in 2 turns.`, true);
+ return `Expedition launched. The garrison is now vulnerable.`;
+ } else {
+ this.logAction(`[ EXPEDITION ] You ordered the garrison at ${formatTerritoryName(territory.name)} to hold their position and ignore the ${poiName}.`);
+ return `Transmission ended. The garrison will maintain their post.`;
}
};
await this.showEncounterModal(title, message, choices, onChoiceCallback);
+ this.modalIsOpen = false;
+ this.updateInfo();
+ this.drawMapText();
+ };
- // Update the UI after the modal has closed
+
+ await this.showEncounterModal(title, message, choices, onChoiceCallback);
+ this.modalIsOpen = false;
this.updateInfo();
this.drawMapText();
};
Gamestate.triggerEncounterCheck = async function (triggerType) {
- // --- THIS IS THE NEW CHECK ---
// If encounters are disabled in the game settings, do nothing.
if (!this.encountersEnabled) return;
let chance = 0;
if (triggerType === 'start_of_turn') {
- chance = 0.08; // 8% chance at the start of a turn
+ chance = 1.0; // TEST MODE: 100% chance to trigger!
} else if (triggerType === 'post_conquest') {
- chance = 0.15; // 15% chance after conquering a territory
+ chance = 1.0; // TEST MODE: 100% chance to trigger!
}
if (Math.random() < chance) {
if (triggerType === 'start_of_turn') {
- await this.resolveCreatureEncounter();
+ await this.resolveRadioEncounter();
}
- // We will add post-conquest story logic here in a future step.
+ // Post-conquest will go here shortly!
}
};
@@ -9919,7 +10419,8 @@ Gamestate.openDiplomacy = function (targetName) {
}
// Log it for good measure
- if (this.logAction) this.logAction("💾 SYSTEM: Game state successfully backed up to local memory.", true);
+ if (this.logAction) this.logAction("[ BACKUP ] SYSTEM: Game state successfully backed up to local memory.", true);
+
} catch (error) {
console.error("Failed to save game:", error);
@@ -9997,7 +10498,7 @@ Gamestate.openDiplomacy = function (targetName) {
}
// 10. Announce success!
- if (this.logAction) this.logAction(`💾 SYSTEM: Successfully restored Save File (Day ${this.turn}).`, true);
+ if (this.logAction) this.logAction(`[ RESTORE ] SYSTEM: Successfully restored Save File (Day ${this.turn}).`, true);
if (this.queueToast) {
this.queueToast(`>>> SYSTEM RESTORE COMPLETE <<<
Welcome back, ${this.player.name}.`, "var(--pip-color)", true);
}
@@ -10051,7 +10552,8 @@ Gamestate.openDiplomacy = function (targetName) {
// --- BULLETPROOF SVG OVERLAP FIX (V2) ---
Gamestate.fixMapTextOrder = function () {
- let svg = document.querySelector('svg');
+ let svg = document.querySelector('.area').closest('svg');
+
// Create a master layer that sits on top of absolutely everything
let topLayer = document.createElementNS("http://www.w3.org/2000/svg", "g");