hwmAdvancedPlayerInfo

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

当前为 2025-05-07 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 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 20.5
// @description Добавляет в инфу персов и на домашнюю: ХП армии, очки и загрузку навыков, баланс рулетки и таверны, сумму умений, перекач, день рождения персонажа. Спойлеры. Статистика клана.
// @include     /^https{0,1}:\/\/(www|my)\.(heroeswm|lordswm)\.(ru|com)\/(home|pl_info|clan_info|tournaments)\.php/
// @grant GM_deleteValue
// @grant GM_getValue
// @grant GM_setValue
// @grant GM.xmlHttpRequest
// @require https://update.greasyfork.org/scripts/490927/1360667/Tamozhnya1Lib.js
// @license MIT
// ==/UserScript==

// Включить/выключить загрузку навыков на домашней странице можно щелчком по надписи "Навыки". День рождения загружается щелчком по надписи. Стоимость боя показывается скриптом hwmOptimalRepairAtMarket. Туда же перенесены расчет ОА и крафта.
// Оригинальный скрипт https://userscripts-mirror.org/scripts/show/178809 (скрипт демина с greasyfork почему-то удален, как и таймеры)

if(!PlayerId) {
    return;
}
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],
    wdancer: [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],
    shadow_witch: [6, 80],
    matriarch: [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],
    nightmare: [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: 9,
        luck: 10,
        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,
        leadership: 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
    },
    109: {
        absoluterage: 7,
        attack: 7,
        shout: 7,
        leadership: 9,
        luck: 10,
        defense: 12
    },
    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();
    saveGender();
    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
    addPlayerInfoSpoilers();
    clanStatistics();
    if(location.pathname == "/tournaments.php") {
        const currentTours = [...document.querySelectorAll("center > h2")].find(x => (x.innerText == (isEn ? "Current tournaments" : "Текущие турниры")));
        const filterByLevelSpan = addElement("span", { innerHTML: `&nbsp;<input id=filterByLevelCheckbox type=checkbox title="${isEn ? "Filter by level" : "Фильтровать по уровню"}">`, style: "font-size: 12px;" }, currentTours);
        const filterByLevelCheckbox = filterByLevelSpan.querySelector("#filterByLevelCheckbox");
        filterByLevelCheckbox.checked = getPlayerBool("filterToursByLevel");
        filterByLevelCheckbox.addEventListener("change", function() { setPlayerValue("filterToursByLevel", this.checked); applyToursFilter(); });
        applyToursFilter();
    }
}
function applyToursFilter() {
    [...document.querySelectorAll("td > b > font")].filter(x => x.closest("td").innerHTML.includes(isEn ? "Participants level" : "Уровень участников")).forEach(x => {
        x.closest("table").style.display = getPlayerBool("filterToursByLevel") ? "none" : "";
    });
}
async function getTalentsToHome() {
    if(location.pathname == '/home.php') {
        const playerLevel = getViewingPlayerLevel();
        if(playerLevel < 5) {
            return;
        }
        let hwmAdvancedPlayerInfoTalentsContainer = document.getElementById("hwmAdvancedPlayerInfoTalentsContainer");
        if(!hwmAdvancedPlayerInfoTalentsContainer) {
            let homePageTalentsContainer;
            if(isNewPersonPage) {
                homePageTalentsContainer = document.querySelector("div.home_friends_block");
                if(!homePageTalentsContainer) {
                    return;
                }
                homePageTalentsContainer.insertAdjacentHTML("afterend", `<div class="home_container_block" style="align-items: left;">
            <div class="global_container_block_header global_a_hover">
                <span id=hwmAdvancedPlayerInfoTalentsCaptionContainer title="${getShowTalentsTitleSpanTitle()}"><b>${isEn ? "Talents" : "Навыки"}:</b><b id="hwmAdvancedPlayerInfoTalentsPointsContainer"></b></span>
            </div>
            <div id=hwmAdvancedPlayerInfoTalentsContainer class="home_inside_margins global_a_hover">
            </div>
        </div>`);
            } 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");
                if(!homePageTalentsContainer) {
                    return;
                }
                homePageTalentsContainer.insertAdjacentHTML("beforeend", `<br />
                <span id=hwmAdvancedPlayerInfoTalentsCaptionContainer title="${getShowTalentsTitleSpanTitle()}">&nbsp;&raquo;&nbsp;<b>${isEn ? "Talents" : "Навыки"}:</b><b id="hwmAdvancedPlayerInfoTalentsPointsContainer"></b></span>
                <br />
                <div id=hwmAdvancedPlayerInfoTalentsContainer></div>
`);
            }
            document.getElementById("hwmAdvancedPlayerInfoTalentsCaptionContainer").addEventListener("click", function() { setValue("ShowTalents", !getBool("ShowTalents")); this.title = getShowTalentsTitleSpanTitle(); getTalentsToHomeCore(); }, false);
        }
        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 win.hwm_hints_init === 'function') win.hwm_hints_init();
            talentsStatistics();
            calcArmyHealth();
        }
    }
}
function getShowTalentsTitleSpanTitle() { return isEn ? `Click for ${getBool("ShowTalents") ? "disable" : "enable"} talents loading` : `Нажмите для ${getBool("ShowTalents") ? "выключения" : "включения"} загрузки навыков`; }
function talentsStatistics() {
    const playerLevel = getViewingPlayerLevel();
    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");
        if(layerTable) {
            talentsCaptionContainer = Array.from(layerTable.rows[0].cells).find(x => x.innerHTML.includes(isEn ? "Talents" : "Навыки")).querySelector("b");
        }
        talentsPointsContainer = document.getElementById("hwmAdvancedPlayerInfoTalentsPointsContainer") || addElement("span", { id: "hwmAdvancedPlayerInfoTalentsPointsContainer" }, talentsCaptionContainer);
    }
    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 playerFraction = getPlayerFraction();
    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" : "Район"));
        if(districtBold) {
            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);
        }
    }
}
function saveGender(doc = document, playerId = getUrlParamValue(location.href, "id")) {
    if(location.pathname == "/pl_info.php" || doc != document) {
        const femalePng = doc.querySelector("body > center > table img[src$='female.png']");
        setValue(`PlayerGender${playerId}`, femalePng ? "w" : "m");//        console.log(`PlayerGender${playerId}: ${getValue(`PlayerGender${playerId}`, "u")}`);
    }
}
function showGender() {
    if(location.pathname == "/clan_info.php") {
        const clanActivity = document.querySelector("tr > td:nth-child(2) > img[src$='clans/online.gif']") || document.querySelector("tr > td:nth-child(2) > img[src$='clans/offline.gif']");
        //console.log(clanActivity);
        if(!clanActivity) {
            return;
        }
        const clanHeroesTable = clanActivity.closest("table");
        const playerRefs = [...clanHeroesTable.querySelectorAll("a[href^='pl_info.php?id=']")];
        for(const playerRef of playerRefs) {
            const playerId = getUrlParamValue(playerRef.href, "id");
            const gender = getValue(`PlayerGender${playerId}`);
            if(gender) {
                const cell = playerRef.closest("td");
                let playerGenderImage = cell.querySelector("img[name=playerGenderImage]");
                if(!playerGenderImage) {
                    //<img class="show_hint" src="https://dcdn2.heroeswm.ru/i/male.png" width="24" height="24" border="0" hint="муж." align="right" hwm_hint_added="1">
                    const hint = isEn ? (gender == "m" ? "male" : "female") : (gender == "m" ? "муж." : "жен.");
                    playerGenderImage = addElement("img", { name: "playerGenderImage", src: `https://dcdn2.heroeswm.ru/i/${gender == "m" ? "male" : "female"}.png`, class: "show_hint", hint: hint, style: "width: 14px; height: 14px; vertical-align: middle;"}, playerRef, "beforebegin");
                }
                playerGenderImage.style.display = getPlayerBool("showPlayersGender") ? "inline-block" : "none"; //               console.log(playerGenderImage);                console.log(`${playerId}, display: ${playerGenderImage.style.display}`);
            }
        }
        if(typeof win.hwm_hints_init === 'function') win.hwm_hints_init();
    }
}
async function refreshClanPlayersGender(e) {
    if(location.pathname == "/clan_info.php") {
        const clanActivity = document.querySelector("tr > td:nth-child(2) > img[src$='clans/online.gif']") || document.querySelector("tr > td:nth-child(2) > img[src$='clans/offline.gif']");
        //console.log(clanActivity);
        if(!clanActivity) {
            return;
        }
        const clanHeroesTable = clanActivity.closest("table");
        const playerRefs = [...clanHeroesTable.querySelectorAll("a[href^='pl_info.php?id=']")];
        let i = 1;
        for(const playerRef of playerRefs) {
            const playerId = getUrlParamValue(playerRef.href, "id");
            const gender = getValue(`PlayerGender${playerId}`);
            if(!gender) {
                const doc = await getRequest(playerRef.href);
                saveGender(doc, playerId);
                e.target.innerHTML = i;
            }
            i++;
        }
        e.target.innerHTML = '&#8635;';
    }
}
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, "");
    setValue(`PlayerBifthday${playerId}`, bifthday);
    document.getElementById("hwmAdvancedPlayerInfoBirthday").innerHTML = bifthday;
}
function showExpBar() {
    if(location.pathname != '/home.php' && location.pathname != '/pl_info.php') {
        return;
    }
    const player = { SkillLevel: [], SkillNumber: [], Expirience: 0 };
    let skillInfoCell;
    if(isNewPersonPage) {
        const levelInfoCell = Array.from(document.querySelectorAll("div.home_pers_info")).find(x => x.innerHTML.includes(text['Combat level']));
        if(!levelInfoCell) {
            return;
        }
        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']) || x.innerHTML.includes('Кавалер'));
        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']));
        if(!levelInfoBold) {
            return;
        }
        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('Кавалер')) && 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);
            if(!skillsData) {
                continue;
            }
            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.TotalSkillLevel = player.SkillLevel.reduce((t, x) => t + x, 0);
    player.SkillSum = Math.floor(player.TotalSkillLevel * 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);
        console.log(playerPersents);
        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 (${player.TotalSkillLevel}) * 0.25` : `сумма уровней умений фракций (${player.TotalSkillLevel}) * 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", `<meter id="playerPointsMeter" min="0" max="147" low="${playerPersents.beginNormalPoint}" high="${playerPersents.endNormalPoint}" optimum="${playerPersents.averagePoint}" value="${player.TotalPoints}"></meter>`);
            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, 9.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, 14, 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, 158, 169];
    const penaltyProcents = [0, 25, 500, 1000, 1000, 1000, 1000, 1000, 1000, 1000];
    // Ищем перекач // 101, 108, 114, 125
    let advacedPersent = 0;
    const hiLevel = Math.min(player.Level + 6, averagePoints.length - 1);
    const nextLevels = averagePoints.slice(player.Level + 1, hiLevel);
    const lowLevel = Math.max(player.Level - 4, 0);
    const prevLevels = averagePoints.slice(lowLevel, player.Level).reverse();
    console.log(prevLevels)
    const averagePoint = averagePoints[player.Level];
    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 + 2)} level` : `Средние очки на ${player.Level - (i + 2)} уровне`;
                    nextEdgeClarification = isEn ? `Average points on ${player.Level - (i + 1)} level` : `Средние очки на ${player.Level - (i + 1)} уровне`;
                }
                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 = Math.min(penaltyProcents[i] + 1, 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 {
        averagePoint: averagePoint,
        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
    };
}
function addPlayerInfoSpoilers() {
    if(location.pathname == "/pl_info.php") {
        const panelNames = isEn ? ["Statistics", "Clans", "Resources", "Best stacks in the Leaders' Guild", "Achievements", "Personal info"] : ["Статистика", "Кланы", "Ресурсы", "Лучшие отряды Гильдии Лидеров", "Достижения", "Личная информация"];
        const bolds = Array.from(document.querySelectorAll("td > b"));
        for(const panelName of panelNames) {
            const panelTitleBold = bolds.find(x => x.innerText == panelName);
            if(panelTitleBold) {
                const panelTitle = panelTitleBold.closest("td");
                const spoiler = addElement("div", { id: getSpoilerId(panelName),  style: "display: inline-block; cursor: pointer;", innerHTML: `<img src="https://dcdn.heroeswm.ru/i/inv_im/btn_expand.svg" style="vertical-align: middle;">` }, panelTitle);
                spoiler.addEventListener("click", function() { setPlayerValue(this.id, !getPlayerBool(this.id)); bindPlayerInfoSpolers(this.id); });
                if(panelName == (isEn ? "Statistics" : "Статистика")) {
                    addElement("a", { innerHTML: ` <b>${isEn ? "Battles" : "Бои"}</b>`, href: `/pl_warlog.php?id=${getUrlParamValue(location.href, "id")}` }, panelTitle);
                    addElement("a", { innerHTML: ` <b>${isEn ? "Transfers" : "Передачи"}</b>`, href: `/pl_transfers.php?id=${getUrlParamValue(location.href, "id")}` }, panelTitle);
                }
                if(panelName == (isEn ? "Clans" : "Кланы")) {
                    const clanRef = panelTitle.closest("tr").nextElementSibling.querySelector("a[href^='clan_info.php']");
                    if(clanRef) {
                        const clone = clanRef.cloneNode(true);
                        panelTitle.insertAdjacentElement("beforeend", clone);
                    }
                }
            }
        }
        bindPlayerInfoSpolers();
    }
}
function bindPlayerInfoSpolers(spoilerId) {
    const panelNames = isEn ? ["Statistics", "Clans", "Resources", "Best stacks in the Leaders' Guild", "Achievements", "Personal info"] : ["Статистика", "Кланы", "Ресурсы", "Лучшие отряды Гильдии Лидеров", "Достижения", "Личная информация"];
    const spoilerIds = spoilerId ? [spoilerId] : panelNames.map(x => getSpoilerId(x));
    for(const spoilerId of spoilerIds) {
        const spoiler = document.getElementById(spoilerId);
        if(spoiler) {
            const spoiled = getPlayerBool(spoilerId);
            spoiler.querySelector("img").style.transform = spoiled ? 'rotate(0deg)' : 'rotate(90deg)';
            spoiler.closest("tr").nextElementSibling.style.display = spoiled ? "none" : "";
        }
    }
}
function getSpoilerId(panelName) { return `${panelName.replace(/\s/g, "").replace(/'/g, "")}Spoiler`; }
function clanStatistics() {
    if(location.pathname == "/clan_info.php") {
        const clanActivity = document.querySelector("tr > td:nth-child(2) > img[src$='clans/online.gif']") || document.querySelector("tr > td:nth-child(2) > img[src$='clans/offline.gif']");
        if(!clanActivity) {
            return;
        }
        const clanHeroesTable = clanActivity.closest("table");
        
        const onlineAmount = clanHeroesTable.querySelectorAll("img[src$='clans/online.gif']").length;
        const offlineAmount = clanHeroesTable.querySelectorAll("img[src$='clans/offline.gif']").length;
        const inBattleAmount = clanHeroesTable.querySelectorAll("img[src$='clans/battle.gif']").length;
        const playArcomagAmount = clanHeroesTable.querySelectorAll("img[src$='clans/arcomag.gif']").length;
        
        //const showGenderCheckbox = addElement("input", { id: "showGenderCheckbox", type: "checkbox", title: isEn ? "Show players gender" : "Показывать пол игроков" }, clanHeroesTable.rows[0].cells[2]);
        const showGenderCheckbox = addElement("input", { id: "showGenderCheckbox", type: "checkbox", title: isEn ? "Show players gender" : "Показывать пол игроков" }, clanHeroesTable, "beforebegin");
        showGenderCheckbox.checked = getPlayerBool("showPlayersGender");
        showGenderCheckbox.addEventListener("change", function() { setPlayerValue("showPlayersGender", this.checked); showGender(); });
        showGender();
        const refreshStatisticsButtonStyle = "display: inline-block; width: fit-content;" + (true ? " max-height: 16px; vertical-align: middle; line-height: 16px;" : "");
        const refreshClanPlayersGenderButton = addElement("div", { id: "refreshClanPlayersGenderButton", innerHTML: '&#8635;', title: isEn ? "Refresh clan players gender" : "Обновить пол игроков клана", style: refreshStatisticsButtonStyle }, clanHeroesTable, "beforebegin");
        refreshClanPlayersGenderButton.addEventListener("click", function(e) { refreshClanPlayersGender(e); showGender(); });
        
        const clanInfoTable = document.querySelector("a[href^='clan_log.php?id']").closest("table");
        const firstRow = clanInfoTable.rows[0];
        firstRow.cells[0].insertAdjacentHTML("beforeend", `
<span id=shortClanActivityInfoSpan>
    <span style="${onlineAmount > 0 ? "" : "display: none;"}"><img src="https://dcdn.heroeswm.ru/i/clans/online.gif" align="absmiddle" border="0" height="15" width="15"> <b>${isEn ? "Online" : "В игре"}:</b> ${onlineAmount}&nbsp;&nbsp;&nbsp;</span>
    <span style="${inBattleAmount > 0 ? "" : "display: none;"}"><img src="https://dcdn.heroeswm.ru/i/clans/battle.gif" align="absmiddle" border="0" height="15" width="15"> <b>${isEn ? "In combat" : "В бою"}:</b> ${inBattleAmount}&nbsp;&nbsp;&nbsp;</span>
    <span style="${playArcomagAmount > 0 ? "" : "display: none;"}"><img src="https://dcdn.heroeswm.ru/i/clans/arcomag.gif" align="absmiddle" border="0" height="15" width="15"> <b>${isEn ? "In card match" : "В таверне"}:</b> ${playArcomagAmount}&nbsp;&nbsp;&nbsp;</span>
</span>`);
        const spoiler = addElement("div", { id: `clanStatisticsSpoiler`,  style: "display: inline-block; cursor: pointer;", innerHTML: `<img src="https://dcdn.heroeswm.ru/i/inv_im/btn_expand.svg" style="vertical-align: middle;">` }, document.getElementById("shortClanActivityInfoSpan"));
        spoiler.addEventListener("click", function() { setPlayerValue("clanStatisticsSpoiled", !getPlayerBool("clanStatisticsSpoiled")); clanStatisticsRowBind(); });
        
        
        const clanHeroes = Array.from(clanHeroesTable.rows).filter(x => x.cells[2].querySelector("a[href^='pl_info.php?id=']")).map(x => {
            const level = parseInt(x.cells[3].innerText);
            const fractionImage = x.cells[2].querySelector("img[src*='/i/f/r']");
            let fractionNumber = 0;
            if(fractionImage) {
                let fractionImageMatch = fractionImage.src.match(/\/i\/f\/r(\d+).png/);
                if(fractionImageMatch) {
                    fractionNumber = parseInt(fractionImageMatch[1]);
                } else {
                    console.log(fractionImage);
                }
            }
            return { level: level, fractionImage: fractionImage, fraction: fractionImage.title, fractionNumber: fractionNumber };
        });
        const clanHeroesAmount = clanHeroes.length;
        const heroesLevels = groupBy(clanHeroes, "level");
        const clanLevels = Object.keys(heroesLevels).map(x => ({ level: parseInt(x), amount: heroesLevels[x].length }));
        clanLevels.sort((a, b) => b.level - a.level);

        const heroesFractions = groupBy(clanHeroes, "fractionNumber"); // console.log(heroesFractions);
        const clanFractions = Object.keys(heroesFractions).map(x => ({
            fractionNumber: parseInt(x) % 100,
            amount: heroesFractions[x].length,
            fractionImage: heroesFractions[x][0].fractionImage,
            fraction: heroesFractions[x][0].fraction,
            altNumber: parseInt(x) >= 100 ? Math.round(parseInt(x) / 100) : 0
        }));
        clanFractions.sort((a, b) => a.fractionNumber == b.fractionNumber ? a.altNumber - b.altNumber : a.fractionNumber - b.fractionNumber); // console.log(clanFractions);
        
        
        const clanStatisticsRow = addElement('tr', { id: "clanStatisticsRow" }, firstRow, "afterend");
        clanStatisticsRow.innerHTML = `
<td colspan="2" class="wbwhite">
    <table width="100%" height="100%">
        <tr>
            <td width="60%" valign="top" style="border-right:1px #5D413A solid;">
                <table width="100%" cellpadding="5">
                    <tr>
                        <td align="center">
                            <b>${isEn ? 'All' : 'Всего'}:</b> ${clanHeroesAmount}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>${isEn ? 'Online' : 'В сети'}:</b> ${clanHeroesAmount - offlineAmount} (${Math.round((clanHeroesAmount - offlineAmount) / clanHeroesAmount * 100)}%)                
                        </td>
                    </tr>
                    <tr>
                        <td align="center" style="white-space:nowrap">
                            <img src="https://dcdn.heroeswm.ru/i/clans/online.gif" align="absmiddle" border="0" height="15" width="15"> <b>${isEn ? "Online" : "В игре"}:</b> ${onlineAmount}&nbsp;&nbsp;&nbsp;
                            <img src="https://dcdn.heroeswm.ru/i/clans/battle.gif" align="absmiddle" border="0" height="15" width="15"> <b>${isEn ? "In combat" : "В бою"}:</b> ${inBattleAmount}&nbsp;&nbsp;&nbsp;
                            <img src="https://dcdn.heroeswm.ru/i/clans/arcomag.gif" align="absmiddle" border="0" height="15" width="15"> <b>${isEn ? "In card match" : "В таверне"}:</b> ${playArcomagAmount}&nbsp;&nbsp;&nbsp;
                            <img src="https://dcdn.heroeswm.ru/i/clans/offline.gif" align="absmiddle" border="0" height="15" width="15"> <b>${isEn ? "Offline" : "Не в игре"}:</b> ${offlineAmount}
                        </td>
                    </tr>
                    <tr>
                        <td>
                        ${clanFractions.map(x => `<img src="${x.fractionImage.src}" align="absmiddle" border="0" height="15" width="15"> <b>${x.fraction}:</b> ${x.amount} (${Math.round(x.amount / clanHeroesAmount * 100)}%)`).join("<br>")}
                        </td>
                    </tr>
                </table>
            </td>
            <td valign="top" height="100%">
                <table width="100%" height="100%" cellpadding="5">
                    <tr>
                        <td align="center">
                            <b>${isEn ? 'The average level of heroes' : 'Средний уровень героев'}:</b> ${round0(clanHeroes.reduce((t, x) => t + x.level, 0) / clanHeroesAmount)}
                        </td>
                    </tr>
                    <tr>
                        <td height="100%" valign="middle">
                            ${clanLevels.map(x => `<b>${x.level} ${isEn ? 'level' : 'уровней'}:</b> ${x.amount} (${Math.round(x.amount / clanHeroesAmount * 100)}%)`).join("<br>")}
                        </td>
                    </tr>
                    <tr>
                        <td align="right" valign="bottom">
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>
</td>`;
        clanStatisticsRowBind();
    }
}
function clanStatisticsRowBind() {
    const spoiled = getPlayerBool("clanStatisticsSpoiled");
    const clanStatisticsSpoiler = document.getElementById("clanStatisticsSpoiler");
    clanStatisticsSpoiler.querySelector("img").style.transform = spoiled ? 'rotate(0deg)' : 'rotate(90deg)';
    clanStatisticsSpoiler.title = isEn ? (spoiled ? "Show clan statistics" : "Hide clan statistics") : (spoiled ? "Показать статистику клана" : "Скрыть статистику клана");
    document.getElementById("clanStatisticsRow").style.display = spoiled ? "none" : "";
}
function getViewingPlayerLevel() {
    if(location.pathname == "/home.php" || location.pathname == "/pl_info.php") {
        if(isNewPersonPage) {
            const levelInfoCell = Array.from(document.querySelectorAll("div.home_pers_info")).find(x => x.innerHTML.includes(isEn ? "Combat level" : "Боевой уровень"));
            if(levelInfoCell) {
                setPlayerValue("playerLevel", parseInt(levelInfoCell.querySelector("div[id=bartext] > span").innerText));
            }
        } else {
            const playerLevelExec = new RegExp(`<b>${isEn ? "Combat level" : "Боевой уровень"}: (\\d+?)<\\/b>`).exec(document.documentElement.innerHTML);
            if(playerLevelExec) {
                setPlayerValue("playerLevel", parseInt(playerLevelExec[1]));
            }
        }
    }
    return parseInt(getPlayerValue("playerLevel", 0));
}
function getPlayerFraction() {
    let fractionSourceText;
    if(location.pathname == '/home.php') {
        const fractionImage = isNewPersonPage ? document.querySelector("div#hwm_no_zoom div.home_main_pers_block center a[href^='castle.php'] img") : document.querySelector("body > center table.wb > tbody > tr:nth-child(2) center a[href^='castle.php'] img");
        fractionSourceText = fractionImage?.src;
    }
    const playerFractionExec = /\/i\/f\/r(\d{1,3})\.png/.exec(fractionSourceText || document.querySelector("body").innerHTML);
    if(playerFractionExec) {
        return playerFractionExec[1];
    }
}

// https://dcdn3.heroeswm.ru/i/april2023/gn_face.png
// https://dcdn.heroeswm.ru/i/april2023/pvp_gerb2.png
// https://dcdn3.heroeswm.ru/i/april2023/gv_face.png
// https://dcdn1.heroeswm.ru/i/april2023/gr_face.png
// https://dcdn1.heroeswm.ru/i/april2023/go_face.png
// https://dcdn.heroeswm.ru/i/kukla_png/premium/1april2023/p8ap.png?v=10.d20e1e
// https://dcdn1.heroeswm.ru/i/kukla_png/premium/1april2023/p1ap.png?v=10.d20e1e
// https://dcdn.heroeswm.ru/i/kukla_png/premium/1april2023/p2ap.png?v=10.d20e1e
// https://dcdn.heroeswm.ru/i/kukla_png/premium/1april2023/p3ap.png?v=10.d20e1e
// https://dcdn3.heroeswm.ru/i/kukla_png/premium/1april2023/p4ap.png?v=10.d20e1e
// https://dcdn3.heroeswm.ru/i/kukla_png/premium/1april2023/p5ap.png?v=10.d20e1e
// https://dcdn2.heroeswm.ru/i/kukla_png/premium/1april2023/p6ap.png?v=10.d20e1e
// https://dcdn2.heroeswm.ru/i/kukla_png/premium/1april2023/p7ap.png?v=10.d20e1e
// https://dcdn.heroeswm.ru/i/kukla_png/premium/1april2023/p9ap.png?v=10.d20e1e
// https://dcdn.heroeswm.ru/i/kukla_png/premium/1april2023/p10ap.png?v=10.d20e1e
// https://dcdn.heroeswm.ru/i/transport/1april/150/2.png
// https://dcdn.heroeswm.ru/i/transport/1april/150/108.png
// https://dcdn.heroeswm.ru/i/transport/1april/150/150.png