您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
An improved version of the comprehensive audio player that attempts to recreate the cart experience with more sound effects, more music, and more customizability.
当前为
// ==UserScript== // @name Improved AWBW Music Player // @description An improved version of the comprehensive audio player that attempts to recreate the cart experience with more sound effects, more music, and more customizability. // @namespace https://awbw.amarriner.com/ // @author DeveloperJose, _twiggy // @match https://awbw.amarriner.com/* // @icon https://developerjose.netlify.app/img/music-player-icon.png // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/howler.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/spark-md5.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/build/can-autoplay.min.js // @run-at document-end // @version 5.1.0 // @supportURL https://github.com/DeveloperJose/JS-AWBW-User-Scripts/issues // @contributionURL https://ko-fi.com/developerjose // @license MIT // @unwrap // @grant none // ==/UserScript== var awbw_music_player = (function (exports, canAutoplay, SparkMD5) { "use strict"; function styleInject(css, ref) { if (ref === undefined) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === "undefined") { return; } var head = document.head || document.getElementsByTagName("head")[0]; var style = document.createElement("style"); style.type = "text/css"; if (insertAt === "top") { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css_248z$1 = '/* This file is used to style the music player settings */\n\niframe {\n border: none;\n}\n\n.cls-settings-menu {\n display: none;\n /* display: flex; */\n top: 40px;\n flex-direction: column;\n width: 750px;\n border: black 1px solid;\n z-index: 20;\n text-align: center;\n align-items: center;\n font-family: "Nova Square", cursive !important;\n}\n\n.cls-settings-menu label {\n background-color: white;\n font-size: 12px;\n}\n\n.cls-settings-menu .cls-group-box > label {\n width: 100%;\n font-size: 13px;\n background-color: #d6e0ed;\n padding-top: 2px;\n padding-bottom: 2px;\n}\n\n.cls-settings-menu .cls-vertical-box {\n display: flex;\n flex-direction: column;\n justify-content: space-evenly;\n align-items: center;\n padding-left: 5px;\n padding-right: 5px;\n padding-top: 1px;\n padding-bottom: 1px;\n height: 100%;\n width: 100%;\n position: relative;\n}\n\n.cls-settings-menu .cls-horizontal-box {\n display: flex;\n flex-direction: row;\n justify-content: space-evenly;\n align-items: center;\n padding-left: 5px;\n padding-right: 5px;\n padding-top: 1px;\n padding-bottom: 1px;\n height: 100%;\n width: 100%;\n position: relative;\n}\n\n/* Puts the checkbox next to the label */\n.cls-settings-menu .cls-vertical-box[id$="options"] {\n align-items: center;\n align-self: center;\n}\n\n.cls-settings-menu .cls-vertical-box[id$="options"] .cls-horizontal-box {\n width: 100%;\n justify-content: center;\n}\n\n.cls-settings-menu .cls-vertical-box[id$="options"] .cls-horizontal-box input {\n vertical-align: middle;\n}\n\n/* .cls-settings-menu .cls-vertical-box[id$="options"] .cls-horizontal-box label {\n display: block;\n padding-right: 10px;\n padding-left: 22px;\n text-indent: -22px;\n} */\n\n/* .cls-settings-menu .cls-horizontal-box[id$="random-themes"],\n.cls-settings-menu .cls-horizontal-box[id$="soundtrack"] {\n justify-content: center;\n} */\n\n.cls-settings-menu-box {\n display: flex;\n flex-direction: column;\n justify-content: space-evenly;\n padding-left: 5px;\n padding-right: 5px;\n padding-top: 1px;\n padding-bottom: 1px;\n width: 100%;\n}\n\n.cls-settings-menu image {\n vertical-align: middle;\n}\n\n.cls-settings-menu label[id$="version"] {\n width: 100%;\n font-size: 10px;\n color: #888888;\n background-color: #f0f0f0;\n}\n\n.cls-settings-menu a[id$="update"] {\n font-size: 12px;\n background-color: #ff0000;\n color: white;\n width: 100%;\n}\n.cls-settings-menu .co_caret {\n position: absolute;\n top: 28px;\n left: 25px;\n border: none;\n z-index: 30;\n}\n\n.cls-settings-menu .co_portrait {\n border-color: #009966;\n z-index: 30;\n border: 2px solid;\n vertical-align: middle;\n align-self: center;\n}\n\n.cls-settings-menu input[type="range"][id$="themes-start-on-day"] {\n --c: rgb(168, 73, 208); /* active color */\n}\n'; styleInject(css_248z$1); var css_248z = '/* \n * CSS Custom Range Slider\n * https://www.sitepoint.com/css-custom-range-slider/ \n */\n\n.cls-settings-menu input[type="range"] {\n --c: rgb(53 57 60); /* active color */\n --l: 15px; /* line thickness*/\n --h: 30px; /* thumb height */\n --w: 15px; /* thumb width */\n\n width: 100%;\n height: var(--h); /* needed for Firefox*/\n --_c: color-mix(in srgb, var(--c), #000 var(--p, 0%));\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n background: none;\n cursor: pointer;\n overflow: hidden;\n display: inline-block;\n}\n.cls-settings-menu input:focus-visible,\n.cls-settings-menu input:hover {\n --p: 25%;\n}\n\n/* chromium */\n.cls-settings-menu input[type="range" i]::-webkit-slider-thumb {\n height: var(--h);\n width: var(--w);\n background: var(--_c);\n border-image: linear-gradient(90deg, var(--_c) 50%, #ababab 0) 0 1 / calc(50% - var(--l) / 2) 100vw/0 100vw;\n -webkit-appearance: none;\n appearance: none;\n transition: 0.3s;\n}\n/* Firefox */\n.cls-settings-menu input[type="range"]::-moz-range-thumb {\n height: var(--h);\n width: var(--w);\n background: var(--_c);\n border-image: linear-gradient(90deg, var(--_c) 50%, #ababab 0) 0 1 / calc(50% - var(--l) / 2) 100vw/0 100vw;\n -webkit-appearance: none;\n appearance: none;\n transition: 0.3s;\n}\n@supports not (color: color-mix(in srgb, red, red)) {\n .cls-settings-menu input {\n --_c: var(--c);\n }\n}\n'; styleInject(css_248z); function logInfo(message, ...args) { console.log("[AWBW Improved Music Player]", message, ...args); } function logError(message, ...args) { console.error("[AWBW Improved Music Player]", message, ...args); } function logDebug(message, ...args) { console.debug("[AWBW Improved Music Player]", message, ...args); } function debounce(ms, callback, immediate = false) { let timeout; return function (...args) { const context = this; const later = () => { timeout = null; if (!immediate) { callback.apply(context, args); } }; const callNow = immediate && !timeout; if (typeof timeout === "number") { window.clearTimeout(timeout); } timeout = window.setTimeout(later, ms); if (callNow) { callback.apply(context, args); } }; } const IFRAME_ID = "music-player-iframe"; const broadcastChannel = new BroadcastChannel("awbw-music-player"); function isIFrameActive() { const iframe = document.getElementById(IFRAME_ID); if (!iframe) return false; const href = iframe.contentDocument?.location.href ?? iframe.src; return href !== null && href !== "" && href !== "about:blank"; } function getCurrentDocument() { if (!isIFrameActive()) return window.document; const iframe = document.getElementById(IFRAME_ID); return iframe?.contentDocument ?? window.document; } function initializeIFrame(init_fn) { const hasFrame = document.getElementById(IFRAME_ID); if (hasFrame) return; const iframe = document.createElement("iframe"); iframe.style.display = "none"; iframe.id = IFRAME_ID; iframe.name = IFRAME_ID; document.body.appendChild(iframe); iframe.addEventListener("load", (event) => onIFrameLoad(event, init_fn)); hijackLinks(window.document); init_fn(); window.addEventListener("popstate", (event) => { const href = window.location.href; const iframe2 = document.getElementById(IFRAME_ID); if (!iframe2 || href.includes("game.php")) { window.location.reload(); return; } iframe2.src = href; const state = event.state; if (!state || !state.scrollX || !state.scrollY) return; window.scrollTo(state.scrollX, state.scrollY); }); } function onIFrameLoad(event, initFn) { const iframe = event.target; if (!iframe || !iframe.contentDocument) return; const href = iframe.contentDocument.location.href ?? iframe.src; if (href === null || href === "" || href === "about:blank") return; for (const child of Array.from(document.body.children)) { if (child === iframe) continue; if (child.id === "overDiv") continue; child.remove(); } iframe.style.display = "block"; iframe.style.width = "100%"; iframe.style.height = "100%"; document.body.style.width = "100%"; document.body.style.height = "100%"; document.body.style.overflow = "hidden"; if (document.body.parentElement) { document.body.parentElement.style.width = "100%"; document.body.parentElement.style.height = "100%"; } const state = { scrollX: window.scrollX, scrollY: window.scrollY }; window.history.pushState(state, "", href); document.title = iframe.contentDocument.title; hijackLinks(iframe.contentDocument); initFn(); } function hijackLinks(doc) { if (!doc) { logError("Could not find the document to hijack links."); return; } const links = doc.querySelectorAll("a"); if (!links) { logError("Could not find any links to hijack."); return; } for (const link of Array.from(links)) { const isGamePageLink = link.href.includes("game.php") || (link.classList.contains("anchor") && link.name.includes("game_")); const isMovePlannerLink = link.href.includes("moveplanner.php"); const isJSLink = link.href.startsWith("javascript:"); if (isJSLink) continue; else if (link.href === "" || isMovePlannerLink) continue; else if (isGamePageLink) link.target = "_top"; else link.target = IFRAME_ID; } } var PageType = /* @__PURE__ */ ((PageType2) => { PageType2["Maintenance"] = "Maintenance"; PageType2["ActiveGame"] = "ActiveGame"; PageType2["MapEditor"] = "MapEditor"; PageType2["MovePlanner"] = "MovePlanner"; PageType2["LiveQueue"] = "LiveQueue"; PageType2["MainPage"] = "MainPage"; PageType2["Default"] = "Default"; return PageType2; })(PageType || {}); function getCurrentPageType() { const doc = getCurrentDocument(); const isMaintenance = doc.querySelector("#server-maintenance-alert") !== null; if (isMaintenance) return "Maintenance" /* Maintenance */; if (doc.location.href.indexOf("game.php") > -1) return "ActiveGame" /* ActiveGame */; if (doc.location.href.indexOf("editmap.php?") > -1) return "MapEditor" /* MapEditor */; if (doc.location.href.indexOf("moveplanner.php") > -1) return "MovePlanner" /* MovePlanner */; if (doc.location.href.indexOf("live_queue.php") > -1) return "LiveQueue" /* LiveQueue */; if (doc.location.href === "https://awbw.amarriner.com/") return "MainPage" /* MainPage */; return "Default" /* Default */; } function getCoordsDiv() { return getCurrentDocument().querySelector("#coords"); } function getReplayControls() { return getCurrentDocument().querySelector(".replay-controls"); } function getReplayOpenBtn() { return getCurrentDocument().querySelector(".replay-open"); } function getReplayCloseBtn() { return getCurrentDocument().querySelector(".replay-close"); } function getReplayForwardBtn() { return getCurrentDocument().querySelector(".replay-forward"); } function getReplayForwardActionBtn() { return getCurrentDocument().querySelector(".replay-forward-action"); } function getReplayBackwardBtn() { return getCurrentDocument().querySelector(".replay-backward"); } function getReplayBackwardActionBtn() { return getCurrentDocument().querySelector(".replay-backward-action"); } function getReplayDaySelectorCheckBox() { return getCurrentDocument().querySelector(".replay-day-selector"); } function getConnectionErrorDiv() { return getCurrentDocument().querySelector(".connection-error-msg"); } function getLiveQueueSelectPopup() { return getCurrentDocument().querySelector("#live-queue-select-popup"); } function getLiveQueueBlockerPopup() { return getCurrentDocument().querySelector(".live-queue-blocker-popup"); } function getBuildingDiv(buildingID) { return getCurrentDocument().querySelector(`.game-building[data-building-id='${buildingID}']`); } function getAllDamageSquares() { return Array.from(getCurrentDocument().getElementsByClassName("dmg-square")); } const moveAnimationDelayMS = 5; function moveDivToOffset(div, dx, dy, steps, ...followUpAnimations) { if (steps <= 1) { if (!followUpAnimations || followUpAnimations.length === 0) return; const nextSet = followUpAnimations.shift()?.then; if (!nextSet) return; moveDivToOffset(div, nextSet[0], nextSet[1], nextSet[2], ...followUpAnimations); return; } window.setTimeout(() => moveDivToOffset(div, dx, dy, steps - 1, ...followUpAnimations), moveAnimationDelayMS); let left = parseFloat(div.style.left); let top = parseFloat(div.style.top); left += dx; top += dy; div.style.left = left + "px"; div.style.top = top + "px"; } function addUpdateCursorObserver(onCursorMove) { const coordsDiv = getCoordsDiv(); if (!coordsDiv) return; const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type !== "childList") return; if (!mutation.target) return; if (!mutation.target.textContent) return; let coordsText = mutation.target.textContent; coordsText = coordsText.substring(1, coordsText.length - 1); const splitCoords = coordsText.split(","); const cursorX = Number(splitCoords[0]); const cursorY = Number(splitCoords[1]); onCursorMove(cursorX, cursorY); } }); observer.observe(coordsDiv, { childList: true }); } const ORANGE_STAR_COs = /* @__PURE__ */ new Set(["andy", "max", "sami", "nell", "hachi", "jake", "rachel"]); const BLUE_MOON_COs = /* @__PURE__ */ new Set(["olaf", "grit", "colin", "sasha"]); const GREEN_EARTH_COs = /* @__PURE__ */ new Set(["eagle", "drake", "jess", "javier"]); const YELLOW_COMET_COs = /* @__PURE__ */ new Set(["kanbei", "sonja", "sensei", "grimm"]); const BLACK_HOLE_COs = /* @__PURE__ */ new Set([ "flak", "lash", "adder", "hawke", "sturm", "jugger", "koal", "kindle", "vonbolt", ]); const AW2_ONLY_COs = /* @__PURE__ */ new Set(["hachi", "colin", "sensei", "jess", "flak", "adder", "lash", "hawke"]); const AW_DS_ONLY_COs = /* @__PURE__ */ new Set([ "jake", "rachel", "sasha", "javier", "grimm", "kindle", "jugger", "koal", "vonbolt", ]); function getAllCONames(properCase = false) { if (!properCase) return [...ORANGE_STAR_COs, ...BLUE_MOON_COs, ...GREEN_EARTH_COs, ...YELLOW_COMET_COs, ...BLACK_HOLE_COs]; const allCOs = [...ORANGE_STAR_COs, ...BLUE_MOON_COs, ...GREEN_EARTH_COs, ...YELLOW_COMET_COs, ...BLACK_HOLE_COs]; allCOs[allCOs.indexOf("vonbolt")] = "Von Bolt"; return allCOs.map((co) => co[0].toUpperCase() + co.slice(1)); } function areAnimationsEnabled() { return typeof gameAnims !== "undefined" ? gameAnims : false; } function isBlackHoleCO(coName) { coName = coName.toLowerCase().replaceAll(" ", ""); return BLACK_HOLE_COs.has(coName); } function getRandomCO(excludedCOs) { const COs = new Set(getAllCONames()); for (const co of excludedCOs) COs.delete(co); if (COs.size === 0) return "map-editor"; if (COs.size === 1) return [...COs][0]; return [...COs][Math.floor(Math.random() * COs.size)]; } var SpecialCOs = /* @__PURE__ */ ((SpecialCOs2) => { SpecialCOs2["Maintenance"] = "maintenance"; SpecialCOs2["MapEditor"] = "map-editor"; SpecialCOs2["MainPage"] = "main-page"; SpecialCOs2["LiveQueue"] = "live-queue"; SpecialCOs2["Default"] = "default"; SpecialCOs2["Victory"] = "victory"; SpecialCOs2["Defeat"] = "defeat"; SpecialCOs2["COSelect"] = "co-select"; SpecialCOs2["ModeSelect"] = "mode-select"; return SpecialCOs2; })(SpecialCOs || {}); var COPowerEnum = /* @__PURE__ */ ((COPowerEnum2) => { COPowerEnum2["NoPower"] = "N"; COPowerEnum2["COPower"] = "Y"; COPowerEnum2["SuperCOPower"] = "S"; return COPowerEnum2; })(COPowerEnum || {}); const siloDelayMS = areAnimationsEnabled() ? 3e3 : 0; const attackDelayMS = areAnimationsEnabled() ? 1e3 : 0; function getMyUsername() { const document = getCurrentDocument(); const profileMenu = document.querySelector("#profile-menu"); if (!profileMenu) return null; const link = profileMenu.getElementsByClassName("dropdown-menu-link")[0]; return link.href.split("username=")[1]; } let myID = -1; function getMyID() { if (getCurrentPageType() !== PageType.ActiveGame) return -1; if (myID < 0) { getAllPlayersInfo().forEach((entry) => { if (entry.users_username === getMyUsername()) { myID = entry.players_id; } }); } return myID; } function getPlayerInfo(pid) { if (getCurrentPageType() !== PageType.ActiveGame) return null; if (typeof playersInfo === "undefined") return null; return playersInfo[pid]; } function getAllPlayersInfo() { if (getCurrentPageType() !== PageType.ActiveGame) return []; if (typeof playersInfo === "undefined") return []; return Object.values(playersInfo); } function isPlayerSpectator(pid) { if (getCurrentPageType() !== PageType.ActiveGame) return false; if (typeof playerKeys === "undefined") return false; return !playerKeys.includes(pid); } function canPlayerActivateCOPower(pid) { if (getCurrentPageType() !== PageType.ActiveGame) return false; const info = getPlayerInfo(pid); if (!info) return false; return info.players_co_power >= info.players_co_max_power; } function canPlayerActivateSuperCOPower(pid) { if (getCurrentPageType() !== PageType.ActiveGame) return false; const info = getPlayerInfo(pid); if (!info) return false; return info.players_co_power >= info.players_co_max_spower; } function getBuildingInfo(x, y) { if (getCurrentPageType() !== PageType.ActiveGame) return null; if (typeof buildingsInfo === "undefined") return null; return buildingsInfo[x][y]; } function isReplayActive() { if (getCurrentPageType() !== PageType.ActiveGame) return false; const replayControls = getReplayControls(); if (!replayControls) return false; const replayOpen = replayControls.style.display !== "none"; return replayOpen && Object.keys(replay).length > 0; } function hasGameEnded() { if (getCurrentPageType() !== PageType.ActiveGame) return false; if (typeof playersInfo === "undefined") return false; const numberOfRemainingPlayers = Object.values(playersInfo).filter( (info) => info.players_eliminated === "N", ).length; return numberOfRemainingPlayers === 1; } function getCOImagePrefix() { if (typeof coTheme === "undefined") return "aw2"; return coTheme; } function getServerTimeZone() { if (getCurrentPageType() !== PageType.ActiveGame) return "-05:00"; if (typeof serverTimezone === "undefined") return "-05:00"; if (!serverTimezone) return "-05:00"; return serverTimezone; } function didGameEndToday() { if (!hasGameEnded()) return false; const serverTimezone2 = parseInt(getServerTimeZone()); const endDate = new Date(gameEndDate); endDate.setHours(23, 59, 59); const now = /* @__PURE__ */ new Date(); const timezoneOffset = now.getTimezoneOffset() / 60; const difference = +serverTimezone2 + timezoneOffset; const nowAdjustedToServer = new Date(now.getTime() + difference * 36e5); const endDateAdjustedToServer = new Date(endDate.getTime() + difference * 36e5); const oneDayMilliseconds = 24 * 60 * 60 * 1e3; return nowAdjustedToServer.getTime() - endDateAdjustedToServer.getTime() < oneDayMilliseconds; } function getCurrentGameDay() { if (getCurrentPageType() !== PageType.ActiveGame) return 1; if (typeof gameDay === "undefined") return 1; if (!isReplayActive()) return gameDay; const replayData = Object.values(replay); if (replayData.length === 0) return gameDay; const lastData = replayData[replayData.length - 1]; if (typeof lastData === "undefined") return gameDay; if (typeof lastData.day === "undefined") return gameDay; return lastData.day; } class currentPlayer { /** * Get the internal info object containing the state of the current player. */ static get info() { if (getCurrentPageType() !== PageType.ActiveGame) return null; if (typeof currentTurn === "undefined") return null; return getPlayerInfo(currentTurn); } /** * Determine whether a CO Power or Super CO Power is activated for the current player. * @returns - True if a regular CO power or a Super CO Power is activated. */ static get isPowerActivated() { if (getCurrentPageType() !== PageType.ActiveGame) return false; return this?.coPowerState !== "N" /* NoPower */; } /** * Gets state of the CO Power for the current player represented as a single letter. * @returns - The state of the CO Power for the current player. */ static get coPowerState() { if (getCurrentPageType() !== PageType.ActiveGame) return "N" /* NoPower */; return this.info?.players_co_power_on; } /** * Determine if the current player has been eliminated from the game. * @returns - True if the current player has been eliminated. */ static get isEliminated() { if (getCurrentPageType() !== PageType.ActiveGame) return false; return this.info?.players_eliminated === "Y"; } /** * Gets the name of the CO for the current player. * If the game has ended, it will return "victory" or "defeat". * If we are in the map editor, it will return "map-editor". * @returns - The name of the CO for the current player. */ static get coName() { if (getCurrentPageType() !== PageType.ActiveGame) return null; const myID2 = getMyID(); const myInfo = getPlayerInfo(myID2); const myWin = myInfo?.players_eliminated === "N"; const myLoss = myInfo?.players_eliminated === "Y"; const endedToday = didGameEndToday(); const isSpectator = isPlayerSpectator(myID2); const endGameTheme = isSpectator || myWin ? "victory" /* Victory */ : "defeat"; /* Defeat */ if (hasGameEnded()) { if (endedToday) return endGameTheme; if (!isReplayActive()) return "co-select" /* COSelect */; return endGameTheme; } if (myLoss) return "defeat" /* Defeat */; return this.info?.co_name; } } function getAllPlayingCONames() { if (getCurrentPageType() === PageType.MapEditor) return /* @__PURE__ */ new Set(["map-editor"]); if (getCurrentPageType() !== PageType.ActiveGame) return /* @__PURE__ */ new Set(); const allPlayers = new Set(getAllPlayersInfo().map((info) => info.co_name)); const allTagPlayers = getAllTagCONames(); return /* @__PURE__ */ new Set([...allPlayers, ...allTagPlayers]); } function isTagGame() { if (getCurrentPageType() !== PageType.ActiveGame) return false; return typeof tagsInfo !== "undefined" && tagsInfo; } function getAllTagCONames() { if (getCurrentPageType() !== PageType.ActiveGame || !isTagGame()) return /* @__PURE__ */ new Set(); if (typeof tagsInfo === "undefined") return /* @__PURE__ */ new Set(); return new Set(Object.values(tagsInfo).map((tag) => tag.co_name)); } function getUnitInfo(unitId) { if (getCurrentPageType() !== PageType.ActiveGame) return null; if (typeof unitsInfo === "undefined") return null; return unitsInfo[unitId]; } function getUnitName(unitId) { if (getCurrentPageType() !== PageType.ActiveGame) return null; return getUnitInfo(unitId)?.units_name; } function getUnitInfoFromCoords(x, y) { if (getCurrentPageType() !== PageType.ActiveGame) return null; if (typeof unitsInfo === "undefined") return null; return Object.values(unitsInfo) .filter((info) => info.units_x == x && info.units_y == y) .pop(); } function isValidUnit(unitId) { if (getCurrentPageType() !== PageType.ActiveGame) return false; if (typeof unitsInfo === "undefined") return false; return unitId !== undefined && unitsInfo[unitId] !== undefined; } function hasUnitMovedThisTurn(unitId) { if (getCurrentPageType() !== PageType.ActiveGame) return false; return isValidUnit(unitId) && getUnitInfo(unitId)?.units_moved === 1; } function addConnectionErrorObserver(onConnectionError) { const connectionErrorDiv = getConnectionErrorDiv(); if (!connectionErrorDiv) return; const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type !== "childList") return; if (!mutation.target) return; if (!mutation.target.textContent) return; const closeMsg = mutation.target.textContent; onConnectionError(closeMsg); } }); observer.observe(connectionErrorDiv, { childList: true }); } var __defProp$1 = Object.defineProperty; var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : (obj[key] = value); var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value); var GameType = /* @__PURE__ */ ((GameType2) => { GameType2["AW1"] = "AW1"; GameType2["AW2"] = "AW2"; GameType2["RBC"] = "RBC"; GameType2["DS"] = "DS"; return GameType2; })(GameType || {}); var ThemeType = /* @__PURE__ */ ((ThemeType2) => { ThemeType2["REGULAR"] = "REGULAR"; ThemeType2["CO_POWER"] = "CO_POWER"; ThemeType2["SUPER_CO_POWER"] = "SUPER_CO_POWER"; return ThemeType2; })(ThemeType || {}); var RandomThemeType = /* @__PURE__ */ ((RandomThemeType2) => { RandomThemeType2["NONE"] = "NONE"; RandomThemeType2["ALL_THEMES"] = "ALL_THEMES"; RandomThemeType2["CURRENT_SOUNDTRACK"] = "CURRENT_SOUNDTRACK"; return RandomThemeType2; })(RandomThemeType || {}); function getCurrentThemeType() { const currentPowerState = currentPlayer?.coPowerState; if (currentPowerState === "Y") return "CO_POWER" /* CO_POWER */; if (currentPowerState === "S") return "SUPER_CO_POWER" /* SUPER_CO_POWER */; return "REGULAR" /* REGULAR */; } function getRandomGameType(excludedGameTypes = /* @__PURE__ */ new Set()) { const gameTypes = Object.values(GameType).filter((gameType) => !excludedGameTypes.has(gameType)); return gameTypes[Math.floor(Math.random() * gameTypes.length)]; } const STORAGE_KEY = "musicPlayerSettings"; const onSettingsChangeListeners = []; function addSettingsChangeListener(fn) { onSettingsChangeListeners.push(fn); } var SettingsKey = /* @__PURE__ */ ((SettingsKey2) => { SettingsKey2["IS_PLAYING"] = "isPlaying"; SettingsKey2["VOLUME"] = "volume"; SettingsKey2["SFX_VOLUME"] = "sfxVolume"; SettingsKey2["UI_VOLUME"] = "uiVolume"; SettingsKey2["GAME_TYPE"] = "gameType"; SettingsKey2["ALTERNATE_THEMES"] = "alternateThemes"; SettingsKey2["ALTERNATE_THEME_DAY"] = "alternateThemeDay"; SettingsKey2["RANDOM_THEMES_TYPE"] = "randomThemesType"; SettingsKey2["CAPTURE_PROGRESS_SFX"] = "captureProgressSFX"; SettingsKey2["PIPE_SEAM_SFX"] = "pipeSeamSFX"; SettingsKey2["OVERRIDE_LIST"] = "overrideList"; SettingsKey2["RESTART_THEMES"] = "restartThemes"; SettingsKey2["AUTOPLAY_ON_OTHER_PAGES"] = "autoplayOnOtherPages"; SettingsKey2["EXCLUDED_RANDOM_THEMES"] = "excludedRandomThemes"; SettingsKey2["LOOP_RANDOM_SONGS_UNTIL_TURN_CHANGE"] = "loopRandomSongsUntilTurnChange"; SettingsKey2["SFX_ON_OTHER_PAGES"] = "sfxOnOtherPages"; SettingsKey2["THEME_TYPE"] = "themeType"; SettingsKey2["CURRENT_RANDOM_CO"] = "currentRandomCO"; SettingsKey2["ALL"] = "all"; SettingsKey2["ADD_OVERRIDE"] = "addOverride"; SettingsKey2["REMOVE_OVERRIDE"] = "removeOverride"; SettingsKey2["ADD_EXCLUDED"] = "addExcluded"; SettingsKey2["REMOVE_EXCLUDED"] = "removeExcluded"; return SettingsKey2; })(SettingsKey || {}); class musicSettings { static toJSON() { return JSON.stringify({ isPlaying: this.__isPlaying, volume: this.__volume, sfxVolume: this.__sfxVolume, uiVolume: this.__uiVolume, gameType: this.__gameType, alternateThemes: this.__alternateThemes, alternateThemeDay: this.__alternateThemeDay, randomThemesType: this.__randomThemesType, captureProgressSFX: this.__captureProgressSFX, pipeSeamSFX: this.__pipeSeamSFX, overrideList: Array.from(this.__overrideList.entries()), restartThemes: this.__restartThemes, autoplayOnOtherPages: this.__autoplayOnOtherPages, excludedRandomThemes: Array.from(this.__excludedRandomThemes), loopRandomSongsUntilTurnChange: this.__loopRandomSongsUntilTurnChange, sfxOnOtherPages: this.__sfxOnOtherPages, }); } static runWithoutSavingSettings(fn) { this.isLoaded = false; this.saveChanges = false; fn(); this.isLoaded = true; this.saveChanges = true; } static fromJSON(json) { const savedSettings = JSON.parse(json); for (let key in this) { key = key.substring(2); if (Object.hasOwn(savedSettings, key)) { if (key === "overrideList") { this.__overrideList = new Map(savedSettings[key]); continue; } if (key === "excludedRandomThemes") { this.__excludedRandomThemes = new Set(savedSettings[key]); continue; } this[key] = savedSettings[key]; } } this.isLoaded = true; broadcastChannel.addEventListener("message", onStorageBroadcast); } static set isPlaying(val) { if (this.__isPlaying === val) return; this.__isPlaying = val; this.onSettingChangeEvent("isPlaying" /* IS_PLAYING */, val); } static get isPlaying() { return this.__isPlaying; } static set volume(val) { if (this.__volume === val) return; this.__volume = val; this.onSettingChangeEvent("volume" /* VOLUME */, val); } static get volume() { return this.__volume; } static set sfxVolume(val) { if (this.__sfxVolume === val) return; this.__sfxVolume = val; this.onSettingChangeEvent("sfxVolume" /* SFX_VOLUME */, val); } static get sfxVolume() { return this.__sfxVolume; } static set uiVolume(val) { if (this.__uiVolume === val) return; this.__uiVolume = val; this.onSettingChangeEvent("uiVolume" /* UI_VOLUME */, val); } static get uiVolume() { return this.__uiVolume; } static set gameType(val) { if (this.__gameType === val) return; this.__gameType = val; this.__currentRandomGameType = val; this.onSettingChangeEvent("gameType" /* GAME_TYPE */, val); } static get gameType() { return this.__gameType; } static set alternateThemes(val) { if (this.__alternateThemes === val) return; this.__alternateThemes = val; this.onSettingChangeEvent("alternateThemes" /* ALTERNATE_THEMES */, val); } static get alternateThemes() { return this.__alternateThemes; } static set alternateThemeDay(val) { if (this.__alternateThemeDay === val) return; this.__alternateThemeDay = val; this.onSettingChangeEvent("alternateThemeDay" /* ALTERNATE_THEME_DAY */, val); } static get alternateThemeDay() { return this.__alternateThemeDay; } static set captureProgressSFX(val) { this.__captureProgressSFX = val; this.onSettingChangeEvent("captureProgressSFX" /* CAPTURE_PROGRESS_SFX */, val); } static get captureProgressSFX() { return this.__captureProgressSFX; } static set pipeSeamSFX(val) { this.__pipeSeamSFX = val; this.onSettingChangeEvent("pipeSeamSFX" /* PIPE_SEAM_SFX */, val); } static get pipeSeamSFX() { return this.__pipeSeamSFX; } static set overrideList(val) { this.__overrideList = new Map([...val.entries()].sort()); this.onSettingChangeEvent("overrideList" /* OVERRIDE_LIST */, val); } static get overrideList() { return this.__overrideList; } static addOverride(coName, gameType) { this.__overrideList.set(coName, gameType); this.__overrideList = new Map([...this.__overrideList.entries()].sort()); this.onSettingChangeEvent("addOverride" /* ADD_OVERRIDE */, [coName, gameType]); } static removeOverride(coName) { if (!this.__overrideList.has(coName)) return; this.__overrideList.delete(coName); this.__overrideList = new Map([...this.__overrideList.entries()].sort()); this.onSettingChangeEvent("removeOverride" /* REMOVE_OVERRIDE */, coName); } static getOverride(coName) { return this.__overrideList.get(coName); } static get restartThemes() { return this.__restartThemes; } static set restartThemes(val) { if (this.__restartThemes === val) return; this.__restartThemes = val; this.onSettingChangeEvent("restartThemes" /* RESTART_THEMES */, val); } static get autoplayOnOtherPages() { return this.__autoplayOnOtherPages; } static set autoplayOnOtherPages(val) { if (this.__autoplayOnOtherPages === val) return; this.__autoplayOnOtherPages = val; this.onSettingChangeEvent("autoplayOnOtherPages" /* AUTOPLAY_ON_OTHER_PAGES */, val); } static get excludedRandomThemes() { return this.__excludedRandomThemes; } static set excludedRandomThemes(val) { this.__excludedRandomThemes = val; this.onSettingChangeEvent("excludedRandomThemes" /* EXCLUDED_RANDOM_THEMES */, val); } static addExcludedRandomTheme(theme) { this.__excludedRandomThemes.add(theme); this.onSettingChangeEvent("addExcluded" /* ADD_EXCLUDED */, theme); } static removeExcludedRandomTheme(theme) { if (!this.__excludedRandomThemes.has(theme)) return; this.__excludedRandomThemes.delete(theme); this.onSettingChangeEvent("removeExcluded" /* REMOVE_EXCLUDED */, theme); } static get loopRandomSongsUntilTurnChange() { return this.__loopRandomSongsUntilTurnChange; } static set loopRandomSongsUntilTurnChange(val) { if (this.__loopRandomSongsUntilTurnChange === val) return; this.__loopRandomSongsUntilTurnChange = val; this.onSettingChangeEvent("loopRandomSongsUntilTurnChange" /* LOOP_RANDOM_SONGS_UNTIL_TURN_CHANGE */, val); } static get sfxOnOtherPages() { return this.__sfxOnOtherPages; } static set sfxOnOtherPages(val) { if (this.__sfxOnOtherPages === val) return; this.__sfxOnOtherPages = val; this.onSettingChangeEvent("sfxOnOtherPages" /* SFX_ON_OTHER_PAGES */, val); } // ************* Non-user configurable settings from here on static set themeType(val) { if (this.__themeType === val) return; this.__themeType = val; this.onSettingChangeEvent("themeType" /* THEME_TYPE */, val); } static get themeType() { return this.__themeType; } static set randomThemesType(val) { if (this.__randomThemesType === val) return; this.__randomThemesType = val; this.onSettingChangeEvent("randomThemesType" /* RANDOM_THEMES_TYPE */, val); } static get randomThemesType() { return this.__randomThemesType; } static get currentRandomCO() { if (!this.__currentRandomCO || this.__currentRandomCO == "") this.randomizeCO(); return this.__currentRandomCO; } static get currentRandomGameType() { return this.__currentRandomGameType; } static randomizeCO() { const excludedCOs = /* @__PURE__ */ new Set([...this.__excludedRandomThemes, this.__currentRandomCO]); this.__currentRandomCO = getRandomCO(excludedCOs); const isPower = this.themeType !== "REGULAR"; /* REGULAR */ const excludedSoundtracks = /* @__PURE__ */ new Set(); if (isPower) excludedSoundtracks.add("AW1" /* AW1 */); this.__currentRandomGameType = getRandomGameType(excludedSoundtracks); this.onSettingChangeEvent("currentRandomCO" /* CURRENT_RANDOM_CO */, null); } static onSettingChangeEvent(key, value) { onSettingsChangeListeners.forEach((fn) => fn(key, value, !this.isLoaded)); } } // User configurable settings __publicField$1(musicSettings, "__isPlaying", false); __publicField$1(musicSettings, "__volume", 0.5); __publicField$1(musicSettings, "__sfxVolume", 0.4); __publicField$1(musicSettings, "__uiVolume", 0.4); __publicField$1(musicSettings, "__gameType", "DS" /* DS */); __publicField$1(musicSettings, "__alternateThemes", true); __publicField$1(musicSettings, "__alternateThemeDay", 15); __publicField$1(musicSettings, "__randomThemesType", "NONE" /* NONE */); __publicField$1(musicSettings, "__captureProgressSFX", true); __publicField$1(musicSettings, "__pipeSeamSFX", true); __publicField$1(musicSettings, "__overrideList", /* @__PURE__ */ new Map()); __publicField$1(musicSettings, "__restartThemes", false); __publicField$1(musicSettings, "__autoplayOnOtherPages", true); __publicField$1(musicSettings, "__excludedRandomThemes", /* @__PURE__ */ new Set()); __publicField$1(musicSettings, "__loopRandomSongsUntilTurnChange", false); __publicField$1(musicSettings, "__sfxOnOtherPages", true); // Non-user configurable settings __publicField$1(musicSettings, "__themeType", "REGULAR" /* REGULAR */); __publicField$1(musicSettings, "__currentRandomCO", ""); __publicField$1(musicSettings, "__currentRandomGameType", "DS" /* DS */); __publicField$1(musicSettings, "isLoaded", false); __publicField$1(musicSettings, "saveChanges", false); function loadSettingsFromLocalStorage() { let storageData = localStorage.getItem(STORAGE_KEY); if (!storageData || storageData === "undefined") { logInfo("No saved settings found, storing defaults"); updateSettingsInLocalStorage(); storageData = localStorage.getItem(STORAGE_KEY); if (!storageData) { logError("Failed to store default settings in local storage"); return; } } musicSettings.fromJSON(storageData); onSettingsChangeListeners.forEach((fn) => fn("all" /* ALL */, null, true)); logDebug("Settings loaded from storage:", storageData); addSettingsChangeListener(onSettingsChange$2); } function allowSettingsToBeSaved() { musicSettings.saveChanges = true; } function onSettingsChange$2(key, value, _isFirstLoad) { if (key === "themeType" /* THEME_TYPE */ || key === "currentRandomCO" /* CURRENT_RANDOM_CO */) return; if (!musicSettings.saveChanges) return; updateSettingsInLocalStorage(); broadcastChannel.postMessage({ type: "settings", key, value }); } const updateSettingsInLocalStorage = debounce(500, __updateSettingsInLocalStorage); function __updateSettingsInLocalStorage() { const jsonSettings = musicSettings.toJSON(); localStorage.setItem(STORAGE_KEY, jsonSettings); logDebug("Saving settings...", jsonSettings); return; } function onStorageBroadcast(event) { if (event.data.type !== "settings") return; const key = event.data.key; const value = event.data.value; logDebug("Received settings change:", key, value); musicSettings.runWithoutSavingSettings(() => { switch (key) { case "volume" /* VOLUME */: musicSettings.volume = value; break; case "sfxVolume" /* SFX_VOLUME */: musicSettings.sfxVolume = value; break; case "uiVolume" /* UI_VOLUME */: musicSettings.uiVolume = value; break; case "themeType" /* THEME_TYPE */: musicSettings.themeType = value; break; case "gameType" /* GAME_TYPE */: musicSettings.gameType = value; break; case "alternateThemes" /* ALTERNATE_THEMES */: musicSettings.alternateThemes = value; break; case "alternateThemeDay" /* ALTERNATE_THEME_DAY */: musicSettings.alternateThemeDay = value; break; case "randomThemesType" /* RANDOM_THEMES_TYPE */: musicSettings.randomThemesType = value; break; case "captureProgressSFX" /* CAPTURE_PROGRESS_SFX */: musicSettings.captureProgressSFX = value; break; case "pipeSeamSFX" /* PIPE_SEAM_SFX */: musicSettings.pipeSeamSFX = value; break; case "restartThemes" /* RESTART_THEMES */: musicSettings.restartThemes = value; break; case "autoplayOnOtherPages" /* AUTOPLAY_ON_OTHER_PAGES */: musicSettings.autoplayOnOtherPages = value; break; case "addOverride" /* ADD_OVERRIDE */: musicSettings.addOverride(value[0], value[1]); break; case "removeOverride" /* REMOVE_OVERRIDE */: musicSettings.removeOverride(value); break; case "addExcluded" /* ADD_EXCLUDED */: musicSettings.addExcludedRandomTheme(value); break; case "removeExcluded" /* REMOVE_EXCLUDED */: musicSettings.removeExcludedRandomTheme(value); break; case "loopRandomSongsUntilTurnChange" /* LOOP_RANDOM_SONGS_UNTIL_TURN_CHANGE */: musicSettings.loopRandomSongsUntilTurnChange = value; break; case "sfxOnOtherPages" /* SFX_ON_OTHER_PAGES */: musicSettings.sfxOnOtherPages = value; break; } }); } const BASE_URL = "https://developerjose.netlify.app"; const BASE_MUSIC_URL = BASE_URL + "/music"; const BASE_SFX_URL = BASE_MUSIC_URL + "/sfx"; const NEUTRAL_IMG_URL = BASE_URL + "/img/music-player-icon.png"; const PLAYING_IMG_URL = BASE_URL + "/img/music-player-playing.gif"; const HASH_JSON_URL = BASE_MUSIC_URL + "/hashes.json"; var SpecialTheme = ((SpecialTheme2) => { SpecialTheme2["Victory"] = BASE_MUSIC_URL + "/t-victory.ogg"; SpecialTheme2["Defeat"] = BASE_MUSIC_URL + "/t-defeat.ogg"; SpecialTheme2["Maintenance"] = BASE_MUSIC_URL + "/t-maintenance.ogg"; SpecialTheme2["COSelect"] = BASE_MUSIC_URL + "/t-co-select.ogg"; SpecialTheme2["ModeSelect"] = BASE_MUSIC_URL + "/t-mode-select.ogg"; return SpecialTheme2; })(SpecialTheme || {}); var GameSFX = /* @__PURE__ */ ((GameSFX2) => { GameSFX2["coGoldRush"] = "co-gold-rush"; GameSFX2["powerActivateAllyCOP"] = "power-activate-ally-cop"; GameSFX2["powerActivateAllySCOP"] = "power-activate-ally-scop"; GameSFX2["powerActivateBHCOP"] = "power-activate-bh-cop"; GameSFX2["powerActivateBHSCOP"] = "power-activate-bh-scop"; GameSFX2["powerActivateAW1COP"] = "power-activate-aw1-cop"; GameSFX2["powerSCOPAvailable"] = "power-scop-available"; GameSFX2["powerCOPAvailable"] = "power-cop-available"; GameSFX2["tagBreakAlly"] = "tag-break-ally"; GameSFX2["tagBreakBH"] = "tag-break-bh"; GameSFX2["tagSwap"] = "tag-swap"; GameSFX2["unitAttackPipeSeam"] = "unit-attack-pipe-seam"; GameSFX2["unitCaptureAlly"] = "unit-capture-ally"; GameSFX2["unitCaptureEnemy"] = "unit-capture-enemy"; GameSFX2["unitCaptureProgress"] = "unit-capture-progress"; GameSFX2["unitMissileHit"] = "unit-missile-hit"; GameSFX2["unitMissileSend"] = "unit-missile-send"; GameSFX2["unitHide"] = "unit-hide"; GameSFX2["unitUnhide"] = "unit-unhide"; GameSFX2["unitSupply"] = "unit-supply"; GameSFX2["unitTrap"] = "unit-trap"; GameSFX2["unitLoad"] = "unit-load"; GameSFX2["unitUnload"] = "unit-unload"; GameSFX2["unitExplode"] = "unit-explode"; GameSFX2["uiCursorMove"] = "ui-cursor-move"; GameSFX2["uiInvalid"] = "ui-invalid"; GameSFX2["uiMenuOpen"] = "ui-menu-open"; GameSFX2["uiMenuClose"] = "ui-menu-close"; GameSFX2["uiMenuMove"] = "ui-menu-move"; GameSFX2["uiUnitSelect"] = "ui-unit-select"; return GameSFX2; })(GameSFX || {}); const onMovementStartMap = /* @__PURE__ */ new Map([ ["APC", "move-tread-light" /* moveTreadLightLoop */], ["Anti-Air", "move-tread-light" /* moveTreadLightLoop */], ["Artillery", "move-tread-light" /* moveTreadLightLoop */], ["B-Copter", "move-bcopter" /* moveBCopterLoop */], ["Battleship", "move-naval" /* moveNavalLoop */], ["Black Boat", "move-naval" /* moveNavalLoop */], ["Black Bomb", "move-plane" /* movePlaneLoop */], ["Bomber", "move-plane" /* movePlaneLoop */], ["Carrier", "move-naval" /* moveNavalLoop */], ["Cruiser", "move-naval" /* moveNavalLoop */], ["Fighter", "move-plane" /* movePlaneLoop */], ["Infantry", "move-inf" /* moveInfLoop */], ["Lander", "move-naval" /* moveNavalLoop */], ["Md.Tank", "move-tread-heavy" /* moveTreadHeavyLoop */], ["Mech", "move-mech" /* moveMechLoop */], ["Mega Tank", "move-tread-heavy" /* moveTreadHeavyLoop */], ["Missile", "move-tires-heavy" /* moveTiresHeavyLoop */], ["Neotank", "move-tread-heavy" /* moveTreadHeavyLoop */], ["Piperunner", "move-piperunner" /* movePiperunnerLoop */], ["Recon", "move-tires-light" /* moveTiresLightLoop */], ["Rocket", "move-tires-heavy" /* moveTiresHeavyLoop */], ["Stealth", "move-plane" /* movePlaneLoop */], ["Sub", "move-sub" /* moveSubLoop */], ["T-Copter", "move-tcopter" /* moveTCopterLoop */], ["Tank", "move-tread-light" /* moveTreadLightLoop */], ]); const onMovementRolloffMap = /* @__PURE__ */ new Map([ ["APC", "move-tread-light-rolloff" /* moveTreadLightOneShot */], ["Anti-Air", "move-tread-light-rolloff" /* moveTreadLightOneShot */], ["Artillery", "move-tread-light-rolloff" /* moveTreadLightOneShot */], ["B-Copter", "move-bcopter-rolloff" /* moveBCopterOneShot */], ["Black Bomb", "move-plane-rolloff" /* movePlaneOneShot */], ["Bomber", "move-plane-rolloff" /* movePlaneOneShot */], ["Fighter", "move-plane-rolloff" /* movePlaneOneShot */], ["Md. Tank", "move-tread-heavy-rolloff" /* moveTreadHeavyOneShot */], ["Mega Tank", "move-tread-heavy-rolloff" /* moveTreadHeavyOneShot */], ["Missile", "move-tires-heavy-rolloff" /* moveTiresHeavyOneShot */], ["Neotank", "move-tread-heavy-rolloff" /* moveTreadHeavyOneShot */], ["Recon", "move-tires-light-rolloff" /* moveTiresLightOneShot */], ["Rocket", "move-tires-heavy-rolloff" /* moveTiresHeavyOneShot */], ["Stealth", "move-plane-rolloff" /* movePlaneOneShot */], ["T-Copter", "move-tcopter-rolloff" /* moveTCopterOneShot */], ["Tank", "move-tread-light-rolloff" /* moveTreadLightOneShot */], ]); const alternateThemes = /* @__PURE__ */ new Map([ [GameType.AW1, /* @__PURE__ */ new Set(["sturm"])], [GameType.AW2, /* @__PURE__ */ new Set(["sturm"])], [GameType.RBC, /* @__PURE__ */ new Set(["andy", "olaf", "eagle", "drake", "grit", "kanbei", "sonja", "sturm"])], [GameType.DS, /* @__PURE__ */ new Set(["sturm", "vonbolt"])], ]); const specialLoops = /* @__PURE__ */ new Set(["vonbolt"]); function getAlternateMusicFilename(coName, gameType, themeType) { if (!alternateThemes.has(gameType)) return; const alternateThemesSet = alternateThemes.get(gameType); const faction = isBlackHoleCO(coName) ? "bh" : "ally"; const isPowerActive = themeType !== ThemeType.REGULAR; if (gameType === GameType.RBC && isPowerActive) { return `t-${faction}-${themeType}`; } if (!alternateThemesSet?.has(coName) || isPowerActive) { return; } if (coName === "andy" && gameType == GameType.RBC) { return isPowerActive ? "t-clone-andy-cop" : "t-clone-andy"; } return `t-${coName}-2`; } function getMusicFilename(coName, gameType, themeType, useAlternateTheme) { if (coName === SpecialCOs.MapEditor) return "t-map-editor"; if (useAlternateTheme) { const alternateFilename = getAlternateMusicFilename(coName, gameType, themeType); if (alternateFilename) return alternateFilename; } const isPowerActive = themeType !== ThemeType.REGULAR; if (!isPowerActive || gameType === GameType.AW1) { return `t-${coName}`; } const isCOInRBC = !AW_DS_ONLY_COs.has(coName); if (gameType === GameType.RBC && isCOInRBC) { return `t-${coName}-cop`; } const faction = isBlackHoleCO(coName) ? "bh" : "ally"; return `t-${faction}-${themeType}`; } function getMusicURL(coName, gameType, themeType, useAlternateTheme) { if (gameType === null || gameType === undefined) gameType = musicSettings.gameType; if (themeType === null || themeType === undefined) themeType = musicSettings.themeType; if (useAlternateTheme === null || useAlternateTheme === undefined) { useAlternateTheme = getCurrentGameDay() >= musicSettings.alternateThemeDay && musicSettings.alternateThemes; } coName = coName.toLowerCase().replaceAll(" ", ""); if (coName === SpecialCOs.Victory) return SpecialTheme.Victory; if (coName === SpecialCOs.Defeat) return SpecialTheme.Defeat; if (coName === SpecialCOs.Maintenance) return SpecialTheme.Maintenance; if (coName === SpecialCOs.COSelect) return SpecialTheme.COSelect; if ( coName === SpecialCOs.ModeSelect || coName === SpecialCOs.MainPage || coName === SpecialCOs.LiveQueue || coName === SpecialCOs.Default ) return SpecialTheme.ModeSelect; const overrideType = musicSettings.getOverride(coName); if (overrideType) gameType = overrideType; const filename = getMusicFilename(coName, gameType, themeType, useAlternateTheme); if (gameType !== GameType.DS && AW_DS_ONLY_COs.has(coName)) gameType = GameType.DS; if (gameType === GameType.AW1 && AW2_ONLY_COs.has(coName)) gameType = GameType.AW2; let gameDir = gameType; if (!gameDir.startsWith("AW")) gameDir = "AW_" + gameDir; const url = `${BASE_MUSIC_URL}/${gameDir}/${filename}.ogg`; return url.toLowerCase().replaceAll("_", "-").replaceAll(" ", ""); } function getCONameFromURL(url) { const parts = url.split("/"); const filename = parts[parts.length - 1]; const coName = filename.split(".")[0].substring(2); return coName; } function getSoundEffectURL(sfx) { return `${BASE_SFX_URL}/${sfx}.ogg`; } function getMovementSoundURL(unitName) { const sfx = onMovementStartMap.get(unitName); if (!sfx) return ""; return `${BASE_SFX_URL}/${onMovementStartMap.get(unitName)}.ogg`; } function getMovementRollOffURL(unitName) { return `${BASE_SFX_URL}/${onMovementRolloffMap.get(unitName)}.ogg`; } function hasMovementRollOff(unitName) { return onMovementRolloffMap.has(unitName); } function hasSpecialLoop(srcURL) { const coName = getCONameFromURL(srcURL); return specialLoops.has(coName); } function getCurrentThemeURLs() { const coNames = getAllPlayingCONames(); const audioList = /* @__PURE__ */ new Set(); coNames.forEach((name) => { const regularURL = getMusicURL(name, musicSettings.gameType, ThemeType.REGULAR, false); const powerURL = getMusicURL(name, musicSettings.gameType, ThemeType.CO_POWER, false); const superPowerURL = getMusicURL(name, musicSettings.gameType, ThemeType.SUPER_CO_POWER, false); const alternateURL = getMusicURL(name, musicSettings.gameType, musicSettings.themeType, true); audioList.add(regularURL); audioList.add(alternateURL); audioList.add(powerURL); audioList.add(superPowerURL); if (specialLoops.has(name)) audioList.add(regularURL.replace(".ogg", "-loop.ogg")); }); return audioList; } var ScriptName = /* @__PURE__ */ ((ScriptName2) => { ScriptName2["None"] = "none"; ScriptName2["MusicPlayer"] = "music_player"; ScriptName2["HighlightCursorCoordinates"] = "highlight_cursor_coordinates"; return ScriptName2; })(ScriptName || {}); const versions = /* @__PURE__ */ new Map([ ["music_player" /* MusicPlayer */, "5.1.0"], ["highlight_cursor_coordinates" /* HighlightCursorCoordinates */, "2.3.0"], ]); const updateURLs = /* @__PURE__ */ new Map([ [ "music_player" /* MusicPlayer */, "https://update.greasyfork.org/scripts/518170/Improved%20AWBW%20Music%20Player.meta.js", ], [ "highlight_cursor_coordinates" /* HighlightCursorCoordinates */, "https://update.greasyfork.org/scripts/520884/AWBW%20Highlight%20Cursor%20Coordinates.meta.js", ], ]); const homepageURLs = /* @__PURE__ */ new Map([ ["music_player" /* MusicPlayer */, "https://greasyfork.org/en/scripts/518170-improved-awbw-music-player"], [ "highlight_cursor_coordinates" /* HighlightCursorCoordinates */, "https://greasyfork.org/en/scripts/520884-awbw-highlight-cursor-coordinates", ], ]); function checkIfUpdateIsAvailable(scriptName) { const isGreater = (a, b) => { return a.localeCompare(b, undefined, { numeric: true }) === 1; }; return new Promise((resolve, reject) => { const updateURL = updateURLs.get(scriptName); if (!updateURL) return reject(`Failed to get the update URL for the script.`); return fetch(updateURL) .then((response) => response.text()) .then((text) => { if (!text) return reject(`Failed to get the HTML from the update URL for the script.`); const latestVersion = text.match(/@version\s+([0-9.]+)/)?.[1]; if (!latestVersion) return reject(`Failed to get the latest version of the script.`); const currentVersion = versions.get(scriptName); if (!currentVersion) return reject(`Failed to get the current version of the script.`); const currentVersionParts = currentVersion.split("."); const latestVersionParts = latestVersion.split("."); const hasThreeParts = currentVersionParts.length === 3 && latestVersionParts.length === 3; if (!hasThreeParts) return reject(`The version number of the script is not in the correct format.`); const isUpdateAvailable = isGreater(latestVersion, currentVersion); logDebug(`Current version: ${currentVersion}, latest: ${latestVersion}, update needed: ${isUpdateAvailable}`); return resolve(isUpdateAvailable); }) .catch((reason) => reject(reason)); }); } var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : (obj[key] = value); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var GroupType = /* @__PURE__ */ ((GroupType2) => { GroupType2["Vertical"] = "cls-vertical-box"; GroupType2["Horizontal"] = "cls-horizontal-box"; return GroupType2; })(GroupType || {}); function sanitize(str) { return str.toLowerCase().replaceAll(" ", "-"); } var NodeID = /* @__PURE__ */ ((NodeID2) => { NodeID2["Parent"] = "parent"; NodeID2["Hover"] = "hover"; NodeID2["Background"] = "background"; NodeID2["Button_Image"] = "button-image"; NodeID2["Settings"] = "settings"; NodeID2["Settings_Left"] = "settings-left"; NodeID2["Settings_Center"] = "settings-center"; NodeID2["Settings_Right"] = "settings-right"; NodeID2["Version"] = "version"; NodeID2["CO_Selector"] = "co-selector"; NodeID2["CO_Portrait"] = "co-portrait"; return NodeID2; })(NodeID || {}); class CustomMenuSettingsUI { /** * Creates a new Custom Menu UI, to add it to AWBW you need to call {@link addToAWBWPage}. * @param prefix - A string used to prefix the IDs of the elements in the menu. * @param buttonImageURL - The URL of the image to be used as the button. * @param hoverText - The text to be displayed when hovering over the button. */ constructor(prefix, buttonImageURL, hoverText = "") { /** * The root element or parent of the custom menu. */ __publicField(this, "parent"); /** * A map that contains the important nodes of the menu. * The keys are the names of the children, and the values are the elements themselves. * Allows for easy access to any element in the menu. */ __publicField(this, "groups", /* @__PURE__ */ new Map()); /** * A map that contains the group types for each group in the menu. * The keys are the names of the groups, and the values are the types of the groups. */ __publicField(this, "groupTypes", /* @__PURE__ */ new Map()); /** * An array of all the input elements in the menu. */ __publicField(this, "inputElements", []); /** * An array of all the button elements in the menu. */ __publicField(this, "buttonElements", []); /** * A boolean that represents whether the settings menu is open or not. */ __publicField(this, "isSettingsMenuOpen", false); /** * A string used to prefix the IDs of the elements in the menu. */ __publicField(this, "prefix"); /** * A boolean that represents whether an update is available for the script. */ __publicField(this, "isUpdateAvailable", false); /** * Text to be displayed when hovering over the main button. */ __publicField(this, "parentHoverText", ""); /** * A map that contains the tables in the menu. * The keys are the names of the tables, and the values are the table elements. */ __publicField(this, "tableMap", /* @__PURE__ */ new Map()); this.prefix = prefix; this.parentHoverText = hoverText; this.parent = document.createElement("div"); this.parent.classList.add("game-tools-btn"); this.parent.style.width = "34px"; this.parent.style.height = "30px"; this.setNodeID(this.parent, "parent" /* Parent */); const hoverSpan = document.createElement("span"); hoverSpan.classList.add("game-tools-btn-text", "small_text"); hoverSpan.innerText = hoverText; this.parent.appendChild(hoverSpan); this.setNodeID(hoverSpan, "hover" /* Hover */); const bgDiv = document.createElement("div"); bgDiv.classList.add("game-tools-bg"); bgDiv.style.backgroundImage = "linear-gradient(to right, #ffffff 0% , #888888 0%)"; this.parent.appendChild(bgDiv); this.setNodeID(bgDiv, "background" /* Background */); bgDiv.addEventListener("mouseover", () => this.setHoverText(this.parentHoverText)); bgDiv.addEventListener("mouseout", () => this.setHoverText("")); const btnLink = document.createElement("a"); btnLink.classList.add("norm2"); bgDiv.appendChild(btnLink); const btnImg = document.createElement("img"); btnImg.src = buttonImageURL; btnLink.appendChild(btnImg); this.setNodeID(btnImg, "button-image" /* Button_Image */); const contextMenu = document.createElement("div"); contextMenu.classList.add("cls-settings-menu"); contextMenu.style.zIndex = "30"; this.parent.appendChild(contextMenu); this.setNodeID(contextMenu, "settings" /* Settings */); const contextMenuBoxesContainer = document.createElement("div"); contextMenuBoxesContainer.classList.add("cls-horizontal-box"); contextMenu.appendChild(contextMenuBoxesContainer); const leftBox = document.createElement("div"); leftBox.classList.add("cls-settings-menu-box"); leftBox.style.display = "none"; contextMenuBoxesContainer.appendChild(leftBox); this.setNodeID(leftBox, "settings-left" /* Settings_Left */); const centerBox = document.createElement("div"); centerBox.classList.add("cls-settings-menu-box"); centerBox.style.display = "none"; contextMenuBoxesContainer.appendChild(centerBox); this.setNodeID(centerBox, "settings-center" /* Settings_Center */); const rightBox = document.createElement("div"); rightBox.classList.add("cls-settings-menu-box"); rightBox.style.display = "none"; contextMenuBoxesContainer.appendChild(rightBox); this.setNodeID(rightBox, "settings-right" /* Settings_Right */); document.addEventListener("contextmenu", (event) => { const element = event.target; if (!element.id.startsWith(this.prefix)) return; event.stopImmediatePropagation(); event.preventDefault(); this.isSettingsMenuOpen = !this.isSettingsMenuOpen; if (this.isSettingsMenuOpen) { this.openContextMenu(); } else { this.closeContextMenu(); } }); document.addEventListener("click", (event) => { let elmnt = event.target; if (!elmnt.id) { while (!elmnt.id) { elmnt = elmnt.parentNode; if (!elmnt) break; } } if (!elmnt) return; if (elmnt.id.startsWith(this.prefix) || elmnt.id === "overDiv") return; this.closeContextMenu(); }); } setNodeID(node, id) { node.id = `${this.prefix}-${id}`; } getNodeByID(id) { const fullID = `${this.prefix}-${id}`; const node = getCurrentDocument().getElementById(fullID) ?? this.parent.querySelector(`#${fullID}`); if (!node) { if (id !== "co-selector" /* CO_Selector */) console.log(`[DeveloperJose] Node with ID ${fullID} not found.`); return null; } const isSettingsSubMenu = id === "settings-left" /* Settings_Left */ || id === "settings-center" /* Settings_Center */ || id === "settings-right"; /* Settings_Right */ const isHidden = node.style.display === "none"; const hasChildren = node.children.length > 0; if (isSettingsSubMenu && isHidden && hasChildren) { node.style.display = "flex"; } return node; } /** * Adds the custom menu to the AWBW page. */ addToAWBWPage(div, prepend = false) { if (!div) { console.error("[DeveloperJose] Parent div is null, cannot add custom menu to the page."); return; } if (!prepend) { div.appendChild(this.parent); this.parent.style.borderLeft = "none"; return; } div.prepend(this.parent); this.parent.style.borderRight = "none"; } hasSettings() { const hasLeftMenu = this.getNodeByID("settings-left" /* Settings_Left */)?.style.display !== "none"; const hasCenterMenu = this.getNodeByID("settings-center" /* Settings_Center */)?.style.display !== "none"; const hasRightMenu = this.getNodeByID("settings-right" /* Settings_Right */)?.style.display !== "none"; return hasLeftMenu || hasCenterMenu || hasRightMenu; } getGroup(groupName) { return this.groups.get(groupName); } /** * Changes the hover text of the main button. * @param text - The text to be displayed when hovering over the button. * @param replaceParent - Whether to replace the current hover text for the main button or not. */ setHoverText(text, replaceParent = false) { const hoverSpan = this.getNodeByID("hover" /* Hover */); if (!hoverSpan) return; if (replaceParent) this.parentHoverText = text; if (this.isUpdateAvailable) text += " (New Update Available!)"; hoverSpan.innerText = text; hoverSpan.style.display = text === "" ? "none" : "block"; } /** * Sets the progress of the UI by coloring the background of the main button. * @param progress - A number between 0 and 100 representing the percentage of the progress bar to fill. */ setProgress(progress) { const bgDiv = this.getNodeByID("background" /* Background */); if (!bgDiv) return; if (progress <= 0 || progress >= 100) { bgDiv.style.backgroundImage = ""; return; } bgDiv.style.backgroundImage = "linear-gradient(to right, #ffffff " + String(progress) + "% , #888888 0%)"; } /** * Sets the image of the main button. * @param imageURL - The URL of the image to be used on the button. */ setImage(imageURL) { const btnImg = this.getNodeByID("button-image" /* Button_Image */); btnImg.src = imageURL; } /** * Adds an event listener to the main button. * @param type - The type of event to listen for. * @param listener - The function to be called when the event is triggered. */ addEventListener(type, listener, options = false) { const div = this.getNodeByID("background" /* Background */); div?.addEventListener(type, listener, options); } /** * Opens the context (right-click) menu. */ openContextMenu() { const contextMenu = this.getNodeByID("settings" /* Settings */); if (!contextMenu) return; const hasVersion = this.getNodeByID("version" /* Version */)?.style.display !== "none"; if (!this.hasSettings() && !hasVersion) return; contextMenu.style.display = "flex"; this.isSettingsMenuOpen = true; } /** * Closes the context (right-click) menu. */ closeContextMenu() { const contextMenu = this.getNodeByID("settings" /* Settings */); if (!contextMenu) return; contextMenu.style.display = "none"; this.isSettingsMenuOpen = false; const overDiv = document.querySelector("#overDiv"); const hasCOSelector = this.getNodeByID("co-selector" /* CO_Selector */) !== null; const isGamePageAndActive = getCurrentPageType() === PageType.ActiveGame; if (overDiv && hasCOSelector && isGamePageAndActive) { overDiv.style.visibility = "hidden"; } } /** * Adds an input slider to the context menu. * @param name - The name of the slider. * @param min - The minimum value of the slider. * @param max - The maximum value of the slider. * @param step - The step value of the slider. * @param hoverText - The text to be displayed when hovering over the slider. * @param position - The position of the slider in the context menu. * @returns - The slider element. */ addSlider(name, min, max, step, hoverText = "", position = "settings-center" /* Settings_Center */) { const submenu = this.getNodeByID(position); if (!submenu) return; const sliderBox = document.createElement("div"); sliderBox.classList.add("cls-vertical-box"); sliderBox.classList.add("cls-group-box"); submenu?.appendChild(sliderBox); const label = document.createElement("label"); sliderBox?.appendChild(label); const slider = document.createElement("input"); slider.id = `${this.prefix}-${sanitize(name)}`; slider.type = "range"; slider.min = String(min); slider.max = String(max); slider.step = String(step); this.inputElements.push(slider); slider.addEventListener("input", (_e) => { let displayValue = slider.value; if (max === 1) displayValue = Math.round(parseFloat(displayValue) * 100) + "%"; label.innerText = `${name}: ${displayValue}`; }); sliderBox?.appendChild(slider); slider.title = hoverText; slider.addEventListener("mouseover", () => this.setHoverText(hoverText)); slider.addEventListener("mouseout", () => this.setHoverText("")); return slider; } addGroup( groupName, type = "cls-horizontal-box" /* Horizontal */, position = "settings-center" /* Settings_Center */, ) { const submenu = this.getNodeByID(position); if (!submenu) return; if (this.groups.has(groupName)) return this.groups.get(groupName); const groupBox = document.createElement("div"); groupBox.classList.add("cls-vertical-box"); groupBox.classList.add("cls-group-box"); submenu?.appendChild(groupBox); const groupLabel = document.createElement("label"); groupLabel.innerText = groupName; groupBox?.appendChild(groupLabel); const group = document.createElement("div"); group.id = `${this.prefix}-${sanitize(groupName)}`; group.classList.add(type); groupBox?.appendChild(group); this.groups.set(groupName, group); this.groupTypes.set(groupName, type); return group; } addRadioButton(name, groupName, hoverText = "") { return this.addInput(name, groupName, hoverText, "radio" /* Radio */); } addCheckbox(name, groupName, hoverText = "") { return this.addInput(name, groupName, hoverText, "checkbox" /* Checkbox */); } addButton(name, groupName, hoverText = "") { return this.addInput(name, groupName, hoverText, "button" /* Button */); } /** * Adds an input to the context menu in a specific group. * @param name - The name of the input. * @param groupName - The name of the group the input belongs to. * @param hoverText - The text to be displayed when hovering over the input. * @param type - The type of input to be added. * @returns - The input element. */ addInput(name, groupName, hoverText = "", type) { const groupDiv = this.getGroup(groupName); const groupType = this.groupTypes.get(groupName); if (!groupDiv || !groupType) return; const inputBox = document.createElement("div"); const otherType = groupType === "cls-horizontal-box" /* Horizontal */ ? "cls-vertical-box" /* Vertical */ : "cls-horizontal-box"; /* Horizontal */ inputBox.classList.add(otherType); groupDiv.appendChild(inputBox); inputBox.title = hoverText; inputBox.addEventListener("mouseover", () => this.setHoverText(hoverText)); inputBox.addEventListener("mouseout", () => this.setHoverText("")); let input; if (type === "button" /* Button */) { input = this.createButton(name, inputBox); } else { input = this.createInput(name, inputBox); } input.type = type; input.name = groupName; return input; } createButton(name, inputBox) { const input = document.createElement("button"); input.innerText = name; inputBox.appendChild(input); this.buttonElements.push(input); return input; } createInput(name, inputBox) { const input = document.createElement("input"); const label = document.createElement("label"); label.appendChild(input); label.appendChild(document.createTextNode(name)); inputBox.appendChild(label); this.inputElements.push(input); return input; } /** * Adds a special version label to the context menu. * @param version - The version to be displayed. */ addVersion() { const version = versions.get(this.prefix); if (!version) return; const contextMenu = this.getNodeByID("settings" /* Settings */); const versionDiv = document.createElement("label"); versionDiv.innerText = `Version: ${version} (DeveloperJose Edition)`; contextMenu?.appendChild(versionDiv); this.setNodeID(versionDiv, "version" /* Version */); } checkIfNewVersionAvailable() { const currentVersion = versions.get(this.prefix); const updateURL = updateURLs.get(this.prefix); const homepageURL = homepageURLs.get(this.prefix) || ""; if (!currentVersion || !updateURL) return; checkIfUpdateIsAvailable(this.prefix) .then((isUpdateAvailable) => { this.isUpdateAvailable = isUpdateAvailable; console.log("[DeveloperJose] Checking if a new version is available...", isUpdateAvailable); if (!isUpdateAvailable) return; const contextMenu = this.getNodeByID("settings" /* Settings */); const versionDiv = document.createElement("a"); versionDiv.id = this.prefix + "-update"; versionDiv.href = homepageURL; versionDiv.target = "_blank"; versionDiv.innerText = `(!) Update Available: Please click here to open the update page in a new tab. (!)`; contextMenu?.append(versionDiv.cloneNode(true)); if (this.hasSettings()) contextMenu?.prepend(versionDiv); }) .catch((error) => console.error(error)); } addTable(name, rows, columns, groupName, hoverText = "") { const groupDiv = this.getGroup(groupName); if (!groupDiv) return; const table = document.createElement("table"); table.classList.add("cls-settings-table"); groupDiv.appendChild(table); table.title = hoverText; table.addEventListener("mouseover", () => this.setHoverText(hoverText)); table.addEventListener("mouseout", () => this.setHoverText("")); const tableData = { table, rows, columns, }; this.tableMap.set(name, tableData); return table; } addItemToTable(name, item) { const tableData = this.tableMap.get(name); if (!tableData) return; const table = tableData.table; if (table.rows.length === 0) table.insertRow(); const maxItemsPerRow = tableData.columns; const currentItemsInRow = table.rows[table.rows.length - 1].cells.length; if (currentItemsInRow >= maxItemsPerRow) table.insertRow(); const currentRow = table.rows[table.rows.length - 1]; const cell = currentRow.insertCell(); cell.appendChild(item); } clearTable(name) { const tableData = this.tableMap.get(name); if (!tableData) return; const table = tableData.table; table.innerHTML = ""; } /** * Calls the input event on all input elements in the menu. * Useful for updating the labels of all the inputs. */ updateAllInputLabels() { const event = new Event("input"); this.inputElements.forEach((input) => { input.dispatchEvent(event); }); } /** * Adds a CO selector to the context menu. Only one CO selector can be added to the menu. * @param groupName - The name of the group the CO selector should be added to. * @param hoverText - The text to be displayed when hovering over the CO selector. * @param onClickFn - The function to be called when a CO is selected from the selector. * @returns - The CO selector element. */ addCOSelector(groupName, hoverText = "", onClickFn) { const groupDiv = this.getGroup(groupName); if (!groupDiv) return; const coSelector = document.createElement("a"); coSelector.classList.add("game-tools-btn"); coSelector.href = "javascript:void(0)"; const imgCaret = this.createCOSelectorCaret(); const imgCO = this.createCOPortraitImage("andy"); coSelector.appendChild(imgCaret); coSelector.appendChild(imgCO); coSelector.title = hoverText; coSelector.addEventListener("mouseover", () => this.setHoverText(hoverText)); coSelector.addEventListener("mouseout", () => this.setHoverText("")); this.setNodeID(coSelector, "co-selector" /* CO_Selector */); this.setNodeID(imgCO, "co-portrait" /* CO_Portrait */); groupDiv?.appendChild(coSelector); const allCOs = getAllCONames(true).sort(); let allColumnsHTML = ""; for (let i = 0; i < 7; i++) { const startIDX = i * 4; const endIDX = startIDX + 4; const templateFn = (coName) => this.createCOSelectorItem(coName); const currentColumnHTML = allCOs.slice(startIDX, endIDX).map(templateFn).join(""); allColumnsHTML += `<td><table>${currentColumnHTML}</table></td>`; } const selectorInnerHTML = `<table><tr>${allColumnsHTML}</tr></table>`; const selectorTitle = `<img src=terrain/ani/blankred.gif height=16 width=1 align=absmiddle>Select CO`; coSelector.onclick = () => { const ret = overlib(selectorInnerHTML, STICKY, CAPTION, selectorTitle, OFFSETY, 25, OFFSETX, -322, CLOSECLICK); const overdiv = document.querySelector("#overDiv"); if (overdiv) overdiv.style.zIndex = "1000"; return ret; }; addCOSelectorListener((coName) => this.onCOSelectorClick(coName)); addCOSelectorListener(onClickFn); return coSelector; } createCOSelectorItem(coName) { const location = "javascript:void(0)"; const internalName = coName.toLowerCase().replaceAll(" ", ""); const coPrefix = getCOImagePrefix(); const imgSrc = `terrain/ani/${coPrefix}${internalName}.png?v=1`; const onClickFn = `awbw_music_player.notifyCOSelectorListeners('${internalName}');`; return `<tr><td class=borderwhite><img class=co_portrait src=${imgSrc}></td><td class=borderwhite align=center valign=center><span class=small_text><a onclick="${onClickFn}" href=${location}>${coName}</a></b></span></td></tr>`; } createCOSelectorCaret() { const imgCaret = document.createElement("img"); imgCaret.classList.add("co_caret"); imgCaret.src = "terrain/co_down_caret.gif"; imgCaret.style.zIndex = "300"; return imgCaret; } createCOPortraitImage(coName) { const imgCO = document.createElement("img"); imgCO.classList.add("co_portrait"); const coPrefix = getCOImagePrefix(); imgCO.src = `terrain/ani/${coPrefix}${coName}.png?v=1`; if (!getAllCONames().includes(coName)) { imgCO.src = `terrain/${coName}`; } return imgCO; } createCOPortraitImageWithText(coName, text) { const div = document.createElement("div"); div.classList.add("cls-vertical-box"); const coImg = this.createCOPortraitImage(coName); div.appendChild(coImg); const coLabel = document.createElement("label"); coLabel.textContent = text; div.appendChild(coLabel); return div; } onCOSelectorClick(coName) { const overDiv = document.querySelector("#overDiv"); overDiv.style.visibility = "hidden"; const imgCO = this.getNodeByID("co-portrait" /* CO_Portrait */); const coPrefix = getCOImagePrefix(); imgCO.src = `terrain/ani/${coPrefix}${coName}.png?v=1`; } } const coSelectorListeners = []; function addCOSelectorListener(listener) { coSelectorListeners.push(listener); } function notifyCOSelectorListeners(coName) { coSelectorListeners.forEach((listener) => listener(coName)); } function getMenu() { const doc = getCurrentDocument(); switch (getCurrentPageType()) { case PageType.Maintenance: return doc.querySelector("#main"); case PageType.MapEditor: return doc.querySelector("#replay-misc-controls"); case PageType.MovePlanner: return doc.querySelector("#map-controls-container"); case PageType.ActiveGame: return doc.querySelector("#game-map-menu")?.parentNode; // case PageType.LiveQueue: // case PageType.MainPage: default: return doc.querySelector("#nav-options"); } } function onMusicBtnClick(_event) { musicSettings.isPlaying = !musicSettings.isPlaying; } function onSettingsChange$1(key, _value, isFirstLoad) { if (isFirstLoad) { if (volumeSlider) volumeSlider.value = musicSettings.volume.toString(); if (sfxVolumeSlider) sfxVolumeSlider.value = musicSettings.sfxVolume.toString(); if (uiVolumeSlider) uiVolumeSlider.value = musicSettings.uiVolume.toString(); if (daySlider) daySlider.value = musicSettings.alternateThemeDay.toString(); const selectedGameTypeRadio = gameTypeRadioMap.get(musicSettings.gameType); if (selectedGameTypeRadio) selectedGameTypeRadio.checked = true; const selectedRandomTypeRadio = randomRadioMap.get(musicSettings.randomThemesType); if (selectedRandomTypeRadio) selectedRandomTypeRadio.checked = true; captProgressBox.checked = musicSettings.captureProgressSFX; pipeSeamBox.checked = musicSettings.pipeSeamSFX; restartThemesBox.checked = musicSettings.restartThemes; autoplayPagesBox.checked = musicSettings.autoplayOnOtherPages; loopToggle.checked = musicSettings.loopRandomSongsUntilTurnChange; uiSFXPagesBox.checked = musicSettings.sfxOnOtherPages; alternateThemesBox.checked = musicSettings.alternateThemes; musicPlayerUI.updateAllInputLabels(); } if (key === SettingsKey.ALL || key === SettingsKey.ADD_OVERRIDE || key === SettingsKey.REMOVE_OVERRIDE) { clearAndRepopulateOverrideList(); if (musicSettings.overrideList.size === 0) { const noOverrides = musicPlayerUI.createCOPortraitImageWithText("followlist.gif", "No overrides set yet..."); musicPlayerUI.addItemToTable("Overrides" /* Override_Table */, noOverrides); } } if (key === SettingsKey.ALL || key === SettingsKey.ADD_EXCLUDED || key === SettingsKey.REMOVE_EXCLUDED) { clearAndRepopulateExcludedList(); if (musicSettings.excludedRandomThemes.size === 0) { const noExcluded = musicPlayerUI.createCOPortraitImageWithText("followlist.gif", "No themes excluded yet..."); musicPlayerUI.addItemToTable("Excluded Random Themes" /* Excluded_Table */, noExcluded); } } const canUpdateDaySlider = daySlider?.parentElement && getCurrentPageType() === PageType.ActiveGame; if (canUpdateDaySlider) daySlider.parentElement.style.display = alternateThemesBox.checked ? "flex" : "none"; if (shuffleBtn) shuffleBtn.disabled = musicSettings.randomThemesType === RandomThemeType.NONE; const currentSounds = getCurrentPageType() === PageType.MovePlanner ? "Sound Effects" : "Tunes"; if (musicSettings.isPlaying) { musicPlayerUI.setHoverText(`Stop ${currentSounds}`, true); musicPlayerUI.setImage(PLAYING_IMG_URL); } else { musicPlayerUI.setHoverText(`Play ${currentSounds}`, true); musicPlayerUI.setImage(NEUTRAL_IMG_URL); } } const parseInputFloat = (event) => parseFloat(event.target.value); const parseInputInt = (event) => parseInt(event.target.value); const musicPlayerUI = new CustomMenuSettingsUI(ScriptName.MusicPlayer, NEUTRAL_IMG_URL, "Play Tunes"); var Description = /* @__PURE__ */ ((Description2) => { Description2["Volume"] = "Adjust the volume of the CO theme music, power activations, and power themes."; Description2["SFX_Volume"] = "Adjust the volume of the unit movement, tag swap, captures, and other unit sounds."; Description2["UI_Volume"] = "Adjust the volume of the UI sound effects like moving your cursor, opening menus, and selecting units."; Description2["AW1"] = "Play the Advance Wars 1 soundtrack. There are no power themes just like the cartridge!"; Description2["AW2"] = "Play the Advance Wars 2 soundtrack. Very classy like Md Tanks."; Description2["DS"] = "Play the Advance Wars: Dual Strike soundtrack. A bit better quality than with the DS speakers though."; Description2["RBC"] = "Play the Advance Wars: Re-Boot Camp soundtrack. Even the new power themes are here now!"; Description2["No_Random"] = "Play the music depending on who the current CO is."; Description2["All_Random"] = "Play random music every turn from all soundtracks."; Description2["Current_Random"] = "Play random music every turn from the current soundtrack."; Description2["Shuffle"] = "Changes the current theme to a new random one."; Description2["SFX_Pages"] = "Play sound effects on other pages like 'Your Games', 'Profile', or during maintenance."; Description2["Capture_Progress"] = "Play a sound effect when a unit makes progress capturing a property."; Description2["Pipe_Seam_SFX"] = "Play a sound effect when a pipe seam is attacked."; Description2["Autoplay_Pages"] = "Autoplay music on other pages like 'Your Games', 'Profile', or during maintenance."; Description2["Restart_Themes"] = "Restart themes at the beginning of each turn (including replays). If disabled, themes will continue from where they left off previously."; Description2["Random_Loop_Toggle"] = "Loop random songs until a turn change happens. If disabled, when a random song ends a new random song will be chosen immediately even if the turn hasn't changed yet."; Description2["Alternate_Themes"] = "Play alternate themes like the Re-Boot Camp factory themes after a certain day. Enable this to be able to select what day alternate themes start."; Description2["Alternate_Day"] = "After what day should alternate themes like the Re-Boot Camp factory themes start playing? Can you find all the hidden themes?"; Description2["Add_Override"] = "Adds an override for a specific CO so it always plays a specific soundtrack or to exclude it when playing random themes."; Description2["Override_Radio"] = "Only play songs from "; Description2["Remove_Override"] = "Removes the override for this specific CO."; Description2["Add_Excluded"] = "Add an override for a specific CO to exclude their themes when playing random themes."; return Description2; })(Description || {}); const LEFT = NodeID.Settings_Left; const volumeSlider = musicPlayerUI.addSlider( "Music Volume" /* Volume */, 0, 1, 5e-3, "Adjust the volume of the CO theme music, power activations, and power themes." /* Volume */, LEFT, ); const sfxVolumeSlider = musicPlayerUI.addSlider( "SFX Volume" /* SFX_Volume */, 0, 1, 5e-3, "Adjust the volume of the unit movement, tag swap, captures, and other unit sounds." /* SFX_Volume */, LEFT, ); const uiVolumeSlider = musicPlayerUI.addSlider( "UI Volume" /* UI_Volume */, 0, 1, 5e-3, "Adjust the volume of the UI sound effects like moving your cursor, opening menus, and selecting units." /* UI_Volume */, LEFT, ); const soundtrackGroupID = "Soundtrack"; musicPlayerUI.addGroup(soundtrackGroupID, GroupType.Horizontal, LEFT); const gameTypeRadioMap = /* @__PURE__ */ new Map(); for (const gameType of Object.values(GameType)) { const description = Description[gameType]; const radio = musicPlayerUI.addRadioButton(gameType, soundtrackGroupID, description); gameTypeRadioMap.set(gameType, radio); } const randomGroupID = "Random Themes"; musicPlayerUI.addGroup(randomGroupID, GroupType.Horizontal, LEFT); const radioNormal = musicPlayerUI.addRadioButton( "Off" /* No_Random */, randomGroupID, "Play the music depending on who the current CO is." /* No_Random */, ); const radioAllRandom = musicPlayerUI.addRadioButton( "All Soundtracks" /* All_Random */, randomGroupID, "Play random music every turn from all soundtracks." /* All_Random */, ); const radioCurrentRandom = musicPlayerUI.addRadioButton( "Current Soundtrack" /* Current_Random */, randomGroupID, "Play random music every turn from the current soundtrack." /* Current_Random */, ); const randomRadioMap = /* @__PURE__ */ new Map([ [RandomThemeType.NONE, radioNormal], [RandomThemeType.ALL_THEMES, radioAllRandom], [RandomThemeType.CURRENT_SOUNDTRACK, radioCurrentRandom], ]); const shuffleBtn = musicPlayerUI.addButton( "Shuffle" /* Shuffle */, randomGroupID, "Changes the current theme to a new random one." /* Shuffle */, ); const sfxGroupID = "Sound Effect (SFX) Options"; musicPlayerUI.addGroup(sfxGroupID, GroupType.Vertical, LEFT); const uiSFXPagesBox = musicPlayerUI.addCheckbox( "Play Sound Effects Outside Of Game Pages" /* SFX_Pages */, sfxGroupID, "Play sound effects on other pages like 'Your Games', 'Profile', or during maintenance." /* SFX_Pages */, ); const captProgressBox = musicPlayerUI.addCheckbox( "Capture Progress SFX" /* Capture_Progress */, sfxGroupID, "Play a sound effect when a unit makes progress capturing a property." /* Capture_Progress */, ); const pipeSeamBox = musicPlayerUI.addCheckbox( "Pipe Seam Attack SFX" /* Pipe_Seam_SFX */, sfxGroupID, "Play a sound effect when a pipe seam is attacked." /* Pipe_Seam_SFX */, ); const musicGroupID = "Music Options"; musicPlayerUI.addGroup(musicGroupID, GroupType.Vertical, LEFT); const autoplayPagesBox = musicPlayerUI.addCheckbox( "Autoplay Music Outside Of Game Pages" /* Autoplay_Pages */, musicGroupID, "Autoplay music on other pages like 'Your Games', 'Profile', or during maintenance." /* Autoplay_Pages */, ); const restartThemesBox = musicPlayerUI.addCheckbox( "Restart Themes Every Turn" /* Restart_Themes */, musicGroupID, "Restart themes at the beginning of each turn (including replays). If disabled, themes will continue from where they left off previously." /* Restart_Themes */, ); const loopToggle = musicPlayerUI.addCheckbox( "Loop Random Songs Until Turn Changes" /* Random_Loop_Toggle */, musicGroupID, "Loop random songs until a turn change happens. If disabled, when a random song ends a new random song will be chosen immediately even if the turn hasn't changed yet." /* Random_Loop_Toggle */, ); const alternateThemesBox = musicPlayerUI.addCheckbox( "Alternate Themes" /* Alternate_Themes */, musicGroupID, "Play alternate themes like the Re-Boot Camp factory themes after a certain day. Enable this to be able to select what day alternate themes start." /* Alternate_Themes */, ); const daySlider = musicPlayerUI.addSlider( "Alternate Themes Start On Day" /* Alternate_Day */, 0, 30, 1, "After what day should alternate themes like the Re-Boot Camp factory themes start playing? Can you find all the hidden themes?" /* Alternate_Day */, LEFT, ); const RIGHT = NodeID.Settings_Right; const addOverrideGroupID = "Override Themes"; musicPlayerUI.addGroup(addOverrideGroupID, GroupType.Horizontal, RIGHT); let currentSelectedCO = "andy"; function onCOSelectorClick(coName) { currentSelectedCO = coName; } musicPlayerUI.addCOSelector( addOverrideGroupID, "Adds an override for a specific CO so it always plays a specific soundtrack or to exclude it when playing random themes." /* Add_Override */, onCOSelectorClick, ); const overrideGameTypeRadioMap = /* @__PURE__ */ new Map(); for (const gameType of Object.values(GameType)) { const radio = musicPlayerUI.addRadioButton( gameType, addOverrideGroupID, "Only play songs from " /* Override_Radio */ + gameType, ); overrideGameTypeRadioMap.set(gameType, radio); radio.checked = true; } const excludeRadio = musicPlayerUI.addRadioButton( "Exclude Random", addOverrideGroupID, "Add an override for a specific CO to exclude their themes when playing random themes." /* Add_Excluded */, ); const overrideBtn = musicPlayerUI.addButton( "Add" /* Add_Override */, addOverrideGroupID, "Adds an override for a specific CO so it always plays a specific soundtrack or to exclude it when playing random themes." /* Add_Override */, ); const overrideListGroupID = "Current Overrides (Click to Remove)"; musicPlayerUI.addGroup(overrideListGroupID, GroupType.Horizontal, RIGHT); const overrideDivMap = /* @__PURE__ */ new Map(); const tableRows = 4; const tableCols = 7; musicPlayerUI.addTable( "Overrides" /* Override_Table */, tableRows, tableCols, overrideListGroupID, "Removes the override for this specific CO." /* Remove_Override */, ); function addOverrideDisplayDiv(coName, gameType) { const displayDiv = musicPlayerUI.createCOPortraitImageWithText(coName, gameType); displayDiv.addEventListener("click", (_event) => { musicSettings.removeOverride(coName); }); overrideDivMap.set(coName, displayDiv); musicPlayerUI.addItemToTable("Overrides" /* Override_Table */, displayDiv); return displayDiv; } function clearAndRepopulateOverrideList() { overrideDivMap.forEach((div) => div.remove()); overrideDivMap.clear(); musicPlayerUI.clearTable("Overrides" /* Override_Table */); for (const [coName, gameType] of musicSettings.overrideList) { addOverrideDisplayDiv(coName, gameType); } } const excludedListGroupID = "Themes Excluded From Randomizer (Click to Remove)"; musicPlayerUI.addGroup(excludedListGroupID, GroupType.Horizontal, RIGHT); const excludedListDivMap = /* @__PURE__ */ new Map(); musicPlayerUI.addTable( "Excluded Random Themes" /* Excluded_Table */, tableRows, tableCols, excludedListGroupID, "Removes the override for this specific CO." /* Remove_Override */, ); function addExcludedDisplayDiv(coName) { const displayDiv = musicPlayerUI.createCOPortraitImageWithText(coName, ""); displayDiv.addEventListener("click", (_event) => { musicSettings.removeExcludedRandomTheme(coName); }); excludedListDivMap.set(coName, displayDiv); musicPlayerUI.addItemToTable("Excluded Random Themes" /* Excluded_Table */, displayDiv); return displayDiv; } function clearAndRepopulateExcludedList() { excludedListDivMap.forEach((div) => div.remove()); excludedListDivMap.clear(); musicPlayerUI.clearTable("Excluded Random Themes" /* Excluded_Table */); for (const coName of musicSettings.excludedRandomThemes) addExcludedDisplayDiv(coName); } musicPlayerUI.addVersion(); function initializeMusicPlayerUI() { musicPlayerUI.setProgress(100); let prepend = false; switch (getCurrentPageType()) { // case PageType.LiveQueue: // return; case PageType.ActiveGame: break; case PageType.MapEditor: musicPlayerUI.parent.style.borderTop = "none"; break; case PageType.Maintenance: musicPlayerUI.parent.style.borderLeft = ""; break; default: musicPlayerUI.parent.style.border = "none"; musicPlayerUI.parent.style.backgroundColor = "#0000"; musicPlayerUI.setProgress(-1); prepend = true; break; } musicPlayerUI.addToAWBWPage(getMenu(), prepend); addMusicUIListeners(); } function addMusicUIListeners() { musicPlayerUI.addEventListener("click", onMusicBtnClick); addSettingsChangeListener(onSettingsChange$1); volumeSlider?.addEventListener("input", (event) => (musicSettings.volume = parseInputFloat(event))); sfxVolumeSlider?.addEventListener("input", (event) => (musicSettings.sfxVolume = parseInputFloat(event))); uiVolumeSlider?.addEventListener("input", (event) => (musicSettings.uiVolume = parseInputFloat(event))); radioNormal.addEventListener("click", (_e) => (musicSettings.randomThemesType = RandomThemeType.NONE)); radioAllRandom.addEventListener("click", (_e) => (musicSettings.randomThemesType = RandomThemeType.ALL_THEMES)); radioCurrentRandom.addEventListener( "click", (_e) => (musicSettings.randomThemesType = RandomThemeType.CURRENT_SOUNDTRACK), ); for (const gameType of Object.values(GameType)) { const radio = gameTypeRadioMap.get(gameType); radio?.addEventListener("click", (_e) => (musicSettings.gameType = gameType)); } shuffleBtn.addEventListener("click", (_e) => musicSettings.randomizeCO()); captProgressBox.addEventListener("click", (_e) => (musicSettings.captureProgressSFX = captProgressBox.checked)); pipeSeamBox.addEventListener("click", (_e) => (musicSettings.pipeSeamSFX = pipeSeamBox.checked)); restartThemesBox.addEventListener("click", (_e) => (musicSettings.restartThemes = restartThemesBox.checked)); autoplayPagesBox.addEventListener("click", (_e) => (musicSettings.autoplayOnOtherPages = autoplayPagesBox.checked)); loopToggle.addEventListener("click", (_e) => (musicSettings.loopRandomSongsUntilTurnChange = loopToggle.checked)); uiSFXPagesBox.addEventListener("click", (_e) => (musicSettings.sfxOnOtherPages = uiSFXPagesBox.checked)); alternateThemesBox.addEventListener("click", (_e) => (musicSettings.alternateThemes = alternateThemesBox.checked)); daySlider?.addEventListener("input", (event) => (musicSettings.alternateThemeDay = parseInputInt(event))); overrideBtn.addEventListener("click", (_e) => { if (excludeRadio.checked) { musicSettings.addExcludedRandomTheme(currentSelectedCO); return; } let currentGameType; for (const [gameType, radio] of overrideGameTypeRadioMap) { if (radio.checked) currentGameType = gameType; } if (!currentGameType) return; musicSettings.addOverride(currentSelectedCO, currentGameType); }); } addMusicUIListeners(); function getQueryTurnFn() { return typeof queryTurn !== "undefined" ? queryTurn : null; } function getShowEventScreenFn() { return typeof showEventScreen !== "undefined" ? showEventScreen : null; } function getShowEndGameScreenFn() { return typeof showEndGameScreen !== "undefined" ? showEndGameScreen : null; } function getOpenMenuFn() { return typeof openMenu !== "undefined" ? openMenu : null; } function getCloseMenuFn() { return typeof closeMenu !== "undefined" ? closeMenu : null; } function getCreateDamageSquaresFn() { return typeof createDamageSquares !== "undefined" ? createDamageSquares : null; } function getUnitClickFn() { return typeof unitClickHandler !== "undefined" ? unitClickHandler : null; } function getWaitFn() { return typeof waitUnit !== "undefined" ? waitUnit : null; } function getAnimUnitFn() { return typeof animUnit !== "undefined" ? animUnit : null; } function getAnimExplosionFn() { return typeof animExplosion !== "undefined" ? animExplosion : null; } function getFogFn() { return typeof updateAirUnitFogOnMove !== "undefined" ? updateAirUnitFogOnMove : null; } function getFireFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Fire : null; } function getAttackSeamFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.AttackSeam : null; } function getMoveFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Move : null; } function getCaptFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Capt : null; } function getBuildFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Build : null; } function getLoadFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Load : null; } function getUnloadFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Unload : null; } function getSupplyFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Supply : null; } function getRepairFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Repair : null; } function getHideFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Hide : null; } function getUnhideFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Unhide : null; } function getJoinFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Join : null; } function getLaunchFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Launch : null; } function getNextTurnFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.NextTurn : null; } function getEliminationFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Elimination : null; } function getPowerFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Power : null; } function getGameOverFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.GameOver : null; } function getResignFn() { return typeof actionHandlers !== "undefined" ? actionHandlers.Resign : null; } let db = null; const dbName = "awbw_music_player"; const dbVersion = 1; const urlQueue$1 = /* @__PURE__ */ new Set(); const replacementListeners = /* @__PURE__ */ new Set(); function addDatabaseReplacementListener(fn) { replacementListeners.add(fn); } function openDB() { const request = indexedDB.open(dbName, dbVersion); return new Promise((resolve, reject) => { request.onerror = (event) => reject(event); request.onupgradeneeded = (event) => { if (!event.target) return reject("No target for database upgrade."); const newDB = event.target.result; newDB.createObjectStore("music"); }; request.onsuccess = (event) => { if (!event.target) return reject("No target for database success."); db = event.target.result; db.onerror = (event2) => { reject(`Error accessing database: ${event2}`); }; resolve(); }; }); } function loadMusicFromDB(srcURL) { if (!srcURL || srcURL === "") return Promise.reject("Invalid URL."); if (urlQueue$1.has(srcURL)) return Promise.reject("URL is already queued for storage."); urlQueue$1.add(srcURL); return new Promise((resolve, reject) => { if (!db) return reject("Database is not open."); const transaction = db.transaction("music", "readonly"); const store = transaction.objectStore("music"); const request = store.get(srcURL); request.onsuccess = (event) => { urlQueue$1.delete(srcURL); const blob = event.target.result; if (!blob) { return storeURLInDB(srcURL) .then((blob2) => resolve(URL.createObjectURL(blob2))) .catch((reason) => reject(reason)); } const url = URL.createObjectURL(blob); resolve(url); }; request.onerror = (event) => { urlQueue$1.delete(srcURL); reject(event); }; }); } function storeBlobInDB(url, blob) { return new Promise((resolve, reject) => { if (!db) return reject("Database not open."); if (!url || url === "") return reject("Invalid URL."); const transaction = db.transaction("music", "readwrite"); const store = transaction.objectStore("music"); const request = store.put(blob, url); request.onsuccess = () => { resolve(blob); replacementListeners.forEach((fn) => fn(url)); }; request.onerror = (event) => reject(event); }); } function storeURLInDB(url) { if (!db) return Promise.reject("Database not open."); if (!url || url === "") return Promise.reject("Invalid URL."); return fetch(url) .then((response) => response.blob()) .then((blob) => storeBlobInDB(url, blob)); } function checkHashesInDB() { if (!db) return Promise.reject("Database not open."); return fetch(HASH_JSON_URL) .then((response) => response.json()) .then((hashes) => compareHashesAndReplaceIfNeeded(hashes)); } function getBlobMD5(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (event) => { if (!event?.target?.result) return reject("FileReader did not load the blob."); const md5 = SparkMD5.ArrayBuffer.hash(event.target.result); resolve(md5); }; reader.onerror = (event) => reject(event); reader.readAsArrayBuffer(blob); }); } function compareHashesAndReplaceIfNeeded(hashesJson) { return new Promise((resolve, reject) => { if (!db) return reject("Database not open."); if (!hashesJson) return reject("No hashes found in server."); const transaction = db.transaction("music", "readonly"); const store = transaction.objectStore("music"); const request = store.openCursor(); request.onerror = (event) => reject(event); request.onsuccess = (event) => { const cursor = event.target.result; if (!cursor) return resolve(); const url = cursor.key; const blob = cursor.value; const serverHash = hashesJson[url]; cursor.continue(); if (!serverHash) { logDebug("No hash found in server for", url); return; } getBlobMD5(blob) .then((hash) => { if (hash === serverHash) return; return storeURLInDB(url); }) .catch((reason) => logError(`Error storing new version of ${url} in database: ${reason}`)); }; }); } const audioMap = /* @__PURE__ */ new Map(); const audioIDMap = /* @__PURE__ */ new Map(); function playOneShotURL(srcURL, volume) { if (!musicSettings.isPlaying) return; const soundInstance = new Audio(srcURL); soundInstance.currentTime = 0; soundInstance.volume = volume; soundInstance.play(); } function getVolumeForURL(url) { if (url.startsWith("blob:") || !url.startsWith("https://")) { logError("Blob URL when trying to get volume for", url); return musicSettings.volume; } if (url.includes("sfx")) { if (url.includes("ui")) return musicSettings.uiVolume; if (url.includes("power") && !url.includes("available")) return musicSettings.volume; return musicSettings.sfxVolume; } return musicSettings.volume; } const urlQueue = /* @__PURE__ */ new Set(); const promiseMap = /* @__PURE__ */ new Map(); function createNewAudio(srcURL, cacheURL) { const audioInMap = audioMap.get(srcURL); if (audioInMap) { logError("Race Condition! Please report this bug!", srcURL); return audioInMap; } const audio = new Howl({ src: [cacheURL], format: ["ogg"], volume: getVolumeForURL(srcURL), // Redundant event listeners to ensure the audio is always at the correct volume onplay: (_id) => audio.volume(getVolumeForURL(srcURL)), onload: (_id) => audio.volume(getVolumeForURL(srcURL)), onseek: (_id) => audio.volume(getVolumeForURL(srcURL)), onpause: (_id) => audio.volume(getVolumeForURL(srcURL)), onloaderror: (_id, error) => logError("Error loading audio:", srcURL, error), onplayerror: (_id, error) => logError("Error playing audio:", srcURL, error), }); audioMap.set(srcURL, audio); return audio; } function preloadAllCommonAudio(afterPreloadFunction) { const audioList = getCurrentThemeURLs(); audioList.add(getSoundEffectURL(GameSFX.uiCursorMove)); audioList.add(getSoundEffectURL(GameSFX.uiUnitSelect)); logDebug("Pre-loading common audio", audioList); preloadAudioList(audioList, afterPreloadFunction); } function preloadAudioList(audioURLs, afterPreloadFunction = () => {}) { let numLoadedAudios = 0; const onAudioPreload = (action, url) => { numLoadedAudios++; const loadPercentage = (numLoadedAudios / audioURLs.size) * 100; musicPlayerUI.setProgress(loadPercentage); if (numLoadedAudios >= audioURLs.size) { numLoadedAudios = 0; if (afterPreloadFunction) afterPreloadFunction(); } if (action === "error") { logInfo(`Could not pre-load: ${url}. This might not be a problem, the audio may still play normally later.`); audioMap.delete(url); return; } if (!audioMap.has(url)) { logError("Race condition on pre-load! Please report this bug!", url); } }; audioURLs.forEach((url) => { if (audioMap.has(url)) { numLoadedAudios++; return; } preloadURL(url) .then((audio) => { audio.once("load", () => onAudioPreload("load", url)); audio.once("loaderror", () => onAudioPreload("error", url)); }) .catch((_reason) => onAudioPreload("error", url)); }); if (numLoadedAudios >= audioURLs.size) { if (afterPreloadFunction) afterPreloadFunction(); } } async function preloadURL(srcURL) { const audio = audioMap.get(srcURL); if (audio) return audio; if (urlQueue.has(srcURL)) { const storedPromise = promiseMap.get(srcURL); if (!storedPromise) return Promise.reject(`No promise found for ${srcURL}, please report this bug!`); return storedPromise; } urlQueue.add(srcURL); const promise = loadMusicFromDB(srcURL).then( (localCacheURL) => createNewAudio(srcURL, localCacheURL), (reason) => { logDebug(reason, srcURL); return createNewAudio(srcURL, srcURL); }, ); promiseMap.set(srcURL, promise); return promise; } const unitIDAudioMap = /* @__PURE__ */ new Map(); function playMovementSound(unitId) { if (!musicSettings.isPlaying) return; if (!unitIDAudioMap.has(unitId)) { const unitName = getUnitName(unitId); if (!unitName) return; const movementSoundURL = getMovementSoundURL(unitName); if (!movementSoundURL) { logError("No movement sound for", unitName); return; } unitIDAudioMap.set(unitId, new Audio(movementSoundURL)); } const movementAudio = unitIDAudioMap.get(unitId); if (!movementAudio) return; movementAudio.currentTime = 0; movementAudio.loop = false; movementAudio.volume = musicSettings.sfxVolume; movementAudio.play(); } function stopMovementSound(unitId, rolloff = true) { if (!musicSettings.isPlaying) return; if (!unitIDAudioMap.has(unitId)) return; const movementAudio = unitIDAudioMap.get(unitId); if (!movementAudio || movementAudio.paused) return; if (movementAudio.readyState != HTMLAudioElement.prototype.HAVE_ENOUGH_DATA) { movementAudio.addEventListener("canplaythrough", whenAudioLoadsPauseIt, { once: true }); return; } movementAudio.pause(); movementAudio.currentTime = 0; const unitName = getUnitName(unitId); if (!rolloff || !unitName) return; if (hasMovementRollOff(unitName)) { const audioURL = getMovementRollOffURL(unitName); playOneShotURL(audioURL, musicSettings.sfxVolume); } } function stopAllMovementSounds() { for (const unitId of unitIDAudioMap.keys()) { stopMovementSound(unitId, false); } } function whenAudioLoadsPauseIt(event) { event.target.pause(); } let currentThemeURL = ""; let currentLoops = 0; const specialLoopMap = /* @__PURE__ */ new Map(); let currentlyDelaying = false; async function playMusicURL(srcURL) { const specialLoopURL = specialLoopMap.get(srcURL); if (specialLoopURL) srcURL = specialLoopURL; if (srcURL !== currentThemeURL) { stopThemeSong(); currentThemeURL = srcURL; } const nextSong = audioMap.get(srcURL) ?? (await preloadURL(srcURL)); nextSong.loop(!hasSpecialLoop(srcURL)); nextSong.volume(getVolumeForURL(srcURL)); nextSong.on("play", () => onThemePlay(nextSong, srcURL)); nextSong.on("load", () => playThemeSong()); nextSong.on("end", () => onThemeEndOrLoop(srcURL)); if (!nextSong.playing() && musicSettings.isPlaying) { logInfo("Now Playing: ", srcURL, " | Cached? =", nextSong._src !== srcURL); const newID = nextSong.play(); if (!newID) return; audioIDMap.set(srcURL, newID); } } function playThemeSong() { if (!musicSettings.isPlaying) return; if (currentlyDelaying) return; let gameType = undefined; let coName = currentPlayer.coName; if (getCurrentPageType() === PageType.Maintenance) coName = SpecialCOs.Maintenance; else if (getCurrentPageType() === PageType.MapEditor) coName = SpecialCOs.MapEditor; else if (getCurrentPageType() === PageType.MainPage) coName = SpecialCOs.MainPage; else if (getCurrentPageType() === PageType.LiveQueue) coName = SpecialCOs.LiveQueue; else if (getCurrentPageType() === PageType.Default) coName = SpecialCOs.Default; const isEndTheme = coName === SpecialCOs.Victory || coName === SpecialCOs.Defeat; const isRandomTheme = musicSettings.randomThemesType !== RandomThemeType.NONE; if (isRandomTheme && !isEndTheme) { coName = musicSettings.currentRandomCO; if (musicSettings.randomThemesType === RandomThemeType.ALL_THEMES) gameType = musicSettings.currentRandomGameType; } if (!coName) { if (!currentThemeURL || currentThemeURL === "") return; playMusicURL(currentThemeURL); return; } playMusicURL(getMusicURL(coName, gameType)); } function stopThemeSong(delayMS = 0) { if (delayMS > 0) { window.setTimeout(() => { currentlyDelaying = false; playThemeSong(); }, delayMS); currentlyDelaying = true; } const currentTheme = audioMap.get(currentThemeURL); if (!currentTheme) return; logDebug("Pausing: ", currentThemeURL); currentTheme.pause(); } function stopAllSounds() { stopThemeSong(); stopAllMovementSounds(); for (const audio of audioMap.values()) { if (audio.playing()) audio.pause(); } } function onThemePlay(audio, srcURL) { currentLoops = 0; audio.volume(getVolumeForURL(srcURL)); broadcastChannel.postMessage("pause"); const isPowerTheme = musicSettings.themeType !== ThemeType.REGULAR; const isRandomTheme = musicSettings.randomThemesType !== RandomThemeType.NONE; const shouldRestart = musicSettings.restartThemes || isPowerTheme || isRandomTheme; const currentPosition = audio.seek(); const isGamePageActive = getCurrentPageType() === PageType.ActiveGame; if (shouldRestart && isGamePageActive && currentPosition > 0.1) { audio.seek(0); } if (currentThemeURL !== srcURL && audio.playing()) { audio.pause(); playThemeSong(); } const audioID = audioIDMap.get(srcURL); if (!audioID) return; for (const id of audio._getSoundIds()) { if (id !== audioID) audio.stop(id); } } function onThemeEndOrLoop(srcURL) { currentLoops++; if (currentThemeURL !== srcURL) { logError("Playing more than one theme at a time! Please report this bug!", srcURL); return; } if (hasSpecialLoop(srcURL)) { const loopURL = srcURL.replace(".ogg", "-loop.ogg"); specialLoopMap.set(srcURL, loopURL); playThemeSong(); } if (srcURL === SpecialTheme.Victory || srcURL === SpecialTheme.Defeat) { if (currentLoops >= 3) playMusicURL(SpecialTheme.COSelect); } if (musicSettings.randomThemesType !== RandomThemeType.NONE && !musicSettings.loopRandomSongsUntilTurnChange) { musicSettings.randomizeCO(); playThemeSong(); } } function onSettingsChange(key, _value, isFirstLoad) { if (isFirstLoad) return; switch (key) { case SettingsKey.ADD_OVERRIDE: case SettingsKey.REMOVE_OVERRIDE: case SettingsKey.OVERRIDE_LIST: case SettingsKey.CURRENT_RANDOM_CO: case SettingsKey.IS_PLAYING: return musicSettings.isPlaying ? playThemeSong() : stopAllSounds(); case SettingsKey.GAME_TYPE: case SettingsKey.ALTERNATE_THEME_DAY: case SettingsKey.ALTERNATE_THEMES: return window.setTimeout(() => playThemeSong(), 500); case SettingsKey.THEME_TYPE: return playThemeSong(); case SettingsKey.REMOVE_EXCLUDED: if (musicSettings.excludedRandomThemes.size === 27) musicSettings.randomizeCO(); return playThemeSong(); case SettingsKey.EXCLUDED_RANDOM_THEMES: case SettingsKey.ADD_EXCLUDED: if (musicSettings.excludedRandomThemes.has(musicSettings.currentRandomCO)) musicSettings.randomizeCO(); return playThemeSong(); case SettingsKey.RANDOM_THEMES_TYPE: { const randomThemes = musicSettings.randomThemesType !== RandomThemeType.NONE; if (!randomThemes) return playThemeSong(); musicSettings.randomizeCO(); playThemeSong(); return; } case SettingsKey.VOLUME: { const currentTheme = audioMap.get(currentThemeURL); if (currentTheme) currentTheme.volume(musicSettings.volume); if (!currentTheme) { const intervalID = window.setInterval(() => { const currentTheme2 = audioMap.get(currentThemeURL); if (currentTheme2) { currentTheme2.volume(musicSettings.volume); window.clearInterval(intervalID); } }); } for (const srcURL of audioMap.keys()) { const audio = audioMap.get(srcURL); if (audio) audio.volume(getVolumeForURL(srcURL)); } return; } } } const restartTheme = debounce(300, __restartTheme, true); function __restartTheme() { const currentTheme = audioMap.get(currentThemeURL); if (!currentTheme) return; currentTheme.seek(0); } function clearThemeDelay() { currentlyDelaying = false; playThemeSong(); } function addThemeListeners() { addSettingsChangeListener(onSettingsChange); addDatabaseReplacementListener((url) => { const audio = audioMap.get(url); if (!audio) return; logInfo("A new version of", url, " is available. Replacing the old version."); if (audio.playing()) audio.stop(); urlQueue.delete(url); promiseMap.delete(url); audioMap.delete(url); audioIDMap.delete(url); preloadURL(url) .catch((reason) => logError(reason)) .finally(() => playThemeSong()); }); } addThemeListeners(); async function playSFX(sfx) { if (!musicSettings.isPlaying) return; if (!musicSettings.captureProgressSFX && sfx === GameSFX.unitCaptureProgress) return; if (!musicSettings.pipeSeamSFX && sfx === GameSFX.unitAttackPipeSeam) return; const sfxURL = getSoundEffectURL(sfx); const audio = audioMap.get(sfxURL) ?? (await preloadURL(sfxURL)); audio.volume(getVolumeForURL(sfxURL)); audio.seek(0); if (audio.playing()) return; const newID = audio.play(); if (!newID) return; audioIDMap.set(sfxURL, newID); } function stopSFX(sfx) { if (!musicSettings.isPlaying) return; const sfxURL = getSoundEffectURL(sfx); const audio = audioMap.get(sfxURL); if (!audio || !audio.playing()) return; audio.stop(); } const CURSOR_THRESHOLD_MS = 25; let lastCursorCall$1 = Date.now(); let lastCursorX = -1; let lastCursorY = -1; let currentMenuType = "None"; /* None */ const visibilityMap = /* @__PURE__ */ new Map(); const movementResponseMap = /* @__PURE__ */ new Map(); const clickedDamageSquaresMap = /* @__PURE__ */ new Map(); const ahQueryTurn = getQueryTurnFn(); const ahShowEventScreen = getShowEventScreenFn(); const ahShowEndGameScreen = getShowEndGameScreenFn(); const ahOpenMenu = getOpenMenuFn(); const ahCloseMenu = getCloseMenuFn(); const ahCreateDamageSquares = getCreateDamageSquaresFn(); const ahUnitClick = getUnitClickFn(); const ahWait = getWaitFn(); const ahAnimUnit = getAnimUnitFn(); const ahAnimExplosion = getAnimExplosionFn(); const ahFog = getFogFn(); const ahFire = getFireFn(); const ahAttackSeam = getAttackSeamFn(); const ahMove = getMoveFn(); const ahCapt = getCaptFn(); const ahBuild = getBuildFn(); const ahLoad = getLoadFn(); const ahUnload = getUnloadFn(); const ahSupply = getSupplyFn(); const ahRepair = getRepairFn(); const ahHide = getHideFn(); const ahUnhide = getUnhideFn(); const ahJoin = getJoinFn(); const ahLaunch = getLaunchFn(); const ahNextTurn = getNextTurnFn(); const ahElimination = getEliminationFn(); const ahPower = getPowerFn(); const ahGameOver = getGameOverFn(); const ahResign = getResignFn(); function addHandlers() { const currentPageType = getCurrentPageType(); if (currentPageType === PageType.Maintenance) return; addUpdateCursorObserver(onCursorMove); switch (currentPageType) { case PageType.ActiveGame: addReplayHandlers(); addGameHandlers(); return; case PageType.MapEditor: return; case PageType.MovePlanner: return; } } function syncMusic() { musicSettings.themeType = getCurrentThemeType(); playThemeSong(); window.setTimeout(() => { musicSettings.themeType = getCurrentThemeType(); playThemeSong(); }, 500); } function refreshMusicForNextTurn(playDelayMS = 0) { visibilityMap.clear(); musicSettings.randomizeCO(); musicSettings.themeType = getCurrentThemeType(); window.setTimeout(() => { musicSettings.themeType = getCurrentThemeType(); if (musicSettings.restartThemes) restartTheme(); playThemeSong(); window.setTimeout(playThemeSong, 350); }, playDelayMS); } function addReplayHandlers() { queryTurn = onQueryTurn; const replayForwardActionBtn = getReplayForwardActionBtn(); const replayBackwardActionBtn = getReplayBackwardActionBtn(); const replayForwardBtn = getReplayForwardBtn(); const replayBackwardBtn = getReplayBackwardBtn(); const replayOpenBtn = getReplayOpenBtn(); const replayCloseBtn = getReplayCloseBtn(); const replayDaySelectorCheckBox = getReplayDaySelectorCheckBox(); replayBackwardActionBtn.addEventListener("click", syncMusic); replayForwardActionBtn.addEventListener("click", syncMusic); replayForwardBtn.addEventListener("click", syncMusic); replayBackwardBtn.addEventListener("click", syncMusic); replayDaySelectorCheckBox.addEventListener("change", syncMusic); replayCloseBtn.addEventListener("click", syncMusic); replayBackwardActionBtn.addEventListener("click", stopAllMovementSounds); replayOpenBtn.addEventListener("click", stopAllMovementSounds); replayCloseBtn.addEventListener("click", stopAllMovementSounds); replayForwardBtn.addEventListener("click", clearThemeDelay); replayBackwardActionBtn.addEventListener("click", clearThemeDelay); replayBackwardBtn.addEventListener("click", clearThemeDelay); const stopExtraSFX = () => { stopSFX(GameSFX.powerActivateAW1COP); stopSFX(GameSFX.powerActivateAllyCOP); stopSFX(GameSFX.powerActivateAllySCOP); stopSFX(GameSFX.powerActivateBHCOP); stopSFX(GameSFX.powerActivateBHSCOP); }; replayBackwardActionBtn.addEventListener("click", stopExtraSFX); replayForwardBtn.addEventListener("click", stopExtraSFX); replayBackwardBtn.addEventListener("click", stopExtraSFX); replayCloseBtn.addEventListener("click", stopExtraSFX); replayCloseBtn.addEventListener("click", () => refreshMusicForNextTurn(500)); } function addGameHandlers() { showEventScreen = onShowEventScreen; showEndGameScreen = onShowEndGameScreen; openMenu = onOpenMenu; closeMenu = onCloseMenu; createDamageSquares = onCreateDamageSquares; unitClickHandler = onUnitClick; waitUnit = onUnitWait; animUnit = onAnimUnit; animExplosion = onAnimExplosion; updateAirUnitFogOnMove = onFogUpdate; actionHandlers.Fire = onFire; actionHandlers.AttackSeam = onAttackSeam; actionHandlers.Move = onMove; actionHandlers.Capt = onCapture; actionHandlers.Build = onBuild; actionHandlers.Load = onLoad; actionHandlers.Unload = onUnload; actionHandlers.Supply = onSupply; actionHandlers.Repair = onRepair; actionHandlers.Hide = onHide; actionHandlers.Unhide = onUnhide; actionHandlers.Join = onJoin; actionHandlers.Launch = onLaunch; actionHandlers.NextTurn = onNextTurn; actionHandlers.Elimination = onElimination; actionHandlers.Power = onPower; actionHandlers.GameOver = onGameOver; actionHandlers.Resign = onResign; addConnectionErrorObserver(onConnectionError); } function onCursorMove(cursorX, cursorY) { if (!musicSettings.isPlaying) return; const dx = Math.abs(cursorX - lastCursorX); const dy = Math.abs(cursorY - lastCursorY); const cursorMoved = dx >= 1 || dy >= 1; const timeSinceLastCursorCall = Date.now() - lastCursorCall$1; if (timeSinceLastCursorCall < CURSOR_THRESHOLD_MS) return; if (cursorMoved) { playSFX(GameSFX.uiCursorMove); lastCursorCall$1 = Date.now(); } lastCursorX = cursorX; lastCursorY = cursorY; } function onQueryTurn(gameId, turn, turnPId, turnDay, replay, initial) { const result = ahQueryTurn?.apply(ahQueryTurn, [gameId, turn, turnPId, turnDay, replay, initial]); if (!musicSettings.isPlaying) return result; refreshMusicForNextTurn(250); return result; } function onShowEventScreen(event) { ahShowEventScreen?.apply(ahShowEventScreen, [event]); if (!musicSettings.isPlaying) return; if (hasGameEnded()) { refreshMusicForNextTurn(); return; } playThemeSong(); window.setTimeout(playThemeSong, 500); } function onShowEndGameScreen(event) { ahShowEndGameScreen?.apply(ahShowEndGameScreen, [event]); if (!musicSettings.isPlaying) return; refreshMusicForNextTurn(); } function onOpenMenu(menu, x, y) { ahOpenMenu?.apply(openMenu, [menu, x, y]); if (!musicSettings.isPlaying) return; currentMenuType = "Regular" /* Regular */; playSFX(GameSFX.uiMenuOpen); const menuOptions = getCurrentDocument().getElementsByClassName("menu-option"); for (let i = 0; i < menuOptions.length; i++) { menuOptions[i].addEventListener("mouseenter", (_e) => playSFX(GameSFX.uiMenuMove)); menuOptions[i].addEventListener("click", (event) => { const target = event.target; if (!target) return; if ( target.classList.contains("forbidden") || target.parentElement?.classList.contains("forbidden") || target.parentElement?.parentElement?.classList.contains("forbidden") || target.parentElement?.parentElement?.parentElement?.classList.contains("forbidden") ) { playSFX(GameSFX.uiInvalid); return; } currentMenuType = "None" /* None */; playSFX(GameSFX.uiMenuOpen); }); } } function onCloseMenu() { ahCloseMenu?.apply(closeMenu, []); if (!musicSettings.isPlaying) return; const isMenuOpen = currentMenuType !== "None"; /* None */ if (isMenuOpen) { playSFX(GameSFX.uiMenuClose); clickedDamageSquaresMap.clear(); currentMenuType = "None" /* None */; } } function onCreateDamageSquares(attackerUnit, unitsInRange, movementInfo, movingUnit) { ahCreateDamageSquares?.apply(createDamageSquares, [attackerUnit, unitsInRange, movementInfo, movingUnit]); if (!musicSettings.isPlaying) return; for (const damageSquare of getAllDamageSquares()) { damageSquare.addEventListener("click", (event) => { if (!event.target) return; const targetSpan = event.target; playSFX(GameSFX.uiMenuOpen); if (clickedDamageSquaresMap.has(targetSpan)) { currentMenuType = "None" /* None */; clickedDamageSquaresMap.clear(); return; } currentMenuType = "DamageSquare" /* DamageSquare */; clickedDamageSquaresMap.set(targetSpan, true); }); } } function onUnitClick(clicked) { ahUnitClick?.apply(unitClickHandler, [clicked]); if (!musicSettings.isPlaying) return; const unitInfo = getUnitInfo(Number(clicked.id)); if (!unitInfo) return; const myID = getMyID(); const isUnitWaited = hasUnitMovedThisTurn(unitInfo.units_id); const isMyUnit = unitInfo?.units_players_id === myID; const isMyTurn = currentTurn === myID; const canActionsBeTaken = !isUnitWaited && isMyUnit && isMyTurn && !isReplayActive(); currentMenuType = canActionsBeTaken ? "UnitSelect" /* UnitSelect */ : "None" /* None */; playSFX(GameSFX.uiUnitSelect); } function onUnitWait(unitId) { ahWait?.apply(waitUnit, [unitId]); if (!musicSettings.isPlaying) return; if (movementResponseMap.has(unitId)) { const response = movementResponseMap.get(unitId); if (response?.trapped) { playSFX(GameSFX.unitTrap); } stopMovementSound(unitId, !response?.trapped); movementResponseMap.delete(unitId); return; } stopMovementSound(unitId); } function onAnimUnit(path, unitId, unitSpan, unitTeam, viewerTeam, i) { ahAnimUnit?.apply(animUnit, [path, unitId, unitSpan, unitTeam, viewerTeam, i]); if (!musicSettings.isPlaying) return; if (!isValidUnit(unitId) || !path || !i) return; if (i >= path.length) return; if (visibilityMap.has(unitId)) return; const unitVisible = path[i].unit_visible; if (!unitVisible) { visibilityMap.set(unitId, unitVisible); window.setTimeout(() => stopMovementSound(unitId, false), 1e3); } } function onAnimExplosion(unit) { ahAnimExplosion?.apply(animExplosion, [unit]); if (!musicSettings.isPlaying) return; const unitId = unit.units_id; const unitFuel = unit.units_fuel; let sfx = GameSFX.unitExplode; if (getUnitName(unitId) === "Black Bomb" && unitFuel > 0) { sfx = GameSFX.unitMissileHit; } playSFX(sfx); stopMovementSound(unitId, false); } function onFogUpdate(x, y, mType, neighbours, unitVisible, change, delay) { ahFog?.apply(updateAirUnitFogOnMove, [x, y, mType, neighbours, unitVisible, change, delay]); if (!musicSettings.isPlaying) return; const unitInfo = getUnitInfoFromCoords(x, y); if (!unitInfo) return; if (change === "Add") { window.setTimeout(() => stopMovementSound(unitInfo.units_id, true), delay); } } function onFire(response) { if (!musicSettings.isPlaying) { ahFire?.apply(actionHandlers.Fire, [response]); return; } const attackerID = response.copValues.attacker.playerId; const defenderID = response.copValues.defender.playerId; const couldAttackerActivateSCOPBefore = canPlayerActivateSuperCOPower(attackerID); const couldAttackerActivateCOPBefore = canPlayerActivateCOPower(attackerID); const couldDefenderActivateSCOPBefore = canPlayerActivateSuperCOPower(defenderID); const couldDefenderActivateCOPBefore = canPlayerActivateCOPower(defenderID); ahFire?.apply(actionHandlers.Fire, [response]); const delay = areAnimationsEnabled() ? 750 : 0; const canAttackerActivateSCOPAfter = canPlayerActivateSuperCOPower(attackerID); const canAttackerActivateCOPAfter = canPlayerActivateCOPower(attackerID); const canDefenderActivateSCOPAfter = canPlayerActivateSuperCOPower(defenderID); const canDefenderActivateCOPAfter = canPlayerActivateCOPower(defenderID); const madeSCOPAvailable = (!couldAttackerActivateSCOPBefore && canAttackerActivateSCOPAfter) || (!couldDefenderActivateSCOPBefore && canDefenderActivateSCOPAfter); const madeCOPAvailable = (!couldAttackerActivateCOPBefore && canAttackerActivateCOPAfter) || (!couldDefenderActivateCOPBefore && canDefenderActivateCOPAfter); window.setTimeout(() => { if (madeSCOPAvailable) playSFX(GameSFX.powerSCOPAvailable); else if (madeCOPAvailable) playSFX(GameSFX.powerCOPAvailable); }, delay); } function wiggleTile(div, startDelay = 0) { const stepsX = 12; const stepsY = 4; const deltaX = 0.2; const deltaY = 0.05; const wiggleAnimation = () => { moveDivToOffset( div, deltaX, 0, stepsX, { then: [0, -0.05, stepsY] }, { then: [-0.2 * 2, 0, stepsX] }, { then: [deltaX * 2, 0, stepsX] }, { then: [0, -0.05, stepsY] }, { then: [-0.2 * 2, 0, stepsX] }, { then: [deltaX * 2, 0, stepsX] }, { then: [0, deltaY, stepsY] }, { then: [-0.2 * 2, 0, stepsX] }, { then: [deltaX, 0, stepsX] }, { then: [0, deltaY, stepsY] }, ); }; window.setTimeout(wiggleAnimation, startDelay); } function onAttackSeam(response) { ahAttackSeam?.apply(actionHandlers.AttackSeam, [response]); if (!musicSettings.isPlaying) return; const seamWasDestroyed = response.seamHp <= 0; if (areAnimationsEnabled()) { const x = response.seamX; const y = response.seamY; const pipeSeamInfo = getBuildingInfo(x, y); if (!pipeSeamInfo) return; const pipeSeamDiv = getBuildingDiv(pipeSeamInfo.buildings_id); const wiggleDelay = seamWasDestroyed ? 0 : attackDelayMS; wiggleTile(pipeSeamDiv, wiggleDelay); } if (seamWasDestroyed) { playSFX(GameSFX.unitAttackPipeSeam); playSFX(GameSFX.unitExplode); return; } window.setTimeout(() => playSFX(GameSFX.unitAttackPipeSeam), attackDelayMS); } function onMove(response, loadFlag) { ahMove?.apply(actionHandlers.Move, [response, loadFlag]); if (!musicSettings.isPlaying) return; const unitId = response.unit.units_id; movementResponseMap.set(unitId, response); const movementDist = response.path.length; stopMovementSound(unitId, false); if (movementDist > 1) { playMovementSound(unitId); } } function onCapture(data) { ahCapt?.apply(actionHandlers.Capt, [data]); if (!musicSettings.isPlaying) return; const finishedCapture = data.newIncome != null; if (!finishedCapture) { playSFX(GameSFX.unitCaptureProgress); return; } const myID = getMyID(); const isSpectator = isPlayerSpectator(myID); const isMyCapture = data.buildingInfo.buildings_team === myID.toString() || isSpectator; const sfx = isMyCapture ? GameSFX.unitCaptureAlly : GameSFX.unitCaptureEnemy; playSFX(sfx); } function onBuild(data) { ahBuild?.apply(actionHandlers.Build, [data]); if (!musicSettings.isPlaying) return; const myID = getMyID(); const isMyBuild = data.newUnit.units_players_id == myID; const isReplay = isReplayActive(); if (!isMyBuild || isReplay) playSFX(GameSFX.unitSupply); } function onLoad(data) { ahLoad?.apply(actionHandlers.Load, [data]); if (!musicSettings.isPlaying) return; playSFX(GameSFX.unitLoad); } function onUnload(data) { ahUnload?.apply(actionHandlers.Unload, [data]); if (!musicSettings.isPlaying) return; playSFX(GameSFX.unitUnload); } function onSupply(data) { ahSupply?.apply(actionHandlers.Supply, [data]); if (!musicSettings.isPlaying) return; playSFX(GameSFX.unitSupply); } function onRepair(data) { ahRepair?.apply(actionHandlers.Repair, [data]); if (!musicSettings.isPlaying) return; playSFX(GameSFX.unitSupply); } function onHide(data) { ahHide?.apply(actionHandlers.Hide, [data]); if (!musicSettings.isPlaying) return; playSFX(GameSFX.unitHide); stopMovementSound(data.unitId); } function onUnhide(data) { ahUnhide?.apply(actionHandlers.Unhide, [data]); if (!musicSettings.isPlaying) return; playSFX(GameSFX.unitUnhide); stopMovementSound(data.unitId); } function onJoin(data) { ahJoin?.apply(actionHandlers.Join, [data]); if (!musicSettings.isPlaying) return; stopMovementSound(data.joinID); stopMovementSound(data.joinedUnit.units_id); } function onLaunch(data) { ahLaunch?.apply(actionHandlers.Launch, [data]); if (!musicSettings.isPlaying) return; playSFX(GameSFX.unitMissileSend); window.setTimeout(() => playSFX(GameSFX.unitMissileHit), siloDelayMS); } function onNextTurn(data) { ahNextTurn?.apply(actionHandlers.NextTurn, [data]); if (!musicSettings.isPlaying) return; if (data.swapCos) { playSFX(GameSFX.tagSwap); } refreshMusicForNextTurn(); } function onElimination(data) { ahElimination?.apply(actionHandlers.Elimination, [data]); if (!musicSettings.isPlaying) return; refreshMusicForNextTurn(); } function onGameOver() { ahGameOver?.apply(actionHandlers.GameOver, []); if (!musicSettings.isPlaying) return; refreshMusicForNextTurn(); } function onResign(data) { ahResign?.apply(actionHandlers.Resign, [data]); if (!musicSettings.isPlaying) return; refreshMusicForNextTurn(); } function onPower(data) { ahPower?.apply(actionHandlers.Power, [data]); if (!musicSettings.isPlaying) return; const coName = data.coName; const isBH = isBlackHoleCO(coName); const isSuperCOPower = data.coPower === COPowerEnum.SuperCOPower; stopSFX(GameSFX.powerCOPAvailable); stopSFX(GameSFX.powerSCOPAvailable); window.setTimeout(() => { stopSFX(GameSFX.powerCOPAvailable); stopSFX(GameSFX.powerSCOPAvailable); }, 755); musicSettings.themeType = isSuperCOPower ? ThemeType.SUPER_CO_POWER : ThemeType.CO_POWER; switch (musicSettings.gameType) { case GameType.AW1: playSFX(GameSFX.powerActivateAW1COP); stopThemeSong(4500); return; case GameType.AW2: case GameType.DS: case GameType.RBC: { if (isSuperCOPower) { const sfx2 = isBH ? GameSFX.powerActivateBHSCOP : GameSFX.powerActivateAllySCOP; const delay2 = isBH ? 1916 : 1100; playSFX(sfx2); stopThemeSong(delay2); break; } const sfx = isBH ? GameSFX.powerActivateBHCOP : GameSFX.powerActivateAllyCOP; const delay = isBH ? 1019 : 881; playSFX(sfx); stopThemeSong(delay); break; } } if (coName === "Colin" && !isSuperCOPower) { window.setTimeout(() => playSFX(GameSFX.coGoldRush), 800); } } function onConnectionError(closeMsg) { closeMsg = closeMsg.toLowerCase(); if (closeMsg.includes("connected to another game")) stopThemeSong(); } let debugOverrides = false; function toggleDebugOverrides() { debugOverrides = !debugOverrides; if (debugOverrides) { for (const coName of getAllCONames()) { musicSettings.addOverride(coName, GameType.AW1); musicSettings.addExcludedRandomTheme(coName); } } else { for (const coName of getAllCONames()) { musicSettings.removeOverride(coName); musicSettings.removeExcludedRandomTheme(coName); } } } function onLiveQueue() { const addMusicFn = () => { const blockerPopup = getLiveQueueBlockerPopup(); if (!blockerPopup) return false; if (blockerPopup.style.display === "none") return false; const popup = getLiveQueueSelectPopup(); if (!popup) return false; const box = popup.querySelector(".flex.row.hv-center"); if (!box) return false; musicPlayerUI.addToAWBWPage(box, true); playMusicURL(SpecialTheme.COSelect); return true; }; const checkStillActiveFn = () => { const blockerPopup = getLiveQueueBlockerPopup(); return blockerPopup?.style.display !== "none"; }; const addPlayerIntervalID = window.setInterval(() => { if (getCurrentPageType() !== PageType.LiveQueue) { window.clearInterval(addPlayerIntervalID); return; } if (!addMusicFn()) return; window.clearInterval(addPlayerIntervalID); const checkInterval = window.setInterval(() => { if (getCurrentPageType() !== PageType.LiveQueue) { window.clearInterval(checkInterval); playThemeSong(); return; } if (checkStillActiveFn()) playMusicURL(SpecialTheme.COSelect); else playThemeSong(); }, 500); }, 500); } let setHashesTimeoutID; function preloadThemes() { addThemeListeners(); preloadAllCommonAudio(() => { logInfo("All common audio has been pre-loaded!"); musicSettings.themeType = getCurrentThemeType(); musicPlayerUI.updateAllInputLabels(); playThemeSong(); window.setTimeout(playThemeSong, 500); if (!setHashesTimeoutID) { const checkHashesMS = 1e3 * 60 * 1; const checkHashesFn = () => { checkHashesInDB() .then(() => logInfo("All music files have been checked for updates.")) .catch((reason) => logError("Could not check for music file updates:", reason)); setHashesTimeoutID = window.setTimeout(checkHashesFn, checkHashesMS); }; checkHashesFn(); } musicPlayerUI.checkIfNewVersionAvailable(); }); } let lastCursorCall = Date.now(); function initializeMusicPlayer() { const currentPageType = getCurrentPageType(); if (currentPageType !== PageType.ActiveGame) musicSettings.isPlaying = musicSettings.autoplayOnOtherPages; switch (currentPageType) { case PageType.LiveQueue: onLiveQueue(); break; case PageType.Maintenance: musicPlayerUI.openContextMenu(); break; case PageType.MovePlanner: musicSettings.isPlaying = true; break; } preloadThemes(); allowSettingsToBeSaved(); initializeMusicPlayerUI(); addHandlers(); const iframe = document.getElementById(IFRAME_ID); iframe?.addEventListener("focus", () => { if (musicSettings.isPlaying) playThemeSong(); }); window.addEventListener("focus", () => { if (musicSettings.isPlaying) playThemeSong(); }); broadcastChannel.onmessage = (ev) => { logDebug("Received message from another tab:", ev.data); if (ev.data === "pause") stopThemeSong(); else if (ev.data === "play") playThemeSong(); }; const fn = (_e) => { const timeSinceLastCursorCall = Date.now() - lastCursorCall; if (!musicSettings.sfxOnOtherPages) return; if (timeSinceLastCursorCall < 80) return; playSFX(GameSFX.uiMenuMove); lastCursorCall = Date.now(); }; const addSFXToPage = () => { getCurrentDocument() .querySelectorAll("a") .forEach((link) => link.addEventListener("click", () => { if (!musicSettings.sfxOnOtherPages) return; playSFX(GameSFX.uiMenuOpen); }), ); const hoverElements = Array.from( getCurrentDocument().querySelectorAll("li, ul, .dropdown-menu, .co_portrait, a, input, button"), ); hoverElements.forEach((menu) => menu.addEventListener("mouseenter", fn)); }; addSFXToPage(); let overDiv = document.querySelector("#overDiv"); if (!overDiv) { overDiv = document.createElement("div"); overDiv.id = "overDiv"; overDiv.style.visibility = "hidden"; overDiv.style.position = "absolute"; overDiv.style.zIndex = "2000"; document.appendChild(overDiv); } const overDivObserver = new MutationObserver(() => { if (overDiv.style.visibility === "visible") addSFXToPage(); }); overDivObserver.observe(overDiv, { attributes: true }); } let autoplayChecked = false; function checkAutoplayThenInitialize() { logDebug("Checking if we can autoplay then initializing the music player."); if (autoplayChecked) { initializeMusicPlayer(); return; } autoplayChecked = true; const ifCanAutoplay = () => { initializeMusicPlayer(); }; const ifCannotAutoplay = () => { const initfn = () => { window.clearInterval(autoplayIntervalID); initializeMusicPlayer(); }; musicPlayerUI.addEventListener("click", initfn, { once: true }); document.querySelector("body")?.addEventListener("click", initfn, { once: true }); }; const autoplayIntervalID = window.setInterval(() => { canAutoplay .audio() .then((response) => { const result = response.result; logDebug("Script starting, does your browser allow you to auto-play:", result); if (result) { ifCanAutoplay(); window.clearInterval(autoplayIntervalID); } else ifCannotAutoplay(); }) .catch((reason) => { logDebug("Script starting, could not check your browser allows auto-play so assuming no: ", reason); ifCannotAutoplay(); }); }, 100); } function main() { if (self !== top) return; const isMainPage = getCurrentPageType() === PageType.MainPage; if (!isMainPage && !window.location.href.includes(".php")) return; loadSettingsFromLocalStorage(); logInfo("Opening database to cache music files."); openDB() .then(() => logInfo("Database opened successfully. Ready to cache music files.")) .catch((reason) => logDebug(`Database Error: ${reason}. Will not be able to cache music files locally.`)) .finally(() => { if (getCurrentPageType() === PageType.Maintenance) { checkAutoplayThenInitialize(); const maintenanceDiv = document.querySelector("#server-maintenance-alert"); const currentText = maintenanceDiv?.textContent; const minutes = currentText?.match(/\d+m/)?.[0].replace("m", "") ?? 0; const seconds = currentText?.match(/\d+s/)?.[0].replace("s", "") ?? 0; logInfo("Maintenance page detected. Will try again in", minutes, "minutes and", seconds, "seconds."); return; } initializeIFrame(checkAutoplayThenInitialize); }); } main(); exports.checkAutoplayThenInitialize = checkAutoplayThenInitialize; exports.initializeMusicPlayer = initializeMusicPlayer; exports.notifyCOSelectorListeners = notifyCOSelectorListeners; exports.toggleDebugOverrides = toggleDebugOverrides; return exports; })({}, canAutoplay, SparkMD5);