hwmSetsMaster

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

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

if(!PlayerId) {
    return;
}
const Strings = {
        "ru": {
            Army: "Армия",
            Save: "Сохранить",
            Add: "Добавить",
            AddCurrent: "Добавить текущий",
            SetName: "Наименование набора",
            Delete: "Удалить",
            Talents: "Навыки",
            Inventory: "Инвентарь",
            RemoveAll: "Снять все",
            EnterJpg: "enter0.jpg",
            SignInTitle: "Войти",
            Castle: "Замок",
            Task: "Задание",
            Apply: "Применить",
            AvailablePoints: "Свободных очков",
            AvailableTalentPoints: "Свободных очков от навыка",
            IncreaseManyPointsTooltip: "Введите число от ${minValue} до ${maxValue}. Нажмите Tab."
        },
        "en": {
            Army: "Army",
            Save: "Save",
            Add: "Add",
            AddCurrent: "Add current",
            SetName: "Set name",
            Delete: "Delete",
            Talents: "Talents",
            Inventory: "Inventory",
            RemoveAll: "Un-equip all",
            EnterJpg: "enter0_eng.jpg",
            SignInTitle: "Sign in",
            Castle: "Castle",
            Task: "Task",
            Apply: "Apply",
            AvailablePoints: "Available points",
            AvailableTalentPoints: "Available talent points",
            IncreaseManyPointsTooltip: "Enter number from ${minValue} to ${maxValue}. Press Tab."
        }
    };
const LocalizedString = Strings[lang];
let Fraction;
getFraction();

//const homeArtsPanelSelector = doc => isNewPersonPage ? doc.querySelector("div#inv_doll_stats") : getParent(doc.querySelector("div.arts_info.shop_art_info"), "table");
const homeArtsPanelSelector = doc => isNewPersonPage ? doc.querySelector("div#inv_doll_stats") : doc.querySelector("body > center table.wb > tbody > tr:nth-child(3) > td > table:nth-child(2)");
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 resourcesPath = `${location.protocol}//${location.host.replace("www", "dcdn")}`;

