您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
[Client Side] Allows users to fetch custom army sprites and use them in game!
当前为
// ==UserScript== // @name AWBW Custom Army Importer - Beta 1.5 // @namespace http://tampermonkey.net/ // @version 1.5 // @description [Client Side] Allows users to fetch custom army sprites and use them in game! // @author Vesper // @match https://awbw.amarriner.com/prevmaps.php* // @match https://awbw.amarriner.com/editmap.php* // @match https://awbw.amarriner.com/game.php* // @icon https://awbw.amarriner.com/terrain/aw1/bluestar.gif // @grant none // @license MIT // ==/UserScript== debugger; function debounce(func, wait) { let timeout; return function (...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } function getKeyByValue(obj, value) { const entry = Object.entries(obj).find(([key, val]) => val === value); return entry ? entry[0] : null; // Return the key or null if not found } function extractCountryAndPath(spritePath) { let countryDict = {} let countries = [] for (let c of Object.keys(BaseInfo.countries)) { let flatname = getFlatName(c); if (flatname == undefined) continue; countryDict[flatname] = c; countries.push(flatname); } for (let c of countries) { if (spritePath.includes(c)) { let path = spritePath.slice(spritePath.indexOf(c)) path = path.replace(c, "") let country = countryDict[c]; return { country, path }; } } let match = spritePath.match(/terrain\/ani\/(gs_)?([a-zA-Z]{2})(.*)/); if (match) { const country = match[2]; // Extracts the 2-letter country code const path = match[3]; // Extracts everything after the country code return { country, path, gs: spritePath.includes("/ani/gs_")}; } // Case 2: Check for "aw2/movement" paths with the country in the folder name match = spritePath.match(/terrain\/aw2\/movement\/([a-zA-Z]{2})\/(.*)/); if (match) { const country = match[1]; // Extracts the 2-letter country code let path = match[2]; // Extracts everything after the country folder // Remove the country code prefix from the path (if it starts with the country code) if (path.startsWith(country)) { path = path.slice(country.length); // Remove the country code prefix } return { country, path }; } return null; // Return null if the format doesn't match } // Country_Code: [githubURL] let countryReplacementMap = {}; let countriesDisabled = []; window.countryReplacementMap = countryReplacementMap; window.countriesDisabled = countriesDisabled; function isBaseURL(url) { for (let baseURL of Object.values(countryReplacementMap)) { return url.startsWith(baseURL); } return false; } var BaseInfo = {}; BaseInfo.units = { 1: { name: "Infantry", cost: 1e3, move_points: 3, move_type: "F", fuel: 99, fuel_per_turn: 0, ammo: 0, short_range: 0, long_range: 0, second_weapon: "N" }, 2: { name: "Mech", cost: 3e3, move_points: 2, move_type: "B", fuel: 70, fuel_per_turn: 0, ammo: 3, short_range: 0, long_range: 0, second_weapon: "Y" }, 3: { name: "Md.Tank", cost: 16e3, move_points: 5, move_type: "T", fuel: 50, fuel_per_turn: 0, ammo: 8, short_range: 0, long_range: 0, second_weapon: "Y" }, 4: { name: "Tank", cost: 7e3, move_points: 6, move_type: "T", fuel: 70, fuel_per_turn: 0, ammo: 9, short_range: 0, long_range: 0, second_weapon: "Y" }, 5: { name: "Recon", cost: 4e3, move_points: 8, move_type: "W", fuel: 80, fuel_per_turn: 0, ammo: 0, short_range: 0, long_range: 0, second_weapon: "N" }, 6: { name: "APC", cost: 5e3, move_points: 6, move_type: "T", fuel: 70, fuel_per_turn: 0, ammo: 0, short_range: 0, long_range: 0, second_weapon: "N" }, 7: { name: "Artillery", cost: 6e3, move_points: 5, move_type: "T", fuel: 50, fuel_per_turn: 0, ammo: 9, short_range: 2, long_range: 3, second_weapon: "N" }, 8: { name: "Rocket", cost: 15e3, move_points: 5, move_type: "W", fuel: 50, fuel_per_turn: 0, ammo: 6, short_range: 3, long_range: 5, second_weapon: "N" }, 9: { name: "Anti-Air", cost: 8e3, move_points: 6, move_type: "T", fuel: 60, fuel_per_turn: 0, ammo: 9, short_range: 0, long_range: 0, second_weapon: "N" }, 10: { name: "Missile", cost: 12e3, move_points: 4, move_type: "W", fuel: 50, fuel_per_turn: 0, ammo: 6, short_range: 3, long_range: 5, second_weapon: "N" }, 11: { name: "Fighter", cost: 2e4, move_points: 9, move_type: "A", fuel: 99, fuel_per_turn: 5, ammo: 9, short_range: 0, long_range: 0, second_weapon: "N" }, 12: { name: "Bomber", cost: 22e3, move_points: 7, move_type: "A", fuel: 99, fuel_per_turn: 5, ammo: 9, short_range: 0, long_range: 0, second_weapon: "N" }, 13: { name: "B-Copter", cost: 9e3, move_points: 6, move_type: "A", fuel: 99, fuel_per_turn: 2, ammo: 6, short_range: 0, long_range: 0, second_weapon: "Y" }, 14: { name: "T-Copter", cost: 5e3, move_points: 6, move_type: "A", fuel: 99, fuel_per_turn: 2, ammo: 0, short_range: 0, long_range: 0, second_weapon: "N" }, 15: { name: "Battleship", cost: 28e3, move_points: 5, move_type: "S", fuel: 99, fuel_per_turn: 1, ammo: 9, short_range: 2, long_range: 6, second_weapon: "N" }, 16: { name: "Cruiser", cost: 18e3, move_points: 6, move_type: "S", fuel: 99, fuel_per_turn: 1, ammo: 9, short_range: 0, long_range: 0, second_weapon: "N" }, 17: { name: "Lander", cost: 12e3, move_points: 6, move_type: "L", fuel: 99, fuel_per_turn: 1, ammo: 0, short_range: 0, long_range: 0, second_weapon: "N" }, 18: { name: "Sub", cost: 2e4, move_points: 5, move_type: "S", fuel: 60, fuel_per_turn: 1, ammo: 6, short_range: 0, long_range: 0, second_weapon: "N" }, 46: { name: "Neotank", cost: 22e3, move_points: 6, move_type: "T", fuel: 99, fuel_per_turn: 1, ammo: 9, short_range: 0, long_range: 0, second_weapon: "Y" }, 960900: { name: "Piperunner", cost: 2e4, move_points: 9, move_type: "P", fuel: 99, fuel_per_turn: 0, ammo: 9, short_range: 2, long_range: 5, second_weapon: "Y" }, 968731: { name: "Black Bomb", cost: 25e3, move_points: 9, move_type: "A", fuel: 45, fuel_per_turn: 5, ammo: 0, short_range: 0, long_range: 0, second_weapon: "N" }, 1141438: { name: "Mega Tank", cost: 28e3, move_points: 4, move_type: "T", fuel: 50, fuel_per_turn: 0, ammo: 3, short_range: 0, long_range: 0, second_weapon: "Y" }, 28: { name: "Black Boat", cost: 7500, move_points: 7, move_type: "L", fuel: 60, fuel_per_turn: 1, ammo: 0, short_range: 0, long_range: 0, second_weapon: "N" }, 30: { name: "Stealth", cost: 24e3, move_points: 6, move_type: "A", fuel: 60, fuel_per_turn: 5, ammo: 6, short_range: 0, long_range: 0, second_weapon: "N" }, 29: { name: "Carrier", cost: 3e4, move_points: 5, move_type: "S", fuel: 99, fuel_per_turn: 1, ammo: 9, short_range: 3, long_range: 8, second_weapon: "N" } } BaseInfo.countries = { os: { id: 1, name: "Orange Star", color: "181, 39, 68" }, bm: { id: 2, name: "Blue Moon", color: "70,110,254" }, ge: { id: 3, name: "Green Earth", color: "61, 194, 45" }, yc: { id: 4, name: "Yellow Comet", color: "201, 189, 2" }, bh: { id: 5, name: "Black Hole", color: "116, 89, 138" }, rf: { id: 6, name: "Red Fire", color: "146, 50, 67" }, gs: { id: 7, name: "Grey Sky", color: "114, 114, 114" }, bd: { id: 8, name: "Brown Desert", color: "152, 83, 51" }, ab: { id: 9, name: "Amber Blaze", color: "252, 163, 57" }, js: { id: 10, name: "Jade Sun", color: "166, 182, 153" }, ci: { id: 16, name: "Cobalt Ice", color: "11, 32, 112" }, pc: { id: 17, name: "Pink Cosmos", color: "255, 102, 204" }, tg: { id: 19, name: "Teal Galaxy", color: "60, 205, 193" }, pl: { id: 20, name: "Purple Lightning", color: "111, 26, 155" }, ar: { id: 21, name: "Acid Rain", color: "97, 124, 14" }, wn: { id: 22, name: "White Nova", color: "205, 155, 154" }, aa: { id: 23, name: "Azure Asteroid", color: "130, 220, 232" }, ne: { id: 24, name: "Noir Eclipse", color: "3, 3, 3" }, sc: { id: 25, name: "Silver Claw", color: "137, 168, 188" } } window.BaseInfo = BaseInfo; Object.freeze(BaseInfo); function getFlatName(countryCode) { if (BaseInfo.countries[countryCode] == undefined) return undefined return BaseInfo.countries[countryCode].name.toLowerCase().replace(" ", "") } // MutationObserver to replace sprites const targetNode = document.querySelector('#gamemap'); if (!targetNode) return; function initArmyImporter() { Vue.component("PrevMapAnalyzer", { template: `<div id='replay-misc-controls'> <div ref='openBtn' class='flex v-center' style='padding: 0px 12px; cursor: pointer; user-select: none;' @click='open = !open'> <img src='terrain/aw1/bluestar.gif'/><b>Custom Armies</b> </div> <div v-show='open' class='flex col' style='position: absolute; z-index:210; top: 100px; right: 0px;'> <div class='bordertitle flex' style='color: #fff; background: #06c; border: 1px black solid; padding: 4px; justify-content: space-between;'> <div style="font-weight: bold; display: block; float: left;">Custom Army Importer - Beta 1.4</div> <div style="cursor: pointer" @click="open = false"> <img width='16' src="terrain/close.png"/> </div> </div> <div style='background: #fff; border: 1px black solid; padding: 4px;'> <div class='custom-army-importer-intro' style="background: #7ebeff; border: 1px black solid; padding: 4px; color: #000000; font-size: 12px"> <div style="display: flex; flex-direction: column; max-width: 312px; align-items: left; justify-content: left; margin: 0 auto;"> <label style='text-align: center; padding-bottom: 8px'><strong>Welcome To The Custom Army Importer!</strong></label> <label style='text-align: left; padding-bottom: 4px'>Here you can set specific countries to any custom army you want.</label> <label style='text-align: left; padding-bottom: 2px'>Each country available on the site can be pointed to a custom github repo or CDN.</label> <label style='text-align: left; padding-bottom: 8px'>Enter the <strong>Folder</strong> that the link points to under any country then press <strong>Enter</strong>.</label> <strong style='text-align: left; padding-bottom: 2px'>Example URL:</strong> <label style='text-align: left; padding-bottom: 2px'>https://raw.githubusercontent.com/ShinyDeagle/My-Custom-Army/refs/heads/main/ab-rework/</label> <label style='text-align: left; padding-bottom: 2px'>Afterwards, you need to press Reload Sprites to import the new sprites.</label> <label style='text-align: left; padding-bottom: 2px'>You'll know if your sprites loaded if you see the custom Infantry and Mech below the url.</label> <strong style='text-align: left; padding-bottom: 2px'>WE ALSO SUPPORT CUSTOM BUILDINGS NOW!</strong> <strong style='text-align: left; padding-bottom: 12px'>Check the Github Readme Plis.</strong> <label style='text-align: left; padding-bottom: 2px'>If the animations seem to <strong>Flicker</strong>...</label> <label style='text-align: left; padding-bottom: 2px'>Reload the page with <strong>CTRL + SHIFT + R</strong> to refresh the browser cache.</label> <label style='text-align: left; padding-bottom: 12px'>Refreshing the page can solve a lot of problems :p</label> <label style='text-align: left; padding-bottom: 2px'>If you want to make your <strong>Own Custom Army</strong>, follow these steps at my <strong>Readme</strong> on this repo.</label> <strong style='text-align: left; padding-bottom: 12px'>https://github.com/ShinyDeagle/My-Custom-Army</strong> <strong style='text-align: left; padding-bottom: 2px'>Have Fun!</strong> </div> </div> <hr style='width: 312px;'> <div v-for='cc in countries' style="display: flex; flex-direction: column; gap: 4px; width: 312px"> <div style='width: 100%; display: flex; flex-direction: row; align-items: center; gap: 4px;'> <img style='width: 24px; margin: 1px;' :src='"terrain/aw1/"+ cc +"logo.gif"' > <strong style="font-size: 16px" > {{BaseInfo.countries[cc].name}} </strong> <img style='width: 24px; margin: 1px;' :src='"terrain/ani/"+ cc +"infantry.gif"' > <img style='width: 24px; margin: 1px;' :src='"terrain/ani/"+ cc +"mech.gif"' > </div> <div style='width: 100%; height: 100%;'> <div style="text-align: left; font-size: 10px; margin-left: 12px; margin-right: 32px; border: 1px black solid; padding: 4px" contenteditable="true" @keydown.enter.prevent="updateCountryURL($event, cc)" > {{ countryReplacementMap[cc] || "Enter Github Folder URL" }} </div> </div> <div v-if='countryReplacementMap[cc]'> <div style="display: flex; flex-direction: column"> <div style="display: flex; flex-direction: row; margin-top: 8px; align-items: center; justify-content: center; color: #ffffff; gap:16px; font-size: 12px"> <div @click='preloadSprites(cc)' style="font-weight: bold; padding: 8px; background: #7ebeff;"> Reload Sprites </div> <div v-show='isDisabled(cc)' @click='disableCountry(cc)' style="font-weight: bold; padding: 8px; background: #ff972b; border: 2px solid black;"> Enable Country </div> <div v-show='!isDisabled(cc)' @click='disableCountry(cc)' style="font-weight: bold; padding: 8px; background: #7ebeff;"> Disable Country </div> </div> <div style='margin-top: 4px; margin-bottom: 0px'> <img style='width: 24px; margin: 1px;' :src='countryReplacementMap[cc] + "xxinfantry.gif"' > <img style='width: 24px; margin: 1px;' :src='countryReplacementMap[cc] + "xxmech.gif"' > <img style='width: 24px; margin: 1px;' :src='countryReplacementMap[cc] + "xxcity.gif"' > <img style='width: 24px; margin: 1px;' :src='countryReplacementMap[cc] + "xxhq.gif"' > <img style='width: 24px; margin: 1px;' :src='countryReplacementMap[cc] + "xxlab.gif"' > </div> </div> </div> <hr style='width: 312px;'> </div> </div> </div>`, props: { countries: Array, }, data: function() { return { open: !1, countriesDisabled: [], countryReplacementMap: {}, } }, created() { this.BaseInfo = BaseInfo; this.countryReplacementMap = countryReplacementMap; this.countriesDisabled = countriesDisabled; // Load settings for countryReplacementMap from localStorage let importerSettings = localStorage.importerSettings; if (importerSettings) { let data = JSON.parse(importerSettings); this.countryReplacementMap = data; window.countryReplacementMap = this.countryReplacementMap; preloadSprites(); } // Load settings for countriesDisabled from localStorage let disabledCountries = localStorage.disabledCountries; if (disabledCountries) { let data = JSON.parse(disabledCountries); this.countriesDisabled = data; window.countriesDisabled = this.countriesDisabled; } // Save settings with debounce to avoid excessive updates this.saveSettings = debounce(() => { // Save countryReplacementMap to localStorage localStorage.importerSettings = JSON.stringify(this.countryReplacementMap); // Save countriesDisabled to localStorage localStorage.disabledCountries = JSON.stringify(this.countriesDisabled); }, 1500); }, methods: { preloadSprites(country) { window.preloadCountrySprites(country, this.countryReplacementMap); }, isDisabled(country) { return this.countriesDisabled.includes(country); }, disableCountry(country) { if (this.countriesDisabled.includes(country)) { let index = this.countriesDisabled.indexOf(country); if (index != -1) this.countriesDisabled.splice(index, 1); } else { this.countriesDisabled.push(country); } window.countriesDisabled = this.countriesDisabled; this.saveSettings(); }, updateCountryURL(event, cc) { const newContent = event.target.innerText.trim(); // Get text content if (newContent) { // Update the map if the input is not empty this.$set(this.countryReplacementMap, cc, newContent); console.log(`Updated country ${cc}: ${newContent}`); } else { if (!this.countriesDisabled.includes(cc)) { this.countriesDisabled.push(cc); } window.checkSrcChanges(); // Remove the entry if the input is empty this.$delete(this.countryReplacementMap, cc); // Force Vue to detect reactivity changes this.countryReplacementMap = { ...this.countryReplacementMap }; window.countryReplacementMap = this.countryReplacementMap; let index = this.countriesDisabled.indexOf(cc); if (index != -1) this.countriesDisabled.splice(index, 1); console.log(`Deleted country ${cc}`); for (let spriteName of window.unitSpriteMap) { let sprite = spriteName.replace("xx", cc); window.replacementSprites.delete(sprite); } event.target.innerText = ""; } this.saveSettings(); // Optionally clear the focus after pressing Enter event.target.blur(); window.forceUpdate(); }, } }); let gameContainer = document.querySelector("#gamecontainer"); if (gameContainer == undefined) return; let extensionPanel = document.querySelector("#vesper-extensions"); if (extensionPanel == undefined) { extensionPanel = document.createElement("div"); extensionPanel.id = "vesper-extensions"; extensionPanel.style.background = '#98a0b8'; extensionPanel.style.border = '2px solid #768a96'; extensionPanel.style.display = 'flex'; extensionPanel.style.flexDirection = 'row'; extensionPanel.style.padding = '4px'; extensionPanel.style.margin = '0px'; extensionPanel.style.marginLeft = '-4px'; extensionPanel.style.marginBottom = '8px'; gameContainer.children[1].after(extensionPanel); } let e = document.createElement("div"); e.id = "custom-army-importer"; extensionPanel.append(e); let o = Object.keys(BaseInfo.countries).map((e => e)).sort(((e, t) => BaseInfo.countries[e].id - BaseInfo.countries[t].id)); window.armyImporter = new Vue({ el: "#custom-army-importer", template: '<PrevMapAnalyzer :countries="countries" :countriesDisabled="countriesDisabled" :countryReplacementMap="countryReplacementMap"/>', data: function() { return { countries: o, countriesDisabled: [], countryReplacementMap: {}, } } }) } // Path to your GitHub sprite folder const githubBaseUrl = "https://raw.githubusercontent.com/ShinyDeagle/custom-army-testing/refs/heads/main/ab-rework/"; // List of possible types sprites that can be replaced. const spriteMap = [ "xxoo.gif", "gs_xxoo.gif", "xxoo_mside.gif", "xxoo_mup.gif", "xxoo_mdown.gif", ]; const buildingMap = [ "xxcity.gif", "xxport.gif", "xxbase.gif", "xxlab.gif", "xxcomtower.gif", "xxairport.gif", "xxhq.gif", ]; const buildingNames = [ "city.gif", "port.gif", "base.gif", "lab.gif", "comtower.gif", "airport.gif", "hq.gif", ]; // List of all possible sprites that can be replaced. const unitSpriteMap = []; for (let id of Object.keys(BaseInfo.units)) { let name = BaseInfo.units[id].name; let cleanedName = name.toLowerCase().replace(" ", ""); for (let spriteKey of spriteMap) { let sprite = spriteKey.replace("oo", cleanedName); unitSpriteMap.push(sprite); } } // Accounts for the loss of the `.` in the md.tank when using animations on the site. unitSpriteMap.push("xxmdtank_mside.gif") unitSpriteMap.push("xxmdtank_mup.gif") unitSpriteMap.push("xxmdtank_mdown.gif") const buildingSpriteMap = buildingMap.slice(); window.unitSpriteMap = unitSpriteMap; window.buildingSpriteMap = unitSpriteMap; // Cache for available sprites const spriteTable = new Map(); let replacementSprites = new Map(); window.replacementSprites = replacementSprites; // Function to check if a sprite exists on GitHub async function checkSpritesExist(baseURLs, spriteNames, isBuilding) { const allChecks = []; for (let URL of baseURLs) { let baseURL = URL; let country = getKeyByValue(this.countryReplacementMap, baseURL); if (isBuilding) country = getFlatName(country); if (country == undefined) continue; const checks = spriteNames.map(spriteName => { const url = baseURL + spriteName; return fetch(url, { method: "HEAD" }) .then(response => ({ spriteName, baseURL: baseURL, country: country, exists: response.ok })) .catch(() => ({ spriteName, exists: false })); // Handle fetch errors }); for (let check of checks) allChecks.push(check); } // Wait for all checks to complete const results = await Promise.all(allChecks); return results; } // Preload all sprites async function preloadSprites() { countryReplacementMap = window.countryReplacementMap; const spriteArray = unitSpriteMap; // Convert unitSpriteMap to an array const baseURLs = Object.values(countryReplacementMap); let results = await checkSpritesExist(baseURLs, spriteArray); // Check all sprites at once // Process the results results.forEach(({ spriteName, baseURL, country, exists }) => { if (exists) { replacementSprites.set(spriteName.replace("xx", country), baseURL + spriteName); console.log(`Cached Unit: ${spriteName}`); } else { // console.log(`Not found: ${spriteName}`); } }); const buildingArray = buildingSpriteMap; results = await checkSpritesExist(baseURLs, buildingArray, true); // Process the results results.forEach(({ spriteName, baseURL, country, exists }) => { if (exists) { replacementSprites.set(spriteName.replace("xx", country), baseURL + spriteName); console.log(`Cached Building: ${spriteName}`); } else { // console.log(`Not found: ${spriteName}`); } }); console.log("All sprites preloaded!"); } async function preloadCountrySprites(country, map) { countryReplacementMap = map; window.countryReplacementMap = map; const spriteArray = unitSpriteMap; const baseURL = countryReplacementMap[country]; if (baseURL == undefined) return; let results = await checkSpritesExist([baseURL], spriteArray); // Process the results results.forEach(({ spriteName, baseURL, country, exists }) => { if (exists) { replacementSprites.set(spriteName.replace("xx", country), baseURL + spriteName); console.log(`Cached: ${spriteName}`); } else { // console.log(`Not found: ${spriteName}`); } }); const buildingArray = buildingSpriteMap; results = await checkSpritesExist([baseURL], buildingArray, true); // Process the results results.forEach(({ spriteName, baseURL, country, exists }) => { if (exists) { replacementSprites.set(spriteName.replace("xx", country), baseURL + spriteName); console.log(`Cached Building: ${spriteName}`); } else { // console.log(`Not found: ${spriteName}`); } }); console.log(`Sprites for ${country} have been preloaded!`); } window.preloadCountrySprites = preloadCountrySprites; function forceUpdate() { countryReplacementMap = window.countryReplacementMap; countriesDisabled = window.countriesDisabled; replacementSprites = window.replacementSprites; } window.forceUpdate = forceUpdate; // Run preloading process preloadSprites(); const preloadedSprites = new Map(); function addToPreloads() { // Preload available sprites to ensure seamless animation replacementSprites.values().forEach((sprite) => { const img = new Image(); img.src = sprite; preloadedSprites.set(sprite, img); }); } addToPreloads(); function doSpriteReplacement(img) { countriesDisabled = window.countriesDisabled; const spriteName = img.src; const result = extractCountryAndPath(spriteName) if (!result) return; const country = result.country; if (countriesDisabled.includes(country)) return; const path = result.path; let unit = country + path; let isBuilding = false; for (let bName of buildingNames) { if (bName == unit.replace(country, "")) { isBuilding = true; break; } } if (isBuilding) unit = getFlatName(country) + path; const replacer = "xx" + path; const desiredSrc = countryReplacementMap[country] ? countryReplacementMap[country] + replacer : null; if (!desiredSrc) return; // Replace the src only if it differs and the sprite is available if (img.src !== desiredSrc && replacementSprites.has((result.gs ? "gs_" : "") + unit)) { img.src = replacementSprites.get((result.gs ? "gs_" : "") + unit); //console.log(`Replaced ${spriteName} with ${img.src}`); } } let debounceTimers = new Map(); const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { const img = mutation.target; if (mutation.type === 'attributes' && mutation.attributeName === 'src') { // Clear any existing timeout for this specific image if (debounceTimers.has(img)) { clearTimeout(debounceTimers.get(img)); } // Create a new debounce timer for this image const timer = setTimeout(() => { doSpriteReplacement(img); // Clean up the timer once the operation is complete debounceTimers.delete(img); }, 10); // Debounce interval // Store the timer for this image debounceTimers.set(img, timer); } if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { // Check if the node is an element (ignores text nodes) if (node.nodeType === Node.ELEMENT_NODE) { // Look for img elements inside the node const images = node.querySelectorAll('img'); images.forEach((img) => { if (isBaseURL(img.src)) return; doSpriteReplacement(img); }); } }); } } }); observer.observe(targetNode, { childList: true, // Detects added or removed child nodes subtree: true, // Detects nodes anywhere within the subtree attributes: true, // Detects changes to attributes (like `src` of images) }); const replaceAllSprites = () => { let images = document.querySelectorAll('#gamemap .game-unit img, #gamemap .game-building img, #calculator .unit-menu img, #calculator .selected-unit.border img, #gamemap-container .unit-info-sprite img, #gamemap-container .terrain-info-sprite img'); // Assuming all the images are within #game-map images.forEach((img) => { doSpriteReplacement(img); }); }; // Call replaceAllSprites to replace images on page load (or wherever appropriate) replaceAllSprites(); const checkInterval = 500; // Check every 500ms function checkSrcChanges() { let images = document.querySelectorAll('#gamemap .game-unit img, #gamemap .game-building img, #calculator .unit-menu img, #calculator .selected-unit.border img, #gamemap-container .unit-info-sprite img, #gamemap-container .terrain-info-sprite img'); // Assuming all the images are within #game-map // console.log("Periodic Src Check!"); // console.log(images.length); const terrainAniPath = 'terrain/ani/'; // The part of the URL we care about images.forEach(img => { if (img.src.includes("hq.gif")) { let a = 0; } if (isBaseURL(img.src)) { let fixed = img.src; for (let country of Object.keys(countryReplacementMap)) { let baseURL = countryReplacementMap[country]; const urlIndex = fixed.indexOf(baseURL); if (urlIndex !== -1) { // Cut everything before "terrain/ani/" and keep everything after it const spritePath = fixed.replace(baseURL, ""); // Get the part of the URL after "terrain/ani/" let spriteName = spritePath.split('/').pop().replace("xx", country); // Get the sprite filename let isBuilding = false; for (let bName of buildingNames) { if (bName == spriteName.replace(country, "")) { isBuilding = true; break; } } if (isBuilding) spriteName = spriteName.replace(country, getFlatName(country)); if (!replacementSprites.has(spriteName) || countriesDisabled.includes(country)) { fixed = terrainAniPath + spriteName; // Set the new sprite path with "terrain/ani/" img.src = fixed; // console.log(`Overridden: ${fixed}`); return; } } } } doSpriteReplacement(img); }); } window.checkSrcChanges = checkSrcChanges; // Call `checkSrcChanges` periodically setInterval(checkSrcChanges, checkInterval); async function loadThisShit() { try { if (typeof Vue === "undefined") { await loadScript("js/vue.js"); } initArmyImporter(); } catch (error) { console.error("Failed to load scripts or initialize:", error); } } window.gameMap = document.getElementById("gamemap") loadThisShit(); console.log("Sprite replacement script with caching is running...");
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址