hwmAdvancedPlayerInfo

Добавляет в инфу персов и на домашнюю: ХП армии, очки и загрузку навыков, баланс рулетки и таверны, сумму умений, перекач, день рождения персонажа

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name hwmAdvancedPlayerInfo
// @author alex_kocharin 2008-2012, Demin 2013-2015, Tamozhnya1 2024
// @namespace Tamozhnya1
// @version 17.5
// @description Добавляет в инфу персов и на домашнюю: ХП армии, очки и загрузку навыков, баланс рулетки и таверны, сумму умений, перекач, день рождения персонажа
// @include     /^https{0,1}:\/\/(www|my)\.(heroeswm|lordswm)\.(ru|com)\/(home|pl_info)\.php/
// @grant GM_deleteValue
// @grant GM_getValue
// @grant GM_setValue
// @grant GM.xmlHttpRequest
// @license MIT
// ==/UserScript==

if(typeof GM_deleteValue != 'function') {
    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]; };
}
const playerIdMatch = document.cookie.match(/pl_id=(\d+)/);
if(playerIdMatch) {
    var PlayerId = playerIdMatch[1];
}
const windowObject = window.wrappedJSObject || unsafeWindow;
const lang = document.documentElement.lang || (location.hostname == "www.lordswm.com" ? "en" : "ru");
const isEn = lang == "en";
let playerFraction;
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");
        playerFraction = currentFractionIconImg.src.split("i/f/r")[1].split(".png")[0];
    }
} else {
    const playerFractionExec = /\/i\/f\/r(\d+)\.png/.exec(document.querySelector("body").innerHTML);
    //console.log(playerFractionExec);
    if(!playerFractionExec) {
        console.log("Unknown player fraction");
        return;
    };
    playerFraction = playerFractionExec[1];
    //console.log(playerFraction);
}
const isNewPersonPage = document.querySelector("div#hwm_no_zoom") ? true : false;
let playerLevel;
if(isNewPersonPage) {
    const levelInfoCell = Array.from(document.querySelectorAll("div.home_pers_info")).find(x => x.innerHTML.includes(isEn ? "Combat level" : "Боевой уровень"));
    playerLevel = parseInt(levelInfoCell.querySelector("div[id=bartext] > span").innerText);
} else {
    playerLevel = parseInt(new RegExp(`<b>${isEn ? "Combat level" : "Боевой уровень"}: (\\d+?)<\\/b>`).exec(document.documentElement.innerHTML)[1]) || 0;
}
const unitsHealth = {
    peasant: [1, 4],
    conscript: [1, 6],
    archer: [1, 7],
    marksman: [1, 10],
    footman: [1, 16],
    squire: [1, 26],
    griffon: [1, 30],
    impergriffin: [1, 35],
    priest: [1, 54],
    inquisitor: [1, 80],
    cavalier: [1, 90],
    paladin: [1, 100],
    angel: [1, 180],
    archangel: [1, 220],

    brute: [101, 8],
    crossman: [101, 8],
    vindicator: [101, 23],
    battlegriffon: [101, 52],
    zealot: [101, 80],
    champion: [101, 100],
    seraph2: [101, 220],

    skeleton: [2, 4],
    skeletonarcher: [2, 4],
    zombie: [2, 17],
    plaguezombie: [2, 17],
    ghost: [2, 8],
    spectre: [2, 19],
    vampire: [2, 30],
    vampirelord: [2, 35],
    lich: [2, 50],
    archlich: [2, 55],
    wight: [2, 95],
    wraith: [2, 100],
    bonedragon: [2, 150],
    spectraldragon: [2, 160],

    sceletonwar: [102, 5],
    rotzombie: [102, 23],
    poltergeist: [102, 20],
    vampireprince: [102, 40],
    masterlich: [102, 55],
    banshee: [102, 110],
    ghostdragon: [102, 150],

    gremlin: [3, 5],
    mastergremlin: [3, 6],
    stone_gargoyle: [3, 15],
    obsgargoyle: [3, 20],
    iron_golem: [3, 18],
    steelgolem: [3, 24],
    mage: [3, 18],
    archmage: [3, 30],
    djinn: [3, 40],
    djinn_sultan: [3, 45],
    rakshasa_rani: [3, 120],
    rakshasa_raja: [3, 140],
    colossus: [3, 175],
    titan: [3, 190],

    saboteurgremlin: [103, 6],
    elgargoly: [103, 16],
    magneticgolem: [103, 28],
    battlemage: [103, 29],
    djinn_vizier: [103, 50],
    rakshasa_kshatra: [103, 135],
    stormtitan: [103, 190],

    pixel: [4, 5],
    sprite: [4, 6],
    dancer: [4, 12],
    wardancer: [4, 12],
    elf: [4, 10],
    masterhunter: [4, 14],
    druid: [4, 34],
    druideld: [4, 38],
    unicorn: [4, 57],
    silverunicorn: [4, 77],
    treant: [4, 175],
    ancienent: [4, 181],
    greendragon: [4, 200],
    emeralddragon: [4, 200],

    dryad_: [104, 6],
    winddancer: [104, 14],
    arcaneelf: [104, 12],
    ddhigh: [104, 34],
    pristineunicorn: [104, 80],
    savageent: [104, 175],
    crystaldragon: [104, 200],

    goblin: [5, 3],
    hobgoblin: [5, 4],
    wolfrider: [5, 10],
    wolfraider: [5, 12],
    orc: [5, 12],
    orcchief: [5, 18],
    ogre: [5, 50],
    ogremagi: [5, 65],
    rocbird: [5, 55],
    thunderbird: [5, 65],
    cyclop: [5, 85],
    cyclopking: [5, 95],
    behemoth: [5, 210],
    ancientbehemoth: [5, 250],

    goblinarcher: [105, 3],
    boarrider: [105, 14],
    orcrubak: [105, 20],
    ogrebrutal: [105, 70],
    firebird: [105, 65],
    cyclopod: [105, 100],
    dbehemoth: [105, 280],

    goblinmag: [205, 3],
    orcshaman: [205, 13],
    darkbird: [205, 60],
    hyenarider: [205, 13],
    ogreshaman: [205, 55],
    shamancyclop: [205, 105],
    cursedbehemoth: [205, 250],

    scout: [6, 10],
    assassin: [6, 14],
    stalker: [6, 15],
    maiden: [6, 16],
    fury: [6, 16],
    bloodsister: [6, 24],
    minotaur: [6, 31],
    minotaurguard: [6, 35],
    taskmaster: [6, 40],
    darkrider: [6, 40],
    grimrider: [6, 50],
    briskrider: [6, 50],
    hydra: [6, 80],
    deephydra: [6, 125],
    foulhydra: [6, 125],
    witch: [6, 80],
    shadow_witch: [6, 90],
    mistress: [6, 100],
    shadowdragon: [6, 200],
    blackdragon: [6, 240],
    reddragon: [6, 235],

    imp: [7, 4],
    familiar: [7, 6],
    horneddemon: [7, 13],
    hornedoverseer: [7, 13],
    hellhound: [7, 15],
    cerberus: [7, 15],
    succubus: [7, 20],
    succubusmis: [7, 30],
    hellcharger: [7, 50],
    stallion: [7, 66],
    pitfiend: [7, 110],
    pitlord: [7, 120],
    devil: [7, 166],
    archdevil: [7, 199],

    vermin: [107, 6],
    jdemon: [107, 13],
    hotdog: [107, 15],
    seducer: [107, 26],
    hellkon: [107, 66],
    pity: [107, 140],
    archdemon: [107, 211],

    defender: [8, 7],
    shieldguard: [8, 12],
    spearwielder: [8, 10],
    skirmesher: [8, 12],
    bearrider: [8, 25],
    blackbearrider: [8, 30],
    brawler: [8, 20],
    berserker: [8, 25],
    runepriest: [8, 60],
    runepatriarch: [8, 70],
    thane: [8, 100],
    thunderlord: [8, 120],
    firedragon: [8, 230],
    magmadragon: [8, 280],

    mountaingr: [108, 12],
    harpooner: [108, 10],
    whitebearrider: [108, 30],
    battlerager: [108, 30],
    runekeeper: [108, 65],
    flamelord: [108, 120],
    lavadragon: [108, 275],

    goblinus: [9, 3],
    trapper: [9, 4],
    fcentaur: [9, 6],
    ncentaur: [9, 9],
    warrior: [9, 12],
    mauler: [9, 12],
    shamaness: [9, 30],
    sdaughter: [9, 35],
    slayer: [9, 34],
    executioner: [9, 40],
    wyvern: [9, 90],
    foulwyvern: [9, 105],
    cyclopus: [9, 220],
    untamedcyc: [9, 225],

    goblinshaman: [109, 5],
    mcentaur: [109, 10],
    warmong: [109, 20],
    eadaughter: [109, 35],
    chieftain: [109, 48],
    poukai: [109, 120],
    bloodeyecyc: [109, 235],

    scorp: [10, 4],
    scorpup: [10, 5],
    duneraider: [10, 12],
    duneraiderup: [10, 12],
    shakal: [10, 24],
    shakalup: [10, 30],
    dromad: [10, 40],
    dromadup: [10, 45],
    priestmoon: [10, 50],
    priestsun: [10, 55],
    slon: [10, 100],
    slonup: [10, 110],
    anubis: [10, 160],
    anubisup: [10, 200]
};
const perkBranchCosts = {
    1: {
        knight_mark: 5,
        attack: 9,
        defense: 8,
        luck: 10,
        leadership: 7,
        dark: 8,
        light: 7
    },
    101: {
        benediction: 7,
        defense: 10,
        leadership: 10,
        enlightenment: 8,
        light: 6,
        summon: 10,
        sorcery: 7
    },
    2: {
        necr_soul: 5,
        defense: 11,
        enlightenment: 9,
        dark: 7,
        summon: 8,
        sorcery: 8
    },
    102: {
        powerraise: 6,
        attack: 11,
        luck: 12,
        enlightenment: 8,
        summon: 6,
        sorcery: 7
    },
    3: {
        magic_mirror: 7,
        enlightenment: 9,
        light: 10,
        summon: 8,
        destructive: 10,
        sorcery: 7
    },
    103: {
        nomagicdamage: 7,
        attack: 8,
        luck: 10,
        leadership: 9,
        enlightenment: 8,
        destructive: 9,
        sorcery: 8
    },
    4: {
        elf_shot: 7,
        attack: 10,
        defense: 9,
        luck: 7,
        leadership: 10,
        enlightenment: 10,
        light: 7,
        summon: 9
    },
    104: {
        zakarrow: 4,
        attack: 9,
        defense: 8,
        luck: 10,
        leadership: 8,
        enlightenment: 12
    },
    5: {
        barb_skill: 7,
        attack: 7,
        defense: 9,
        luck: 9,
        leadership: 10
    },
    105: {
        save_rage: 6,
        attack: 7,
        defense: 8,
        luck: 7,
        leadership: 9
    },
    205: {
        dark_blood: 5,
        defense: 9,
        luck: 10,
        leadership: 10,
        enlightenment: 8,
        dark: 6,
        sorcery: 7
    },
    6: {
        dark_power: 7,
        attack: 8,
        luck: 9,
        leadership: 10,
        enlightenment: 10,
        dark: 8,
        destructive: 9,
        sorcery: 7
    },
    106: {
        cre_master: 5,
        attack: 8,
        defense: 11,
        leadership: 8,
        luck: 10,
        enlightenment: 8,
        dark: 8
    },
    7: {
        hellfire: 6,
        attack: 7,
        defense: 9,
        luck: 10,
        dark: 8,
        destructive: 10,
        sorcery: 8
    },
    107: {
        consumecorpse: 5,
        defense: 9,
        enlightenment: 9,
        dark: 6,
        sorcery: 7
    },
    8: {
        runeadv: 7,
        defense: 9,
        destructive: 11,
        light: 7,
        leadership: 9,
        luck: 10
    },
    108: {
        firelord: 5,
        attack: 7,
        defense: 10,
        leadership: 8,
        luck: 8,
        destructive: 6,
    },
    9: {
        memoryblood: 6,
        attack: 7,
        enlightenment: 9,
        leadership: 9,
        luck: 10,
        defense: 9
    },
    10: {
        dayandnight: 7,
        attack: 8,
        defense: 10,
        leadership: 10,
        light: 6,
        dark: 6,
        sorcery: 9,
        enlightenment: 9
    }
};
addStyle(`
.bar_wrap {
    width: 120px;
    /*margin: 3px 0 3px 9px;*/
    border: 1px solid #1C1C1C;
    background-color: #8C7526;
    box-shadow: 0 0 1px #666, inset 0 1px 1px #222;
    background-image: linear-gradient(#65541B, #8C7526 50%, #65541B);
    display: inline-block;
}
.bar {
    height: 5px;
    background-color: #f9e37e;
    border-right: 1px solid #282828;
    box-shadow: inset 0 0 1px #ddd;
    background-image: linear-gradient(#e7ae6b, #be8d55 50%, #a57b4b 51%, #ae804c);
    transition: all 1s ease;
    max-width: 150px;
}
.bar:hover {
    animation: animate-stripes 3s linear infinite;
}
@keyframes animate-stripes {
    0% {background-position: 0 0;}
    100% {background-position: 0 22px;}
}
.htooltip, .htooltip: visited, .tooltip: active {
    color: #0077AA;
    text-decoration: none;
}
.htooltip:hover {
    color: #0099CC;
}
.htooltip > span {
    background-color: rgba(0,0,0, 0.8);
    border-radius: 5px 5px 0px 0px;
    box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
    color: #fff;
    margin-left: -1px;
    margin-top: -24px;
    opacity: 0;
    padding: 2px 5px;
    position: absolute;
    text-decoration: none;
    visibility: hidden;
    z-index: 10;
    transition: opacity 0.4s ease-in-out, visibility 0.4s ease-in-out;
}
.htooltip:hover > span {
    position: absolute;
    opacity: 1;
    visibility: visible;
}
.htooltip1 > span:first-child {
    background-color: rgba(0,0,0, 0.8);
    border-radius: 5px 5px 0px 0px;
    box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
    color: #fff;
    margin-left: -1px;
    margin-top: -24px;
    opacity: 0;
    padding: 2px 5px;
    position: absolute;
    text-decoration: none;
    visibility: hidden;
    z-index: 10;
    transition: opacity 0.4s ease-in-out, visibility 0.4s ease-in-out;
}
.htooltip1:hover > span:first-child {
    position: absolute;
    opacity: 1;
    visibility: visible;
}
`);
var lang_en = {
    'Knight'               : 'Knight',
    'Necromancer'          : 'Necromancer',
    'Wizard'               : 'Wizard',
    'Elf'                  : 'Elf',
    'Barbarian'            : 'Barbarian',
    'Dark elf'             : 'Dark elf',
    'Demon'                : 'Demon',
    'Dwarf'                : 'Dwarf',
    'Steepe barbarian'     : 'Steepe barbarian',
    'Pharaoh'              : 'Pharaoh',
    'Combat level'         : 'Combat level',
    'Hunters\' guild'      : 'Hunters\' guild',
    'Laborers\' guild'     : 'Laborers\' guild',
    'Gamblers\' guild'     : 'Gamblers\' guild',
    'Thieves\' guild'      : 'Thieves\' guild',
    'Rangers\' guild'      : 'Rangers\' guild',
    'Mercenaries\' guild'  : 'Mercenaries\' guild',
    'Commanders\' guild'   : 'Commanders\' guild',
    'Watchers\' guild'     : 'Watchers\' guild',
    'Adventurers\' guild'  : 'Adventurers\' guild',
    'Leaders\' Guild'      : 'Leaders\' Guild',
    'Smiths\' guild'       : 'Smiths\' guild',
    'Enchanters\' guild'   : 'Enchanters\' guild',
    'Enchanters'   : 'Enchanters'
};
var lang_ru = {
    'Knight'               : 'Рыцарь',
    'Necromancer'          : 'Некромант',
    'Wizard'               : 'Маг',
    'Elf'                  : 'Эльф',
    'Barbarian'            : 'Варвар',
    'Dark elf'             : 'Темный эльф',
    'Demon'                : 'Демон',
    'Dwarf'                : 'Гном',
    'Steepe barbarian'     : 'Степной варвар',
    'Pharaoh'              : 'Фараон',
    'Combat level'         : 'Боевой уровень',
    'Hunters\' guild'      : 'Гильдия Охотников',
    'Laborers\' guild'     : 'Гильдия Рабочих',
    'Gamblers\' guild'     : 'Гильдия Картежников',
    'Thieves\' guild'      : 'Гильдия Воров',
    'Rangers\' guild'      : 'Гильдия Рейнджеров',
    'Mercenaries\' guild'  : 'Гильдия Наемников',
    'Commanders\' guild'   : 'Гильдия Тактиков',
    'Watchers\' guild'     : 'Гильдия Стражей',
    'Adventurers\' guild'  : 'Гильдия Искателей',
    'Leaders\' Guild'      : 'Гильдия Лидеров',
    'Smiths\' guild'       : 'Гильдия Кузнецов',
    'Enchanters\' guild'   : 'Гильдия Оружейников',
    'Enchanters'   : 'Оружейников'
};
const text = isEn ? lang_en : lang_ru;
const fractions = isEn ? ["Knight", "Necromancer", "Wizard", "Elf", "Barbarian", "Dark elf", "Demon", "Dwarf", "Tribal", "Pharaoh"] : ["Рыцарь", "Некромант", "Маг", "Эльф", "Варвар", "Темный эльф", "Демон", "Гном", "Степной варвар", "Фараон"];

