diff --git a/index.html b/index.html index bab97a6..7c1c036 100644 --- a/index.html +++ b/index.html @@ -3342,7 +3342,7 @@ -
+ @@ -4957,16 +4957,28 @@ if (totalPlayable > 0 && (playerOwnedCount / totalPlayable) > 0.35) { }); Gamestate.debugLoggingEnabled = false; // The global flag for our new mode + Gamestate.debugLog = []; // NEW: Array to hold the last 50 technical logs // New helper function for conditional logging Gamestate.logDebug = function(message) { + // 1. Always push to the hidden rolling array + let now = new Date(); + let timeString = now.toLocaleTimeString([], { hour12: false }); + this.debugLog.push(`[${timeString}] ${message}`); + if (this.debugLog.length > 500) { + this.debugLog.shift(); // Remove the oldest entry if we exceed 500 + } + + // 2. Keep the old behavior of printing to UI if toggle is checked + + if (this.debugLoggingEnabled) { - // Prepend [DEBUG] to make these messages stand out this.logAction(`[DEBUG] ${message}`); } }; Gamestate.logQueue = []; + Gamestate.isLogging = false; Gamestate.logAction = function (message, isImportant = false, isNuke = false) { @@ -5654,11 +5666,17 @@ Gamestate.renderInventory = function () { // 2. Identify Valid Territories Based on Phase if (this.stage === "Battle") { - valid = owned.filter(t => t.army > 1 && !t.isLockedDown && !t.isExploring && t.neighbours.some(n => { - let nc = this.countries.find(x => x.name === n); - return nc && nc.owner !== this.player.name && !nc.isCrater && !(this.isAllianceMode && this.areAllies(this.player.name, nc.owner)); - })); + valid = owned.filter(t => { + if (t.army <= 1 || t.isLockedDown || t.isExploring) return false; + let hasEnemyNeighbor = t.neighbours.some(n => { + let nc = this.countries.find(x => x.name === n); + return nc && nc.owner !== this.player.name && !nc.isCrater && !(this.isAllianceMode && this.areAllies(this.player.name, nc.owner)); + }); + let hasTrespasser = this.commandersEnabled && this.players.some(p => p !== this.player && p.alive && !p.isNeutral && p.commander && p.commander.hp > 0 && p.commander.loc === t.name); + return hasEnemyNeighbor || hasTrespasser; + }); } else if (this.stage === "Commander Phase" && this.commandersEnabled && this.player.commander && this.player.commander.ap > 0) { + let cmdrCountry = this.countries.find(c => c.name === this.player.commander.loc); if (cmdrCountry && !this.player.commander.isConverting) valid.push(cmdrCountry); } else if (this.stage === "Maneuver") { @@ -8421,20 +8439,11 @@ Gamestate.openDiplomacy = function (targetName) { } -Gamestate.handleClick = function (e) { + Gamestate.handleClick = function (e) { if (this.aiTurn || this.modalIsOpen) return; - // --- NEW: Spontaneous Map Encounter Trigger (3% chance per click) --- - // Only allowed if encounters are enabled, we are NOT mid-attack, we aren't targeting a nuke, AND we are past Turn 3 - if (this.encountersEnabled && this.turn > 3 && 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 - } - } - } // --- RELIC TARGETING HANDLER --- + if (this.targetingMode === 'relic' && this.pendingRelic) { const countryId = e.target.id; const country = this.countries.find(c => c.name === countryId); @@ -9036,10 +9045,12 @@ Gamestate.handleClick = function (e) { ownedTerritories.forEach(t => { if (t.army > 1 && !t.isExploring) { let hasEnemyNeighbor = t.neighbours.some(n => { let nc = this.countries.find(x => x.name === n); return nc && nc.owner !== this.player.name && !nc.isCrater; }); - if (hasEnemyNeighbor) { currentStrikeForce += (t.army - 1); validAttacks++; } + let hasTrespasser = this.commandersEnabled && this.players.some(p => p !== this.player && p.alive && !p.isNeutral && p.commander && p.commander.hp > 0 && p.commander.loc === t.name); + if (hasEnemyNeighbor || hasTrespasser) { currentStrikeForce += (t.army - 1); validAttacks++; } } }); if (validAttacks === 0) apPercentage = 0; + else { if (this.lastStage !== "Battle") this.initialStrikeForce = currentStrikeForce; if (currentStrikeForce > (this.initialStrikeForce || 1)) this.initialStrikeForce = currentStrikeForce; @@ -10062,6 +10073,12 @@ if (sourceMapEl && sourceMapEl.nextElementSibling) sourceMapEl.nextElementSiblin return; } + // --- RANDOM WASTELAND ENCOUNTERS (8% Chance per phase transition) --- + if (this.encountersEnabled && this.turn > 3 && Math.random() < 0.08) { + this.resolveCreatureEncounter(); + return; // Pause phase transition so the modal takes priority. Player will click End Phase again after. + } + // --- FORTIFY / RECRUITMENT -> BATTLE BRIDGE --- if (this.stage === "Fortify" || this.stage === "Recruitment") { // Modal confirmation for unspent Caps @@ -11657,9 +11674,13 @@ this.checkAutoPhaseAdvance(); } if (winModal) winModal.style.display = "block"; } + + // --- NEW: UNIVERSAL VICTORY CHECK --- + this.checkWinCondition(); } Gamestate.processRadDecay = async function () { + // --- NEW: DECREMENT RADAWAY IMMUNITY --- this.players.forEach(p => { if (p.radImmunity && p.radImmunity > 0) p.radImmunity--; @@ -11946,9 +11967,10 @@ this.checkAutoPhaseAdvance(); } if (this.perksEnabled) { for (let p of this.players) { - if (p && p.perk) { + if (p && p.perk && p.alive) { // --- Chem Frenzy Cooldown --- if (p.perk.id === 'chem_frenzy' && p.chemFrenzyCooldown > 0) { + p.chemFrenzyCooldown--; } // --- Technology Overdrive Cooldown --- @@ -14570,7 +14592,30 @@ this.addXP(player, xpReward); let winMessage = document.querySelector('.win-message'); if (!winModal || !winMessage) return; + // --- NEW: DEATH SWEEP FOR GHOST FACTIONS --- + this.players.forEach(p => { + if (p.alive && !p.isNeutral) { + let isDead = false; + if (this.commandersEnabled) { + if (!p.commander || p.commander.hp <= 0) isDead = true; + } else { + if (p.areas.length === 0) isDead = true; + } + + if (isDead) { + p.alive = false; + p.areas = []; + p.army = 0; + let index = this.players.indexOf(p); + if (typeof infoName !== 'undefined' && infoName[index]) { + infoName[index].parentElement.classList.add('defeated'); + } + } + } + }); + // --- CHECK 1: PLAYER DEFEAT --- + let playerDead = false; let isAllyDeath = false; @@ -15460,7 +15505,8 @@ Gamestate.triggerEncounterCheck = async function (triggerType, territoryName = n { file: "TheWanderer.mp3", title: "Dion DiMucci - The Wanderer" }, { file: "TakeMeHomeCountryRoads.mp3", title: "John Denver - Take Me Home Country Roads" } ]; - let currentTrackIndex = 0; let isRadioActive = false; let pipboyAudio = new Audio(); let broadcastDelay; + let currentTrackIndex = 0; let isRadioActive = false; let pipboyAudio = new Audio(); let broadcastDelay; let consecutiveRadioFailures = 0; + const radioBtn = document.getElementById('radio-toggle'); radioBtn.addEventListener('click', () => { if (isRadioActive) { @@ -15479,21 +15525,52 @@ Gamestate.triggerEncounterCheck = async function (triggerType, territoryName = n if (!isRadioActive) return; let currentTrack = wastelandRadio[currentTrackIndex]; pipboyAudio.src = currentTrack.file; - pipboyAudio.play(); + + let playPromise = pipboyAudio.play(); + + if (playPromise !== undefined) { + playPromise.then(() => { + // Playback successful! Reset the failure counter. + consecutiveRadioFailures = 0; + if (Gamestate && Gamestate.logAction) { + Gamestate.logAction(`RADIO: Picked up a broadcast... Now playing "${currentTrack.title}".`); + // --- SINGING LOG EVENT --- + if (currentTrack.file.includes("TakeMeHomeCountryRoads")) { + setTimeout(() => Gamestate.logAction("'Wait, I know this one...'"), 4000); + setTimeout(() => Gamestate.logAction("You start singing: '♫ Almost heaven, West Virginia.'"), 7000); + setTimeout(() => Gamestate.logAction("'♫ Blue Ridge Mountains, Shenandoah River.'"), 12700); + setTimeout(() => Gamestate.logAction("'♫ Life is old there, older than the trees...Great song.'"), 18800); + } + } + }).catch(error => { + // File not found or playback failed + if (!isRadioActive) return; + + consecutiveRadioFailures++; + + // Check if we've cycled through the whole list with no success + if (consecutiveRadioFailures >= wastelandRadio.length) { + isRadioActive = false; + radioBtn.classList.remove('radio-on'); + if (Gamestate && Gamestate.logAction) { + Gamestate.logAction("RADIO: Hardware failure. No audio files detected on any frequency. Powering down..."); + } + consecutiveRadioFailures = 0; // Reset for next time they try to turn it on + return; + } - if (Gamestate && Gamestate.logAction) { - Gamestate.logAction(`RADIO: Picked up a broadcast... Now playing "${currentTrack.title}".`); - - // --- SINGING LOG EVENT --- - if (currentTrack.file.includes("TakeMeHomeCountryRoads")) { - setTimeout(() => Gamestate.logAction("'Wait, I know this one...'"), 4000); - setTimeout(() => Gamestate.logAction("You start singing: '♫ Almost heaven, West Virginia.'"), 7000); - setTimeout(() => Gamestate.logAction("'♫ Blue Ridge Mountains, Shenandoah River.'"), 12700); - setTimeout(() => Gamestate.logAction("'♫ Life is old there, older than the trees...Great song.'"), 18800); - } + if (Gamestate && Gamestate.logAction) { + Gamestate.logAction("RADIO: *BZZZT* ...Heavy radiation interference. Skipping dead frequency."); + } + // Skip to next track after a brief delay of static + currentTrackIndex = (currentTrackIndex + 1) % wastelandRadio.length; + broadcastDelay = setTimeout(() => { playWastelandTrack(); }, 3000); + }); } } + + pipboyAudio.addEventListener('ended', () => { if (!isRadioActive) return; currentTrackIndex = (currentTrackIndex + 1) % wastelandRadio.length; if (Gamestate && Gamestate.logAction) Gamestate.logAction("RADIO: Broadcast ended. Scanning frequencies for the next signal..."); @@ -15539,9 +15616,11 @@ Gamestate.triggerEncounterCheck = async function (triggerType, territoryName = n diplomacy: this.diplomacy, dogmeatQuest: this.dogmeatQuest, activeRelicPool: this.activeRelicPool, + debugLog: this.debugLog, // NEW: Save the 50-line technical history // The Main Entities players: this.players, + countries: this.countries, bobbleheads: this.bobbleheads }; @@ -15621,6 +15700,7 @@ Gamestate.triggerEncounterCheck = async function (triggerType, territoryName = n this.diplomacy = loadedData.diplomacy; this.dogmeatQuest = loadedData.dogmeatQuest; this.activeRelicPool = loadedData.activeRelicPool; + this.debugLog = loadedData.debugLog || []; // NEW: Restore the technical history this.players = loadedData.players; this.countries = loadedData.countries; this.bobbleheads = loadedData.bobbleheads; @@ -15663,7 +15743,62 @@ Gamestate.triggerEncounterCheck = async function (triggerType, territoryName = n } }; + // --- NEW: DIAGNOSTIC EXPORT & HIDDEN TRIGGER --- + Gamestate.exportDiagnostics = function() { + if (!this.player) return; // Game hasn't started yet + + let p = this.player; + let preset = document.getElementById('game-mode-preset') ? document.getElementById('game-mode-preset').value : "UNKNOWN"; + + let report = "========================================\n"; + report += " WASTELAND CONQUEST - DIAGNOSTIC REPORT \n"; + report += "========================================\n\n"; + + report += "--- OPERATOR PROFILE ---\n"; + report += `Name: ${p.name}\nFaction: ${p.country}\nLevel: ${p.level || 1} (${p.xp || 0} XP)\n`; + report += `Commander Health: ${this.commandersEnabled && p.commander ? p.commander.hp : 'N/A'}\n`; + report += `Territories Owned: ${p.areas.length}\n\n`; + + report += "--- SIMULATION PARAMETERS ---\n"; + report += `Turn: ${this.turn} | Stage: ${this.stage} | Preset: ${preset.toUpperCase()} | Difficulty: ${this.difficulty || "NORMAL"}\n`; + report += `Wasteland Economy: ${!!this.wastelandEconomyActive} | Faction Perks: ${!!this.perksEnabled} | Commanders: ${!!this.commandersEnabled}\n`; + report += `Fog of War: ${!!(document.getElementById('opt-fog-of-war') && document.getElementById('opt-fog-of-war').checked)} | Radstorms: ${!!this.hazardsEnabled} | Encounters: ${!!this.encountersEnabled} | Scorched Earth: ${!!this.nukesEnabled}\n\n`; + + report += "--- TECHNICAL LOG (LAST 500 EVENTS) ---\n"; + if (this.debugLog && this.debugLog.length > 0) { + + this.debugLog.forEach(line => { report += line + "\n"; }); + } else { + report += "No debug logs recorded yet.\n"; + } + + report += "\n========================================\nEND OF REPORT\n========================================\n"; + + const blob = new Blob([report], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `Wasteland_Diagnostic_Day${this.turn}.txt`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + if (this.showToast) this.showToast("DIAGNOSTIC REPORT EXPORTED", "var(--pip-color)"); + }; + + // The hidden keyboard shortcut (Ctrl + Shift + D) + window.addEventListener('keydown', function(e) { + if (e.ctrlKey && e.shiftKey && (e.key === 'd' || e.key === 'D')) { + e.preventDefault(); // Prevent default browser behavior like bookmarking + if (Gamestate && Gamestate.exportDiagnostics) { + Gamestate.exportDiagnostics(); + } + } + }); + Gamestate.updateInfo = function () { + Gamestate.originalUpdateInfo.call(this); // Run the normal game logic first @@ -15794,6 +15929,7 @@ Gamestate.updateInfo = function () { `> MAP LEGEND (A visual guide explaining map colors, borders, and icons)