hwmSetsMaster

Меню наборов армии, навыков и оружия. Смена фракции.

当前为 2024-01-31 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           hwmSetsMaster
// @author         Tamozhnya1
// @namespace      Tamozhnya1
// @description    Меню наборов армии, навыков и оружия. Смена фракции.
// @version        10.0
// @include        *heroeswm.ru/*
// @include        *lordswm.com/*
// @exclude        */rightcol.php*
// @exclude        */ch_box.php*
// @exclude        */chat*
// @exclude        */ticker.html*
// @exclude        */frames*
// @exclude        */brd.php*
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_deleteValue
// @grant 		   GM.xmlHttpRequest
// @license        MIT
// ==/UserScript==

const playerIdMatch = document.cookie.match(/pl_id=(\d+)/);
if(!playerIdMatch) {
    return;
}
const PlayerId = playerIdMatch[1];
const windowObject = window.wrappedJSObject || unsafeWindow;
const lang = document.documentElement.lang || (location.hostname == "www.lordswm.com" ? "en" : "ru");
const isEn = lang == "en";
const Strings = {
        "ru": {
            Army: ustring("Армия"),
            Save: ustring("Сохранить"),
            Add: ustring("Добавить"),
            AddCurrent: ustring("Добавить текущий"),
            SetName: ustring("Наименование набора"),
            Delete: ustring("Удалить"),
            Talents: ustring("Навыки"),
            Weapon: ustring("Оружие"),
            RemoveAll: ustring("Снять все"),
            EnterJpg: "enter0.jpg",
            SignInTitle: "Войти",
            Castle: "Замок",
            Task: ustring("Задание"),
            Apply: ustring("Применить"),
            EnterButtonValue: ustring("ВОЙТИ"),
            AvailablePoints: "Свободных очков",
            AvailableTalentPoints: "Свободных очков от навыка",
            IncreaseManyPointsTooltip: "Введите число от ${minValue} до ${maxValue}. Нажмите Tab."
        },
        "en": {
            Army: "Army",
            Save: "Save",
            Add: "Add",
            AddCurrent: "Add current",
            SetName: "Set name",
            Delete: "Delete",
            Talents: "Talents",
            Weapon: "Weapon",
            RemoveAll: "Un-equip all",
            EnterJpg: "enter0_eng.jpg",
            SignInTitle: "Sign in",
            Castle: "Castle",
            Task: "Task",
            Apply: "Apply",
            EnterButtonValue: "ENTER",
            AvailablePoints: "Available points",
            AvailableTalentPoints: "Available talent points",
            IncreaseManyPointsTooltip: "Enter number from ${minValue} to ${maxValue}. Press Tab."
        }
    };
const LocalizedString = Strings[lang];
const isMobileInterface = document.querySelector("div#btnMenuGlobal") ? true : false;
const isMobileDevice = mobileCheck(); // Там нет мышки
const isNewInterface = document.querySelector("div#hwm_header") ? true : false;
const isNewPersonPage = document.querySelector("div#hwm_no_zoom") ? true : false;
if(!this.GM_getValue) {
    this.GM_getValue = function(key, def) { return localStorage[key] || def; };
    this.GM_setValue = function(key, value) { localStorage[key] = value; };
    this.GM_deleteValue = function(key) { return delete localStorage[key]; };
}
let Fraction;
getFraction();

const homeArtsPanelSelector = doc => isNewPersonPage ? doc.querySelector("div#inv_doll_stats") : getParent(doc.querySelector("div.arts_info.shop_art_info"), "table");
const homeStatsPanelSelector = doc => isNewPersonPage ? doc.getElementById("home_css_stats_wrap_div") : getParent(doc.querySelector("img[src*='attr_attack']"), "table", 2);
const homeArmyPanelSelector = doc => isNewPersonPage ? doc.querySelector("div.home_pers_army") : doc.querySelector("center > div > div.cre_creature72").parentNode;
const playerInfoArtsPanelSelector = doc => getParent(doc.querySelector("div[class^='slot']"), "div");
const playerInfoStatsPanelSelector = doc => getParent(doc.querySelector("img[src*='attr_attack']"), "table");
const playerInfoArmyPanelSelector = doc => doc.querySelector("center > div > div.cre_creature72").parentNode;
const playerInfoPerksPanelSelector = doc => getParent(doc.querySelector("a[href^='showperkinfo.php']"), "table", 2);
const mapHuntButtonsPanelSelector = doc => doc.querySelector("div#neut_right_block div.map_buttons_container");
const mapHuntButtons2PanelSelector = doc => doc.querySelector("div#neut_right_block2 div.map_buttons_container");
const mapMercenaryTaskPanelSelector = doc => getParent(doc.querySelector("div#map_right_block_inside > table.wbwhite.rounded_table.map_table_margin center a[href='mercenary_guild.php']"), "center");
const inventoryStatsPanelSelector = doc => doc.querySelector("div.inventory_stats");