main();
async function main() {
    tavernAndRouletteBalances();
    birthday();
    await getTalentsToHome();
    if(location.pathname == "/pl_info.php") {
        talentsStatistics();
    }
    calcArmyHealth(); // После загрузки навыков, если она есть, чтоб обработать навык "vitality"
    showExpBar();
    setTimeout(function() {
        const talentsChanger = document.getElementById("hwmSetsMasterSkillSetSelectedValue");
        const armyChanger = document.getElementById("hwmSetsMasterArmySetSelectedValue");
        if(talentsChanger) {
            if(location.pathname == "/pl_info.php" && getUrlParamValue(location.href, "id") == PlayerId) { 
                observe([talentsChanger], talentsStatistics);
            }
            if(location.pathname == "/home.php" || location.pathname == "/pl_info.php" && getUrlParamValue(location.href, "id") == PlayerId) {
                const panels = [armyChanger];
                if(location.pathname == "/pl_info.php" && getUrlParamValue(location.href, "id") == PlayerId) {
                    panels.push(talentsChanger);
                }
                observe(panels, calcArmyHealth);
            }
        }
    }, 1500); // Здесь ждем загрузки hwmSetsMaster, чтоб подписаться на элемент hwmSetsMasterSkillSetSelectedValue и hwmSetsMasterArmySetSelectedValue
}
async function getTalentsToHome() {
    if(playerLevel < 5) {
        return;
    }
    if(location.pathname == '/home.php') {
        let hwmAdvancedPlayerInfoTalentsContainer = document.getElementById("hwmAdvancedPlayerInfoTalentsContainer");
        if(!hwmAdvancedPlayerInfoTalentsContainer) {
            let homePageTalentsContainer;
            if(isNewPersonPage) {
                homePageTalentsContainer = document.querySelector("div.home_friends_block");
            } else {
                homePageTalentsContainer = document.querySelector("body > center > table:nth-child(2) > tbody > tr:nth-child(1) > td:nth-child(1) > table > tbody > tr:nth-child(2) > td:nth-child(1) > table > tbody > tr > td > table > tbody > tr > td");
            }
            addElement("br", homePageTalentsContainer);
            const ShowTalentsTitleSpan = addElement("span", homePageTalentsContainer, { id: "hwmAdvancedPlayerInfoTalentsCaptionContainer", innerHTML: `&nbsp;&raquo;&nbsp;<b>${isEn ? "Talents" : "Навыки"}:</b><b id="hwmAdvancedPlayerInfoTalentsPointsContainer"></b>`, title: getShowTalentsTitleSpanTitle() });
            ShowTalentsTitleSpan.addEventListener("click", function() { setValue("ShowTalents", !getBool("ShowTalents")); this.title = getShowTalentsTitleSpanTitle(); getTalentsToHomeCore(); }, false);
            addElement("br", homePageTalentsContainer);
            hwmAdvancedPlayerInfoTalentsContainer = addElement("div", homePageTalentsContainer, { id: "hwmAdvancedPlayerInfoTalentsContainer" });
        }
        await getTalentsToHomeCore();
        const talentsChanger = document.getElementById("hwmSetsMasterSkillSetSelectedValue");
        if(talentsChanger) {
            observe([talentsChanger], getTalentsToHomeCore);
            //setTimeout(function() { observe([talentsChanger], getTalentsToHomeCore); }, 3000);
        }
    }
}
async function getTalentsToHomeCore() {
    if(location.pathname == '/home.php' && getBool("ShowTalents")) {
        const doc = await getRequest(`/pl_info.php?id=${PlayerId}`);
        const perksTable = getParent(doc.querySelector("a[href^='showperkinfo.php']"), "table", 2);
        if(perksTable) {
            Array.from(perksTable.querySelectorAll("a[href^='showperkinfo.php'] > img")).forEach(x => { x.style.width = `48px`; x.style.height = "auto"; });
            //Array.from(perksTable.querySelectorAll("a[href^='showperkinfo.php'] > img")).forEach(x => { x.setAttribute("title", x.getAttribute("hint")); x.style.width = `48px`; x.style.height = "auto"; });
            document.getElementById("hwmAdvancedPlayerInfoTalentsContainer").innerHTML = perksTable.outerHTML;
            if(typeof windowObject.hwm_hints_init === 'function') windowObject.hwm_hints_init();
            talentsStatistics();
            calcArmyHealth();
        }
    }
}
function getShowTalentsTitleSpanTitle() { return isEn ? `Click for ${getBool("ShowTalents") ? "disable" : "enable"} talents loading` : `Нажмите для ${getBool("ShowTalents") ? "выключения" : "включения"} загрузки навыков`; }
function talentsStatistics() {
    if(playerLevel < 5) {
        return;
    }
    let talentsContainer;
    let talentsCaptionContainer;
    let talentsPointsContainer;
    if(location.pathname == "/home.php") {
        talentsContainer = document.getElementById("hwmAdvancedPlayerInfoTalentsContainer");
        talentsCaptionContainer = document.getElementById("hwmAdvancedPlayerInfoTalentsCaptionContainer");
        talentsPointsContainer = document.getElementById("hwmAdvancedPlayerInfoTalentsPointsContainer");
    }
    if(location.pathname == "/pl_info.php") {
        talentsContainer = getParent(document.querySelector("a[href^='showperkinfo.php?name=']"), "td", 3);
        const layerTable = getParent(talentsContainer, "table");
        talentsCaptionContainer = Array.from(layerTable.rows[0].cells).find(x => x.innerHTML.includes(isEn ? "Talents" : "Навыки")).querySelector("b");
        talentsPointsContainer = document.getElementById("hwmAdvancedPlayerInfoTalentsPointsContainer") || addElement("span", talentsCaptionContainer, { id: "hwmAdvancedPlayerInfoTalentsPointsContainer" });
    }
    if(!talentsContainer || !talentsCaptionContainer) {
        return;
    }
    const talentBranchRows = talentsContainer.querySelectorAll(":scope > table > tbody > tr");
    const playerTalentBranches = Array.from(talentBranchRows).map(x => Array.from(x.querySelectorAll("a[href^='showperkinfo.php?name=']")).map(y => getUrlParamValue(y.href, "name")));
    //console.log(playerTalentBranches);
    let talentPoints = 0;
    const playerPerkBranchCosts = perkBranchCosts[playerFraction];
    //console.log(playerPerkBranchCosts);
    for(const playerPerkBranche of playerTalentBranches) {
        let branchName = playerPerkBranche[0];
        let firstPerkPromout = 1;
        if(["1", "2", "3"].includes(branchName.slice(-1))) {
            firstPerkPromout = parseInt(branchName.slice(-1));
            branchName = branchName.slice(0, -1);
        }
        //console.log(`branchName: ${branchName}, branchCost: ${playerPerkBranchCosts[branchName]}, branchePerks: ${playerPerkBranche.length}, firstPerkPromout: ${firstPerkPromout}`);
        talentPoints += playerPerkBranchCosts[branchName] * (playerPerkBranche.length + firstPerkPromout - 1);
    }
    talentsPointsContainer.innerText = ` (${isEn ? "points" : "очки"} ${talentPoints} ${isEn ? "from" : "из"} ${10 + 5 * (playerLevel - 5)})`;
}
function calcArmyHealth() {
    let armyContainer;
    if(location.pathname == "/pl_info.php") {
        armyContainer = getParent(document.querySelector(".cre_creature72"), "center");
    }
    if(location.pathname == "/home.php") {
        if(isNewPersonPage) {
            armyContainer = document.querySelector("div.home_css_creature_list");
        } else {
            armyContainer = getParent(document.querySelector(".cre_creature72"), "center");
        }
    }
    if(!armyContainer) {
        return;
    }
    const creatures = Array.from(document.querySelectorAll(`div.${isNewPersonPage ? "castle_creature54" : "cre_creature72"}`)).filter(x => x.querySelector("div#add_now_count")).map(x => ({ Name: getUrlParamValue(x.querySelector("a[href^='army_info.php?name=']").href, "name"),  Amount: parseInt(x.querySelector("div#add_now_count").innerText) }));
    const hasVitality = document.querySelector("a[href='showperkinfo.php?name=vitality']") ? true : false;
    const sum = creatures.reduce((t, x) => {
        const army = unitsHealth[x.Name]; 
        if(army) {
            t += x.Amount * (army[1] + (hasVitality ? 2 : 0));
        }
        return t;
    }, 0);
    const unknownCreatures = creatures.filter(x => !unitsHealth[x.Name]);
    let unknownCreaturesMessage = "";
    if(unknownCreatures.length > 0) {
        unknownCreaturesMessage = isEn ? `Creatures not found: ${unknownCreatures.map(x => x.Name).join(', ')}. Please, contact developer.` : `Существа не найдены: ${unknownCreatures.map(x => x.Name).join(', ')}. Пожалуйста, сообщите разработчику.`;
    }
    let hwmAdvancedPlayerInfoArmyHealthContainer = document.getElementById("hwmAdvancedPlayerInfoArmyHealthContainer");
    if(!hwmAdvancedPlayerInfoArmyHealthContainer) {
        armyContainer.insertAdjacentHTML("afterend", `&nbsp;»&nbsp;<b>${isEn ? "Total HP" : "Общее HP"}:&nbsp;</b><span id=hwmAdvancedPlayerInfoArmyHealthContainer>${sum}</span>
<a id=hwmAdvancedPlayerInfouUknownCreaturesMessageContainer href="javascript:alert('${unknownCreaturesMessage}');" title="${unknownCreaturesMessage}"> (?)</a>`);
        hwmAdvancedPlayerInfoArmyHealthContainer = document.getElementById("hwmAdvancedPlayerInfoArmyHealthContainer");
    }
    hwmAdvancedPlayerInfoArmyHealthContainer.innerText = sum;
    hwmAdvancedPlayerInfoArmyHealthContainer.style.color = hasVitality ? "#ff0000" : "";
    const hwmAdvancedPlayerInfouUknownCreaturesMessageContainer = document.getElementById("hwmAdvancedPlayerInfouUknownCreaturesMessageContainer");
    hwmAdvancedPlayerInfouUknownCreaturesMessageContainer.style.display = unknownCreaturesMessage ? "" : "none";
    hwmAdvancedPlayerInfouUknownCreaturesMessageContainer.title = unknownCreaturesMessage;
    hwmAdvancedPlayerInfouUknownCreaturesMessageContainer.href = `javascript:alert('${unknownCreaturesMessage}');`;
}
function tavernAndRouletteBalances() {
    if(location.pathname != "/pl_info.php") {
        return;
    }
    // Баланс рулетки
    const rouletteDebitText = findChildrenTextContainsValue("td", isEn ? "Roulette bets total" : "Поставлено в рулетке");
    const rouletteCreditText = findChildrenTextContainsValue("td", isEn ? "Roulette winnings total" : "Выиграно в рулетке");
    if(rouletteDebitText.length == 1 && rouletteCreditText.length == 1 && rouletteDebitText[0].nextSibling.tagName.toLowerCase() == "b" && rouletteCreditText[0].nextSibling.tagName.toLowerCase() == "b") {
        const rouletteBalance = rouletteCreditText[0].nextSibling.innerText.replace(/,/g, "") - rouletteDebitText[0].nextSibling.innerText.replace(/,/g, "");
        rouletteCreditText[0].nextSibling.insertAdjacentHTML("afterend", `<br>&nbsp;&nbsp;${isEn ? 'Balance' : 'Баланс'}: <b>${rouletteBalance.toLocaleString()}</b>`);
    }
    // Баланс таверны
    var statisticsSecondCell = getParent(document.querySelector("td.wb > a[href^='pl_transfers.php?id']"), "td").nextSibling;
    var tavern_parent = statisticsSecondCell.querySelector("tr").parentNode.childNodes[0].childNodes[1].firstChild.firstChild.childNodes;
    const winsRow = tavern_parent[1];
    const failsRow = tavern_parent[2];
    tavern_parent[0].childNodes[1].setAttribute('style', 'white-space: nowrap;');
    winsRow.childNodes[1].setAttribute('style', 'white-space: nowrap;');
    //console.log(winsRow)
    failsRow.childNodes[1].setAttribute('style', 'white-space: nowrap;');
    var tavern_0bal = winsRow.querySelector("tr");
    var tavern_1bal = failsRow.querySelector("tr");
    if(!tavern_0bal && !tavern_1bal) return;
    let wins = 0;
    let fails = 0;
    if(tavern_0bal) {
        wins = parseInt(winsRow.cells[1].querySelector("b").innerText.replace(/,/g, ""));
        tavern_0bal.childNodes[1].setAttribute('style', 'text-align: right; padding-right: 5px;');
        tavern_0bal = tavern_0bal.childNodes[1].innerHTML.replace(/,/g, "");
        winsRow.childNodes[3].firstChild.width = "100%";
        var totalRow = winsRow.cloneNode(true);
    } else {
        tavern_0bal = 0;
    }
    if(tavern_1bal) {
        fails = parseInt(failsRow.cells[1].querySelector("b").innerText.replace(/,/g, ""));
        tavern_1bal.childNodes[1].setAttribute('style', 'text-align: right; padding-right: 5px;');
        tavern_1bal = tavern_1bal.childNodes[1].innerHTML.replace(/,/g, "");
        failsRow.childNodes[3].firstChild.width = "100%";
        var totalRow = totalRow || failsRow.cloneNode(true);
    } else {
        tavern_1bal = 0;
    }
    var tavern_bal = tavern_0bal - tavern_1bal;
    totalRow.firstChild.innerHTML = `&nbsp;&nbsp;${isEn ? 'Balance:' : 'Баланс:'}`;
    totalRow.childNodes[1].innerHTML = `<b>${(wins - fails).toLocaleString()}</b>`;
    totalRow.childNodes[2].innerHTML = "&nbsp;";
    totalRow.childNodes[3].innerHTML = `
<table border="0" cellspacing="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td>
                <img class="rs" width="24" height="24" src="https://dcdn2.heroeswm.ru/i/r/48/gold.png?v=3.23de65" border="0" title="${isEn ? "Gold" : "Золото"}" alt="">
            </td>
            <td style="text-align: right; padding-right: 5px;">
            ${tavern_bal.toLocaleString()}
            </td>
        </tr>
    </tbody>
</table>`;
    failsRow.insertAdjacentElement("afterend", totalRow);
}
function birthday() {
    if(location.pathname == "/pl_info.php") {
        const playerId = getUrlParamValue(location.href, "id");
        let bifthday = GM_getValue(`PlayerBifthday${playerId}`, "");
        const districtBold = Array.from(document.querySelectorAll("b")).find(x => x.innerHTML.includes(isEn ? "Location" : "Район"));
        districtBold.nextElementSibling.nextElementSibling.insertAdjacentHTML("afterend", `&nbsp;&raquo;&nbsp;<b id=hwmAdvancedPlayerInfoBirthdayCaption title="${isEn ? "Click to download birthday" : "Нажмите для загрузки дня рождения"}">${isEn ? "Birthday" : "День рождения"}: </b><span id=hwmAdvancedPlayerInfoBirthday>${bifthday}</span>`);
        document.getElementById("hwmAdvancedPlayerInfoBirthdayCaption").addEventListener("click", loadBirthday);
    }
}
async function loadBirthday() {
    const playerId = getUrlParamValue(location.href, "id");
    const firstProtocolPage = await getRequest(`/pl_transfers.php?id=${playerId}&page=50000`);
    const lines = firstProtocolPage.querySelector(`div#set_mobile_max_width > div > div`).innerHTML.split("<br>").filter(x => x != "");
    bifthday = lines[lines.length - 2];
    bifthday = bifthday.replace(/&nbsp;/g, "");
    GM_setValue(`PlayerBifthday${playerId}`, bifthday);
    document.getElementById("hwmAdvancedPlayerInfoBirthday").innerHTML = bifthday;
}
function showExpBar() {
    const player = { SkillLevel: [], SkillNumber: []};
    let skillInfoCell;
    if(isNewPersonPage) {
        const levelInfoCell = Array.from(document.querySelectorAll("div.home_pers_info")).find(x => x.innerHTML.includes(text['Combat level']));
        player.Level = parseInt(levelInfoCell.querySelector("div[id=bartext] > span").innerText);
        player.Expirience = parseInt(levelInfoCell.querySelector("div.home_text_exp").firstChild.textContent.replace(/,/g, ""));
        
        const homeContainerBlocks = Array.from(document.querySelectorAll("div#set_mobile_max_width > div > div.home_container_block"));
        //console.log(homeContainerBlocks)
        skillInfoCell = homeContainerBlocks.find(x => x.innerHTML.includes(text['Knight']));
        Array.from(skillInfoCell.querySelectorAll("div[id=row]")).forEach((x, i) => {
            player.SkillLevel[i] = parseInt(x.querySelector("div#bartext span").innerText);
            player.SkillNumber[i] = parseFloat(x.querySelector("div.home_text_exp").firstChild.textContent);
        });
        const guildsDiv = homeContainerBlocks.find(x => x.innerHTML.includes(text['Enchanters']));
        Array.from(guildsDiv.querySelectorAll("div[id=row]")).forEach(x => {
            const key = findKey(text, y => text[y].includes(x.querySelector("span.home_guild_text").innerText));
            if(key) {
                player[key] = parseInt(x.querySelector("div#bartext span").innerText);
            }
        });
    } else {
        const levelInfoBold = Array.from(document.querySelectorAll("td > b")).find(x => x.innerHTML.includes(text['Combat level']));
        const levelRegex = new RegExp(`${text['Combat level']}: (\\d{1,2})`);
        player.Level = parseInt(levelRegex.exec(levelInfoBold.innerHTML)[1]);
        const expirienceRegex = /\(([\d\.\,]+)\)/g;
        const expirienceRegexExec = expirienceRegex.exec(levelInfoBold.parentNode.innerHTML);
        if(expirienceRegexExec) {
            player.Expirience = parseInt(expirienceRegexExec[1].replace(/,/g, ""));
        }
        skillInfoCell = Array.from(document.querySelectorAll("td")).find(x => x.innerHTML.includes(text['Knight']) && x.innerHTML.includes(text['Enchanters\' guild']) && !x.innerHTML.includes("<td"));
        const regex = /\(([\d\.\,]+)\)/g;
        for(const fraction of fractions) {
            const skillRegex = new RegExp(`${fraction}: (\\d{1,2})`);
            const skillsData = skillRegex.exec(skillInfoCell.innerHTML);
            player.SkillLevel.push(parseInt(skillsData[1]));
            const skillsNumberData = regex.exec(skillInfoCell.innerHTML);
            let skillNumber = 0;
            if(skillsNumberData) {
                skillNumber = parseFloat(skillsNumberData[1]);
            }
            player.SkillNumber.push(skillNumber);
        }
        for(const key of Object.keys(text).filter(x => x.includes("guild") || x.includes("Guild"))) {
            const guildName = text[key];
            let guildRegex = new RegExp(`&nbsp;&nbsp;${guildName}: (\\d{1,2}) \\(`, "g");
            if(key == 'Hunters\' guild') {
                guildRegex = new RegExp(`&nbsp;&nbsp;${guildName}: .+>(\\d{1,2})<`, "g");
            }
            player[key] = parseInt(guildRegex.exec(skillInfoCell.innerHTML)[1]);
        }
    }
    player.GuildStatsNumber = getGuildStatsNumber(player);
    player.MainSkillStatsNumber = getMainSkillStatsNumber(player);
    player.SkillSum = Math.floor(player.SkillLevel.reduce((t, x) => t + x, 0) * 0.25);
    player.TotalPoints = player.GuildStatsNumber + player.MainSkillStatsNumber + player.SkillSum;
    player.TotalSkills = round00(player.SkillNumber.reduce((t, x) => t + x, 0));
    player.Ratio = player.TotalSkills > 0 ? Math.round(player.Expirience / player.TotalSkills) : 0;
    //console.log(player);
    if(player.Level > 2) {
        //console.log(`isNewPersonPage: ${isNewPersonPage}`)
        const playerPersents = getPlayerPercents(player);
        let resumeText =  "В норме!";
        let resumeTitle =  "";
        const prevEdgeSpan = getSpanHtml(playerPersents.prevEdge, playerPersents.prevEdgeClarification);
        const totalPointsSpan = getSpanHtml(player.TotalPoints, isEn ? "Player points" : "Очков игрока");
        const guildStatsNumberSpan = getSpanHtml(player.GuildStatsNumber, isEn ? "total guilds parameters" : "сумма параметров от всех гильдий");
        const skillSumSpan = getSpanHtml(player.SkillSum, isEn ? "total factions skill levels*0.25" : "сумма уровней умений фракций*0.25");
        const mainSkillStatsNumberSpan = getSpanHtml(player.MainSkillStatsNumber, isEn ? "max faction skill level parameters (with possible faction potion)" : "сумма параметров от максимального умения фракции(с учётом возможного применения \"зелья фракции\")");
        const nextEdgeSpan = getSpanHtml(playerPersents.nextEdge, playerPersents.nextEdgeClarification);
        const progressText = `${isEn ? "Main progress" : "Основной прогресс"}: ${playerPersents.normalPercent}%. ${prevEdgeSpan}->${totalPointsSpan}=(${guildStatsNumberSpan}+${skillSumSpan}+${mainSkillStatsNumberSpan})->${nextEdgeSpan}`;
        if(playerPersents.advacedPersent > 0){
            resumeText = `Перекач! ${isEn ? "Exp" : "Опыт"}: +${playerPersents.advacedPersent}%`;
            //resumeTitle = `${isEn ? "More experience" : "Опыта больше на"}: ${playerPersents.advacedPersent}%`;
        }
        if(playerPersents.advacedPersent < 0) {
            resumeText = `Недокач!`;
            resumeTitle = `${isEn ? "more skill points will be awarded in proportion to the missing points" : "будет начисляться больше очков умений в соотношении к недостающим очкам"}`;
        }
        const resumeSpan = getSpanHtml(resumeText, resumeTitle);
        if(isNewPersonPage) {
            const ratioSpan = getSpanHtml(player.Ratio.toLocaleString(), isEn ? "Expirience points to one skill point" : "Очков опыта на одно очко умения");
            skillInfoCell.querySelector("div.home_inside_margins").insertAdjacentHTML("beforeend", `
<div class="home_scroll_content" id="row" onclick="show_all_hwm_exp();">
    <span class="home_guild_text">${isEn ? "Total skills" : "Сумма умений"}</span> <div class="home_text_exp" style="display: block;">${player.TotalSkills.toLocaleString()} (${ratioSpan})</div>
</div>
<div class="home_scroll_content htooltip1" id="row" onclick="show_all_hwm_exp();">
    <span>${progressText}</span>
    <span class="home_guild_text">${isEn ? "Progress" : "Прогресс"}
    <div id="bar" class="home_bar_exp" style="opacity: 1;"><div id="barprogress" style="width: ${playerPersents.normalPercent}%;"></div></div>
    <div class="home_text_exp" style="display: block;">${playerPersents.normalPercent}% ${resumeSpan}</div>
`);
        } else {
            const levelInfoBold = getParent(Array.from(document.querySelectorAll("td > b")).find(x => x.innerHTML.includes(isEn ? "Combat level:" : "Боевой уровень:")), "td").querySelector("br");
            levelInfoBold.insertAdjacentHTML("afterend", `
<span id=hwmAdvancedPlayerInfoTotalSkillsSpan>
    &nbsp;&raquo;&nbsp;<b>${isEn ? "Skills count" : "Сумма умений"}: </b>${player.TotalSkills.toLocaleString()}, ${isEn ? "ratio" : "соотношение"}: <span title="${isEn ? "Expirience points to one skill point" : "Очков опыта на одно очко умения"}">${ player.Ratio.toLocaleString() }</span>
</span>
<br>&nbsp;&raquo;&nbsp;<b>${isEn ? "Progress" : "Прогресс"}:</b> <div class="bar_wrap htooltip">
    <div class="bar" style="width: ${playerPersents.normalPercent}%"></div>
    <span>${progressText}</span>
</div>
<div style='font-size: 8px; font-weight: bold; display: inline-block;'>${playerPersents.normalPercent}% ${resumeSpan}</div>`);
        }
    }
}
function getSpanHtml(value, title) { return `<span title="${title}">${value}</span>`; }
function getGuildStatsNumber(player) {
    const mercStats = [0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11];
    const commandersStats = [0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
    const watchersStats = [0, 1, 2, 3, 4, 5, 6, 7, 7.5, 8.5];
    const leadersStats = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5, 0.5, 0.5, 0.5, 0.5, 1];
    const hunters = player['Hunters\' guild'];
    const laborers = Math.floor(player['Laborers\' guild'] / 2);
    const thieves = Math.max(player['Thieves\' guild'], player['Rangers\' guild']) / 2;
    const mercenaries = mercStats[player['Mercenaries\' guild']];
    const commanders = commandersStats[player['Commanders\' guild']];
    const watchers = watchersStats[player['Watchers\' guild']];
    const adventurers = player['Adventurers\' guild'];
    const leaders = leadersStats[player['Leaders\' Guild']];
    //console.log(`Hunters: ${hunters}, Laborers: ${laborers}, Thieves/Rangers: ${thieves}, Mercenaries: ${mercenaries}, Commanders: ${commanders}, Watchers: ${watchers}, Adventurers: ${adventurers}, Leaders: ${leaders}`);
    return hunters + laborers + thieves + mercenaries + commanders + watchers + adventurers + leaders;
}
function getMainSkillStatsNumber(player) {
    let skillLevel = player.SkillLevel.reduce((t, x) => Math.max(t, x), 0);
    const averageSkill = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 10, 11, 12, 12, 12, 13, 13, 13, 13, 14];
    skillLevel = Math.max(skillLevel, averageSkill[player.Level]);
    const skillStats = [0, 1, 2, 2.5, 4, 5.5, 6.5, 9, 11.5, 16, 21, 27, 33, 35, 36.5];
    return skillStats[skillLevel];
}
function getPlayerPercents(player) {
    //player.Level++; //для тестов
    const averagePoints = [0, 0, 0, 3, 5, 7, 10, 13, 17, 20, 25, 29, 35, 41, 48, 58, 68, 77, 84, 90, 96, 101, 108, 114,       125, 136, 147];
    const penaltyProcents = [0, 25, 500, 1000];
    // Ищем перекач // 101, 108, 114, 125
    let advacedPersent = 0;
    const nextLevels = averagePoints.slice(player.Level + 1, player.Level + 5);
    const prevLevels = averagePoints.slice(player.Level - 4, player.Level).reverse();
    //console.log(prevLevels)
    let beginNormalPoint = prevLevels[0];
    let endNormalPoint = nextLevels[0] + 2;
    //console.log(`beginNormalPoint: ${beginNormalPoint}, endNormalPoint: ${endNormalPoint}, player.TotalPoints: ${player.TotalPoints}, player.Level: ${player.Level}`)
    let prevEdge = beginNormalPoint;
    let nextEdge = endNormalPoint;
    let prevEdgeClarification = isEn ? `Average points on ${player.Level - 1} level` : `Средние очки на ${player.Level - 1} уровне`;
    let nextEdgeClarification = isEn ? `Average points + 2 on ${player.Level + 1} level` : `Средние очки + 2 на ${player.Level + 1} уровне`;
    if(player.TotalPoints <= endNormalPoint) {
        if(player.TotalPoints < beginNormalPoint) {
            // Недокач - будет начисляться больше очков умений в соотношении к недостающим очкам
        }
        if(player.TotalPoints < beginNormalPoint) {
            prevLevels.forEach((x, i) => {
                const add = i == 0 ? 0 : 0;
                if(player.TotalPoints < (x - add) && player.TotalPoints >= prevLevels[i + 1]) {
                    const beginPoint = x - add;
                    const endPoint = prevLevels[i + 1];
                    const beginProcent = -penaltyProcents[i] - 1;
                    const endProcent = penaltyProcents[i + 1];
                    const advacedPoints = beginPoint - player.TotalPoints;
                    const percentStep = Math.round((endProcent - beginProcent) / (endPoint - beginPoint));
                    advacedPersent = beginProcent + percentStep * advacedPoints;
                    //console.log(`excessLevel: ${i}, player.TotalPoints: ${player.TotalPoints}, levelAveragePoints: ${x}, add: ${add}, beginPoint: ${beginPoint}, endPoint: ${endPoint}, beginProcent: ${beginProcent}, endProcent: ${endProcent}, advacedPoints: ${advacedPoints}, percentStep: ${percentStep}, advacedPersent: ${advacedPersent}`);
                    prevEdge = endPoint;
                    nextEdge = beginPoint;
                    prevEdgeClarification = isEn ? `Average points on ${player.Level - (i + 1)} level` : `Средние очки на ${player.Level - (i + 1)} уровне`;
                    nextEdgeClarification = isEn ? `Average points on ${player.Level - i} level` : `Средние очки на ${player.Level - i} уровне`;
                }
                if(i == prevLevels.length - 1 && player.TotalPoints < prevLevels[i + 1]) {
                    advacedPersent = penaltyProcents[i + 1];
                    //console.log(`excessLevel: ${i}, player.TotalPoints: ${player.TotalPoints}, levelAveragePoints: ${x}, add: ${add}, advacedPersent: ${advacedPersent}`);
                }
            });
        }
    } else {
        nextLevels.forEach((x, i) => {
            const add = i == 0 ? 2 : 0;
            if(player.TotalPoints > (x + add) && player.TotalPoints <= nextLevels[i + 1]) {
                const beginPoint = x + add;
                const endPoint = nextLevels[i + 1];
                const beginProcent = penaltyProcents[i] + 1;
                const endProcent = penaltyProcents[i + 1];
                const advacedPoints = player.TotalPoints - beginPoint;
                const percentStep = Math.round((endProcent - beginProcent) / (endPoint - beginPoint));
                advacedPersent = beginProcent + percentStep * advacedPoints;
                //console.log(`excessLevel: ${i}, player.TotalPoints: ${player.TotalPoints}, levelAveragePoints: ${x}, add: ${add}, beginPoint: ${beginPoint}, endPoint: ${endPoint}, beginProcent: ${beginProcent}, endProcent: ${endProcent}, advacedPoints: ${advacedPoints}, percentStep: ${percentStep}, advacedPersent: ${advacedPersent}`);
                prevEdge = x + add;
                nextEdge = endPoint;
                prevEdgeClarification = isEn ? `Average points${add > 0 ? ` + ${add}` : ""} on ${player.Level + 1 + i} level` : `Средние очки${add > 0 ? ` + ${add}` : ""} на ${player.Level + 1 + i} уровне`;
                nextEdgeClarification = isEn ? `Average points on ${player.Level + 1 + i + 1} level` : `Средние очки на ${player.Level + 1 + i + 1} уровне`;
            }
            if(i == nextLevels.length - 1 && player.TotalPoints > nextLevels[i + 1]) {
                advacedPersent = penaltyProcents[i + 1];
                //console.log(`excessLevel: ${i}, player.TotalPoints: ${player.TotalPoints}, levelAveragePoints: ${x}, add: ${add}, advacedPersent: ${advacedPersent}`);
            }
        });
    }
    return {
        beginNormalPoint: beginNormalPoint,
        endNormalPoint: endNormalPoint,
        normalPercent: Math.max(Math.min(Math.round((player.TotalPoints - beginNormalPoint) * 100 / (endNormalPoint - beginNormalPoint)), 100), 0),
        advacedPersent: advacedPersent,
        prevEdge: prevEdge,
        nextEdge: nextEdge,
        prevEdgeClarification: prevEdgeClarification,
        nextEdgeClarification: nextEdgeClarification
    };
}

