hwmTimers

Таймеры здоровья, гильдии рабочих, кузнецов, наёмников, охотников, воров/рейнджеров, лидеров

当前为 2023-12-31 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name           hwmTimers
// @namespace      Tamozhnya1
// @author         Tamozhnya1
// @description    Таймеры здоровья, гильдии рабочих, кузнецов, наёмников, охотников, воров/рейнджеров, лидеров
// @version        8.5
// @include        *heroeswm.ru/*
// @include        *lordswm.com/*
// @exclude        */rightcol.php*
// @exclude        */ch_box.php*
// @exclude        */chat*
// @exclude        */ticker.html*
// @exclude        */frames*
// @exclude        */brd.php*
// @grant          GM_deleteValue
// @grant          GM_getValue
// @grant          GM_setValue
// @grant 		   GM.xmlHttpRequest
// @grant 		   GM.notification
// @license        MIT
// ==/UserScript==

// TODO
// 1) После новогодних праздников в новом интерфейсе может случиться крах из-за того, что в нем неизвестно как определять каталог картинок (а может он останется новогодним)
// 2) После каждого проигрыша армии в ГН таймер начинает отсчет и репутация падает. Т.о. после последнего боя таймер неточен из-за неправильной репутации
// 3) Главная неприятность- периодически не запскаются таймеры ГО, ГН. Причина то, что при входе в бой обработчик, висящий на кнопке, не всегда успевает до того, как админский обработчик запустит бой.
// Я перевесил свой обработчик на image, но кажется это не помогло.
if(!this.GM_getValue || (this.GM_getValue.toString && this.GM_getValue.toString().indexOf("not supported") > -1)) {
    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) {
    return;
}
const PlayerId = playerIdMatch[1];
const isEn = document.documentElement.lang == "en" || location.href.includes("lordswm.com");
const isHeartOnPage = document.querySelector("canvas#heart") || document.querySelector("div#heart_js_mobile") ? true : false;
const isNewInterface = document.querySelector("div#hwm_header") ? true : false;
const isNewPersonPage = document.querySelector("div#hwm_no_zoom") ? true : false;
const mooving = location.pathname == '/map.php' && !document.getElementById("map_right_block");
const windowObject = window.wrappedJSObject || unsafeWindow;
const timerNames = ["health", "work", "smith", "merc", "hunt", "thief", "leader", "defence", "mana"];
const timeFormats = { full: 1, hoursOrSeconds: 2, secondsLastMinute: 3 };
const timerSettings = {
    leader: {
        timeFormat: timeFormats.hoursOrSeconds
    },
    defence: {
        timeFormat: timeFormats.secondsLastMinute,
        isShowNotEmptyOnly: true
    }
};
const audioScenaries = ["health", "work", "smith", "warlike", "leader", "defence"];
const timersAudioMap = {"health": "health", "work": "work", "smith": "smith", "merc": "warlike", "hunt": "warlike", "thief": "warlike", "leader": "leader", "defence":  "defence"};
const playingAudios = {};
let defaultAudio;
let texts;