/************************************************************************************************************/
const weaponSetsPreferences = {
    menuTitle: LocalizedString.Weapon,
    menuImage: "https://dcdn3.heroeswm.ru/i/mobile_view_ny/icons/_panelInventory.png?v=3.23de65",
    setReferencePage: "inventory.php",
    sets: new Array(),
    menuItems: {},
    currentMenuItem: undefined,
    refreshingPages: "home.php;inventory.php;pl_info.php;map.php",
    updatePanelHandler: async function() {
        if(location.pathname == '/home.php') {
            refreshUpdatePanels([homeArtsPanelSelector, homeStatsPanelSelector], hintProcessor);
            return true;
        }
        if(location.pathname == '/pl_info.php') {
            if(getUrlParamValue(location.href, "id") == PlayerId) {
                refreshUpdatePanels([playerInfoArtsPanelSelector, playerInfoStatsPanelSelector], hintProcessor);
            }
            return true;
        }
        if(location.pathname == '/map.php') {
            refreshUpdatePanels([mapHuntButtonsPanelSelector, mapHuntButtons2PanelSelector, mapMercenaryTaskPanelSelector], hintProcessor);
            return true;
        }
        return false;
    },
    // refreshingConditions: function() {
        // if(location.pathname == '/map.php') {
            // return (document.querySelector("#neut_right_block") || document.querySelector("div#map_right_block_inside > table.wbwhite.rounded_table.map_table_margin center a[href='mercenary_guild.php']")) ? true : false;
        // }
        // return false;
    // },
    initSetsApplyAction: function() {
        this.sets.length = 0;
        this.sets.push({ number: 0, name: LocalizedString.RemoveAll, method: "GET", url: "inventory.php?all_off=100" });
        const weaponSets = JSON.parse(getPlayerValue("WeaponSets", "{}"));
        for(const setNumber in weaponSets) {
            this.sets.push({ number: setNumber, name: weaponSets[setNumber], method: "GET", url: `inventory.php?all_on=${setNumber}`, headers: null });
        }
    },
    getCurrentSetName: function() { return "WeaponSet"; },
    onPageLoad: function() {
        if(/inventory.php/.test(location.href)) {
            const undressDiv = document.querySelector("div[id ='undress_all_div']");
            addSetChangerListener(undressDiv, this, 0);

            const setDivs = document.querySelectorAll("div[set_div_id]"); // Если setDivs.length = 0, то ничего не делаем - мы в заявке на бой
            if(setDivs.length > 0) {
                const weaponSets = {};
                for(const setDiv of setDivs) {
                    const setNumber = setDiv.getAttribute("set_div_id");
                    if(setDiv.hasAttribute("onclick")) {
                        weaponSets[setNumber] = setDiv.innerText;
                        addSetChangerListener(setDiv, this, setNumber);
                    }
                }
                setPlayerValue("WeaponSets", JSON.stringify(weaponSets));
            }
        }
        this.findSetChangersAndAddListener();
    },
    findSetChangersAndAddListener: function() {
        let setRefs = document.querySelectorAll("a[href^='inventory.php?all_off=100']");
        for(const setRef of setRefs) {
            addSetChangerListener(setRef, this, 0);
        }
        setRefs = document.querySelectorAll("a[href*='inventory.php?all_on=']");
        for(const weaponSetReference of setRefs) {
            let setNumber = weaponSetReference.getAttribute("href").split("all_on=")[1];
            addSetChangerListener(weaponSetReference, this, setNumber);
        }
    }
};
/***********************************************************************************************************/
const skillSetsPreferences = {
    menuTitle: LocalizedString.Talents,
    menuImage: "https://dcdn.heroeswm.ru/i/perks/2xleadership1.png",
    setReferencePage: "skillwheel.php",
    sets: new Array(),
    menuItems: {},
    currentMenuItem: undefined,
    refreshingPages: "skillwheel.php;pl_info.php;home.php;inventory.php",
    updatePanelHandler: async function() {
        if(location.pathname == '/home.php') {
            refreshUpdatePanels(homeStatsPanelSelector, hintProcessor);
            getPerksToHomeCore();
            return true;
        }
        if(location.pathname == '/pl_info.php') {
            if(getUrlParamValue(location.href, "id") == PlayerId) {
                refreshUpdatePanels([playerInfoStatsPanelSelector, playerInfoPerksPanelSelector], hintProcessor);
            }
            return true;
        }
        if(location.pathname == '/inventory.php') {
            refreshUpdatePanels(inventoryStatsPanelSelector, hintProcessor);
            return true;
        }
        return false;
    },

    onPageLoad: function() {
        const setRefs = document.querySelectorAll("a[href^='skillwheel.php?setuserperk']"); // skillwheel.php?setuserperk=1&prace=4&buildid=5 // skillwheel.php?rand=1&setstats=1&param0=20&param1=8&param2=0&param3=2
        const pageSets = {};
        for(const setRef of setRefs) {
            pageSets[setRef.innerHTML] = getUrlParamValue(setRef.href, "buildid");
            addSetChangerListener(setRef, this, parseInt(getUrlParamValue(setRef.href, "buildid")));
        }
        setPlayerFractionValue("SkillSets", JSON.stringify(pageSets));
    },
    initSetsApplyAction: function () {
        const skillSets = JSON.parse(getPlayerFractionValue("SkillSets", "{}"));
        this.sets = Object.keys(skillSets).map(x => { return { number: parseInt(skillSets[x]), name: x, method: "GET", url: `/skillwheel.php?setuserperk=1&prace=${Fraction}&buildid=${skillSets[x]}` }; });
    },
    getCurrentSetName: function() { return getFractionKey("SkillSet"); }
};
/************************************************************************************************************/
const armySetsPreferences = {
    menuTitle: LocalizedString.Army,
    menuImage: "https://dcdn.heroeswm.ru/i/castle_im/btn_recruit.png",
    setReferencePage: "army.php",
    sets: new Array(),
    menuItems: {},
    currentMenuItem: undefined,
    refreshingPages: "home.php;army.php;pl_info.php",
    updatePanelHandler: async function() {
        if(location.pathname == '/home.php') {
            refreshUpdatePanels(homeArmyPanelSelector, hintProcessor);
            return true;
        }
        if(location.pathname == '/pl_info.php') {
            if(getUrlParamValue(location.href, "id") == PlayerId) {
                refreshUpdatePanels(playerInfoArmyPanelSelector, hintProcessor);
            }
            return true;
        }
        return false;
    },
    setsTable: null,
    getCurrentSetName: function() { return getFractionKey("ArmySet"); },
    initSetsApplyAction: function () {
        const armySetsData = JSON.parse(getPlayerFractionValue("ArmySets", "{}"));
        const armySets = Object.keys(armySetsData).map(x => { const data = armySetsData[x].split("|"); return { Id: x, Name: data[0], Army: data.slice(1).map(y => Number(y)) } });
        this.sets = armySets.map(x => { return { number: parseInt(x.Id), name: x.Name, title: x.Army.join("+"), method: "POST", url: "/army_apply.php", data: x.Army.map((x, i) => `countv${i + 1}=${x}`).join("&"), contentType: "application/x-www-form-urlencoded" }; });
        if(/\/army.php/.test(location.href)) {
            this.drawSetsTable(armySets);
        }
    },
    drawSetsTable: function(armySets) {
        const isInit = this.setsTable ? false : true;
        let container;
        if(isInit) {
            const hwm_for_zoom = document.getElementById("hwm_for_zoom");
            container = document.body;
            if(!isMobileInterface) {
                container = addElement("center", hwm_for_zoom);
            }
            const tableStyle = "background-color: #959595; border: 1px solid #f5c137; table-layout: fixed;" + (isMobileInterface ? "margin: 0 0 0 50px;" : "");
            this.setsTable = addElement("table", container, { style: tableStyle });
        } else {
            this.setsTable.innerHTML = "";
            container = this.setsTable.parentNode;
        }

        const cellWidths = [100, 60, 60, 60, 60, 60, 60, 60, 10, 10];
        for(const cellWidth of cellWidths) {
            this.setsTable.innerHTML += `<col style="width: ${cellWidth}px;" />`;
        }
        this.drawTableHeader();
        for(const armySet of armySets) {
            this.drawSetRow(armySet);
        }
        if(isInit) {
            const saveButton = addElement("input", container, { type: "button", value: LocalizedString.Save, style: isMobileInterface ? "margin: 0 0 0 50px;" : "" });
            saveButton.addEventListener("click", this.saveSets);
            const addCurrentButton = addElement("input", container, { type: "button", value: LocalizedString.AddCurrent });
            addCurrentButton.addEventListener("click", function() { armySetsPreferences.drawSetRow(); });
        }
    },
    drawTableHeader: function () {
        if(!this.setsTable) {
            return;
        }
        let cellStyle = "border: 1px solid #f5c137; overflow: hidden;";
        let units = windowObject.obj;
        let tr = addElement("tr", this.setsTable);
        addElement("td", tr, { innerHTML: LocalizedString.SetName.replace(/ /g, "<br>").replace(/-/g, "-<br>"), style: "font-weight: bold; font-size: 8pt; text-align: center; border: 1px solid #f5c137; overflow: hidden;" }); // ; overflow: hidden
        for(let i = 1; i <= 7; i++) {
            addElement("td", tr, { innerHTML: units[i]['name'].replace(/ /g, "<br>").replace(/-/g, "-<br>"), style: "font-weight: bold; font-size: 8pt; text-align: center; border: 1px solid #f5c137; overflow: hidden;", onClick: `ChangeSlider(event, ${i}, 0);` });
        }
        addElement("td", tr, { style: cellStyle });
        addElement("td", tr, { style: cellStyle });
    },
    drawSetRow: function(armySet) {
        const isNew = armySet ? false : true;
        const units = windowObject.obj;
        //console.log(`armySet: ${armySet}, units: ${units}`);
        //console.log(units.slice(1));
        armySet = armySet || { Id: (new Date()).getTime(), Name: "", Army: [units[1], units[2], units[3], units[4], units[5], units[6], units[7]].map(x => x.nownumberd) };
        let cellStyle = "border: 1px solid #f5c137; overflow: hidden;";
        if(!this.setsTable) {
            return;
        }
        let tr = addElement("tr", this.setsTable, { setId: armySet.Id });
        let td = addElement("td", tr, { style: cellStyle });
        let input = addElement("input", td, { value: armySet.Name, onfocus: `this.select();`, style: "width: 97px;" }); //, style: "overflow: hidden;"
        for(let i = 0; i < armySet.Army.length; i++) {
            td = addElement("td", tr, { style: cellStyle });
            input = addElement("input", td, { value: armySet.Army[i], onfocus: `ChangeSlider(event, ${i + 1}, 0); this.select();`, type: "number", style: "min-width: 47px; width: 98%; text-align: right;" });
        }
        td = addElement("td", tr, { style: cellStyle });
        let delButton = addElement("input", td, { type: "button", value: "x", title: LocalizedString.Delete });
        delButton.addEventListener("click", this.deleteSet);

        td = addElement("td", tr, { style: cellStyle });
        if(!isNew) {
            let applyButton = addElement("input", td, { type : "button", value : "v", title : LocalizedString.Apply });
            applyButton.addEventListener("click", function() { armySetsPreferences.saveSets(); applySet(null, armySetsPreferences, { number: parseInt(armySet.Id), name: armySet.Name, title: armySet.Army.join("+"), method: "POST", url: "army_apply.php", data: armySet.Army.reduce((x, y, i) => `${x}&countv${i + 1}=${y}`, ""), contentType: "application/x-www-form-urlencoded" }); });
        }
    },
    saveSets: function () {
        const rows = Array.from(armySetsPreferences.setsTable.rows).slice(1);
        const armySetsData = rows.reduce((t, x) => ({ ...t, [x.getAttribute("setId")]: Array.from(x.cells).slice(0, 8).map(y => y.firstChild.value).join("|") }), {});
        setPlayerFractionValue("ArmySets", JSON.stringify(armySetsData));
    },
    deleteSet: function () {
        const table = this.parentNode.parentNode.parentNode;
        const row = this.parentNode.parentNode;
        table.removeChild(row);
    }
};
/************************************************************************************************************/
const fractionsPreferences = {
    menuTitle: LocalizedString.Castle,
    menuImage: "https://dcdn.heroeswm.ru/i/castle_im/btn_fraction.png",
    setReferencePage: "castle.php",
    sets: [],
    menuItems: {},
    currentMenuItem: undefined,
    refreshingPages: "home.php;army.php;pl_info.php;castle.php;inventory.php;skillwheel.php",
    initSetsApplyAction: function() {
        const fractions = JSON.parse(getPlayerValue("Fractions", "{}"));
        this.sets = Object.keys(fractions).map(x => { return { number: fractions[x], name: x, method: "GET", url: `castle.php?change_clr_to=${fractions[x]}&sign=${getPlayerValue("Sign")}` }; });
    },
    getCurrentSetName: function() { return "Fraction"; },
    onPageLoad: async function () {
        await this.initCastlesList();
        this.findSetChangersAndAddListener();
    },
    initCastlesList: async function () {
        if(location.pathname == '/castle.php' || !getPlayerValue("Fractions")) {
            const doc = location.pathname == '/castle.php' ? document : await getRequest("/castle.php");
            const fractions = Array.from(doc.querySelectorAll("div.castle_faction_div_inside")).reduce((t, x) => ({...t, [x.getAttribute("hint")]: getUrlParamValue(x.firstChild.href, "show_castle_f") }), {});
            setPlayerValue("Fractions", JSON.stringify(fractions));
            const changeCastleRef = doc.querySelector("div.castle_yes_no_dialog a[href*='castle.php?change_clr_to']");
            if(changeCastleRef) {
                setPlayerValue("Sign", getUrlParamValue(changeCastleRef.href, "sign"));
            }
        }
    },
    findSetChangersAndAddListener: function() {
        const setRefs = document.querySelectorAll("a[href*='castle.php?change_clr_to']");
        for(const setRef of setRefs) {
            addSetChangerListener(setRef, this, getUrlParamValue(setRef.href, "change_clr_to"));
        }
    },
    setChanged: function(newSetNumber) {
        Fraction = newSetNumber;
        setPlayerValue("Fraction", Fraction);
        createMenu();
    }
};