/************************************************************************************************************/
const weaponSetsPreferences = {
    menuTitle: LocalizedString.Inventory,
    menuImage: `${resourcesPath}/i/combat/btn_inventory.png?v=7`,
    setReferencePage: "inventory.php",
    sets: new Array(),
    menuItems: {},
    currentMenuItem: undefined,
    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}` });
        }
    },
    name: "WeaponSet",
    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: `${resourcesPath}/i/combat/btn_skills_v3.png?v=7`,
    setReferencePage: "skillwheel.php",
    sets: new Array(),
    menuItems: {},
    currentMenuItem: undefined,
    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]}` }; });
    },
    name: "SkillSet",
    getCurrentSetName: function() { return getFractionKey("SkillSet"); },
};
/************************************************************************************************************/
const armySetsPreferences = {
    menuTitle: LocalizedString.Army,
    menuImage: `${resourcesPath}/i/castle_im/btn_recruit.png`,
    setReferencePage: "army.php",
    sets: new Array(),
    menuItems: {},
    currentMenuItem: undefined,
    setsTable: null,
    name: "ArmySet",
    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: "set_id=7&" + x.Army.map((x, i) => `countv${i + 1}=${x}`).join("&") }; });
        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") || document.getElementById("hwm_no_zoom");
            container = document.body;
            if(!isMobileInterface) {
                container = addElement("center", null, hwm_for_zoom);
            }
            this.setsTable = addElement("table", { class: "smithTable", style: `${isMobileInterface ? "margin-left: px;" : ""}` }, container);
        } 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", { type: "button", value: LocalizedString.Save, class: "button-62", style: isMobileInterface ? "margin: 0 0 0 50px;" : "" }, container);
            saveButton.addEventListener("click", this.saveSets);
            const addCurrentButton = addElement("input", { type: "button", value: LocalizedString.AddCurrent, class: "button-62" }, container);
            addCurrentButton.addEventListener("click", function() { armySetsPreferences.drawSetRow(); });
        }
    },
    drawTableHeader: function () {
        if(!this.setsTable) {
            return;
        }
        const units = win.obj;
        //console.log(units)
        const tr = addElement("tr", null, this.setsTable);
        addElement("th", { innerHTML: LocalizedString.SetName.replace(/ /g, "<br>").replace(/-/g, "-<br>") }, tr);
        for(let i = 1; i <= 7; i++) {
            addElement("th", { innerHTML: (units[i]?.name || "").replace(/ /g, "<br>").replace(/-/g, "-<br>"), onClick: `ChangeSlider(event, ${i}, 0);` }, tr);
        }
        addElement("th", { style: "width: 30px;" }, tr);
        addElement("th", { style: "width: 30px;" }, tr);
    },
    drawSetRow: function(armySet) {
        const isNew = armySet ? false : true;
        const units = win.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 || 0) };
        if(!this.setsTable) {
            return;
        }
        const tr = addElement("tr", { setId: armySet.Id }, this.setsTable);
        let td = addElement("td", {}, tr);
        let input = addElement("input", { value: armySet.Name, onfocus: `this.select();`, style: "width: 100%;" }, td);
        for(let i = 0; i < armySet.Army.length; i++) {
            td = addElement("td", {}, tr);
            input = addElement("input", { value: armySet.Army[i], onfocus: `ChangeSlider(event, ${i + 1}, 0); this.select();`, type: "number", style: "min-width: 47px; width: 100%; text-align: right;" }, td);
        }
        td = addElement("td", {}, tr);
        let delButton = addElement("input", { type: "button", value: "x", class: "button-62", style: "width: 100%;", title: LocalizedString.Delete }, td);
        delButton.addEventListener("click", this.deleteSet);

        td = addElement("td", {}, tr);
        if(!isNew) {
            let applyButton = addElement("input", { type : "button", value : "v", class: "button-62", style: "width: 100%;", title : LocalizedString.Apply }, td);
            applyButton.addEventListener("click", function() {
                armySetsPreferences.saveSets();
                const data = armySet.Army.reduce((x, y, i) => `${x}&countv${i + 1}=${y}`, "set_id=7");
                //console.log(data);
                applySet(null, armySetsPreferences, {
                    number: parseInt(armySet.Id),
                    name: armySet.Name,
                    title: armySet.Army.join("+"),
                    method: "POST", url: "/army_apply.php",
                    data: data
                });
            });
        }
    },
    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: `${resourcesPath}/i/castle_im/btn_fraction.png`,
    setReferencePage: "castle.php",
    sets: [],
    menuItems: {},
    currentMenuItem: undefined,
    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")}` }; });
    },
    name: "Fraction",
    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 visibleMunuItems = [[], [], [], []];