main();
function main() {
    initUserName();
    verifyOptionKeys();
    texts = setTexts();
    preloadDefaultAudio();
    if(location.pathname == '/war.php') {
        inBattle(); // в бою
        return;
    }
    if(!isHeartOnPage) {
        return;
    }
    const [army_percent] = healthTimer();
    manaTimer();
    // Обработка результатов битвы, если только что из неё вышли
    const bselect_link = document.querySelector("a[href='bselect.php']") || document.querySelector("a[href='plstats.php']");
    if(bselect_link && bselect_link.parentNode.innerHTML.indexOf("#ff0000") === -1) { // Сидим на форуме, меню - красное
        checkBattleResults(army_percent < 100 ? "fail" : "win");
    }
    requestServerTime();
    checkPremiumAccount();
    if(location.pathname == "/home.php") {
        GM_setValue(`IsDeer${PlayerId}`, document.querySelector("img[src*='deer2.png']") ? true : false);
    }
    checkHuntLicense();
    checkWork();
    checkWorkaholic();
    checkMercenary();
    checkLeaders();
    checkRangerGuild();
    checkModWorkebench();
    checkDefences();
    if(location.pathname == '/map.php') {
        checkThiefAmbush();
        checkRangerAmbush();
        checkMapHunter();
    }
    createTimersPanel();
    timersPanelDataBind();
    tick();
}
function verifyOptionKeys() {
    const defaultOptions = {
        "healthNotification": "no",
        "healthSound": "",
        
        "isShowWorkTimer": "1",
        "workNotification": "yes",
        "workSound": "",
        "enrollNumber": "0",
        "showWorkaholicAlarmLastTwoEnrolls": "1",
        "disableWorkaholicAlarm": "0",
        
        "isShowSmithTimer": "1",
        "smithNotification": "yes",
        "smithSound": "",
        
        "isShowMercTimer": "1",
        "mercNotification": "yes",
        "warlikeSound": "",
        
        "isShowHuntTimer": "1",
        "huntNotification": "yes",
        "huntLicenseRate": "1",
        "huntLicenseExpirationTime": "0",
        "huntLicenseText": "",
        
        "isShowThiefTimer": "1",
        "thiefNotification": "yes",
        "thiefOrRanger": "0",
        "joinRangerBattle": "0",
        
        isShowLeaderTimer: "1",
        "leaderNotification": "yes",
        "leaderSound": "",
        
        "isShowDefenceTimer": "0",
        "defenceNotification": "yes",
        "defenceSound": "",

        "customTimeRate": "1",
        "abuBlessRate": "1",
        "abuBlessExpirationTime": "0",
        "abuBlessInfo": "",
        "battleType": ""
    };
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`, JSON.stringify(defaultOptions)));
    Object.keys(defaultOptions).forEach(x => { if(!options.hasOwnProperty(x)) { options[x] = defaultOptions[x]; } });
    Object.keys(options).forEach(x => { if(!defaultOptions.hasOwnProperty(x)) { delete options[x]; } });
    GM_setValue(`hwmTimersOptions${PlayerId}`, JSON.stringify(options));
    //console.log(options);
    //console.log(Object.keys(options).reduce((t, x) => t + `${x}: ${typeof x}, ${options[x]}, ${typeof options[x]}\n`, ""))
}
function inBattle() {
    if(/warlog\|0/.exec(document.querySelector("html").innerHTML)) {
        //flash & html: warlog|0| -> бой происходит сейчас, warlog|1| -> запись боя, |player|7146446| -> id текущего игрока
        const playerIdExec = /\|player\|(\d+)\|/.exec(document.querySelector("html").innerHTML);
        if(playerIdExec && playerIdExec[1] == PlayerId) {
            GM_setValue(`HoldBattle${PlayerId}`, true);
            const finalResultDiv = document.getElementById("finalresult_text");
            if(finalResultDiv.innerHTML.length <= 10) {
                observe(finalResultDiv, parseBattleResultPanel);
            }
        }
    }
}
function parseBattleResultPanel() {
    const finalResultDiv = document.getElementById("finalresult_text");
    if(finalResultDiv.innerHTML.length > 10) {
        const bolds = finalResultDiv.querySelectorAll("font b");
        let result = "fail";
        for(const bold of bolds) {
            if(bold.innerHTML == (isEn ? "Victorious:" : "Победившая сторона:")) {
                //console.log(`${bold.parentNode.nextSibling.nextSibling.firstChild.innerText}, UserName: ${GM_getValue("UserName")}`);
                if(bold.parentNode.nextSibling.nextSibling.firstChild.innerText == GM_getValue("UserName")) {
                    result = "win";
                }
                break;
            }
        }
        let expirience = 0;
        let skill = 0;
        const myResult = finalResultDiv.innerHTML.split("<br>").find(x => x.includes(GM_getValue("UserName")));
        if(myResult) {
            const myResultExec = new RegExp(`(\\d+) ${isEn ? "exp" : "опыт"}.+ (\\d+\\.?\\d*) ${isEn ? "skill" : "умен"}`).exec(myResult);
            if(myResultExec) {
                expirience = parseInt(myResultExec[1]);
                skill = parseFloat(myResultExec[2]);
            }
        }
        checkBattleResults(result, expirience, skill);
    }
}
function checkBattleResults(result, expirience, skill) {
    if(gmGetBool(`HoldBattle${PlayerId}`)) {
        const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
        console.log(`battleType: ${options.battleType}, result: ${result}, expirience: ${expirience}, skill: ${skill}`);
        switch(options.battleType) {
            case "thief":
                if(result == "fail") {
                    GM_setValue(`thiefTimeoutEnd${PlayerId}`, Date.now() + 60 * 60000 * options.customTimeRate * (gmGetBool(`IsDeer${PlayerId}`) ? 0.6 : 1) * options.abuBlessRate);
                } else {
                    GM_deleteValue(`thiefTimeoutEnd${PlayerId}`);
                }
                break;
            case "hunt":
                setHuntTimeout();
                break;
            case "merc":
                setMercTimeout(result);
                break;
            case "merc":
                setLeaderTimeout(result);
                break;
        }
        if(result == "win" && options.battleType != "leader") {
            let enrollNumber = parseInt(options.enrollNumber);
            if(skill >= 0.5) {
                enrollNumber = 0;
            } else if(skill > 0) {
                enrollNumber -= Math.floor(skill / 0.05);
                enrollNumber = Math.max(enrollNumber, 0);
            }
            if(!skill) {
                enrollNumber = 0;
            }
            updateOption("enrollNumber", enrollNumber);
        }
        updateOption("battleType", "");
        GM_deleteValue(`HoldBattle${PlayerId}`);
    }
}
async function preloadDefaultAudio() {
    defaultAudio = new Audio('data:audio/mp3;base64,/+OAxAAAAAAAAAAAAEluZm8AAAAPAAAABQAACcoAMzMzMzMzMzMzMzMzMzMzMzMzM2ZmZmZmZmZmZmZmZmZmZmZmZmZmmZmZmZmZmZmZmZmZmZmZmZmZmZnMzMzMzMzMzMzMzMzMzMzMzMzMzP//////////////////////////AAAAOUxBTUUzLjk4cgE3AAAAAAAAAAAUQCQCTiIAAEAAAAnKGRQoyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+OAxABX1F4koVjAAQjCEwjMYQMNFN52kFuDMg7zN6S6b5yx01M0x11uJOV6eYZQoAsSQOw1xyGGKAISEUHFBIzpQ4IL0Piieg+pvC30VO49A5D8R9h6gag6YDTJKsOmOseB4gzhU6Y6g7E37tSik5MMoRMLYFtEUGmQaoGWkLwLonYbcty2tqnQCJEMsnK9Pbr07+P5GKTGvK4bf9yH8lmFSURixK2cM4dyWXYbcty5fz5h2GGKAKCOo4ZdxBxTRrjkNYdyUqZlkCzCKDEIpjTxt/3IchyIcxlb/v/D8s5Xp+yh/IcvUljCpKHYdyWXXYYYoAhIQCIOKaQlc5aQuIuicz1SVXbVIoAqRYjEIcvSt/3IfycrxuNy+/Uht/3/l9yGGsM4a4xN36ZlCJhchAAzRgYAAYAFmEHFiPxVhty2uO5OV43G7esK8bdt34vchhrC7FB2XypciEhAImA8jSy7iABdEUrxuNy/sdUgMCEYDAYDIYDEYDEYCeapz8c1sF1vw7MA2FaUsp8YwZDBEYNhWZMjfLOfb9r5p0lxsxHh/+OCxDRkHGbSX5nqA8jG0fWPNSWWZzFJWn3iM+keNcjuMPAiMRxVMchckCm7dWzuJA7X25tHSGDhiM0jUGg8MwA6MVRlMeRFc4LgGgPTMdOTpuI4vqhQgA4YtFoZTF4aHDYY3j+FQNMJQfAAi4ax7z82SvnelEritIYJgGucGiKYohiYMAUZKC4YkCcTDAZFD134cs58m6fmMst8qcprjzGKwLgIXzAkATBsETDcUzIgPDDwQjDgFTBwCQoEZgqBJhEF2OfcOc/Cn7hqWxqSTk3STW5fP0XgIdDBABjGAMDCQJAKCYQHaYBheG5YCoqAOYMA6BBHIQqMFgDblLOU/cKev2nzz53fK+7dLQasZSvPlXDehUIQ4CCIADBYCGJGCgFGBAZEIEgEFjB0AAQIqQxg4Dq9QgHUrTBsATAsGRCBBhuD///8z1+ff/P////+7/Llzes8/u/++cw59bO7zD///33////V4SolSuRCmPL6nAqR9ZQ0l+oyuAOCyR+7FK1oVDzAwRqbMUv6FhjNAQEMzLsmIgS1UOBgAImIjQoipf/jgsQ4ZLx2nAGY2ADAELIgUAh5h5eawLGRKBiAwYwIDS0ZmSGrrBAOmEGpgouigCBMxr2N3HiEvAQ2aIDGiCZphKHYxkpACC0WAgEWEJSRLJMWI9mGDJdQCFQcamMhKN7LzABcSEjHA8AA5elmCe6Wyi8XQAKiAgOjA3eGCAEC4GQCqXiwTLlTL5YaBQRe6EhFFL9fUOA0AL3Q64KYyQbgt8mkv1/V9P4JChgQoIASD1WhYEVpesZBH3VMAQBB5E1E5H4mAwAALneJRhsr1OGyC1DFaKvMma+jkOosKrLL2iNOadDTcpLBz1Mob+FzywCx3ThmMsqTXZiplIWdwPIXRj7WqRrDayxfVC8V+dl1aRR2NtKd6IwPBzgOHArgzcOydwWEtee5yqC3EKOBYBlNSIajDvVs696AXWjEzYj0zWiESlUWuWsb+M/aiFL3KW5SCe1fmpBWfr/+kk8SlmOX/9LVpKtJMzwBIiRGAngdQbAVvPs+70vm3FR5QGDWsugz5YIx02MFFn6TrWuxJy7xiJkYuqGZsQKPgUHGFgQUFQr/44LEOmbMdnABmdgAhKn3zEAY9pkI2cEtkxQaUamfHRgpKYkJoiCQyjYDgEw0JAwcu8oDzYms8RzMdCgUxpsEIUDSIEgZkBmBQsQASPSb7jusKggBA0bDEgFDEtgRBDSAMhKao2qGq3ofo0Q+pg+rdk92msTdeNCMGMCHzCgMUDQsEjAAFi9CS2NSLCkt1uQ+mMtOEyt6EAa4FRqWr4f2BncV48oMCRoXTaC4EoEAjss8vQVBCAAXmuFmzrq+bm26eLBIQ6LLnpruZTO0uh2WRZOBAEFID2syWVM6CoEIwRJp2m7JdqmZul6sLATxwJC3QdtNVrssgZuLSoKguZbDIG4uk+9O3sATbiWKSBtOUpasEyNjmC5Y7EZUwaJtNhpXUudGaf9nNO1xsbcFG2MwFLnphyLy+JPO0xpD708akEpls9Ny2Q0sbi71zrcYzFYlGnqxr7d+UvHK4nDjnf/wbDz0WJFR//tfoLTcY9mqAQMgAqUQjTA76PNjioDGCBQNciLWVhUVUiVilrWC2KV2VhS/qgKJpbEABLIqbRNHks6W/+OCxDNgzGY2JdjIAGUFX4R6LZMFZkWeMMQxQEuhEGaDZvQmaMmtfyoVKQASaC5sNmWQXSdaOsNSFQkoqoml/i0xbZMKFOCoCiqiqu53n+f6HpdnKX1ZUmMisptAzopelsS0qRSxlhkTi7xeJMJl0ndJL4u8g8sZymHJDISmWyhcyQyAZB5IpUzEp19lhlAlTLuZ0153qjKlAkVkVkVi7xaYtMumDFAkJSAZIpQYvEhKL+lpS2qKLTXiQTAEIwxEHXVw7KXZWFTFRVXc/Wcpf1/X9f2WzLWWIuLOPssMoEkMkKgFQCpgurAScxd4uUsWQ/Vdlhq7VSrti0y/stwymXBZzFoi1lhrEWuw7hammtM6fqbXKXVAAJhBoPOtnS8rQ1DUPWa0af52nKcpynKayu1drOX5vSp/n+hmMwy/rWVMS4JgCmIKuqVspXau1iLOXJcmNS7tWlpcJVGqXlVMQU1FMy45OC40VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ==');
    defaultAudio.preload = 'auto';
}
function setTexts() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    var obj;
    if(isEn) {
        obj = {
            healthNotificationEnabled: 'Army restore alarm on',
            onceHealthNotificationEnabled: 'Alarm once at army restore',
            workNotificationEnabled: 'Workshift alarm on',
            notificationDisabled: 'Alarm off',
            smithNotificationEnabled: 'Blacksmith alarm on',
            mercNotificationEnabled: 'Mercenaries Guild alarm on',
            defenceNotificationEnabled: 'Defence begin alarm on',
            regexp_timegn0: /Come back in (\d+) minutes\./,
            regexp_timegn1: /\. Time left: (\d+) minutes\./,
            regexp_timegn2: /ou have (\d+) minutes left/,
            regexp_timegn3: /\. Time left: (\d+) minutes\./,
            regexp_timegn4: /still have (\d+) minutes/,
            regexp_timegn5: /you still have \d+ attempts and (\d+) minutes/,
            huntNotificationEnabled: 'Hunters Guild alarm on',
            nativeHuntTimerText: 'Next hunt available in',
            thiefNotificationEnabled: options.thiefOrRanger == '0' ? 'Thieves Guild alarm on' : 'Rangers Guild alarm on',
            leaderNotificationEnabled: 'Leader Guild alarm on',
            regexp_timegre: /Come in (\d+) min/,
            enrollAgainIn: /You may enroll again in (\d+) min/,
            workPlace: 'Work place:',
            workMessage: 'LG: You may enroll again',
            smithMessage: 'BS: Blacksmith works are finished',
            mercMessage: 'MG: Mercenaries Guild has a quest for you',
            huntMessage: 'HG: You notice traces ...',
            defenceMessage: 'Defence begining',
            thiefMessage: options.thiefOrRanger == '0' ? 'TG: You may set an ambush' : 'RG: Rangers Guild has a quest for you',
            signalSound: 'Audio file ',
            alarm_mode: '<b>Timer alarm mode</b>:',
            alarm_mode_sound: 'audio',
            alarm_mode_alert: 'message',
            alarm_mode_both: 'notification',
            alarm_mode_none: 'off',
            healthTitle: 'health',
            warlikeTitle: 'MHT(R)G',
            leaderTitle: 'LG',
            defenceTitle: "defence",
            workTimerPanelCaption: 'LG',
            smithTimerPanelCaption: 'BS',
            smithWelcome: 'To Blacksmith',
            mercTimerPanelCaption: 'MG',
            mercWelcome: 'To Mercenaries\' Guild',
            huntTimerPanelCaption: 'HG',
            huntTimerPanelTitle: 'To Hunters\' Guild',
            thiefTimerPanelCaption: options.thiefOrRanger == '0' ? 'TG' : 'RG',
            thiefWelcome: options.thiefOrRanger == '0' ? 'To Thieves\' Guild' : 'To Rangers Guild post',
            leaderWelcome: "To leaders guild",
            leaderTimerPanelCaption: "LG",
            defenceTimerPanelCaption: "D",
            defenceWelcome: 'Defences',
            manaWelcome: 'Settings',
            successfullyEnrolled: 'You have successfully enrolled',
            currentlyUnemployed: 'You are currently unemployed',
            regexp_map_go: 'During the journey you have access to the',
            huntLicenseExpirationMessage: 'The license expires ',
            resetTimersTitle: 'Reset all timers',
            setOnceThiefTimeout: 'Set TG/RG timer for once to',
            cusomRateTitle: 'Quests HG, MG, TG, RG more often',
            joinRangerBattleText: 'Immediately initiate Rangers\' guild battle on arrival',
            isShowTimersTitle: 'Show timers:',
            showWorkaholicAlarmLastTwoEnrollsTitle: 'Notify about workaholic penalty only 2 workshifts away',
            disableWorkaholicAlarmTitle: 'Turn off all notifications on workaholic penalty',
            huntLicenseAuto: '<b>Hunter license</b> is detected automatically in Hunters\' Guild',
            hide: 'Hide',
            alreadyEemployed: 'You are already employed\.',
            passedLessThanOneHour: 'Less than one hour passed since last enrollment\. Please wait\.',
            noVacancies: 'No vacancies\.'
        };
    } else {
        obj = {
            healthNotificationEnabled: 'Будет предупреждение о восстановлении армии',
            onceHealthNotificationEnabled: 'Установить единоразово предупреждение о восстановлении армии',
            workNotificationEnabled: 'Будет предупреждение о конце рабочего часа',
            notificationDisabled: 'Не будет предупреждения',
            smithNotificationEnabled: 'Будет предупреждение о завершении работ в Кузнице',
            mercNotificationEnabled: 'Будет предупреждение Гильдии Наемников',
            defenceNotificationEnabled: 'Будет предупреждение о начале защиты',
            regexp_timegn0: /Приходи через (\d+) мин/,
            regexp_timegn1: /Осталось времени: (\d+) минут/,
            regexp_timegn2: /тебя осталось (\d+) минут/,
            regexp_timegn3: /у тебя еще есть (\d+) минут/,
            regexp_timegn4: /\. Осталось (\d+) минут\./,
            regexp_timegn5: /осталось \d+ попыток и (\d+) минут/,
            huntNotificationEnabled: 'Будет предупреждение Гильдии Охотников',
            nativeHuntTimerText: 'Следующая охота будет доступна через',
            thiefNotificationEnabled: options.thiefOrRanger == '0' ? 'Будет предупреждение Гильдии Воров' : 'Будет предупреждение Гильдии Рейнджеров',
            leaderNotificationEnabled: 'Будет предупреждение Гильдии лидеров',
            regexp_timegre: /приходи через (\d+) мин/,
            enrollAgainIn: /Вы можете устроиться на работу через (\d+)/,
            workPlace: 'Место работы:',
            workMessage: 'ГР: Пора на работу',
            smithMessage: 'ГК: Работа в Кузнице завершена',
            mercMessage: 'ГН: Для Вас есть задание в Гильдии Наемников',
            huntMessage: 'ГО: Вы увидели следы ...',
            defenceMessage: 'Защита началась',
            thiefMessage: options.thiefOrRanger == '0' ? 'ГВ: Вы можете устроить засаду' : 'ГРж: Есть задание в Гильдии Рейнджеров',
            signalSound: 'Звук сигнала ',
            alarm_mode: '<b>Режим оповещения</b> окончания таймера:',
            alarm_mode_sound: 'звук',
            alarm_mode_alert: 'сообщение',
            alarm_mode_both: 'оповещение',
            alarm_mode_none: 'отключен',
            healthTitle: 'здоровья',
            warlikeTitle: 'ГОНВ(Рж)',
            leaderTitle: 'ГЛ',
            defenceTitle: "защиты",
            workTimerPanelCaption: 'ГР',
            smithTimerPanelCaption: 'ГК',
            smithWelcome: 'В Кузницу',
            mercTimerPanelCaption: 'ГН',
            mercWelcome: 'В здание Гильдии Наемников',
            huntTimerPanelCaption: 'ГО',
            huntTimerPanelTitle: 'В здание Гильдии Охотников',
            thiefTimerPanelCaption: options.thiefOrRanger == '0' ? 'ГВ' : 'ГРж',
            thiefWelcome: options.thiefOrRanger == '0' ? 'В здание Гильдии Воров' : 'В здание Гильдии Рейнджеров',
            leaderWelcome: "В гильдию лидеров",
            leaderTimerPanelCaption: "ГЛ",
            defenceTimerPanelCaption: "З",
            defenceWelcome: 'Защиты',
            manaWelcome: 'Настройки',
            successfullyEnrolled: 'Вы устроены на работу',
            currentlyUnemployed: 'Вы нигде не работаете',
            regexp_map_go: 'Во время пути Вам доступны',
            huntLicenseExpirationMessage: 'Лицензия истекает ',
            resetTimersTitle: 'Обнулить все таймеры',
            setOnceThiefTimeout: 'Единоразово установить таймер ГВ/ГРж равным',
            cusomRateTitle: 'Задания ГО, ГН, ГВ, ГРж чаще на',
            joinRangerBattleText: 'По прибытии вступать в бои Гильдии Рейнджеров',
            isShowTimersTitle: '<b>Отображать:</b>',
            showWorkaholicAlarmLastTwoEnrollsTitle: '<b>Показывать</b> штраф трудоголика только <b>за 2 часа</b>',
            disableWorkaholicAlarmTitle: '<b>Отключить</b> ВСЕ уведомления о штрафе трудоголика',
            huntLicenseAuto: '<b>Лицензия охотника</b> определяется автоматически (в Гильдии Охотников)',
            hide: '<b>Скрывать</b>',
            alreadyEemployed: 'Вы уже устроены\.',
            passedLessThanOneHour: 'Прошло меньше часа с последнего устройства на работу\. Ждите\.',
            noVacancies: 'Нет рабочих мест\.'
        };
    }
    return obj;
}
function createTimersPanel() {
    const shContainer = document.querySelector("div.sh_container");
    const sh_MenuPanel = document.querySelector("div.sh_MenuPanel");

    const dragonLeft = document.querySelector("img[src*='i/top'][src*='/dragon__left']"); // https://dcdn1.heroeswm.ru/i/top_ny_rus/dragon__left_.jpg
    const dragonRight = document.querySelector("img[src*='i/top'][src*='/dragon__right']");

    //document.querySelector("img.mm_decor1") // https://dcdn.heroeswm.ru/i/new_top_ny/mm_decor1.png

    //TODO в новом интерфейсе надо уметь определять НЕ новогодний каталог
    let folder = "https://dcdn2.heroeswm.ru/i/top_ny_rus/line/";
    const img_link = document.querySelector("img[src*='i/top'][src*='/line/t_end']");
    if(img_link) {
        folder = /(\S*\/line\/)/.exec(img_link.src)[1];
    }
    const newYearSuffix = document.querySelector("img[src*='i/top_ny']") || document.querySelector("img[src*='i/new_top_ny']") ? "_" : ""; // если новый год
    //console.log(`folder: ${folder}, newYearSuffix: ${newYearSuffix}, ${folder}t_end${newYearSuffix}.jpg`); // folder: https://dcdn2.heroeswm.ru/i/top_ny_rus/line/, newYearSuffix: _

    let container = dragonLeft?.parentNode || sh_MenuPanel;
    let timersContainerWidth;
    let zIndex = 0;
    let leftMargin = 0;
    let topMargin = 0;
    let height;
    if(isNewInterface) {
        timersContainerWidth = shContainer.getBoundingClientRect().width - 30 * 2;
        topMargin = -67;
        leftMargin = 159;
        zIndex = 2;
        height = 26;
        //GM_addStyle(`.noTransition { -webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important; }`);
        //Array.from(document.querySelectorAll(".mm_item")).forEach(x => { x.classList.add('noTransition'); x.style.marginTop = "18px"; }); window.innerHeight; // Не получилось убрать плавный сдвиг меню вниз
        document.querySelector("div#ResourcesPanel").style.height = "20px";
    } else {
        timersContainerWidth = dragonRight.getBoundingClientRect().left - dragonLeft.getBoundingClientRect().left + 124;
        topMargin = -26;
        leftMargin = -43;
        height = 26;
    }
    let timersHtml = `
<style>
    .hwm_tb * {font-size: 11px; color: #f5c137;}
    .hwm_tb_cell {border-collapse: collapse; background-color: #6b6b69;}
    .hwm_tb_cell TD {padding: 0px;}
    .cell_t {height: 3px; background: url(${folder}t_top_bkg${newYearSuffix}.jpg);}
    .cell_c {white-space: nowrap; height: 18px; background: url(${folder}t_com_bkg${newYearSuffix}.jpg); font-weight: bold;}
    .cell_b {height: 5px; background: url(${folder}t_bot_bkg${newYearSuffix}.jpg); text-align: center;}
    .cell_b IMG {width: 17px; height: 5px;}
</style>
<table cellpadding=0 cellspacing=0 align="center" class="hwm_tb" style="width: ${timersContainerWidth}px;">
    <tr>
        <td>
            <table width="100%" cellpadding=0 cellspacing=0 style="background: url(${folder}t_bkg${newYearSuffix}.jpg);">
                <tr valign=middle align=center>
                    <td width=5 style="overflow: hidden;">
                        <img src="${folder}t_end${newYearSuffix}.jpg" alt="" width=9 height=${height}px style="margin:0px 0px 0px -4px;">
                    </td>`;
    const timersWidths = { "health": { width: 44 }, "mana": { width: 44, closeWidth: "5", closeStyle: 'style="overflow: hidden;"', closeImageStyle: 'style="margin:0px -4px 0px 0px;"' } };
    for(const tag of timerNames) {
        const timerName = `${tag}Timer`;
        timersHtml += `
                    <td id="${timerName}Cell" ${timersWidths[tag] ? `width: ${timersWidths[tag].width}px` : ""}>
                        <table class="hwm_tb_cell">
                            <tr>
                                <td class="cell_t" />
                            </tr>
                            <tr>
                                <td class="cell_c">
                                    <span style="cursor: pointer;" id="${timerName}PanelCaption">${texts[`${timerName}PanelCaption`] ? texts[`${timerName}PanelCaption`] + ":" : ""}</span>
                                    <a id="${timerName}Panel" style="text-decoration: none;">00:00</a>
                                </td>
                            </tr>
                            <tr>
                                <td class="cell_b">
                                    <img src="${folder}t_center${newYearSuffix}.jpg">
                                </td>
                            </tr>
                        </table>
                    </td>
                    <td id="${timerName}CloseCell" width="${timersWidths[tag]?.closeWidth || "9"}px" ${timersWidths[tag]?.closeStyle || ""}>
                        <img src="${folder}t_end${newYearSuffix}.jpg" width=9 height="${height}px" ${timersWidths[tag]?.closeImageStyle || ""}>
                    </td>

`;
}
    timersHtml += `
                </tr>
            </table>
        </td>
    </tr>
</table>`;
    const timersPanel = addElement("div", container, { style: `position: absolute; margin: ${topMargin}px 0px 0px ${leftMargin}px; text-align: center; z-index: ${zIndex};`, innerHTML: timersHtml });
    timersPanel.querySelector('#healthTimerPanel').addEventListener("click", function() { updateOption("healthNotification", x => x.healthNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#workTimerPanelCaption').addEventListener("click", function() { updateOption("workNotification", x => x.workNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#smithTimerPanelCaption').addEventListener("click", function() { updateOption("smithNotification", x => x.smithNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#mercTimerPanelCaption').addEventListener("click", function() { updateOption("mercNotification", x => x.mercNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#huntTimerPanelCaption').addEventListener("click", function() { updateOption("huntNotification", x => x.huntNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#thiefTimerPanelCaption').addEventListener("click", function() { updateOption("thiefNotification", x => x.thiefNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#leaderTimerPanelCaption').addEventListener("click", function() { updateOption("leaderNotification", x => x.leaderNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#defenceTimerPanelCaption').addEventListener("click", function() { updateOption("defenceNotification", x => x.defenceNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    
    timersPanel.querySelector("#manaTimerPanel").addEventListener("click", settings);
}
function timersPanelDataBind() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    const timersData = {
        "health": {
            panelTitle: options.healthNotification == 'yes' ? texts.healthNotificationEnabled : texts.onceHealthNotificationEnabled,
            color: options.healthNotification == 'yes' ? '#ff9c00' : '#f5c137'
        },
        "work": {
            panelTitle: [options.abuBlessInfo, getWorkaholicPenaltyText()].join('\n'),
            panelReference: GM_getValue(`LastWorkObjectId${PlayerId}`) ? `object-info.php?id=${GM_getValue(`LastWorkObjectId${PlayerId}`)}` : undefined,
            isHideable: true,
            color: options.disableWorkaholicAlarm == '0' && options.enrollNumber > 8 ? '#ff9c00' : '#f5c137'
        },
        "smith": {
            panelReference: "/mod_workbench.php?type=repair",
            isHideable: true
        },
        "merc": {
            panelReference: "/mercenary_guild.php",
            isHideable: true
        },
        "hunt": {
            panelTitle: [texts.huntTimerPanelTitle, options.huntLicenseText == "" ? "" : texts.huntLicenseExpirationMessage + options.huntLicenseText].join('\n'),
            panelReference: "/hunter_guild.php",
            isHideable: true
        },
        "thief": {
            panelReference: options.thiefOrRanger == '0' ? "/thief_guild.php" : "/ranger_guild.php",
            isHideable: true,
        },
        "leader": {
            panelTitle: `${isEn ? "Tasks available" : "Доступно заданий"}: ${JSON.parse(GM_getValue(`leaderTasks${PlayerId}`, "[3, 3]"))[0]}`,
            panelReference: "/leader_guild.php",
            isHideable: true,
        },
        "defence": {
            panelReference: "/mapwars.php",
            isHideable: true,
        },
        "mana": { }
    };
    for(const timer in timersData) {
        const timerData = timersData[timer];
        if(timerData.isHideable) {
            const timerPanelCaption = document.getElementById(`${timer}TimerPanelCaption`);
            timerPanelCaption.style.color = options[`${timer}Notification`] == 'yes' ? '#FF0000' : '#f5c137';
            timerPanelCaption.title = options[`${timer}Notification`] == 'yes' ? texts[`${timer}NotificationEnabled`] : texts.notificationDisabled;
            let isShow = options[`isShow${firstUpper(timer)}Timer`] == "1";
            const isShowNotEmptyOnly = timerSettings[timer] && timerSettings[timer].isShowNotEmptyOnly;
            if(isShowNotEmptyOnly && !getSecondsLeft(timer)) {
                isShow = false;
            }
            document.getElementById(`${timer}TimerCell`).style.display = document.getElementById(`${timer}TimerCloseCell`).style.display = (isShow ? '' : "none");
            if(timerData.panelCaption) {
                timerPanelCaption.innerText = timerData.panelCaption;
            }
        }
        const timerPanel = document.getElementById(`${timer}TimerPanel`);
        timerPanel.href = timerData.panelReference || "javascript: void(0);";
        timerPanel.title = timerData.panelTitle || texts[`${timer}Welcome`];
        if(timerData.color) {
            timerPanel.style.color = timerData.color;
        }
    }
}
function firstUpper(str) { return str[0].toUpperCase() + str.slice(1); }
function timersDataBind() {
    timerNames.forEach(x => {
        document.getElementById(`${x}TimerPanel`).innerHTML = secondsFormat(getSecondsLeft(x), timerSettings[x]?.timeFormat ?? timeFormats.full); 
    });
}
function checkWork() {
    if(location.pathname == '/object_do.php' || location.pathname == '/object-info.php') {
        if(document.body.innerHTML.match(texts.successfullyEnrolled)) {
            setWorkTimeoutEnd();
        }
    }
    if(location.pathname == '/home.php') {
        if(document.body.innerHTML.match(texts.currentlyUnemployed)) {
            GM_deleteValue(`workTimeoutEnd${PlayerId}`);
            return;
        }
        //const currentlyEmployedAt = isEn ? "Currently employed at:" : "Место работы:";
        const currentlyEmployedAt = isEn ? "employed" : "работы";
        const workObjectRef = Array.from(document.querySelectorAll("a[href^='object-info.php']")).find(x => getParent(x, isNewPersonPage ? "span" : "td").innerHTML.includes(currentlyEmployedAt));
        //console.log(workObjectRef)
        const workObjectId = workObjectRef ? getUrlParamValue(workObjectRef.href, "id") : "";
        // подхватывание времени окончания работы с home.php и его проверка
        const enrollAgainInExec = texts.enrollAgainIn.exec(document.body.innerHTML);
        if(enrollAgainInExec) {
            setWorkTimeoutEnd(getServerTime() + Number(enrollAgainInExec[1]) * 60000, workObjectId);
        } else {
            const enrollTimeExec = new RegExp(` ${isEn ? "since" : "с"} (\\d{1,2}:\\d{1,2})`).exec(document.body.innerHTML);
            if(enrollTimeExec) {
                setWorkTimeoutEnd(parseDate(enrollTimeExec[1], false, true).getTime() + 60 * 60 * 1000, workObjectId);
            }
        }
    }
}
function checkWorkaholic() {
    if(location.pathname == '/object-info.php') {
        var parent_trud = document.querySelector("a[href*='objectworkers.php']");
        if(parent_trud) {
            const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
            const workaholicPenaltyExec = new RegExp(`\\*\\&nbsp;(0\\.?\\d?) ${isEn ? "workaholic penalty" : "штраф трудоголика"}`).exec(document.body.innerHTML);
            // отработано смен
            let enrollNumber = Number(options.enrollNumber);
            if(workaholicPenaltyExec) {
                var workaholicPenalty = Number(workaholicPenaltyExec[1]);
                if(workaholicPenalty == 0.8) {
                    enrollNumber = 9;
                } else if(workaholicPenalty == 0.7) {
                    enrollNumber = 10;
                } else if(workaholicPenalty == 0.6) {
                    enrollNumber = 11;
                } else if(workaholicPenalty == 0.5) {
                    enrollNumber = 12;
                } else if(workaholicPenalty == 0.4) {
                    enrollNumber = 13;
                } else if(workaholicPenalty == 0.2) {
                    enrollNumber = 14;
                } else if(workaholicPenalty == 0.1 && enrollNumber < 15) {
                    enrollNumber = 15;
                } else if(workaholicPenalty == 0 && enrollNumber < 48) {
                    enrollNumber = 48;
                }
            } else if(enrollNumber > 8) {
                enrollNumber = 8;
            }
            //console.log(`oldEnrollNumber: ${options.enrollNumber}, enrollNumber: ${enrollNumber}, workaholicPenalty: ${workaholicPenalty}`);
            updateOption("enrollNumber", enrollNumber);
            if(options.disableWorkaholicAlarm == '0' && (options.showWorkaholicAlarmLastTwoEnrolls == '0' || enrollNumber >= 7)) {
                const add_trud = document.createElement('span');
                if(enrollNumber >= 7) {
                    add_trud.setAttribute('style', 'color: red; font-weight: bold;'); // выделить цветом
                }
                add_trud.innerHTML = getWorkaholicPenaltyText();
                parent_trud = parent_trud.parentNode.previousSibling.previousSibling;
                parent_trud.parentNode.insertBefore(add_trud, parent_trud);
            }
            // замена "Уже устроен"
            parent_trud = document.querySelector("a[href*='objectworkers.php']").parentNode.parentNode;
            if(Date.now() > parseInt(GM_getValue(`workTimeoutEnd${PlayerId}`, 0)) && (parent_trud.innerHTML.match(texts.alreadyEemployed) || (texts.alreadyEemployed = parent_trud.innerHTML.match(texts.passedLessThanOneHour)) || (texts.alreadyEemployed = parent_trud.innerHTML.match(texts.noVacancies)))) {
                parent_trud.innerHTML = parent_trud.innerHTML.replace(texts.alreadyEemployed, '<style>@-webkit-keyframes blink {80% {opacity:0.0;}} @-moz-keyframes blink {80% {opacity:0.0;}} @-o-keyframes blink {80% {opacity:0.0;}} @keyframes blink {80% {opacity:0.0;}}</style><font color=blue style="-webkit-animation: blink 1s steps(1,end) 0s infinite; -moz-animation: blink 1s steps(1,end) 0s infinite; -o-animation: blink 1s steps(1,end) 0s infinite; animation: blink 1s steps(1,end) 0s infinite"><b>' + texts.alreadyEemployed + '</b></font>');
            }
        }
    }
}
function getWorkaholicPenaltyText() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    const enrollNumber = Number(options.enrollNumber);
    const limits = [9, 11, 48];
    if(enrollNumber < limits[0]) {
        return isEn ? `Workaholic penalty through ${limits[0] - enrollNumber} enrollments` : `Штраф трудоголика через ${limits[0] - enrollNumber} устройств`;
    } else if(enrollNumber < limits[1]) {
        return isEn ? `It will be impossible to get a job in production through ${limits[1] - enrollNumber} enrollments` : `Невозможно устроиться на производство будет через ${limits[1] - enrollNumber} устройств`;
    } else if(enrollNumber < limits[2]) {
        return isEn ? `The opportunity to get a job is ending through ${limits[2] - enrollNumber} enrollments` : `Возможность устраиваться на работу заканчивается через ${limits[2] - enrollNumber} устройств`;
    }
    return "";
}
function setWorkTimeoutEnd(workTimeoutEnd, workObjectId) {
    workTimeoutEnd = workTimeoutEnd || Date.now() + 60 * 60000;
    workObjectId = workObjectId || getUrlParamValue(location.href, "id");
    const oldValue = parseInt(GM_getValue(`workTimeoutEnd${PlayerId}`, 0));
    if(Math.abs(oldValue - workTimeoutEnd) > 70000) {
        GM_setValue(`workTimeoutEnd${PlayerId}`, workTimeoutEnd);
        GM_setValue(`LastWorkObjectId${PlayerId}`, workObjectId);
        updateOption("enrollNumber", x => Number(x.enrollNumber) + 1);
    }
}
function checkPremiumAccount() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    // проверка наличия эффекта блага АБУ Бекра (премиум аккаунт) // skipn=1 это 'Ознакомился' или 'Got it!'
    if(location.pathname == '/home.php' && document.querySelector("img[src*='i/icons/attr_defense.png']") && !document.querySelector("a[href*='home.php?skipn=1']")) {
        const starImage = document.querySelector("img[src$='i/star_extend.png']") || document.querySelector("img[src$='i/star.png']");
        options.abuBlessRate = starImage ? "0.7" : "1";
        options.abuBlessExpirationTime = '0';
        options.abuBlessInfo = starImage ? (starImage.title || starImage.getAttribute("hint")) : '';
        if(starImage) {
            starImage.align = "absmiddle";
            const time_prem = /(\d+-\d+-\d+ \d+:\d+)/.exec(options.abuBlessInfo);
            if(time_prem) {
                const abuEnd = parseDate(time_prem[1], true);
                options.abuBlessExpirationTime = abuEnd.getTime();
            }
        }
    }
    if(options.abuBlessInfo && Number(options.abuBlessExpirationTime) < serverNow()) {
        options.abuBlessRate = '1';
        options.abuBlessExpirationTime = '0';
        options.abuBlessInfo = '';
    }
    GM_setValue(`hwmTimersOptions${PlayerId}`, JSON.stringify(options));
}
function checkHuntLicense() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    var form_f2 = document.querySelector("form[name='f2']");
    if(location.pathname == '/hunter_guild.php' && form_f2) {
        while(form_f2.tagName != 'TR') {
            form_f2 = form_f2.parentNode;
        }
        options.huntLicenseRate = '1';
        options.huntLicenseExpirationTime = '0';
        options.huntLicenseText = '';
        if(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/.exec(form_f2.innerHTML)) {
            if(!form_f2.querySelector("input[type='submit'][onclick*='confirm']")) {
                // лицензия МО
                options.huntLicenseRate = '' + (50 / 100);
            } else {
                // лицензия О
                options.huntLicenseRate = '' + (75 / 100);
            }
            const forms = form_f2.querySelectorAll("td");
            var time_lic_mo_max = 0;
            for(const form of forms) {
                if(form.innerHTML.indexOf("<td") != -1) {
                    continue;
                }
                var time_lic_mo = /(\d+-\d+-\d+ \d+:\d+)/.exec(form.innerHTML);
                if(time_lic_mo) {
                    const licEndTime = parseDate(time_lic_mo[1], true).getTime();
                    if(licEndTime > time_lic_mo_max) {
                        time_lic_mo_max = licEndTime;
                        options.huntLicenseExpirationTime = licEndTime;
                        options.huntLicenseText = time_lic_mo[0];
                    }
                }
            }
        }
    }
    if(options.huntLicenseText && Number(options.huntLicenseExpirationTime) < serverNow()) {
        // лицензия охотника истекла
        options.huntLicenseRate = '1';
        options.huntLicenseExpirationTime = '0';
        options.huntLicenseText = '';
    }
    GM_setValue(`hwmTimersOptions${PlayerId}`, JSON.stringify(options));
}
function setHuntTimeout(restSeconds, rate = 1) {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    restSeconds = restSeconds || ((getServerDateTime().getHours() < 8 ? 20 : 40) * rate * 60 * options.customTimeRate * (gmGetBool(`IsDeer${PlayerId}`) ? 0.6 : 1) * options.abuBlessRate * options.huntLicenseRate);
    GM_setValue(`huntTimeoutEnd${PlayerId}`, Date.now() + restSeconds * 1000);
}
function skipHunt() {
    setHuntTimeout(undefined, 0.5);
    const map_hunt_block_div = document.querySelector("div#map_hunt_block_div");
    if(map_hunt_block_div) {
        observe(map_hunt_block_div, toggleNativeHuntTimerPanel, true);
    }
}
function checkMercenary() {
    if(location.pathname == '/mercenary_guild.php') {
        const mercReputation = parseFloat(new RegExp(`${isEn ? "Reputation" : "Репутация"}: <b>([\\d\\.]+)`).exec(document.body.innerHTML)[1]);
        GM_setValue(`mercReputation${PlayerId}`, mercReputation);
        const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
        const mercTimeout = (40 - mercReputation * 2) * options.customTimeRate * (gmGetBool(`IsDeer${PlayerId}`) ? 0.6 : 1) * options.abuBlessRate * 60000;
        let newMercTaskRestTimeExec;
        if(document.querySelector("a[href^='/mercenary_guild.php?action=accept']")) {
            GM_deleteValue(`mercTimeoutEnd${PlayerId}`);
        } else if((newMercTaskRestTimeExec = texts.regexp_timegn0.exec(document.body.innerHTML)) || (newMercTaskRestTimeExec = texts.regexp_timegn1.exec(document.body.innerHTML)) || (newMercTaskRestTimeExec = texts.regexp_timegn2.exec(document.body.innerHTML)) || (newMercTaskRestTimeExec = texts.regexp_timegn3.exec(document.body.innerHTML)) || (newMercTaskRestTimeExec = texts.regexp_timegn4.exec(document.body.innerHTML)) || (newMercTaskRestTimeExec = texts.regexp_timegn5.exec(document.body.innerHTML))) {
            let restTimeout = Number(newMercTaskRestTimeExec[1]);
            if(texts.regexp_timegn0.exec(document.body.innerHTML) && (restTimeout == 19 || restTimeout == 13)) {
                restTimeout++;
            }
            setMercTimeout(null, restTimeout * 60000);
        }
    }
    if(location.pathname == "/map.php") {
        const mercTaskTable = getParent(document.querySelector("table.wbwhite center a[href='mercenary_guild.php']"), "table");
        if(mercTaskTable) {
            const execTaskButton = mercTaskTable.querySelector("div[id^='hunt_but_'] > img");
            if(execTaskButton) {
                execTaskButton.addEventListener("click", function() { updateOption("battleType", "merc"); });
            }
        }
    }
}
function setMercTimeout(battleResult = null, timeout = undefined) {
    console.log(`battleResult: ${battleResult}, timeout: ${timeout}`);
    let mercReputation = parseFloat(GM_getValue(`mercReputation${PlayerId}`, 0));
    if(battleResult == "win") {
        mercReputation = Math.min(mercReputation + 0.5, 10);
        GM_setValue(`mercReputation${PlayerId}`, mercReputation);
    }
    if(battleResult == "fail") {
        mercReputation = Math.max(mercReputation - 1, 0);
        GM_setValue(`mercReputation${PlayerId}`, mercReputation);
    }
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    timeout = timeout || (40 - mercReputation * 2) * options.customTimeRate * (gmGetBool(`IsDeer${PlayerId}`) ? 0.6 : 1) * options.abuBlessRate * 60000;
    const newTimeoutEnd = Date.now() + timeout;
    if(Math.abs(parseInt(GM_getValue(`mercTimeoutEnd${PlayerId}`, 0)) - newTimeoutEnd) > 70000) {
        GM_setValue(`mercTimeoutEnd${PlayerId}`, newTimeoutEnd);
    }
}
function checkLeaders(doc = document) {
    if(location.pathname == '/leader_guild.php') {
        const nextLg = doc.querySelector("div#next_lg");
        const timeoutEnd = parseTimeoutEnd(nextLg.innerHTML, isEn ? {Hours: "h", Minutes: "m", Seconds: "s"} : {Hours: "ч.", Minutes: "мин.", Seconds: "с."});
        refreshTimeout("leader", timeoutEnd.getTime());
        const tasksExec = new RegExp(`(\\d) ${isEn ? "of" : "из"} (\\d)`).exec(getParent(nextLg, "td").firstChild.textContent);
        //console.log(tasksExec)
        GM_setValue(`leaderTasks${PlayerId}`, JSON.stringify([parseInt(tasksExec[1]), parseInt(tasksExec[2])]));
        GM_setValue(`leaderGoalsNumber${PlayerId}`, Array.from(doc.querySelectorAll("form[name=f] > input[type=submit]")).length);

        Array.from(document.querySelectorAll("form[name^='f'] input[type='submit']")).forEach(x => x.addEventListener("click", function () { updateOption("battleType", "leader"); }));
    }
    //console.log(`leaderGoalsNumber: ${GM_getValue(`leaderGoalsNumber${PlayerId}`)}, leaderTasks: ${GM_getValue(`leaderTasks${PlayerId}`)}, TimeoutEnd: ${GM_getValue(`leaderTimeoutEnd${PlayerId}`)}`);
}
function setLeaderTimeout(result) {
    const leaderGoalsNumber = parseInt(GM_getValue(`leaderGoalsNumber${PlayerId}`, 0));
    if(result == "win" || result == "fail" && leaderGoalsNumber == 1) {
        // Если победили или проиграли, когда на выбор оставалась одна цель. Тогда уменьшаем количество доступных заданий. А если их был максимум, и таймер стоял, то запустим таймер.
        const leaderTasks = JSON.parse(GM_getValue(`leaderTasks${PlayerId}`, "[3, 3]"));
        let tasksNumber = leaderTasks[0];
        const maxTasksNumber = leaderTasks[1];
        if(tasksNumber == maxTasksNumber) {
            GM_setValue(`leaderTimeoutEnd${PlayerId}`, serverNow() + 3600000 * 3);
        }
        tasksNumber = Math.max(tasksNumber - 1, 0);
        GM_setValue(`leaderTasks${PlayerId}`, JSON.stringify([tasksNumber, maxTasksNumber]));
        GM_setValue(`leaderGoalsNumber${PlayerId}`, 3); // Также сбрасываем счетчик доступных целей
    }
}
function refreshTimeout(timer, newTime) {
    const savedTimeoutEnd = GM_getValue(`${timer}TimeoutEnd${PlayerId}`);
    if(!savedTimeoutEnd || Math.abs(newTime - parseInt(savedTimeoutEnd)) / 1000 / 60 > 2) {
        GM_setValue(`${timer}TimeoutEnd${PlayerId}`, newTime);
    }
    //console.log(new Date(parseInt(GM_getValue(`${timer}TimeoutEnd${PlayerId}`, 0))));
}
function checkRangerGuild() {
    if(location.pathname == '/ranger_guild.php') {
        if(document.querySelector("a[href^='ranger_guild.php?action=accept']")) {
            GM_deleteValue(`thiefTimeoutEnd${PlayerId}`);
            updateOption("thiefOrRanger", "1");
        }
        var time_gv = texts.regexp_timegre.exec(document.body.innerHTML);
        if(time_gv) {
            time_gv = Number(time_gv[1]) * 60000; // в миллисекундах
            const now = Date.now();
            var time_gv_temp = time_gv - Math.abs(parseInt(GM_getValue(`thiefTimeoutEnd${PlayerId}`, 0)) - now);
            if(Math.abs(time_gv_temp) > 70000) {
                GM_setValue(`thiefTimeoutEnd${PlayerId}`, now + time_gv)
                updateOption("thiefOrRanger", "1");
            }
        }
    }
    if(location.pathname == '/ranger_list.php') {
        var link_ranger_attack = document.querySelectorAll("a[href^='ranger_attack.php?join']");
        if(link_ranger_attack.length > 0) {
            GM_deleteValue(`thiefTimeoutEnd${PlayerId}`)
            updateOption("thiefOrRanger", "1");
            for(const link_ranger_attackItem of link_ranger_attack) {
                link_ranger_attackItem.addEventListener("click", function() { updateOption("battleType", 'thief'); });
            }
        }
    }
}
function checkModWorkebench() {
    if(location.pathname == '/mod_workbench.php') {
        parseSmithPage(document);
    }
}
function parseSmithPage(doc) {
    const allb = doc.querySelectorAll("b");
    for(const bold of allb) {
        if(bold.innerText.includes(isEn ? "Under repair" : "В ремонте")) {
            var repairData = bold.innerText;
            break;
        }
    }
    if(repairData) {
        const repairEnd = parseTimeoutEnd(repairData, isEn ? {Hours: "h.", Minutes: "min."} : {Hours: "ч.", Minutes: "мин."}, 59);
        refreshTimeout("smith", repairEnd.getTime());
    } else {
        GM_deleteValue(`smithTimeoutEnd${PlayerId}`);
    }
}
function parseTimeoutEnd(text, masks, defaultSeconds = 0) {
    const restTime = { Hours: 0, Minutes: 0, Seconds: defaultSeconds };
    for(const mask in masks) {
        const regex = new RegExp(`(\\d{1,2}) ${masks[mask]}`);
        const regexResult = regex.exec(text);
        if(regexResult) {
            restTime[mask] = parseInt(regexResult[1]);
        }
    }
    //console.log(text);
    //console.log(restTime);
    const timeEnd = new Date(serverNow());
    timeEnd.setHours(timeEnd.getHours() + restTime.Hours, timeEnd.getMinutes() + restTime.Minutes, timeEnd.getSeconds() + restTime.Seconds);
    return timeEnd;
}
function checkThiefAmbush() {
    const thief_ambush_cancel = document.querySelector("a[href^='thief_ambush_cancel.php']");
    const form_thief_ambush = document.querySelector("form[action='thief_ambush.php']");
    if(thief_ambush_cancel || form_thief_ambush) {
        GM_deleteValue(`thiefTimeoutEnd${PlayerId}`);
        updateOption("thiefOrRanger", "0");
    }
    if(thief_ambush_cancel) {
        updateOption("battleType", "thief"); // Сидим в засаде, будет воровской бой
    }
}
function checkRangerAmbush() {
    var form_ranger_attack = document.querySelector("form[action='ranger_attack.php']");
    if(form_ranger_attack) {
        GM_deleteValue(`thiefTimeoutEnd${PlayerId}`);
        updateOption("thiefOrRanger", "1");
        form_ranger_attack.querySelector("input[type='submit']").addEventListener("click", function() { updateOption("battleType", 'thief'); });
        const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
        if(options.joinRangerBattle == '1') {
            setTimeout(function() { updateOption("battleType", 'thief'); form_ranger_attack.submit(); }, 500);
        }
    }
}
function checkMapHunter() {
    const map_hunt_block_div = document.querySelector("div#map_hunt_block_div");
    if(map_hunt_block_div) {
        const nativeHuntTimer = map_hunt_block_div.querySelector("div#next_ht_new");
        if(nativeHuntTimer) {
            setHuntTimeout(windowObject.MapHunterDelta);
        } else {
            GM_deleteValue(`huntTimeoutEnd${PlayerId}`);
            const attackButtons = map_hunt_block_div.querySelectorAll(`div[hint='${isEn ? "Attack" : "Напасть"}'] > img`);
            for(const button of attackButtons) {
                //console.log(button)
                button.addEventListener("click", function() { updateOption("battleType", "hunt"); });
            }
            const skipButtons = map_hunt_block_div.querySelectorAll(`div[hint^='${isEn ? "Pass" : "Пройти"}'] > img`);
            for(const button of skipButtons) {
                button.addEventListener("click", function() { skipHunt(); });
            }
            const callButtons = map_hunt_block_div.querySelectorAll(`div[hint^='${isEn ? "Ask" : "Позвать"}'] > img`);
            for(const button of callButtons) {
                button.addEventListener("click", function() { setTimeout(function() { Array.from(document.querySelectorAll("form[action='/map.php']")).forEach(x => x.querySelector("input[type='submit']").addEventListener("click", function() { updateOption("battleType", "hunt"); })); }, 200); });
            }
        }
    }
    if(mooving && !GM_getValue(`huntTimeoutEnd${PlayerId}`)) {
        skipHunt();
    }
    toggleNativeHuntTimerPanel();
}
function toggleNativeHuntTimerPanel() {
    const next_ht_new = document.querySelector("div#next_ht_new");
    if(next_ht_new) {
        document.querySelector("div#map_hunt_block_div").style.display = gmGetBool("HideNativeHuntTimer") ? "none" : "";
    }
}
function secondsFormat(secondsLeft, timeFormat = timeFormats.full) {
    if(!secondsLeft || secondsLeft < 0) {
        return timeFormat == timeFormats.secondsLastMinute ? "00" : "00:00";
    }
    const days = Math.floor(secondsLeft / 86400);
    const hours = Math.floor((secondsLeft - days * 86400) / 3600);
    const minutes = Math.floor((secondsLeft - days * 86400 - hours * 3600) / 60);
    const seconds = secondsLeft % 60;
    //console.log(`timeFormat: ${timeFormat}, days: ${days}, hours: ${hours}`)
    return (days === 0 ? '' : ((days < 10) ? '0' : '') + days + ':')
    + (days === 0 && hours === 0 ? '' : hours.toString().padStart(2, "0") + ':')
    + (timeFormat == timeFormats.secondsLastMinute && secondsLeft < 60 ? "" : minutes.toString().padStart(2, "0"))
    + ((timeFormat == timeFormats.secondsLastMinute && secondsLeft >= 60 || timeFormat == timeFormats.hoursOrSeconds && (days > 0 || hours > 0)) ? "" : (timeFormat == timeFormats.secondsLastMinute && secondsLeft < 60 ? "" : ':') + seconds.toString().padStart(2, "0"));
}
function getSecondsLeft(timerName) {
    //console.log(`${timerName}: ${GM_getValue(`${timerName}TimeoutEnd${PlayerId}`)}`)
    if(GM_getValue(`${timerName}TimeoutEnd${PlayerId}`)) {
        const result = Math.round((parseInt(GM_getValue(`${timerName}TimeoutEnd${PlayerId}`)) - Date.now()) / 1000);
        if(result >= 0) {
            return result;
        }
    }
}
function tick() {
    timersDataBind();
    for(const timerName of timerNames) {
        const secondsLeft = getSecondsLeft(timerName);
        if(secondsLeft == 0) {
            GM_deleteValue(`${timerName}TimeoutEnd${PlayerId}`);
            signal(timerName);
        } else if(!secondsLeft && GM_getValue(`${timerName}TimeoutEnd${PlayerId}`)) {
            GM_deleteValue(`${timerName}TimeoutEnd${PlayerId}`);
        }
    }
    setTimeout(tick, 1000);
}
async function signal(timerName) {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    if(options[`${timerName}Notification`] == 'no') {
        return;
    }
    if(Date.now() < parseInt(GM_getValue(`${timerName}LastNotificationTime${PlayerId}`, 0)) + 60000) {
        return;
    }
    GM_setValue(`${timerName}LastNotificationTime${PlayerId}`, Date.now());
    switch(GM_getValue(`NotificationType${PlayerId}`, '0')) {
        case '0':
            toggleAudio(timersAudioMap[timerName]);
            break;
        case '1':
            alert(texts[`${timerName}Message`]);
            break;
        case '2':
            GM.notification(texts[`${timerName}Message`], "ГВД", "https://dcdn.heroeswm.ru/i/rewards/fast_t/3x3_1.png", function() { window.focus(); });
            break;
    }
    if(timerName == 'health') {
        updateOption("healthNotification", 'no');
        timersPanelDataBind();
    }
    if(timerName == "leader") {
        const leaderTasks = JSON.parse(GM_getValue(`leaderTasks${PlayerId}`, "[3, 3]"));
        let tasksNumber = leaderTasks[0];
        const maxTasksNumber = leaderTasks[1];
        if(tasksNumber < maxTasksNumber) {
            tasksNumber++;
            GM_setValue(`leaderTasks${PlayerId}`, JSON.stringify([tasksNumber, maxTasksNumber]));
            timersPanelDataBind();
            if(tasksNumber < maxTasksNumber) {
                GM_setValue(`${timerName}TimeoutEnd${PlayerId}`, serverNow() + 3600000 * 3);
            }
        }
    }
}
function settings() {
    if(showPupupPanel(GM_info.script.name)) {
        return;
    }
    const bgcinnerHTML = `
<tr>
    <td>${texts.isShowTimersTitle}&nbsp;&nbsp;${texts.workTimerPanelCaption}:<input id=isShowWorkTimerCheckbox type=checkbox>
&nbsp;&nbsp;${texts.smithTimerPanelCaption}:<input id=isShowSmithTimerCheckbox type=checkbox>
&nbsp;&nbsp;${texts.mercTimerPanelCaption}:<input id=isShowMercTimerCheckbox type=checkbox>
&nbsp;&nbsp;${texts.huntTimerPanelCaption}:<input id=isShowHuntTimerCheckbox type=checkbox>
&nbsp;&nbsp;${texts.thiefTimerPanelCaption}:<input id=isShowThiefTimerCheckbox type=checkbox>
&nbsp;&nbsp;${texts.leaderTimerPanelCaption}:<input id=isShowLeaderTimerCheckbox type=checkbox>
&nbsp;&nbsp;${texts.defenceTimerPanelCaption}:<input id=isShowDefenceTimerCheckbox type=checkbox>
    </td>
</tr>
<tr>
    <td>${texts.joinRangerBattleText}: <input id="joinRangerBattleCheckbox" type=checkbox></td>
</tr>
<tr>
    <td>${texts.hide} "<i>${texts.nativeHuntTimerText} ..</i>": <input id=hideNativeHuntTimerCheckbox type=checkbox></td>
</tr>
<tr>
    <td>${texts.disableWorkaholicAlarmTitle}: <input id=disableWorkaholicAlarmCheckbox type=checkbox></td>
</tr>
<tr>
    <td>${texts.showWorkaholicAlarmLastTwoEnrollsTitle}: <input id=showWorkaholicAlarmLastTwoEnrollsCheckbox type=checkbox></td>
</tr>
<tr>
    <td id="twmTimersSettingsAbuText"></td>
</tr>
<tr>
    <td id="deerText"></td>
</tr>
<tr>
    <td>${texts.huntLicenseAuto}</td>
</tr>
<tr>
    <td>${texts.cusomRateTitle} <input id="cusomRateInput" type="number" style="width: 50px;" maxlength="2" onfocus="this.select();"> <b>%</b></td>
</tr>
<tr>
    <td>${texts.setOnceThiefTimeout} <input id="onceThiefTimeoutInput" type="number" style="width: 50px;" maxlength="2" onfocus="this.select();"> <b>min</b>
    <input type="submit" id="setOnceThiefTimerButton" value="ok"></td>
</tr>
<tr>
    <td> <input type="submit" id="resetTimersButton" value="${texts.resetTimersTitle}">&nbsp;&nbsp;&nbsp;</td>
</tr>
<tr>
    <td>${texts.alarm_mode} <input type="radio" name="notificationTypeOption" id="notificationType0Option">${texts.alarm_mode_sound}
    <input type="radio" name="notificationTypeOption" id="notificationType1Option">${texts.alarm_mode_alert}
    <input type="radio" name="notificationTypeOption" id="notificationType2Option">${texts.alarm_mode_both}
    <input type="radio" name="notificationTypeOption" id="notificationType3Option">${texts.alarm_mode_none}
    </td>
</tr>
<tr>
    <td>
        <table>
            <tr>
                <td>${texts.signalSound + texts.healthTitle}</td>
                <td><input size=55 type="text" id="healthSoundInput"></td>
                <td><input size=55 type="button" id="healthSoundInputPlay"></td>
            </tr>
            <tr>
                <td>${texts.signalSound + texts.workTimerPanelCaption}</td>
                <td><input size=55 type="text" id="workSoundInput"></td>
                <td><input size=55 type="button" id="workSoundInputPlay"></td>
            </tr>
            <tr>
                <td>${texts.signalSound + texts.smithTimerPanelCaption}</td>
                <td><input size=55 type="text" id="smithSoundInput"></td>
                <td><input size=55 type="button" id="smithSoundInputPlay"></td>
            </tr>
            <tr>
                <td>${texts.signalSound + texts.warlikeTitle}</td>
                <td><input size=55 type="text" id="warlikeSoundInput"></td>
                <td><input size=55 type="button" id="warlikeSoundInputPlay"></td>
            </tr>
            <tr>
                <td>${texts.signalSound + texts.leaderTitle}</td>
                <td><input size=55 type="text" id="leaderSoundInput"></td>
                <td><input size=55 type="button" id="leaderSoundInputPlay"></td>
            </tr>
            <tr>
                <td>${texts.signalSound + texts.defenceTitle}</td>
                <td><input size=55 type="text" id="defenceSoundInput"></td>
                <td><input size=55 type="button" id="defenceSoundInputPlay"></td>
            </tr>
        </table>
    </td>
</tr>
`;
    const optionsContainer = createElement("table", { innerHTML: bgcinnerHTML });

    optionsContainer.querySelector("#resetTimersButton").addEventListener("click", resetTimers);
    optionsContainer.querySelector("#setOnceThiefTimerButton").addEventListener("click", function() { if(Number(document.getElementById("onceThiefTimeoutInput").value) >= 0) { GM_setValue(`thiefTimeoutEnd${PlayerId}`, Date.now() + document.getElementById("onceThiefTimeoutInput").value * 60000); } });
    optionsContainer.querySelector("#cusomRateInput").addEventListener("change", function() { updateOption("customTimeRate", (100 - this.value) / 100); settingsDataBind(); });
    optionsContainer.querySelector("#joinRangerBattleCheckbox").addEventListener("click", function() { updateOption("joinRangerBattle", this.checked ? '1' : '0'); });
    optionsContainer.querySelector("#hideNativeHuntTimerCheckbox").addEventListener("click", function() { GM_setValue("HideNativeHuntTimer", this.checked); toggleNativeHuntTimerPanel(); }, false);
    optionsContainer.querySelector("#showWorkaholicAlarmLastTwoEnrollsCheckbox").addEventListener("click", function() { updateOption("showWorkaholicAlarmLastTwoEnrolls", this.checked ? '1' : '0'); });
    optionsContainer.querySelector("#disableWorkaholicAlarmCheckbox").addEventListener("click", function() { updateOption("disableWorkaholicAlarm", this.checked ? '1' : '0'); });

    optionsContainer.querySelector("#isShowWorkTimerCheckbox").addEventListener("click", function() { updateOption("isShowWorkTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });
    optionsContainer.querySelector("#isShowSmithTimerCheckbox").addEventListener("click", function() { updateOption("isShowSmithTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });
    optionsContainer.querySelector("#isShowMercTimerCheckbox").addEventListener("click", function() { updateOption("isShowMercTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });
    optionsContainer.querySelector("#isShowHuntTimerCheckbox").addEventListener("click", function() { updateOption("isShowHuntTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });
    optionsContainer.querySelector("#isShowThiefTimerCheckbox").addEventListener("click", function() { updateOption("isShowThiefTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });
    optionsContainer.querySelector("#isShowLeaderTimerCheckbox").addEventListener("click", function() { updateOption("isShowLeaderTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });
    optionsContainer.querySelector("#isShowDefenceTimerCheckbox").addEventListener("click", function() { updateOption("isShowDefenceTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });


    optionsContainer.querySelector("#workSoundInput").addEventListener("change", function() { updateOption("workSound", this.value.trim()); });
    optionsContainer.querySelector("#warlikeSoundInput").addEventListener("change", function() { updateOption("warlikeSound", this.value.trim()); });
    optionsContainer.querySelector("#leaderSoundInput").addEventListener("change", function() { updateOption("leaderSound", this.value.trim()); });
    optionsContainer.querySelector("#defenceSoundInput").addEventListener("change", function() { updateOption("defenceSound", this.value.trim()); });
    optionsContainer.querySelector("#smithSoundInput").addEventListener("change", function() { updateOption("smithSound", this.value.trim()); });
    optionsContainer.querySelector("#healthSoundInput").addEventListener("change", function() { updateOption("healthSound", this.value.trim()); });

    optionsContainer.querySelector("#healthSoundInputPlay").addEventListener("click", buttonToggleAudio);
    optionsContainer.querySelector("#workSoundInputPlay").addEventListener("click", buttonToggleAudio);
    optionsContainer.querySelector("#smithSoundInputPlay").addEventListener("click", buttonToggleAudio);
    optionsContainer.querySelector("#warlikeSoundInputPlay").addEventListener("click", buttonToggleAudio);
    optionsContainer.querySelector("#leaderSoundInputPlay").addEventListener("click", buttonToggleAudio);
    optionsContainer.querySelector("#defenceSoundInputPlay").addEventListener("click", buttonToggleAudio);

    optionsContainer.querySelector("#notificationType0Option").addEventListener("click", function() { if(this.checked) { GM_setValue(`NotificationType${PlayerId}`, "0"); } });
    optionsContainer.querySelector("#notificationType1Option").addEventListener("click", function() { if(this.checked) { GM_setValue(`NotificationType${PlayerId}`, "1"); } });
    optionsContainer.querySelector("#notificationType2Option").addEventListener("click", function() { if(this.checked) { GM_setValue(`NotificationType${PlayerId}`, "2"); } });
    optionsContainer.querySelector("#notificationType3Option").addEventListener("click", function() { if(this.checked) { GM_setValue(`NotificationType${PlayerId}`, "3"); } });
    optionsContainer.querySelector(`#notificationType${GM_getValue(`NotificationType${PlayerId}`, 0)}Option`).checked = true;

    createPupupPanel(GM_info.script.name, `${isEn ? "Options" : "Настройки"} ${GM_info.script.name}`, [[optionsContainer]]);

    settingsDataBind();
}
function settingsDataBind() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    document.getElementById("isShowWorkTimerCheckbox").checked = options.isShowWorkTimer == "1";
    document.getElementById("isShowSmithTimerCheckbox").checked = options.isShowSmithTimer == "1";
    document.getElementById("isShowMercTimerCheckbox").checked = options.isShowMercTimer == "1";
    document.getElementById("isShowHuntTimerCheckbox").checked = options.isShowHuntTimer == "1";
    document.getElementById("isShowThiefTimerCheckbox").checked = options.isShowThiefTimer == "1";
    document.getElementById("isShowLeaderTimerCheckbox").checked = options.isShowLeaderTimer == "1";
    document.getElementById("isShowDefenceTimerCheckbox").checked = options.isShowDefenceTimer == "1";
    

    document.getElementById("joinRangerBattleCheckbox").checked = options.joinRangerBattle == "1";
    document.getElementById("hideNativeHuntTimerCheckbox").checked = gmGetBool("HideNativeHuntTimer");
    document.getElementById("disableWorkaholicAlarmCheckbox").checked = options.disableWorkaholicAlarm == "1";
    document.getElementById("showWorkaholicAlarmLastTwoEnrollsCheckbox").checked = options.showWorkaholicAlarmLastTwoEnrolls == "1";

    document.getElementById("deerText").innerHTML = gmGetBool(`IsDeer${PlayerId}`) ? `${isEn ? `Deer Yasha. HG, MG, TG, RG tasks 40% more often` : `Олень Яша. Задания ГО, ГН, ГВ, ГРж на 40% чаще`}` : "";
    document.getElementById("twmTimersSettingsAbuText").innerHTML = options.abuBlessInfo;

    document.getElementById("cusomRateInput").value = 100 - options.customTimeRate * 100;
    document.getElementById("onceThiefTimeoutInput").value = 60 * options.customTimeRate * options.abuBlessRate;

    for(const audioScenario of audioScenaries) {
        document.getElementById(`${audioScenario}SoundInput`).value = options[`${audioScenario}Sound`];
        const audioPlayed = playingAudios.hasOwnProperty(audioScenario);
        document.getElementById(`${audioScenario}SoundInputPlay`).value = audioPlayed ? "Stop" : "Play";
        if(audioPlayed) {
            playingAudios[audioScenario].addEventListener("ended", () => { document.getElementById(`${audioScenario}SoundInputPlay`).value = "Play"; }, true);
        }
    }
}
function initAudio(soundUrl, onEnded) {
    let audio = defaultAudio;
    if(soundUrl) {
        audio = new Audio();
        audio.src = soundUrl;
        audio.preload = 'auto';
    }
    if(onEnded) {
        audio.addEventListener("ended", onEnded, true);
    }
    return audio;
}
function buttonToggleAudio(event) { toggleAudio(event.target.id.replace("SoundInputPlay", "")); }
async function toggleAudio(audioScenarioName) {
    const playButton = document.getElementById(`${audioScenarioName}SoundInputPlay`);
    const isPlaying = playingAudios.hasOwnProperty(audioScenarioName);
    if(isPlaying) {
        playingAudios[audioScenarioName].pause();
        delete playingAudios[audioScenarioName];
    } else {
        const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
        const audio = initAudio(options[`${audioScenarioName}Sound`], () => { if(playButton) playButton.value = "Play"; delete playingAudios[audioScenarioName]; });
        playingAudios[audioScenarioName] = audio;
        audio.play();
    }
    if(playButton) playButton.value = isPlaying ? "Play" : "Stop";
}
function updateOption(key, val) {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    if(typeof val == "function") {
        options[key] = val(options);
    } else {
        options[key] = val;
    }
    if(key == "battleType") {
        console.log(`battleType: ${options[key]}`);
    }
    GM_setValue(`hwmTimersOptions${PlayerId}`, JSON.stringify(options));
}
function resetTimers() {
    GM_deleteValue(`workTimeoutEnd${PlayerId}`);
    GM_deleteValue(`smithTimeoutEnd${PlayerId}`);
    GM_deleteValue(`mercTimeoutEnd${PlayerId}`);
    GM_deleteValue(`huntTimeoutEnd${PlayerId}`);
    GM_deleteValue(`thiefTimeoutEnd${PlayerId}`);
    GM_deleteValue(`leaderTimeoutEnd${PlayerId}`);
}
async function checkDefences() {
    //GM_deleteValue(`defenceTimeoutEnd${PlayerId}`);
    const defenceWaiting = (isNewInterface ? document.querySelector("div#MenuBattles_expandable > a[href='mapwars.php'] > div.sh_dd_container_orange") : document.querySelector("li > a[href='mapwars.php'] > font[color='#ff9c00']")) ? true : false;
    const defenceTimeoutEnd = GM_getValue(`defenceTimeoutEnd${PlayerId}`);
    //console.log(`defenceWaiting: ${defenceWaiting}, defenceTimeoutEnd: ${(new Date(parseInt(defenceTimeoutEnd || 0))).toLocaleString()} ${defenceTimeoutEnd}`);
    if(defenceWaiting && !defenceTimeoutEnd) {
        const defenceTime = await findDefences();
        GM_setValue(`defenceTimeoutEnd${PlayerId}`, defenceTime);
        timersPanelDataBind();
        //console.log(`defenceTime: ${new Date(defenceTime).toLocaleString()}`);
    }
    if(!defenceWaiting && defenceTimeoutEnd) {
        GM_deleteValue(`defenceTimeoutEnd${PlayerId}`);
    }
}
async function findDefences() {
    const doc = location.pathname == "/mapwars.php" ? document : await getRequest("/mapwars.php");
    const defenceTable = doc.querySelector("body > center > table:nth-child(2) * table * table.wbwhite");
    if(defenceTable) {
        for(const row of defenceTable.rows) {
            const enlistingRegExp = new RegExp(`${isEn ? "Enlisting in defense possible at" : "вступление на защиту с"} (\\d{1,2}:\\d{1,2})`);
            const defenceEndExec = enlistingRegExp.exec(row.cells[2].innerHTML);
            if(defenceEndExec) {
                return parseDate(defenceEndExec[1], true).getTime();
            }
        }
    }
}
// API
function getUrlParamValue(url, paramName) { return (new URLSearchParams(url.split("?")[1])).get(paramName); }
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 GM_addStyle(css) { addElement("style", document.head, { type: "text/css", innerHTML: css }); }
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 getServerTime() { return Date.now() + parseInt(GM_getValue("ClientServerTimeDifference", 0)); }
function getServerDateTime() { return new Date(getServerTime()); }
function toServerTime(clientTime) { return clientTime - parseInt(GM_getValue("ClientServerTimeDifference", 0)); }
function serverNow() { return toServerTime(Date.now()); }
async function requestServerTime() {
    if(parseInt(GM_getValue("LastClientServerTimeDifferenceRequestDate", 0)) + 60 * 60 * 1000 < Date.now()) {
        GM_setValue("LastClientServerTimeDifferenceRequestDate", Date.now());
        const responseText = await getRequestText("/time.php");
        const responseParcing = /now (\d+)/.exec(responseText); //responseText: now 1681711364 17-04-23 09:02
        if(responseParcing) {
            GM_setValue("ClientServerTimeDifference", Date.now() - parseInt(responseParcing[1]) * 1000);
        }
    } else {
        setTimeout(requestServerTime, 60 * 60 * 1000);
    }
}
function gmGetBool(valueName) {
    const value = GM_getValue(valueName);
    if(value) {
        if(typeof(value) == "string") {
            return value == "true";
        }
        if(typeof(value) == "boolean") {
            return value;
        }
    }
    return false;
}
function observe(target, handler, once = false) {
    const config = { childList: true, subtree: true };
    const ob = new MutationObserver(async function(mut, observer) {
        observer.disconnect();
        if(handler.constructor.name === 'AsyncFunction') {
            await handler();
        } else {
            handler();
        }
        if(!once) {
            observer.observe(target, config);
        }
    });
    ob.observe(target, config);
}
function createPupupPanel(panelName, panelTitle, fieldsMap, panelToggleHandler) {
    const backgroundPopupPanel = addElement("div", document.body, { id: panelName + "1", style: "position: fixed; left: 0pt; width: 100%; background: none repeat scroll 0% 0% gray; opacity: 0.5; top: 0px; height: 100%; display: block; z-index: 200;" });
    backgroundPopupPanel.addEventListener("click", function() { hidePupupPanel(panelName, panelToggleHandler); });

    const popupPanel = addElement("div", document.body, { id: panelName + "2", style: `position: fixed; width: 650px; background: none repeat scroll 0% 0%; background-image: linear-gradient(to right, #eea2a2 0%, #bbc1bf 19%, #57c6e1 42%, #b49fda 79%, #7ac5d8 100%); left: ${((document.body.offsetWidth - 650) / 2)}px; top: 150px; display: block; z-index: 200; border: 4mm ridge rgba(211, 220, 50, .6);` });
    const contentDiv = addElement("div", popupPanel, { id: panelName + "3", style: "border: 1px solid #abc; padding: 5px; margin: 2px; display: flex; flex-wrap: wrap;" });

    if(panelTitle) {
        addElement("b", contentDiv, { innerText: panelTitle, style: "text-align: center; margin: auto; width: 90%; display: block;" });
    }
    const divClose = addElement("div", contentDiv, { id: panelName + "close", title: "Close", innerText: "x", style: "border: 1px solid #abc; width: 15px; height: 15px; text-align: center; cursor: pointer;" });
    divClose.addEventListener("click", function() { hidePupupPanel(panelName, panelToggleHandler); });

    addElement("div", contentDiv, { style: "flex-basis: 100%; height: 0;"});

    if(fieldsMap) {
        let contentTable = addElement("table", contentDiv);
        for(const rowData of fieldsMap) {
            if(rowData.length == 0) { // Спомощью передачи пустой стороки-массива, указываем, что надо начать новую таблицу после брейка
                addElement("div", contentDiv, { style: "flex-basis: 100%; height: 0;"});
                contentTable = addElement("table", contentDiv);
                continue;
            }
            const row = addElement("tr", contentTable);
            for(const cellData of rowData) {
                const cell = addElement("td", row);
                if(cellData) {
                    if(typeof(cellData) == "string") {
                        cell.innerText = cellData;
                    } else {
                        cell.appendChild(cellData);
                    }
                }
            }
        }
    }
    if(panelToggleHandler) {
        panelToggleHandler(true);
    }
    return contentDiv;
}
function showPupupPanel(panelName, panelToggleHandler) {
    let backgroundPopupPanel = document.getElementById(panelName + "1");
    let popupPanel = document.getElementById(panelName + "2");
    if(backgroundPopupPanel) {
        backgroundPopupPanel.style.display = popupPanel.style.display = 'block';
        if(panelToggleHandler) {
            panelToggleHandler(true);
        }
        return true;
    }
    return false;
}
function hidePupupPanel(panelName, panelToggleHandler) {
    let backgroundPopupPanel = document.getElementById(panelName + "1");
    let popupPanel = document.getElementById(panelName + "2");
    backgroundPopupPanel.style.display = popupPanel.style.display = 'none';
    if(panelToggleHandler) {
        panelToggleHandler(false);
    }
}
function healthTimer() {
    if(isHeartOnPage) {
        const health_amount = document.getElementById("health_amount");
        let heart; // 78 %
        let maxHeart; // 100 %
        let timeHeart; // 405 сек.
        if(health_amount) {
            const res = /top_line_draw_canvas_heart\((\d+), (\d+), (\d+)\);/.exec(document.body.innerHTML); // top_line_draw_canvas_heart(0, 100, 405);
            if(res) {
                heart = parseInt(res[1]);
                maxHeart = parseInt(res[2]);
                timeHeart = parseInt(res[3]);
            }
        } else {
            heart = windowObject.heart;
            maxHeart = windowObject.max_heart;
            timeHeart = windowObject.time_heart;
        }
        //console.log(`healthTimer heart: ${heart}, maxHeart: ${maxHeart}, timeHeart: ${timeHeart}`);
        let restSeconds = timeHeart * (maxHeart - heart) / maxHeart;
        if(restSeconds > 0) {
            GM_setValue(`healthTimeoutEnd${PlayerId}`, serverNow() + restSeconds * 1000);
        } else {
            GM_deleteValue(`healthTimeoutEnd${PlayerId}`);
        }
        return [heart, maxHeart, timeHeart];
    }
}
function manaTimer() {
    if(isHeartOnPage) {
        const mana_amount = document.getElementById("mana_amount");
        // var mana=15;
        // var max_mana=40;
        // var time_mana=900;
        let mana = 10; // 15
        let maxMana = 10; // 40
        let timeMana = 900; // 900 сек.
        if(mana_amount) {
            // const res = /top_line_draw_canvas_heart\((\d+), (\d+), (\d+)\);/.exec(document.body.innerHTML); // top_line_draw_canvas_heart(0, 100, 405);
            // if(res) {
                // mana = parseInt(res[1]);
                // maxMana = parseInt(res[2]);
                // timeMana = parseInt(res[3]);
            // }
        } else {
            mana = windowObject.mana;
            maxMana = windowObject.max_mana;
            timeMana = windowObject.time_mana;
        }
        //console.log(`manaTimer mana: ${mana}, maxMana: ${maxMana}, timeMana: ${timeMana}`);
        let restSeconds = timeMana * (maxMana - mana) / maxMana;
        //mana+max_mana/time_mana*((curTime-startTime)/1000
        if(restSeconds > 0) {
            GM_setValue(`manaTimeoutEnd${PlayerId}`, serverNow() + restSeconds * 1000);
        } else {
            GM_deleteValue(`manaTimeoutEnd${PlayerId}`);
        }
        return [mana, maxMana, timeMana];
    }
}
async function initUserName() {
    if(GM_getValue("TransporterUserName")) {
        GM_deleteValue("UserName");
        GM_deleteValue("TransporterUserName");
    }
    if(location.pathname == "/pl_info.php" && getUrlParamValue(location.href, "id") == PlayerId) {
        //console.log(document.querySelector("h1").innerText)
        GM_setValue("UserName", document.querySelector("h1").innerText);
    }
    if(location.pathname == "/home.php") {
        //console.log(document.querySelector(`a[href='pl_info.php?id=${PlayerId}'] > b`).innerText)
        GM_setValue("UserName", document.querySelector(`a[href='pl_info.php?id=${PlayerId}'] > b`).innerText);
    }
    if(!GM_getValue("UserName")) {
        const doc = await getRequest(`/pl_info.php?id=${PlayerId}`);
        GM_setValue("UserName", doc.querySelector("h1").innerText);
    }
}
function parseDate(dateString, isFuture = false, isPast = false) {
    //console.log(dateString)
    if(!dateString) {
        return;
    }
    const dateStrings = dateString.split(" ");

    let hours = 0;
    let minutes = 0;
    let seconds = 0;
    const nowDate = new Date(serverNow());
    let year = nowDate.getFullYear();
    let month = nowDate.getMonth();
    let day = nowDate.getDate();
    const timePart = dateStrings.find(x => x.includes(":"));
    if(timePart) {
        var time = timePart.split(":");
        hours = parseInt(time[0]);
        minutes = parseInt(time[1]);
        if(time.length > 2) {
            seconds = parseInt(time[2]);
        }
        if(dateStrings.length == 1) {
            let result = new Date(year, month, day, hours, minutes, seconds);
            if(isPast && result > nowDate) {
                result.setDate(result.getDate() - 1);
            }
            if(isFuture && result < nowDate) {
                result.setDate(result.getDate() + 1);
            }
            //console.log(`result: ${result}, nowDate: ${nowDate}`)
            return result;
        }
    }

    const datePart = dateStrings.find(x => x.includes("-"));
    if(datePart) {
        const date = datePart.split("-");
        month = parseInt(date[isEn ? (date.length == 3 ? 1 : 0) : 1]) - 1;
        day = parseInt(date[isEn ? (date.length == 3 ? 2 : 1) : 0]);
        if(date.length == 3) {
            const yearText = isEn ? date[0] : date[2];
            year = parseInt(yearText);
            if(yearText.length < 4) {
                year += Math.floor((new Date()).getFullYear() / 1000) * 1000;
            }
        } else {
            if(isFuture && month == 0 && nowDate.getMonth() == 11) {
                year += 1;
            }
        }
    }
    if(dateStrings.length > 2) {
        const letterDateExec = /(\d{2}):(\d{2}) (\d{2}) (.{3,4})/.exec(dateString);
        if(letterDateExec) {
            //console.log(letterDateExec)
            day = parseInt(letterDateExec[3]);
            //const monthNames = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
            const monthShortNames = ['янв', 'фев', 'март', 'апр', 'май', 'июнь', 'июль', 'авг', 'сент', 'окт', 'ноя', 'дек'];
            month = monthShortNames.findIndex(x => x.toLowerCase() == letterDateExec[4].toLowerCase());
            if(isPast && (new Date(year, month, day, hours, minutes, seconds)) > nowDate) {
                year -= 1;
            }
        }
    }
    //console.log(`year: ${year}, month: ${month}, day: ${day}, time[0]: ${time[0]}, time[1]: ${time[1]}, ${new Date(year, month, day, parseInt(time[0]), parseInt(time[1]))}`);
    return new Date(year, month, day, hours, minutes, seconds);
}
function getRequest(url) {
    return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({ method: "GET", url: url, overrideMimeType: "text/html; charset=windows-1251",
            onload: function(response) { resolve((new DOMParser).parseFromString(response.responseText, "text/html")); },
            onerror: function(error) { reject(error); }
        });
    });
}
function getRequestText(url, overrideMimeType = "text/html; charset=windows-1251") {
    return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({ method: "GET", url: url, overrideMimeType: overrideMimeType,
            onload: function(response) { resolve(response.responseText); },
            onerror: function(error) { reject(error); }
        });
    });
}