Userscripty - tutorial

Môžete kecať o čom len chcete ... :D
User avatar
ySpirit
Posts: 151
Joined: Wed 26. Feb 2014 19:20:57

Userscripty - tutorial

Post by ySpirit »

Vzhledem k tomu, ze nekteri by radi meli presety na eventy a nechce se jim vecne vyklikavat jednotky z boje, tady je (pomerne snadne) reseni v podobe userscriptu.

Snazil jsem se to napsat jednoduse, aby to mohl pouzit kazdy.
Nerucim vam za predmety, co si rozflakate, protoze nekdy poslete utok se slabou armadou (treba si nekde rozbijete stit a pak poslete malou armadu bez hlavniho stitu) :mrgreen:
Nicmene, pokud jsme 100% konfidentni, ze eventy dame, muzeme snadno nastavit, jaka armada se posle...

Jak zacit?
Staci si stahnout extension Tampermonkey (nejpopularnejsi manazer na vytvareni a pouzivani userscriptu) - nahore si vyberte, jaky prohlizec pouzivate.
Kousek pod odkazy na stahovani je video, jak nainstalovat.

Krok 1 - vytvoreni user scriptu
Staci kliknout na rozsireni (extension) Tampermonkey pravym tlacitkem mysi, a rozkliknout "Create a new user script..."

Krok 2 - vyplnovani dokumentace userscriptu
Z noveho userscriptu muzeme klidne vse smazat, dokumentaci staci vyplnit takto:

Code: Select all

// ==UserScript==
// @name         Land of Ice - eventy demon
// @version      0.1
// @description  Upravy poctu poslanych jednotek do demonich eventu ve hre Land of Ice
// @author       tady se podepiste
// @include      *.landofice.com/utok.php?utok=eventy_demon
// ==/UserScript==
Vysvetlivky:
name je nazev user scriptu
author - kdo to napsal
include - na jake strance se userscript spusti (hvezdicky znamenaji, ze pred/po tom muze byt cokoliv - dalsi priklad treba *.landofice.com/utok.php?utok=chram (script se pusti na vsech chramech, svatynich, domech)

Krok 3 - kopirovani funkci
Do tohoto userscriptu zkopirujeme dolu tyto funkce, pro snadnejsi praci.
Vsechny maji u sebe nejakou dokumentaci.

Code: Select all

/**
 * Ziska nadpis eventu, podle ktereho se rozlisuji treba chramy, demoni eventy a podobne
 */
function getTitle() {
    var head = document.getElementsByTagName('h2');
    return head[0].getInnerHTML();
}

/**
 * Zkontroluje, zdali jednotka se zadanym unit_id vubec existuje (aby se napr. nevypnula cela armada pred posilanim)
 * @param {int} unit_id - id jednotky, napr 1024
 */
function doesUnitWithIdExist(unit_id) {
    var input = document.getElementById("num_" + unit_id);
    return input != null;
}

/**
 * Nastavi pocet jednotek na value k jednotce s IDckem unit_id
 * @param {int} unit_id - id jednotky, napr 1024
 * @param {int} value - zadany pocet jednotek, napr 1000
 */
function setValueToInputByUnitId(unit_id, value) {
    setValueToInput("num_" + unit_id, value);
}

/**
 * Nastavi pocet jednotek na value k jednotce s celym IDckem element_id
 * @param {string} element_id - id elementu, napr "num_1024"
 * @param {int} value - zadany pocet jednotek, napr 1000
 */
function setValueToInput(element_id, value) {
    var input = document.getElementById(element_id); // ziska input s tezkym katapultem na mem klanu
    if (input != null) {
        input.value = value; // nastavi pocet jednotek
    }
}

/**
 * Vypne predmet pro jednotku s ID unit_id
 * @param {int} unit_id - id jednotky, napr 1024
 */
function disableItemByUnitId(unit_id) {
    disableItem("predmet_" + unit_id);
}

/**
 * Vypne predmet na element_id
 * @param {string} element_name - jmeno elementu, napr "predmet_1024"
 */
function disableItem(element_name) {
    var input = document.getElementsByName(element_name)[0];
    if (input != null) {
        input.checked = false; // vypne predmet
    }
}

/**
 * Vynuluje vsechny inputy (idealni pro dreaddy, co chteji farmit s nekromancery jednotky)
 */
function emptyAllInputs() {
    emptyAllInput();
}

/**
 * Odesle formular a rovnou zautoci BEZ KLIKNUTI NA POTVRZENI UTOKU (doporucuji nepouzivat, max pokud chcete treba usetrit klik u vesnice, osady po nejake dobe)
 */
function attack() {
    document.forms[0].submit();
}
Krok 4 - pouziti / nastavovani vyplnovani jednotek
Funkce uz mame, staci je jen pouzit.
Abychom tyto funkce mohli pouzit, potrebujeme ziskat jeste par veci.
1) Nejdriv zkontrolujeme, ze jsme na spravnem klanu - pouzijeme ID nejake jednotky (idealne jednotku, co budete mit 100% v klanu, ne velitele, co se muze nekde spalit a pak se zapomene ho vratit zpatky) - na pouziti existuje funkce doesUnitWithIdExist(unit_id).
unit_id se ziska pres inpect stranky - proste otevrete nejaky event, kdyz jste v tabulce, kliknete pravym tlacitkem na nejaky input, vyberte inspect
otevre se zdrojovy kod s tim, ze by melo ukazovat na input. Tento input bude mit name="id_CISLO" a id="num_CISLO" - proste opiste nebo zkopirujte cislo - toto se pouziva ve funkcich jako unit_id (jak pro kontrolu jednotky, tak pro vyplnovani inputu a nebo odskrtavani predmetu).
2) Titulek - ten se ziska jednoduse pres metodu getTitle() - nic vic neni potreba
3) Porovnani titulku s nazvu eventu, na ktere chceme upravovat armadu
4) Uprava poctu jednotek, odskrtavani predmetu (kdyz se treba nechce riskovat, ze se jednotka s malym poctem spali a riskuje se ztrata, nebo kdyz zombici maji tvrzene platy a nemuzou se hybat)
5) Po dokonceni staci zmacknout ctrl+s pro ulozeni scriptu (a nebo rozkliknout File - save).