main();
function main() {
    if(isHeartOnPage && Fraction) {
        addStyle(`
.button-62 {
  background: linear-gradient(to bottom right, #E47B8E, #FF9A5A);
  border: 0;
  border-radius: 5px;
  color: #FFFFFF;
  cursor: pointer;
  display: inline-block;
  font-family: -apple-system,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  font-size: 16px;
  font-weight: 500;
  outline: transparent;
  padding: 0 5px;
  text-align: center;
  text-decoration: none;
  transition: box-shadow .2s ease-in-out;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
  white-space: nowrap;
}

.button-62:not([disabled]):focus {
  box-shadow: 0 0 .25rem rgba(0, 0, 0, 0.5), -.125rem -.125rem 1rem rgba(239, 71, 101, 0.5), .125rem .125rem 1rem rgba(255, 154, 90, 0.5);
}

.button-62:not([disabled]):hover {
  box-shadow: 0 0 .25rem rgba(0, 0, 0, 0.5), -.125rem -.125rem 1rem rgba(239, 71, 101, 0.5), .125rem .125rem 1rem rgba(255, 154, 90, 0.5);
}
.button-62:disabled,button[disabled] {
    background: linear-gradient(177.9deg, rgb(58, 62, 88) 3.6%, rgb(119, 127, 148) 105.8%);
}
table.smithTable { 
    width: 100%;
    background: BurlyWood;
    border: 5px solid BurlyWood;
    border-radius: 5px;
    margin-top: 1px;
}
table.smithTable th {
    border: 1px none #f5c137;
    overflow: hidden;
    text-align: center;
    font-size: 11px;
}
table.smithTable td {
    border: 1px none #f5c137;
    overflow: hidden;
    text-align: center;
}
table.smithTable tr:nth-child(odd) {
  background: Wheat;
}
table.smithTable tr:nth-child(even) {
  background: white;
}
.waiting {
    cursor: wait;
}
.not-allowed {
    cursor: not-allowed;
}
`);
        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(branchIndex, baseElement, style, toRight = false) {
    const dropdownId = `${baseElement.id}Dropdown`;
    let dropdown = document.getElementById(dropdownId);
    if(!dropdown) {
        dropdown = addElement("div", { id: dropdownId, style: `position: absolute; box-shadow: 3px 3px 5px #333;` + (style || "") }, document.body);
        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"; 
                baseElement.setAttribute("menuShown", "");
            }, dropdownTimeout); } });
            baseElement.addEventListener("mouseleave", function() { hideTimer = setTimeout(function() {
                dropdown.style.display = "none";
                baseElement.removeAttribute("menuShown");
            }, dropdownTimeout); });
            dropdown.addEventListener("mouseenter", function() {
                clearTimeout(hideTimer); 
                //console.log(`mouseenter dropdownId: ${dropdownId}`);
            });
            dropdown.addEventListener("mouseleave", function() {
                //console.log(`mouseleave dropdownId: ${dropdownId}`);
                const menuShown = dropdown.querySelector("[menuShown]");
                //console.log(menuShown)
                if(menuShown) {
                    return;
                }
                hideTimer = setTimeout(function() {
                    dropdown.style.display = "none";
                    baseElement.removeAttribute("menuShown");
                }, dropdownTimeout); 
            });
        }
    }
    const baseElementRect = baseElement.getBoundingClientRect();
    dropdown.style.top = `${baseElementRect.bottom + window.scrollY + 1}px`;
    dropdown.style.left = `${toRight ? baseElementRect.right : baseElementRect.left}px`;
    return dropdown;
}
function createMenu(isResize) {
    const menuPanel = document.querySelector("div#hwm_header") || document.querySelector("#main_top_table") || document.querySelector("body > table");
    if(!menuPanel) {
        armySetsPreferences.initSetsApplyAction();
        return;
    }
    const setsMenuPosition = getPlayerBool("ShowMenyAtRight") ? "right" : "left"; //const setsMenuPosition = isNewInterface && isMobileDevice || getPlayerBool("ShowMenyAtRight") ? "right" : "left";
    const homeRef = menuPanel.querySelector("a[href='home.php']");
    const framesRef = menuPanel.querySelector("a[href='frames.php']");
    const menuAnchor = setsMenuPosition == "left" ? (isNewInterface ? homeRef.parentNode : getParent(homeRef, "td", 3)) : (isNewInterface ? framesRef.parentNode : getParent(framesRef, "td", 3));
    if(!menuAnchor) {
        return;
    }
    const superSets = JSON.parse(getPlayerValue("SuperSets", "[]"));
    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 = setsMenuPosition == "left" ? (anchorRect.left - borderWidth - 1) : (anchorRect.right + borderWidth + 1);
    let previousWidth = 0;
    for(let i = preferences.length - 1; i >= 0; i--) {
        const currentPreferences = preferences[i];
        const mainMenuItemId = `SetsMenuItem${i}`;
        let mainMenuItem = document.getElementById(mainMenuItemId);
        let selectedValueHidden = document.getElementById(`hwmSetsMaster${currentPreferences.name}SelectedValue`);
        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", { id: mainMenuItemId, style: menuHeaderStyle }, document.body);
            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", { innerHTML: itemContent, style: `color: ${foreColor}; text-decoration: none;` }, mainMenuItem);
            } else {
                itemChild = addElement("a", { innerHTML: itemContent, href: currentPreferences.setReferencePage, style: `color: ${foreColor}; text-decoration: none;` }, mainMenuItem);
            }
            if(itemTitle != "") {
                itemChild.title = itemTitle;
            }
            selectedValueHidden = addElement("div", { id: `hwmSetsMaster${currentPreferences.name}SelectedValue`, hidden: "hidden" }, mainMenuItem);
            if(i == 3) {
                createBuildButton();
            }
            visibleMunuItems[i] = [mainMenuItem];
        }
        mainMenuItem.style.height = `${menuItemHeight}px`;
        mainMenuItem.style.lineHeight = `${menuItemLineHeight}px`;
        mainMenuItem.style.top = `${menuItemTop}px`;

        const mainMenuItemRect = mainMenuItem.getBoundingClientRect();
        currentMenuItemLeft = currentMenuItemLeft + (setsMenuPosition == "left" ? (- mainMenuItemRect.width) : previousWidth);
        previousWidth = mainMenuItemRect.width;
        mainMenuItem.style.left = `${currentMenuItemLeft}px`;

        const menuContent = getOrCreateAndResizeDropdown(i, mainMenuItem, ` z-index: ${zIndex}; 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", { type: "disc", style: "text-align: left;" }, menuContent);
            const currentSetReference = addElement("b", { id: `${currentPreferences.getCurrentSetName()}SetReference${currentSet.number}`, innerHTML: currentSet.name, title: currentSet.title || "", style: `color: ${foreColor}; cursor: pointer;` }, dropDownMenuItem);
            if(currentSet.url) {
                currentSetReference.addEventListener("click", function() { applySet(currentSetReference, currentPreferences, currentSet); });
            }
            if(currentSet.number == currentSetNumber) {
                selectedValueHidden.innerHTML = currentSet.number;
                markCurrent(currentSetReference, currentPreferences, currentSet.number);
            }
            currentPreferences.menuItems[currentSet.number] = currentSetReference;

            if(i == 3) {
                const fractionBuilds = superSets.filter(x => x.Fraction == currentSet.number);
                if(fractionBuilds.length > 0) {
                    const superDropdown = getOrCreateAndResizeDropdown(i, currentSetReference, ` z-index: ${Number(zIndex) + 1}; list-style-position: inside; color: ${foreColor}; padding: 2px 3px 2px 3px; white-space: nowrap; background: ${backgroundColor};`, true);
                    for(const build of fractionBuilds) {
                        const superDropdownMenuItem = addElement("li", { type: "disc", style: "text-align: left;" }, superDropdown);
                        const html = `${build.Name} <span id="deleteSuperSet${build.Id}Button" title='${isEn ? "Delete" : "Удалить"}' style="cursor: pointer; display: inline; color: yellow;">&times;</span>`
                        const superDropdownText = addElement("b", { name: "superDropdownText",  innerHTML: html, style: `color: ${foreColor}; cursor: pointer;` }, superDropdownMenuItem);
                        document.getElementById(`deleteSuperSet${build.Id}Button`).addEventListener("click", function(e) { e.stopPropagation(); deleteSuperSet(build.Id, superDropdownMenuItem); });
                        superDropdownMenuItem.addEventListener("click", function() { applyBuild(superDropdownText, build); });
                        if(build.Id == getPlayerValue("SuperSet")) {
                            superDropdownText.style.color = '#0f0';
                        }
                    }
                    superDropdown.style.display = "none";
                }
            }
            let currentWidth = getTextWidth(currentSet.name, getCanvasFont(currentSetReference));
            if(maxClientWidth < currentWidth) {
                maxClientWidth = currentWidth;
            }
        }
        if(i == 3) {
            const showAddSuperSetButtonLable = addElement("label", { for: "showAddSuperSetButtonCheckbox", innerText: (isEn ? 'Show button "Create super set"' : 'Показать кнопку "Создать билд"') + "\t" }, menuContent);
            const showAddSuperSetButtonCheckbox = addElement("input", { id: "showAddSuperSetButtonCheckbox", type: "checkbox" }, menuContent);
            showAddSuperSetButtonCheckbox.checked = getPlayerBool("ShowAddSuperSetButton", true);
            showAddSuperSetButtonCheckbox.addEventListener("change", function() { setPlayerValue("ShowAddSuperSetButton", this.checked); }, false);
            
            addElement("br", { }, menuContent);

            const showMenyAtRightLable = addElement("label", { for: "showMenyAtRightCheckbox", innerText: (isEn ? 'Show menu at right' : 'Показать меню справа') + "\t" }, menuContent);
            const showMenyAtRightCheckbox = addElement("input", { id: "showMenyAtRightCheckbox", type: "checkbox" }, menuContent);
            showMenyAtRightCheckbox.checked = getPlayerBool("ShowMenyAtRight");
            showMenyAtRightCheckbox.addEventListener("change", function() { setPlayerValue("ShowMenyAtRight", this.checked); }, false);
        }
        menuContent.style.minWidth = `${(maxClientWidth + 25)}px`;
        menuContent.style.display = "none";
    }
}
function createBuildButton() {
    if(!getPlayerBool("ShowAddSuperSetButton", true)) {
        return;
    }
    const fractionsMainMenuItem = document.getElementById(`SetsMenuItem3`);
    let addBuildButton = document.getElementById("addBuildButton");
    if(addBuildButton) {
        return;
    }
    if(isNewInterface) {
        addBuildButton = addElement("div", { id: "addBuildButton", class: "position_tr", innerHTML: `<img class="NotificationIcon" src="${resourcesPath}/i/new_top/_panelBattles.png" style="width: 16px; height: 16px;" title="${isEn ? "Create build" : "Создать билд"}" >` }, fractionsMainMenuItem);
    } else {
        addBuildButton = addElement("a", { id: "addBuildButton", href: "javascript:void(0);", style: "text-decoration: none; vertical-align: bottom;", innerHTML: `<img src="${resourcesPath}/i/new_top/_panelBattles.png" style="width: 16px; height: 16px; border-radius: 50%;" title="${isEn ? "Create build" : "Создать билд"}" >` }, fractionsMainMenuItem);
    }
    addBuildButton.addEventListener("click", function(e) { e.stopPropagation(); addBuild(); });
}
function addBuild() {
    const superSetName = prompt(isEn ? "Enter build name" : "Введите название билда");
    if(superSetName) {
        //deletePlayerValue("SuperSets");
        const superSets = JSON.parse(getPlayerValue("SuperSets", "[]"));
        superSets.push({
            Id: Date.now(),
            Name: superSetName,
            Fraction: Fraction,
            WeaponSet: getPlayerValue(weaponSetsPreferences.getCurrentSetName(), -1),
            SkillSet: getPlayerValue(skillSetsPreferences.getCurrentSetName(), -1),
            ArmySet: getPlayerValue(armySetsPreferences.getCurrentSetName(), -1)
        });
        setPlayerValue("SuperSets", JSON.stringify(superSets));
    }
}
function deleteSuperSet(id, menuItem) {
    const superSets = JSON.parse(getPlayerValue("SuperSets", "[]"));
    const deletingIndex = superSets.findIndex(x => x.Id == id);
    if(deletingIndex > -1) {
        superSets.splice(deletingIndex, 1);
        setPlayerValue("SuperSets", JSON.stringify(superSets));
        if(menuItem) {
            menuItem.remove();
        }
    }
}
async function applyBuild(superDropdownText, build) {
    const sets = [weaponSetsPreferences.name, skillSetsPreferences.name, armySetsPreferences.name];
    const originalText = superDropdownText ? superDropdownText.innerHTML : "";
    if(superDropdownText) {
        superDropdownText.innerHTML += " " + getWheelImage();
    }
    if(build.Fraction != Fraction) {
        await applySet(document.getElementById(`${fractionsPreferences.getCurrentSetName()}SetReference${build.Fraction}`), fractionsPreferences, fractionsPreferences.sets.find(x => x.number == build.Fraction), false);
        sets.push(fractionsPreferences.name);
    }
    await applySet(document.getElementById(`${weaponSetsPreferences.getCurrentSetName()}SetReference${build.WeaponSet}`), weaponSetsPreferences, weaponSetsPreferences.sets.find(x => x.number == build.WeaponSet), false);
    await applySet(document.getElementById(`${skillSetsPreferences.getCurrentSetName()}SetReference${build.SkillSet}`), skillSetsPreferences, skillSetsPreferences.sets.find(x => x.number == build.SkillSet), false);
    await applySet(document.getElementById(`${armySetsPreferences.getCurrentSetName()}SetReference${build.ArmySet}`), armySetsPreferences, armySetsPreferences.sets.find(x => x.number == build.ArmySet), false);
    await updatePanels(sets);
    setPlayerValue("SuperSet", build.Id);
    if(superDropdownText) {
        Array.from(document.querySelectorAll("b[name=superDropdownText]")).forEach(x => x.style.color = "#f5c137");
        superDropdownText.innerHTML = originalText;
        superDropdownText.style.color = '#0f0';
    }
}
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); });
}
async function applySet(selectedMenuItem, currentPreferences, currentSet, callPageRefreshFunction = true) {
    markCurrent(selectedMenuItem, currentPreferences, currentSet.number);
    const originalText = selectedMenuItem ? selectedMenuItem.innerHTML : "";
    if(selectedMenuItem) {
        selectedMenuItem.innerHTML += " " + getWheelImage();
    }
    if(currentSet.method == "POST") {
        await postRequest(currentSet.url, currentSet.data);
    } else {
        await getRequest(currentSet.url);
    }
    if(selectedMenuItem) {
        selectedMenuItem.innerHTML = originalText;
        if(isMobileDevice) {
            selectedMenuItem.parentNode.parentNode.style.display = "none";
        }
    }
    const selectedValueDiv = document.getElementById(`hwmSetsMaster${currentPreferences.name}SelectedValue`);
    if(selectedValueDiv) {
        selectedValueDiv.innerHTML = currentSet.number;
    }
    if(typeof(currentPreferences.setChanged) == "function") {
        await currentPreferences.setChanged(currentSet.number);
    }
    if(callPageRefreshFunction) {
        updatePanels([currentPreferences.name]);
    }
}
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", { 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) }, skillValueContainer);
            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 win.hwm_hints_init === 'function') win.hwm_hints_init();
            }
        } else {
            increaseAmountInput.value = currentValue;
        }
    }
    if(!isNewPersonPage) {
        location.reload();
    }
}
function getWheelImage() { return `<img border="0" align="absmiddle" height="11" src="${resourcesPath}/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"));
}
async function updatePanels(sets) {
    let pageReloadNeeded = false;
    let panels = [];
    if(sets.includes(weaponSetsPreferences.name) && ["/home.php", "/inventory.php", "/pl_info.php", "/map.php"].includes(location.pathname)) {
        pageReloadNeeded = true;
        if(location.pathname == '/home.php') {
            pushNew(panels, homeArtsPanelSelector);
            pushNew(panels, homeStatsPanelSelector);
            pageReloadNeeded = false;
        }
        if(location.pathname == '/pl_info.php') {
            if(getUrlParamValue(location.href, "id") == PlayerId) {
                pushNew(panels, playerInfoArtsPanelSelector);
                pushNew(panels, playerInfoStatsPanelSelector);
            }
            pageReloadNeeded = false;
        }
        if(location.pathname == '/map.php') {
            pushNew(panels, mapHuntButtons2PanelSelector);
            pushNew(panels, mapMercenaryTaskPanelSelector);
            pageReloadNeeded = false;
        }
    }
    if(sets.includes(skillSetsPreferences.name) && ["/skillwheel.php", "/pl_info.php", "/home.php", "/inventory.php"].includes(location.pathname)) {
        pageReloadNeeded = true;
        if(location.pathname == '/home.php') {
            pushNew(panels, homeStatsPanelSelector);
            pageReloadNeeded = false;
        }
        if(location.pathname == '/pl_info.php') {
            if(getUrlParamValue(location.href, "id") == PlayerId) {
                pushNew(panels, playerInfoStatsPanelSelector);
                pushNew(panels, playerInfoPerksPanelSelector);
            }
            pageReloadNeeded = false;
        }
        if(location.pathname == '/inventory.php') {
            pushNew(panels, inventoryStatsPanelSelector);
            pageReloadNeeded = false;
        }
    }
    if(sets.includes(armySetsPreferences.name) && ["/home.php", "/army.php", "/pl_info.php"].includes(location.pathname)) {
        pageReloadNeeded = true;
        if(location.pathname == '/home.php') {
            pushNew(panels, homeArmyPanelSelector);
            pageReloadNeeded = false;
        }
        if(location.pathname == '/pl_info.php') {
            if(getUrlParamValue(location.href, "id") == PlayerId) {
                pushNew(panels, playerInfoArmyPanelSelector);
            }
            pageReloadNeeded = false;
        }
    }
    if(sets.includes(fractionsPreferences.name) && ["/home.php", "/army.php", "/pl_info.php", "/castle.php", "/inventory.php", "/skillwheel.php"].includes(location.pathname)) {
        pageReloadNeeded = true;
    }
    if(pageReloadNeeded) {
        if(location.pathname == "/army.php") {
            location = "/army.php";
        } else {
            window.location.reload();
        }
    } else {
        await refreshUpdatePanels(panels);
    }
}
// API
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`; }