您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
[Client Side] Allows users to fetch custom army sprites and use them in game!
// ==UserScript== // @name AWBW Custom Army Importer - Beta 2.3 // @namespace http://tampermonkey.net/ // @version 2.3 // @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 convertToRaw(url) { return url .replace("https://github.com/", "https://raw.githubusercontent.com/") .replace("/tree/", "/refs/heads/") .replace("/blob/", "/"); } 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.lengthgth); // 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; class CustomArmy { name = "Custom Army"; color = "grey"; desc = ""; author = ""; github = ""; logo = ""; // Sprites from an Army will be loaded onto this and refered to when sprites = new Map(); async checkSpritesExist(spriteNames, isBuilding) { const allChecks = []; let baseURL = this.github; const checks = spriteNames.map(spriteName => { const url = baseURL + spriteName; return fetch(url, { method: "HEAD" }) .then(response => ({ spriteName, baseURL: baseURL, 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; } async preload() { if (!this.github) return; const spriteArray = unitSpriteMap; let results = await this.checkSpritesExist(spriteArray); // Process the results results.forEach(({ spriteName, baseURL, exists }) => { if (exists) { this.sprites.set(spriteName, baseURL + spriteName); console.log(`Cached Unit: ${spriteName} for ${this.author}'s ${this.name}`); } else { // console.log(`Not found: ${spriteName}`); } }); const buildingArray = buildingSpriteMap; results = await this.checkSpritesExist(buildingArray, true); // Process the results results.forEach(({ spriteName, baseURL, exists }) => { if (exists) { this.sprites.set(spriteName, baseURL + spriteName); console.log(`Cached Building: ${spriteName} for ${this.author}'s ${this.name}`); } else { // console.log(`Not found: ${spriteName}`); } }); // Logo Fetch let logoURL = this.github + "logo.gif"; fetch(logoURL, { method: "HEAD" }) .then(response => { if (response.ok) { this.logo = logoURL; } else { if (this.github.includes("replace-")) { this.logo = "terrain/ani/" + this.github.slice(this.github.indexOf("replace-")).replace("replace-", "").replace("/", "") + "logo.gif" } } }) .catch(() => { console.warn("Logo not found for: ", logoURL); }); console.log(`Sprites for ${this.author}'s ${this.name} have been preloaded!`); } // Example Link: https://raw.githubusercontent.com/ShinyDeagle/My-Custom-Army/refs/heads/main/ab-rework/ static fromLink(link) { let after = link.replace("https://raw.githubusercontent.com/", "").replace(" ", ""); let splits = after.split("/"); if (splits[splits.length - 1] == "") { splits.splice(splits.length - 1, 1); } if (splits.length <= 2) { return null; } let army = new CustomArmy(); army.name = splits[1].replace("-", " "); army.author = splits[0]; if (splits[splits.length - 1] != "contents") { army.name = army.name = splits[splits.length - 1].replace("-", " "); } army.desc = `${army.name} by ${army.author}.`; army.github = link; if (!army.github.endsWith("/")) army.github += "/"; return army } // Example Link: https://github.com/ShinyDeagle/My-Custom-Army/tree/main/ab-rework static importLink(link) { if (link.includes("raw.github")) return CustomArmy.fromLink(link); let after = link.replace("https://github.com/", "").replace(" ", ""); let splits = after.split("/"); if (splits[splits.length - 1] == "") { splits.splice(splits.length - 1, 1); } if (splits.length <= 2) { return null; } let army = new CustomArmy(); army.name = splits[1].replace("-", " "); army.author = splits[0]; if (splits[splits.length - 1] != "contents") { army.name = splits[splits.length - 1].replace("-", " "); } army.desc = `${army.name} by ${army.author}.`; army.github = convertToRaw(link); if (!army.github.endsWith("/")) army.github += "/"; return army } static fromJson(data) { let army = new CustomArmy(); army.name = data.name; army.desc = data.desc; army.author = data.author; army.github = data.github; return army } toJson() { return { name: this.name, desc: this.desc, author: this.author, github: this.github, } } } let customArmies = []; window.customArmies = customArmies; // Each Army can be mapped to a Country // Replaces the ReplacementMaps thing I used to have. let countryToArmy = new Map(); window.countryToArmy = countryToArmy; 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" }, uw: { id: 26, name: "Umber Wilds", color: "222, 139, 56" } } 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="{ top: position.y + 'px', right: -position.x + 'px', zIndex: '1000', position: 'absolute', cursor: 'pointer',}"> <div class='bordertitle flex' style='color: #fff; background: #06c; border: 1px black solid; padding: 4px; justify-content: space-between;' draggable="true" @dragstart="onDragStart" @dragend="onDragEnd"> <div style="font-weight: bold; display: block; float: left;">Custom Army Importer - Beta 2.3</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; width: 384px;'> <div style="height: 32px; width: 95%; display: flex; gap: 16px; align-items: center; justify-content: center; padding: 8px; background: #0066cc; margin-bottom: 8px;"> <div @click="viewMode = 'Info'" :style="{ cursor: 'pointer', background: viewMode == 'Info' ? '#7ebeff' : '#004d99', color: 'white', padding: '4px', borderLeft: '2px solid rgb(0, 68, 170)', borderRight: '2px solid rgb(0, 68, 170)', width: '100%', }">Info</div> <div @click="viewMode = 'Assign'" :style="{ cursor: 'pointer', background: viewMode == 'Assign' ? '#7ebeff' : '#004d99', color: 'white', padding: '4px', borderLeft: '2px solid rgb(0, 68, 170)', borderRight: '2px solid rgb(0, 68, 170)', width: '100%', }">Assign Army</div> <div @click="viewMode = 'Import'" :style="{ cursor: 'pointer', background: viewMode == 'Import' ? '#7ebeff' : '#004d99', color: 'white', padding: '4px', borderLeft: '2px solid rgb(0, 68, 170)', borderRight: '2px solid rgb(0, 68, 170)', width: '100%', }">Import Army</div> </div> <div v-show="viewMode == 'Info'" 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; width: 100%; 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'>This extension went through a Big 2.0 Update. Use the buttons up top to navigate everywhere.</label> <label style='text-align: left; padding-bottom: 2px'>====================================================</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'>Head to 'Import Army' to add new armies via the link at the bottom.</label> <label style='text-align: left; padding-bottom: 8px'>You need a valid github URL or raw URL to import the army.</label> <strong style='text-align: left; padding-bottom: 2px'>Example URLs:</strong> <label style='text-align: left; padding-bottom: 2px'>https://github.com/ShinyDeagle/My-Custom-Army/tree/main/ab-rework/</label> <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'>====================================================</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> <label style='text-align: left; padding-bottom: 2px'>Afterwards, Head to 'Assign Army' to pick the country and the army you want to replace it with..</label> <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'>====================================================</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> <div v-show="viewMode == 'Assign'" style="display: flex; flex-direction: column; width: 100%;"> <div style="display: flex; width: 100%; align-items: center; justify-content: center; padding: 4px 0px 4px 0px;"> <div style="display: flex; flex-wrap: wrap; width: 100%;"> <img @click="editingArmy = cc" v-for='cc in countries' :style="{ cursor: 'pointer', width: '24px', height: '24px', margin: '1px', border: editingArmy === cc ? '4px solid cyan' : countryToArmy.has(cc) ? '4px solid orange' : '4px solid transparent', }" :src='"terrain/aw1/"+ cc +"logo.gif"' :title="BaseInfo.countries[cc].name"> </div> </div> <hr style="width: 100%"> <div style="display: flex; flex-direction: column; gap: 4px; width: 100%;"> <div style='width: 100%; display: flex; flex-direction: row; align-items: center; gap: 4px;'> <img style='width: 24px; margin: 1px;' :src='"terrain/aw1/"+ editingArmy +"logo.gif"'> <strong style="font-size: 16px">{{BaseInfo.countries[editingArmy].name}}</strong> <img style='width: 24px; margin: 1px;' :src='"terrain/ani/"+ editingArmy +"infantry.gif"'> <img style='width: 24px; margin: 1px;' :src='"terrain/ani/"+ editingArmy +"mech.gif"'> </div> <hr style="width: 100%;"> <div style="width: 100%; text-align: left;">Replaced With: {{ countryToArmy.has(editingArmy) ? countryToArmy.get(editingArmy).name : "None" }}</div> <div v-if='countryToArmy.has(editingArmy)'> <div style='margin-top: 4px; margin-bottom: 0px; display: flex; align-items: end;'> <img style='width: 24px; margin: 1px;' :src='countryToArmy.get(editingArmy).github + "xxinfantry.gif"'> <img style='width: 24px; margin: 1px;' :src='countryToArmy.get(editingArmy).github + "xxmech.gif"'> <img style='width: 24px; margin: 1px;' :src='countryToArmy.get(editingArmy).github + "xxcity.gif"'> <img style='width: 24px; margin: 1px;' :src='countryToArmy.get(editingArmy).github + "xxhq.gif"'> <img style='width: 24px; margin: 1px;' :src='countryToArmy.get(editingArmy).github + "xxlab.gif"'> </div> </div> <hr> <div style="width: 100%; text-align: center; font-size: 12px; font-weight: bold; margin-bottom: 4px; text-align: left;">Pick a Custom Army to replace this one.</div> <div style="display: flex; flex-wrap: wrap; width: 100%; gap: 4px;"> <div @click="countryToArmy.delete(editingArmy); updateArmyMap()" :style="getArmyStyle(null)"> <img width="16" src="terrain/close.png" /> <div>None</div> </div> <div v-for="army in customArmies" :key="army.github" @click="setArmy(editingArmy, army);" :style="getArmyStyle(army)" :title="army.name"> <img :src="army.logo || 'terrain/moveplanner.gif'" style="width: 24px; height: 24px; padding: 2px; margin: 1px;"> <div style="word-wrap: normal; max-width: 64px; font-size: 12px;">{{ army.name }}</div> </div> </div> </div> </div> <div v-show="viewMode == 'Import'" style="display: flex; flex-direction: column; width: 100%;"> <div style="width: 100%; text-align: center; font-size: 20px; font-weight: bold; margin-bottom: 4px;">Imported Custom Armies</div> <div style="width: 100%; text-align: center; font-size: 12px; font-weight: bold; margin-bottom: 4px;">Click on the Icon to Edit the Army</div> <div style="width: 100%; text-align: center; font-size: 12px; font-weight: bold; margin-bottom: 4px;">Import an Army if you don't see anything here.</div> <div style="display: flex; flex-wrap: wrap; width: 100%; gap: 4px;"> <div @click="editingCustomArmy = army" v-for='army in customArmies' :title="army.name" :style="{ display: 'flex', alignItems: 'center', padding: '4px', cursor: 'pointer', border: editingCustomArmy == army ? '2px solid cyan' : '2px solid grey', borderBottom: '2px solid grey',}"> <img @click="editingCustomArmy = army" :style="{ width: '24px', height: '24px', padding: '2px', margin: '1px', }" :src="army.logo ? army.logo : 'terrain/moveplanner.gif'"> <div style="word-wrap: normal; max-width: 64px; font-size: 12px;">{{ army.name }}</div> </div> </div> <hr style="width: 100%"> <div v-show="editingCustomArmy != null" style="display: flex; flex-direction: column; width: 100%; text-align: left;"> <div><span style="font-weight: bold">Name:</span> {{ editingCustomArmy?.name }}</div> <div><span style="font-weight: bold">Description:</span> {{ editingCustomArmy?.desc }}</div> <div><span style="font-weight: bold">Author:</span> {{ editingCustomArmy?.author }}</div> <div><span style="font-weight: bold">Github:</span> {{ editingCustomArmy?.github }}</div> <div style='margin-top: 4px; margin-bottom: 0px; display: flex; align-items: end;'> <img style='width: 24px; margin: 1px;' :src='editingCustomArmy?.github + "xxinfantry.gif"'> <img style='width: 24px; margin: 1px;' :src='editingCustomArmy?.github + "xxmech.gif"'> <img style='width: 24px; margin: 1px;' :src='editingCustomArmy?.github + "xxcity.gif"'> <img style='width: 24px; margin: 1px;' :src='editingCustomArmy?.github + "xxhq.gif"'> <img style='width: 24px; margin: 1px;' :src='editingCustomArmy?.github + "xxlab.gif"'> </div> <div @click="deleteArmy(editingCustomArmy); updateArmyMap(); editingCustomArmy = null" :style="{display: 'flex', alignItems: 'center', padding: '4px', gap: '4px', cursor: 'pointer', width: 'fit-content', marginTop: '16px', color: 'black', background: 'red',}"> <img width="16" src="terrain/close.png" /> <div>Remove Army</div> </div> </div> <hr style="width: 100%"> <div> <div style="width: 100%; text-align: center; font-size: 20px; font-weight: bold; margin-bottom: 4px;">Import Army Via Link</div> <div style="width: 100%; height: 100%; display: flex; flex-direction: column"> <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="importCountryURL($event)" > {{ "Enter Github Folder URL | EG: https://github.com/ShinyDeagle/My-Custom-Army/tree/main/ab-rework" }} </div> </div> </div> </div> </div> </div>`, props: { countries: Array, }, data: function() { return { viewMode: "Info", open: !1, countriesDisabled: [], countryReplacementMap: {}, // Now deprecated, will be used for something else instead. editingArmy: "os", editingCustomArmy: null, countryToArmy: new Map(), customArmies: [], position: { x: 50, y: 100 }, offset: { x: 0, y: 0 }, } }, 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); for (let link of Object.values(data)) { let army = CustomArmy.fromLink(link); if (!army) continue; let skip = false; for (let a of customArmies) { if (a.github == army.github) { skip = true; break; }; } if (skip) continue; army.preload(); customArmies.push(army); console.log(`Loaded: ${army.name} by ${army.author}`); } } let savedArmies = localStorage.customArmies; if (savedArmies) { let data = JSON.parse(savedArmies); for (let github of data) { let army = CustomArmy.fromLink(github); if (!army) continue; let skip = false; for (let a of customArmies) { if (a.github == army.github) { skip = true; break; }; } if (skip) continue; army.preload(); customArmies.push(army); console.log(`Loaded: ${army.name} by ${army.author}`); } } let customArmySettings = localStorage.customArmySettings; if (customArmySettings) { let data = JSON.parse(customArmySettings); for (let country of Object.keys(data)) { let github = data[country]; let army = null; for (let a of window.customArmies) { if (a.github == github) { army = a; break; } } if (!army) continue; countryToArmy.set(country, army); console.log(`Assigned: ${army.name} by ${army.author}`); } } // 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); let armySettingData = {}; for (let entry of countryToArmy.keys()) { let army = countryToArmy.get(entry); let github = army.github; armySettingData[entry] = github; } localStorage.customArmySettings = JSON.stringify(armySettingData); let armyGithubs = []; for (let army of customArmies) { armyGithubs.push(army.github); } localStorage.customArmies = JSON.stringify(armyGithubs); }, 1500); this.updateArmyMap(); }, methods: { onDragStart(event) { // Record the initial mouse position relative to the element this.offset.x = event.clientX - this.position.x; this.offset.y = event.clientY - this.position.y; }, onDragEnd(event) { // Update the position of the element based on the drag this.position.x = event.clientX - this.offset.x; this.position.y = event.clientY - this.offset.y; this.position.x = Math.max(-600, this.position.x); this.position.y = Math.max(0, this.position.y); }, getArmyStyle(army) { this.countryToArmy = countryToArmy; let isSelected = (army === null && !this.countryToArmy.has(this.editingArmy)) || (army !== null && this.countryToArmy.has(this.editingArmy) && this.countryToArmy.get(this.editingArmy)?.github === army.github); return { display: 'flex', alignItems: 'center', padding: '4px', gap: '4px', cursor: 'pointer', border: isSelected ? '2px solid cyan' : '2px solid grey', borderBottom: '2px solid grey' }; }, setArmy(target, army) { countryToArmy.set(target, army); this.updateArmyMap(); this.saveSettings(); }, deleteArmy(army) { let github = army.github; for (let a of countryToArmy.keys()) { if (countryToArmy.get(a).github == github) { countryToArmy.delete(a); } } let index = customArmies.indexOf(army); if (index != -1) customArmies.splice(index, 1); this.saveSettings(); }, updateArmyMap() { // Create a new Map with the same entries to trigger Vue update this.countryToArmy = new Map(countryToArmy); this.customArmies = customArmies.slice(); }, preloadSprites(country) { window.preloadCountrySprites(country, this.countryReplacementMap); }, importCountryURL(event) { const newContent = event.target.innerText.trim(); // Get text content if (!newContent) return; let army = CustomArmy.importLink(newContent); if (!army) return; for (let a in window.customArmies) { if (a.github == army.github) return; } army.preload(); customArmies.push(army); this.updateArmyMap(); this.saveSettings(); }, } }); 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/"; const documentFile = "config.json"; // 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", ]; const weatherNames = [ "_rain.gif", "_snow.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") let buildingSpriteMap = buildingMap.slice(); window.unitSpriteMap = unitSpriteMap; window.buildingSpriteMap = buildingSpriteMap; for (let building of buildingSpriteMap.slice()) { for (let weatherEffect of weatherNames) { buildingSpriteMap.push(building.replace(".gif", weatherEffect)); } } function doSpriteReplacement(img) { const spriteName = img.src; if (spriteName.includes("neutral")) return; let result = extractCountryAndPath(spriteName) if (!result) { let c = img.getAttribute("country"); if (c == null) return; result = { country: c, path: spriteName.slice(spriteName.indexOf("xx")).replace("xx", ""), gs: spriteName.includes("/ani/gs_") || spriteName.includes("gs_xx"), } } const country = result.country; if (countriesDisabled.includes(country)) return; let path = result.path; let target = country + path; if (path.includes(country)) { target = result.path; path = path.replace(country, ""); } let isBuilding = false; for (let bName of buildingNames) { if (target.replace(country, "").includes(bName.replace(".gif", ""))) { isBuilding = true; break; } } if (isBuilding) target = getFlatName(country) + path; const replacer = "xx" + path; const army = countryToArmy.get(country); const desiredSrc = army ? army.github + replacer : null; if (!desiredSrc) return; // Replace the src only if it differs and the sprite is available if (img.src !== desiredSrc) { if (army.sprites.has((result.gs ? "gs_" : "") + replacer)) { img.src = army.sprites.get((result.gs ? "gs_" : "") + replacer); } else if (img.getAttribute("weather")) { let weatherless = replacer; for (let weather of weatherNames) { weatherless = weatherless.replace(weather, ".gif"); } if (army.sprites.has((result.gs ? "gs_" : "") + weatherless)) { img.src = army.sprites.get((result.gs ? "gs_" : "") + weatherless); } else { img.src = "terrain/ani/" + target; } } else { img.src = "terrain/ani/" + target; } //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') { const img = mutation.target; const parent = img.parentElement; if (parent && parent.classList.contains("game-building") && parent.classList.contains("s")) { if (!img.src.includes("xx")) { let result = extractCountryAndPath(img.src); if (result) { img.setAttribute("country", result.country); } let weather = false; for (let weatherEffect of weatherNames) { if (img.src.includes(weatherEffect)) { weather = true; img.setAttribute("weather", weatherEffect.replace("_", "").replace(".gif", "")); } } if (!weather) img.setAttribute("weather", null); } } // 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 (img.getAttribute("country") == null) { let result = extractCountryAndPath(img.src) if (!result) return; const country = result.country; img.setAttribute("country", country); } if (img.getAttribute("country") == null) return; if (img.src.includes("xx")) 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-container img, #calculator img'); images.forEach((img) => { let result = extractCountryAndPath(img.src) if (!result) return; const country = result.country; img.setAttribute("country", country); }); images.forEach((img) => { if (img.getAttribute("country") == null) return; 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-container img, #calculator img'); // console.log("Periodic Src Check!"); // console.log(images.lengthgth); const terrainAniPath = 'terrain/ani/'; // The part of the URL we care about images.forEach(img => { if (img.src.includes("xx")) { let fixed = img.src; let country = img.getAttribute("country"); if (country == null) return; let path = fixed.slice(fixed.indexOf("xx")).replace("xx", country); let isBuilding = false; for (let bName of buildingNames) { if (path.replace(country, "").includes(bName.replace(".gif", ""))) { isBuilding = true; break; } } if (isBuilding) path = path.replace(country, getFlatName(country)); if (!countryToArmy.has(country)) { fixed = terrainAniPath + path; // Set the new sprite path with "terrain/ani/" img.src = fixed; if (img.getAttribute("weather")) img.src + "_" + img.getAttribute("weather") + ".gif"; // 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或关注我们的公众号极客氢云获取最新地址