Kdyz bych treba vzal sveho Dreadda a chtel si mirne upravit armadu, co se posila na obetiste, kod, co se prida do stale stejneho userscriptu, bude vypadat takto:

Code: Select all

if (doesUnitWithIdExist(1411)) { // zkontrolovani, ze jsme na spravne armade, kdyz by se vynechalo, script by zbytecne vyhledaval elementy, co neexistuji

    var title = getTitle(); // ziskani titulku

    if (title == "Obětiště") { // event je obetiste, upravime armadu, aby stitovali Upiri
        setValueToInputByUnitId(1024, 8000); // vesnicani
        setValueToInputByUnitId(1025, 2000); // zombie
        disableItemByUnitId(1025); // vypnout tvrzene platy
        setValueToInputByUnitId(1029, 20000); // kostlivci
        disableItemByUnitId(1029); // vypnout prapor stinu
        setValueToInputByUnitId(2574, 10000); // ghulove
    } else if (title == "Hlídka zrádců") {
        // vyplnit armadu
    } else if (title == "Karavana démonů") {
        // vyplnit armadu
    }
}
Pote se zobrazi v zalozce Installed Userscripts - tam je moznost usercript kdykoliv vypnout (napr. se klan rozflaka, ztrati upiry, co by meli stitovat, nebude potreba a tak)
Nove userscripty pak jde pridavat pres [+] a nebo pres rozkliknuti extensiony a "Create a new script..."

Pokud by se chtel scriptik udelat chytrejsi s pocty podle hlavniho stitu, slo by udelat treba takto:

Code: Select all

if (doesUnitWithIdExist(1411)) { // zkontrolovani, ze jsme na spravne armade
    if (title == "Chrám Ledu" || title == "Svatyně Ledu" || title == "Dóm Ledu" || title == "Æthrova Svatyne" || title == "Svatyně Ohně") {
        var upiri_input = document.getElementById("num_4053"); // ziskame input, na kterem jsou upiri
        var hlavni_stit_hp = upiri_input.value * 40 * 0.5; // veme se pocet upiru, vynasovi se 40 (vychozi HP upiru) a pak hodi na polovinu (rezerva, kdyby neco dostatecne sundalo upiry, jinak je risk, ze by jednotky probili ostatni, co maji nizkou obranu a zbytecne by se ztratily predmety)

        setValueToInputByUnitId(1024, Math.floor(hlavni_stit_hp/7-10)); // vesnicani
        setValueToInputByUnitId(1025, Math.floor(hlavni_stit_hp/30)); // zombie
        disableItemByUnitId(1025); // vypnout tvrzene platy
        setValueToInputByUnitId(1029, Math.floor(hlavni_stit_hp/2-5)); // kostlivci
        setValueToInputByUnitId(2574, Math.floor(hlavni_stit_hp/4-15)); // ghulove
    }
}
Nazvy eventu kopirujte ze stranky a nebo si muzete upravit funkci getTitle, aby na konci bylo return title.toLowerCase() - cely retezec bude malymi pismenky. Nazvy eventu, pokud budete kopirovat ode me, si radsi overte :D
Pokud budete mit stejnou armadu na vice eventu, muzete hodit do podminky title == "nazev" || title == "nazev2" - pak se nemusi rozepisovat pro kazdy zvlast.

Kdyby neco nebylo dostatecne jasne, ptejte se, poradim. :)
A kdyby mel nekdo pripominky, jak neco vylepsit, at klidne napise, ja tomu nedal zrovna moc casu :mrgreen:
User avatar
nezmar
Administrátor
Posts: 1603
Joined: Mon 27. Jan 2014 7:42:38

Re: Userscripty - tutorial

Post by nezmar »

svojho času som používal na firefox greasemonkey rozšírenie. Dokonca som spravil aj skript pre Land Of Ice kde som menil vizuál hry (ešte skôr než som tam začal robiť sám zmeny).
Pokiaľ sa mi podarí nájsť ten skript tak by som ho sem zavesil :D
doon
Posts: 6
Joined: Thu 31. May 2018 0:50:45

Re: Userscripty - tutorial

Post by doon »

Tak já se taky podělím.

Tento userscript přidá tlačítko "Armáda" pro zkopírování klanové armády do clipboardu (pak můžete jednoduše vložit do simulátoru).

Funguje na Hlavní stránce, v Klanové armádě a ve špehování můžete zkopírovat armádu jiného klanu (ale bez předmětů).

Code: Select all

// ==UserScript==
// @name         LoI Army Clipboard
// @version      0.2
// @description  Copy army to clipboard
// @author       doon
// @match        http://*.landofice.com/main.php*
// @match        http://*.landofice.com/clanarmy
// @match        http://*.landofice.com/spying/*
// @grant        GM.xmlHttpRequest
// @grant        GM.setClipboard
// ==/UserScript==

