From b4255c103d94cfefd52971497ff5791ad202f5e7 Mon Sep 17 00:00:00 2001 From: threememories Date: Mon, 6 Apr 2026 19:52:48 -0500 Subject: [PATCH] Delete index.js --- index.js | 1411 ------------------------------------------------------ 1 file changed, 1411 deletions(-) delete mode 100644 index.js diff --git a/index.js b/index.js deleted file mode 100644 index 737f1d6..0000000 --- a/index.js +++ /dev/null @@ -1,1411 +0,0 @@ -/*=============================== - ROBCO RISK -===============================*/ - -// --- DYNAMIC HAZARD CSS INJECTION --- -if (!document.getElementById('radstorm-styles')) { - let style = document.createElement('style'); - style.id = 'radstorm-styles'; - style.innerHTML = ` - @keyframes radstormWarn { 0% { fill: #ffcc00; filter: drop-shadow(0 0 5px #ffcc00); } 100% { fill: #ff8800; filter: drop-shadow(0 0 15px #ff8800); } } - @keyframes radstormAct { 0% { fill: #39ff14; filter: drop-shadow(0 0 10px #39ff14); } 100% { fill: #00ff00; filter: drop-shadow(0 0 25px #00ff00); } } - .radstorm-warning { animation: radstormWarn 1s infinite alternate !important; opacity: 0.85; } - .radstorm-active { animation: radstormAct 0.4s infinite alternate !important; opacity: 0.95; } - `; - document.head.appendChild(style); -} - -const continents = [ - { areas: ["indonesia", "new_guinea", "eastern_australia", "western_australia"], name: "The Southern Wastes", bonus: 2 }, - { areas: ["brazil", "peru", "venezuela", "argentina"], name: "Amazonian Wastes", bonus: 2 }, - { areas: ["egypt", "north_africa", "east_africa", "congo", "south_africa", "madagascar"], name: "Saharan Wastes", bonus: 3 }, - { areas: ["iceland", "uk", "scandinavia", "northern_europe", "western_europe", "ukraine", "southern_europe"], name: "European Commonwealth", bonus: 5 }, - { areas: ["central_america", "eastern_us", "western_us", "quebec", "ontario", "alberta", "northwest_territory", "alaska", "greenland"], name: "Capital Wasteland", bonus: 5 }, - { areas: ["middle_east", "afghanistan", "ural", "siberia", "irkutsk", "yakutsk", "kamchatka", "mongolia", "japan", "china", "siam", "india"], name: "The Great Wastes", bonus: 7 } -]; - -const countries = [ - {name: "indonesia", continent: "The Southern Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["siam", "western_australia", "new_guinea"]}, - {name: "new_guinea", continent: "The Southern Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["indonesia", "eastern_australia", "western_australia"]}, - {name: "eastern_australia", continent: "The Southern Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["western_australia", "new_guinea"]}, - {name: "western_australia", continent: "The Southern Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["eastern_australia", "new_guinea", "indonesia"]}, - {name: "ural", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["ukraine", "siberia", "afghanistan", "china"]}, - {name: "siberia", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["ural", "mongolia", "yakutsk", "irkutsk", "china"]}, - {name: "afghanistan", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["ukraine", "ural", "middle_east", "china", "india"]}, - {name: "irkutsk", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["yakutsk", "siberia", "kamchatka", "mongolia"]}, - {name: "yakutsk", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["irkutsk", "siberia", "kamchatka"]}, - {name: "kamchatka", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["alaska", "yakutsk", "japan", "irkutsk", "mongolia"]}, - {name: "middle_east", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["ukraine", "afghanistan", "india", "egypt", "east_africa", "southern_europe"]}, - {name: "india", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["middle_east", "siam", "afghanistan", "china"]}, - {name: "siam", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["indonesia", "india", "china"]}, - {name: "china", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["ural", "siberia", "afghanistan", "mongolia", "siam", "india"]}, - {name: "mongolia", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["irkutsk", "siberia", "kamchatka", "china", "japan"]}, - {name: "japan", continent: "The Great Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["kamchatka", "mongolia"]}, - {name: "egypt", continent: "Saharan Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["middle_east", "southern_europe", "north_africa", "east_africa"]}, - {name: "north_africa", continent: "Saharan Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["egypt", "southern_europe", "western_europe", "east_africa", "congo", "brazil"]}, - {name: "east_africa", continent: "Saharan Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["middle_east", "egypt", "north_africa", "congo", "madagascar", "south_africa"]}, - {name: "congo", continent: "Saharan Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["south_africa", "north_africa", "east_africa"]}, - {name: "south_africa", continent: "Saharan Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["congo", "madagascar", "east_africa"]}, - {name: "madagascar", continent: "Saharan Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["south_africa", "east_africa"]}, - {name: "brazil", continent: "Amazonian Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["peru", "argentina", "north_africa", "venezuela"]}, - {name: "peru", continent: "Amazonian Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["brazil", "argentina", "venezuela"]}, - {name: "argentina", continent: "Amazonian Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["brazil", "peru"]}, - {name: "venezuela", continent: "Amazonian Wastes", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["brazil", "peru", "central_america"]}, - {name: "iceland", continent: "European Commonwealth", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["greenland", "uk", "scandinavia"]}, - {name: "scandinavia", continent: "European Commonwealth", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["iceland", "uk", "ukraine", "northern_europe"]}, - {name: "northern_europe", continent: "European Commonwealth", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["ukraine", "uk", "scandinavia", "southern_europe", "western_europe"]}, - {name: "western_europe", continent: "European Commonwealth", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["north_africa", "uk", "northern_europe", "southern_europe"]}, - {name: "southern_europe", continent: "European Commonwealth", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["north_africa", "egypt", "northern_europe", "western_europe", "middle_east", "ukraine"]}, - {name: "uk", continent: "European Commonwealth", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["western_europe", "iceland", "northern_europe", "scandinavia"]}, - {name: "ukraine", continent: "European Commonwealth", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["scandinavia", "ural", "northern_europe", "southern_europe", "afghanistan", "middle_east"]}, - {name: "greenland", continent: "Capital Wasteland", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["iceland", "quebec", "ontario", "northwest_territory"]}, - {name: "central_america", continent: "Capital Wasteland", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["venezuela", "eastern_us", "western_us"]}, - {name: "eastern_us", continent: "Capital Wasteland", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["central_america", "western_us", "ontario", "quebec"]}, - {name: "western_us", continent: "Capital Wasteland", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["central_america", "eastern_us", "ontario", "alberta"]}, - {name: "quebec", continent: "Capital Wasteland", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["eastern_us", "ontario", "greenland"]}, - {name: "ontario", continent: "Capital Wasteland", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["eastern_us", "western_us", "quebec", "alberta", "northwest_territory", "greenland"]}, - {name: "alberta", continent: "Capital Wasteland", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["western_us", "ontario", "northwest_territory", "alaska"]}, - {name: "northwest_territory", continent: "Capital Wasteland", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["alberta", "ontario", "greenland", "alaska"]}, - {name: "alaska", continent: "Capital Wasteland", owner: "none", color:"#1a1a1a", "army": 0, neighbours: ["alberta", "northwest_territory", "kamchatka"]} -]; - -const wastelandEncounters = [ - "Spotted a Vertibird flying low over the wastes.", - "Radio picked up a strange distress signal. Probably a raider trap.", - "Caravan guards chased off a pack of wild Mongrels.", - "Another settlement needs our help. Marking it on the global map.", - "Traded a handful of mutfruit for some polished 5.56 rounds.", - "Super Mutants spotted dragging captives into a metro tunnel.", - "Avoided a Radscorpion nest near the old irradiated crater.", - "Brahmin stampede delayed supply lines by three hours.", - "A wandering Eyebot is blasting patriotic music nearby.", - "Dust storm rolling in. Visibility dropping to near zero.", - "Picked up a HAM radio broadcast from KQ4JZM... Static approaching.", - "Scavenged an abandoned pickup truck. The tonneau cover was surprisingly intact.", - "Found an old pre-war safe containing detailed, multi-tiered bug-out lists.", - "Spotted survivors establishing a security perimeter around a ruined church.", - "Hacked a terminal and found a manuscript for 'SurvivalSOS Fundamentals of Survival' by Joseph Howard." -]; - -const combatFlavors = [ - "while they were complaining about the radioactive heat.", - "after distracting their guards with a loaded Brahmin.", - "by unleashing a swarm of Cazadores on their flank.", - "while they were trying to hack a Novice terminal.", - "after hitting them with a Fat Man. Overkill, but effective.", - "catching them mid-game of Caravan and flipping the table.", - "storming the base while the commander was literally in the shower.", - "distracting the guards with a holotape of 'Butcher Pete'.", - "finding the leader trying to repair a toaster and hitting them with it.", - "walking in the front door because the guards were asleep on Mentats." -]; - -const basePlayers = [ - { "name": "Elder Lyons", "country": "Brotherhood of Steel", "color": "#0088ff", "army": 10, "reserve": 10, "areas": [], "bonus": 2, "alive": true, "cards": [], "conqueredThisTurn": false, "isNeutral": false }, - { "name": "President Eden", "country": "Enclave", "color": "#ff003c", "army": 20, "reserve": 20, "areas": [], "bonus": 2, "alive": true, "cards": [], "conqueredThisTurn": false, "isNeutral": false }, - { "name": "Overlord", "country": "Super Mutants", "color": "#ffaa00", "army": 20, "reserve": 20, "areas": [], "bonus": 2, "alive": true, "cards": [], "conqueredThisTurn": false, "isNeutral": false }, - { "name": "Boss", "country": "Raiders", "color": "#ff00ff", "army": 20, "reserve": 20, "areas": [], "bonus": 2, "alive": true, "cards": [], "conqueredThisTurn": false, "isNeutral": false }, - { "name": "Protector Casdin", "country": "Outcasts", "color": "#00ff00", "army": 20, "reserve": 20, "areas": [], "bonus": 2, "alive": true, "cards": [], "conqueredThisTurn": false, "isNeutral": false }, - { "name": "Sonora Cruz", "country": "Regulators", "color": "#ffff00", "army": 20, "reserve": 20, "areas": [], "bonus": 2, "alive": true, "cards": [], "conqueredThisTurn": false, "isNeutral": false } -]; - -const themeFactions = { - "fo3": [ - { name: "Elder Lyons", country: "Brotherhood of Steel" }, - { name: "President Eden", country: "The Enclave" }, - { name: "Overlord", country: "Vault 87 Mutants" }, - { name: "Flak", country: "Wasteland Raiders" }, - { name: "Protector Casdin", country: "BOS Outcasts" }, - { name: "Reilly", country: "Reilly's Rangers" } - ], - "fnv": [ - { name: "General Oliver", country: "New California Republic" }, - { name: "Caesar", country: "Caesar's Legion" }, - { name: "Mr. House", country: "New Vegas Securitrons" }, - { name: "Elder McNamara", country: "Mojave Brotherhood" }, - { name: "Papa Khan", country: "Great Khans" }, - { name: "Motor-Runner", country: "The Fiends" } - ] -}; - -const cardTypes = ["Nuka-Cola Cap", "Sunset Sarsaparilla Cap", "Quantum Cap"]; -let deck = []; -let tradeCount = 0; - -function generateDeck() { - deck = []; - countries.forEach((country, index) => { - deck.push({ country: country.name, type: cardTypes[index % 3] }); - }); - deck.push({ country: "Wild", type: "Wild" }); - deck.push({ country: "Wild", type: "Wild" }); - shuffle(deck); -} - -function getTradeBonus() { - tradeCount++; - if (tradeCount <= 5) return 2 + (tradeCount * 2); - return 15 + ((tradeCount - 6) * 5); -} - -function shuffle(array) { - var currentIndex = array.length, temporaryValue, randomIndex; - while (0 !== currentIndex) { - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; - } - return array; -} - -const infoName = Array.from(document.getElementsByClassName('country')); -const infoLeader = Array.from(document.getElementsByClassName('leader')); -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 modal = document.querySelector('#start-modal'); -const reserveDisplay = document.querySelector('#reserve'); -const chosenLeader = document.querySelector('#chosen-leader'); -const chosenCountry = document.querySelector('#chosen-country'); -const submitName = document.querySelector('#submit-name'); -const winModal = document.querySelector('#win-modal'); -const winMessage = document.querySelector('.win-message'); -const playAgain = document.querySelector('#play-again'); - -const playerName = document.querySelector('.player-name'); -const playerCountry = document.querySelector('.player-country'); -const restart = document.querySelector('#restart'); -const playerPanel = document.querySelector('.player-panel'); -const infoPanel = document.querySelector('.info'); -const turnInfo = document.querySelector('.turn-info'); -const turnInfoMessage = document.querySelector('.turn-info-message'); -const end = document.querySelector('#end'); -const combatLog = document.getElementById('combat-log'); -const turboToggle = document.getElementById('turbo-toggle'); - -const helpBtn = document.querySelector('#help-btn'); -const helpModal = document.querySelector('#help-modal'); -const closeHelpBtn = document.querySelector('#close-help-btn'); - -let Gamestate = {}; -Gamestate.logQueue = []; -Gamestate.isLogging = false; - -Gamestate.logAction = function(message, isImportant = false) { - return new Promise(resolve => { - this.logQueue.push({message, isImportant, resolve}); - this.processLogQueue(); - }); -} - -Gamestate.processLogQueue = function() { - if(this.isLogging || this.logQueue.length === 0) return; - this.isLogging = true; - - let logData = this.logQueue.shift(); - if(!combatLog) { - this.isLogging = false; - logData.resolve(); - return; - } - - let entry = document.createElement('div'); - entry.className = 'log-entry' + (logData.isImportant ? ' important' : ''); - let now = new Date(); - let timeString = now.toLocaleTimeString([], {hour12: false}); - let fullText = `> [${timeString}] ${logData.message}`; - - combatLog.appendChild(entry); - if (combatLog.children.length > 50) combatLog.removeChild(combatLog.firstChild); - - let i = 0; - entry.textContent = ""; - let typeSpeed = turboToggle && turboToggle.checked ? 0 : 8; - - if (typeSpeed === 0) { - entry.textContent = fullText; - combatLog.scrollTop = combatLog.scrollHeight; - this.isLogging = false; - logData.resolve(); - this.processLogQueue(); - } else { - let typeInterval = setInterval(() => { - entry.textContent += fullText.charAt(i); - i++; - combatLog.scrollTop = combatLog.scrollHeight; - if (i >= fullText.length) { - clearInterval(typeInterval); - setTimeout(() => { - this.isLogging = false; - logData.resolve(); - this.processLogQueue(); - }, 200); - } - }, typeSpeed); - } -} - -Gamestate.injectHolidayEvents = function() { - let today = new Date(); - let currentYear = today.getFullYear(); - let todayTime = today.getTime(); - const daysToMs = (days) => days * 24 * 60 * 60 * 1000; - - function getFloatingDate(year, month, occurrence, dayOfWeek) { - let d = new Date(year, month, 1); - let day = d.getDay(); - let diff = dayOfWeek - day; - if (diff < 0) diff += 7; - let date = diff + 1 + (occurrence - 1) * 7; - if (occurrence === 5) { - let dTemp = new Date(year, month, date); - if (dTemp.getMonth() !== month) date -= 7; - } - return new Date(year, month, date); - } - - function getEaster(year) { - let f = Math.floor, G = year % 19, C = f(year / 100); - let H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30; - let I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11)); - let J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7; - let L = I - J, month = 3 + f((L + 40) / 44), day = L + 28 - 31 * f(month / 4); - return new Date(year, month - 1, day); - } - - [currentYear - 1, currentYear, currentYear + 1].forEach(year => { - let holidays = [ - { name: "New Year's Day", date: new Date(year, 0, 1), msgs: ["Resolutions for the New Year: Survive. Don't mutate.", "Someone set off a Fat Man to celebrate the New Year. Typical."] }, - { name: "MLK Day", date: getFloatingDate(year, 0, 3, 1), msgs: ["A cracked terminal displays an old speech about equality. Rare sentiment these days.", "Scavenged a pre-war holotape preaching peace and brotherhood."] }, - { name: "Valentine's Day", date: new Date(year, 1, 14), msgs: ["Found a skeleton clutching a faded box of Cram and a diamond ring.", "Raiders were spotted sharing a heart-shaped box of... questionable meat."] }, - { name: "St. Patrick's Day", date: new Date(year, 2, 17), msgs: ["Discovered a hidden stash of perfectly preserved Shamrock Gwinnett Ale.", "Someone painted a Power Armor helmet bright green. Subtle."] }, - { name: "Easter", date: getEaster(year), msgs: ["Found a clutch of colorful Deathclaw eggs. Best to leave them alone.", "Spotted a wastelander in a pre-war rabbit mascot suit. Terrifying."] }, - { name: "Mother's Day", date: getFloatingDate(year, 4, 2, 0), msgs: ["Unearthed a heartfelt pre-war letter addressed to 'Mom'.", "Found a faded holotape of a family's last Mother's day dinner."] }, - { name: "Memorial Day", date: getFloatingDate(year, 4, 5, 1), msgs: ["A moment of silence for the Anchorage veterans at a ruined memorial.", "Found a rusted set of dog tags and a folded flag."] }, - { name: "Juneteenth", date: new Date(year, 5, 19), msgs: ["A settlement radio is broadcasting songs of freedom and emancipation.", "Found a pre-war terminal documenting the end of slavery. A reminder of better ideals."] }, - { name: "Father's Day", date: getFloatingDate(year, 5, 3, 0), msgs: ["Found a dusty 'World's Best Dad' mug next to a hunting rifle.", "Unearthed a broken pocket watch with 'To Dad' engraved on the back."] }, - { name: "Independence Day", date: new Date(year, 6, 4), msgs: ["Boomers are launching fireworks. Or artillery. Hard to tell.", "A stray eyebot is blasting the Star-Spangled Banner at max volume."] }, - { name: "Labor Day", date: getFloatingDate(year, 8, 1, 1), msgs: ["Protectrons are stuck in a holiday loop, demanding fair wages.", "Found an old union strike poster in a ruined factory."] }, - { name: "Columbus Day", date: getFloatingDate(year, 9, 2, 1), msgs: ["Raiders claim they 'discovered' a settlement that was already occupied.", "Found a pre-war globe. So much of it is just glowing craters now."] }, - { name: "The Great War", date: new Date(year, 9, 23), msgs: ["October 23rd. The day the world ended. The air feels heavier today.", "Found a stopped watch. 9:47 AM. Never forget."] }, - { name: "Halloween", date: new Date(year, 9, 31), msgs: ["Feral ghouls are looking extra festive today. Still deadly, though.", "Found a pristine plastic pumpkin bucket full of bottle caps."] }, - { name: "Veterans Day", date: new Date(year, 10, 11), msgs: ["Found a pristine pre-war military uniform folded neatly in a footlocker.", "A lone bugler is playing Taps somewhere in the ruins."] }, - { name: "Thanksgiving Day", date: getFloatingDate(year, 10, 4, 4), msgs: ["Traders are roasting a two-headed Radturkey. Smells like radiation and sage.", "Settlers are giving thanks for another year of not being eaten."] }, - { name: "Christmas Eve", date: new Date(year, 11, 24), msgs: ["Spotted a sleigh pulled by Radstags in the distance. Probably hallucinations.", "A lone radio station is playing a static-filled version of Silent Night."] }, - { name: "Christmas Day", date: new Date(year, 11, 25), msgs: ["A protectron wrapped in tinsel is wishing everyone a 'Merry Christmas' before firing.", "Found a pristine snow globe. A tiny, un-nuked world in glass."] }, - { name: "New Year's Eve", date: new Date(year, 11, 31), msgs: ["Raiders are stockpiling explosives for midnight. Standard procedure.", "The last hours of the year. Let's hope the next one is less irradiated."] } - ]; - - holidays.forEach(holiday => { - let hTime = holiday.date.getTime(); - let startWindow = hTime - daysToMs(4); - let endWindow = hTime + daysToMs(2) + (daysToMs(1) - 1); - - if (todayTime >= startWindow && todayTime <= endWindow) { - holiday.msgs.forEach(msg => { - if (!wastelandEncounters.includes(msg)) { - wastelandEncounters.push(msg); - } - }); - } - }); - }); -} - -Gamestate.init = function(){ - if (winModal) winModal.style.display = "none"; - if (!map) return; - if (modal) modal.style.display = "block"; - - helpBtn?.addEventListener('click', (e) => { - e.preventDefault(); - if (helpModal) helpModal.style.display = 'block'; - }); - - closeHelpBtn?.addEventListener('click', () => { - if (helpModal) helpModal.style.display = 'none'; - }); - - this.injectHolidayEvents(); - - submitName?.addEventListener('click', this.start.bind(this)); - map?.addEventListener('mousedown', this.handleClick.bind(this)); - end?.addEventListener('click', this.handleEndTurn.bind(this)); - playAgain?.addEventListener('click', this.restart.bind(this)); - - restart?.addEventListener('click', () => { - let confirmModal = document.getElementById('confirm-restart-modal'); - if (confirmModal) confirmModal.style.display = 'flex'; - }); - - 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'; - }); - - document.getElementById('view-cards-btn')?.addEventListener('click', () => { - let cardsModal = document.getElementById('cards-modal'); - if (cardsModal) cardsModal.style.display = 'flex'; - this.renderCards(); - }); - - document.getElementById('close-cards-btn')?.addEventListener('click', () => { - let cardsModal = document.getElementById('cards-modal'); - if (cardsModal) cardsModal.style.display = 'none'; - }); - - document.getElementById('trade-btn')?.addEventListener('click', () => { - this.executeTrade(); - }); -} - -Gamestate.updateButtonText = function() { - if (!end) return; - if (this.stage === "Fortify") { - end.textContent = "Deploy Troops"; - end.style.opacity = "0.5"; - end.style.pointerEvents = "none"; - } else if (this.stage === "Battle") { - end.textContent = "End Attack Phase"; - end.style.opacity = "1"; - end.style.pointerEvents = "auto"; - } else if (this.stage === "Maneuver") { - end.textContent = "End Turn"; - end.style.opacity = "1"; - end.style.pointerEvents = "auto"; - } else if (this.stage === "AI Turn") { - end.textContent = "AI is thinking..."; - end.style.opacity = "0.5"; - end.style.pointerEvents = "none"; - } -} - -Gamestate.start = async function(){ - if(combatLog) combatLog.innerHTML = ""; - this.logQueue = []; - this.isLogging = false; - - if (end) end.style.pointerEvents = "auto"; - if (map) map.style.pointerEvents = "auto"; - if (modal) modal.style.display = "none"; - if (playerPanel) playerPanel.style.display = "flex"; - if (infoPanel) infoPanel.style.display = "flex"; - - let optRadstorms = document.getElementById('opt-radstorms') && document.getElementById('opt-radstorms').checked; - let optHorrors = document.getElementById('opt-horrors') && document.getElementById('opt-horrors').checked; - this.hazardsEnabled = optRadstorms; - - this.radstorm = { state: 'none', timer: 0, cooldown: Math.floor(Math.random() * 11) + 5, areas: [] }; - - this.countries = JSON.parse(JSON.stringify(countries)); - this.countries.forEach(c => { - let el = document.getElementById(c.name); - if (el) { - el.classList.remove('radstorm-warning'); - el.classList.remove('radstorm-active'); - } - }); - - let themeDropdown = document.getElementById('chosen-theme'); - let selectedTheme = themeDropdown ? themeDropdown.value : "fo3"; - - document.body.classList.remove('theme-fnv'); - if (selectedTheme === "fnv") document.body.classList.add('theme-fnv'); - - this.players = JSON.parse(JSON.stringify(basePlayers)); - - let factionList = themeFactions[selectedTheme]; - for(let i = 0; i < 6; i++) { - this.players[i].name = factionList[i].name; - this.players[i].country = factionList[i].country; - } - - if (optHorrors) { - this.players.push({ - "name": "Wasteland Horrors", "country": "Feral Ghouls & Deathclaws", - "color": "#333333", "army": 0, "reserve": 0, "areas": [], "bonus": 0, - "alive": true, "cards": [], "conqueredThisTurn": false, "isNeutral": true - }); - } - - this.aiTurn = false; - this.gameOver = false; - this.prevCountry = null; - this.prevTarget = null; - this.turn = 1; - this.stage = "Fortify"; - this.playerTroopsPlaced = 0; - - this.player = this.players[0]; - - let diffSelect = document.getElementById('chosen-difficulty'); - this.difficulty = diffSelect ? diffSelect.value : "Normal"; - - if (chosenLeader && chosenCountry) { - this.players[0].name = chosenLeader.value; - this.players[0].country = chosenCountry.value; - if (playerName) playerName.textContent = chosenLeader.value; - if (playerCountry) playerCountry.textContent = chosenCountry.value; - } - - generateDeck(); - tradeCount = 0; - this.players.forEach(p => { p.cards = []; p.conqueredThisTurn = false; }); - - let cardCount = document.getElementById('card-count'); - if (cardCount) cardCount.textContent = "0"; - if(this.prevTarget) this.prevTarget.classList.remove('flash'); - - for(let j = 0; j < this.players.length; j++){ - if(infoName[j]) infoName[j].innerHTML = this.players[j].country; - if(infoLeader[j]) infoLeader[j].innerHTML = this.players[j].name; - if(infoName[j]) infoName[j].parentElement.classList.remove('defeated'); - if(bar[j]) bar[j].style.background = this.players[j].color; - } - - this.stage = "Fortify"; - this.updateButtonText(); - if (turnInfoMessage) turnInfoMessage.textContent = "Click on your own areas to place reserve troops"; - - this.players.forEach(p => { p.reserve = p.isNeutral ? 0 : 20; p.army = 0; p.areas = []; }); - let shuffledAreas = shuffle([...areas]); - - shuffledAreas.forEach((area, i) => { - let player = this.players[i % this.players.length]; - let country = this.countries.find(c => c.name === area.id); - country.owner = player.name; - country.color = player.color; - country.army = player.isNeutral ? 8 : 1; - player.areas.push(country.name); - player.army += country.army; - if (!player.isNeutral) player.reserve -= 1; - }); - - this.players.forEach(player => { - while(player.reserve > 0 && !player.isNeutral) { - let randomArea = player.areas[Math.floor(Math.random() * player.areas.length)]; - let country = this.countries.find(c => c.name === randomArea); - country.army += 1; - player.army += 1; - player.reserve -= 1; - } - }); - - this.countries.forEach(country => { - let areaOnMap = document.getElementById(country.name); - if (areaOnMap) { - areaOnMap.style.fill = country.color; - if (areaOnMap.nextElementSibling) areaOnMap.nextElementSibling.textContent = country.army; - } - }); - - this.player.reserve = this.unitBonus(this.player, 0); - this.player.army += this.player.reserve; - this.updateInfo(); - - await this.logAction("RobCo OS Booted. Wasteland Simulation Online.", true); - if (optRadstorms) await this.logAction(">>> SYSTEM: METEOROLOGICAL HAZARD SUBSYSTEM LOADED."); - if (optHorrors) await this.logAction(">>> SYSTEM: NEUTRAL THREAT SCANNER ACTIVE."); -} - -Gamestate.showToast = function(message, bgColor = "#222") { - let toast = document.getElementById("toast"); - if(toast) { - toast.textContent = message; - toast.style.backgroundColor = bgColor; - toast.className = "toast show"; - if (this.toastTimeout) clearTimeout(this.toastTimeout); - this.toastTimeout = setTimeout(function(){ - toast.className = toast.className.replace("show", ""); - }, 3500); - } -} - -Gamestate.handleClick = function(e){ - if (this.aiTurn) return; - if(this.stage === "Fortify"){ - this.addArmy(e); - } else if(this.stage === "Battle"){ - this.attack(e); - } else if(this.stage === "Maneuver"){ - this.maneuver(e); - } -} - -Gamestate.win = function(player){ - if (winMessage) { - winMessage.textContent = player.name; - winMessage.style.color = player.color; - } - if (winModal) winModal.style.display = "block"; -} - -Gamestate.restart = function(){ - if (modal) modal.style.display = "flex"; - if (winModal) winModal.style.display = "none"; -} - -Gamestate.updateInfo = function(){ - if (turnInfo) turnInfo.textContent = this.stage; - let totalArmy = 0; - this.players.forEach(player => { totalArmy += player.army }); - - let sortedPlayers = [...this.players].sort((a, b) => b.army - a.army); - - this.players.forEach((player, i) => { - let infoBox = infoName[i] ? infoName[i].parentElement : null; - if (!infoBox) return; - - if (player.alive) { - if (player.isNeutral) { - if (infoIncome[i]) infoIncome[i].parentElement.style.display = "none"; - } else { - if (infoIncome[i]) { - infoIncome[i].innerHTML = player.bonus; - infoIncome[i].parentElement.style.display = "block"; - } - } - if (bar[i]) bar[i].style.width = (player.army / totalArmy) * 230 + 'px'; - } else { - if (infoIncome[i]) { - infoIncome[i].innerHTML = "OFFLINE"; - infoIncome[i].parentElement.style.display = "block"; - } - if (bar[i]) bar[i].style.width = "0px"; - } - - let rank = sortedPlayers.findIndex(p => p.name === player.name); - infoBox.style.display = "block"; - infoBox.style.order = rank; - }); - - 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"; - - let restartBtn = document.getElementById('restart'); - if (restartBtn) restartBtn.style.order = "999"; - - let cardCount = document.getElementById('card-count'); - if (cardCount) cardCount.textContent = this.player.cards.length; - if (reserveDisplay) reserveDisplay.innerHTML = this.player.reserve; - - let viewCardsBtn = document.getElementById('view-cards-btn'); - if (viewCardsBtn) { - if (this.getBestTrade(this.player.cards)) { - viewCardsBtn.textContent = "OPEN STASH"; - viewCardsBtn.style.opacity = "1"; - viewCardsBtn.style.pointerEvents = "auto"; - viewCardsBtn.classList.add('ready-to-trade'); - } else { - viewCardsBtn.textContent = this.player.cards.length > 0 ? "NO ELIGIBLE SETS" : "STASH EMPTY"; - viewCardsBtn.style.opacity = "0.5"; - viewCardsBtn.style.pointerEvents = "none"; - viewCardsBtn.classList.remove('ready-to-trade'); - } - } - - let hpFill = document.getElementById('hp-fill'); - let apFill = document.getElementById('ap-fill'); - - if (hpFill && apFill && totalArmy > 0 && this.player.alive) { - - let hpPercentage = Math.min(100, (this.player.areas.length / 24) * 100); - hpFill.style.width = hpPercentage + "%"; - - if (this.player.areas.length <= 5) { - hpFill.style.background = "#ff0000"; - hpFill.style.boxShadow = "0 0 10px rgba(255, 0, 0, 0.8)"; - } else { - hpFill.style.background = "var(--pip-color)"; - hpFill.style.boxShadow = "var(--pip-glow)"; - } - - let apPercentage = 0; - - if (this.stage === "Fortify") { - let maxReserve = Math.max(this.player.reserve + this.playerTroopsPlaced, 1); - apPercentage = (this.player.reserve / maxReserve) * 100; - - } else if (this.stage === "Battle") { - let currentStrikeForce = 0; - let validAttacks = 0; - let ownedTerritories = this.countries.filter(c => c.owner === this.player.name); - - ownedTerritories.forEach(t => { - if (t.army > 1) { - let hasEnemyNeighbor = t.neighbours.some(n => { - let neighborCountry = this.countries.find(x => x.name === n); - return neighborCountry && neighborCountry.owner !== this.player.name; - }); - if (hasEnemyNeighbor) { - 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; - } - apPercentage = Math.min(100, (currentStrikeForce / Math.max(this.initialStrikeForce, 1)) * 100); - } - - } else if (this.stage === "Maneuver") { - let canManeuver = false; - let ownedTerritories = this.countries.filter(c => c.owner === this.player.name); - for (let t of ownedTerritories) { - if (t.army > 1 && t.neighbours.some(n => { - let neighborCountry = this.countries.find(x => x.name === n); - return neighborCountry && neighborCountry.owner === this.player.name; - })) { - canManeuver = true; break; - } - } - if (canManeuver) { - apPercentage = this.maneuverSource ? 0 : 100; - } else { - apPercentage = 0; - } - - } else { - apPercentage = 0; - } - - apFill.style.width = apPercentage + "%"; - - if (apPercentage <= 0) { - apFill.style.opacity = "0"; - apFill.style.visibility = "hidden"; - } else { - apFill.style.opacity = "1"; - apFill.style.visibility = "visible"; - } - - if (hpPercentage <= 0) { - hpFill.style.opacity = "0"; - hpFill.style.visibility = "hidden"; - } else { - hpFill.style.opacity = "1"; - hpFill.style.visibility = "visible"; - } - - } else if (!this.player.alive && hpFill && apFill) { - hpFill.style.width = "0%"; - hpFill.style.opacity = "0"; - hpFill.style.visibility = "hidden"; - apFill.style.width = "0%"; - apFill.style.opacity = "0"; - apFill.style.visibility = "hidden"; - } - - if (this.radstorm && this.radstorm.areas) { - this.radstorm.areas.forEach(areaName => { - let el = document.getElementById(areaName); - if (el) { - if (this.radstorm.state === 'warning') el.classList.add('radstorm-warning'); - if (this.radstorm.state === 'active') el.classList.add('radstorm-active'); - } - }); - } - - this.lastStage = this.stage; -} - -Gamestate.renderCards = function() { - const list = document.getElementById('card-list'); - if (!list) return; - list.innerHTML = ''; - this.selectedCards = []; - - this.player.cards.forEach((card, index) => { - let cardEl = document.createElement('div'); - cardEl.textContent = `${card.country} (${card.type})`; - cardEl.className = 'risk-card'; - cardEl.onclick = () => this.toggleCardSelection(cardEl, index); - list.appendChild(cardEl); - }); - - let tradeBtn = document.getElementById('trade-btn'); - if (tradeBtn) { - tradeBtn.disabled = true; - tradeBtn.textContent = "SELECT 3 ELIGIBLE CAPS"; - tradeBtn.classList.remove('ready-to-trade'); - } -} - -Gamestate.toggleCardSelection = function(element, index) { - if (this.selectedCards.includes(index)) { - this.selectedCards = this.selectedCards.filter(i => i !== index); - element.classList.remove('selected'); - } else if (this.selectedCards.length < 3) { - this.selectedCards.push(index); - element.classList.add('selected'); - } - - let tradeBtn = document.getElementById('trade-btn'); - if (tradeBtn) { - let isValid = this.isValidTrade(); - tradeBtn.disabled = !isValid; - tradeBtn.textContent = isValid ? "SPEND CAPS FOR TROOPS" : "SELECT 3 ELIGIBLE CAPS"; - - if (isValid) { - tradeBtn.classList.add('ready-to-trade'); - } else { - tradeBtn.classList.remove('ready-to-trade'); - } - } -} - -Gamestate.isValidTrade = function() { - if (this.selectedCards.length !== 3) return false; - let types = this.selectedCards.map(i => this.player.cards[i].type); - let wilds = types.filter(t => t === "Wild").length; - let regularTypes = types.filter(t => t !== "Wild"); - let uniqueTypes = new Set(regularTypes).size; - return (uniqueTypes === 1 || uniqueTypes === 3 || wilds > 0); -} - -Gamestate.getBestTrade = function(cards) { - if (cards.length < 3) return null; - for (let i=0; i c.type); - let wilds = types.filter(t => t === "Wild").length; - let regularTypes = types.filter(t => t !== "Wild"); - let uniqueTypes = new Set(regularTypes).size; - if (uniqueTypes === 1 || uniqueTypes === 3 || wilds > 0) return [i, j, k]; - } - } - } - return null; -} - -Gamestate.executeTrade = async function() { - if (this.aiTurn) return; // Prevent trading while AI is calculating - - if (this.isValidTrade()) { - let bonus = getTradeBonus(); - this.selectedCards.sort((a,b) => b-a).forEach(index => { - deck.unshift(this.player.cards[index]); - this.player.cards.splice(index, 1); - }); - - this.player.reserve += bonus; - this.player.army += bonus; - - if (this.stage === "Battle" || this.stage === "Maneuver" || this.stage === "AI Turn") { - this.stage = "Fortify"; - if (turnInfo) turnInfo.textContent = "Combat Phase"; - if (turnInfoMessage) turnInfoMessage.textContent = "Bonus Received! Place your new troops."; - this.updateButtonText(); - } - - if (reserveDisplay) reserveDisplay.innerHTML = this.player.reserve; - this.updateInfo(); - - await this.logAction(`SUPPLY DROP: ${this.player.name} spent Caps for +${bonus} troops!`, true); - - this.renderCards(); - - let tradeBtn = document.getElementById('trade-btn'); - if (tradeBtn) { - tradeBtn.disabled = true; - tradeBtn.classList.remove('ready-to-trade'); - } - - let cardsModal = document.getElementById('cards-modal'); - if (cardsModal) cardsModal.style.display = 'none'; - } -} - -Gamestate.processRadstorm = async function() { - if (!this.hazardsEnabled) return; - - if (!this.radstorm) { - this.radstorm = { state: 'none', timer: 0, cooldown: Math.floor(Math.random() * 11) + 5, areas: [] }; - } - - this.countries.forEach(c => { - let el = document.getElementById(c.name); - if (el) { - el.classList.remove('radstorm-warning'); - el.classList.remove('radstorm-active'); - } - }); - - if (this.radstorm.state === 'none') { - this.radstorm.cooldown--; - if (this.radstorm.cooldown <= 0) { - - this.radstorm.state = 'warning'; - this.radstorm.timer = 2; - - let startCountry = this.countries[Math.floor(Math.random() * this.countries.length)]; - this.radstorm.areas = [startCountry.name]; - - let targetCount = Math.floor(Math.random() * 4) + 1; - while(this.radstorm.areas.length < targetCount) { - let potential = []; - this.radstorm.areas.forEach(a => { - let c = this.countries.find(x => x.name === a); - c.neighbours.forEach(n => { - let neighborExists = this.countries.some(x => x.name === n); - if (neighborExists && !this.radstorm.areas.includes(n) && !potential.includes(n)) potential.push(n); - }); - }); - if (potential.length === 0) break; - let nextArea = potential[Math.floor(Math.random() * potential.length)]; - this.radstorm.areas.push(nextArea); - } - await this.logAction(`[ S.O.S ] WARNING: Severe Radstorm forming over ${this.radstorm.areas.length} territories! Evacuate immediately.`, true); - } - } else if (this.radstorm.state === 'warning') { - this.radstorm.timer--; - if (this.radstorm.timer <= 0) { - this.radstorm.state = 'active'; - this.radstorm.timer = Math.floor(Math.random() * 4) + 2; - await this.logAction(`[ HAZARD ] The Radstorm has touched down! It will rage for ${this.radstorm.timer} days.`, true); - } else { - await this.logAction(`[ S.O.S ] Radstorm arriving in ${this.radstorm.timer} day(s)...`, true); - } - } - - if (this.radstorm.state === 'active') { - let totalKilled = 0; - this.radstorm.areas.forEach(areaName => { - let c = this.countries.find(x => x.name === areaName); - if (c.army > 1) { - let dmgPercent = (Math.random() * 0.15) + 0.10; - let dmg = Math.floor(c.army * dmgPercent); - if (dmg < 1) dmg = 1; - if (c.army - dmg < 1) dmg = c.army - 1; - - c.army -= dmg; - totalKilled += dmg; - - let owner = this.players.find(p => p.name === c.owner); - if (owner) owner.army -= dmg; - - let areaOnMap = document.getElementById(c.name); - if (areaOnMap && areaOnMap.nextElementSibling) areaOnMap.nextElementSibling.textContent = c.army; - } - }); - - if (totalKilled > 0) { - await this.logAction(`[ HAZARD ] Radstorm killed ${totalKilled} troops in the irradiated zone.`, true); - } else { - await this.logAction(`[ HAZARD ] Radstorm rages, but garrisons are already at minimum capacity.`); - } - - this.radstorm.timer--; - if (this.radstorm.timer <= 0) { - this.radstorm.state = 'none'; - this.radstorm.cooldown = Math.floor(Math.random() * 11) + 5; - await this.logAction(`[ CLEAR ] The Radstorm has dissipated. Skies are clear for now.`, true); - } - } - - this.radstorm.areas.forEach(areaName => { - let el = document.getElementById(areaName); - if (el) { - if (this.radstorm.state === 'warning') el.classList.add('radstorm-warning'); - if (this.radstorm.state === 'active') el.classList.add('radstorm-active'); - } - }); -} - -Gamestate.handleEndTurn = async function(){ - if(this.aiTurn || this.player.reserve > 0){ return; } - - if (this.stage === "Battle") { - let canManeuver = false; - let owned = this.countries.filter(c => c.owner === this.player.name); - for (let t of owned) { - if (t.army > 1 && t.neighbours.some(n => this.countries.find(x => x.name === n).owner === this.player.name)) { - canManeuver = true; break; - } - } - - if(this.prevTarget) this.prevTarget.classList.remove('flash'); - this.prevCountry = null; - this.prevTarget = null; - - if (canManeuver) { - this.stage = "Maneuver"; - this.maneuverSource = null; - this.maneuverDest = null; - - this.updateButtonText(); - if (turnInfo) turnInfo.textContent = "Maneuver Phase"; - if (turnInfoMessage) turnInfoMessage.textContent = "Move troops between adjacent territories. Hold SHIFT to move all."; - this.updateInfo(); - return; - } - } - - if (this.player.conqueredThisTurn && deck.length > 0) { - this.player.cards.push(deck.pop()); - this.updateInfo(); - if (this.getBestTrade(this.player.cards)) { - await this.logAction("STASH FULL: Enough Caps collected to hire more troops."); - } else { - await this.logAction("SCAVENGED: Found a Bottle Cap after securing enemy territory."); - } - } - this.player.conqueredThisTurn = false; - - this.aiTurn = true; - this.updateButtonText(); - if (map) map.style.pointerEvents = "none"; - this.aiMove(); -} - -Gamestate.unitBonus = function(player, i){ - if (!player.alive || player.isNeutral) return 0; - - player.bonus = 0; - player.bonus += Math.floor(player.areas.length / 3); - player.bonus += this.continentBonus(player); - if(player.bonus < 3){ player.bonus = 3; } - if (infoIncome[i]) infoIncome[i].innerHTML = player.bonus; - return player.bonus; -} - -Gamestate.continentBonus = function(player){ - if (player.isNeutral) return 0; - let bonus = 0; - continents.forEach( continent => { - let ownsContinent = continent.areas.every(area => player.areas.includes(area)); - - if(ownsContinent){ - bonus += continent.bonus; - } - }) - return bonus; -} - -Gamestate.addArmy = async function(e){ - let actionFired = false; - this.countries.forEach(country => { - if(e.target.id === country.name && this.player.reserve > 0 && country.owner === this.player.name){ - if(e.shiftKey){ - this.playerTroopsPlaced += this.player.reserve; - country.army += this.player.reserve; - this.player.reserve = 0; - } - else{ - this.playerTroopsPlaced += 1; - country.army += 1; - this.player.reserve -= 1; - } - - if (reserveDisplay) reserveDisplay.innerHTML = this.player.reserve; - if (e.target.nextElementSibling) e.target.nextElementSibling.textContent = country.army; - actionFired = true; - } - }) - - if(actionFired){ - this.updateInfo(); - - if(this.player.reserve === 0){ - await this.logAction(`${this.player.name} deployed ${this.playerTroopsPlaced} fresh troops across their sectors.`); - this.playerTroopsPlaced = 0; - - this.stage = "Battle"; - if (turnInfo) turnInfo.textContent = "Combat Phase"; - if (turnInfoMessage) turnInfoMessage.textContent = "Select staging territory, then target an enemy."; - this.updateButtonText(); - this.updateInfo(); - } - } -} - -Gamestate.vatsTargeting = async function(attackerEl, defenderEl) { - let turbo = turboToggle && turboToggle.checked; - if (turbo) return; - - await this.logAction(`[ V.A.T.S. TARGETING SEQUENCE ENGAGED ]`); - - for(let i=0; i<3; i++) { - if(attackerEl) attackerEl.classList.add('vats-flash'); - if(defenderEl) defenderEl.classList.add('vats-flash'); - await new Promise(r => setTimeout(r, 80)); - if(attackerEl) attackerEl.classList.remove('vats-flash'); - if(defenderEl) defenderEl.classList.remove('vats-flash'); - await new Promise(r => setTimeout(r, 60)); - } -} - -Gamestate.attack = async function(e){ - if(this.prevTarget) this.prevTarget.classList.remove('flash'); - - let country = this.countries.find(c => c.name === e.target.id); - if (!country) return; - - if (!this.prevCountry) { - if (country.owner === this.player.name) { - e.target.classList.add('flash'); - this.prevTarget = e.target; - this.prevCountry = country; - } - return; - } - - if (this.prevCountry.name !== country.name && country.owner !== this.player.name && this.prevCountry.owner === this.player.name) { - if (this.prevCountry.neighbours.includes(country.name) && this.prevCountry.army > 1) { - - let attackerMap = document.getElementById(this.prevCountry.name); - let defenderMap = document.getElementById(country.name); - - if (map) map.style.pointerEvents = "none"; - - await this.vatsTargeting(attackerMap, defenderMap); - await this.battle(this.prevCountry, country, this.player, 0); - - if (map) map.style.pointerEvents = "auto"; - } else if (this.prevCountry.army === 1) { - this.updateInfo(); - await this.logAction("Cannot attack: A minimum garrison of 1 troop must remain in the territory.", true); - } - } - - this.prevCountry = null; - this.prevTarget = null; -} - -Gamestate.maneuver = function(e){ - if(this.prevTarget){ this.prevTarget.classList.remove('flash'); } - this.countries.forEach(country => { - if(e.target.id === country.name){ - if (country.owner !== this.player.name) return; - - e.target.classList.add('flash'); - this.prevTarget = e.target; - - if(this.prevCountry){ - if(this.prevCountry.name !== country.name && this.prevCountry.owner === this.player.name){ - if (this.maneuverSource && this.maneuverDest) { - let valid1 = (this.prevCountry.name === this.maneuverSource && country.name === this.maneuverDest); - let valid2 = (this.prevCountry.name === this.maneuverDest && country.name === this.maneuverSource); - if (!valid1 && !valid2) { return; } - } - - if(this.prevCountry.neighbours.includes(country.name) && this.prevCountry.army > 1){ - this.maneuverSource = this.prevCountry.name; - this.maneuverDest = country.name; - - let moveAmount = e.shiftKey ? (this.prevCountry.army - 1) : 1; - - country.army += moveAmount; - this.prevCountry.army -= moveAmount; - - let sourceMap = document.getElementById(`${this.prevCountry.name}`); - let destMap = document.getElementById(`${country.name}`); - - if (sourceMap && sourceMap.nextElementSibling) sourceMap.nextElementSibling.textContent = this.prevCountry.army; - if (destMap && destMap.nextElementSibling) destMap.nextElementSibling.textContent = country.army; - - this.updateInfo(); - } - } - } - this.prevCountry = country; - } - }); -} - -Gamestate.aiMove = async function(){ - if(this.gameOver) return; - if(this.prevTarget) this.prevTarget.classList.remove('flash'); - - this.stage = "AI Turn"; - this.updateButtonText(); - if (turnInfoMessage) turnInfoMessage.textContent = ""; - - for(let i = 1; i <= this.players.length; i++){ - if(i === this.players.length){ - if(this.player.areas.length === 0){ - this.player.alive = false; - return this.aiMove(); - } - this.turn += 1; - this.aiTurn = false; - - await this.processRadstorm(); - - this.stage = "Fortify"; - this.updateButtonText(); - - if (turnInfoMessage) turnInfoMessage.textContent = "Deploy reserve troops to your territories."; - let bonus = this.unitBonus(this.player, 0); - this.player.reserve += bonus; - this.player.army += bonus; - if (map) map.style.pointerEvents = "auto"; - if (infoName[i-1]) infoName[i-1].parentElement.classList.remove('highlight'); - if (infoName[0]) infoName[0].parentElement.classList.add('highlight') - if (reserveDisplay) reserveDisplay.innerHTML = this.player.reserve; - this.updateInfo(); - return; - } - - if (infoName[i-1]) infoName[i-1].parentElement.classList.remove('highlight'); - if(this.players[i].alive){ - - if (this.players[i].isNeutral) { - continue; - } - - if (infoName[i]) infoName[i].parentElement.classList.add('highlight') - - let turbo = turboToggle && turboToggle.checked; - - if (Math.random() < 0.20) { - let randomEvent = wastelandEncounters[Math.floor(Math.random() * wastelandEncounters.length)]; - await this.logAction(`SYSTEM: ${randomEvent}`); - } - - this.players[i].reserve = this.unitBonus(this.players[i], i); - let troopsToPlace = this.players[i].reserve; - - let tradeIndices = this.getBestTrade(this.players[i].cards); - if (tradeIndices) { - let bonus = getTradeBonus(); - tradeIndices.sort((a,b)=>b-a).forEach(index => { - deck.unshift(this.players[i].cards[index]); - this.players[i].cards.splice(index, 1); - }); - this.players[i].reserve += bonus; - troopsToPlace += bonus; - await this.logAction(`SUPPLY: ${this.players[i].name} spent Caps for +${bonus} troops!`, true); - } - - this.players[i].army += this.players[i].reserve; - - if (troopsToPlace > 0) { - await this.logAction(`${this.players[i].name} deployed ${troopsToPlace} troops to their sectors.`); - } - - let areaToFortify = ["", 0]; - this.players[i].areas.forEach(area => { - this.countries.forEach(country => { - if(country.name === area && this.players[i].reserve > 0){ - country.neighbours.forEach(neighbour => { - this.countries.forEach(c => { - if(c.name === neighbour && c.owner !== this.players[i].name){ - let continent = continents.find(x => x.name === country.continent); - let count = 0; - continent.areas.forEach(x => { - if (this.players[i].areas.includes(x)) count++; - }); - let ratio = count / continent.areas.length; - if (ratio >= areaToFortify[1]){ areaToFortify = [country, ratio] } - } - }) - }) - } - }) - }) - - if (areaToFortify[0]) { - areaToFortify[0].army += this.players[i].reserve; - this.players[i].reserve = 0; - let areaOnMap = document.getElementById(`${areaToFortify[0].name}`); - if (areaOnMap && areaOnMap.nextElementSibling) areaOnMap.nextElementSibling.textContent = areaToFortify[0].army; - } - - let currentAreas = [...this.players[i].areas]; - for (let area of currentAreas) { - let country = this.countries.find(c => c.name === area); - if (country && country.army > 1 && country.owner === this.players[i].name) { - await this.aiAttack(country, i, turbo); - } - } - - this.aiManeuver(i); - - if (this.players[i].conqueredThisTurn && deck.length > 0) { - this.players[i].cards.push(deck.pop()); - this.players[i].conqueredThisTurn = false; - } - - this.updateInfo(); - } - } -} - -Gamestate.aiAttack = async function(country, i, turbo){ - let possibleTargets = []; - country.neighbours.forEach(neighbour => { - this.countries.forEach(opponent => { - if(neighbour === opponent.name && opponent.army + 1 < country.army && country.owner !== opponent.owner ){ - possibleTargets.push(opponent); - } - }) - }) - - let target = [possibleTargets[0], 0]; - possibleTargets.forEach(poss => { - let continent = continents.find(x => x.name === poss.continent); - let count = 0; - continent.areas.forEach(x => { if(this.players[i].areas.includes(x)) count++; }); - let ratio = count / continent.areas.length; - if(ratio >= target[1]){ target = [poss, ratio] } - }) - if(!target[0]){ return; } - - let attackerMap = document.getElementById(country.name); - let defenderMap = document.getElementById(target[0].name); - - await this.vatsTargeting(attackerMap, defenderMap); - await this.battle(country, target[0], this.players[i], i); -} - -Gamestate.aiManeuver = function(i){ - let player = this.players[i]; - let owned = this.countries.filter(c => c.owner === player.name); - let internal = owned.filter(c => c.army > 1 && c.neighbours.every(n => { - let neighborCountry = this.countries.find(x => x.name === n); - return neighborCountry && neighborCountry.owner === player.name; - })); - - if(internal.length > 0) { - internal.sort((a,b) => b.army - a.army); - let source = internal[0]; - let destName = source.neighbours.find(n => this.countries.find(x => x.name === n).owner === player.name); - if(destName) { - let dest = this.countries.find(x => x.name === destName); - let moveAmount = source.army - 1; - dest.army += moveAmount; - source.army -= moveAmount; - - let sourceMap = document.getElementById(`${source.name}`); - let destMap = document.getElementById(`${dest.name}`); - if(sourceMap && sourceMap.nextElementSibling) sourceMap.nextElementSibling.textContent = source.army; - if(destMap && destMap.nextElementSibling) destMap.nextElementSibling.textContent = dest.army; - } - } -} - -Gamestate.battle = async function(country, opponent, player, i){ - let defender = document.getElementById(`${opponent.name}`) - let attacker = document.getElementById(`${country.name}`) - let opp; - this.players.forEach(p => { if(p.name === opponent.owner){ opp = p; } }); - - const originalOwner = opponent.owner; - - let attackerWinChance = 0.5; - if (this.difficulty === "Easy") { - if (player === this.player) attackerWinChance = 0.60; - else if (opp === this.player) attackerWinChance = 0.40; - } else if (this.difficulty === "Hard") { - if (player === this.player) attackerWinChance = 0.40; - else if (opp === this.player) attackerWinChance = 0.60; - } - - if (opp.isNeutral) { - attackerWinChance -= 0.15; - } - - let isVictory = false; - let flavor = ""; - - while(opponent.army > 0){ - if(country.army <= 1){ - if (attacker && attacker.nextElementSibling) attacker.nextElementSibling.textContent = country.army; - if (defender && defender.nextElementSibling) defender.nextElementSibling.textContent = opponent.army; - - this.updateInfo(); - - await this.logAction(`REPULSED: ${player.name} assaulted ${originalOwner} in ${opponent.name} but failed.`); - return; - } - if(Math.random() > attackerWinChance){ country.army -= 1; } else { opponent.army -= 1; } - } - - if(opponent.army <= 0 ){ - isVictory = true; - player.conqueredThisTurn = true; - - flavor = (Math.random() < 0.10) ? (" " + combatFlavors[Math.floor(Math.random() * combatFlavors.length)]) : "!"; - - this.players.forEach(p => { - if(p.name === opponent.owner){ - let index = p.areas.indexOf(opponent.name); - if (index > -1) { p.areas.splice(index, 1); } - } - }); - - opponent.owner = player.name; - opponent.color = player.color; - player.areas.push(opponent.name); - - let movedTroops = Math.max(1, Math.floor(country.army / 2)); - opponent.army = movedTroops; - country.army -= movedTroops; - - if (defender) defender.style.fill = opponent.color; - if (defender && defender.nextElementSibling) defender.nextElementSibling.textContent = opponent.army; - if (attacker && attacker.nextElementSibling) attacker.nextElementSibling.textContent = country.army; - - if(opp.areas.length === 0){ - opp.alive = false; - let index = this.players.indexOf(opp) - if (infoName[index]) infoName[index].parentElement.classList.add('defeated'); - } - } - - player.army = 0; - opp.army = 0; - this.countries.forEach(c => { - player.areas.forEach(area => { if(area === c.name){ player.army += c.army } }) - opp.areas.forEach(area => { if(area === c.name){ opp.army += c.army } }) - }); - - this.updateInfo(); - - if (isVictory) { - await this.logAction(`VICTORY: ${player.name} wiped out ${originalOwner} in ${opponent.name}${flavor}`, true); - } - - if(this.player.alive){ - continents.forEach(continent => { - let ownsContinent = continent.areas.every(area => player.areas.includes(area)); - - if(ownsContinent){ - let matchedCountry = continent.areas.some(a => { return a === opponent.name; }); - if(matchedCountry){ - this.showToast(`${player.name} controls ${continent.name}! (+${continent.bonus} troops)`, player.color); - } - } - }) - } - - if(player.areas.length === 42){ - this.gameOver = true; - this.win(player); - } -} - -Gamestate.init(); \ No newline at end of file