hwmTransporter

Перемещения по карте, поиск работы и засады с любой страницы

目前為 2023-10-28 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name		   hwmTransporter
// @namespace	   Tamozhnya1
// @version		   8.9
// @description	   Перемещения по карте, поиск работы и засады с любой страницы
// @author		   Tamozhnya1
// @encoding	   utf-8
// @include		   *heroeswm.ru/*
// @include		   *lordswm.com/*
// @grant		   GM_log
// @grant		   GM_listValues
// @grant		   GM_setValue
// @grant		   GM_getValue
// @grant		   GM_deleteValue
// @grant 		   GM.xmlHttpRequest
// @grant 		   GM.notification
// @license        MIT
// ==/UserScript==

if(typeof GM_getValue != 'function') {
    this.GM_getValue = function (key,def) { return localStorage[key] || def; };
    this.GM_setValue = function (key,value) { return localStorage[key] = value; };
    this.GM_deleteValue = function (key) { return delete localStorage[key]; };
}
const playerIdMatch = document.cookie.match(/pl_id=(\d+)/);
if(playerIdMatch) {
    var PlayerId = playerIdMatch[1];
}
if(!PlayerId) {
    GM_deleteValue("TransporterUserName");
    return;
}
const isEn = document.documentElement.lang == "en";
var AmbushState = { Setted: 1, Battle: 2 };
var finalResultDiv;
const isHeartOnPage = document.querySelector("canvas#heart") || document.querySelector("div#heart_js_mobile");