(function() {
    'use strict';

    // Simulátor má v některých jednotkách překlepy a některé nezná
    // Vlevo je "oficiální" název jednotky, vpravo je název, který se vloží do schránky
    const unitNameReplacements = {
        "Topázový Golem": "Topazový Golem",
        "Příklad neznámá jednotka": null,
    };

    // --------------------------------------------------------------------------------------

    let unitName = function(text) {
        if (text.indexOf("velitel klanu") !== -1) {
            return null;
        }
        return text in unitNameReplacements ? unitNameReplacements[text] : text;
    };

    let unitCount = function(text) {
        // remove commas
        return text.replace(/,/g, "");
    };

    let armyFromClanarmy = function(dom) {
        let army = [];
        let rows = dom.getElementsByTagName("tr");
        for (let i = 1; i < rows.length; ++i) {
            let unit = unitName(rows[i].children[1].firstChild.textContent);
            if (!unit) {
                continue;
            }

            let count = unitCount(rows[i].children[2].textContent);
            let line = count + " x " + unit;

            // add item if unit has one
            let itemElem = rows[i].children[1].querySelector(".predmet");
            if (itemElem) {
                line += " (" + itemElem.textContent + ")";
            }

            army.push(line);
        }
        return army.join("\n");
    };

    let armyFromSpying = function(dom) {
        let army = [];
        let rows = dom.querySelectorAll(".equip-items-table.t-r tr");
        for (let i = 1; i < rows.length; ++i) {
            let unit = unitName(rows[i].children[0].textContent);
            if (!unit) {
                continue;
            }

            let count = unitCount(rows[i].children[1].textContent);
            let line = count + " x " + unit;

            army.push(line);
        }
        return army.join("\n");
    };

    let getArmyHtml = function(callback) {
        GM.xmlHttpRequest({
            method: "GET",
            url: "/clanarmy",
            onload: function(response) {
                let parser = new DOMParser();
                let dom = parser.parseFromString(response.responseText, "text/html");
                callback(dom);
            }
        });
    };

    let copyArmyToClipboard = function() {
        let button = this;
        button.className = "";
        let onSuccess = function(army) {
            button.className = "new-msg";
            GM.setClipboard(army);
        };

        // Main page - send XHR request to /clanarmy
        if (location.href.indexOf("/main.php") !== -1) {
            getArmyHtml(dom => onSuccess(armyFromClanarmy(dom)));
        }
        // Clan army page
        else if (location.href.indexOf("/clanarmy") !== -1) {
            onSuccess(armyFromClanarmy(document));
        }
        // Spying page
        else if (location.href.indexOf("/spying") !== -1) {
            onSuccess(armyFromSpying(document));
        }
    };

    let elem = document.createElement("a");
    elem.href = "javascript:void(0)";
    elem.onclick = copyArmyToClipboard;
    elem.textContent = "Armáda";
    elem.title = "Zkopíruje klanovou armádu do schránky";

    let target = document.getElementsByClassName("menu-right")[0];
    target.style = "width: 300px; background-size: 100% 70px;";
    target.insertBefore(elem, target.children[0]);

})();
User avatar
ySpirit
Posts: 151
Joined: Wed 26. Feb 2014 19:20:57

Re: Userscripty - tutorial

Post by ySpirit »

Tlacitka na nastaveni dani pro klan nalevo v menu (nad odkazy valka, pruzkum okolo a pustina), protoze chodit do klanoveho hospodarstvi a tam vecne davat alt + sipka doprava / alt + sipka doleva je otravne.
Jednoduchy klik na tlacitko a dane se prepnou.

Image

Code: Select all

// ==UserScript==
// @name         Land of Ice zmeny
// @version      0.1
// @description  Menu
// @author       yS
// @match        *://*.landofice.com/main.php*
// ==/UserScript==


modifyMenu();


// FUNCTIONS


function modifyMenu() {
    let elements = document.getElementsByClassName("action-list");
    if (elements == null) {
        return;
    }
    let left_menu = elements[0];
    let index = 0;

    let li = document.createElement("li"), a, button;
    li.innerText = "Daně:";
    left_menu.insertBefore(li, left_menu.children[index++]);
    let vat_li = li;

    li = document.createElement("li");
    button = document.createElement("button");
    button.innerText = "Snížit ";
    button.onclick = function() { changeVat("klanstats.php?dane=snizit", vat_li, "snížené") };
    li.append(button);

    a = document.createElement("a");
    button = document.createElement("button");
    button.innerText = "Normální ";
    button.onclick = function() { changeVat("klanstats.php?dane=normal", vat_li, "normální") };
    li.append(button);

    a = document.createElement("a");
    button = document.createElement("button");
    button.innerText = "Navýšit";
    button.onclick = function() { changeVat("klanstats.php?dane=zvysit", vat_li, "zvýšené") };
    li.append(button);

    left_menu.insertBefore(li, left_menu.children[index++]);

    li = document.createElement("li");
    li.classList.add("empty");
    left_menu.insertBefore(li, left_menu.children[index++]);

}

function changeVat(url, vat_li, text) {
    httpGetAsync(url, vatChanged, vat_li, text);
}

function vatChanged(vat_li, text) {
    vat_li.innerText = "Daně (" + text + "):";
}

function httpGetAsync(theUrl, callback, vat_li, text) {
    let xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function() {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
            callback(vat_li, text);
        }
    }
    xmlHttp.open("GET", theUrl, true); // true for asynchronous
    xmlHttp.send(null);
}
User avatar
ySpirit
Posts: 151
Joined: Wed 26. Feb 2014 19:20:57

Re: Userscripty - tutorial

Post by ySpirit »

Po uprave danich jsem si rekl, co kdyby se usnadnilo a urychlilo klikani o neco vice.
Cim se na hre nejvice plytva casem?
Proklikavanim eventu.

A tak jsem se rozhodl, ze do hry pridam moznost rychlych utoku.
Rychle utoky zautoci na event, jako byste jej sami otevreli a bez nejake zmeny odeslali do utoku.
Tzv. silne klany (nebo klidne i slabe), co vi, ze nejake eventy davaji bez problemu, nemusi zbytecne travit nekolik vterin utocenim na event.