// API
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 getUrlParamValue(url, paramName) { return (new URLSearchParams(url.split("?")[1])).get(paramName); }
function findChildrenTextContainsValue(selector, value) { return Array.from(document.querySelectorAll(selector)).reduce((t, x) => { const match = Array.from(x.childNodes).filter(y => y.nodeName == "#text" && y.textContent.includes(value)); return [...t, ...match]; }, []); }
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 round00(value) { return Math.round(value * 100) / 100; }
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;
}
function addStyle(css) { addElement("style", document.head, { type: "text/css", innerHTML: css }); }
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 observe(targets, handler, config = { childList: true, subtree: true }) {
    targets = Array.isArray(targets) ? targets : [targets];
    targets = targets.map(x => { if(typeof x === 'function') { return x(document); } return x; }); // Можем передавать не элементы, а их селекторы
    const ob = new MutationObserver(async function(mut, observer) {
        //console.log(`Mutation start`);
        observer.disconnect();
        if(handler.constructor.name === 'AsyncFunction') {
            await handler();
        } else {
            handler();
        }
        for(const target of targets) {
            if(target) {
                observer.observe(target, config);
            }
        }
    });
    for(const target of targets) {
        if(target) {
            ob.observe(target, config);
        }
    }
}
function findKey(obj, selector) { return Object.keys(obj).find(selector); }