// Граф всех возможных путей перемещения по карте
const routes = [null,
[null,null,["1-2",1],["1-3",1],["1-4",1.4],["1-5",1],["1-3-6",2],["1-7",1.4],["1-8",1],["1-3-9",2.4],["1-5-10",2],["1-11",1.4],["1-12",1.4],["1-8-13",2.4],["1-2-14",2],["1-2-15",2.4],["1-4-16",2.8],["1-2-14-17",3],["1-2-14-18",3.4],["1-5-19",2.4],["1-5-10-20",3.4],["1-5-19-21",3.8],["1-5-10-20-22",4.8],["1-12-23",2.8],["1-3-24",2.4],null,["1-5-26",2.4],["1-8-27",2]],
[null,["2-1",1],null,["2-3",1.4],["2-4",1],["2-5",1.4],["2-3-6",2.4],["2-1-7",2.4],["2-1-8",2],["2-3-9",2.8],["2-5-10",2.4],["2-11",1],["2-1-12",2.4],["2-1-8-13",3.4],["2-14",1],["2-15",1.4],["2-4-16",2.4],["2-14-17",2],["2-14-18",2.4],["2-11-19",2],["2-11-19-20",3],["2-11-19-21",3.4],["2-11-19-20-22",4.4],["2-3-9-23",3.8],["2-4-24",2],null,["2-5-26",2.8],["2-1-8-27",3]],
[null,["3-1",1],["3-2",1.4],null,["3-4",1],["3-1-5",2],["3-6",1],["3-1-7",2.4],["3-8",1.4],["3-9",1.4],["3-1-5-10",3],["3-1-11",2.4],["3-12",1],["3-12-13",2],["3-2-14",2.4],["3-4-15",2],["3-4-16",2.4],["3-2-14-17",3.4],["3-4-15-18",3],["3-1-5-19",3.4],["3-1-5-10-20",4.4],["3-1-5-19-21",4.8],["3-1-5-19-21-22",5.8],["3-9-23",2.4],["3-24",1.4],null,["3-1-5-26",3.4],["3-8-27",2.4]],
[null,["4-1",1.4],["4-2",1],["4-3",1],null,["4-1-5",2.4],["4-6",1.4],["4-1-7",2.8],["4-1-8",2.4],["4-3-9",2.4],["4-1-5-10",3.4],["4-2-11",2],["4-3-12",2],["4-3-12-13",3],["4-14",1.4],["4-15",1],["4-16",1.4],["4-14-17",2.4],["4-15-18",2],["4-2-11-19",3],["4-2-11-19-20",4],["4-2-11-19-21",4.4],["4-2-11-19-20-22",5.4],["4-3-9-23",3.4],["4-24",1],null,["4-1-5-26",3.8],["4-1-8-27",3.4]],
[null,["5-1",1],["5-2",1.4],["5-1-3",2],["5-1-4",2.4],null,["5-1-3-6",3],["5-7",1],["5-8",1.4],["5-1-3-9",3.4],["5-10",1],["5-11",1],["5-1-12",2.4],["5-8-13",2.8],["5-2-14",2.4],["5-2-15",2.8],["5-1-4-16",3.8],["5-2-14-17",3.4],["5-2-14-18",3.8],["5-19",1.4],["5-10-20",2.4],["5-19-21",2.8],["5-10-20-22",3.8],["5-1-12-23",3.8],["5-1-3-24",3.4],null,["5-26",1.4],["5-7-27",2.4]],
[null,["6-3-1",2],["6-3-2",2.4],["6-3",1],["6-4",1.4],["6-3-1-5",3],null,["6-3-1-7",3.4],["6-3-8",2.4],["6-9",1],["6-3-1-5-10",4],["6-3-1-11",3.4],["6-12",1.4],["6-9-13",2.4],["6-4-14",2.8],["6-4-15",2.4],["6-4-16",2.8],["6-4-14-17",3.8],["6-4-15-18",3.4],["6-3-1-5-19",4.4],["6-3-1-5-10-20",5.4],["6-3-1-5-19-21",5.8],["6-3-1-5-10-20-22",6.8],["6-9-23",2],["6-24",1],null,["6-3-1-5-26",4.4],["6-12-27",2.8]],
[null,["7-1",1.4],["7-1-2",2.4],["7-1-3",2.4],["7-1-4",2.8],["7-5",1],["7-1-3-6",3.4],null,["7-8",1],["7-8-12-9",3],["7-10",1.4],["7-5-11",2],["7-8-12",2],["7-8-13",2.4],["7-1-2-14",3.4],["7-1-2-15",3.8],["7-1-4-16",4.2],["7-1-2-14-17",4.4],["7-1-2-14-18",4.8],["7-5-19",2.4],["7-10-20",2.8],["7-5-19-21",3.8],["7-10-20-22",4.2],["7-8-12-23",3.4],["7-1-3-24",3.8],null,["7-26",1],["7-27",1.4]],
[null,["8-1",1],["8-1-2",2],["8-3",1.4],["8-1-4",2.4],["8-5",1.4],["8-3-6",2.4],["8-7",1],null,["8-12-9",2],["8-5-10",2.4],["8-1-11",2.4],["8-12",1],["8-13",1.4],["8-1-2-14",3],["8-1-2-15",3.4],["8-1-4-16",3.8],["8-1-2-14-17",4],["8-1-2-14-18",4.4],["8-5-19",2.8],["8-5-10-20",3.8],["8-5-19-21",4.2],["8-5-10-20-22",5.2],["8-12-23",2.4],["8-3-24",2.8],null,["8-7-26",2],["8-27",1]],
[null,["9-3-1",2.4],["9-3-2",2.8],["9-3",1.4],["9-3-4",2.4],["9-3-1-5",3.4],["9-6",1],["9-12-8-7",3],["9-12-8",2],null,["9-3-1-5-10",4.4],["9-3-1-11",3.8],["9-12",1],["9-13",1.4],["9-3-2-14",3.8],["9-3-4-15",3.4],["9-3-4-16",3.8],["9-3-2-14-17",4.8],["9-3-4-15-18",4.4],["9-3-1-5-19",4.8],["9-3-1-5-10-20",5.8],["9-3-1-5-19-21",6.2],["9-3-1-5-10-20-22",7.2],["9-23",1],["9-6-24",2],null,["9-12-8-7-26",4],["9-12-27",2.4]],
[null,["10-5-1",2],["10-5-2",2.4],["10-5-1-3",3],["10-5-1-4",3.4],["10-5",1],["10-5-1-3-6",4],["10-7",1.4],["10-5-8",2.4],["10-5-1-3-9",4.4],null,["10-11",1.4],["10-5-1-12",3.4],["10-5-8-13",3.8],["10-11-14",2.8],["10-5-2-15",3.8],["10-5-1-4-16",4.8],["10-11-14-17",3.8],["10-11-14-18",4.2],["10-19",1],["10-20",1.4],["10-19-21",2.4],["10-20-22",2.8],["10-5-1-12-23",4.8],["10-5-1-3-24",4.4],null,["10-26",1],["10-7-27",2.8]],
[null,["11-1",1.4],["11-2",1],["11-1-3",2.4],["11-2-4",2],["11-5",1],["11-1-3-6",3.4],["11-5-7",2],["11-1-8",2.4],["11-1-3-9",3.8],["11-10",1.4],null,["11-1-12",2.8],["11-1-8-13",3.8],["11-14",1.4],["11-2-15",2.4],["11-2-4-16",3.4],["11-14-17",2.4],["11-14-18",2.8],["11-19",1],["11-19-20",2],["11-19-21",2.4],["11-19-20-22",3.4],["11-1-12-23",4.2],["11-2-4-24",3],null,["11-5-26",2.4],["11-5-7-27",3.4]],
[null,["12-1",1.4],["12-1-2",2.4],["12-3",1],["12-3-4",2],["12-1-5",2.4],["12-6",1.4],["12-8-7",2],["12-8",1],["12-9",1],["12-1-5-10",3.4],["12-1-11",2.8],null,["12-13",1],["12-1-2-14",3.4],["12-3-4-15",3],["12-3-4-16",3.4],["12-1-2-14-17",4.4],["12-3-4-15-18",4],["12-1-5-19",3.8],["12-1-5-10-20",4.8],["12-1-5-19-21",5.2],["12-1-5-10-20-22",6.2],["12-23",1.4],["12-3-24",2.4],null,["12-8-7-26",3],["12-27",1.4]],
[null,["13-8-1",2.4],["13-8-1-2",3.4],["13-12-3",2],["13-12-3-4",3],["13-8-5",2.8],["13-9-6",2.4],["13-8-7",2.4],["13-8",1.4],["13-9",1.4],["13-8-5-10",3.8],["13-8-1-11",3.8],["13-12",1],null,["13-8-1-2-14",4.4],["13-12-3-4-15",4],["13-12-3-4-16",4.4],["13-8-1-2-14-17",5.4],["13-12-3-4-15-18",5],["13-8-5-19",4.2],["13-8-5-10-20",5.2],["13-8-5-19-21",5.6],["13-8-5-10-20-22",6.6],["13-23",1],["13-9-6-24",3.4],null,["13-8-7-26",3.4],["13-27",1]],
[null,["14-2-1",2],["14-2",1],["14-2-3",2.4],["14-4",1.4],["14-2-5",2.4],["14-4-6",2.8],["14-2-1-7",3.4],["14-2-1-8",3],["14-2-3-9",3.8],["14-11-10",2.8],["14-11",1.4],["14-2-1-12",3.4],["14-2-1-8-13",4.4],null,["14-15",1],["14-15-16",2],["14-17",1],["14-18",1.4],["14-11-19",2.4],["14-11-19-20",3.4],["14-11-19-21",3.8],["14-11-19-20-22",4.8],["14-2-3-9-23",4.8],["14-4-24",2.4],null,["14-2-5-26",3.8],["14-2-1-8-27",4]],
[null,["15-2-1",2.4],["15-2",1.4],["15-4-3",2],["15-4",1],["15-2-5",2.8],["15-4-6",2.4],["15-2-1-7",3.8],["15-2-1-8",3.4],["15-4-3-9",3.4],["15-2-5-10",3.8],["15-2-11",2.4],["15-4-3-12",3],["15-4-3-12-13",4],["15-14",1],null,["15-16",1],["15-17",1.4],["15-18",1],["15-2-11-19",3.4],["15-2-11-19-20",4.4],["15-2-11-19-21",4.8],["15-2-11-19-21-22",5.8],["15-4-3-9-23",4.4],["15-24",1.4],null,["15-2-5-26",4.2],["15-2-1-8-27",4.4]],
[null,["16-4-1",2.8],["16-4-2",2.4],["16-4-3",2.4],["16-4",1.4],["16-4-1-5",3.8],["16-4-6",2.8],["16-4-1-7",4.2],["16-4-1-8",3.8],["16-4-3-9",3.8],["16-4-1-5-10",4.8],["16-4-2-11",3.4],["16-4-3-12",3.4],["16-4-3-12-13",4.4],["16-15-14",2],["16-15",1],null,["16-15-17",2.4],["16-18",1.4],["16-4-2-11-19",4.4],["16-4-2-11-19-20",5.4],["16-4-2-11-19-21",5.8],["16-4-2-11-19-20-22",6.8],["16-4-3-9-23",4.8],["16-4-24",2.4],null,["16-4-1-5-26",5.2],["16-4-1-8-27",4.8]],
[null,["17-14-2-1",3],["17-14-2",2],["17-14-2-3",3.4],["17-14-4",2.4],["17-14-2-5",3.4],["17-14-4-6",3.8],["17-14-2-1-7",4.4],["17-14-2-1-8",4],["17-14-2-3-9",4.8],["17-14-11-10",3.8],["17-14-11",2.4],["17-14-2-1-12",4.4],["17-14-2-1-8-13",5.4],["17-14",1],["17-15",1.4],["17-15-16",2.4],null,["17-18",1],["17-14-11-19",3.4],["17-14-11-19-20",4.4],["17-14-11-19-21",4.8],["17-14-11-19-21-22",5.8],["17-14-2-3-9-23",5.8],["17-15-24",2.8],null,["17-14-2-5-26",4.8],["17-14-2-1-8-27",5]],
[null,["18-14-2-1",3.4],["18-14-2",2.4],["18-15-4-3",3],["18-15-4",2],["18-14-2-5",3.8],["18-15-4-6",3.4],["18-14-2-1-7",4.8],["18-14-2-1-8",4.4],["18-15-4-3-9",4.4],["18-14-11-10",4.2],["18-14-11",2.8],["18-15-4-3-12",4],["18-15-4-3-12-13",5],["18-14",1.4],["18-15",1],["18-16",1.4],["18-17",1],null,["18-14-11-19",3.8],["18-14-11-19-20",4.8],["18-14-11-19-21",5.2],["18-14-11-19-20-22",6.2],["18-15-4-3-9-23",5.4],["18-15-24",2.4],null,["18-14-2-5-26",5.2],["18-14-2-1-8-27",5.4]],
[null,["19-5-1",2.4],["19-11-2",2],["19-5-1-3",3.4],["19-11-2-4",3],["19-5",1.4],["19-5-1-3-6",4.4],["19-5-7",2.4],["19-5-8",2.8],["19-5-1-3-9",4.8],["19-10",1],["19-11",1],["19-5-1-12",3.8],["19-5-8-13",4.2],["19-11-14",2.4],["19-11-2-15",3.4],["19-11-2-4-16",4.4],["19-11-14-17",3.4],["19-11-14-18",3.8],null,["19-20",1],["19-21",1.4],["19-20-22",2.4],["19-5-1-12-23",5.2],["19-11-2-4-24",4],null,["19-10-26",2],["19-5-7-27",3.8]],
[null,["20-10-5-1",3.4],["20-19-11-2",3],["20-10-5-1-3",4.4],["20-19-11-2-4",4],["20-10-5",2.4],["20-10-5-1-3-6",5.4],["20-10-7",2.8],["20-10-5-8",3.8],["20-10-5-1-12-9",5.8],["20-10",1.4],["20-19-11",2],["20-10-5-1-12",4.8],["20-10-5-8-13",5.2],["20-19-11-14",3.4],["20-19-11-2-15",4.4],["20-19-11-2-4-16",5.4],["20-19-11-14-17",4.4],["20-19-11-14-18",4.8],["20-19",1],null,["20-21",1],["20-22",1.4],["20-10-5-1-12-23",6.2],["20-19-11-2-4-24",5],null,["20-10-26",2.4],["20-10-7-27",4.2]],
[null,["21-19-5-1",3.8],["21-19-11-2",3.4],["21-19-5-1-3",4.8],["21-19-11-2-4",4.4],["21-19-5",2.8],["21-19-5-1-3-6",5.8],["21-19-5-7",3.8],["21-19-5-8",4.2],["21-19-5-1-3-9",6.2],["21-19-10",2.4],["21-19-11",2.4],["21-19-5-1-12",5.2],["21-19-5-8-13",5.6],["21-19-11-14",3.8],["21-19-11-2-15",4.8],["21-19-11-2-4-16",5.8],["21-19-11-14-17",4.8],["21-19-11-14-18",5.2],["21-19",1.4],["21-20",1],null,["21-22",1],["21-19-5-1-12-23",6.6],["21-19-11-2-4-24",5.4],null,["21-19-10-26",3.4],["21-19-5-7-27",5.2]],
[null,["22-20-10-5-1",4.8],["22-20-19-11-2",4.4],["22-20-10-5-1-3",5.8],["22-20-19-11-2-4",5.4],["22-20-10-5",3.8],["22-20-10-5-1-3-6",6.8],["22-20-10-7",4.2],["22-20-10-5-8",5.2],["22-20-10-5-1-12-9",7.2],["22-20-10",2.8],["22-20-19-11",3.4],["22-20-10-5-1-12",6.2],["22-20-10-5-8-13",6.6],["22-20-19-11-14",4.8],["22-20-19-11-2-15",5.8],["22-20-19-11-2-4-16",6.8],["22-20-19-11-14-17",5.8],["22-20-19-11-14-18",6.2],["22-20-19",2.4],["22-20",1.4],["22-21",1],null,["22-20-10-5-1-12-23",7.6],["22-20-19-11-2-4-24",6.4],null,["22-20-10-26",3.8],["22-20-10-7-27",5.6]],
[null,["23-12-1",2.8],["23-9-3-2",3.8],["23-9-3",2.4],["23-9-3-4",3.4],["23-12-1-5",3.8],["23-9-6",2],["23-12-8-7",3.4],["23-12-8",2.4],["23-9",1],["23-12-1-5-10",4.8],["23-12-1-11",4.2],["23-12",1.4],["23-13",1],["23-9-3-2-14",4.8],["23-9-3-4-15",4.4],["23-9-3-4-16",4.8],["23-9-3-2-14-17",5.8],["23-9-3-4-15-18",5.4],["23-12-1-5-19",5.2],["23-12-1-5-10-20",6.2],["23-12-1-5-19-21",6.6],["23-12-1-5-10-20-22",7.6],null,["23-9-6-24",3],null,["23-12-8-7-26",4.4],["23-13-27",2]],
[null,["24-3-1",2.4],["24-4-2",2],["24-3",1.4],["24-4",1],["24-3-1-5",3.4],["24-6",1],["24-3-1-7",3.8],["24-3-8",2.8],["24-6-9",2],["24-3-1-5-10",4.4],["24-4-2-11",3],["24-3-12",2.4],["24-6-9-13",3.4],["24-4-14",2.4],["24-15",1.4],["24-4-16",2.4],["24-15-17",2.8],["24-15-18",2.4],["24-4-2-11-19",4],["24-4-2-11-19-20",5],["24-4-2-11-19-21",5.4],["24-4-2-11-19-20-22",6.4],["24-6-9-23",3],null,null,["24-3-1-5-26",4.8],["24-3-8-27",3.8]],
[],
[null,["26-5-1",2.4],["26-5-2",2.8],["26-5-1-3",3.4],["26-5-1-4",3.8],["26-5",1.4],["26-5-1-3-6",4.4],["26-7",1],["26-7-8",2],["26-7-8-12-9",4],["26-10",1],["26-5-11",2.4],["26-7-8-12",3],["26-7-8-13",3.4],["26-5-2-14",3.8],["26-5-2-15",4.2],["26-5-1-4-16",5.2],["26-5-2-14-17",4.8],["26-5-2-14-18",5.2],["26-10-19",2],["26-10-20",2.4],["26-10-19-21",3.4],["26-10-20-22",3.8],["26-7-8-12-23",4.4],["26-5-1-3-24",4.8],null,null,["26-7-27",2.4]],
[null,["27-8-1",2],["27-8-1-2",3],["27-8-3",2.4],["27-8-1-4",3.4],["27-7-5",2.4],["27-12-6",2.8],["27-7",1.4],["27-8",1],["27-12-9",2.4],["27-7-10",2.8],["27-7-5-11",3.4],["27-12",1.4],["27-13",1],["27-8-1-2-14",4],["27-8-1-2-15",4.4],["27-8-1-4-16",4.8],["27-8-1-2-14-17",5],["27-8-1-2-14-18",5.4],["27-7-5-19",3.8],["27-7-10-20",4.2],["27-7-5-19-21",5.2],["27-7-10-20-22",5.6],["27-13-23",2],["27-8-3-24",3.8],null,["27-7-26",2.4]]];
// Номера, названия и координаты секторов
const locations = {
    1: [50,50,"Empire Capital","EmC","Столица Империи"],
    2: [51,50,"East River","EsR","Восточная Река"],
    3: [50,49,"Tiger Lake","TgL","Тигриное Озеро"],
    4: [51,49,"Rogues' Wood","RgW","Лес Разбойников"],
    5: [50,51,"Wolf Dale","WoD","Долина Волков"],
    6: [50,48,"Peaceful Camp","PcC","Мирный Лагерь"],
    7: [49,51,"Lizard Lowland","LzL","Равнина Ящеров"],
    8: [49,50,"Green Wood","GrW","Зеленый Лес"],
    9: [49,48,"Eagle Nest","EgN","Орлиное Гнездо"],
    10: [50,52,"Portal Ruins","PoR","Руины Портала"],
    11: [51,51,"Dragons' Caves","DrC","Пещеры Драконов"],
    12: [49,49,"Shining Spring","ShS","Сияющий Родник"],
    13: [48,49,"Sunny City","SnC","Солнечный Город"],
    14: [52,50,"Magma Mines","MgM","Магма Шахты"],
    15: [52,49,"Bear Mountain","BrM","Медвежья Гора"],
    16: [52,48,"Fairy Trees","FrT","Магический Лес"],
    17: [53,50,"Harbour City","HrC","Портовый Город"],
    18: [53,49,"Mythril Coast","MfC","Мифриловый Берег"],
    19: [51,52,"Great Wall","GtW","Великая Стена"],
    20: [51,53,"Titans' Valley","TiV","Равнина Титанов"],
    21: [52,53,"Fishing Village","FsV","Рыбачье село"],
    22: [52,54,"Kingdom Castle","KiC","Замок Королевства"],
    23: [48,48,"Ungovernable Steppe","UnS","Непокорная Степь"],
    24: [51,48,"Crystal Garden","CrG","Кристальный Сад"],
    25: [53,52,"East Island","EsI","Восточный Остров"],
    26: [49,52,"The Wilderness","ThW","Дикие земли"],
    27: [48,50,"Sublime Arbor","SbA","Великое Древо"],
    101: [48, 53, "Watchers' guild", "", "Гильдия стражей", { Url: "/task_guild.php", Color: "DarkGoldenRod" }],
    102: [48, 54, "Leaders' Guild", "", "Гильдия лидеров", { Url: "/leader_guild.php", Color: "DarkGoldenRod" }],
    103: [49, 54, "Adventurers' guild", "", "Гильдия искателей", { Url: "/campaign_list.php", Color: "DarkGoldenRod" }],
    104: [50, 54, "Thieves' guild", "", "Гильдия воров", { Url: "/thief_guild.php", Color: "DarkGoldenRod" }],
    105: [51, 54, "Smith", "", "Кузница", { Url: "/mod_workbench.php?type=repair", Color: "DarkGoldenRod" }]
};
const objectLocations = { 0 : 0
, 5: 1, 9: 1, 6: 1, 7: 1, 4: 1, 3: 1, 8: 1, 165: 1, 10: 1, 11: 1, 12: 1, 32: 1, 34: 1, 38: 1
, 238: 2, 300: 2, 75: 2, 26: 2, 258: 2, 279: 2, 25: 2, 23: 2, 33: 2, 342: 2, 24: 2, 36: 2, 87: 2, 89: 2, 321: 2, 28: 2
, 239: 3, 16: 3, 15: 3, 13: 3, 259: 3, 343: 3, 280: 3, 84: 3, 301: 3, 224: 3, 27: 3, 39: 3, 14: 3, 31: 3, 35: 3, 322: 3
, 344: 4, 281: 4, 302: 4, 260: 4, 19: 4, 21: 4, 22: 4, 323: 4, 18: 4, 20: 4, 30: 4, 37: 4, 78: 4, 90: 4, 225: 4, 240: 4
, 282: 5, 74: 5, 226: 5, 43: 5, 44: 5, 303: 5, 85: 5, 45: 5, 46: 5, 47: 5, 48: 5, 86: 5, 241: 5, 261: 5, 324: 5, 345: 5
, 50: 6, 283: 6, 262: 6, 304: 6, 73: 6, 49: 6, 141: 6, 51: 6, 79: 6, 53: 6, 54: 6, 55: 6, 52: 6, 82: 6, 325: 6, 346: 6
, 56: 7, 326: 7, 59: 7, 64: 7, 284: 7, 58: 7, 60: 7, 61: 7, 63: 7, 80: 7, 83: 7, 242: 7, 263: 7, 57: 7, 305: 7, 347: 7
, 285: 8, 243: 8, 68: 8, 88: 8, 69: 8, 67: 8, 71: 8, 72: 8, 76: 8, 77: 8, 81: 8, 70: 8, 264: 8, 306: 8, 327: 8, 348: 8
, 244: 9, 328: 9, 227: 9, 265: 9, 286: 9, 94: 9, 349: 9, 139: 9, 307: 9, 101: 9, 98: 9, 95: 9, 119: 9, 120: 9, 140: 9, 97: 9
, 163: 10, 329: 10, 350: 10, 99: 10, 100: 10, 102: 10, 118: 10, 211: 10, 217: 10, 287: 10, 308: 10, 93: 10, 228: 10, 266: 10, 245: 10, 92: 10
, 167: 11, 169: 11, 168: 11, 210: 11, 172: 11, 170: 11, 209: 11, 171: 11, 218: 11, 229: 11, 246: 11, 267: 11, 288: 11, 309: 11, 330: 11, 351: 11
, 247: 12, 331: 12, 289: 12, 219: 12, 117: 12, 108: 12, 111: 12, 110: 12, 109: 12, 112: 12, 113: 12, 114: 12, 230: 12, 268: 12, 310: 12, 352: 12
, 104: 13, 311: 13, 269: 13, 220: 13, 116: 13, 248: 13, 332: 13, 106: 13, 107: 13, 115: 13, 213: 13, 231: 13, 103: 13, 290: 13, 105: 13, 353: 13
, 270: 14, 122: 14, 312: 14, 249: 14, 216: 14, 333: 14, 164: 14, 291: 14, 121: 14, 135: 14, 142: 14, 143: 14, 144: 14, 145: 14, 232: 14, 354: 14
, 313: 15, 334: 15, 124: 15, 125: 15, 214: 15, 136: 15, 123: 15, 146: 15, 147: 15, 148: 15, 149: 15, 215: 15, 250: 15, 271: 15, 292: 15, 355: 15
, 335: 16, 233: 16, 272: 16, 251: 16, 126: 16, 134: 16, 153: 16, 151: 16, 127: 16, 152: 16, 150: 16, 212: 16, 221: 16, 293: 16, 314: 16, 356: 16
, 131: 17, 252: 17, 273: 17, 315: 17, 294: 17, 336: 17, 162: 17, 161: 17, 160: 17, 132: 17, 133: 17, 222: 17, 234: 17, 158: 17, 159: 17, 357: 17
, 358: 18, 235: 18, 253: 18, 130: 18, 129: 18, 137: 18, 138: 18, 154: 18, 128: 18, 155: 18, 156: 18, 157: 18, 274: 18, 295: 18, 316: 18, 337: 18
, 192: 19, 202: 19, 179: 19, 173: 19, 193: 19, 194: 19, 195: 19, 201: 19, 178: 19, 203: 19, 254: 19, 275: 19, 296: 19, 317: 19, 338: 19, 359: 19
, 191: 20, 176: 20, 177: 20, 187: 20, 188: 20, 189: 20, 190: 20, 206: 20, 207: 20, 208: 20, 255: 20, 276: 20, 297: 20, 318: 20, 339: 20, 360: 20
, 174: 21, 199: 21, 197: 21, 166: 21, 361: 21, 198: 21, 196: 21, 200: 21, 223: 21, 236: 21, 256: 21, 277: 21, 298: 21, 319: 21, 340: 21, 175: 21
, 186: 22, 184: 22, 320: 22, 185: 22, 183: 22, 362: 22, 182: 22, 180: 22, 204: 22, 205: 22, 237: 22, 257: 22, 278: 22, 299: 22, 341: 22, 181: 22
, 365: 23, 364: 23, 370: 23, 363: 23, 369: 23, 366: 23, 371: 23, 372: 23, 373: 23, 374: 23, 375: 23, 376: 23, 377: 23, 378: 23, 379: 23, 380: 23
, 367: 24, 392: 24, 390: 24, 388: 24, 383: 24, 384: 24, 385: 24, 386: 24, 387: 24, 368: 24, 389: 24, 381: 24, 391: 24, 382: 24, 393: 24, 394: 24
, 395: 26, 409: 26, 399: 26, 397: 26, 398: 26, 400: 26, 401: 26, 402: 26, 403: 26, 404: 26, 405: 26, 406: 26, 407: 26, 408: 26, 396: 26, 410: 26
, 411: 27, 419: 27, 413: 27, 414: 27, 415: 27, 416: 27, 417: 27, 418: 27, 412: 27, 420: 27, 421: 27, 422: 27, 423: 27, 424: 27, 425: 27, 426: 27
 };