Jak to funguje?
  • Vedle eventu se prida extra tlacitko "rychle zaútočit"
  • Kliknutim na toho tlacitko se automaticky odesle cela armada do eventu a zautoci se.
  • Vysledek eventu se vypise normalne v hlavnim okne pod rekrut jednotek / nad eventy.
  • Take pridano do menu tlacitko na rychly utok na mesto.
  • Oblehani demonem ma take tlacitko na rychly utok.
  • + Uprava tlacitek dani z predchoziho komentu (uz nejsou tak hrozne vypadajici tlacitka). :D
  • 0.1.5 - pridan chytry rekrut (inputy povoluji x* znaky. V priprade, ze na inputech jsou, se hodnota nasobi. Tzv 1600*5 => 9000, co se odesle)
  • 0.1.5 - chytry rekrut funguje stejne, jako rychle utoky. Posle se na pozadi, vysledek se vyhodi do okna. Neni potreba potom klikat na pruzkum pustiny, jestli jste prozkoumavali pred tim, proste F5 a pokracujeme v tahani :D
  • 0.1.5 - Protoze uz toho zacina byt vic, pridany nastaveni, co vypnout (editaci leveho menu, rychle utoky, zmenu dani nebo zmeny rekrutu), kdyby se neco nelibilo.
  • 0.1.6 - Chytre inputy nyni berou i K (nasobi tisicem) a M (nasobi milionem). 20m = 20.000.000.
  • 0.1.6 - Moznost pouzit desetinna cisla. 19.5M = 19.500.000. Nebo treba 0.5k = 500
  • 0.1.6 - Chytry rekrut rozdelen na upravu inputu a na asynchroni rekrut (jenom odesilani)
  • 0.1.6 - Asynchroni rekrut uz i refreshuje suroviny (lidi, zlato, runy) a maximalni pocet, co se muze do budov jeste dat.
  • 0.1.6 - Chytre inputy upraveny i na trzisti (moznost vypnout)
  • 0.1.6.4 - Opravy a pridana moznost zobrazit shrnuti ztrat (a ziskanych jednotek) u normalnich utoku
  • 0.1.7 - Pridano tlacitko do nastaveni pro obnovu poctu aktualnich tahu u klanu
  • 0.1.8 - Stranka s vybavenim - select nezobrazuje jednotky, co uz nejake vybaveni maji
  • 0.1.9 - Pridana moznost zastavit / povolit produkci jednotek v levem menu (pod dane, nad valku / dobyti mesta)
  • 0.1.10 - Velitel klanu se nyni automaticky vraci do armady, kdyz padne v boji
Co chybi?
  • Ztraty predmetu pri ztrate celeho stacku.
  • Ziskavani predmetu, kdyz spadnou z nepratel
  • Zmeny jsou pouze na hlavni strance hry. V pleneni / dobyvani / valce / alianci nejsou. Script pridan ruzne do stranek, kde se pouziva (nastaveni - tlacitko na aktualizaci kol vsech klanu, utoceni - vypisuje ztraty + vraci velitele zpatky, trh - chytre inputy, vybaveni klanu - schovani jednotek s vybavenim)
Jestli pouzivate script na kopirovani armady, musi mit nizsi # cislo v tampermonkey, jinak se horni menu muze spatne vykreslit. Pokud jste ho pridali pred mymi modifikacemi, vse bude v poradku.
V opacnem pripade:
1. Rozkliknout si tampermonkey
2. rozkliknout si nainstalovane userscripty (Installed Userscripts)
3. kliknout na #, aby bylo serazene od nejnizsiho cisla po nejvyssi.
4. objevi se sloupecek Sort
5. Najdete LoI Army Clipboard a pretahnete ho pomoci sloupecku sort (kurzor se tam zmeni), nad ten muj.
6. Pak by se melo vse nahrat v perfektnim poradku


Instalace:
Snadna instalace jednim kliknutim
Nebo zdrojak (prilozen dole)

Jak to vypada v akci:
Image
Image

Kdyby mel Nezmar zajem, muze zmeny pridat do hry.
Je to ciste javascript, castecne spaghety, protoze ze zacatku byla mensi vize.
Ale neco z toho by se snad dalo pouzit. :mrgreen:

Zdrojak:

Code: Select all

// ==UserScript==
// @name         Land of Ice modifikace
// @version      0.1.10
// @description  Menu, rychle utoky, dane, dobyti mesta
// @author       yS
// @match        *://*.landofice.com/main.php*
// @match        *://*.landofice.com/market*
// @match        *://*.landofice.com/utok*
// @match        *://*.landofice.com/settings*
// @match        *://*.landofice.com/klanarmy_vybaveni*
// @grant        GM_setValue
// @grant        GM_getValue
// @namespace    https://greasyfork.org/users/1005892
// @icon         https://www.google.com/s2/favicons?sz=64&domain=landofice.com
// ==/UserScript==

// Constants for disabling certain parts of the script
const ZMENIT_MENU = "edit_menu";
const PRIDAT_DANE = "add_vat";
const PRIDAT_DOBYT_MESTO = "add_city";
const PRIDAT_NASTAVENI_PRODUKCE = "add_recruit_buttons";
const PRIDAT_RYCHLE_UTOKY = "add_quick_attacks";
const PRIDAT_SMART_REKRUT = "add_smart_recruit";
const POUZIT_ASYNC_REKRUT = "async_recruit";
const PRIDAT_SMART_MARKET = "add_smart_market";
const ZOBRAZIT_ZTRATY_V_UTOKU = "add_attack_losses";

const REFRESH_CLAN_URL = "/settings/changeClan/";
const REFRESH_MAIN = "/main.php?obnovit";

let configuration_showing = 0;
let active_clan_id = null;

if (GM_getValue(PRIDAT_SMART_MARKET, true) && location.href.indexOf("market") != -1) {
    modifyMarket();
    return;
}

if (location.href.indexOf("utok") != -1) {
    if (GM_getValue(ZOBRAZIT_ZTRATY_V_UTOKU, true) == false) {
        return;
    }

    let konec = document.getElementById("konec");
    if (konec != null) {
        let results_div = addBattleResult(document, false, false);

        let br = document.createElement("br");
        results_div.append(br);
        br = document.createElement("br");
        results_div.append(br);

        konec.append(results_div);
    }
    return;
}

if (location.href.indexOf("settings") != -1) {
    modifySettings();
    return;
}

if (location.href.indexOf("klanarmy_vybaveni") != -1) {
    processEquipment();
    return;
}

if (GM_getValue(ZMENIT_MENU, true)) {
    modifyMenu();
}
if (GM_getValue(PRIDAT_RYCHLE_UTOKY, true)) {
    modifyBattles();
}
if (GM_getValue(PRIDAT_SMART_REKRUT, true) || GM_getValue(POUZIT_ASYNC_REKRUT, true)) {
    modifyRecruit();
}
addConfigurationButton();


// FUNCTIONS

// MARKET
function modifyMarket() {
    let elements = document.querySelectorAll("input[type='number']");
    if (elements.length == 0) {
        elements = document.querySelectorAll("input[type='text']");
    }

    for (let i = 0; i < elements.length; i++) {
        let input = elements[i];
        input.type = "text";
        input.addEventListener("input", filterField);

        input.form.addEventListener("submit", e => {
            e.preventDefault();
            processFinalValueOfInput(input);
            if (Math.floor(parseInt(input.value)) == 0) {
                input.value = null;
                return;
            }
            input.form.submit();
        });
    }
}
// MARKET END

