SettlersCombatSimulator_MissingGenerals

Modifies the request to add the missing generals in the Settlers Online Combat Simulator.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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

})();