Modifies the request to add the missing generals in the Settlers Online Combat Simulator.
// ==UserScript== // @name SettlersCombatSimulator_MissingGenerals // @namespace http://tampermonkey.net/ // @version 1.2 // @description Modifies the request to add the missing generals in the Settlers Online Combat Simulator. // @author [email protected] // @match *://*.settlerscombatsimulator.com/* // @grant none // @run-at document-start // @connect www.settlerscombatsimulator.com // @license MIT // ==/UserScript== (function() { 'use strict'; const IMG_NAR = "data:image/png;base64,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"; const IMG_BRO = "data:image/png;base64,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"; const IMAGE_MAP = { "assets/img/uniticons/IMG_GEN_NAR.PNG": IMG_NAR, "assets/img/uniticons/IMG_GEN_BRO.PNG": IMG_BRO, }; function attachImageFallbacks(root = document) { root.querySelectorAll('img').forEach(img => { const fallback = IMAGE_MAP[img.getAttribute('src')]; if (fallback && img.src !== fallback) { img.onerror = function() { this.src = fallback; this.onerror = null; // zapobiega pętli }; } }); } function createGeneralBrohman() { return { "id": "Brohmann", "name": "Brohmann, The Raider", "shortName": "", "description": "General Brohmann added by TamperMonkey script", "priority": 936, "hitPoints": 500, "hitDamage": 3000, "missDamage": 1500, "accuracy": 0.7, "experience": 0, "icon": "IMG_GEN_BRO.PNG", "value": 0, "isWeak": false, "isElite": false, "isGeneral": true, "isPlayer": true, "timeBonus": 300, "maxUnits": 125, "sortIndex": 1, "strike": 0, "combatantType": "CombatGeneral", "isBoss": false, "combatModifier": [ { "name": "Player", "item": "MaxAttackDamage", "type": "Cavalry", "chance": 1, "adder": 30, "channel": "specialist" }, { "name": "Player", "item": "MinAttackDamage", "type": "Cavalry", "chance": 1, "adder": 30, "channel": "specialist" }, { "name": "Player", "item": "MaxAttackDamage", "type": "Knight", "chance": 1, "adder": 30, "channel": "specialist" }, { "name": "Player", "item": "MinAttackDamage", "type": "Knight", "chance": 1, "adder": 30, "channel": "specialist" }, { "name": "Player", "item": "GainSkill_Flanking", "type": "Knight", "chance": 1, "channel": "specialist" } ], "skillSplashDamage": true, "skillAttackWeakestFirst": true, "skillIgnoreAC": false, "skillName": "Trait_CombatGeneral", "modifierCombatXPMultiplier": 1.2 }; }; function createGeneralNarcissistic() { //<GeneralRecoverySpeed multiplier="0.75" /> return { "id": "Narcissistic", "name": "Narcissistic General", "shortName": "", "description": "General Narcissistic added by TamperMonkey script", "priority": 1113, "hitPoints": 250, "hitDamage": 6000, "missDamage": 5400, "accuracy": 0.7, "experience": 0, "icon": "IMG_GEN_NAR.PNG", "value": 0, "isWeak": false, "isElite": false, "isGeneral": true, "isPlayer": true, "timeBonus": 300, "maxUnits": 10, "sortIndex": 1, "strike": 0, "combatantType": "CombatGeneral", "isBoss": false, "combatModifier": [ { "name": "Player", "item": "CombatXP", "type": "CombatGeneral", "chance": 1, "multiplier": 0.2, "channel": "specialist" }, { "name": "Player", "item": "InstantRecover", "type": "CombatGeneral", "value": 1, "chance": 1.0, "channel": "specialist" }, { "name": "Enemy", "item": "UnitHP", "type": "Boss", "chance": 1, "multiplier": 0.75, "channel": "specialist" } ], "skillSplashDamage": false, "skillAttackWeakestFirst": true, "skillIgnoreAC": false, "skillName": "Trait_NarcissisticGeneral", "modifierCombatXPMultiplier": 0.2 }; }; // funtion injects the unit into the JSON function injectUnitIntoJson(jsonObj) { try { // check if structure matches expected if (jsonObj && jsonObj.data && jsonObj.data[0] && Array.isArray(jsonObj.data[0].units)) { jsonObj.data[0].units.push(createGeneralBrohman()); jsonObj.data[0].units.push(createGeneralNarcissistic()); console.log(`$====== Successfully injected Generals into JSON response.`); return jsonObj; } else { console.warn(`$====== JSON structure mismatch. Could not find data[0].units`, jsonObj); return jsonObj; // return original } } catch (e) { console.error(`$====== Error during injection logic:`, e); return jsonObj; } } const originalXMLHttpRequest = window.XMLHttpRequest; window.XMLHttpRequest = function() { const xhr = new originalXMLHttpRequest(); const send = xhr.send; xhr.send = function() { this.addEventListener("readystatechange", function() { // verify URL and state if (this.readyState === 4 && this.responseURL && this.responseURL.includes("/api/init")) { console.log(`====== XHR intercepted: ${this.responseURL}`); try { const originalText = this.responseText; let json = JSON.parse(originalText); // injection json = injectUnitIntoJson(json); const newText = JSON.stringify(json); Object.defineProperty(this, 'responseText', { get: () => newText }); Object.defineProperty(this, 'response', { get: () => newText }); } catch (err) { console.error(`====== XHR modification failed:`, err); } } }, false); return send.apply(this, arguments); }; return xhr; }; console.log(`====== Injector loaded and watching for /api/init`); attachImageFallbacks(); const observer = new MutationObserver(() => attachImageFallbacks()); observer.observe(document.documentElement, { childList: true, subtree: true }); })();