const minX = 48;
const maxX = 53;
const minY = 48;
const maxY = 54;
const DefaultTravelingTime = 40;
let AmbushMinutesInterval = 60;
const ClientServerTimeDifferenceRequestInterval = 60 * 60 * 1000;
const isMobileInterface = document.querySelector("div#btnMenuGlobal") ? true : false;
const GoTo = isEn ? "Go to" : "Перейти в";
const Second = isEn ? "sec." : "с.";
const YourPremiumMountUntil = isEn ? "Your premium mount  until" : "Ваш премиум транспорт";
const YourMountUntil = isEn ? "Your mount until" : "Ваш транспорт до";
const Until = isEn ? " until " : " до ";
const UndressBeforeMove = isEn ? "Undress before move" : "Раздеться перед перемещением";
const NotUseComplexRoute = isEn ? "Not use complex route" : "Не использовать сложный маршрут";
const HideMapName = isEn ? "Hide map" : "Скрыть карту";
const HideHuntName = isEn ? "Hide hunt" : "Скрыть охоты";
const HideRightBlockName = isEn ? "Hide right block" : "Скрыть правый блок";
const ComplexRouteName = isEn ? "Complex route" : "Сложный маршрут";
const MountUntilName = isEn ? "Mount until" : "Транспорт до";
const BreakTravelingName = isEn ? "Break traveling" : "Прервать путешествие";
const TravelingNotEnabled = isEn ? "Traveling not enabled" : "Путешествие не доступно";
const YouAreInAChallenge = isEn ? "You are in a challenge. Your actions are limited!" : "Вы находитесь в заявке на бой. Ваши действия ограничены!";
const How = isEn ? "How?" : "Как?";
const YouAreHere = isEn ? "You're already here" : "Ты уже здесь";
const AmbushMaySettedOnAdjacentSector = isEn ? "Ambush may setted on adjacent sector" : "Засада ставится на соседний сектор";
const ItIsTooEarly = isEn ? "It is too early" : "Ещё рано";
const Legend = isEn ? "Click: moving, ctrl(mdl)+click: ambush, alt+click: sector info" : "Click: движение, ctrl(mdl)+click: засада, alt+click: просмотр";
const MoveHere = isEn ? "Move here" : "Перейти сюда";
const YouAreInADifferentLocation = isEn ? "You are in a different location." : "Вы находитесь в другом районе.";
const TravelingTimeName = isEn ? "Traveling time" : "Время пути";
const Antithief = isEn ? "Antithief" : "Антивор";
const AmbushingName = isEn ? "You are in ambush" : "Вы в засаде";
const CheckMountName = isEn ? "Check mount" : "Проверить транспорт";
const LoadName = isEn ? "Load" : "Грузить";
const PremiunAccountName = isEn ? "Abu" : "Абу";
const locationNamesLocalizedColumn = isEn ? 2 : 4;
const AmbushResult = { NotFound: 0, Win: 1, Fail: 2 };
const factoryTypes = ["mn", "fc", "sh"];
const Mining = isEn ? "Mining" : "Добыча";
const Machining = isEn ? "Machining" : "Обработка";
const Production = isEn ? "Production" : "Производство";
const PopupAlwaysName = isEn ? "Popup always" : "Всегда всплывающая";
const HideBattlesDayWithName = isEn ? "Hide battles day with..." : "Скрыть день боёв с...";
const FindNearestMiningName = isEn ? "Find nearest mining" : "Найти ближайшую добычу";
const FindNearestManufactureName = isEn ? "Find nearest manufacture" : "Найти ближайшую обработку";
const FindNearestProductionName = isEn ? "Find nearest production" : "Найти ближайшее производство";
const MoveToWorkAfterFindName = isEn ? "Move to work after find" : "Сразу идти на предприятие";
const ShowLocationNumbersName = isEn ? "Show location numbers" : "Номера локаций";
const JobFinding = isEn ? "Job finding" : "Поиск работы";

const windowObject = window.wrappedJSObject || unsafeWindow;

let persGvStopped = false;
let playerLocationNumber = getPlayerLocationNumber();
if(!playerLocationNumber) {
    return; // При первом запуске скрипта, если мы не на карте, неизвестна текущая локация игрока
}
let MountInfo;
var AmbushMoratoriumPanel;