// RECRUIT
/**
 * Modifies the buildings. Changes recruit from refreshing the page, to sending the form through XHR
 */
function modifyRecruit() {
    // modify the form
    for (let i = 0; i < document.forms.length; i++) {
        const form = document.forms[i];
        if (form.action.indexOf("changeClan") != -1) {
            continue;
        }

        let input = form.bu_kolik;
        if (input == null) {
            console.log("Chybi input na pocet vybaveni");
            continue;
        }

        if (GM_getValue(PRIDAT_SMART_REKRUT, true)) {
            input.type = "text";
            input.value = null;
            input.addEventListener("input", filterField);
        }

        form.addEventListener("submit", e => {
            e.preventDefault();
            processFinalValueOfInput(input);
            if (Math.floor(parseInt(input.value)) == 0) {
                input.value = null;
                return;
            }
            if (GM_getValue(POUZIT_ASYNC_REKRUT, true)) {
                handleForm(form, recruitCallback);
                input.value = null;
                input.blur();
            } else {
                form.submit();
            }
        });
    }

    // if not using async recruit, skip modifying the anchors
    if (GM_getValue(POUZIT_ASYNC_REKRUT, true) == false) {
        return;
    }

    // modify the anchors
    let elements = document.querySelectorAll(".recrut_btn a");
    for (let i = 0; i < elements.length; i++) {
        let element = elements[i];
        let action = element.href;
        element.href = "javascript:void(0)";

        let form = element.parentElement.nextElementSibling;

        element.addEventListener("click", e => {
            httpPostAsync(action, null, recruitCallback, form);
        });
    }
}

function processFinalValueOfInput(input) {
    input.value = input.value.toLowerCase().replace(",", ".").replace("k", "*1000").replace("m", "*1000000");
    let values = input.value.split(/[*x]+/);
    let value = 0;
    if (values.length == 1) {
        value = values[0];
    } else {
        value = 1;
        for (let i = 0; i < values.length; i++) {
            value *= parseFloat(values[i]);
        }
    }
    input.value = value;
}

function filterField(e) {
    let t = e.target;
    let badValues = /[^0-9xkm,.*]/gi;
    t.value = t.value.replace(badValues, '');
}

function recruitCallback(dom, form) {
    let elements = dom.getElementsByClassName("odehraj-ostatni");
    if (elements.length != 1) {
        return;
    }

    let div = document.createElement("div");
    div.innerText = elements[0].innerText;

    let added_value = elements[0].innerText.split(" x");
    added_value = added_value[0].substring(added_value[0].lastIndexOf(" "));
    added_value = parseInt(added_value);

    appendToOdtah(div);
    updateMaterials(dom);

    let values = form.parentElement.children[0].innerText.split(" ");

    form.parentElement.children[0].innerText = "Celkem vybavení: " + (parseInt(values[values.length-1]) + added_value);
}

function updateMaterials(dom) {
    let original_element = document.getElementsByClassName("klan-stats")[0];
    let updated_element = dom.getElementsByClassName("klan-stats")[0];
    original_element.parentElement.replaceChild(updated_element, original_element);

    original_element = document.getElementsByClassName("klan-source")[0];
    updated_element = dom.getElementsByClassName("klan-source")[0];
    original_element.parentElement.replaceChild(updated_element, original_element);

    let original_elements = document.querySelectorAll(".odehraj-budovy-akce .build .data p:NOT(.delimit)");
    let updated_elements = dom.querySelectorAll(".odehraj-budovy-akce .build .data p:NOT(.delimit)");
    for (let i = 0; i < original_elements.length; i++) {
        original_elements[i].parentElement.replaceChild(updated_elements[i], original_elements[i]);
    }
}


// RECRUIT END

// QUICK BATTLES

/**
 * Adds a button for quick battles
 */
function modifyBattles() {
    let links_node_list = document.querySelectorAll(".odehraj:NOT(.odehraj-budovy-akce, .odehraj-odtah, .odehraj-new-stavby, .odehraj-valka) a"); // prophet, new buildings and buildings
    let links = Array.prototype.slice.call((links_node_list));

    let defend_city_link = document.querySelector(".odehraj.odehraj-valka a:nth-of-type(2)"); // sieged city
    if (defend_city_link != null) {
        links.push(defend_city_link);
    }

    for (let i = 0; i < links.length; i++) {
        addQuickBattle(links[i]);
    }
}

function quickAttackRenderBattleResult(dom) {
    let div = addBattleResult(dom, true, true);
    if (div !== null) {
        appendToOdtah(div);
    }
}

/**
 * Renders all of the battle results. The losses, new units, experience gained and rewards.
 * @param HTMLDocument dom
 */
function addBattleResult(dom, show_rewards = true, show_exp = true) {
    let div = document.createElement("div");
    let br = document.createElement("br");
    div.append(br);

    if (show_rewards) {
        let konec = dom.getElementById("konec");

        if (konec == null) {
            return null;
        }

        for (let i=1;; i++) {
            konec = konec.nextSibling;

            if (konec == null) {
                break;
            }

            let element = konec.cloneNode(true);
            div.append(element);
        }
    }

    const results = processLosses(dom);
    const losses_map = results[0];
    const experience_element = results[1];

    if (losses_map != null) {
        renderLosses(losses_map, div);
    }

    if (experience_element != null && show_exp) {
        div.append(experience_element);
    }
    return div;
}

/**
 * Renders losses / gained units
 * @param  Map losses_map
 * @param  HTMLDivElement div
 */
function renderLosses(losses_map, div) {
    let losses_div = document.createElement("div");
    let reincarnated_div = document.createElement("div");
    let gained_units = false;
    let lost_units = false;
    let p;

    losses_map.forEach((count, unit) => {
        p = document.createElement("p");
        p.innerText = Math.abs(count) + " x " + unit;
        if (count < 0) {
            gained_units = true;
            reincarnated_div.append(p);
        } else {
            losses_div.append(p);
            lost_units = true;
        }
    });

    if (lost_units == true) {
        p = document.createElement("p");
        p.style.color = "red";
        p.innerText = "Ztráty";
        losses_div.prepend(p);
        div.append(losses_div);
    }

    if (gained_units) {
        p = document.createElement("p");
        p.style.color = "green";
        p.innerText = "Získáno";
        reincarnated_div.prepend(p);
        div.append(reincarnated_div);
    }
}

