// ==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...");