main();
async function main(forceGetMountInfo) {
    initUserName();
    MountInfo = await getMountInfo(forceGetMountInfo);
    AmbushMinutesInterval *= 1 - (MountInfo.IsPremiunAccount ? 0.3 : 0);

    tryGetAmbushState();
    if(!isHeartOnPage) {
        return;
    }
    healthTimer();
    requestServerTime();
    checkActivity();
    setActivity();
    if(/map.php/.test(location.href)) {
        const mapRightBlock = document.getElementById("map_right_block");
        if(!mapRightBlock) {
            drawStopButton();
            return; // Нет правого блока с предприятиями, значит мы в пути. Скрипт не выполняем
        }
        GM_setValue("LastObservingLocationNumber", location.search != '' ? getLocationNumberFromMapUrlByXy(location.href) : playerLocationNumber);
        loadFactories();
    }
    drawMap();
    processMoving();
    checkAmbushResult();
    if(location.pathname == '/object-info.php') {
        const center = document.querySelector("body center");
        const objectLocationReference = center.querySelector("a[href*='map.php?cx='][href*='&cy=']");
        //console.log(`center: ${center}, objectLocationReference: ${objectLocationReference}`);
        const moveHereReference = getMoveToObjectReference(location.pathname + location.search, objectLocationReference); // Добавление кнопки перемещения на предприятие
    }
    if(location.pathname == '/ecostat_details.php' || location.pathname == '/home.php') {
        const objs = document.querySelectorAll("a[href*='object-info.php?id=']");
        const doubledHash = [];
        for(let obj of objs) {
            if(!doubledHash.includes(obj.href)) {
                const moveHereReference = getMoveToObjectReference(obj.href, obj);
                doubledHash.push(obj.href);
            }
        }
    }
    if(location.pathname == '/group_wars.php') {
        addMoveToMapObjectReferences();
    }
    if(location.pathname == '/mercenary_guild.php') {
        const colorElement = document.querySelector("font[color='#E65054']");
        if(colorElement.querySelector("b").innerText == YouAreInADifferentLocation) {
            const lastObservingLocationNumber = parseInt(GM_getValue("LastObservingLocationNumber", 0));
            if([2, 6, 16, 21].includes(lastObservingLocationNumber)) {
                const ref = getMoveToMapObjectReference(location.href, colorElement, lastObservingLocationNumber);
            } else {
                const nearestLocations = getLocationsSortedByDistance();
                //console.log(`nearestLocations: ${nearestLocations}`);
                for(const nearestLocation of nearestLocations) {
                    if([2, 6, 16, 21].includes(parseInt(nearestLocation))) {
                        const ref = getMoveToMapObjectReference(location.href, colorElement, nearestLocation);
                        break;
                    }
                }
            }
        }
    }
    if(location.pathname == '/pirate_event.php') {
        const tableDiv = document.getElementById("tableDiv");
        addMoveToMapObjectReferences(tableDiv);
    }
    processHouseInfo();
}
function checkActivity() {
    checkTurnedOn();
    setTimeout(checkActivity, 1000);
}
function setActivity() {
    checkTurnedOn();
    GM_setValue("LastActivityDate", Date.now());
    setTimeout(setActivity, 60 * 1000);
}
function checkTurnedOn() {
    const now = Date.now();
    const computerTurnedOn = parseInt(GM_getValue("LastActivityDate", 0)) + 5 * 60 * 1000 < now;
    //console.log(`now: ${now}, LastActivityDate: ${GM_getValue("LastActivityDate")}, computerTurnedOn: ${computerTurnedOn}`);
    if(computerTurnedOn) {
        //console.log(`now: ${now}, LastActivityDate: ${GM_getValue("LastActivityDate")}, computerTurnedOn: ${computerTurnedOn}`);
        checkAmbushResult(true);
    }
}
function addMoveToMapObjectReferences(element) {
    element = element || document;
    const locationRefs = element.querySelectorAll("a[href^='map.php?cx=']");
    for(let locationRef of locationRefs) {
        const locationNumber = getLocationNumberByCoordinate(getUrlParamValue(locationRef.href, "cx"), getUrlParamValue(locationRef.href, "cy"));
        const ref = getMoveToMapObjectReference(location.href, locationRef, locationNumber);
    }
}
function getObjectLocations() {
    const objs = document.querySelectorAll("a[href*='object-info.php?id=']");
    let absentList = "";
    for(let obj of objs) {
        const objectId = getUrlParamValue(obj.href, "id");
        if(!objectLocations[objectId]) {
            objectLocations[objectId] = playerLocationNumber;
            absentList += `, ${objectId}: ${playerLocationNumber}`;
        }
    }
    if(absentList != "") {
        ShowBigData(absentList);
    }
    //console.log(absentList);
}
function drawStopButton() {
    const mapRightBlock = document.getElementById("map_right_block");
    //console.log([MountInfo.ComplexRoute, GM_getValue("IgnoreComplexRoute"), GM_getValue("IgnoreComplexRoute") == "true", (!MountInfo.ComplexRoute) || (GM_getValue("IgnoreComplexRoute") == "true")]);
    if(!mapRightBlock && GM_getValue("TargetLocationNumber") && (!MountInfo.ComplexRoute || gmGetBool("IgnoreComplexRoute"))) {
        let targetLocationNumber = parseInt(GM_getValue("TargetLocationNumber"));
        let route = routes[playerLocationNumber][targetLocationNumber][0];
        let nextLocationNumber = route.split('-')[1];
        if(nextLocationNumber != targetLocationNumber) {
            const insideMap = document.querySelector("#inside_map");
            const mapMoving = addElement("div", insideMap, { id: "mapMoving" });
            const stopMovingButton = addElement("div", mapMoving, { id: "stopMovingButton", class: "home_button2 btn_hover2 map_btn_width", style: "width: 300px;", innerHTML: BreakTravelingName });
            stopMovingButton.addEventListener("click", stopMoving, false);
        }
    }
}
async function toggleAntithief(toggleAntithiefCheckbox) {
    const toggleKey = GM_getValue("ToggleAntithiefKey");
    if(toggleKey && MountInfo.AntithiefControlled) {
        const toggleVar = toggleAntithiefCheckbox.checked ? "t_on" : "t_off";
        //console.log(`toggleKey: ${toggleKey}, url: /shop.php?${toggleVar}=${toggleKey}&cat=transport`);
        await getRequest(`/shop.php?${toggleVar}=${toggleKey}&cat=transport`);
    }
}
function toggleHuntBlock() {
    const map_hunt_block_div = document.getElementById("map_hunt_block_div");
    if(map_hunt_block_div) {
        const mapRightBlock = document.getElementById("map_right_block");
        let mapRightBlockHeight = mapRightBlock.offsetHeight;
        if(gmGetBool("HideHuntBlock")) {
            mapRightBlockHeight -= map_hunt_block_div.offsetHeight;
        }
        map_hunt_block_div.style.display = gmGetBool("HideHuntBlock") ? "none" : "block";
        if(!gmGetBool("HideHuntBlock")) {
            mapRightBlockHeight += map_hunt_block_div.offsetHeight;
        }
        mapRightBlock.style.height = `${mapRightBlockHeight}px`;
    }
}
function toggleBattlesDayWith() {
    const mapRightBlock = document.querySelector("#map_right_block");
    if(mapRightBlock) {
        const battlesDayTable = mapRightBlock.querySelector("table.wbwhite");
        if(battlesDayTable && !battlesDayTable.classList.contains("rounded_table")) {
            battlesDayTable.style.display = gmGetBool("HideBattlesDayWithBlock") ? "none" : "";
        }
    }
}
function toggleRightBlock() {
    const mapRightBlock = document.getElementById("map_right_block");
    if(mapRightBlock) {
        mapRightBlock.style.display = gmGetBool("HideRightBlock") ? "none" : "block";
    }
}
function tryGetAmbushState() {
    if(location.pathname == '/map.php') {
        const ambushDiv = Array.from(document.querySelectorAll("div#map_right_block .wbwhite.rounded_table")).find(x => x.innerHTML.includes(AmbushingName));
        if(ambushDiv) {
            GM_setValue("AmbushState", AmbushState.Setted);
            GM_deleteValue("AmbushSuspendExpireDate");
        } else {
            if(GM_getValue("AmbushState") == AmbushState.Setted) {
                GM_deleteValue("AmbushState");
            }
        }
    }
    if(location.pathname == '/war.php') {
        const warId = getUrlParamValue(location.href, "warid");
        const lt = getUrlParamValue(location.href, "lt");
        finalResultDiv = document.getElementById("finalresult_text");
        if(lt != "-1" && finalResultDiv.innerHTML.length <= 10 && GM_getValue("AmbushState") == AmbushState.Setted) {
            GM_setValue("AmbushState", AmbushState.Battle);
            observe(finalResultDiv, parseBattleResultPanel);
        }
    }
    console.log(`tryGetAmbushState AmbushState: ${GM_getValue("AmbushState")}`);
}
function parseBattleResultPanel() {
    if(GM_getValue("AmbushState") == AmbushState.Battle && finalResultDiv.innerHTML.length > 10) {
        GM_deleteValue("AmbushState");
        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);
                if(bold.parentNode.nextSibling.nextSibling.firstChild.innerText == GM_getValue("TransporterUserName")) {
                    result = "win";
                }
                break;
            }
        }
        if(result == "fail") {
            GM_setValue("AmbushSuspendExpireDate", Date.now() + AmbushMinutesInterval * 60 * 1000);
        }
        console.log(`result: ${result}, AmbushSuspendExpireDate: ${GM_getValue("AmbushSuspendExpireDate")}`);
    }
}
async function checkAmbushResult(force = false) {
    console.log(`checkAmbushResult force: ${force}, AmbushState: ${GM_getValue("AmbushState")}, AmbushSuspendExpireDate: ${GM_getValue("AmbushSuspendExpireDate")}`);
    const afterAmbushUnknownResult = GM_getValue("AmbushState") == AmbushState.Battle; // После засады был бой
    if(afterAmbushUnknownResult && !isFullHealth() && !force) {
        GM_setValue("AmbushSuspendExpireDate", Date.now() + AmbushMinutesInterval * 60 * 1000);
    } else {
        let doc;
        if(location.pathname == '/pl_warlog.php') {
            const page = getUrlParamValue(location.href, "page");
            const id = getUrlParamValue(location.href, "id");
            if(id == PlayerId && (!page || page == 0)) {
                doc = document;
            }
        }
        if(doc || afterAmbushUnknownResult || force) {
            doc = doc || await getRequest(`/pl_warlog.php?id=${PlayerId}`);
            processWarlog(doc);
        }
    }
    if(afterAmbushUnknownResult) {
        GM_deleteValue("AmbushState");
    }
    refreshAmbushMoratoriumPanel();
}
function getHealth() {
    const health_amount = document.getElementById("health_amount");
    let health;
    if(health_amount) {
        health = parseInt(health_amount.innerText);
    } else {
        health = windowObject.heart;
    }
    return health;
}
function isFullHealth() {
    const healthRestoreTime = parseInt(GM_getValue("HealthRestoreTime", 0));
    return healthRestoreTime < Date.now();
}
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("HealthRestoreTime", Date.now() + restSeconds * 1000);
        } else {
            GM_deleteValue("HealthRestoreTime");
        }
    }
    expireHealthRestoreTime();
}
function expireHealthRestoreTime() {
    const healthRestoreTime = parseInt(GM_getValue("HealthRestoreTime", 0));
    //console.log(`expireHealthRestoreTime healthRestoreTime: ${healthRestoreTime}}`);
    if(healthRestoreTime > 0) {
        if(Date.now() > healthRestoreTime) {
            GM_deleteValue("HealthRestoreTime");
            drawAmbushMarks();
        } else {
            setTimeout(expireHealthRestoreTime, 1000);
        }
    }
}
function processWarlog(doc) {
    const lastAmbushRef = Array.from(doc.querySelectorAll("a[href*='warlog.php?warid=']")).find(x => x.nextSibling.textContent == ": • ");
    let lastAmbushResult = AmbushResult.NotFound;
    if(lastAmbushRef) {
        lastAmbushResult = lastAmbushRef.nextElementSibling.tagName.toLowerCase() == "b" ? AmbushResult.Win : AmbushResult.Fail;
        var lastAmbushDate = toClientTime(parseDate(lastAmbushRef.innerText));
    }
    if(lastAmbushDate) {
        if(lastAmbushResult == AmbushResult.Fail) {
            const newAmbushSuspendExpireDate = lastAmbushDate.getTime() + (AmbushMinutesInterval + 1) * 60 * 1000;
            // Если нет старого значения AmbushSuspendExpireDate или расхождение с новым из лога больше минуты, то установим новое, если оно ещё актуально
            if(!GM_getValue("AmbushSuspendExpireDate") || Math.abs(parseInt(GM_getValue("AmbushSuspendExpireDate")) - newAmbushSuspendExpireDate) > 60 * 1000) {
                if(newAmbushSuspendExpireDate > Date.now()) {
                    GM_setValue("AmbushSuspendExpireDate", newAmbushSuspendExpireDate);
                } else {
                    GM_deleteValue("AmbushSuspendExpireDate");
                }
            }
        }
    } else {
        GM_deleteValue("AmbushSuspendExpireDate");
    }
    console.log(`processWarlog lastAmbushResult: ${lastAmbushResult}, lastAmbushDate: ${lastAmbushDate.toLocaleString()}, ambushSuspendExpireDate: ${fromTimeSpanToString(GM_getValue("AmbushSuspendExpireDate"))}`);
}
function fromTimeSpanToString(timeSpan) {
    if(timeSpan) {
        return (new Date(parseInt(timeSpan))).toLocaleString();
    }
}
function toClientTime(serverDateTime) {
    //console.log(`LastRequestedClientServerTimeDifferenceMilliseconds: ${GM_getValue("LastRequestedClientServerTimeDifferenceMilliseconds", 0)}, serverDateTime.getTime(): ${serverDateTime.getTime()}, ${serverDateTime.getTime() + GM_getValue("LastRequestedClientServerTimeDifferenceMilliseconds", 0)}`);
    return new Date(serverDateTime.getTime() + parseInt(GM_getValue("LastRequestedClientServerTimeDifferenceMilliseconds", 0)));
}
function getServerTime() { return Date.now() + parseInt(GM_getValue("LastRequestedClientServerTimeDifferenceMilliseconds", 0)); }
function requestServerTime() {
    if(parseInt(GM_getValue("LastClientServerTimeDifferenceRequestDate", 0)) + ClientServerTimeDifferenceRequestInterval < (new Date().getTime())) {
        GM_setValue("LastClientServerTimeDifferenceRequestDate", new Date().getTime());
        const objXMLHttpReqTime = new XMLHttpRequest();
        objXMLHttpReqTime.open('GET', '/time.php?rand=' + (Math.random() * 1000000), true);
        objXMLHttpReqTime.onreadystatechange = function() {
            if(objXMLHttpReqTime.readyState == 4 && objXMLHttpReqTime.status == 200) {
                let responseParcing = /now (\d+)/.exec(objXMLHttpReqTime.responseText); //objXMLHttpReqTime.responseText: now 1681711364 17-04-23 09:02
                if(responseParcing) {
                    GM_setValue("LastRequestedClientServerTimeDifferenceMilliseconds", new Date().getTime() - parseInt(responseParcing[1]) * 1000);
                }
            }
        }
        objXMLHttpReqTime.send(null);
    } else {
        setTimeout(function() { requestServerTime(); }, ClientServerTimeDifferenceRequestInterval);
    }
}
function truncDateTimeToMinutes(dateTime) {
    const coeff = 1000 * 60;
    return new Date(Math.floor(dateTime.getTime() / coeff) * coeff);
}
function refreshAmbushMoratoriumPanel() {
    if(GM_getValue("AmbushSuspendExpireDate")) {
        const ambushMoratoriumEndDate = parseInt(GM_getValue("AmbushSuspendExpireDate"));
        const now = Date.now();
        //console.log(`ambushMoratoriumEndDate > now: ${ambushMoratoriumEndDate > now}`);
        if(ambushMoratoriumEndDate > now) {
            const formatedTime = formatInterval(ambushMoratoriumEndDate - now);
            setAmbushMoratoriumPanelValue(formatedTime);
            
            const pers_gv = refreshAmbushMoratoriumPanel.pers_gv || (refreshAmbushMoratoriumPanel.pers_gv = document.querySelector("#pers_gv"));
            persGvStopped = persGvStopped || pers_gv && pers_gv.innerText == "00:00" && formatedTime != "00:00";
            if(persGvStopped) {
                pers_gv.innerText = formatedTime;
            }
            setTimeout(refreshAmbushMoratoriumPanel, 1000);
        } else {
            setAmbushMoratoriumPanelValue();
            GM_deleteValue("AmbushSuspendExpireDate");
            if(gmGetBool("ThievesGuildNotifications")) {
                GM.notification("Вы можете устроить засаду", "ГВД", "https://dcdn.heroeswm.ru/i/portraits/thiefwarrioranip33.png", function() { window.focus(); getURL("/map.php"); });
            }
            persGvStopped = false;
            drawAmbushMarks();
        }
    } else {
        setAmbushMoratoriumPanelValue();
        drawAmbushMarks();
    }
}
function setAmbushMoratoriumPanelValue(value) {
    if(AmbushMoratoriumPanel) {
        AmbushMoratoriumPanel.innerText = value || "";
    }
}
function formatInterval(interval) {
    let diff = interval;
    const hours = Math.floor(diff / 1000 / 60 / 60);
    diff -= hours * 1000 * 60 * 60;
    const mimutes = Math.floor(diff / 1000 / 60);
    diff -= mimutes * 1000 * 60;
    const seconds = Math.floor(diff / 1000);
    const formatedTime = (hours > 0 ? hours + ":" : "") + ( (mimutes < 10) ? '0' : '' ) + mimutes + ':' + (seconds < 10 ? '0' : '') + seconds;
    return formatedTime;
}
function drawMap() {
    const insideMap = document.querySelector("#inside_map");
    const jsmap = document.querySelector("#jsmap");
    const isHideMap = gmGetBool("HideMap");
    const mapRightBlock = document.getElementById("map_right_block");
    if(jsmap) {
        jsmap.style.display = isHideMap ? "none" : "block";
    }
    toggleRightBlock();
    toggleBattlesDayWith();
    toggleHuntBlock();
    if(insideMap && (!gmGetBool("PopupAlways") || isMobileInterface)) {
        mapRightBlock.style.removeProperty('height');
        let mapMovingContainer = insideMap;
        let mapMovingStyle = "";
        if(isMobileInterface) {
            insideMap.style.flexWrap = "wrap";
            //insideMap.style.justifyContent = "normal";
            addElement("div", insideMap, { style: "flex-basis: 100%; height: 0;"});
            mapMovingStyle = "order: 4;";
        } else {
            mapMovingContainer = addElement("div", insideMap, { style: "order: 1;" });
            mapMovingContainer.appendChild(jsmap);
            insideMap.style.gap = "10px";
        }
        drawMapCore(mapMovingContainer, mapMovingStyle);
    } else {
        let mapHeight = 350;
        let mapWidth = 500;
        const mapActivatorDiv = addElement("div", document.body, { style: `position: fixed; width: 5px; height: ${ClientHeight() - 50}px; top: 50px; left: 0; background-color: #FFCCCB; opacity: 80%;` });
        const mapContainerDiv = addElement("div", document.body, { style: `position: fixed; width: ${mapWidth}px; height: ${mapHeight}px; top: ${(ClientHeight() - mapHeight)/ 2}px; left: -${mapWidth}px; transition-duration: 0.3s; z-index: 10;` }, true);
        const mapMoving = drawMapCore(mapContainerDiv, "box-shadow: 1px 1px 5px #333;"); //, `height: ${innerMapHeight}px`
        let timer;
        mapActivatorDiv.addEventListener("mouseover", function(event) { timer = setTimeout(function() { mapContainerDiv.style.top = `${ClientHeight() / 2 - 175}px`; mapContainerDiv.style.left = "0"; }, 100); }, false);
        mapActivatorDiv.addEventListener("mouseout", function(event) { timer = setTimeout(function() { mapContainerDiv.style.left = `-${mapWidth + 10}px`; }, 300); }, false);
        mapContainerDiv.addEventListener("mouseover", function(event) { clearTimeout(timer); }, false);
        mapContainerDiv.addEventListener("mouseout", function(event) { timer = setTimeout(function() { mapContainerDiv.style.left = `-${mapWidth + 10}px`; }, 100); }, false);
    }
}
function drawMapCore(parentNode, mainStyle) {
    let mapMoving = document.getElementById("mapMoving");
    if(mapMoving) {
        mapMoving.parentNode.removeChild(mapMoving);
    }
    const workImageSize = 25;
    mapMoving = addElement("div", parentNode, { id: "mapMoving", style: (mainStyle || "") + "border: 2mm ridge rgba(211, 220, 50, .6); background: linear-gradient(0.15turn, #3f87a6, #ebf8e1, #f69d3c 80%);" });
    mapMoving.innerHTML = `
<div style="position: relative; text-align: center; font-size: 9px;">
    <span style="font-size: 9px;">${ (MountInfo.EndDate ? `${MountInfo.ComplexRoute ? ComplexRouteName + ", " : ""}${MountInfo.IsPremiunAccount ? PremiunAccountName + ", " : ""}${MountUntilName}: ${(new Date(MountInfo.EndDate)).toLocaleString()}, ` : "") + `${TravelingTimeName}: ${MountInfo.TravelingTime} ${Second}` }</span>
    ${ MountInfo.AntithiefControlled ? `, <input id='toggleAntithiefCheckbox' type='checkbox' ${MountInfo.Antithief ? "checked" : ""}>${Antithief}` : "" }
    <div id="checkMountButton" title="${CheckMountName}" style="display: inline-block;">
        <img src="https://dcdn2.heroeswm.ru/i/pl_info/btn_reset.png" style="height: 12px; wigth: 12px;">
    </div>
    <br />
    <input id='removeArtsBeforeMovingCheckbox' type='checkbox' ${gmGetBool("removeArtsBeforeMoving") ? "checked" : ""}>${UndressBeforeMove}
    <input id='IgnoreComplexRouteCheckbox' type='checkbox' ${gmGetBool("IgnoreComplexRoute") ? "checked" : ""}>${NotUseComplexRoute}
    <input id='HideMapCheckbox' type='checkbox' ${gmGetBool("HideMap") ? "checked" : ""}>${HideMapName}
    <br />
    <input id='HideHuntBlockCheckbox' type='checkbox' ${gmGetBool("HideHuntBlock") ? "checked" : ""}>${HideHuntName}
    <input id='HideRightBlockCheckbox' type='checkbox' ${gmGetBool("HideRightBlock") ? "checked" : ""}>${HideRightBlockName}
    <input id='popupAlwaysCheckbox' type='checkbox' ${gmGetBool("PopupAlways") ? "checked" : ""}>${PopupAlwaysName}
    <input id='showLocationNumbersCheckbox' type='checkbox' ${gmGetBool("ShowLocationNumbers") ? "checked" : ""}>${ShowLocationNumbersName}
</div>
<div>
    <table id="hwmMapMoveTable" style="">
    </table>
</div>
<div style="position: relative; text-align: left; font-size: 9px;">
    <div id="findNearestProductionBigButton" title="${FindNearestProductionName}" style="float: right;">
        <img src="https://dcdn.heroeswm.ru/i/btns/job_fl_btn_production.png" style="scale: 1.5;">
    </div>
    ${LoadName}<input id='loadMiningCheckbox' type='checkbox' ${gmGetBool("Load" + factoryTypes[0]) ? "checked" : ""}>${Mining}
    <input id='loadMachiningCheckbox' type='checkbox' ${gmGetBool("Load" + factoryTypes[1]) ? "checked" : ""}>${Machining}
    <input id='loadProductionCheckbox' type='checkbox' ${gmGetBool("Load" + factoryTypes[2]) ? "checked" : ""}>${Production}
    <input id='HideBattlesDayWithBlockCheckbox' type='checkbox' ${gmGetBool("HideBattlesDayWithBlock") ? "checked" : ""}>${HideBattlesDayWithName}
    <br/>
    <span style="font-size: 9px;">${Legend}, PlayerSector: ${playerLocationNumber}${playerLocationNumber != GM_getValue("LastObservingLocationNumber") ? ", View: " + GM_getValue("LastObservingLocationNumber") : ""}</span>
    <!--<span id="ClickMe">ClickMe</span>-->
    <br/>
    ${isEn ? "Min salary: " : "Минимальная зарплата: "}<input id='minSalaryNumber' type='number' value='${GM_getValue("MinSalary", "")}' size="4" onfocus="this.select();">
    <input id='thievesGuildNotificationsCheckbox' type='checkbox' ${gmGetBool("ThievesGuildNotifications") ? "checked" : ""}>${isEn ? "Thieves guild notifications" : "Оповещение о засаде"}
    <br/>
    <b>${JobFinding}:</b>
    <div id="findNearestMiningButton" title="${FindNearestMiningName}" style="display: inline-block;">
        <img src="https://dcdn.heroeswm.ru/i/btns/job_fl_btn_mining.png" style="width: ${workImageSize}px; height: ${workImageSize}px;">
    </div>
    <div id="findNearestManufactureButton" title="${FindNearestManufactureName}" style="display: inline-block;">
        <img src="https://dcdn.heroeswm.ru/i/btns/job_fl_btn_manufacture.png" style="width: ${workImageSize}px; height: ${workImageSize}px;">
    </div>
    <div id="findNearestProductionButton" title="${FindNearestProductionName}" style="display: inline-block;">
        <img src="https://dcdn.heroeswm.ru/i/btns/job_fl_btn_production.png" style="width: ${workImageSize}px; height: ${workImageSize}px;">
    </div>
    <input id='moveToWorkAfterFindCheckbox' type='checkbox' ${gmGetBool("MoveToWorkAfterFind") ? "checked" : ""}>${MoveToWorkAfterFindName}
    <span id="workFindingMessageSpan"/>
</div>`;
    mapMoving.querySelector("#removeArtsBeforeMovingCheckbox").addEventListener("click", function() { GM_setValue("removeArtsBeforeMoving", this.checked); }, false);
    mapMoving.querySelector("#IgnoreComplexRouteCheckbox").addEventListener("click", function() { GM_setValue("IgnoreComplexRoute", this.checked); }, false);
    mapMoving.querySelector("#HideMapCheckbox").addEventListener("click", function() { GM_setValue("HideMap", this.checked); window.location.reload(); }, false);
    if(MountInfo.AntithiefControlled) {
        mapMoving.querySelector("#toggleAntithiefCheckbox").addEventListener("click", function() { toggleAntithief(this); }, false);
    }
    mapMoving.querySelector("#HideHuntBlockCheckbox").addEventListener("click", function() { GM_setValue("HideHuntBlock", this.checked); toggleHuntBlock(); }, false);
    mapMoving.querySelector("#HideRightBlockCheckbox").addEventListener("click", function() { GM_setValue("HideRightBlock", this.checked); toggleRightBlock(); }, false);
    mapMoving.querySelector("#checkMountButton").addEventListener("click", function() { main(true); }, false);
    
    mapMoving.querySelector("#loadMiningCheckbox").addEventListener("click", function() { GM_setValue("Load" + factoryTypes[0], this.checked); }, false);
    mapMoving.querySelector("#loadMachiningCheckbox").addEventListener("click", function() { GM_setValue("Load" + factoryTypes[1], this.checked); }, false);
    mapMoving.querySelector("#loadProductionCheckbox").addEventListener("click", function() { GM_setValue("Load" + factoryTypes[2], this.checked); }, false);
    mapMoving.querySelector("#popupAlwaysCheckbox").addEventListener("click", function() { GM_setValue("PopupAlways", this.checked); window.location.reload(); }, false);
    mapMoving.querySelector("#showLocationNumbersCheckbox").addEventListener("click", function() { GM_setValue("ShowLocationNumbers", this.checked); window.location.reload(); }, false);
    mapMoving.querySelector("#HideBattlesDayWithBlockCheckbox").addEventListener("click", function() { GM_setValue("HideBattlesDayWithBlock", this.checked); toggleBattlesDayWith(); }, false);
    
    //mapMoving.querySelector("#ClickMe").addEventListener("click", function() { getObjectLocations(); }, false);
    
    mapMoving.querySelector("#moveToWorkAfterFindCheckbox").addEventListener("click", function() { GM_setValue("MoveToWorkAfterFind", this.checked); }, false);
    mapMoving.querySelector("#minSalaryNumber").addEventListener("change", function() { GM_setValue("MinSalary", parseInt(this.value)); }, false);
    mapMoving.querySelector("#findNearestMiningButton").addEventListener("click", function() { findNearestWork("mn"); }, false);
    mapMoving.querySelector("#findNearestManufactureButton").addEventListener("click", function() { findNearestWork("fc"); }, false);
    mapMoving.querySelector("#findNearestProductionButton").addEventListener("click", function() { findNearestWork("sh"); }, false);
    mapMoving.querySelector("#findNearestProductionBigButton").addEventListener("click", function() { findNearestWork("sh"); }, false);
    mapMoving.querySelector("#thievesGuildNotificationsCheckbox").addEventListener("click", function() { GM_setValue("ThievesGuildNotifications", this.checked); }, false);

    const table = mapMoving.querySelector("#hwmMapMoveTable");
    const now = new Date();
    for(let y = minY; y <= maxY; y++) {
        const tableRow = addElement("tr", table);
        for(let x = minX; x <= maxX; x++) {
            let cellStyle = "";
            let locationNumber = getLocationNumberByCoordinate(x, y);
            const tableCell = addElement("td", tableRow, { id: `transporterLocation${locationNumber}` });
            if(locationNumber) {
                const loc = locations[locationNumber];
                tableCell.innerHTML = `${loc[locationNamesLocalizedColumn].replace(" ", "<br>")} ${ gmGetBool("ShowLocationNumbers") ? `(${locationNumber})` : "" }`;
                tableCell.style = `cursor: pointer; font-size: 10px; font-weight: bold; text-align: center; border: 1px solid #000; padding: 3px; color: ${locationNumber == playerLocationNumber ? "red" : "black"};`;
                if(locationNumber < 100) {
                    let routeTime = "";
                    const routeSettings = routes[playerLocationNumber][locationNumber];
                    if(routeSettings) {
                        const routeLength = routeSettings[1];
                        routeTime = `\n${TravelingTimeName} ${routeLength * MountInfo.TravelingTime} ${Second}`;
                    }
                    tableCell.title = `${GoTo} ${loc[locationNamesLocalizedColumn]}${routeTime}`;
                    tableCell.addEventListener("mouseenter", function() { this.style.border = "1px solid #fff"; }, false);
                    tableCell.addEventListener("mouseleave", function() { this.style.border = "1px solid #000"; }, false);
                    tableCell.addEventListener("click", function(e) { if(e.altKey) { getURL(`/map.php?cx=${x}&cy=${y}`); } else if(e.ctrlKey) { setThiefAmbush(locationNumber); } else { tryMoving(locationNumber); } }, false);
                    tableCell.addEventListener("auxclick", function(e) { e.stopPropagation();/*if(e.which === 2) { e.preventDefault(); }*/ if(e.button == 1) { setThiefAmbush(locationNumber); } }, false); // console.log(`e.which: ${e.which}, e.button: ${e.button}`);
                    const sectorViewReference = addElement("a", tableCell, { href: `/map.php?cx=${x}&cy=${y}`, style: "display: block; float: left;", title: `${isEn ? "View" : "Просмотр"}` });
                    addElement("img", sectorViewReference, { style: "border: 0;", src: "data:image/gif,GIF89a%10%00%10%00%D5%00%00B%40B%15%15%18((-PPUzz%7FHHJ%5D%5D_--.zz%7BWWX23%3BEHS%80%88%A2rx%8C%DA%DD%E7bj%80sx%87%88%8C%97%7D%85%98HO_%7D%85%97%88%8C%95psz%DC%DD%DF%D4%D5%D7x%7F%8C%2B-0%C2%DF%FF%DF%F4%FFMOPmop%EF%F1%F2%E7%F9%FF%EA%FD%FF%E7%FF%FF%13%15%15%E9%FF%FF%18%1A%1A%FA%FF%FFmoo%FC%FF%FF%FD%FF%FFZ%5B%5B%5D%5D%5B%40%3F%3F%FF%FF%FF%16%16%16%10%10%10%05%05%05%00%00%00%FF%FF%FF%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%002%00%2C%00%00%00%00%10%00%10%00%00%06%5C%40%99pH%2C%12%13%01%8D%80e%24%02ZP%A8%A2)k%C8%A2%D1As%01Ma'%CDF%D4%14%7D4!-%94%A8%95%BAJ%9A%07P%8B%E4mm%2C%D4H(%CA%A9%AC%A82%05%19%0C%14%041*%17%80E0%1D%18%8AD%2F%06%89%8FB%25'%1F%94B.%08-%992%23%1E%9ECA%00%3B" });
                    sectorViewReference.addEventListener("click", function(e) { e.stopPropagation(); })
                    
                    if([2, 6, 16, 21].includes(parseInt(locationNumber))) {
                        getMoveToMapObjectReference('/mercenary_guild.php', tableCell, locationNumber, "https://dcdn.heroeswm.ru/i/btns/job_fl_btn_mercenary.png", "display: block; float: right; width: 16px; height: 16px;");
                    }
                    const guestInfo = getGuestInfo(locationNumber);
                    const guestInfoKeys = Object.keys(guestInfo);
                    if(guestInfoKeys.length > 0) {
                        const guestInfoTitles = guestInfoKeys.map(x => `${guestInfo[x].HostInfo} до ${(new Date(guestInfo[x].ExpireDate)).toLocaleString()}`);
                        const guestReference = addElement("a", tableCell, { href: `/house_info.php?id=${guestInfoKeys[0]}`, style: "display: block; float: left;", title: guestInfoTitles.join("\n") });
                        guestReference.addEventListener("click", function(e) { e.stopPropagation(); })
                        addElement("img", guestReference, { style: "border: 0;", src: "https://dcdn.heroeswm.ru/i/btns/job_fl_btn_houses.png", style: "width: 16px; height: 16px;" });
                    }
                } else {
                    tableCell.style.color = loc[5].Color;
                    tableCell.addEventListener("click", function() { getURL(loc[5].Url); }, false);
                    if(x == 50 && y == 54) {
                        addElement("br", tableCell);
                        AmbushMoratoriumPanel = addElement("span", tableCell, { id: "AmbushMoratoriumPanel" });
                    }
                    if(x == 51 && y == 54) {
                        addElement("br", tableCell);
                        addElement("span", tableCell, { id: "SmithMoratoriumPanel" });
                    }
                }
            }
        }
    }
    processMercenaryTaskLocation();
    return mapMoving;
}
function drawAmbushMarks() {
    for(const locationNumber of Object.keys(locations).filter(x => parseInt(x) < 100)) {
        const routeSettings = routes[playerLocationNumber][locationNumber];
        const tableCell = document.getElementById(`transporterLocation${locationNumber}`);
        const thiefwarrioranip33 = tableCell.querySelector("img[src*='thiefwarrioranip33']");
        if(routeSettings && routeSettings[1] < 2 && !GM_getValue("AmbushSuspendExpireDate") && !GM_getValue("AmbushState") && isFullHealth()) {
            if(!thiefwarrioranip33) {
                const ambushImage = addElement("img", tableCell, { src: "https://dcdn.heroeswm.ru/i/portraits/thiefwarrioranip33.png", style: "width: 16px; height: 16px;", title: `${isEn ? "Ambush" : "Засада"}` });
                ambushImage.addEventListener("click", function(e) { e.stopPropagation(); setThiefAmbush(locationNumber); });
            }
        } else {
            if(thiefwarrioranip33) {
                thiefwarrioranip33.remove();
            }
        }
    }
}
async function processMercenaryTaskLocation() {
    const mercenaryTaskLocation = await getMercenaryTaskLocation();
    if(mercenaryTaskLocation) {
        addElement("img", document.getElementById(`transporterLocation${mercenaryTaskLocation}`), { src: "https://dcdn2.heroeswm.ru/i/cssmap/map_sectors_naim.png", style: "width: 16px; height: 16px;", title: `${isEn ? "Mercenary guild task" : "Задание гильдии наемников"}` });
    }
}
function getMercenaryTaskLocation() {
    return new Promise((resolve, reject) => {
        if(location.pathname == '/map.php') {
            setTimeout(function() {
                if(windowObject.naim_type == 'fight') {
                    GM_setValue("MercenaryTaskLocation", windowObject.naim_sector);
                } else {
                    GM_deleteValue("MercenaryTaskLocation");
                }
                resolve(GM_getValue("MercenaryTaskLocation"));
            }, 300);
        } else {
            resolve(GM_getValue("MercenaryTaskLocation"));
        }
    });
}
async function findNearestWork(factoryType) {
    const movingEnabled = await varifyMovingEnabled();
    let notEmptyLocationsSortedByDistance = [playerLocationNumber];
    if(movingEnabled) {
        notEmptyLocationsSortedByDistance = getLocationsSortedByDistance();
    }
    notEmptyLocationsSortedByDistance = notEmptyLocationsSortedByDistance.filter(x => !GM_getValue(`LastDetectedEmptyFactoriesDate_${factoryType}${x}`));
    const workFindingMessageSpan = document.querySelector("span#workFindingMessageSpan");
    for(const locationNumber of notEmptyLocationsSortedByDistance) {
        workFindingMessageSpan.innerText = `Viewing ${locationNumber}...`;
        var findedWorkObject = await findWorkInLocation(factoryType, locationNumber);
        if(findedWorkObject) {
            if(gmGetBool("MoveToWorkAfterFind")) {
                tryMoving(locationNumber, findedWorkObject.href);
            } else {
                getURL(findedWorkObject.href);
            }
            workFindingMessageSpan.innerText = "";
            break;
        }
    }
    if(!findedWorkObject) {
        workFindingMessageSpan.innerText = "Vacancy not found";
    }
}
async function findWorkInLocation(factoryType, locationNumber) {
    const x = locations[locationNumber][0];
    const y = locations[locationNumber][1];
    const doc = await getRequest(`map.php?cx=${x}&cy=${y}&st=${factoryType}`);
    const objectRefs = doc.querySelectorAll("a[href^='object-info.php?id=']");
    //console.log(`objectRefs: ${objectRefs.length}`);
    for(const objectRef of objectRefs) {
        //console.log(`objectRef.innerText: ${objectRef.innerText}, ${objectRef.innerText.includes("\u00bb\u00bb\u00bb")}`);
        if(objectRef.innerText.includes("\u00bb\u00bb\u00bb") && !objectRef.querySelector("font")) {
            const salary = parseInt(objectRef.parentNode.previousElementSibling.querySelector("b").innerHTML);
            const minSalary = parseInt(GM_getValue("MinSalary", 0));
            //console.log(`minSalary: ${minSalary}, salary: ${salary}, objectRef: ${objectRef}`);
            if(minSalary == 0 || salary >= minSalary) {
                return objectRef;
            }
        }
    }
}
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 postRequest(url, data) {
    return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({ method: "POST", url: url, headers: { "Content-Type": "application/x-www-form-urlencoded" }, data: data,
            onload: function(response) { resolve(response); },
            onerror: function(error) { reject(error); }
        });
    });
}
function getLocationsSortedByDistance() {
    if(playerLocationNumber in routes && routes[playerLocationNumber]) {
        const playerLocationRoutes = routes[playerLocationNumber];
        const availableLocationsDistance = { };
        availableLocationsDistance[playerLocationNumber] = 0;
        for(let i = 0; i < playerLocationRoutes.length; i++) {
            if(playerLocationRoutes[i]) {
                availableLocationsDistance[i] = playerLocationRoutes[i][1];
            }
        }
        const locationsSortedByDistance = Object.keys(availableLocationsDistance).sort(function(a,b) { return availableLocationsDistance[a] - availableLocationsDistance[b]; });
        //console.log(locationsSortedByDistance);
        return locationsSortedByDistance;
    }
}
function getLocationNumberByCoordinate(x, y) {
    for(let locationNumber in locations) {
        if(locations[locationNumber][0] == x && locations[locationNumber][1] == y) {
            return locationNumber;
        }
    }
}
function processMoving() {
    let targetLocationNumber = parseInt(GM_getValue("TargetLocationNumber"));
    //console.log(`playerLocationNumber: ${playerLocationNumber}, TargetLocationNumber: ${GM_getValue("TargetLocationNumber")}, LastMoveTryFrom: ${GM_getValue("LastMoveTryFrom")}`);
    if(GM_getValue("LastMoveTryFrom") == playerLocationNumber && targetLocationNumber != playerLocationNumber) {
        stopMoving(); // Мы не смогли уйти из локации, поэтому останавливаем движение; и мы не движемся на объект в этой же локации
        return;
    }
    let arrived = false;
    if(targetLocationNumber) {
        if(targetLocationNumber == playerLocationNumber) {
            GM_deleteValue("TargetLocationNumber");
            GM_deleteValue("LastMoveTryFrom");
            GM_deleteValue("LastMoveTryTo");
            arrived = true;
        } else {
            goToNextRoutePoint();
        }
    }
    //console.log([playerLocationNumber, targetLocationNumber, arrived, GM_getValue("EnterObjectOnArrived")]);
    // Перемещение к объекту после прибытия
    if(arrived) {
        let enterObjectOnArrived = GM_getValue("EnterObjectOnArrived");
        if(enterObjectOnArrived) {
            GM_deleteValue("EnterObjectOnArrived");
            getURL(enterObjectOnArrived);
            //setTimeout(function() { window.location = enterObjectOnArrived; }, 300);
        }
    }
}
function tryMoving(targetLocationNumber, enterObjectOnArrived) {
    if(!MountInfo.ComplexRoute) {
        alert(isEn ? "Script working only if mount with complex route enabled" : "Скрипт работает, если есть транспорт со сложным маршрутом");
        return;
    }
    if(targetLocationNumber == playerLocationNumber && !enterObjectOnArrived) {
        alert(YouAreHere);
        return;
    }
    if(targetLocationNumber == 25) {
        alert(How);
        return;
    }
    checkMovingEnabledAndGo(targetLocationNumber, enterObjectOnArrived);

}
async function varifyMovingEnabled() {
    let movingEnabled = true;
    if(/map.php/.test(location.href)) {
        const mapNavigator = document.querySelector("#map_navigator");
        movingEnabled = (Array.from(mapNavigator.querySelectorAll("div[id*='dbut']")).find(x => x.style.display == "block")) ? true : false;
    } else {
        const doc = await getRequest("/inventory.php");
        const invNote = doc.querySelector("div.inv_note_inside");
        movingEnabled = !(invNote && invNote.innerHTML.includes(YouAreInAChallenge));
    }
    return movingEnabled;
}
async function checkMovingEnabledAndGo(targetLocationNumber, enterObjectOnArrived) {
    let movingEnabled = true;
    if(targetLocationNumber != playerLocationNumber) {
        movingEnabled = await varifyMovingEnabled();
    }
    if(movingEnabled) {
        undressAndGo(targetLocationNumber, enterObjectOnArrived);
    } else {
        alert(YouAreInAChallenge);
    }
}
async function undressAndGo(targetLocationNumber, enterObjectOnArrived) {
    if(gmGetBool("removeArtsBeforeMoving") && targetLocationNumber != playerLocationNumber) {
        await getRequest("/inventory.php?all_off=100");
    }
    startMoving(targetLocationNumber, enterObjectOnArrived);
}
function startMoving(targetLocationNumber, enterObjectOnArrived) {
    GM_setValue("TargetLocationNumber", targetLocationNumber);
    if(enterObjectOnArrived) {
        GM_setValue("EnterObjectOnArrived", enterObjectOnArrived);
    }
    goToNextRoutePoint();
}
function goToNextRoutePoint() {
    const targetLocationNumber = parseInt(GM_getValue("TargetLocationNumber"));
    let nextRoutePoint;
    if(MountInfo.ComplexRoute && !gmGetBool("IgnoreComplexRoute")) {
        nextRoutePoint = targetLocationNumber;
    } else if(playerLocationNumber in routes && targetLocationNumber in routes[playerLocationNumber] && routes[playerLocationNumber][targetLocationNumber]) {
        const route = routes[playerLocationNumber][targetLocationNumber][0];
        nextRoutePoint = route.split('-')[1];
    }
    if(nextRoutePoint) {
        GM_setValue("LastMoveTryFrom", playerLocationNumber);
        GM_setValue("LastMoveTryTo", nextRoutePoint);
        //getURL(`/move_sector.php?id=${nextRoutePoint}&rand=${Math.random()}`);
        if(playerLocationNumber != nextRoutePoint) {
            getURL(`/move_sector.php?id=${nextRoutePoint}&rand=${Math.random()}`);
        } else if(GM_getValue("EnterObjectOnArrived")) {
            processMoving();
        }
    }
}
function stopMoving() {
    GM_deleteValue("TargetLocationNumber");
    GM_deleteValue("EnterObjectOnArrived");
    GM_deleteValue("LastMoveTryFrom");
    GM_deleteValue("LastMoveTryTo");
}
function getPlayerLocationNumber() {
    if(location.pathname == '/map.php' && location.search == '') {
        // Если мы на карте без параметров, т.е. на локации, где сами находимся. Если мы не в пути, тогда видим предприятия. Мы можем обновить текущее положение игрока.
        const minesRef = document.querySelector("a[href*='map.php?cx='][href*='&cy='][href*='&st=mn']"); // Берем из ссылки на заголовке шахт данной локации
        if(minesRef) {
            const locationNumber = getLocationNumberFromMapUrlByXy(minesRef.href);
            if(locationNumber) {
                GM_setValue("PlayerLocationNumber", locationNumber);
                return locationNumber;
            }
        }
    }
    return parseInt(GM_getValue("PlayerLocationNumber")); // Иначе, возьмем из кеша. Там будет пусто только при первом запуске скрипта, когда мы не просматриваем карту.
}
function getLocationNumberFromMapUrlByXy(href) {
    const x = getUrlParamValue(href, "cx");
    const y = getUrlParamValue(href, "cy");
    for(let locationNumber in locations) {
        if(x == locations[locationNumber][0] && y == locations[locationNumber][1]) {
            return locationNumber;
        }
    }
}
function getMoveToObjectReference(objectUrl, parentNode) {
    const objectId = getUrlParamValue(objectUrl, "id");
    const objectLocationNumber = objectLocations[objectId];
    //console.log(`objectId: ${objectId}, objectLocationNumber: ${objectLocationNumber}, parentNode: ${parentNode}`);
    return getMoveToMapObjectReference(objectUrl, parentNode, objectLocationNumber);
}
function getMoveToMapObjectReference(objectUrl, parentNode, objectLocationNumber, image, imageStyle) {
    //console.log(`objectLocationNumber: ${objectLocationNumber}, playerLocationNumber: ${playerLocationNumber}, image: ${image}`);
    if(objectLocationNumber != playerLocationNumber || image) {
        let routeTime = "";
        const routeSettings = routes[playerLocationNumber][objectLocationNumber];
        if(routeSettings) {
            const routeLength = routeSettings[1];
            routeTime = `\n${TravelingTimeName} ${routeLength * MountInfo.TravelingTime} ${Second}`;
        }
        const moveHereReference = createElement('span', { style: "cursor: pointer;", innerHTML: `<img src="${image || getHorseImageData()}" alt="${MoveHere}" title="${MoveHere + routeTime}" align="absmiddle" height="15" width="15" style="max-height: 12px; max-width: 12px;${imageStyle || ""}">&nbsp;&nbsp;` });
        moveHereReference.onclick = function(e) { tryMoving(objectLocationNumber, objectUrl); e.stopPropagation(); };
        //console.log(`objectUrl: ${objectUrl}, moveHereReference: ${moveHereReference}, routeTime: ${routeTime}, parentNode: ${parentNode}, parentNode.nextSibling: ${parentNode.nextSibling}, parentNode.nextElementSibling: ${parentNode.nextElementSibling}, parentNode.parentNode: ${parentNode.parentNode}, parentNode.previousSibling: ${parentNode.previousSibling}`);
        if(image) {
            parentNode.appendChild(moveHereReference);
        } else {
            const insertResult = parentNode.parentNode.insertBefore(moveHereReference, parentNode.nextSibling);
            //console.log(`insertResult: ${insertResult}, parentNode: ${parentNode}`);
        }
        return moveHereReference;
    }
}
async function setThiefAmbush(locationNumber) {
    if(checkSectorForAmbush(locationNumber)) {
        if(GM_getValue("AmbushSuspendExpireDate")) {
            alert(`${ItIsTooEarly}`);
        } else {
            await postRequest("/thief_ambush.php", `id=${locationNumber}&with_who=0`);
            getURL("/map.php");
        }
    } else {
        alert(AmbushMaySettedOnAdjacentSector);
    }
}
function checkSectorForAmbush(locationNumber) { 
    if(playerLocationNumber in routes && locationNumber in routes[playerLocationNumber] && routes[playerLocationNumber][locationNumber]) {
        const route = routes[playerLocationNumber][locationNumber][0];
        const nextRoutePoint = route.split('-')[1];
        return nextRoutePoint == locationNumber;
    }
    return false;
}
function resetMountInfo() {
    GM_deleteValue("TravelingTime");
    GM_deleteValue("ComplexRoute");
    GM_deleteValue("EndDate");
    GM_deleteValue("Antithief");
    GM_deleteValue("AntithiefControlled");
    GM_deleteValue("ToggleAntithiefKey");
    GM_deleteValue("IsPremiunAccount");
}
async function getMountInfo(force) {
    let endDate = GM_getValue("EndDate") ? parseInt(GM_getValue("EndDate")) : undefined;
    if(!endDate || endDate <= Date.now() || force) {
        if(endDate <= Date.now()) {
            resetMountInfo();
        }
        const doc = await getRequest("/shop.php?cat=transport");
        const mounts = doc.querySelectorAll('div.s_art_r');
        //console.log(["mounts", mounts.length]);
        const mountsInfoGeted = mounts.length > 0; // Если 0, то мы в пути и на страницу магазина нас не пускают. Поэтому используем старые данные о транспорте
        if(mountsInfoGeted) {
            let isPremiunAccount = false;
            for(const mount of mounts) {
                if(mount.innerHTML.includes(YourPremiumMountUntil) || mount.innerHTML.includes(YourMountUntil)) {
                    var mountRow = mount;
                    isPremiunAccount = mount.innerHTML.includes(YourPremiumMountUntil);
                    break;
                }
            }
            if(mountRow) {
                resetMountInfo();
                GM_setValue("IsPremiunAccount", isPremiunAccount);
                const mountExpireTimeDiv = mountRow.querySelector('div.s_art_note_transport') || mountRow.querySelector('div.s_art_name.s_transport_name');
                const dateString = (mountExpireTimeDiv.querySelector('span') || mountExpireTimeDiv).innerText.split(Until)[1];
                GM_setValue("EndDate", parseDate(dateString).getTime());
                const transportOptions = mountRow.querySelectorAll("div.s_art_prop_transport");
                for(let transportOption of transportOptions) {
                    if(transportOption.innerHTML.includes(TravelingTimeName)) {
                        const timeValue = /(\d{1,2})/.exec(transportOption.innerHTML);
                        GM_setValue("TravelingTime", timeValue[1]);
                    }
                    if(transportOption.innerHTML.includes(ComplexRouteName)) {
                        GM_setValue("ComplexRoute", transportOption.querySelector("img.s_art_prop_transport_amount_image[src*='check_yes']") ? true : false);
                    }
                    if(transportOption.innerHTML.includes(Antithief)) {
                        let antithiefControlImage = transportOption.querySelector("img.s_art_prop_transport_amount_image[src*='check_on']");
                        antithiefControlImage = antithiefControlImage || transportOption.querySelector("img.s_art_prop_transport_amount_image[src*='check_off']");
                        GM_setValue("Antithief", antithiefControlImage ? true : false);
                        GM_setValue("AntithiefControlled", antithiefControlImage ? true : false);
                        if(antithiefControlImage) {
                            GM_setValue("ToggleAntithiefKey", getUrlParamValue(antithiefControlImage.parentNode.href, "t_on") || getUrlParamValue(antithiefControlImage.parentNode.href, "t_off"));
                        }
                    }
                }
            }
        }
    }
    const mountInfo = {
        TravelingTime: parseInt(GM_getValue("TravelingTime", DefaultTravelingTime)),
        ComplexRoute: gmGetBool("ComplexRoute"),
        EndDate: GM_getValue("EndDate") ? parseInt(GM_getValue("EndDate")) : undefined,
        Antithief: gmGetBool("Antithief"),
        AntithiefControlled: gmGetBool("AntithiefControlled"),
        IsPremiunAccount: gmGetBool("IsPremiunAccount")
    };
    //console.log(mountInfo);
    return mountInfo;
}
function processHouseInfo() {
    if(location.pathname == '/house_info.php') {
        const houseId = getUrlParamValue(location.href, "id");
        const hostDiv = document.querySelector("div#tt");
        const container = getParent(hostDiv, "table");
        const loc = container.querySelector("a[href^='map.php?cx=']");
        const locationNumber = getLocationNumberFromMapUrlByXy(loc.href);
        const ownerName = container.querySelector("a[href^='pl_info.php?id=']").querySelector("b").innerText;
        const hostName = hostDiv.querySelector("b").innerText;
        let maxExpireDate;
        const guestRoomDivs = container.querySelectorAll("div[id^='gr']");
        for(const guestRoomDiv of guestRoomDivs) {
            const playerReference = guestRoomDiv.querySelector(`a[href='pl_info.php?id=${PlayerId}']`)
            if(playerReference && playerReference.nextSibling) {
                const re = /(\d{2}:\d{2} \d{2}-\d{2})/.exec(playerReference.nextSibling.nodeValue);
                if(re) {
                    const expireDate = parseDate(re[1], true);
                    if(!maxExpireDate || maxExpireDate < expireDate) {
                        maxExpireDate = expireDate;
                    }
                }
            }
        }
        const guestInfo = getGuestInfo(locationNumber);
        if(maxExpireDate) {
            guestInfo[houseId] = { HostInfo: `${ownerName} (${hostName})`, ExpireDate: maxExpireDate.toJSON() };
        }
        GM_setValue(`GuestInfo${locationNumber}`, JSON.stringify(guestInfo));
        //console.log(guestInfo);
    }
}
function getGuestInfo(locationNumber) {
    if(GM_getValue(`GuestInfo${locationNumber}`)) {
        const now = new Date();
        const guestInfo = JSON.parse(GM_getValue(`GuestInfo${locationNumber}`));
        for(const guestInfoKey of Object.keys(guestInfo)) {
            if(new Date(guestInfo[guestInfoKey].ExpireDate) < now) {
                var repackNeeded = true;
                delete guestInfo[guestInfoKey];
            }
        }
        const newKeys = Object.keys(guestInfo);
        if(repackNeeded) {
            if(newKeys.length == 0) {
                GM_deleteValue(`GuestInfo${locationNumber}`);
            } else {
                GM_setValue(`GuestInfo${locationNumber}`, JSON.stringify(guestInfo));
            }
        }
        if(newKeys.length > 0) {
            return guestInfo;
        }
    }
    return {};
}