function appendToOdtah(div) {
    let odehraj_odtah = document.getElementsByClassName("odehraj-odtah");
    if (odehraj_odtah.length == 1) {
        odehraj_odtah = odehraj_odtah[0];
        odehraj_odtah.append(div);
    }
}

function getUnitData(element) {
    let unit_data = element.innerText.replaceAll(",", "").split(" x ");
    if (unit_data.length == 1) {
        return null;
    }
    let unit_name = unit_data[1].split(" (");
    return [unit_data[0], unit_name[0]];
}

function processLosses(dom) {
    let konec = dom.getElementById("konec");
    let elements = konec.querySelectorAll(".utocnik p");

    let ztraty_hodnota_text = elements[elements.length-1].innerText;
    ztraty_hodnota_text = ztraty_hodnota_text.split(" / ");
    ztraty_hodnota_text = ztraty_hodnota_text[0].split(": ")[1];

    if (parseInt(ztraty_hodnota_text) == 0) {
        return [null, null];
    }

    const units_map = new Map();
    let losses_map = new Map();

    // BEFORE BATTLE
    let army_elements = dom.querySelectorAll(".armady p.utocnik");
    for (let i = 0; i < army_elements.length; i++) {
        let unit_data = getUnitData(army_elements[i]);

        let original_count = units_map.get(unit_data[1]);
        if (original_count == null) {
            // in case more stacks of the same unit exist (general of old imperium)
            original_count = 0;
        }

        units_map.set(unit_data[1], original_count + parseInt(unit_data[0]));
    }

    // AFTER BATTLE
    let experience_element = null;

    for (let i = 0; i < elements.length; i++) {
        if (elements[i].children[0] == null || elements[i].children[0].className != 'privolane') {
            let unit_data = getUnitData(elements[i]);
            if (unit_data == null) {
                if (elements[i].innerText.indexOf("Za tuhle slavnou bitvu ziskava velitel") != -1) {
                    experience_element = elements[i].cloneNode(true);
                    experience_element.style.color = "chartreuse";
                }
                continue;
            }

            let original_count = units_map.get(unit_data[1]);
            units_map.delete(unit_data[1]);
            if (original_count == null) {
                // unit was not there at the beginning => added by necromancy
                original_count = 0;
            }

            if ((original_count - unit_data[0]) != 0) {
                let current_count = losses_map.get(unit_data[1]);
                if (current_count == null) {
                    current_count = original_count;
                }
                current_count = current_count - parseInt(unit_data[0]);
                if (current_count == 0) {
                    losses_map.delete(unit_data[1]);
                } else {
                    if (unit_data[1].indexOf("velitel klanu") != -1) {
                        // Commander is dead -> add back to army
                        httpGetAsync("/clanarmy/addCommander", null);
                        losses_map.set("Velitel klanu se vrátil zpátky do armády", -1);
                    }
                    losses_map.set(unit_data[1], current_count);
                }
            }
        }
    }

    if (losses_map.size == 0) {
        losses_map = null;
    }

    return [losses_map, experience_element];
}

function attack(response) {
    let responseText = response.responseText;
    let dom = new DOMParser().parseFromString(responseText, "text/html");

    handleForm(dom.forms[0], quickAttackRenderBattleResult);
}

function addQuickBattle(link) {
    let anchor = newAnchor("(Rychle zaútočit)", "Provede útok okamžitě, bez otevírání oken");
    addClickEventListener(anchor, link.href, attack);
    link.parentElement.insertBefore(anchor, link.nextSibling);
}

function addClickEventListener(element, url, callback) {
    element.addEventListener("click", e => {
        httpGetAsync(url, callback, null);
    });
}

// QUICK BATTLES END

// MENU

function modifyMenu() {
    if (!GM_getValue(PRIDAT_DANE, true) && !GM_getValue(PRIDAT_DOBYT_MESTO, true) && !(GM_getValue(PRIDAT_NASTAVENI_PRODUKCE, true))) {
        return;
    }

    let elements = document.getElementsByClassName("action-list");
    if (elements == null) {
        return;
    }
    let left_menu = elements[0];
    let index = 0;
    const sees_valka = left_menu.children[index].innerText == "Válka";

    if (GM_getValue(PRIDAT_DANE, true) && sees_valka) {
        index = addVatModificationToMenu(left_menu, index);
    }

    if (GM_getValue(PRIDAT_NASTAVENI_PRODUKCE, true)) {
        index = addRecruitButtonsToMenu(left_menu, index);
    }

    if (GM_getValue(PRIDAT_DOBYT_MESTO, true) && sees_valka) {
        let anchor = newAnchor("Dobýt město", "Okamžitě dobyje město bez nových oken");
        addClickEventListener(anchor, "utok.php?utok=mesto&", attack);

        let li = document.createElement("li");
        li.append(anchor);
        left_menu.insertBefore(li, left_menu.children[index++]);
    }
}

// MENU (VAT)

function addVatModificationToMenu(left_menu, index) {
    let li = document.createElement("li");
    li.innerText = "Daně:";
    left_menu.insertBefore(li, left_menu.children[index++]);
    let vat_li = li;

    li = document.createElement("li");
    left_menu.insertBefore(li, left_menu.children[index++]);

    let anchor = newAnchor("<<", "Snížit daně");
    anchor.onclick = function() { changeValueAction("klanstats.php?dane=snizit", vat_li, "Daně (snížené):") };
    li.append(anchor);

    anchor = newAnchor(" --- ", "Normální daně");
    anchor.onclick = function() { changeValueAction("klanstats.php?dane=normal", vat_li, "Daně (normální):") };
    li.append(anchor);

    anchor = newAnchor(">>", "Zvýšit daně");
    anchor.onclick = function() { changeValueAction("klanstats.php?dane=zvysit", vat_li, "Daně (zvýšené):") };
    li.append(anchor);

    // separate Dane from other menu options
    li = document.createElement("li");
    li.classList.add("empty");
    left_menu.insertBefore(li, left_menu.children[index++]);

    return index;
}