/************************************************************************************************************/
const preferences = [weaponSetsPreferences, skillSetsPreferences, armySetsPreferences, fractionsPreferences];
const isHeartOnPage = document.querySelector("canvas#heart") || document.querySelector("div#heart_js_mobile");
main();
function main() {
    if(isHeartOnPage && Fraction) {
        update();
        for(const preference of preferences) {
            if(preference.onPageLoad) {
                preference.onPageLoad();
            }
        }
        createMenu();
        window.addEventListener("resize", function() { createMenu(true); });
        drowSkillChangers();
        if(location.pathname == '/home.php' && isNewPersonPage) {
            observe(document.querySelector("div#home_css_stats_wrap_div"), drowSkillChangers);
        }
    }
}
function update() {
    const fractionNumbers = [1, 101, 2, 102, 3, 103, 4, 104, 5, 105, 205, 6, 106, 7, 107, 8, 108, 9, 10];
    for(const fractionNumber of fractionNumbers) {
        //deletePlayerFractionValue("SkillSets", fractionNumber);
        const skillSetsOld = getValue(`SkillSets${PlayerId}Fraction${fractionNumber}`);
        const skillSets = getPlayerFractionValue("SkillSets", undefined, fractionNumber);
        // if(fractionNumber == 8) {
            // console.log(`fractionNumber: ${fractionNumber}, skillSetsOld: ${skillSetsOld}, skillSets: ${skillSets}, CurrentSkillSet: ${getValue(`CurrentSkillSet${PlayerId}Fraction${fractionNumber}`)}`);
        // }
        if(skillSetsOld && !skillSets) {
            setPlayerFractionValue("SkillSets", skillSetsOld, fractionNumber);
            setPlayerFractionValue("SkillSet", getValue(`CurrentSkillSet${PlayerId}Fraction${fractionNumber}`), fractionNumber);
        }
        //deletePlayerFractionValue("ArmySets", fractionNumber);
        const armySetsOld = getValue(`ArmySets${PlayerId}Fraction${fractionNumber}`);
        const armySets = getPlayerFractionValue("ArmySets", undefined, fractionNumber);
        // if(fractionNumber == 8) {
            // console.log(`fractionNumber: ${fractionNumber}, armySetsOld: ${armySetsOld}, armySets: ${armySets}, ArmySet: ${getValue(`ArmySet${PlayerId}Fraction${fractionNumber}`)}`);
        // }
        if(armySetsOld && !armySets) {
            setPlayerFractionValue("ArmySets", armySetsOld, fractionNumber);
            setPlayerFractionValue("ArmySet", getValue(`ArmySet${PlayerId}Fraction${fractionNumber}`), fractionNumber);
        }
        // if(fractionNumber == 8) {
            // console.log(`ArmySet: ${getPlayerFractionValue("ArmySet", undefined, fractionNumber)}`);
        // }
    }
}
function getTextWidth(text, font) {
    const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas")); // re-use canvas object for better performance
    const context = canvas.getContext("2d");
    context.font = font;
    const metrics = context.measureText(text);
    return metrics.width;
}
function getCssStyle(element, prop) { return window.getComputedStyle(element, null).getPropertyValue(prop); }
function getCanvasFont(el = document.body) {
    const fontWeight = getCssStyle(el, 'font-weight') || 'normal';
    const fontSize = getCssStyle(el, 'font-size') || '16px';
    const fontFamily = getCssStyle(el, 'font-family') || 'Times New Roman';
    return `${fontWeight} ${fontSize} ${fontFamily}`;
}
function getOrCreateAndResizeDropdown(baseElement, style) {
    const dropdownId = `${baseElement.id}Dropdown`;
    let dropdown = document.getElementById(dropdownId);
    if(!dropdown) {
        dropdown = addElement("div", document.body, { id: dropdownId, style: `position: absolute; z-index: ${baseElement.style.zIndex};` + (style || "") });
        if(isMobileDevice) {
            baseElement.addEventListener("click", function() {
                dropdown.style.display = dropdown.style.display == "none" ? "block" : "none";
                if(dropdown.style.display == "block") {
                    const dropdowns = document.querySelectorAll("div[id$=Dropdown]");
                    for(const dropdown of dropdowns) {
                        if(dropdown.id != dropdownId) {
                            dropdown.style.display = "none";
                        }
                    }
                }
            });
        } else {
            let hideTimer;
            const dropdownTimeout = 100;
            baseElement.addEventListener("mouseenter", function() { clearTimeout(hideTimer); if(dropdown.style.display == "none") { setTimeout(function() { dropdown.style.display = "block"; }, dropdownTimeout); } });
            baseElement.addEventListener("mouseleave", function() { hideTimer = setTimeout(function() { dropdown.style.display = "none"; }, dropdownTimeout); });
            dropdown.addEventListener("mouseenter", function() { clearTimeout(hideTimer); });
            dropdown.addEventListener("mouseleave", function() { hideTimer = setTimeout(function() { dropdown.style.display = "none"; }, dropdownTimeout); });
        }
    }
    const baseElementRect = baseElement.getBoundingClientRect();
    dropdown.style.top = `${baseElementRect.bottom + window.scrollY + 1}px`;
    dropdown.style.left = `${baseElementRect.left}px`;
    return dropdown;
}
function createMenu(isResize) {
    const menuPanel = document.querySelector("div#hwm_header") || document.querySelector("#main_top_table") || document.querySelector("body > table");
    const homeRef = menuPanel.querySelector("a[href='home.php']");
    const menuAnchor = isNewInterface ? homeRef.parentNode : getParent(homeRef, "td", 3);
    if(!menuAnchor) {
        return;
    }
    const anchorRect = menuAnchor.getBoundingClientRect();
    //console.log(anchorRect);
    const borderWidth = isNewInterface ? 1 : 2;
    const menuItemHeight = anchorRect.height - borderWidth * 2;
    const menuItemLineHeight = menuItemHeight - borderWidth;
    const menuItemTop = anchorRect.top + window.scrollY;
    const foreColor = "#f5c137";
    const backgroundColor = isNewInterface ? "linear-gradient(to top, #09203f 0%, #537895 100%)" : (document.querySelector("img[src*='i/top_ny']") ? "#003399" : "#6b6b69");
    const zIndex = location.pathname == "/photo_pl_photos.php" ? "0" : "100";
    let currentMenuItemLeft = anchorRect.left - borderWidth - 1;
    for(let i = preferences.length - 1; i >= 0; i--) {
        const currentPreferences = preferences[i];
        const mainMenuItemId = `SetsMenuItem${i}`;
        let mainMenuItem = document.getElementById(mainMenuItemId);
        if(!mainMenuItem) {
            const menuHeaderStyle = `min-width: ${menuItemHeight}px; font-size: 9pt; position: absolute; border-radius: 5px; background: ${backgroundColor}; color: ${foreColor}; border: ${borderWidth}px solid ${foreColor}; padding: 0 3px 0 3px; font-weight: bold; text-align: center; z-index: ${zIndex};`;
            mainMenuItem = addElement("div", document.body, { id: mainMenuItemId, style: menuHeaderStyle });
            let itemContent = currentPreferences.menuTitle;
            let itemTitle = "";
            if(currentPreferences.menuImage && isNewInterface) {
                itemTitle = currentPreferences.menuTitle;
                itemContent = `<img src="${currentPreferences.menuImage}" alt="${itemTitle}" style="height: 90%; margin-top: 2px; border-radius: 50%;">`;
            }
            let itemChild;
            if(isMobileDevice) {
                itemChild = addElement("span", mainMenuItem, { innerHTML: itemContent, style: `color: ${foreColor}; text-decoration: none;` });
            } else {
                itemChild = addElement("a", mainMenuItem, { innerHTML: itemContent, href: currentPreferences.setReferencePage, style: `color: ${foreColor}; text-decoration: none;` });
            }
            if(itemTitle != "") {
                itemChild.title = itemTitle;
            }
        }
        mainMenuItem.style.height = `${menuItemHeight}px`;
        mainMenuItem.style.lineHeight = `${menuItemLineHeight}px`;
        mainMenuItem.style.top = `${menuItemTop}px`;

        const mainMenuItemRect = mainMenuItem.getBoundingClientRect();
        currentMenuItemLeft -= mainMenuItemRect.width;
        mainMenuItem.style.left = `${currentMenuItemLeft}px`;

        const menuContent = getOrCreateAndResizeDropdown(mainMenuItem, `list-style-position: inside; color: ${foreColor}; padding: 2px 3px 2px 3px; white-space: nowrap; background: ${backgroundColor};`);
        if(isResize) {
            continue;
        }
        currentPreferences.initSetsApplyAction();
        menuContent.style.display = "block"; // Перед заполнением покажем див для правильного определения его размеров (нужно, если он установлен в none)
        menuContent.innerHTML = '';
        const currentSetNumber = getPlayerValue(currentPreferences.getCurrentSetName(), -1);
        let maxClientWidth = menuContent.clientWidth;
        for(const currentSet of currentPreferences.sets) {
            const dropDownMenuItem = addElement("li", menuContent, { type: "disc", style: "text-align: left;" });
            const currentSetReference = addElement("b", dropDownMenuItem, { innerHTML: currentSet.name, title: currentSet.title || "", style: `color: ${foreColor}; cursor: pointer;` });
            if(currentSet.url) {
                currentSetReference.addEventListener("click", function() { applySet(currentSetReference, currentPreferences, currentSet); }, false);
            }
            if(currentSet.number == currentSetNumber) {
                markCurrent(currentSetReference, currentPreferences, currentSet.number);
            }
            currentPreferences.menuItems[currentSet.number] = currentSetReference;

            let currentWidth = getTextWidth(currentSet.name, getCanvasFont(currentSetReference));
            if(maxClientWidth < currentWidth) {
                maxClientWidth = currentWidth;
            }
        }
        menuContent.style.minWidth = `${(maxClientWidth + 25)}px`;
        menuContent.style.display = "none";
    }
}
function markCurrent(selectedMenuItem, currentPreferences, currentSetNumber) {
    setPlayerValue(currentPreferences.getCurrentSetName(), currentSetNumber);
    if(selectedMenuItem) {
        selectedMenuItem.style.color = '#0f0';
        if (currentPreferences.currentMenuItem && currentPreferences.currentMenuItem != selectedMenuItem) {
            currentPreferences.currentMenuItem.style.color = "#f5c137";
        }
        currentPreferences.currentMenuItem = selectedMenuItem;
    }
}
function addSetChangerListener(htmlElement, currentPreferences, setNumber) {
    htmlElement.addEventListener("click", function() { markCurrent(currentPreferences.menuItems[setNumber], currentPreferences, setNumber); });
}
function applySet(selectedMenuItem, currentPreferences, currentSet) {
    markCurrent(selectedMenuItem, currentPreferences, currentSet.number);
    const originalText = selectedMenuItem ? selectedMenuItem.innerHTML : "";
    if(selectedMenuItem) {
        selectedMenuItem.innerHTML += " " + getWheelImage();
    }
    const request = new XMLHttpRequest();
    request.open(currentSet.method, currentSet.url, true);
    request.onreadystatechange = async function () {
        if(request.readyState == 2) {
            request.abort();
            if(selectedMenuItem) {
                selectedMenuItem.innerHTML = originalText;
                if(isMobileDevice) {
                    selectedMenuItem.parentNode.parentNode.style.display = "none";
                }
            }
            if(typeof(currentPreferences.setChanged) == "function") {
                currentPreferences.setChanged(currentSet.number);
            }
            if(currentPreferences.refreshingPages) {
                let pages = currentPreferences.refreshingPages.split(';');
                for(const page of pages) {
                    if(location.href.indexOf(page) > -1) {
                        let handled = false;
                        if(currentPreferences.updatePanelHandler) {
                            handled = await currentPreferences.updatePanelHandler();
                        }
                        if(!handled) {
                            window.location.reload();
                        }
                    }
                }
            }
            if(currentPreferences.refreshingConditions && currentPreferences.refreshingConditions()) {
                window.location.reload();
            }
        }
    };
    if(currentSet.contentType) {
        request.setRequestHeader('Content-type', currentSet.contentType);
    }
    //console.log(currentSet.data);
    request.send(currentSet.data);
    return false;
}
function drowSkillChangers() {
    if(location.pathname=='/home.php' && !document.querySelector(`#increaseattackAmountInput`)) {
        let skillsCount = 0;
        let re = new RegExp(isNewPersonPage ? `>${LocalizedString.AvailablePoints}:\\s(\\d+)<` : `<b>${LocalizedString.AvailablePoints}:</b>\\s(\\d+)`);
        const skillsExec = re.exec(document.body.innerHTML);
        if(skillsExec) {
            skillsCount += parseInt(skillsExec[1]);
        }
        re = new RegExp(isNewPersonPage ? `>${LocalizedString.AvailableTalentPoints}:\\s(\\d+)<` : `<b>${LocalizedString.AvailableTalentPoints}:</b>\\s(\\d+)`);
        const perksSkillsExec = re.exec(document.body.innerHTML);
        if(perksSkillsExec) {
            skillsCount += parseInt(perksSkillsExec[1]);
        }
        //console.log(`skillsCount: ${skillsCount}`);
        if(skillsCount == 0) {
            return;
        }
        const skillValueContainers = [];
        if(isNewPersonPage) {
            const container = document.querySelector("div#home_css_stats_wrap_div");
            const inv_stat_dataDivs = container.querySelectorAll("div.inv_stat_data.home_stat_data.show_hint");
            for(const inv_stat_dataDiv of inv_stat_dataDivs) {
                const increaseButton = inv_stat_dataDiv.querySelector("div.home_button2.btn_hover2");
                if(!increaseButton) {
                    continue;
                }
                const skillValueContainer = inv_stat_dataDiv.querySelector("div.inv_stat_text.home_stat_text");
                skillValueContainers.push(skillValueContainer);
            }
        } else {
            const increaseRefs = document.querySelectorAll("a[href^='home.php?increase=']"); //home.php?increase=defence
            for(const increaseRef of increaseRefs) {
                const sklilIncreaseCell = getParent(increaseRef, "td");
                const skillValueContainer = sklilIncreaseCell.previousSibling;
                skillValueContainers.push(skillValueContainer);
            }
        }
        const skillNames = ["attack", "defence", "power", "knowledge"];
        let i = 0;
        for(const skillValueContainer of skillValueContainers) {
            const skillValue = Number((skillValueContainer.querySelector("b") || skillValueContainer).innerText);
            skillValueContainer.innerHTML = "";
            const skill = skillNames[i];
            const increaseAmountInput = addElement("input", skillValueContainer, { id: `increase${skill}AmountInput`, name: "increaseAmountInput", value: skillValue, type: "number", min: skillValue, max: skillValue + skillsCount, size: 4, onfocus: "this.select();", title: LocalizedString.IncreaseManyPointsTooltip.replace("${minValue}", skillValue + 1).replace("${maxValue}", skillValue + skillsCount) });
            increaseAmountInput.addEventListener("change", function() { const targetValue = Number(increaseAmountInput.value); if(targetValue > skillValue && targetValue <= skillValue + skillsCount) { changeSkill(increaseAmountInput, isNewPersonPage, skill, skillValue, targetValue); } });
            i++;
        }
    }
}
async function changeSkill(increaseAmountInput, isNewPersonPage, skill, currentValue, targetValue) {
    while(currentValue < targetValue) {
        const url = `/home.php?increase=${skill}` + (isNewPersonPage ? `&info=1&js_output=1&rand=${Math.random() * 1000000}` : "");
        const txt = await getRequestText(url, isNewPersonPage ? "text/html; charset=UTF-8" : "text/html; charset=windows-1251");
        currentValue++;
        if(isNewPersonPage) {
            if (txt.substring(0, 7) != 'HCSS_OK') {
                window.location = '/home.php?info';
                return;
            }
            const data = txt.split('@');
            const home_css_stats_wrap_div = document.getElementById('home_css_stats_wrap_div');
            if(data && data[1] && home_css_stats_wrap_div) {
                home_css_stats_wrap_div.innerHTML = data[1];
                if(data.length > 2 && document.getElementById('home_css_mana_count')) {
                    document.getElementById('home_css_mana_count').innerHTML = parseInt(data[2]);
                }
                if(typeof windowObject.hwm_hints_init === 'function') windowObject.hwm_hints_init();
            }
        } else {
            increaseAmountInput.value = currentValue;
        }
    }
    if(!isNewPersonPage) {
        location.reload();
    }
}
function hintProcessor(panel) {
    //Array.from(panel.querySelectorAll(".show_hint[hint]")).forEach(x => x.setAttribute("title", x.getAttribute("hint").replace(/<br>/ig, "\n").replace(/<br \/>/ig, "\n")));
}
function getWheelImage() { return '<img border="0" align="absmiddle" height="11" src="https://dcdn.heroeswm.ru/css/loading.gif">'; }
function getFraction() {
    let currentFractionNumber;
    if(location.pathname == '/home.php') {
        // for new home page
        let currentFractionIconContainer = document.querySelector("div.home_css_pl_fract.show_hint");
        if(!currentFractionIconContainer) {
            currentFractionIconContainer = document.querySelector("a[href^='castle.php?change_faction_dialog']");
        }
        if(currentFractionIconContainer) {
            const currentFractionIconImg = currentFractionIconContainer.querySelector("img");
            currentFractionNumber = currentFractionIconImg.src.split("i/f/r")[1].split(".png")[0];
        }
    } else if(location.pathname=='/pl_info.php' && getUrlParamValue(location.href, "id") == PlayerId) {
        const fractionImage = document.querySelector("img[src*='i/f/r']");
        const regExp = new RegExp('\\/i\\/f\\/r(\\d+)\\.png');
        const regExpExec = regExp.exec(fractionImage.src);
        if(regExpExec) {
            currentFractionNumber = regExpExec[1];
        }
    } else if(location.pathname=='/castle.php') {
        const selectedFractionImg = document.querySelector("div.castle_faction_div_inside2 img");
        const selectedFractionImgName = selectedFractionImg.getAttribute("src");
        const selectedFractionNumber = selectedFractionImgName.split("kukla_png/kukla")[1].split(".")[0]; //dcdn.heroeswm.ru/i/kukla_png/kukla5.png

        const fractionsDiv = document.querySelector("div[id='faction_list']");
        if(fractionsDiv.getAttribute("style").includes("display:none;")) {
            currentFractionNumber = selectedFractionNumber;
        }
    }
    if(currentFractionNumber) {
        setPlayerValue("Fraction", currentFractionNumber);
    }
    Fraction = parseInt(getPlayerValue("Fraction"));
}
// API
function getUrlParamValue(url, paramName) { return (new URLSearchParams(url.split("?")[1])).get(paramName); }
function uchar(s) { switch (s[0]) {case "А": return "\u0410"; case "Б": return "\u0411"; case "В": return "\u0412"; case "Г": return "\u0413"; case "Д": return "\u0414"; case "Е": return "\u0415"; case "Ж": return "\u0416"; case "З": return "\u0417"; case "И": return "\u0418"; case "Й": return "\u0419"; case "К": return "\u041a"; case "Л": return "\u041b"; case "М": return "\u041c"; case "Н": return "\u041d"; case "О": return "\u041e"; case "П": return "\u041f"; case "Р": return "\u0420"; case "С": return "\u0421"; case "Т": return "\u0422"; case "У": return "\u0423"; case "Ф": return "\u0424"; case "Х": return "\u0425"; case "Ц": return "\u0426"; case "Ч": return "\u0427"; case "Ш": return "\u0428"; case "Щ": return "\u0429"; case "Ъ": return "\u042a"; case "Ы": return "\u042b"; case "Ь": return "\u042c"; case "Э": return "\u042d"; case "Ю": return "\u042e"; case "Я": return "\u042f"; case "а": return "\u0430"; case "б": return "\u0431"; case "в": return "\u0432"; case "г": return "\u0433"; case "д": return "\u0434"; case "е": return "\u0435"; case "ж": return "\u0436"; case "з": return "\u0437"; case "и": return "\u0438"; case "й": return "\u0439"; case "к": return "\u043a"; case "л": return "\u043b"; case "м": return "\u043c"; case "н": return "\u043d"; case "о": return "\u043e"; case "п": return "\u043f"; case "р": return "\u0440"; case "с": return "\u0441"; case "т": return "\u0442"; case "у": return "\u0443"; case "ф": return "\u0444"; case "х": return "\u0445"; case "ц": return "\u0446"; case "ч": return "\u0447"; case "ш": return "\u0448"; case "щ": return "\u0449"; case "ъ": return "\u044a"; case "ы": return "\u044b"; case "ь": return "\u044c"; case "э": return "\u044d"; case "ю": return "\u044e"; case "я": return "\u044f"; case "Ё": return "\u0401"; case "ё": return "\u0451"; default: return s[0]; } }
function ustring(s) {
    s = String(s);
    let result = "";
    for(let i = 0; i < s.length; i++) {
        result += uchar(s[i]);
    }
    return result;
}
function getRequest(url, overrideMimeType) {
    return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({ method: "GET", url: url, overrideMimeType: overrideMimeType || "text/html; charset=windows-1251",
            onload: function(response) { resolve((new DOMParser).parseFromString(response.responseText, "text/html")); },
            onerror: function(error) { reject(error); }
        });
    });
}
function getRequestText(url, overrideMimeType) {
    return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({ method: "GET", url: url, overrideMimeType: overrideMimeType || "text/html; charset=windows-1251",
            onload: function(response) { resolve(response.responseText); },
            onerror: function(error) { reject(error); }
        });
    });
}
function postRequest(url, data) {
    return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({ method: "POST", url: url, headers: { "Content-Type": "application/x-www-form-urlencoded" }, data: data,
            onload: function(response) { resolve(response); },
            onerror: function(error) { reject(error); }
        });
    });
}
function observe(target, handler, config = { childList: true, subtree: true }) {
    const ob = new MutationObserver(async function(mut, observer) {
        //console.log(`Mutation start`);
        observer.disconnect();
        if(handler.constructor.name === 'AsyncFunction') {
            await handler();
        } else {
            handler();
        }
        observer.observe(target, config);
    });
    ob.observe(target, config);
}
function gmGetBool(valueName, defaultValue = false) {
    const value = getValue(valueName);
    if(value) {
        if(typeof(value) == "string") {
            return value == "true";
        }
        if(typeof(value) == "boolean") {
            return value;
        }
    }
    return defaultValue;
}
function mobileCheck() {
    let check = false;
    (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
    return check;
};
function getParent(element, parentType, number = 1) {
    if(!element) {
        return;
    }
    let result = element;
    let foundNumber = 0;
    while(result = result.parentNode) {
        if(result.nodeName.toLowerCase() == parentType.toLowerCase()) {
            foundNumber++;
            if(foundNumber == number) {
                return result;
            }
        }
    }
}
function addElement(type, parent, data) {
    let el = createElement(type, data);
    if(parent) {
        parent.appendChild(el);
    }
    return el;
}
function createElement(type, data) {
    let el = document.createElement(type);
    if(data) {
        for(let key in data) {
            if(key == "innerText" || key == "innerHTML") {
                el[key] = data[key];
            } else {
                el.setAttribute(key, data[key]);
            }
        }
    }
    return el;
}
async function refreshUpdatePanels(panelSelectors, postProcessor) {
    panelSelectors = Array.isArray(panelSelectors) ? panelSelectors : [panelSelectors];
    let freshDocument;
    for(const panelSelector of panelSelectors) {
        const updatePanel = panelSelector(document);
        if(updatePanel) {
            freshDocument = freshDocument || await getRequest(location.href);
            const freshUpdatePanel = panelSelector(freshDocument);
            if(!freshUpdatePanel) {
                console.log(updatePanel)
                continue;
            }
            if(postProcessor) {
                postProcessor(freshUpdatePanel);
            }
            updatePanel.innerHTML = freshUpdatePanel.innerHTML;
        }
    }
    if(typeof windowObject.hwm_hints_init === 'function') windowObject.hwm_hints_init();
}
function getValue(key, defaultValue) { return GM_getValue(key, defaultValue); };
function setValue(key, value) { GM_setValue(key, value); };
function deleteValue(key) { return GM_deleteValue(key); };
function getPlayerValue(key, defaultValue) { return getValue(`${key}${PlayerId}`, defaultValue); };
function setPlayerValue(key, value) { setValue(`${key}${PlayerId}`, value); };
function deletePlayerValue(key) { return deleteValue(`${key}${PlayerId}`); };
function getPlayerBool(valueName, defaultValue = false) { return getBool(valueName + PlayerId, defaultValue); }
function getBool(valueName, defaultValue = false) {
    const value = getValue(valueName);
    //console.log(`valueName: ${valueName}, value: ${value}, ${typeof(value)}`)
    if(value) {
        if(typeof(value) == "string") {
            return value == "true";
        }
        if(typeof(value) == "boolean") {
            return value;
        }
    }
    return defaultValue;
}
function getPlayerFractionValue(key, defaultValue, fraction = Fraction) { return getPlayerValue(getFractionKey(key, fraction), defaultValue); };
function setPlayerFractionValue(key, value, fraction = Fraction) { setPlayerValue(getFractionKey(key, fraction), value); };
function deletePlayerFractionValue(key, fraction = Fraction) { return deletePlayerValue(getFractionKey(key, fraction)); };
function getFractionKey(key, fraction = Fraction) { return `${key}${fraction}f`; }