function parseDate(dateString, isFuture = false) {
    //console.log(dateString)
    if(!dateString) {
        return;
    }
    const dateStrings = dateString.split(" ");
    
    let hours = 0;
    let minutes = 0;
    let seconds = 0;
    const timePart = dateStrings.find(x => x.includes(":"));
    if(timePart) {
        const time = timePart.split(":");
        hours = parseInt(time[0]);
        minutes = parseInt(time[1]);
        if(time.length > 2) {
            seconds = parseInt(time[2]);
        }
    }

    const now = new Date();
    let year = now.getFullYear();
    let month = now.getMonth();
    let day = now.getDate();
    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) {
            year = isEn ? parseInt(date[0]) : parseInt(date[2]);
            if(year < 1000) {
                year += Math.floor((new Date()).getFullYear() / 1000) * 1000;
            }
        } else {
            if(isFuture && month == 0 && now.getMonth() == 11) {
                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 addElement(type, parent, data, insertFirst = false) {
    let el = createElement(type, data);
    if(parent) {
        if(insertFirst) {
            parent.insertBefore(el, parent.firstChild);
        } else {
            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 getHorseImageData() { return "data:image/png;base64,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"; }
function getUrlParamValue(url, paramName) { return (new URLSearchParams(url.split("?")[1])).get(paramName); }
function getURL(url) { window.location.href = url; }
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;
}
async function loadFactories() {
    let selectedJobDiv = document.querySelector("div.job_fl_btn.show_hint.job_fl_btn_selected");
    if(!selectedJobDiv) {
        return; // Мы в пути
    }
    let currentFactoryType = getUrlParamValue(selectedJobDiv.parentNode.href, "st");
    const viewingLocationNumber = getLocationNumberFromMapUrlByXy(selectedJobDiv.parentNode.href);
    let indexOfCurrentFactoryType = factoryTypes.indexOf(currentFactoryType);
    if(indexOfCurrentFactoryType == -1) {
        return;
    }
    const mapRightBlock = document.getElementById("map_right_block");
    //const mapRightBlockInside = document.getElementById("map_right_block_inside");
    //console.log(`${mapRightBlock.id}, offsetHeight: ${mapRightBlock.offsetHeight}, height: ${mapRightBlock.style.height}`);
    //console.log(`${mapRightBlockInside.id}, offsetHeight: ${mapRightBlockInside.offsetHeight}, height: ${mapRightBlockInside.style.height}`);
    let mapRightBlockHeight = mapRightBlock.offsetHeight;
    let mainFactoriesTable = mapRightBlock.querySelector("table.wb");
    if(!mainFactoriesTable) {
        return;
    }
    let mapRef = document.querySelector("a[href^='map.php?cx=']");
    let x = getUrlParamValue(mapRef.href, "cx");
    let y = getUrlParamValue(mapRef.href, "cy");
    let factoriesTableContainer = mainFactoriesTable.parentNode;
    const factoryTables = { };
    factoryTables[currentFactoryType] = mainFactoriesTable;
    for(const factoryType of factoryTypes) {
        if(factoryType != currentFactoryType && gmGetBool("Load" + factoryType) && GM_getValue(`LastDetectedEmptyFactoriesDate_${factoryType}${viewingLocationNumber}`, 0) + 1000 * 60 * 60 * 24 * 30 < Date.now()) {
            const doc = await getRequest(`map.php?cx=${x}&cy=${y}&st=${factoryType}`);
            const docMapRightBlock = doc.getElementById("map_right_block");
            factoryTables[factoryType] = docMapRightBlock.querySelector("table.wb");
        }
    }
    for(let i = 0; i < factoryTypes.length; i++) {
        let tableElement = factoryTables[factoryTypes[i]];
        if(!tableElement) {
            continue;
        }
        const isTableEmpty = tableElement.rows.length <= 2 && (tableElement.rows.length < 2 || tableElement.rows[1].cells.length == 0);
        if(isTableEmpty) {
            GM_setValue(`LastDetectedEmptyFactoriesDate_${factoryTypes[i]}${viewingLocationNumber}`, Date.now());
        } else if(GM_getValue(`LastDetectedEmptyFactoriesDate_${factoryTypes[i]}${viewingLocationNumber}`)) {
            GM_deleteValue(`LastDetectedEmptyFactoriesDate_${factoryTypes[i]}${viewingLocationNumber}`);
        }
        if(!isTableEmpty) {
            if(indexOfCurrentFactoryType < i) {
                factoriesTableContainer.appendChild(tableElement)
                mapRightBlockHeight += tableElement.offsetHeight;
            }
            if(indexOfCurrentFactoryType > i) {
                factoriesTableContainer.insertBefore(tableElement, mainFactoriesTable);
                mapRightBlockHeight += tableElement.offsetHeight;
            }
        } else if(indexOfCurrentFactoryType == i) {
            mapRightBlockHeight -= mainFactoriesTable.offsetHeight;
            factoriesTableContainer.removeChild(mainFactoriesTable); // Если текущий список предприятий - пустой, то уберем его
        }
    }
    mapRightBlock.style.height = `${mapRightBlockHeight}px`;
}
function ClientHeight() { return document.compatMode == 'CSS1Compat' && document.documentElement ? document.documentElement.clientHeight : document.body.clientHeight; }
function ShowBigData(data) { addElement("TEXTAREA", document.body, { innerHTML: data }); }
function getParent(element, parentType, number = 1) {
    let result = element;
    let foundNumber = 0;
    while(result = result.parentNode) {
        if(result.nodeName.toLowerCase() == parentType.toLowerCase()) {
            foundNumber++;
            if(foundNumber == number) {
                return result;
            }
        }
    }
}
function observe(target, handler, config = { childList: true, subtree: true }) {
    const ob = new MutationObserver(async function(mut, observer) {
        //console.log(`Mutation start`);
        observer.disconnect();
        if(handler.constructor.name === 'AsyncFunction') {
            await handler();
        } else {
            handler();
        }
        observer.observe(target, config);
    });
    ob.observe(target, config);
}
async function initUserName() {
    if(!GM_getValue("TransporterUserName")) {
        const doc = await getRequest(`/pl_info.php?id=${PlayerId}`);    
        GM_setValue("TransporterUserName", doc.querySelector("h1").innerText);
    }
}