// MENU (RECRUIT BUTTONS)

function addRecruitButtonsToMenu(left_menu, index) {
    let li = document.createElement("li");
    li.innerText = "Produkce jednotek:";
    left_menu.insertBefore(li, left_menu.children[index++]);
    let recruit_li = li;

    li = document.createElement("li");
    left_menu.insertBefore(li, left_menu.children[index++]);

    let anchor = newAnchor("Stop", "Zastavit rekrut");
    anchor.onclick = function() { changeValueAction("klanstats.php?produkce=stop", recruit_li, "Produkce jednotek (Zastavená):") };
    li.append(anchor);

    anchor = newAnchor("Start", "Povolit rekrut");
    anchor.onclick = function() { changeValueAction("klanstats.php?produkce=start", recruit_li, "Produkce jednotek (Povolená):") };
    li.append(anchor);

    // separate recruit from other menu options
    li = document.createElement("li");
    li.classList.add("empty");
    left_menu.insertBefore(li, left_menu.children[index++]);

    return index;
}

// MENU END

// Menu actions

function changeValueAction(url, vat_li, text) {
    httpGetAsync(url, valueChanged, {vat_li: vat_li, text : text});
}

function valueChanged(xmlHttp, params) {
    params.vat_li.innerText = params.text;
}

// VAT END

// CONFIG

function addConfigurationButton() {
    let elem = document.createElement("a");
    elem.href = "javascript:void(0)";
    elem.addEventListener("click", renderSettings);
    elem.textContent = "Mód";
    elem.title = "Nastavení modifikací LoI";

    let target = document.getElementsByClassName("menu-right")[0];
    target.insertBefore(elem, target.children[0]);
    const width = target.children.length * 75;
    target.style = "width: " + width + "px; background-size: 100% 70px;";
}

function renderSettings() {
    if (configuration_showing == 2) {
        configuration_showing = 1;
        document.getElementById("mod_configuration").classList.add("hide");
        return;
    }
    let div;
    if (configuration_showing == 1) {
        configuration_showing = 2;
        document.getElementById("mod_configuration").classList.remove("hide");
        return;
    }

    configuration_showing = 2;
    div = document.createElement("div");
    div.id = "mod_configuration";

    let p = document.createElement("p");
    p.innerText = "Změny se provedou po obnovení stránky. Po změnách v levém menu stiskněte \"Obnovit\"";
    div.append(p);
    div.append(document.createElement("br"));

    p = document.createElement("p");
    p.innerText = "Chytrý rekrut povoluje znaky x* v inputech. \nNapř. 1863*5 = 9315. \n";
    div.append(p);

    div.append(document.createElement("br"));
    div.append(document.createElement("br"));

    appendConfigurationInputToDiv(div, ZMENIT_MENU, "Povolit úpravy levého menu");
    appendConfigurationInputToDiv(div, PRIDAT_DANE, "Přidat změnu daní do levého menu");
    appendConfigurationInputToDiv(div, PRIDAT_NASTAVENI_PRODUKCE, "Přidat nastavení produkce (povolení / zastavení rekrutování)");
    appendConfigurationInputToDiv(div, PRIDAT_DOBYT_MESTO, "Přidat dobytí města do levého menu");
    appendConfigurationInputToDiv(div, PRIDAT_RYCHLE_UTOKY, "Povolit rychlé utoky u eventů");
    appendConfigurationInputToDiv(div, PRIDAT_SMART_REKRUT, "Povolit chytrý rekrut - použití x* pro násobení, k = násobení tisícem, m = násobení miliónem a ',.' pro desetinná čísla.");
    appendConfigurationInputToDiv(div, POUZIT_ASYNC_REKRUT, "Použít asynchroní rekrut - bez obnovení stránky");
    appendConfigurationInputToDiv(div, PRIDAT_SMART_MARKET, "Použít chytré tržiště - použití x* pro násobení, k = násobení tisícem, m = násobení miliónem a ',.' pro desetinná čísla.");
    appendConfigurationInputToDiv(div, ZOBRAZIT_ZTRATY_V_UTOKU, "Zobrazit přehled ztrát (a získaných jednotek) v normalních útocích.");

    appendToOdtah(div);
}

function appendConfigurationInputToDiv(div, input_id, label_text) {
    let input = document.createElement("input");
    input.id = input_id;
    input.type = "checkbox";
    input.style.verticalAlign = "middle";
    input.checked = GM_getValue(input_id, true);
    input.addEventListener("change", e => {
        GM_setValue(input.id, input.checked);
    });

    let label = document.createElement("label");
    label.htmlFor = input.id;
    label.innerText = label_text;
    label.style.marginLeft = "5px";

    let container = document.createElement("div");
    container.append(input);
    container.append(label);
    div.append(container);
}

// CONFIG END

// SETTINGS

function modifySettings() {
    let headers = document.getElementsByTagName("h3");
    if (headers.length == 0) {
        return;
    }

    // add refresh button
    let refresh_button = document.createElement("a");
    refresh_button.href = "javascript:void(0)";
    refresh_button.addEventListener("click", refreshClans);
    refresh_button.textContent = "Obnovit tahy";
    refresh_button.title = "Obnoví počet tahů u klanů";
    refresh_button.classList.add("button");

    headers[0].parentElement.insertBefore(refresh_button, headers[0].nextSibling);
}

function refreshClans() {
    let clan_id_elements = document.querySelectorAll("td.t-c");
    let active_clan_id_element = document.querySelector(".active td.t-c");
    active_clan_id = active_clan_id_element.innerText;

    let clan_ids = [];
    for (let i = 0; i < clan_id_elements.length; i++) {
        clan_ids.push(clan_id_elements[i].innerText);
    }

    // settings, refresh turns for all clans, set active the clan that was active
    refreshNextClan(null, clan_ids);
}

function refreshNextClan(xmlHttp, clan_ids) {
    if (clan_ids == null || clan_ids.length == 0) {
        if (active_clan_id != null) {
            httpGetAsync(REFRESH_CLAN_URL + active_clan_id, refreshMain, null);
            active_clan_id = null;
        } else {
            location.reload();
        }
        return;
    }

    let clan_id = clan_ids[0];
    clan_ids.shift();

    httpGetAsync(REFRESH_CLAN_URL + clan_id, refreshMain, clan_ids);
}

function refreshMain(xmlHttp, clan_ids) {
    httpGetAsync(REFRESH_MAIN, refreshNextClan, clan_ids);
}

//

// EQUIPMENT

function processEquipment() {
    let table = document.getElementsByClassName("equip-items-table")[1];
    if (table == null) {
        console.log("missing table");
        return;
    }
    if (document.forms[0] == null) {
        console.log("missing form");
        return;
    }

    let units_with_equipment = [];

    for (let i = 0; i < table.rows.length; i++) {
        if (table.rows[i].children[1].innerText != "žádné") {
            units_with_equipment.push(table.rows[i].children[0].innerText);
        }
    }

    for (let i = 0; i < document.forms[0].jednotka.options.length; i++) {
        let option = document.forms[0].jednotka.options[i];
        if (units_with_equipment.includes(option.innerText.trim())) {
            option.classList.add("hide");
        }
    }
}

// EQUIPMENT END

// GENERAL FUNCTIONS

function newAnchor(text, title) {
    let element = document.createElement("a");
    element.style.marginLeft = "5px";
    element.innerText = text;
    element.href = "javascript:void(0)";
    if (title != null) {
        element.title = title;
    }
    return element;
}

function handleForm(form, func) {
    let data = new FormData(form);
    let params = new URLSearchParams(data).toString();

    httpPostAsync(form.action, params, func, form);
}

function httpPostAsync(theUrl, params, callback, form) {
    let xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function() {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
            let responseText = xmlHttp.responseText;
            let dom = new DOMParser().parseFromString(responseText, "text/html");
            callback(dom, form);
        }
    }
    xmlHttp.open("POST", theUrl, true); // true for asynchronous
    xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xmlHttp.send(params);
}

function httpGetAsync(theUrl, callback, params) {
    let xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function() {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
            if (callback != null) {
                callback(xmlHttp, params);
            }
        }
    }
    xmlHttp.open("GET", theUrl, true); // true for asynchronous
    xmlHttp.send(null);
}

// GENERAL FUNCTIONS END
Last edited by ySpirit on Tue 21. Mar 2023 22:04:46, edited 10 times in total.
User avatar
Hron
Posts: 1130
Joined: Mon 08. Feb 2016 16:16:11

Re: Userscripty - tutorial

Post by Hron »

Zkouším ten script na rychlé útoky a je to opravdu příjemné a děkuji za něj.

Jen pokud ho budeš dodělávat, tak bych tě chtěl poprosit, aby jsi nepřidával rychlý útok pro plenění. Vše ostatní je ulehčení pro hraní, které neovlivňuje ostatní hráče a tak proti nim nic nemám. Ale pokud se to zavede pro plenění, které je kompetitivní a bude tak hráčům, kteří použijí tuto "externí pomůcku" výhodou oproti ostatním hráčům, tak to už dle mě nebude správné.
User avatar
Hron
Posts: 1130
Joined: Mon 08. Feb 2016 16:16:11

Re: Userscripty - tutorial

Post by Hron »

Nevím proč, ale při těžších bojích se mi ukazuje, že jsem získat Generála i když jsem žádného nezískal (a ani neztratil). Děje se to od doby, kdy jsem získal jednoho od věštce.

Image
User avatar
ySpirit
Posts: 151
Joined: Wed 26. Feb 2014 19:20:57

Re: Userscripty - tutorial

Post by ySpirit »

Do pleneni neplanuju, by se muselo s kazdym kliknutim nacist stranku pleneni a to by mohlo byt otravny a zbytecna zatez serveru navic.
Jedine na pevno hodit do stranky odkazy na ta pleneni, ale to by se pak nevedelo, jestli je pleneni volne, takze by to bylo zbytecnych x tlacitek navic. :)

Co se tyce generala, tak asi vim, v cem muze byt chyba.

edit - opraveno, bylo to tim, ze jich ma klan vice. Do mapy se hodil vzdy jen jeden, ke konci se pak stacky scitaji.
Zdrojak na predchozim prispevku jsem editoval a na odkazu je update.

Kdyby psalo porad, kliknout na tampermonkey, v ni utilities a v menu check for updates.
Jestli se neaktualizuje samo, ale to je asi jednou za den.
User avatar
ySpirit
Posts: 151
Joined: Wed 26. Feb 2014 19:20:57

Re: Userscripty - tutorial

Post by ySpirit »

Dalsich par modifikaci.
Vsechno je ve velkem postu:
ySpirit wrote: Sat 18. Feb 2023 1:16:55...
Pridan chytry rekrut (inputy povoluji x* znaky. V priprade, ze na inputech jsou, se hodnota nasobi. Tzv 1600*5 => 9000, co se odesle. Muze se nasobit vickrat, nemusi byt jen jednou)
Chytry rekrut funguje stejne, jako rychle utoky. Posle se na pozadi, vysledek se vyhodi do okna. Neni potreba potom klikat na pruzkum pustiny, jestli jste prozkoumavali pred tim => staci F5

Pridana nastaveni, pro vypinani nezadoucich zmen (editaci leveho menu, rychle utoky, zmenu dani nebo zmeny rekrutu), kdyby se neco nelibilo.


Image
Image
User avatar
Hron
Posts: 1130
Joined: Mon 08. Feb 2016 16:16:11

Re: Userscripty - tutorial

Post by Hron »

Ten rekrut na první pohled vypadá zajímavě, ale protože se po něm neaktualizují tvoje zdroje (runy, zlato, lidi, ...), tak pro mě osobně je nepraktický.
Byla by možnost, že by se při tom tvém vkládání zásob do budov "aktualizovala" stránka stejně, jako když vkládáš zásoby normálně?
Post Reply